From d7edb3ea7c9b813b8794e62a2c1c5078e881e1cb Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Tue, 25 Mar 2025 13:32:33 +0400 Subject: Feat: Moved majority of the code to `arm` module. Reasoning: 1. Majority of code assumes the usage of `Intrinsic` and related types, which is derived from the JSON structure of the ARM intrinsics JSON source file 2. Further commits will start with extracting common parts of the code (eg: Create C/Rust file, Build C/Rust file, etc) --- .../stdarch/crates/intrinsic-test/src/argument.rs | 274 -------- .../crates/intrinsic-test/src/arm/argument.rs | 274 ++++++++ .../crates/intrinsic-test/src/arm/format.rs | 22 + .../crates/intrinsic-test/src/arm/intrinsic.rs | 152 +++++ .../crates/intrinsic-test/src/arm/json_parser.rs | 99 +++ .../stdarch/crates/intrinsic-test/src/arm/mod.rs | 665 ++++++++++++++++++ .../stdarch/crates/intrinsic-test/src/arm/types.rs | 508 ++++++++++++++ .../crates/intrinsic-test/src/common/mod.rs | 2 + .../crates/intrinsic-test/src/common/types.rs | 5 + .../crates/intrinsic-test/src/common/values.rs | 120 ++++ .../stdarch/crates/intrinsic-test/src/format.rs | 22 - .../stdarch/crates/intrinsic-test/src/intrinsic.rs | 152 ----- .../crates/intrinsic-test/src/json_parser.rs | 99 --- library/stdarch/crates/intrinsic-test/src/main.rs | 758 +-------------------- library/stdarch/crates/intrinsic-test/src/types.rs | 508 -------------- .../stdarch/crates/intrinsic-test/src/values.rs | 120 ---- 16 files changed, 1850 insertions(+), 1930 deletions(-) delete mode 100644 library/stdarch/crates/intrinsic-test/src/argument.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/arm/argument.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/arm/format.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/arm/intrinsic.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/arm/json_parser.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/arm/mod.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/arm/types.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/common/mod.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/common/types.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/common/values.rs delete mode 100644 library/stdarch/crates/intrinsic-test/src/format.rs delete mode 100644 library/stdarch/crates/intrinsic-test/src/intrinsic.rs delete mode 100644 library/stdarch/crates/intrinsic-test/src/json_parser.rs delete mode 100644 library/stdarch/crates/intrinsic-test/src/types.rs delete mode 100644 library/stdarch/crates/intrinsic-test/src/values.rs (limited to 'library/stdarch/crates') diff --git a/library/stdarch/crates/intrinsic-test/src/argument.rs b/library/stdarch/crates/intrinsic-test/src/argument.rs deleted file mode 100644 index 3011bbf4a36..00000000000 --- a/library/stdarch/crates/intrinsic-test/src/argument.rs +++ /dev/null @@ -1,274 +0,0 @@ -use std::ops::Range; - -use crate::Language; -use crate::format::Indentation; -use crate::json_parser::ArgPrep; -use crate::types::{IntrinsicType, TypeKind}; - -/// An argument for the intrinsic. -#[derive(Debug, PartialEq, Clone)] -pub struct Argument { - /// The argument's index in the intrinsic function call. - pub pos: usize, - /// The argument name. - pub name: String, - /// The type of the argument. - pub ty: IntrinsicType, - /// Any constraints that are on this argument - pub constraints: Vec, -} - -#[derive(Debug, PartialEq, Clone)] -pub enum Constraint { - Equal(i64), - Range(Range), -} - -impl TryFrom for Constraint { - type Error = (); - - fn try_from(prep: ArgPrep) -> Result { - let parsed_ints = match prep { - ArgPrep::Immediate { min, max } => Ok((min, max)), - _ => Err(()), - }; - if let Ok((min, max)) = parsed_ints { - if min == max { - Ok(Constraint::Equal(min)) - } else { - Ok(Constraint::Range(min..max + 1)) - } - } else { - Err(()) - } - } -} - -impl Constraint { - pub fn to_range(&self) -> Range { - match self { - Constraint::Equal(eq) => *eq..*eq + 1, - Constraint::Range(range) => range.clone(), - } - } -} - -impl Argument { - fn to_c_type(&self) -> String { - self.ty.c_type() - } - - fn is_simd(&self) -> bool { - self.ty.is_simd() - } - - pub fn is_ptr(&self) -> bool { - self.ty.is_ptr() - } - - pub fn has_constraint(&self) -> bool { - !self.constraints.is_empty() - } - - pub fn type_and_name_from_c(arg: &str) -> (&str, &str) { - let split_index = arg - .rfind([' ', '*']) - .expect("Couldn't split type and argname"); - - (arg[..split_index + 1].trim_end(), &arg[split_index + 1..]) - } - - pub fn from_c(pos: usize, arg: &str, arg_prep: Option) -> Argument { - let (ty, var_name) = Self::type_and_name_from_c(arg); - - let ty = IntrinsicType::from_c(ty) - .unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); - - let constraint = arg_prep.and_then(|a| a.try_into().ok()); - - Argument { - pos, - name: String::from(var_name), - ty, - constraints: constraint.map_or(vec![], |r| vec![r]), - } - } - - fn is_rust_vals_array_const(&self) -> bool { - use TypeKind::*; - match self.ty { - // Floats have to be loaded at runtime for stable NaN conversion. - IntrinsicType::Type { kind: Float, .. } => false, - IntrinsicType::Type { - kind: Int | UInt | Poly, - .. - } => true, - _ => unimplemented!(), - } - } - - /// The binding keyword (e.g. "const" or "let") for the array of possible test inputs. - pub fn rust_vals_array_binding(&self) -> impl std::fmt::Display { - if self.is_rust_vals_array_const() { - "const" - } else { - "let" - } - } - - /// The name (e.g. "A_VALS" or "a_vals") for the array of possible test inputs. - pub fn rust_vals_array_name(&self) -> impl std::fmt::Display { - if self.is_rust_vals_array_const() { - format!("{}_VALS", self.name.to_uppercase()) - } else { - format!("{}_vals", self.name.to_lowercase()) - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ArgumentList { - pub args: Vec, -} - -impl ArgumentList { - /// Converts the argument list into the call parameters for a C function call. - /// e.g. this would generate something like `a, &b, c` - pub fn as_call_param_c(&self) -> String { - self.args - .iter() - .map(|arg| match arg.ty { - IntrinsicType::Ptr { .. } => { - format!("&{}", arg.name) - } - IntrinsicType::Type { .. } => arg.name.clone(), - }) - .collect::>() - .join(", ") - } - - /// Converts the argument list into the call parameters for a Rust function. - /// e.g. this would generate something like `a, b, c` - pub fn as_call_param_rust(&self) -> String { - self.args - .iter() - .filter(|a| !a.has_constraint()) - .map(|arg| arg.name.clone()) - .collect::>() - .join(", ") - } - - pub fn as_constraint_parameters_rust(&self) -> String { - self.args - .iter() - .filter(|a| a.has_constraint()) - .map(|arg| arg.name.clone()) - .collect::>() - .join(", ") - } - - /// Creates a line for each argument that initializes an array for C from which `loads` argument - /// values can be loaded as a sliding window. - /// e.g `const int32x2_t a_vals = {0x3effffff, 0x3effffff, 0x3f7fffff}`, if loads=2. - pub fn gen_arglists_c(&self, indentation: Indentation, loads: u32) -> String { - self.iter() - .filter_map(|arg| { - (!arg.has_constraint()).then(|| { - format!( - "{indentation}const {ty} {name}_vals[] = {values};", - ty = arg.ty.c_scalar_type(), - name = arg.name, - values = arg.ty.populate_random(indentation, loads, &Language::C) - ) - }) - }) - .collect::>() - .join("\n") - } - - /// Creates a line for each argument that initializes an array for Rust from which `loads` argument - /// values can be loaded as a sliding window, e.g `const A_VALS: [u32; 20] = [...];` - pub fn gen_arglists_rust(&self, indentation: Indentation, loads: u32) -> String { - self.iter() - .filter_map(|arg| { - (!arg.has_constraint()).then(|| { - format!( - "{indentation}{bind} {name}: [{ty}; {load_size}] = {values};", - bind = arg.rust_vals_array_binding(), - name = arg.rust_vals_array_name(), - ty = arg.ty.rust_scalar_type(), - load_size = arg.ty.num_lanes() * arg.ty.num_vectors() + loads - 1, - values = arg.ty.populate_random(indentation, loads, &Language::Rust) - ) - }) - }) - .collect::>() - .join("\n") - } - - /// Creates a line for each argument that initializes the argument from an array `[arg]_vals` at - /// an offset `i` using a load intrinsic, in C. - /// e.g `uint8x8_t a = vld1_u8(&a_vals[i]);` - pub fn load_values_c(&self, indentation: Indentation, target: &str) -> String { - self.iter() - .filter_map(|arg| { - // The ACLE doesn't support 64-bit polynomial loads on Armv7 - // This and the cast are a workaround for this - let armv7_p64 = if let TypeKind::Poly = arg.ty.kind() { - target.contains("v7") - } else { - false - }; - - (!arg.has_constraint()).then(|| { - format!( - "{indentation}{ty} {name} = {open_cast}{load}(&{name}_vals[i]){close_cast};\n", - ty = arg.to_c_type(), - name = arg.name, - load = if arg.is_simd() { - arg.ty.get_load_function(armv7_p64) - } else { - "*".to_string() - }, - open_cast = if armv7_p64 { - format!("cast<{}>(", arg.to_c_type()) - } else { - "".to_string() - }, - close_cast = if armv7_p64 { - ")".to_string() - } else { - "".to_string() - } - ) - }) - }) - .collect() - } - - /// Creates a line for each argument that initializes the argument from array `[ARG]_VALS` at - /// an offset `i` using a load intrinsic, in Rust. - /// e.g `let a = vld1_u8(A_VALS.as_ptr().offset(i));` - pub fn load_values_rust(&self, indentation: Indentation) -> String { - self.iter() - .filter_map(|arg| { - (!arg.has_constraint()).then(|| { - format!( - "{indentation}let {name} = {load}({vals_name}.as_ptr().offset(i));\n", - name = arg.name, - vals_name = arg.rust_vals_array_name(), - load = if arg.is_simd() { - arg.ty.get_load_function(false) - } else { - "*".to_string() - }, - ) - }) - }) - .collect() - } - - pub fn iter(&self) -> std::slice::Iter<'_, Argument> { - self.args.iter() - } -} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/argument.rs b/library/stdarch/crates/intrinsic-test/src/arm/argument.rs new file mode 100644 index 00000000000..adc93da37f4 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/argument.rs @@ -0,0 +1,274 @@ +use std::ops::Range; + +use super::format::Indentation; +use super::json_parser::ArgPrep; +use super::types::{IntrinsicType, TypeKind}; +use crate::common::types::Language; + +/// An argument for the intrinsic. +#[derive(Debug, PartialEq, Clone)] +pub struct Argument { + /// The argument's index in the intrinsic function call. + pub pos: usize, + /// The argument name. + pub name: String, + /// The type of the argument. + pub ty: IntrinsicType, + /// Any constraints that are on this argument + pub constraints: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Constraint { + Equal(i64), + Range(Range), +} + +impl TryFrom for Constraint { + type Error = (); + + fn try_from(prep: ArgPrep) -> Result { + let parsed_ints = match prep { + ArgPrep::Immediate { min, max } => Ok((min, max)), + _ => Err(()), + }; + if let Ok((min, max)) = parsed_ints { + if min == max { + Ok(Constraint::Equal(min)) + } else { + Ok(Constraint::Range(min..max + 1)) + } + } else { + Err(()) + } + } +} + +impl Constraint { + pub fn to_range(&self) -> Range { + match self { + Constraint::Equal(eq) => *eq..*eq + 1, + Constraint::Range(range) => range.clone(), + } + } +} + +impl Argument { + fn to_c_type(&self) -> String { + self.ty.c_type() + } + + fn is_simd(&self) -> bool { + self.ty.is_simd() + } + + pub fn is_ptr(&self) -> bool { + self.ty.is_ptr() + } + + pub fn has_constraint(&self) -> bool { + !self.constraints.is_empty() + } + + pub fn type_and_name_from_c(arg: &str) -> (&str, &str) { + let split_index = arg + .rfind([' ', '*']) + .expect("Couldn't split type and argname"); + + (arg[..split_index + 1].trim_end(), &arg[split_index + 1..]) + } + + pub fn from_c(pos: usize, arg: &str, arg_prep: Option) -> Argument { + let (ty, var_name) = Self::type_and_name_from_c(arg); + + let ty = IntrinsicType::from_c(ty) + .unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); + + let constraint = arg_prep.and_then(|a| a.try_into().ok()); + + Argument { + pos, + name: String::from(var_name), + ty, + constraints: constraint.map_or(vec![], |r| vec![r]), + } + } + + fn is_rust_vals_array_const(&self) -> bool { + use TypeKind::*; + match self.ty { + // Floats have to be loaded at runtime for stable NaN conversion. + IntrinsicType::Type { kind: Float, .. } => false, + IntrinsicType::Type { + kind: Int | UInt | Poly, + .. + } => true, + _ => unimplemented!(), + } + } + + /// The binding keyword (e.g. "const" or "let") for the array of possible test inputs. + pub fn rust_vals_array_binding(&self) -> impl std::fmt::Display { + if self.is_rust_vals_array_const() { + "const" + } else { + "let" + } + } + + /// The name (e.g. "A_VALS" or "a_vals") for the array of possible test inputs. + pub fn rust_vals_array_name(&self) -> impl std::fmt::Display { + if self.is_rust_vals_array_const() { + format!("{}_VALS", self.name.to_uppercase()) + } else { + format!("{}_vals", self.name.to_lowercase()) + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ArgumentList { + pub args: Vec, +} + +impl ArgumentList { + /// Converts the argument list into the call parameters for a C function call. + /// e.g. this would generate something like `a, &b, c` + pub fn as_call_param_c(&self) -> String { + self.args + .iter() + .map(|arg| match arg.ty { + IntrinsicType::Ptr { .. } => { + format!("&{}", arg.name) + } + IntrinsicType::Type { .. } => arg.name.clone(), + }) + .collect::>() + .join(", ") + } + + /// Converts the argument list into the call parameters for a Rust function. + /// e.g. this would generate something like `a, b, c` + pub fn as_call_param_rust(&self) -> String { + self.args + .iter() + .filter(|a| !a.has_constraint()) + .map(|arg| arg.name.clone()) + .collect::>() + .join(", ") + } + + pub fn as_constraint_parameters_rust(&self) -> String { + self.args + .iter() + .filter(|a| a.has_constraint()) + .map(|arg| arg.name.clone()) + .collect::>() + .join(", ") + } + + /// Creates a line for each argument that initializes an array for C from which `loads` argument + /// values can be loaded as a sliding window. + /// e.g `const int32x2_t a_vals = {0x3effffff, 0x3effffff, 0x3f7fffff}`, if loads=2. + pub fn gen_arglists_c(&self, indentation: Indentation, loads: u32) -> String { + self.iter() + .filter_map(|arg| { + (!arg.has_constraint()).then(|| { + format!( + "{indentation}const {ty} {name}_vals[] = {values};", + ty = arg.ty.c_scalar_type(), + name = arg.name, + values = arg.ty.populate_random(indentation, loads, &Language::C) + ) + }) + }) + .collect::>() + .join("\n") + } + + /// Creates a line for each argument that initializes an array for Rust from which `loads` argument + /// values can be loaded as a sliding window, e.g `const A_VALS: [u32; 20] = [...];` + pub fn gen_arglists_rust(&self, indentation: Indentation, loads: u32) -> String { + self.iter() + .filter_map(|arg| { + (!arg.has_constraint()).then(|| { + format!( + "{indentation}{bind} {name}: [{ty}; {load_size}] = {values};", + bind = arg.rust_vals_array_binding(), + name = arg.rust_vals_array_name(), + ty = arg.ty.rust_scalar_type(), + load_size = arg.ty.num_lanes() * arg.ty.num_vectors() + loads - 1, + values = arg.ty.populate_random(indentation, loads, &Language::Rust) + ) + }) + }) + .collect::>() + .join("\n") + } + + /// Creates a line for each argument that initializes the argument from an array `[arg]_vals` at + /// an offset `i` using a load intrinsic, in C. + /// e.g `uint8x8_t a = vld1_u8(&a_vals[i]);` + pub fn load_values_c(&self, indentation: Indentation, target: &str) -> String { + self.iter() + .filter_map(|arg| { + // The ACLE doesn't support 64-bit polynomial loads on Armv7 + // This and the cast are a workaround for this + let armv7_p64 = if let TypeKind::Poly = arg.ty.kind() { + target.contains("v7") + } else { + false + }; + + (!arg.has_constraint()).then(|| { + format!( + "{indentation}{ty} {name} = {open_cast}{load}(&{name}_vals[i]){close_cast};\n", + ty = arg.to_c_type(), + name = arg.name, + load = if arg.is_simd() { + arg.ty.get_load_function(armv7_p64) + } else { + "*".to_string() + }, + open_cast = if armv7_p64 { + format!("cast<{}>(", arg.to_c_type()) + } else { + "".to_string() + }, + close_cast = if armv7_p64 { + ")".to_string() + } else { + "".to_string() + } + ) + }) + }) + .collect() + } + + /// Creates a line for each argument that initializes the argument from array `[ARG]_VALS` at + /// an offset `i` using a load intrinsic, in Rust. + /// e.g `let a = vld1_u8(A_VALS.as_ptr().offset(i));` + pub fn load_values_rust(&self, indentation: Indentation) -> String { + self.iter() + .filter_map(|arg| { + (!arg.has_constraint()).then(|| { + format!( + "{indentation}let {name} = {load}({vals_name}.as_ptr().offset(i));\n", + name = arg.name, + vals_name = arg.rust_vals_array_name(), + load = if arg.is_simd() { + arg.ty.get_load_function(false) + } else { + "*".to_string() + }, + ) + }) + }) + .collect() + } + + pub fn iter(&self) -> std::slice::Iter<'_, Argument> { + self.args.iter() + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/format.rs b/library/stdarch/crates/intrinsic-test/src/arm/format.rs new file mode 100644 index 00000000000..9ee331d7f7a --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/format.rs @@ -0,0 +1,22 @@ +//! Basic code formatting tools. +//! +//! We don't need perfect formatting for the generated tests, but simple indentation can make +//! debugging a lot easier. + +#[derive(Copy, Clone, Debug, Default)] +pub struct Indentation(u32); + +impl Indentation { + pub fn nested(self) -> Self { + Self(self.0 + 1) + } +} + +impl std::fmt::Display for Indentation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for _ in 0..self.0 { + write!(f, " ")?; + } + Ok(()) + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/intrinsic.rs b/library/stdarch/crates/intrinsic-test/src/arm/intrinsic.rs new file mode 100644 index 00000000000..a650707f4db --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/intrinsic.rs @@ -0,0 +1,152 @@ +use super::format::Indentation; +use super::types::{IntrinsicType, TypeKind}; + +use super::argument::ArgumentList; + +/// An intrinsic +#[derive(Debug, PartialEq, Clone)] +pub struct Intrinsic { + /// The function name of this intrinsic. + pub name: String, + + /// Any arguments for this intrinsic. + pub arguments: ArgumentList, + + /// The return type of this intrinsic. + pub results: IntrinsicType, + + /// Whether this intrinsic is only available on A64. + pub a64_only: bool, +} + +impl Intrinsic { + /// Generates a std::cout for the intrinsics results that will match the + /// rust debug output format for the return type. The generated line assumes + /// there is an int i in scope which is the current pass number. + pub fn print_result_c(&self, indentation: Indentation, additional: &str) -> String { + let lanes = if self.results.num_vectors() > 1 { + (0..self.results.num_vectors()) + .map(|vector| { + format!( + r#""{ty}(" << {lanes} << ")""#, + ty = self.results.c_single_vector_type(), + lanes = (0..self.results.num_lanes()) + .map(move |idx| -> std::string::String { + format!( + "{cast}{lane_fn}(__return_value.val[{vector}], {lane})", + cast = self.results.c_promotion(), + lane_fn = self.results.get_lane_function(), + lane = idx, + vector = vector, + ) + }) + .collect::>() + .join(r#" << ", " << "#) + ) + }) + .collect::>() + .join(r#" << ", " << "#) + } else if self.results.num_lanes() > 1 { + (0..self.results.num_lanes()) + .map(|idx| -> std::string::String { + format!( + "{cast}{lane_fn}(__return_value, {lane})", + cast = self.results.c_promotion(), + lane_fn = self.results.get_lane_function(), + lane = idx + ) + }) + .collect::>() + .join(r#" << ", " << "#) + } else { + format!( + "{promote}cast<{cast}>(__return_value)", + cast = match self.results.kind() { + TypeKind::Float if self.results.inner_size() == 16 => "float16_t".to_string(), + TypeKind::Float if self.results.inner_size() == 32 => "float".to_string(), + TypeKind::Float if self.results.inner_size() == 64 => "double".to_string(), + TypeKind::Int => format!("int{}_t", self.results.inner_size()), + TypeKind::UInt => format!("uint{}_t", self.results.inner_size()), + TypeKind::Poly => format!("poly{}_t", self.results.inner_size()), + ty => todo!("print_result_c - Unknown type: {:#?}", ty), + }, + promote = self.results.c_promotion(), + ) + }; + + format!( + r#"{indentation}std::cout << "Result {additional}-" << i+1 << ": {ty}" << std::fixed << std::setprecision(150) << {lanes} << "{close}" << std::endl;"#, + ty = if self.results.is_simd() { + format!("{}(", self.results.c_type()) + } else { + String::from("") + }, + close = if self.results.is_simd() { ")" } else { "" }, + lanes = lanes, + additional = additional, + ) + } + + pub fn generate_loop_c( + &self, + indentation: Indentation, + additional: &str, + passes: u32, + target: &str, + ) -> String { + let body_indentation = indentation.nested(); + format!( + "{indentation}for (int i=0; i<{passes}; i++) {{\n\ + {loaded_args}\ + {body_indentation}auto __return_value = {intrinsic_call}({args});\n\ + {print_result}\n\ + {indentation}}}", + loaded_args = self.arguments.load_values_c(body_indentation, target), + intrinsic_call = self.name, + args = self.arguments.as_call_param_c(), + print_result = self.print_result_c(body_indentation, additional) + ) + } + + pub fn generate_loop_rust( + &self, + indentation: Indentation, + additional: &str, + passes: u32, + ) -> String { + let constraints = self.arguments.as_constraint_parameters_rust(); + let constraints = if !constraints.is_empty() { + format!("::<{constraints}>") + } else { + constraints + }; + + // the `intrinsic-test` crate compares the output of C and Rust intrinsics. Currently, It uses + // a string representation of the output value to compare. In C, f16 values are currently printed + // as hexadecimal integers. Since https://github.com/rust-lang/rust/pull/127013, rust does print + // them as decimal floating point values. To keep the intrinsics tests working, for now, format + // vectors containing f16 values like C prints them. + let return_value = match self.results.kind() { + TypeKind::Float if self.results.inner_size() == 16 => "debug_f16(__return_value)", + _ => "format_args!(\"{__return_value:.150?}\")", + }; + + let indentation2 = indentation.nested(); + let indentation3 = indentation2.nested(); + + format!( + "{indentation}for i in 0..{passes} {{\n\ + {indentation2}unsafe {{\n\ + {loaded_args}\ + {indentation3}let __return_value = {intrinsic_call}{const}({args});\n\ + {indentation3}println!(\"Result {additional}-{{}}: {{:?}}\", i + 1, {return_value});\n\ + {indentation2}}}\n\ + {indentation}}}", + loaded_args = self.arguments.load_values_rust(indentation3), + intrinsic_call = self.name, + const = constraints, + args = self.arguments.as_call_param_rust(), + additional = additional, + ) + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/json_parser.rs b/library/stdarch/crates/intrinsic-test/src/arm/json_parser.rs new file mode 100644 index 00000000000..32bb10e6f69 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/json_parser.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use std::path::Path; + +use serde::Deserialize; + +use super::argument::{Argument, ArgumentList}; +use super::intrinsic::Intrinsic; +use super::types::IntrinsicType; + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +struct ReturnType { + value: String, +} + +#[derive(Deserialize, Debug)] +#[serde(untagged, deny_unknown_fields)] +pub enum ArgPrep { + Register { + #[serde(rename = "register")] + #[allow(dead_code)] + reg: String, + }, + Immediate { + #[serde(rename = "minimum")] + min: i64, + #[serde(rename = "maximum")] + max: i64, + }, + Nothing {}, +} + +#[derive(Deserialize, Debug)] +struct JsonIntrinsic { + #[serde(rename = "SIMD_ISA")] + simd_isa: String, + name: String, + arguments: Vec, + return_type: ReturnType, + #[serde(rename = "Arguments_Preparation")] + args_prep: Option>, + #[serde(rename = "Architectures")] + architectures: Vec, +} + +pub fn get_neon_intrinsics(filename: &Path) -> Result, Box> { + let file = std::fs::File::open(filename)?; + let reader = std::io::BufReader::new(file); + let json: Vec = serde_json::from_reader(reader).expect("Couldn't parse JSON"); + + let parsed = json + .into_iter() + .filter_map(|intr| { + if intr.simd_isa == "Neon" { + Some(json_to_intrinsic(intr).expect("Couldn't parse JSON")) + } else { + None + } + }) + .collect(); + Ok(parsed) +} + +fn json_to_intrinsic(mut intr: JsonIntrinsic) -> Result> { + let name = intr.name.replace(['[', ']'], ""); + + let results = IntrinsicType::from_c(&intr.return_type.value)?; + + let mut args_prep = intr.args_prep.as_mut(); + let args = intr + .arguments + .into_iter() + .enumerate() + .map(|(i, arg)| { + let arg_name = Argument::type_and_name_from_c(&arg).1; + let arg_prep = args_prep.as_mut().and_then(|a| a.remove(arg_name)); + let mut arg = Argument::from_c(i, &arg, arg_prep); + // The JSON doesn't list immediates as const + if let IntrinsicType::Type { + ref mut constant, .. + } = arg.ty + { + if arg.name.starts_with("imm") { + *constant = true + } + } + arg + }) + .collect(); + + let arguments = ArgumentList { args }; + + Ok(Intrinsic { + name, + arguments, + results, + a64_only: intr.architectures == vec!["A64".to_string()], + }) +} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/mod.rs b/library/stdarch/crates/intrinsic-test/src/arm/mod.rs new file mode 100644 index 00000000000..2d1846e14eb --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/mod.rs @@ -0,0 +1,665 @@ +pub(crate) mod argument; +pub(crate) mod format; +pub(crate) mod intrinsic; +pub(crate) mod json_parser; +pub(crate) mod types; + +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; +use std::process::Command; + +use intrinsic::Intrinsic; +use itertools::Itertools; +use rayon::prelude::*; +use types::TypeKind; + +use argument::Argument; +use format::Indentation; +use json_parser::get_neon_intrinsics; + +// The number of times each intrinsic will be called. +const PASSES: u32 = 20; + +fn gen_code_c( + indentation: Indentation, + intrinsic: &Intrinsic, + constraints: &[&Argument], + name: String, + target: &str, +) -> String { + if let Some((current, constraints)) = constraints.split_last() { + let range = current + .constraints + .iter() + .map(|c| c.to_range()) + .flat_map(|r| r.into_iter()); + + let body_indentation = indentation.nested(); + range + .map(|i| { + format!( + "{indentation}{{\n\ + {body_indentation}{ty} {name} = {val};\n\ + {pass}\n\ + {indentation}}}", + name = current.name, + ty = current.ty.c_type(), + val = i, + pass = gen_code_c( + body_indentation, + intrinsic, + constraints, + format!("{name}-{i}"), + target, + ) + ) + }) + .join("\n") + } else { + intrinsic.generate_loop_c(indentation, &name, PASSES, target) + } +} + +fn generate_c_program( + notices: &str, + header_files: &[&str], + intrinsic: &Intrinsic, + target: &str, +) -> String { + let constraints = intrinsic + .arguments + .iter() + .filter(|i| i.has_constraint()) + .collect_vec(); + + let indentation = Indentation::default(); + format!( + r#"{notices}{header_files} +#include +#include +#include +#include + +template T1 cast(T2 x) {{ + static_assert(sizeof(T1) == sizeof(T2), "sizeof T1 and T2 must be the same"); + T1 ret{{}}; + memcpy(&ret, &x, sizeof(T1)); + return ret; +}} + +#ifdef __aarch64__ +std::ostream& operator<<(std::ostream& os, poly128_t value) {{ + std::stringstream temp; + do {{ + int n = value % 10; + value /= 10; + temp << n; + }} while (value != 0); + std::string tempstr(temp.str()); + std::string res(tempstr.rbegin(), tempstr.rend()); + os << res; + return os; +}} +#endif + +std::ostream& operator<<(std::ostream& os, float16_t value) {{ + uint16_t temp = 0; + memcpy(&temp, &value, sizeof(float16_t)); + std::stringstream ss; + ss << "0x" << std::setfill('0') << std::setw(4) << std::hex << temp; + os << ss.str(); + return os; +}} + +{arglists} + +int main(int argc, char **argv) {{ +{passes} + return 0; +}}"#, + header_files = header_files + .iter() + .map(|header| format!("#include <{header}>")) + .collect::>() + .join("\n"), + arglists = intrinsic.arguments.gen_arglists_c(indentation, PASSES), + passes = gen_code_c( + indentation.nested(), + intrinsic, + constraints.as_slice(), + Default::default(), + target, + ), + ) +} + +fn gen_code_rust( + indentation: Indentation, + intrinsic: &Intrinsic, + constraints: &[&Argument], + name: String, +) -> String { + if let Some((current, constraints)) = constraints.split_last() { + let range = current + .constraints + .iter() + .map(|c| c.to_range()) + .flat_map(|r| r.into_iter()); + + let body_indentation = indentation.nested(); + range + .map(|i| { + format!( + "{indentation}{{\n\ + {body_indentation}const {name}: {ty} = {val};\n\ + {pass}\n\ + {indentation}}}", + name = current.name, + ty = current.ty.rust_type(), + val = i, + pass = gen_code_rust( + body_indentation, + intrinsic, + constraints, + format!("{name}-{i}") + ) + ) + }) + .join("\n") + } else { + intrinsic.generate_loop_rust(indentation, &name, PASSES) + } +} + +fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, target: &str) -> String { + let constraints = intrinsic + .arguments + .iter() + .filter(|i| i.has_constraint()) + .collect_vec(); + + let indentation = Indentation::default(); + format!( + r#"{notices}#![feature(simd_ffi)] +#![feature(link_llvm_intrinsics)] +#![feature(f16)] +#![cfg_attr(target_arch = "arm", feature(stdarch_arm_neon_intrinsics))] +#![cfg_attr(target_arch = "arm", feature(stdarch_aarch32_crc32))] +#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_fcma))] +#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_dotprod))] +#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_i8mm))] +#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_sha3))] +#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_sm4))] +#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_ftts))] +#![feature(stdarch_neon_f16)] +#![allow(non_upper_case_globals)] +use core_arch::arch::{target_arch}::*; + +fn main() {{ +{arglists} +{passes} +}} +"#, + target_arch = if target.contains("v7") { + "arm" + } else { + "aarch64" + }, + arglists = intrinsic + .arguments + .gen_arglists_rust(indentation.nested(), PASSES), + passes = gen_code_rust( + indentation.nested(), + intrinsic, + &constraints, + Default::default() + ) + ) +} + +fn compile_c( + c_filename: &str, + intrinsic: &Intrinsic, + compiler: &str, + target: &str, + cxx_toolchain_dir: Option<&str>, +) -> bool { + let flags = std::env::var("CPPFLAGS").unwrap_or("".into()); + let arch_flags = if target.contains("v7") { + "-march=armv8.6-a+crypto+crc+dotprod+fp16" + } else { + "-march=armv8.6-a+crypto+sha3+crc+dotprod+fp16+faminmax+lut" + }; + + let intrinsic_name = &intrinsic.name; + + let compiler_command = if target == "aarch64_be-unknown-linux-gnu" { + let Some(cxx_toolchain_dir) = cxx_toolchain_dir else { + panic!( + "When setting `--target aarch64_be-unknown-linux-gnu` the C++ compilers toolchain directory must be set with `--cxx-toolchain-dir `" + ); + }; + + /* clang++ cannot link an aarch64_be object file, so we invoke + * aarch64_be-unknown-linux-gnu's C++ linker. This ensures that we + * are testing the intrinsics against LLVM. + * + * Note: setting `--sysroot=<...>` which is the obvious thing to do + * does not work as it gets caught up with `#include_next ` + * not existing... */ + format!( + "{compiler} {flags} {arch_flags} \ + -ffp-contract=off \ + -Wno-narrowing \ + -O2 \ + --target=aarch64_be-unknown-linux-gnu \ + -I{cxx_toolchain_dir}/include \ + -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include \ + -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1 \ + -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1/aarch64_be-none-linux-gnu \ + -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1/backward \ + -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/libc/usr/include \ + -c {c_filename} \ + -o c_programs/{intrinsic_name}.o && \ + {cxx_toolchain_dir}/bin/aarch64_be-none-linux-gnu-g++ c_programs/{intrinsic_name}.o -o c_programs/{intrinsic_name} && \ + rm c_programs/{intrinsic_name}.o", + ) + } else { + // -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations + let base_compiler_command = format!( + "{compiler} {flags} {arch_flags} -o c_programs/{intrinsic_name} {c_filename} -ffp-contract=off -Wno-narrowing -O2" + ); + + /* `-target` can be passed to some c++ compilers, however if we want to + * use a c++ compiler does not support this flag we do not want to pass + * the flag. */ + if compiler.contains("clang") { + format!("{base_compiler_command} -target {target}") + } else { + format!("{base_compiler_command} -flax-vector-conversions") + } + }; + + let output = Command::new("sh").arg("-c").arg(compiler_command).output(); + if let Ok(output) = output { + if output.status.success() { + true + } else { + error!( + "Failed to compile code for intrinsic: {}\n\nstdout:\n{}\n\nstderr:\n{}", + intrinsic.name, + std::str::from_utf8(&output.stdout).unwrap_or(""), + std::str::from_utf8(&output.stderr).unwrap_or("") + ); + false + } + } else { + error!("Command failed: {:#?}", output); + false + } +} + +fn build_notices(line_prefix: &str) -> String { + format!( + "\ +{line_prefix}This is a transient test file, not intended for distribution. Some aspects of the +{line_prefix}test are derived from a JSON specification, published under the same license as the +{line_prefix}`intrinsic-test` crate.\n +" + ) +} + +fn build_c( + notices: &str, + intrinsics: &Vec, + compiler: Option<&str>, + target: &str, + cxx_toolchain_dir: Option<&str>, +) -> bool { + let _ = std::fs::create_dir("c_programs"); + intrinsics + .par_iter() + .map(|i| { + let c_filename = format!(r#"c_programs/{}.cpp"#, i.name); + let mut file = File::create(&c_filename).unwrap(); + + let c_code = generate_c_program( + notices, + &["arm_neon.h", "arm_acle.h", "arm_fp16.h"], + i, + target, + ); + file.write_all(c_code.into_bytes().as_slice()).unwrap(); + match compiler { + None => true, + Some(compiler) => compile_c(&c_filename, i, compiler, target, cxx_toolchain_dir), + } + }) + .find_any(|x| !x) + .is_none() +} + +fn build_rust( + notices: &str, + intrinsics: &[Intrinsic], + toolchain: Option<&str>, + target: &str, + linker: Option<&str>, +) -> bool { + intrinsics.iter().for_each(|i| { + let rust_dir = format!(r#"rust_programs/{}"#, i.name); + let _ = std::fs::create_dir_all(&rust_dir); + let rust_filename = format!(r#"{rust_dir}/main.rs"#); + let mut file = File::create(&rust_filename).unwrap(); + + let c_code = generate_rust_program(notices, i, target); + file.write_all(c_code.into_bytes().as_slice()).unwrap(); + }); + + let mut cargo = File::create("rust_programs/Cargo.toml").unwrap(); + cargo + .write_all( + format!( + r#"[package] +name = "intrinsic-test-programs" +version = "{version}" +authors = [{authors}] +license = "{license}" +edition = "2018" +[workspace] +[dependencies] +core_arch = {{ path = "../crates/core_arch" }} +{binaries}"#, + version = env!("CARGO_PKG_VERSION"), + authors = env!("CARGO_PKG_AUTHORS") + .split(":") + .format_with(", ", |author, fmt| fmt(&format_args!("\"{author}\""))), + license = env!("CARGO_PKG_LICENSE"), + binaries = intrinsics + .iter() + .map(|i| { + format!( + r#"[[bin]] +name = "{intrinsic}" +path = "{intrinsic}/main.rs""#, + intrinsic = i.name + ) + }) + .collect::>() + .join("\n") + ) + .into_bytes() + .as_slice(), + ) + .unwrap(); + + let toolchain = match toolchain { + None => return true, + Some(t) => t, + }; + + /* If there has been a linker explicitly set from the command line then + * we want to set it via setting it in the RUSTFLAGS*/ + + let cargo_command = format!( + "cargo {toolchain} build --target {target} --release", + toolchain = toolchain, + target = target + ); + + let mut command = Command::new("sh"); + command + .current_dir("rust_programs") + .arg("-c") + .arg(cargo_command); + + let mut rust_flags = "-Cdebuginfo=0".to_string(); + if let Some(linker) = linker { + rust_flags.push_str(" -C linker="); + rust_flags.push_str(linker); + rust_flags.push_str(" -C link-args=-static"); + + command.env("CPPFLAGS", "-fuse-ld=lld"); + } + + command.env("RUSTFLAGS", rust_flags); + let output = command.output(); + + if let Ok(output) = output { + if output.status.success() { + true + } else { + error!( + "Failed to compile code for rust intrinsics\n\nstdout:\n{}\n\nstderr:\n{}", + std::str::from_utf8(&output.stdout).unwrap_or(""), + std::str::from_utf8(&output.stderr).unwrap_or("") + ); + false + } + } else { + error!("Command failed: {:#?}", output); + false + } +} + +/// Intrinsic test tool +#[derive(clap::Parser)] +#[command( + name = "Intrinsic test tool", + about = "Generates Rust and C programs for intrinsics and compares the output" +)] +struct Cli { + /// The input file containing the intrinsics + input: PathBuf, + + /// The rust toolchain to use for building the rust code + #[arg(long)] + toolchain: Option, + + /// The C++ compiler to use for compiling the c++ code + #[arg(long, default_value_t = String::from("clang++"))] + cppcompiler: String, + + /// Run the C programs under emulation with this command + #[arg(long)] + runner: Option, + + /// Filename for a list of intrinsics to skip (one per line) + #[arg(long)] + skip: Option, + + /// Regenerate test programs, but don't build or run them + #[arg(long)] + generate_only: bool, + + /// Pass a target the test suite + #[arg(long, default_value_t = String::from("aarch64-unknown-linux-gnu"))] + target: String, + + /// Set the linker + #[arg(long)] + linker: Option, + + /// Set the sysroot for the C++ compiler + #[arg(long)] + cxx_toolchain_dir: Option, +} + +pub fn test() { + let args: Cli = clap::Parser::parse(); + + let filename = args.input; + let c_runner = args.runner.unwrap_or_default(); + let target: &str = args.target.as_str(); + let linker = args.linker.as_deref(); + let cxx_toolchain_dir = args.cxx_toolchain_dir; + + let skip = if let Some(filename) = args.skip { + let data = std::fs::read_to_string(&filename).expect("Failed to open file"); + data.lines() + .map(str::trim) + .filter(|s| !s.contains('#')) + .map(String::from) + .collect_vec() + } else { + Default::default() + }; + let a32 = target.contains("v7"); + let mut intrinsics = get_neon_intrinsics(&filename).expect("Error parsing input file"); + + intrinsics.sort_by(|a, b| a.name.cmp(&b.name)); + + let mut intrinsics = intrinsics + .into_iter() + // Not sure how we would compare intrinsic that returns void. + .filter(|i| i.results.kind() != TypeKind::Void) + .filter(|i| i.results.kind() != TypeKind::BFloat) + .filter(|i| !i.arguments.iter().any(|a| a.ty.kind() == TypeKind::BFloat)) + // Skip pointers for now, we would probably need to look at the return + // type to work out how many elements we need to point to. + .filter(|i| !i.arguments.iter().any(|a| a.is_ptr())) + .filter(|i| !i.arguments.iter().any(|a| a.ty.inner_size() == 128)) + .filter(|i| !skip.contains(&i.name)) + .filter(|i| !(a32 && i.a64_only)) + .collect::>(); + intrinsics.dedup(); + + let (toolchain, cpp_compiler) = if args.generate_only { + (None, None) + } else { + ( + Some(args.toolchain.map_or_else(String::new, |t| format!("+{t}"))), + Some(args.cppcompiler), + ) + }; + + let notices = build_notices("// "); + + if !build_c( + ¬ices, + &intrinsics, + cpp_compiler.as_deref(), + target, + cxx_toolchain_dir.as_deref(), + ) { + std::process::exit(2); + } + + if !build_rust(¬ices, &intrinsics, toolchain.as_deref(), target, linker) { + std::process::exit(3); + } + + if let Some(ref toolchain) = toolchain { + if !compare_outputs(&intrinsics, toolchain, &c_runner, target) { + std::process::exit(1) + } + } +} + +enum FailureReason { + RunC(String), + RunRust(String), + Difference(String, String, String), +} + +fn compare_outputs( + intrinsics: &Vec, + toolchain: &str, + runner: &str, + target: &str, +) -> bool { + let intrinsics = intrinsics + .par_iter() + .filter_map(|intrinsic| { + let c = Command::new("sh") + .arg("-c") + .arg(format!( + "{runner} ./c_programs/{intrinsic}", + runner = runner, + intrinsic = intrinsic.name, + )) + .output(); + + let rust = if target != "aarch64_be-unknown-linux-gnu" { + Command::new("sh") + .current_dir("rust_programs") + .arg("-c") + .arg(format!( + "cargo {toolchain} run --target {target} --bin {intrinsic} --release", + intrinsic = intrinsic.name, + toolchain = toolchain, + target = target + )) + .env("RUSTFLAGS", "-Cdebuginfo=0") + .output() + } else { + Command::new("sh") + .arg("-c") + .arg(format!( + "{runner} ./rust_programs/target/{target}/release/{intrinsic}", + runner = runner, + target = target, + intrinsic = intrinsic.name, + )) + .output() + }; + + let (c, rust) = match (c, rust) { + (Ok(c), Ok(rust)) => (c, rust), + a => panic!("{a:#?}"), + }; + + if !c.status.success() { + error!("Failed to run C program for intrinsic {}", intrinsic.name); + return Some(FailureReason::RunC(intrinsic.name.clone())); + } + + if !rust.status.success() { + error!( + "Failed to run rust program for intrinsic {}", + intrinsic.name + ); + return Some(FailureReason::RunRust(intrinsic.name.clone())); + } + + info!("Comparing intrinsic: {}", intrinsic.name); + + let c = std::str::from_utf8(&c.stdout) + .unwrap() + .to_lowercase() + .replace("-nan", "nan"); + let rust = std::str::from_utf8(&rust.stdout) + .unwrap() + .to_lowercase() + .replace("-nan", "nan"); + + if c == rust { + None + } else { + Some(FailureReason::Difference(intrinsic.name.clone(), c, rust)) + } + }) + .collect::>(); + + intrinsics.iter().for_each(|reason| match reason { + FailureReason::Difference(intrinsic, c, rust) => { + println!("Difference for intrinsic: {intrinsic}"); + let diff = diff::lines(c, rust); + diff.iter().for_each(|diff| match diff { + diff::Result::Left(c) => println!("C: {c}"), + diff::Result::Right(rust) => println!("Rust: {rust}"), + diff::Result::Both(_, _) => (), + }); + println!("****************************************************************"); + } + FailureReason::RunC(intrinsic) => { + println!("Failed to run C program for intrinsic {intrinsic}") + } + FailureReason::RunRust(intrinsic) => { + println!("Failed to run rust program for intrinsic {intrinsic}") + } + }); + println!("{} differences found", intrinsics.len()); + intrinsics.is_empty() +} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/types.rs b/library/stdarch/crates/intrinsic-test/src/arm/types.rs new file mode 100644 index 00000000000..98fcac4e00f --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/types.rs @@ -0,0 +1,508 @@ +use std::fmt; +use std::str::FromStr; + +use itertools::Itertools as _; + +use super::format::Indentation; +use crate::common::types::Language; +use crate::common::values::value_for_array; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum TypeKind { + BFloat, + Float, + Int, + UInt, + Poly, + Void, +} + +impl FromStr for TypeKind { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "bfloat" => Ok(Self::BFloat), + "float" => Ok(Self::Float), + "int" => Ok(Self::Int), + "poly" => Ok(Self::Poly), + "uint" | "unsigned" => Ok(Self::UInt), + "void" => Ok(Self::Void), + _ => Err(format!("Impossible to parse argument kind {s}")), + } + } +} + +impl fmt::Display for TypeKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::BFloat => "bfloat", + Self::Float => "float", + Self::Int => "int", + Self::UInt => "uint", + Self::Poly => "poly", + Self::Void => "void", + } + ) + } +} + +impl TypeKind { + /// Gets the type part of a c typedef for a type that's in the form of {type}{size}_t. + pub fn c_prefix(&self) -> &str { + match self { + Self::Float => "float", + Self::Int => "int", + Self::UInt => "uint", + Self::Poly => "poly", + _ => unreachable!("Not used: {:#?}", self), + } + } + + /// Gets the rust prefix for the type kind i.e. i, u, f. + pub fn rust_prefix(&self) -> &str { + match self { + Self::Float => "f", + Self::Int => "i", + Self::UInt => "u", + Self::Poly => "u", + _ => unreachable!("Unused type kind: {:#?}", self), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum IntrinsicType { + Ptr { + constant: bool, + child: Box, + }, + Type { + constant: bool, + kind: TypeKind, + /// The bit length of this type (e.g. 32 for u32). + bit_len: Option, + + /// Length of the SIMD vector (i.e. 4 for uint32x4_t), A value of `None` + /// means this is not a simd type. A `None` can be assumed to be 1, + /// although in some places a distinction is needed between `u64` and + /// `uint64x1_t` this signals that. + simd_len: Option, + + /// The number of rows for SIMD matrices (i.e. 2 for uint8x8x2_t). + /// A value of `None` represents a type that does not contain any + /// rows encoded in the type (e.g. uint8x8_t). + /// A value of `None` can be assumed to be 1 though. + vec_len: Option, + }, +} + +impl IntrinsicType { + /// Get the TypeKind for this type, recursing into pointers. + pub fn kind(&self) -> TypeKind { + match *self { + IntrinsicType::Ptr { ref child, .. } => child.kind(), + IntrinsicType::Type { kind, .. } => kind, + } + } + + /// Get the size of a single element inside this type, recursing into + /// pointers, i.e. a pointer to a u16 would be 16 rather than the size + /// of a pointer. + pub fn inner_size(&self) -> u32 { + match self { + IntrinsicType::Ptr { child, .. } => child.inner_size(), + IntrinsicType::Type { + bit_len: Some(bl), .. + } => *bl, + _ => unreachable!(""), + } + } + + pub fn num_lanes(&self) -> u32 { + match *self { + IntrinsicType::Ptr { ref child, .. } => child.num_lanes(), + IntrinsicType::Type { + simd_len: Some(sl), .. + } => sl, + _ => 1, + } + } + + pub fn num_vectors(&self) -> u32 { + match *self { + IntrinsicType::Ptr { ref child, .. } => child.num_vectors(), + IntrinsicType::Type { + vec_len: Some(vl), .. + } => vl, + _ => 1, + } + } + + /// Determine if the type is a simd type, this will treat a type such as + /// `uint64x1` as simd. + pub fn is_simd(&self) -> bool { + match *self { + IntrinsicType::Ptr { ref child, .. } => child.is_simd(), + IntrinsicType::Type { + simd_len: None, + vec_len: None, + .. + } => false, + _ => true, + } + } + + pub fn is_ptr(&self) -> bool { + match *self { + IntrinsicType::Ptr { .. } => true, + IntrinsicType::Type { .. } => false, + } + } + + pub fn c_scalar_type(&self) -> String { + format!( + "{prefix}{bits}_t", + prefix = self.kind().c_prefix(), + bits = self.inner_size() + ) + } + + pub fn rust_scalar_type(&self) -> String { + format!( + "{prefix}{bits}", + prefix = self.kind().rust_prefix(), + bits = self.inner_size() + ) + } + + /// Gets a string containing the typename for this type in C format. + pub fn c_type(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.c_type(), + IntrinsicType::Type { + constant, + kind, + bit_len: Some(bit_len), + simd_len: None, + vec_len: None, + .. + } => format!( + "{}{}{}_t", + if *constant { "const " } else { "" }, + kind.c_prefix(), + bit_len + ), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: None, + .. + } => format!("{}{bit_len}x{simd_len}_t", kind.c_prefix()), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: Some(vec_len), + .. + } => format!("{}{bit_len}x{simd_len}x{vec_len}_t", kind.c_prefix()), + _ => todo!("{:#?}", self), + } + } + + pub fn c_single_vector_type(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.c_single_vector_type(), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: Some(_), + .. + } => format!("{}{bit_len}x{simd_len}_t", kind.c_prefix()), + _ => unreachable!("Shouldn't be called on this type"), + } + } + + pub fn rust_type(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.c_type(), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: None, + vec_len: None, + .. + } => format!("{}{bit_len}", kind.rust_prefix()), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: None, + .. + } => format!("{}{bit_len}x{simd_len}_t", kind.c_prefix()), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: Some(vec_len), + .. + } => format!("{}{bit_len}x{simd_len}x{vec_len}_t", kind.c_prefix()), + _ => todo!("{:#?}", self), + } + } + + /// Gets a cast for this type if needs promotion. + /// This is required for 8 bit types due to printing as the 8 bit types use + /// a char and when using that in `std::cout` it will print as a character, + /// which means value of 0 will be printed as a null byte. + /// + /// This is also needed for polynomial types because we want them to be + /// printed as unsigned integers to match Rust's `Debug` impl. + pub fn c_promotion(&self) -> &str { + match *self { + IntrinsicType::Type { + kind, + bit_len: Some(8), + .. + } => match kind { + TypeKind::Int => "(int)", + TypeKind::UInt => "(unsigned int)", + TypeKind::Poly => "(unsigned int)(uint8_t)", + _ => "", + }, + IntrinsicType::Type { + kind: TypeKind::Poly, + bit_len: Some(bit_len), + .. + } => match bit_len { + 8 => unreachable!("handled above"), + 16 => "(uint16_t)", + 32 => "(uint32_t)", + 64 => "(uint64_t)", + 128 => "", + _ => panic!("invalid bit_len"), + }, + _ => "", + } + } + + /// Generates an initialiser for an array, which can be used to initialise an argument for the + /// intrinsic call. + /// + /// This is determistic based on the pass number. + /// + /// * `loads`: The number of values that need to be loaded from the argument array + /// * e.g for argument type uint32x2, loads=2 results in a string representing 4 32-bit values + /// + /// Returns a string such as + /// * `{0x1, 0x7F, 0xFF}` if `language` is `Language::C` + /// * `[0x1 as _, 0x7F as _, 0xFF as _]` if `language` is `Language::Rust` + pub fn populate_random( + &self, + indentation: Indentation, + loads: u32, + language: &Language, + ) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.populate_random(indentation, loads, language), + IntrinsicType::Type { + bit_len: Some(bit_len @ (8 | 16 | 32 | 64)), + kind: kind @ (TypeKind::Int | TypeKind::UInt | TypeKind::Poly), + simd_len, + vec_len, + .. + } => { + let (prefix, suffix) = match language { + Language::Rust => ("[", "]"), + Language::C => ("{", "}"), + }; + let body_indentation = indentation.nested(); + format!( + "{prefix}\n{body}\n{indentation}{suffix}", + body = (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1) + loads - 1)) + .format_with(",\n", |i, fmt| { + let src = value_for_array(*bit_len, i); + assert!(src == 0 || src.ilog2() < *bit_len); + if *kind == TypeKind::Int && (src >> (*bit_len - 1)) != 0 { + // `src` is a two's complement representation of a negative value. + let mask = !0u64 >> (64 - *bit_len); + let ones_compl = src ^ mask; + let twos_compl = ones_compl + 1; + if (twos_compl == src) && (language == &Language::C) { + // `src` is INT*_MIN. C requires `-0x7fffffff - 1` to avoid + // undefined literal overflow behaviour. + fmt(&format_args!("{body_indentation}-{ones_compl:#x} - 1")) + } else { + fmt(&format_args!("{body_indentation}-{twos_compl:#x}")) + } + } else { + fmt(&format_args!("{body_indentation}{src:#x}")) + } + }) + ) + } + IntrinsicType::Type { + kind: TypeKind::Float, + bit_len: Some(bit_len @ (16 | 32 | 64)), + simd_len, + vec_len, + .. + } => { + let (prefix, cast_prefix, cast_suffix, suffix) = match (language, bit_len) { + (&Language::Rust, 16) => ("[", "f16::from_bits(", ")", "]"), + (&Language::Rust, 32) => ("[", "f32::from_bits(", ")", "]"), + (&Language::Rust, 64) => ("[", "f64::from_bits(", ")", "]"), + (&Language::C, 16) => ("{", "cast(", ")", "}"), + (&Language::C, 32) => ("{", "cast(", ")", "}"), + (&Language::C, 64) => ("{", "cast(", ")", "}"), + _ => unreachable!(), + }; + format!( + "{prefix}\n{body}\n{indentation}{suffix}", + body = (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1) + loads - 1)) + .format_with(",\n", |i, fmt| fmt(&format_args!( + "{indentation}{cast_prefix}{src:#x}{cast_suffix}", + indentation = indentation.nested(), + src = value_for_array(*bit_len, i) + ))) + ) + } + _ => unimplemented!("populate random: {:#?}", self), + } + } + + /// Determines the load function for this type. + pub fn get_load_function(&self, armv7_p64_workaround: bool) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.get_load_function(armv7_p64_workaround), + IntrinsicType::Type { + kind: k, + bit_len: Some(bl), + simd_len, + vec_len, + .. + } => { + let quad = if simd_len.unwrap_or(1) * bl > 64 { + "q" + } else { + "" + }; + format!( + "vld{len}{quad}_{type}{size}", + type = match k { + TypeKind::UInt => "u", + TypeKind::Int => "s", + TypeKind::Float => "f", + // The ACLE doesn't support 64-bit polynomial loads on Armv7 + TypeKind::Poly => if armv7_p64_workaround && *bl == 64 {"s"} else {"p"}, + x => todo!("get_load_function TypeKind: {:#?}", x), + }, + size = bl, + quad = quad, + len = vec_len.unwrap_or(1), + ) + } + _ => todo!("get_load_function IntrinsicType: {:#?}", self), + } + } + + /// Determines the get lane function for this type. + pub fn get_lane_function(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.get_lane_function(), + IntrinsicType::Type { + kind: k, + bit_len: Some(bl), + simd_len, + .. + } => { + let quad = if (simd_len.unwrap_or(1) * bl) > 64 { + "q" + } else { + "" + }; + format!( + "vget{quad}_lane_{type}{size}", + type = match k { + TypeKind::UInt => "u", + TypeKind::Int => "s", + TypeKind::Float => "f", + TypeKind::Poly => "p", + x => todo!("get_load_function TypeKind: {:#?}", x), + }, + size = bl, + quad = quad, + ) + } + _ => todo!("get_lane_function IntrinsicType: {:#?}", self), + } + } + + pub fn from_c(s: &str) -> Result { + const CONST_STR: &str = "const"; + if let Some(s) = s.strip_suffix('*') { + let (s, constant) = match s.trim().strip_suffix(CONST_STR) { + Some(stripped) => (stripped, true), + None => (s, false), + }; + let s = s.trim_end(); + Ok(IntrinsicType::Ptr { + constant, + child: Box::new(IntrinsicType::from_c(s)?), + }) + } else { + // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t] + let (mut s, constant) = match s.strip_prefix(CONST_STR) { + Some(stripped) => (stripped.trim(), true), + None => (s, false), + }; + s = s.strip_suffix("_t").unwrap_or(s); + let mut parts = s.split('x'); // [[{bitlen}], [{simdlen}], [{vec_len}] ] + let start = parts.next().ok_or("Impossible to parse type")?; + if let Some(digit_start) = start.find(|c: char| c.is_ascii_digit()) { + let (arg_kind, bit_len) = start.split_at(digit_start); + let arg_kind = arg_kind.parse::()?; + let bit_len = bit_len.parse::().map_err(|err| err.to_string())?; + let simd_len = match parts.next() { + Some(part) => Some( + part.parse::() + .map_err(|_| "Couldn't parse simd_len: {part}")?, + ), + None => None, + }; + let vec_len = match parts.next() { + Some(part) => Some( + part.parse::() + .map_err(|_| "Couldn't parse vec_len: {part}")?, + ), + None => None, + }; + Ok(IntrinsicType::Type { + constant, + kind: arg_kind, + bit_len: Some(bit_len), + simd_len, + vec_len, + }) + } else { + let kind = start.parse::()?; + let bit_len = match kind { + TypeKind::Int => Some(32), + _ => None, + }; + Ok(IntrinsicType::Type { + constant, + kind: start.parse::()?, + bit_len, + simd_len: None, + vec_len: None, + }) + } + } + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/mod.rs b/library/stdarch/crates/intrinsic-test/src/common/mod.rs new file mode 100644 index 00000000000..f5710ca82b8 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/mod.rs @@ -0,0 +1,2 @@ +pub mod types; +pub mod values; diff --git a/library/stdarch/crates/intrinsic-test/src/common/types.rs b/library/stdarch/crates/intrinsic-test/src/common/types.rs new file mode 100644 index 00000000000..8b3b46818e8 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/types.rs @@ -0,0 +1,5 @@ +#[derive(Debug, PartialEq)] +pub enum Language { + Rust, + C, +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/values.rs b/library/stdarch/crates/intrinsic-test/src/common/values.rs new file mode 100644 index 00000000000..1b614a742ef --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/values.rs @@ -0,0 +1,120 @@ +/// Get a single value for an argument values array in a determistic way. +/// * `bits`: The number of bits for the type, only 8, 16, 32, 64 are valid values +/// * `index`: The position in the array we are generating for +pub fn value_for_array(bits: u32, index: u32) -> u64 { + let index = index as usize; + match bits { + 8 => VALUES_8[index % VALUES_8.len()].into(), + 16 => VALUES_16[index % VALUES_16.len()].into(), + 32 => VALUES_32[index % VALUES_32.len()].into(), + 64 => VALUES_64[index % VALUES_64.len()], + _ => unimplemented!("value_for_array(bits: {bits}, ..)"), + } +} + +pub const VALUES_8: &[u8] = &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0xf0, 0x80, 0x3b, 0xff, +]; + +pub const VALUES_16: &[u16] = &[ + 0x0000, // 0.0 + 0x0400, // The smallest normal value. + 0x37ff, // The value just below 0.5. + 0x3800, // 0.5 + 0x3801, // The value just above 0.5. + 0x3bff, // The value just below 1.0. + 0x3c00, // 1.0 + 0x3c01, // The value just above 1.0. + 0x3e00, // 1.5 + 0x4900, // 10 + 0x7bff, // The largest finite value. + 0x7c00, // Infinity. + // NaNs. + // - Quiet NaNs + 0x7f23, 0x7e00, // - Signalling NaNs + 0x7d23, 0x7c01, // Subnormals. + // - A recognisable bit pattern. + 0x0012, // - The largest subnormal value. + 0x03ff, // - The smallest subnormal value. + 0x0001, // The same values again, but negated. + 0x8000, 0x8400, 0xb7ff, 0xb800, 0xb801, 0xbbff, 0xbc00, 0xbc01, 0xbe00, 0xc900, 0xfbff, 0xfc00, + 0xff23, 0xfe00, 0xfd23, 0xfc01, 0x8012, 0x83ff, 0x8001, +]; + +pub const VALUES_32: &[u32] = &[ + // Simple values. + 0x00000000, // 0.0 + 0x00800000, // The smallest normal value. + 0x3effffff, // The value just below 0.5. + 0x3f000000, // 0.5 + 0x3f000001, // The value just above 0.5. + 0x3f7fffff, // The value just below 1.0. + 0x3f800000, // 1.0 + 0x3f800001, // The value just above 1.0. + 0x3fc00000, // 1.5 + 0x41200000, // 10 + 0x7f8fffff, // The largest finite value. + 0x7f800000, // Infinity. + // NaNs. + // - Quiet NaNs + 0x7fd23456, 0x7fc00000, // - Signalling NaNs + 0x7f923456, 0x7f800001, // Subnormals. + // - A recognisable bit pattern. + 0x00123456, // - The largest subnormal value. + 0x007fffff, // - The smallest subnormal value. + 0x00000001, // The same values again, but negated. + 0x80000000, 0x80800000, 0xbeffffff, 0xbf000000, 0xbf000001, 0xbf7fffff, 0xbf800000, 0xbf800001, + 0xbfc00000, 0xc1200000, 0xff8fffff, 0xff800000, 0xffd23456, 0xffc00000, 0xff923456, 0xff800001, + 0x80123456, 0x807fffff, 0x80000001, +]; + +pub const VALUES_64: &[u64] = &[ + // Simple values. + 0x0000000000000000, // 0.0 + 0x0010000000000000, // The smallest normal value. + 0x3fdfffffffffffff, // The value just below 0.5. + 0x3fe0000000000000, // 0.5 + 0x3fe0000000000001, // The value just above 0.5. + 0x3fefffffffffffff, // The value just below 1.0. + 0x3ff0000000000000, // 1.0 + 0x3ff0000000000001, // The value just above 1.0. + 0x3ff8000000000000, // 1.5 + 0x4024000000000000, // 10 + 0x7fefffffffffffff, // The largest finite value. + 0x7ff0000000000000, // Infinity. + // NaNs. + // - Quiet NaNs + 0x7ff923456789abcd, + 0x7ff8000000000000, + // - Signalling NaNs + 0x7ff123456789abcd, + 0x7ff0000000000000, + // Subnormals. + // - A recognisable bit pattern. + 0x000123456789abcd, + // - The largest subnormal value. + 0x000fffffffffffff, + // - The smallest subnormal value. + 0x0000000000000001, + // The same values again, but negated. + 0x8000000000000000, + 0x8010000000000000, + 0xbfdfffffffffffff, + 0xbfe0000000000000, + 0xbfe0000000000001, + 0xbfefffffffffffff, + 0xbff0000000000000, + 0xbff0000000000001, + 0xbff8000000000000, + 0xc024000000000000, + 0xffefffffffffffff, + 0xfff0000000000000, + 0xfff923456789abcd, + 0xfff8000000000000, + 0xfff123456789abcd, + 0xfff0000000000000, + 0x800123456789abcd, + 0x800fffffffffffff, + 0x8000000000000001, +]; diff --git a/library/stdarch/crates/intrinsic-test/src/format.rs b/library/stdarch/crates/intrinsic-test/src/format.rs deleted file mode 100644 index 9ee331d7f7a..00000000000 --- a/library/stdarch/crates/intrinsic-test/src/format.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Basic code formatting tools. -//! -//! We don't need perfect formatting for the generated tests, but simple indentation can make -//! debugging a lot easier. - -#[derive(Copy, Clone, Debug, Default)] -pub struct Indentation(u32); - -impl Indentation { - pub fn nested(self) -> Self { - Self(self.0 + 1) - } -} - -impl std::fmt::Display for Indentation { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - for _ in 0..self.0 { - write!(f, " ")?; - } - Ok(()) - } -} diff --git a/library/stdarch/crates/intrinsic-test/src/intrinsic.rs b/library/stdarch/crates/intrinsic-test/src/intrinsic.rs deleted file mode 100644 index 4cc27525011..00000000000 --- a/library/stdarch/crates/intrinsic-test/src/intrinsic.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::format::Indentation; -use crate::types::{IntrinsicType, TypeKind}; - -use super::argument::ArgumentList; - -/// An intrinsic -#[derive(Debug, PartialEq, Clone)] -pub struct Intrinsic { - /// The function name of this intrinsic. - pub name: String, - - /// Any arguments for this intrinsic. - pub arguments: ArgumentList, - - /// The return type of this intrinsic. - pub results: IntrinsicType, - - /// Whether this intrinsic is only available on A64. - pub a64_only: bool, -} - -impl Intrinsic { - /// Generates a std::cout for the intrinsics results that will match the - /// rust debug output format for the return type. The generated line assumes - /// there is an int i in scope which is the current pass number. - pub fn print_result_c(&self, indentation: Indentation, additional: &str) -> String { - let lanes = if self.results.num_vectors() > 1 { - (0..self.results.num_vectors()) - .map(|vector| { - format!( - r#""{ty}(" << {lanes} << ")""#, - ty = self.results.c_single_vector_type(), - lanes = (0..self.results.num_lanes()) - .map(move |idx| -> std::string::String { - format!( - "{cast}{lane_fn}(__return_value.val[{vector}], {lane})", - cast = self.results.c_promotion(), - lane_fn = self.results.get_lane_function(), - lane = idx, - vector = vector, - ) - }) - .collect::>() - .join(r#" << ", " << "#) - ) - }) - .collect::>() - .join(r#" << ", " << "#) - } else if self.results.num_lanes() > 1 { - (0..self.results.num_lanes()) - .map(|idx| -> std::string::String { - format!( - "{cast}{lane_fn}(__return_value, {lane})", - cast = self.results.c_promotion(), - lane_fn = self.results.get_lane_function(), - lane = idx - ) - }) - .collect::>() - .join(r#" << ", " << "#) - } else { - format!( - "{promote}cast<{cast}>(__return_value)", - cast = match self.results.kind() { - TypeKind::Float if self.results.inner_size() == 16 => "float16_t".to_string(), - TypeKind::Float if self.results.inner_size() == 32 => "float".to_string(), - TypeKind::Float if self.results.inner_size() == 64 => "double".to_string(), - TypeKind::Int => format!("int{}_t", self.results.inner_size()), - TypeKind::UInt => format!("uint{}_t", self.results.inner_size()), - TypeKind::Poly => format!("poly{}_t", self.results.inner_size()), - ty => todo!("print_result_c - Unknown type: {:#?}", ty), - }, - promote = self.results.c_promotion(), - ) - }; - - format!( - r#"{indentation}std::cout << "Result {additional}-" << i+1 << ": {ty}" << std::fixed << std::setprecision(150) << {lanes} << "{close}" << std::endl;"#, - ty = if self.results.is_simd() { - format!("{}(", self.results.c_type()) - } else { - String::from("") - }, - close = if self.results.is_simd() { ")" } else { "" }, - lanes = lanes, - additional = additional, - ) - } - - pub fn generate_loop_c( - &self, - indentation: Indentation, - additional: &str, - passes: u32, - target: &str, - ) -> String { - let body_indentation = indentation.nested(); - format!( - "{indentation}for (int i=0; i<{passes}; i++) {{\n\ - {loaded_args}\ - {body_indentation}auto __return_value = {intrinsic_call}({args});\n\ - {print_result}\n\ - {indentation}}}", - loaded_args = self.arguments.load_values_c(body_indentation, target), - intrinsic_call = self.name, - args = self.arguments.as_call_param_c(), - print_result = self.print_result_c(body_indentation, additional) - ) - } - - pub fn generate_loop_rust( - &self, - indentation: Indentation, - additional: &str, - passes: u32, - ) -> String { - let constraints = self.arguments.as_constraint_parameters_rust(); - let constraints = if !constraints.is_empty() { - format!("::<{constraints}>") - } else { - constraints - }; - - // the `intrinsic-test` crate compares the output of C and Rust intrinsics. Currently, It uses - // a string representation of the output value to compare. In C, f16 values are currently printed - // as hexadecimal integers. Since https://github.com/rust-lang/rust/pull/127013, rust does print - // them as decimal floating point values. To keep the intrinsics tests working, for now, format - // vectors containing f16 values like C prints them. - let return_value = match self.results.kind() { - TypeKind::Float if self.results.inner_size() == 16 => "debug_f16(__return_value)", - _ => "format_args!(\"{__return_value:.150?}\")", - }; - - let indentation2 = indentation.nested(); - let indentation3 = indentation2.nested(); - - format!( - "{indentation}for i in 0..{passes} {{\n\ - {indentation2}unsafe {{\n\ - {loaded_args}\ - {indentation3}let __return_value = {intrinsic_call}{const}({args});\n\ - {indentation3}println!(\"Result {additional}-{{}}: {{:?}}\", i + 1, {return_value});\n\ - {indentation2}}}\n\ - {indentation}}}", - loaded_args = self.arguments.load_values_rust(indentation3), - intrinsic_call = self.name, - const = constraints, - args = self.arguments.as_call_param_rust(), - additional = additional, - ) - } -} diff --git a/library/stdarch/crates/intrinsic-test/src/json_parser.rs b/library/stdarch/crates/intrinsic-test/src/json_parser.rs deleted file mode 100644 index 70ab51561bc..00000000000 --- a/library/stdarch/crates/intrinsic-test/src/json_parser.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; - -use serde::Deserialize; - -use crate::argument::{Argument, ArgumentList}; -use crate::intrinsic::Intrinsic; -use crate::types::IntrinsicType; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -struct ReturnType { - value: String, -} - -#[derive(Deserialize, Debug)] -#[serde(untagged, deny_unknown_fields)] -pub enum ArgPrep { - Register { - #[serde(rename = "register")] - #[allow(dead_code)] - reg: String, - }, - Immediate { - #[serde(rename = "minimum")] - min: i64, - #[serde(rename = "maximum")] - max: i64, - }, - Nothing {}, -} - -#[derive(Deserialize, Debug)] -struct JsonIntrinsic { - #[serde(rename = "SIMD_ISA")] - simd_isa: String, - name: String, - arguments: Vec, - return_type: ReturnType, - #[serde(rename = "Arguments_Preparation")] - args_prep: Option>, - #[serde(rename = "Architectures")] - architectures: Vec, -} - -pub fn get_neon_intrinsics(filename: &Path) -> Result, Box> { - let file = std::fs::File::open(filename)?; - let reader = std::io::BufReader::new(file); - let json: Vec = serde_json::from_reader(reader).expect("Couldn't parse JSON"); - - let parsed = json - .into_iter() - .filter_map(|intr| { - if intr.simd_isa == "Neon" { - Some(json_to_intrinsic(intr).expect("Couldn't parse JSON")) - } else { - None - } - }) - .collect(); - Ok(parsed) -} - -fn json_to_intrinsic(mut intr: JsonIntrinsic) -> Result> { - let name = intr.name.replace(['[', ']'], ""); - - let results = IntrinsicType::from_c(&intr.return_type.value)?; - - let mut args_prep = intr.args_prep.as_mut(); - let args = intr - .arguments - .into_iter() - .enumerate() - .map(|(i, arg)| { - let arg_name = Argument::type_and_name_from_c(&arg).1; - let arg_prep = args_prep.as_mut().and_then(|a| a.remove(arg_name)); - let mut arg = Argument::from_c(i, &arg, arg_prep); - // The JSON doesn't list immediates as const - if let IntrinsicType::Type { - ref mut constant, .. - } = arg.ty - { - if arg.name.starts_with("imm") { - *constant = true - } - } - arg - }) - .collect(); - - let arguments = ArgumentList { args }; - - Ok(Intrinsic { - name, - arguments, - results, - a64_only: intr.architectures == vec!["A64".to_string()], - }) -} diff --git a/library/stdarch/crates/intrinsic-test/src/main.rs b/library/stdarch/crates/intrinsic-test/src/main.rs index cdf9f5bb302..a383c5304c6 100644 --- a/library/stdarch/crates/intrinsic-test/src/main.rs +++ b/library/stdarch/crates/intrinsic-test/src/main.rs @@ -2,762 +2,10 @@ #[macro_use] extern crate log; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; -use std::process::Command; - -use intrinsic::Intrinsic; -use itertools::Itertools; -use rayon::prelude::*; -use types::TypeKind; - -use crate::argument::Argument; -use crate::format::Indentation; -use crate::json_parser::get_neon_intrinsics; - -mod argument; -mod format; -mod intrinsic; -mod json_parser; -mod types; -mod values; - -// The number of times each intrinsic will be called. -const PASSES: u32 = 20; - -#[derive(Debug, PartialEq)] -pub enum Language { - Rust, - C, -} - -fn gen_code_c( - indentation: Indentation, - intrinsic: &Intrinsic, - constraints: &[&Argument], - name: String, - target: &str, -) -> String { - if let Some((current, constraints)) = constraints.split_last() { - let range = current - .constraints - .iter() - .map(|c| c.to_range()) - .flat_map(|r| r.into_iter()); - - let body_indentation = indentation.nested(); - range - .map(|i| { - format!( - "{indentation}{{\n\ - {body_indentation}{ty} {name} = {val};\n\ - {pass}\n\ - {indentation}}}", - name = current.name, - ty = current.ty.c_type(), - val = i, - pass = gen_code_c( - body_indentation, - intrinsic, - constraints, - format!("{name}-{i}"), - target, - ) - ) - }) - .join("\n") - } else { - intrinsic.generate_loop_c(indentation, &name, PASSES, target) - } -} - -fn generate_c_program( - notices: &str, - header_files: &[&str], - intrinsic: &Intrinsic, - target: &str, -) -> String { - let constraints = intrinsic - .arguments - .iter() - .filter(|i| i.has_constraint()) - .collect_vec(); - - let indentation = Indentation::default(); - format!( - r#"{notices}{header_files} -#include -#include -#include -#include - -template T1 cast(T2 x) {{ - static_assert(sizeof(T1) == sizeof(T2), "sizeof T1 and T2 must be the same"); - T1 ret{{}}; - memcpy(&ret, &x, sizeof(T1)); - return ret; -}} - -#ifdef __aarch64__ -std::ostream& operator<<(std::ostream& os, poly128_t value) {{ - std::stringstream temp; - do {{ - int n = value % 10; - value /= 10; - temp << n; - }} while (value != 0); - std::string tempstr(temp.str()); - std::string res(tempstr.rbegin(), tempstr.rend()); - os << res; - return os; -}} -#endif - -std::ostream& operator<<(std::ostream& os, float16_t value) {{ - uint16_t temp = 0; - memcpy(&temp, &value, sizeof(float16_t)); - std::stringstream ss; - ss << "0x" << std::setfill('0') << std::setw(4) << std::hex << temp; - os << ss.str(); - return os; -}} - -{arglists} - -int main(int argc, char **argv) {{ -{passes} - return 0; -}}"#, - header_files = header_files - .iter() - .map(|header| format!("#include <{header}>")) - .collect::>() - .join("\n"), - arglists = intrinsic.arguments.gen_arglists_c(indentation, PASSES), - passes = gen_code_c( - indentation.nested(), - intrinsic, - constraints.as_slice(), - Default::default(), - target, - ), - ) -} - -fn gen_code_rust( - indentation: Indentation, - intrinsic: &Intrinsic, - constraints: &[&Argument], - name: String, -) -> String { - if let Some((current, constraints)) = constraints.split_last() { - let range = current - .constraints - .iter() - .map(|c| c.to_range()) - .flat_map(|r| r.into_iter()); - - let body_indentation = indentation.nested(); - range - .map(|i| { - format!( - "{indentation}{{\n\ - {body_indentation}const {name}: {ty} = {val};\n\ - {pass}\n\ - {indentation}}}", - name = current.name, - ty = current.ty.rust_type(), - val = i, - pass = gen_code_rust( - body_indentation, - intrinsic, - constraints, - format!("{name}-{i}") - ) - ) - }) - .join("\n") - } else { - intrinsic.generate_loop_rust(indentation, &name, PASSES) - } -} - -fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, target: &str) -> String { - let constraints = intrinsic - .arguments - .iter() - .filter(|i| i.has_constraint()) - .collect_vec(); - - // Format f16 values (and vectors containing them) in a way that is consistent with C. - let f16_formatting = r#" -/// Used to continue `Debug`ging SIMD types as `MySimd(1, 2, 3, 4)`, as they -/// were before moving to array-based simd. -#[inline] -fn debug_simd_finish( - formatter: &mut core::fmt::Formatter<'_>, - type_name: &str, - array: &[T; N], -) -> core::fmt::Result { - core::fmt::Formatter::debug_tuple_fields_finish( - formatter, - type_name, - &core::array::from_fn::<&dyn core::fmt::Debug, N, _>(|i| &array[i]), - ) -} - -#[repr(transparent)] -struct Hex(T); - -impl core::fmt::Debug for Hex { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - ::fmt(&self.0, f) - } -} - -fn debug_f16(x: T) -> impl core::fmt::Debug { - Hex(x) -} - -trait DebugHexF16 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result; -} - -impl DebugHexF16 for f16 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#06x?}", self.to_bits()) - } -} - -impl DebugHexF16 for float16x4_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let array = unsafe { core::mem::transmute::<_, [Hex; 4]>(*self) }; - debug_simd_finish(f, "float16x4_t", &array) - } -} - -impl DebugHexF16 for float16x8_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let array = unsafe { core::mem::transmute::<_, [Hex; 8]>(*self) }; - debug_simd_finish(f, "float16x8_t", &array) - } -} - -impl DebugHexF16 for float16x4x2_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_simd_finish(f, "float16x4x2_t", &[Hex(self.0), Hex(self.1)]) - } -} -impl DebugHexF16 for float16x4x3_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_simd_finish(f, "float16x4x3_t", &[Hex(self.0), Hex(self.1), Hex(self.2)]) - } -} -impl DebugHexF16 for float16x4x4_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_simd_finish(f, "float16x4x4_t", &[Hex(self.0), Hex(self.1), Hex(self.2), Hex(self.3)]) - } -} - -impl DebugHexF16 for float16x8x2_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_simd_finish(f, "float16x8x2_t", &[Hex(self.0), Hex(self.1)]) - } -} -impl DebugHexF16 for float16x8x3_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_simd_finish(f, "float16x8x3_t", &[Hex(self.0), Hex(self.1), Hex(self.2)]) - } -} -impl DebugHexF16 for float16x8x4_t { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_simd_finish(f, "float16x8x4_t", &[Hex(self.0), Hex(self.1), Hex(self.2), Hex(self.3)]) - } -} - "#; - - let indentation = Indentation::default(); - format!( - r#"{notices}#![feature(simd_ffi)] -#![feature(link_llvm_intrinsics)] -#![feature(f16)] -#![feature(fmt_helpers_for_derive)] -#![cfg_attr(target_arch = "arm", feature(stdarch_arm_neon_intrinsics))] -#![cfg_attr(target_arch = "arm", feature(stdarch_aarch32_crc32))] -#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_fcma))] -#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_dotprod))] -#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_i8mm))] -#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_sha3))] -#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_sm4))] -#![cfg_attr(any(target_arch = "aarch64", target_arch = "arm64ec"), feature(stdarch_neon_ftts))] -#![feature(stdarch_neon_f16)] -#![allow(non_upper_case_globals)] -use core_arch::arch::{target_arch}::*; - -{f16_formatting} - -fn main() {{ -{arglists} -{passes} -}} -"#, - target_arch = if target.contains("v7") { - "arm" - } else { - "aarch64" - }, - arglists = intrinsic - .arguments - .gen_arglists_rust(indentation.nested(), PASSES), - passes = gen_code_rust( - indentation.nested(), - intrinsic, - &constraints, - Default::default() - ) - ) -} - -fn compile_c( - c_filename: &str, - intrinsic: &Intrinsic, - compiler: &str, - target: &str, - cxx_toolchain_dir: Option<&str>, -) -> bool { - let flags = std::env::var("CPPFLAGS").unwrap_or("".into()); - let arch_flags = if target.contains("v7") { - "-march=armv8.6-a+crypto+crc+dotprod+fp16" - } else { - "-march=armv8.6-a+crypto+sha3+crc+dotprod+fp16+faminmax+lut" - }; - - let intrinsic_name = &intrinsic.name; - - let compiler_command = if target == "aarch64_be-unknown-linux-gnu" { - let Some(cxx_toolchain_dir) = cxx_toolchain_dir else { - panic!( - "When setting `--target aarch64_be-unknown-linux-gnu` the C++ compilers toolchain directory must be set with `--cxx-toolchain-dir `" - ); - }; - - /* clang++ cannot link an aarch64_be object file, so we invoke - * aarch64_be-unknown-linux-gnu's C++ linker. This ensures that we - * are testing the intrinsics against LLVM. - * - * Note: setting `--sysroot=<...>` which is the obvious thing to do - * does not work as it gets caught up with `#include_next ` - * not existing... */ - format!( - "{compiler} {flags} {arch_flags} \ - -ffp-contract=off \ - -Wno-narrowing \ - -O2 \ - --target=aarch64_be-unknown-linux-gnu \ - -I{cxx_toolchain_dir}/include \ - -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include \ - -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1 \ - -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1/aarch64_be-none-linux-gnu \ - -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1/backward \ - -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/libc/usr/include \ - -c {c_filename} \ - -o c_programs/{intrinsic_name}.o && \ - {cxx_toolchain_dir}/bin/aarch64_be-none-linux-gnu-g++ c_programs/{intrinsic_name}.o -o c_programs/{intrinsic_name} && \ - rm c_programs/{intrinsic_name}.o", - ) - } else { - // -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations - let base_compiler_command = format!( - "{compiler} {flags} {arch_flags} -o c_programs/{intrinsic_name} {c_filename} -ffp-contract=off -Wno-narrowing -O2" - ); - - /* `-target` can be passed to some c++ compilers, however if we want to - * use a c++ compiler does not support this flag we do not want to pass - * the flag. */ - if compiler.contains("clang") { - format!("{base_compiler_command} -target {target}") - } else { - format!("{base_compiler_command} -flax-vector-conversions") - } - }; - - let output = Command::new("sh").arg("-c").arg(compiler_command).output(); - if let Ok(output) = output { - if output.status.success() { - true - } else { - error!( - "Failed to compile code for intrinsic: {}\n\nstdout:\n{}\n\nstderr:\n{}", - intrinsic.name, - std::str::from_utf8(&output.stdout).unwrap_or(""), - std::str::from_utf8(&output.stderr).unwrap_or("") - ); - false - } - } else { - error!("Command failed: {:#?}", output); - false - } -} - -fn build_notices(line_prefix: &str) -> String { - format!( - "\ -{line_prefix}This is a transient test file, not intended for distribution. Some aspects of the -{line_prefix}test are derived from a JSON specification, published under the same license as the -{line_prefix}`intrinsic-test` crate.\n -" - ) -} - -fn build_c( - notices: &str, - intrinsics: &Vec, - compiler: Option<&str>, - target: &str, - cxx_toolchain_dir: Option<&str>, -) -> bool { - let _ = std::fs::create_dir("c_programs"); - intrinsics - .par_iter() - .map(|i| { - let c_filename = format!(r#"c_programs/{}.cpp"#, i.name); - let mut file = File::create(&c_filename).unwrap(); - - let c_code = generate_c_program( - notices, - &["arm_neon.h", "arm_acle.h", "arm_fp16.h"], - i, - target, - ); - file.write_all(c_code.into_bytes().as_slice()).unwrap(); - match compiler { - None => true, - Some(compiler) => compile_c(&c_filename, i, compiler, target, cxx_toolchain_dir), - } - }) - .find_any(|x| !x) - .is_none() -} - -fn build_rust( - notices: &str, - intrinsics: &[Intrinsic], - toolchain: Option<&str>, - target: &str, - linker: Option<&str>, -) -> bool { - intrinsics.iter().for_each(|i| { - let rust_dir = format!(r#"rust_programs/{}"#, i.name); - let _ = std::fs::create_dir_all(&rust_dir); - let rust_filename = format!(r#"{rust_dir}/main.rs"#); - let mut file = File::create(&rust_filename).unwrap(); - - let c_code = generate_rust_program(notices, i, target); - file.write_all(c_code.into_bytes().as_slice()).unwrap(); - }); - - let mut cargo = File::create("rust_programs/Cargo.toml").unwrap(); - cargo - .write_all( - format!( - r#"[package] -name = "intrinsic-test-programs" -version = "{version}" -authors = [{authors}] -license = "{license}" -edition = "2018" -[workspace] -[dependencies] -core_arch = {{ path = "../crates/core_arch" }} -{binaries}"#, - version = env!("CARGO_PKG_VERSION"), - authors = env!("CARGO_PKG_AUTHORS") - .split(":") - .format_with(", ", |author, fmt| fmt(&format_args!("\"{author}\""))), - license = env!("CARGO_PKG_LICENSE"), - binaries = intrinsics - .iter() - .map(|i| { - format!( - r#"[[bin]] -name = "{intrinsic}" -path = "{intrinsic}/main.rs""#, - intrinsic = i.name - ) - }) - .collect::>() - .join("\n") - ) - .into_bytes() - .as_slice(), - ) - .unwrap(); - - let toolchain = match toolchain { - None => return true, - Some(t) => t, - }; - - /* If there has been a linker explicitly set from the command line then - * we want to set it via setting it in the RUSTFLAGS*/ - - let cargo_command = format!( - "cargo {toolchain} build --target {target} --release", - toolchain = toolchain, - target = target - ); - - let mut command = Command::new("sh"); - command - .current_dir("rust_programs") - .arg("-c") - .arg(cargo_command); - - let mut rust_flags = "-Cdebuginfo=0".to_string(); - if let Some(linker) = linker { - rust_flags.push_str(" -C linker="); - rust_flags.push_str(linker); - rust_flags.push_str(" -C link-args=-static"); - - command.env("CPPFLAGS", "-fuse-ld=lld"); - } - - command.env("RUSTFLAGS", rust_flags); - let output = command.output(); - - if let Ok(output) = output { - if output.status.success() { - true - } else { - error!( - "Failed to compile code for rust intrinsics\n\nstdout:\n{}\n\nstderr:\n{}", - std::str::from_utf8(&output.stdout).unwrap_or(""), - std::str::from_utf8(&output.stderr).unwrap_or("") - ); - false - } - } else { - error!("Command failed: {:#?}", output); - false - } -} - -/// Intrinsic test tool -#[derive(clap::Parser)] -#[command( - name = "Intrinsic test tool", - about = "Generates Rust and C programs for intrinsics and compares the output" -)] -struct Cli { - /// The input file containing the intrinsics - input: PathBuf, - - /// The rust toolchain to use for building the rust code - #[arg(long)] - toolchain: Option, - - /// The C++ compiler to use for compiling the c++ code - #[arg(long, default_value_t = String::from("clang++"))] - cppcompiler: String, - - /// Run the C programs under emulation with this command - #[arg(long)] - runner: Option, - - /// Filename for a list of intrinsics to skip (one per line) - #[arg(long)] - skip: Option, - - /// Regenerate test programs, but don't build or run them - #[arg(long)] - generate_only: bool, - - /// Pass a target the test suite - #[arg(long, default_value_t = String::from("aarch64-unknown-linux-gnu"))] - target: String, - - /// Set the linker - #[arg(long)] - linker: Option, - - /// Set the sysroot for the C++ compiler - #[arg(long)] - cxx_toolchain_dir: Option, -} +mod arm; +mod common; fn main() { pretty_env_logger::init(); - - let args: Cli = clap::Parser::parse(); - - let filename = args.input; - let c_runner = args.runner.unwrap_or_default(); - let target: &str = args.target.as_str(); - let linker = args.linker.as_deref(); - let cxx_toolchain_dir = args.cxx_toolchain_dir; - - let skip = if let Some(filename) = args.skip { - let data = std::fs::read_to_string(&filename).expect("Failed to open file"); - data.lines() - .map(str::trim) - .filter(|s| !s.contains('#')) - .map(String::from) - .collect_vec() - } else { - Default::default() - }; - let a32 = target.contains("v7"); - let mut intrinsics = get_neon_intrinsics(&filename).expect("Error parsing input file"); - - intrinsics.sort_by(|a, b| a.name.cmp(&b.name)); - - let mut intrinsics = intrinsics - .into_iter() - // Not sure how we would compare intrinsic that returns void. - .filter(|i| i.results.kind() != TypeKind::Void) - .filter(|i| i.results.kind() != TypeKind::BFloat) - .filter(|i| !i.arguments.iter().any(|a| a.ty.kind() == TypeKind::BFloat)) - // Skip pointers for now, we would probably need to look at the return - // type to work out how many elements we need to point to. - .filter(|i| !i.arguments.iter().any(|a| a.is_ptr())) - .filter(|i| !i.arguments.iter().any(|a| a.ty.inner_size() == 128)) - .filter(|i| !skip.contains(&i.name)) - .filter(|i| !(a32 && i.a64_only)) - .collect::>(); - intrinsics.dedup(); - - let (toolchain, cpp_compiler) = if args.generate_only { - (None, None) - } else { - ( - Some(args.toolchain.map_or_else(String::new, |t| format!("+{t}"))), - Some(args.cppcompiler), - ) - }; - - let notices = build_notices("// "); - - if !build_c( - ¬ices, - &intrinsics, - cpp_compiler.as_deref(), - target, - cxx_toolchain_dir.as_deref(), - ) { - std::process::exit(2); - } - - if !build_rust(¬ices, &intrinsics, toolchain.as_deref(), target, linker) { - std::process::exit(3); - } - - if let Some(ref toolchain) = toolchain { - if !compare_outputs(&intrinsics, toolchain, &c_runner, target) { - std::process::exit(1) - } - } -} - -enum FailureReason { - RunC(String), - RunRust(String), - Difference(String, String, String), -} - -fn compare_outputs( - intrinsics: &Vec, - toolchain: &str, - runner: &str, - target: &str, -) -> bool { - let intrinsics = intrinsics - .par_iter() - .filter_map(|intrinsic| { - let c = Command::new("sh") - .arg("-c") - .arg(format!( - "{runner} ./c_programs/{intrinsic}", - runner = runner, - intrinsic = intrinsic.name, - )) - .output(); - - let rust = Command::new("sh") - .current_dir("rust_programs") - .arg("-c") - .arg(format!( - "cargo {toolchain} run --target {target} --bin {intrinsic} --release", - intrinsic = intrinsic.name, - toolchain = toolchain, - target = target - )) - .env("RUSTFLAGS", "-Cdebuginfo=0") - .output(); - - let (c, rust) = match (c, rust) { - (Ok(c), Ok(rust)) => (c, rust), - a => panic!("{a:#?}"), - }; - - if !c.status.success() { - error!( - "Failed to run C program for intrinsic {intrinsic}\nstdout: {stdout}\nstderr: {stderr}", - intrinsic = intrinsic.name, - stdout = std::str::from_utf8(&c.stdout).unwrap_or(""), - stderr = std::str::from_utf8(&c.stderr).unwrap_or(""), - ); - return Some(FailureReason::RunC(intrinsic.name.clone())); - } - - if !rust.status.success() { - error!( - "Failed to run Rust program for intrinsic {intrinsic}\nstdout: {stdout}\nstderr: {stderr}", - intrinsic = intrinsic.name, - stdout = std::str::from_utf8(&rust.stdout).unwrap_or(""), - stderr = std::str::from_utf8(&rust.stderr).unwrap_or(""), - ); - return Some(FailureReason::RunRust(intrinsic.name.clone())); - } - - info!("Comparing intrinsic: {}", intrinsic.name); - - let c = std::str::from_utf8(&c.stdout) - .unwrap() - .to_lowercase() - .replace("-nan", "nan"); - let rust = std::str::from_utf8(&rust.stdout) - .unwrap() - .to_lowercase() - .replace("-nan", "nan"); - - if c == rust { - None - } else { - Some(FailureReason::Difference(intrinsic.name.clone(), c, rust)) - } - }) - .collect::>(); - - intrinsics.iter().for_each(|reason| match reason { - FailureReason::Difference(intrinsic, c, rust) => { - println!("Difference for intrinsic: {intrinsic}"); - let diff = diff::lines(c, rust); - diff.iter().for_each(|diff| match diff { - diff::Result::Left(c) => println!("C: {c}"), - diff::Result::Right(rust) => println!("Rust: {rust}"), - diff::Result::Both(_, _) => (), - }); - println!("****************************************************************"); - } - FailureReason::RunC(intrinsic) => { - println!("Failed to run C program for intrinsic {intrinsic}") - } - FailureReason::RunRust(intrinsic) => { - println!("Failed to run rust program for intrinsic {intrinsic}") - } - }); - println!("{} differences found", intrinsics.len()); - intrinsics.is_empty() + arm::test() } diff --git a/library/stdarch/crates/intrinsic-test/src/types.rs b/library/stdarch/crates/intrinsic-test/src/types.rs deleted file mode 100644 index d5bf7c8c641..00000000000 --- a/library/stdarch/crates/intrinsic-test/src/types.rs +++ /dev/null @@ -1,508 +0,0 @@ -use std::fmt; -use std::str::FromStr; - -use itertools::Itertools as _; - -use crate::Language; -use crate::format::Indentation; -use crate::values::value_for_array; - -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum TypeKind { - BFloat, - Float, - Int, - UInt, - Poly, - Void, -} - -impl FromStr for TypeKind { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "bfloat" => Ok(Self::BFloat), - "float" => Ok(Self::Float), - "int" => Ok(Self::Int), - "poly" => Ok(Self::Poly), - "uint" | "unsigned" => Ok(Self::UInt), - "void" => Ok(Self::Void), - _ => Err(format!("Impossible to parse argument kind {s}")), - } - } -} - -impl fmt::Display for TypeKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::BFloat => "bfloat", - Self::Float => "float", - Self::Int => "int", - Self::UInt => "uint", - Self::Poly => "poly", - Self::Void => "void", - } - ) - } -} - -impl TypeKind { - /// Gets the type part of a c typedef for a type that's in the form of {type}{size}_t. - pub fn c_prefix(&self) -> &str { - match self { - Self::Float => "float", - Self::Int => "int", - Self::UInt => "uint", - Self::Poly => "poly", - _ => unreachable!("Not used: {:#?}", self), - } - } - - /// Gets the rust prefix for the type kind i.e. i, u, f. - pub fn rust_prefix(&self) -> &str { - match self { - Self::Float => "f", - Self::Int => "i", - Self::UInt => "u", - Self::Poly => "u", - _ => unreachable!("Unused type kind: {:#?}", self), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum IntrinsicType { - Ptr { - constant: bool, - child: Box, - }, - Type { - constant: bool, - kind: TypeKind, - /// The bit length of this type (e.g. 32 for u32). - bit_len: Option, - - /// Length of the SIMD vector (i.e. 4 for uint32x4_t), A value of `None` - /// means this is not a simd type. A `None` can be assumed to be 1, - /// although in some places a distinction is needed between `u64` and - /// `uint64x1_t` this signals that. - simd_len: Option, - - /// The number of rows for SIMD matrices (i.e. 2 for uint8x8x2_t). - /// A value of `None` represents a type that does not contain any - /// rows encoded in the type (e.g. uint8x8_t). - /// A value of `None` can be assumed to be 1 though. - vec_len: Option, - }, -} - -impl IntrinsicType { - /// Get the TypeKind for this type, recursing into pointers. - pub fn kind(&self) -> TypeKind { - match *self { - IntrinsicType::Ptr { ref child, .. } => child.kind(), - IntrinsicType::Type { kind, .. } => kind, - } - } - - /// Get the size of a single element inside this type, recursing into - /// pointers, i.e. a pointer to a u16 would be 16 rather than the size - /// of a pointer. - pub fn inner_size(&self) -> u32 { - match self { - IntrinsicType::Ptr { child, .. } => child.inner_size(), - IntrinsicType::Type { - bit_len: Some(bl), .. - } => *bl, - _ => unreachable!(""), - } - } - - pub fn num_lanes(&self) -> u32 { - match *self { - IntrinsicType::Ptr { ref child, .. } => child.num_lanes(), - IntrinsicType::Type { - simd_len: Some(sl), .. - } => sl, - _ => 1, - } - } - - pub fn num_vectors(&self) -> u32 { - match *self { - IntrinsicType::Ptr { ref child, .. } => child.num_vectors(), - IntrinsicType::Type { - vec_len: Some(vl), .. - } => vl, - _ => 1, - } - } - - /// Determine if the type is a simd type, this will treat a type such as - /// `uint64x1` as simd. - pub fn is_simd(&self) -> bool { - match *self { - IntrinsicType::Ptr { ref child, .. } => child.is_simd(), - IntrinsicType::Type { - simd_len: None, - vec_len: None, - .. - } => false, - _ => true, - } - } - - pub fn is_ptr(&self) -> bool { - match *self { - IntrinsicType::Ptr { .. } => true, - IntrinsicType::Type { .. } => false, - } - } - - pub fn c_scalar_type(&self) -> String { - format!( - "{prefix}{bits}_t", - prefix = self.kind().c_prefix(), - bits = self.inner_size() - ) - } - - pub fn rust_scalar_type(&self) -> String { - format!( - "{prefix}{bits}", - prefix = self.kind().rust_prefix(), - bits = self.inner_size() - ) - } - - /// Gets a string containing the typename for this type in C format. - pub fn c_type(&self) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.c_type(), - IntrinsicType::Type { - constant, - kind, - bit_len: Some(bit_len), - simd_len: None, - vec_len: None, - .. - } => format!( - "{}{}{}_t", - if *constant { "const " } else { "" }, - kind.c_prefix(), - bit_len - ), - IntrinsicType::Type { - kind, - bit_len: Some(bit_len), - simd_len: Some(simd_len), - vec_len: None, - .. - } => format!("{}{bit_len}x{simd_len}_t", kind.c_prefix()), - IntrinsicType::Type { - kind, - bit_len: Some(bit_len), - simd_len: Some(simd_len), - vec_len: Some(vec_len), - .. - } => format!("{}{bit_len}x{simd_len}x{vec_len}_t", kind.c_prefix()), - _ => todo!("{:#?}", self), - } - } - - pub fn c_single_vector_type(&self) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.c_single_vector_type(), - IntrinsicType::Type { - kind, - bit_len: Some(bit_len), - simd_len: Some(simd_len), - vec_len: Some(_), - .. - } => format!("{}{bit_len}x{simd_len}_t", kind.c_prefix()), - _ => unreachable!("Shouldn't be called on this type"), - } - } - - pub fn rust_type(&self) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.c_type(), - IntrinsicType::Type { - kind, - bit_len: Some(bit_len), - simd_len: None, - vec_len: None, - .. - } => format!("{}{bit_len}", kind.rust_prefix()), - IntrinsicType::Type { - kind, - bit_len: Some(bit_len), - simd_len: Some(simd_len), - vec_len: None, - .. - } => format!("{}{bit_len}x{simd_len}_t", kind.c_prefix()), - IntrinsicType::Type { - kind, - bit_len: Some(bit_len), - simd_len: Some(simd_len), - vec_len: Some(vec_len), - .. - } => format!("{}{bit_len}x{simd_len}x{vec_len}_t", kind.c_prefix()), - _ => todo!("{:#?}", self), - } - } - - /// Gets a cast for this type if needs promotion. - /// This is required for 8 bit types due to printing as the 8 bit types use - /// a char and when using that in `std::cout` it will print as a character, - /// which means value of 0 will be printed as a null byte. - /// - /// This is also needed for polynomial types because we want them to be - /// printed as unsigned integers to match Rust's `Debug` impl. - pub fn c_promotion(&self) -> &str { - match *self { - IntrinsicType::Type { - kind, - bit_len: Some(8), - .. - } => match kind { - TypeKind::Int => "(int)", - TypeKind::UInt => "(unsigned int)", - TypeKind::Poly => "(unsigned int)(uint8_t)", - _ => "", - }, - IntrinsicType::Type { - kind: TypeKind::Poly, - bit_len: Some(bit_len), - .. - } => match bit_len { - 8 => unreachable!("handled above"), - 16 => "(uint16_t)", - 32 => "(uint32_t)", - 64 => "(uint64_t)", - 128 => "", - _ => panic!("invalid bit_len"), - }, - _ => "", - } - } - - /// Generates an initialiser for an array, which can be used to initialise an argument for the - /// intrinsic call. - /// - /// This is determistic based on the pass number. - /// - /// * `loads`: The number of values that need to be loaded from the argument array - /// * e.g for argument type uint32x2, loads=2 results in a string representing 4 32-bit values - /// - /// Returns a string such as - /// * `{0x1, 0x7F, 0xFF}` if `language` is `Language::C` - /// * `[0x1 as _, 0x7F as _, 0xFF as _]` if `language` is `Language::Rust` - pub fn populate_random( - &self, - indentation: Indentation, - loads: u32, - language: &Language, - ) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.populate_random(indentation, loads, language), - IntrinsicType::Type { - bit_len: Some(bit_len @ (8 | 16 | 32 | 64)), - kind: kind @ (TypeKind::Int | TypeKind::UInt | TypeKind::Poly), - simd_len, - vec_len, - .. - } => { - let (prefix, suffix) = match language { - Language::Rust => ("[", "]"), - Language::C => ("{", "}"), - }; - let body_indentation = indentation.nested(); - format!( - "{prefix}\n{body}\n{indentation}{suffix}", - body = (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1) + loads - 1)) - .format_with(",\n", |i, fmt| { - let src = value_for_array(*bit_len, i); - assert!(src == 0 || src.ilog2() < *bit_len); - if *kind == TypeKind::Int && (src >> (*bit_len - 1)) != 0 { - // `src` is a two's complement representation of a negative value. - let mask = !0u64 >> (64 - *bit_len); - let ones_compl = src ^ mask; - let twos_compl = ones_compl + 1; - if (twos_compl == src) && (language == &Language::C) { - // `src` is INT*_MIN. C requires `-0x7fffffff - 1` to avoid - // undefined literal overflow behaviour. - fmt(&format_args!("{body_indentation}-{ones_compl:#x} - 1")) - } else { - fmt(&format_args!("{body_indentation}-{twos_compl:#x}")) - } - } else { - fmt(&format_args!("{body_indentation}{src:#x}")) - } - }) - ) - } - IntrinsicType::Type { - kind: TypeKind::Float, - bit_len: Some(bit_len @ (16 | 32 | 64)), - simd_len, - vec_len, - .. - } => { - let (prefix, cast_prefix, cast_suffix, suffix) = match (language, bit_len) { - (&Language::Rust, 16) => ("[", "f16::from_bits(", ")", "]"), - (&Language::Rust, 32) => ("[", "f32::from_bits(", ")", "]"), - (&Language::Rust, 64) => ("[", "f64::from_bits(", ")", "]"), - (&Language::C, 16) => ("{", "cast(", ")", "}"), - (&Language::C, 32) => ("{", "cast(", ")", "}"), - (&Language::C, 64) => ("{", "cast(", ")", "}"), - _ => unreachable!(), - }; - format!( - "{prefix}\n{body}\n{indentation}{suffix}", - body = (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1) + loads - 1)) - .format_with(",\n", |i, fmt| fmt(&format_args!( - "{indentation}{cast_prefix}{src:#x}{cast_suffix}", - indentation = indentation.nested(), - src = value_for_array(*bit_len, i) - ))) - ) - } - _ => unimplemented!("populate random: {:#?}", self), - } - } - - /// Determines the load function for this type. - pub fn get_load_function(&self, armv7_p64_workaround: bool) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.get_load_function(armv7_p64_workaround), - IntrinsicType::Type { - kind: k, - bit_len: Some(bl), - simd_len, - vec_len, - .. - } => { - let quad = if simd_len.unwrap_or(1) * bl > 64 { - "q" - } else { - "" - }; - format!( - "vld{len}{quad}_{type}{size}", - type = match k { - TypeKind::UInt => "u", - TypeKind::Int => "s", - TypeKind::Float => "f", - // The ACLE doesn't support 64-bit polynomial loads on Armv7 - TypeKind::Poly => if armv7_p64_workaround && *bl == 64 {"s"} else {"p"}, - x => todo!("get_load_function TypeKind: {:#?}", x), - }, - size = bl, - quad = quad, - len = vec_len.unwrap_or(1), - ) - } - _ => todo!("get_load_function IntrinsicType: {:#?}", self), - } - } - - /// Determines the get lane function for this type. - pub fn get_lane_function(&self) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.get_lane_function(), - IntrinsicType::Type { - kind: k, - bit_len: Some(bl), - simd_len, - .. - } => { - let quad = if (simd_len.unwrap_or(1) * bl) > 64 { - "q" - } else { - "" - }; - format!( - "vget{quad}_lane_{type}{size}", - type = match k { - TypeKind::UInt => "u", - TypeKind::Int => "s", - TypeKind::Float => "f", - TypeKind::Poly => "p", - x => todo!("get_load_function TypeKind: {:#?}", x), - }, - size = bl, - quad = quad, - ) - } - _ => todo!("get_lane_function IntrinsicType: {:#?}", self), - } - } - - pub fn from_c(s: &str) -> Result { - const CONST_STR: &str = "const"; - if let Some(s) = s.strip_suffix('*') { - let (s, constant) = match s.trim().strip_suffix(CONST_STR) { - Some(stripped) => (stripped, true), - None => (s, false), - }; - let s = s.trim_end(); - Ok(IntrinsicType::Ptr { - constant, - child: Box::new(IntrinsicType::from_c(s)?), - }) - } else { - // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t] - let (mut s, constant) = match s.strip_prefix(CONST_STR) { - Some(stripped) => (stripped.trim(), true), - None => (s, false), - }; - s = s.strip_suffix("_t").unwrap_or(s); - let mut parts = s.split('x'); // [[{bitlen}], [{simdlen}], [{vec_len}] ] - let start = parts.next().ok_or("Impossible to parse type")?; - if let Some(digit_start) = start.find(|c: char| c.is_ascii_digit()) { - let (arg_kind, bit_len) = start.split_at(digit_start); - let arg_kind = arg_kind.parse::()?; - let bit_len = bit_len.parse::().map_err(|err| err.to_string())?; - let simd_len = match parts.next() { - Some(part) => Some( - part.parse::() - .map_err(|_| "Couldn't parse simd_len: {part}")?, - ), - None => None, - }; - let vec_len = match parts.next() { - Some(part) => Some( - part.parse::() - .map_err(|_| "Couldn't parse vec_len: {part}")?, - ), - None => None, - }; - Ok(IntrinsicType::Type { - constant, - kind: arg_kind, - bit_len: Some(bit_len), - simd_len, - vec_len, - }) - } else { - let kind = start.parse::()?; - let bit_len = match kind { - TypeKind::Int => Some(32), - _ => None, - }; - Ok(IntrinsicType::Type { - constant, - kind: start.parse::()?, - bit_len, - simd_len: None, - vec_len: None, - }) - } - } - } -} diff --git a/library/stdarch/crates/intrinsic-test/src/values.rs b/library/stdarch/crates/intrinsic-test/src/values.rs deleted file mode 100644 index 1b614a742ef..00000000000 --- a/library/stdarch/crates/intrinsic-test/src/values.rs +++ /dev/null @@ -1,120 +0,0 @@ -/// Get a single value for an argument values array in a determistic way. -/// * `bits`: The number of bits for the type, only 8, 16, 32, 64 are valid values -/// * `index`: The position in the array we are generating for -pub fn value_for_array(bits: u32, index: u32) -> u64 { - let index = index as usize; - match bits { - 8 => VALUES_8[index % VALUES_8.len()].into(), - 16 => VALUES_16[index % VALUES_16.len()].into(), - 32 => VALUES_32[index % VALUES_32.len()].into(), - 64 => VALUES_64[index % VALUES_64.len()], - _ => unimplemented!("value_for_array(bits: {bits}, ..)"), - } -} - -pub const VALUES_8: &[u8] = &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0xf0, 0x80, 0x3b, 0xff, -]; - -pub const VALUES_16: &[u16] = &[ - 0x0000, // 0.0 - 0x0400, // The smallest normal value. - 0x37ff, // The value just below 0.5. - 0x3800, // 0.5 - 0x3801, // The value just above 0.5. - 0x3bff, // The value just below 1.0. - 0x3c00, // 1.0 - 0x3c01, // The value just above 1.0. - 0x3e00, // 1.5 - 0x4900, // 10 - 0x7bff, // The largest finite value. - 0x7c00, // Infinity. - // NaNs. - // - Quiet NaNs - 0x7f23, 0x7e00, // - Signalling NaNs - 0x7d23, 0x7c01, // Subnormals. - // - A recognisable bit pattern. - 0x0012, // - The largest subnormal value. - 0x03ff, // - The smallest subnormal value. - 0x0001, // The same values again, but negated. - 0x8000, 0x8400, 0xb7ff, 0xb800, 0xb801, 0xbbff, 0xbc00, 0xbc01, 0xbe00, 0xc900, 0xfbff, 0xfc00, - 0xff23, 0xfe00, 0xfd23, 0xfc01, 0x8012, 0x83ff, 0x8001, -]; - -pub const VALUES_32: &[u32] = &[ - // Simple values. - 0x00000000, // 0.0 - 0x00800000, // The smallest normal value. - 0x3effffff, // The value just below 0.5. - 0x3f000000, // 0.5 - 0x3f000001, // The value just above 0.5. - 0x3f7fffff, // The value just below 1.0. - 0x3f800000, // 1.0 - 0x3f800001, // The value just above 1.0. - 0x3fc00000, // 1.5 - 0x41200000, // 10 - 0x7f8fffff, // The largest finite value. - 0x7f800000, // Infinity. - // NaNs. - // - Quiet NaNs - 0x7fd23456, 0x7fc00000, // - Signalling NaNs - 0x7f923456, 0x7f800001, // Subnormals. - // - A recognisable bit pattern. - 0x00123456, // - The largest subnormal value. - 0x007fffff, // - The smallest subnormal value. - 0x00000001, // The same values again, but negated. - 0x80000000, 0x80800000, 0xbeffffff, 0xbf000000, 0xbf000001, 0xbf7fffff, 0xbf800000, 0xbf800001, - 0xbfc00000, 0xc1200000, 0xff8fffff, 0xff800000, 0xffd23456, 0xffc00000, 0xff923456, 0xff800001, - 0x80123456, 0x807fffff, 0x80000001, -]; - -pub const VALUES_64: &[u64] = &[ - // Simple values. - 0x0000000000000000, // 0.0 - 0x0010000000000000, // The smallest normal value. - 0x3fdfffffffffffff, // The value just below 0.5. - 0x3fe0000000000000, // 0.5 - 0x3fe0000000000001, // The value just above 0.5. - 0x3fefffffffffffff, // The value just below 1.0. - 0x3ff0000000000000, // 1.0 - 0x3ff0000000000001, // The value just above 1.0. - 0x3ff8000000000000, // 1.5 - 0x4024000000000000, // 10 - 0x7fefffffffffffff, // The largest finite value. - 0x7ff0000000000000, // Infinity. - // NaNs. - // - Quiet NaNs - 0x7ff923456789abcd, - 0x7ff8000000000000, - // - Signalling NaNs - 0x7ff123456789abcd, - 0x7ff0000000000000, - // Subnormals. - // - A recognisable bit pattern. - 0x000123456789abcd, - // - The largest subnormal value. - 0x000fffffffffffff, - // - The smallest subnormal value. - 0x0000000000000001, - // The same values again, but negated. - 0x8000000000000000, - 0x8010000000000000, - 0xbfdfffffffffffff, - 0xbfe0000000000000, - 0xbfe0000000000001, - 0xbfefffffffffffff, - 0xbff0000000000000, - 0xbff0000000000001, - 0xbff8000000000000, - 0xc024000000000000, - 0xffefffffffffffff, - 0xfff0000000000000, - 0xfff923456789abcd, - 0xfff8000000000000, - 0xfff123456789abcd, - 0xfff0000000000000, - 0x800123456789abcd, - 0x800fffffffffffff, - 0x8000000000000001, -]; -- cgit 1.4.1-3-g733a5