diff options
Diffstat (limited to 'library/stdarch/crates/intrinsic-test/src')
20 files changed, 2383 insertions, 0 deletions
diff --git a/library/stdarch/crates/intrinsic-test/src/arm/compile.rs b/library/stdarch/crates/intrinsic-test/src/arm/compile.rs new file mode 100644 index 00000000000..8276cd87c1c --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/compile.rs @@ -0,0 +1,64 @@ +use crate::common::compile_c::CompilationCommandBuilder; +use crate::common::gen_c::compile_c_programs; + +pub fn compile_c_arm( + intrinsics_name_list: &[String], + compiler: &str, + target: &str, + cxx_toolchain_dir: Option<&str>, +) -> bool { + // -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations + let mut command = CompilationCommandBuilder::new() + .add_arch_flags(vec!["armv8.6-a", "crypto", "crc", "dotprod", "fp16"]) + .set_compiler(compiler) + .set_target(target) + .set_opt_level("2") + .set_cxx_toolchain_dir(cxx_toolchain_dir) + .set_project_root("c_programs") + .add_extra_flags(vec!["-ffp-contract=off", "-Wno-narrowing"]); + + if !target.contains("v7") { + command = command.add_arch_flags(vec!["faminmax", "lut", "sha3"]); + } + + /* + * 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 <stdlib.h>` + * not existing... + */ + if target.contains("aarch64_be") { + command = command + .set_linker( + cxx_toolchain_dir.unwrap_or("").to_string() + "/bin/aarch64_be-none-linux-gnu-g++", + ) + .set_include_paths(vec![ + "/include", + "/aarch64_be-none-linux-gnu/include", + "/aarch64_be-none-linux-gnu/include/c++/14.2.1", + "/aarch64_be-none-linux-gnu/include/c++/14.2.1/aarch64_be-none-linux-gnu", + "/aarch64_be-none-linux-gnu/include/c++/14.2.1/backward", + "/aarch64_be-none-linux-gnu/libc/usr/include", + ]); + } + + if !compiler.contains("clang") { + command = command.add_extra_flag("-flax-vector-conversions"); + } + + let compiler_commands = intrinsics_name_list + .iter() + .map(|intrinsic_name| { + command + .clone() + .set_input_name(intrinsic_name) + .set_output_name(intrinsic_name) + .make_string() + }) + .collect::<Vec<_>>(); + + compile_c_programs(&compiler_commands) +} diff --git a/library/stdarch/crates/intrinsic-test/src/arm/config.rs b/library/stdarch/crates/intrinsic-test/src/arm/config.rs new file mode 100644 index 00000000000..cee80374ae9 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/config.rs @@ -0,0 +1,122 @@ +pub 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 +" + ) +} + +pub const POLY128_OSTREAM_DEF: &str = r#"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; +}"#; + +// Format f16 values (and vectors containing them) in a way that is consistent with C. +pub const F16_FORMATTING_DEF: &str = 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<T: core::fmt::Debug, const N: usize>( + 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>(T); + +impl<T: DebugHexF16> core::fmt::Debug for Hex<T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + <T as DebugHexF16>::fmt(&self.0, f) + } +} + +fn debug_f16<T: DebugHexF16>(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<f16>; 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<f16>; 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)]) + } +} + "#; + +pub const AARCH_CONFIGURATIONS: &str = r#" +#![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(fmt_helpers_for_derive)] +#![feature(stdarch_neon_f16)] +"#; 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..773dabf4d75 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/intrinsic.rs @@ -0,0 +1,95 @@ +use crate::common::argument::ArgumentList; +use crate::common::indentation::Indentation; +use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; +use crate::common::intrinsic_helpers::{IntrinsicType, IntrinsicTypeDefinition, TypeKind}; +use std::ops::Deref; + +#[derive(Debug, Clone, PartialEq)] +pub struct ArmIntrinsicType(pub IntrinsicType); + +impl Deref for ArmIntrinsicType { + type Target = IntrinsicType; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl IntrinsicDefinition<ArmIntrinsicType> for Intrinsic<ArmIntrinsicType> { + fn arguments(&self) -> ArgumentList<ArmIntrinsicType> { + self.arguments.clone() + } + + fn results(&self) -> ArmIntrinsicType { + self.results.clone() + } + + fn name(&self) -> String { + self.name.clone() + } + + /// 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. + 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::<Vec<_>>() + .join(r#" << ", " << "#) + ) + }) + .collect::<Vec<_>>() + .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::<Vec<_>>() + .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 { "" }, + ) + } +} 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..0ac47484b01 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/json_parser.rs @@ -0,0 +1,137 @@ +use super::intrinsic::ArmIntrinsicType; +use crate::common::argument::{Argument, ArgumentList}; +use crate::common::constraint::Constraint; +use crate::common::intrinsic::Intrinsic; +use crate::common::intrinsic_helpers::{IntrinsicType, IntrinsicTypeDefinition}; +use serde::Deserialize; +use serde_json::Value; +use std::collections::HashMap; +use std::path::Path; + +#[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 {}, +} + +impl TryFrom<Value> for ArgPrep { + type Error = serde_json::Error; + + fn try_from(value: Value) -> Result<Self, Self::Error> { + serde_json::from_value(value) + } +} + +#[derive(Deserialize, Debug)] +struct JsonIntrinsic { + #[serde(rename = "SIMD_ISA")] + simd_isa: String, + name: String, + arguments: Vec<String>, + return_type: ReturnType, + #[serde(rename = "Arguments_Preparation")] + args_prep: Option<HashMap<String, Value>>, + #[serde(rename = "Architectures")] + architectures: Vec<String>, +} + +pub fn get_neon_intrinsics( + filename: &Path, + target: &str, +) -> Result<Vec<Intrinsic<ArmIntrinsicType>>, Box<dyn std::error::Error>> { + let file = std::fs::File::open(filename)?; + let reader = std::io::BufReader::new(file); + let json: Vec<JsonIntrinsic> = 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, target).expect("Couldn't parse JSON")) + } else { + None + } + }) + .collect(); + Ok(parsed) +} + +fn json_to_intrinsic( + mut intr: JsonIntrinsic, + target: &str, +) -> Result<Intrinsic<ArmIntrinsicType>, Box<dyn std::error::Error>> { + let name = intr.name.replace(['[', ']'], ""); + + let results = ArmIntrinsicType::from_c(&intr.return_type.value, target)?; + + let args = intr + .arguments + .into_iter() + .enumerate() + .map(|(i, arg)| { + let arg_name = Argument::<ArmIntrinsicType>::type_and_name_from_c(&arg).1; + let metadata = intr.args_prep.as_mut(); + let metadata = metadata.and_then(|a| a.remove(arg_name)); + let arg_prep: Option<ArgPrep> = metadata.and_then(|a| a.try_into().ok()); + let constraint: Option<Constraint> = arg_prep.and_then(|a| a.try_into().ok()); + + let mut arg = Argument::<ArmIntrinsicType>::from_c(i, &arg, target, constraint); + + // The JSON doesn't list immediates as const + let IntrinsicType { + ref mut constant, .. + } = arg.ty.0; + if arg.name.starts_with("imm") { + *constant = true + } + arg + }) + .collect(); + + let arguments = ArgumentList::<ArmIntrinsicType> { args }; + + Ok(Intrinsic { + name, + arguments, + results: *results, + arch_tags: intr.architectures, + }) +} + +/// ARM-specific +impl TryFrom<ArgPrep> for Constraint { + type Error = (); + + fn try_from(prep: ArgPrep) -> Result<Self, Self::Error> { + 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(()) + } + } +} 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..6aaa49ff97f --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/mod.rs @@ -0,0 +1,124 @@ +mod compile; +mod config; +mod intrinsic; +mod json_parser; +mod types; + +use crate::common::SupportedArchitectureTest; +use crate::common::cli::ProcessedCli; +use crate::common::compare::compare_outputs; +use crate::common::gen_rust::compile_rust_programs; +use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; +use crate::common::intrinsic_helpers::TypeKind; +use crate::common::write_file::{write_c_testfiles, write_rust_testfiles}; +use compile::compile_c_arm; +use config::{AARCH_CONFIGURATIONS, F16_FORMATTING_DEF, POLY128_OSTREAM_DEF, build_notices}; +use intrinsic::ArmIntrinsicType; +use json_parser::get_neon_intrinsics; + +pub struct ArmArchitectureTest { + intrinsics: Vec<Intrinsic<ArmIntrinsicType>>, + cli_options: ProcessedCli, +} + +impl SupportedArchitectureTest for ArmArchitectureTest { + fn create(cli_options: ProcessedCli) -> Box<Self> { + let a32 = cli_options.target.contains("v7"); + let mut intrinsics = get_neon_intrinsics(&cli_options.filename, &cli_options.target) + .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| !cli_options.skip.contains(&i.name)) + .filter(|i| !(a32 && i.arch_tags == vec!["A64".to_string()])) + .collect::<Vec<_>>(); + intrinsics.dedup(); + + Box::new(Self { + intrinsics, + cli_options, + }) + } + + fn build_c_file(&self) -> bool { + let compiler = self.cli_options.cpp_compiler.as_deref(); + let target = &self.cli_options.target; + let cxx_toolchain_dir = self.cli_options.cxx_toolchain_dir.as_deref(); + let c_target = "aarch64"; + + let intrinsics_name_list = write_c_testfiles( + &self + .intrinsics + .iter() + .map(|i| i as &dyn IntrinsicDefinition<_>) + .collect::<Vec<_>>(), + target, + c_target, + &["arm_neon.h", "arm_acle.h", "arm_fp16.h"], + &build_notices("// "), + &[POLY128_OSTREAM_DEF], + ); + + match compiler { + None => true, + Some(compiler) => compile_c_arm( + intrinsics_name_list.as_slice(), + compiler, + target, + cxx_toolchain_dir, + ), + } + } + + fn build_rust_file(&self) -> bool { + let rust_target = if self.cli_options.target.contains("v7") { + "arm" + } else { + "aarch64" + }; + let target = &self.cli_options.target; + let toolchain = self.cli_options.toolchain.as_deref(); + let linker = self.cli_options.linker.as_deref(); + let intrinsics_name_list = write_rust_testfiles( + self.intrinsics + .iter() + .map(|i| i as &dyn IntrinsicDefinition<_>) + .collect::<Vec<_>>(), + rust_target, + &build_notices("// "), + F16_FORMATTING_DEF, + AARCH_CONFIGURATIONS, + ); + + compile_rust_programs(intrinsics_name_list, toolchain, target, linker) + } + + fn compare_outputs(&self) -> bool { + if let Some(ref toolchain) = self.cli_options.toolchain { + let intrinsics_name_list = self + .intrinsics + .iter() + .map(|i| i.name.clone()) + .collect::<Vec<_>>(); + + compare_outputs( + &intrinsics_name_list, + toolchain, + &self.cli_options.c_runner, + &self.cli_options.target, + ) + } else { + true + } + } +} 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..9f3d6302f46 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/arm/types.rs @@ -0,0 +1,195 @@ +use super::intrinsic::ArmIntrinsicType; +use crate::common::cli::Language; +use crate::common::intrinsic_helpers::{IntrinsicType, IntrinsicTypeDefinition, TypeKind}; + +impl IntrinsicTypeDefinition for ArmIntrinsicType { + /// Gets a string containing the typename for this type in C format. + fn c_type(&self) -> String { + let prefix = self.0.kind.c_prefix(); + let const_prefix = if self.0.constant { "const " } else { "" }; + + if let (Some(bit_len), simd_len, vec_len) = + (self.0.bit_len, self.0.simd_len, self.0.vec_len) + { + match (simd_len, vec_len) { + (None, None) => format!("{const_prefix}{prefix}{bit_len}_t"), + (Some(simd), None) => format!("{prefix}{bit_len}x{simd}_t"), + (Some(simd), Some(vec)) => format!("{prefix}{bit_len}x{simd}x{vec}_t"), + (None, Some(_)) => todo!("{:#?}", self), // Likely an invalid case + } + } else { + todo!("{:#?}", self) + } + } + + fn c_single_vector_type(&self) -> String { + if let (Some(bit_len), Some(simd_len)) = (self.0.bit_len, self.0.simd_len) { + format!( + "{prefix}{bit_len}x{simd_len}_t", + prefix = self.0.kind.c_prefix() + ) + } else { + unreachable!("Shouldn't be called on this type") + } + } + + fn rust_type(&self) -> String { + let rust_prefix = self.0.kind.rust_prefix(); + let c_prefix = self.0.kind.c_prefix(); + if self.0.ptr_constant { + self.c_type() + } else if let (Some(bit_len), simd_len, vec_len) = + (self.0.bit_len, self.0.simd_len, self.0.vec_len) + { + match (simd_len, vec_len) { + (None, None) => format!("{rust_prefix}{bit_len}"), + (Some(simd), None) => format!("{c_prefix}{bit_len}x{simd}_t"), + (Some(simd), Some(vec)) => format!("{c_prefix}{bit_len}x{simd}x{vec}_t"), + (None, Some(_)) => todo!("{:#?}", self), // Likely an invalid case + } + } else { + todo!("{:#?}", self) + } + } + + /// Determines the load function for this type. + fn get_load_function(&self, language: Language) -> String { + if let IntrinsicType { + kind: k, + bit_len: Some(bl), + simd_len, + vec_len, + target, + .. + } = &self.0 + { + let quad = if simd_len.unwrap_or(1) * bl > 64 { + "q" + } else { + "" + }; + + let choose_workaround = language == Language::C && target.contains("v7"); + 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 + // if armv7 and bl == 64, use "s", else "p" + TypeKind::Poly => if choose_workaround && *bl == 64 {"s"} else {"p"}, + x => todo!("get_load_function TypeKind: {:#?}", x), + }, + size = bl, + quad = quad, + len = vec_len.unwrap_or(1), + ) + } else { + todo!("get_load_function IntrinsicType: {:#?}", self) + } + } + + /// Determines the get lane function for this type. + fn get_lane_function(&self) -> String { + if let IntrinsicType { + kind: k, + bit_len: Some(bl), + simd_len, + .. + } = &self.0 + { + 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, + ) + } else { + todo!("get_lane_function IntrinsicType: {:#?}", self) + } + } + + fn from_c(s: &str, target: &str) -> Result<Box<Self>, String> { + 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(); + let temp_return = ArmIntrinsicType::from_c(s, target); + temp_return.map(|mut op| { + let edited = op.as_mut(); + edited.0.ptr = true; + edited.0.ptr_constant = constant; + op + }) + } 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::<TypeKind>()?; + let bit_len = bit_len.parse::<u32>().map_err(|err| err.to_string())?; + let simd_len = match parts.next() { + Some(part) => Some( + part.parse::<u32>() + .map_err(|_| "Couldn't parse simd_len: {part}")?, + ), + None => None, + }; + let vec_len = match parts.next() { + Some(part) => Some( + part.parse::<u32>() + .map_err(|_| "Couldn't parse vec_len: {part}")?, + ), + None => None, + }; + Ok(Box::new(ArmIntrinsicType(IntrinsicType { + ptr: false, + ptr_constant: false, + constant, + kind: arg_kind, + bit_len: Some(bit_len), + simd_len, + vec_len, + target: target.to_string(), + }))) + } else { + let kind = start.parse::<TypeKind>()?; + let bit_len = match kind { + TypeKind::Int => Some(32), + _ => None, + }; + Ok(Box::new(ArmIntrinsicType(IntrinsicType { + ptr: false, + ptr_constant: false, + constant, + kind: start.parse::<TypeKind>()?, + bit_len, + simd_len: None, + vec_len: None, + target: target.to_string(), + }))) + } + } + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/argument.rs b/library/stdarch/crates/intrinsic-test/src/common/argument.rs new file mode 100644 index 00000000000..443ccb919f4 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/argument.rs @@ -0,0 +1,209 @@ +use super::cli::Language; +use super::constraint::Constraint; +use super::indentation::Indentation; +use super::intrinsic_helpers::IntrinsicTypeDefinition; + +/// An argument for the intrinsic. +#[derive(Debug, PartialEq, Clone)] +pub struct Argument<T: IntrinsicTypeDefinition> { + /// 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: T, + /// Any constraints that are on this argument + pub constraint: Option<Constraint>, +} + +impl<T> Argument<T> +where + T: IntrinsicTypeDefinition, +{ + pub fn to_c_type(&self) -> String { + self.ty.c_type() + } + + pub 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.constraint.is_some() + } + + 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..]) + } + + /// The binding keyword (e.g. "const" or "let") for the array of possible test inputs. + fn rust_vals_array_binding(&self) -> impl std::fmt::Display { + if self.ty.is_rust_vals_array_const() { + "const" + } else { + "let" + } + } + + /// The name (e.g. "A_VALS" or "a_vals") for the array of possible test inputs. + fn rust_vals_array_name(&self) -> impl std::fmt::Display { + if self.ty.is_rust_vals_array_const() { + format!("{}_VALS", self.name.to_uppercase()) + } else { + format!("{}_vals", self.name.to_lowercase()) + } + } + + pub fn from_c( + pos: usize, + arg: &str, + target: &str, + constraint: Option<Constraint>, + ) -> Argument<T> { + let (ty, var_name) = Self::type_and_name_from_c(arg); + + let ty = + T::from_c(ty, target).unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); + + Argument { + pos, + name: String::from(var_name), + ty: *ty, + constraint, + } + } + + fn as_call_param_c(&self) -> String { + self.ty.as_call_param_c(&self.name) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ArgumentList<T: IntrinsicTypeDefinition> { + pub args: Vec<Argument<T>>, +} + +impl<T> ArgumentList<T> +where + T: IntrinsicTypeDefinition, +{ + /// 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.iter() + .map(|arg| arg.as_call_param_c()) + .collect::<Vec<String>>() + .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.iter() + .filter(|a| !a.has_constraint()) + .map(|arg| arg.name.clone()) + .collect::<Vec<String>>() + .join(", ") + } + + pub fn as_constraint_parameters_rust(&self) -> String { + self.iter() + .filter(|a| a.has_constraint()) + .map(|arg| arg.name.clone()) + .collect::<Vec<String>>() + .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(|&arg| !arg.has_constraint()) + .map(|arg| { + 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::<Vec<_>>() + .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(|&arg| !arg.has_constraint()) + .map(|arg| { + 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::<Vec<_>>() + .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]);` + /// + /// ARM-specific + pub fn load_values_c(&self, indentation: Indentation) -> String { + self.iter() + .filter(|&arg| !arg.has_constraint()) + .map(|arg| { + format!( + "{indentation}{ty} {name} = cast<{ty}>({load}(&{name}_vals[i]));\n", + ty = arg.to_c_type(), + name = arg.name, + load = if arg.is_simd() { + arg.ty.get_load_function(Language::C) + } 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(|&arg| !arg.has_constraint()) + .map(|arg| { + 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(Language::Rust) + } else { + "*".to_string() + }, + ) + }) + .collect() + } + + pub fn iter(&self) -> std::slice::Iter<'_, Argument<T>> { + self.args.iter() + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/cli.rs b/library/stdarch/crates/intrinsic-test/src/common/cli.rs new file mode 100644 index 00000000000..1d572723008 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/cli.rs @@ -0,0 +1,113 @@ +use itertools::Itertools; +use std::path::PathBuf; + +#[derive(Debug, PartialEq)] +pub enum Language { + Rust, + C, +} + +pub enum FailureReason { + RunC(String), + RunRust(String), + Difference(String, String, String), +} + +/// Intrinsic test tool +#[derive(clap::Parser)] +#[command( + name = "Intrinsic test tool", + about = "Generates Rust and C programs for intrinsics and compares the output" +)] +pub struct Cli { + /// The input file containing the intrinsics + pub input: PathBuf, + + /// The rust toolchain to use for building the rust code + #[arg(long)] + pub toolchain: Option<String>, + + /// The C++ compiler to use for compiling the c++ code + #[arg(long, default_value_t = String::from("clang++"))] + pub cppcompiler: String, + + /// Run the C programs under emulation with this command + #[arg(long)] + pub runner: Option<String>, + + /// Filename for a list of intrinsics to skip (one per line) + #[arg(long)] + pub skip: Option<PathBuf>, + + /// Regenerate test programs, but don't build or run them + #[arg(long)] + pub generate_only: bool, + + /// Pass a target the test suite + #[arg(long, default_value_t = String::from("armv7-unknown-linux-gnueabihf"))] + pub target: String, + + /// Set the linker + #[arg(long)] + pub linker: Option<String>, + + /// Set the sysroot for the C++ compiler + #[arg(long)] + pub cxx_toolchain_dir: Option<String>, +} + +pub struct ProcessedCli { + pub filename: PathBuf, + pub toolchain: Option<String>, + pub cpp_compiler: Option<String>, + pub c_runner: String, + pub target: String, + pub linker: Option<String>, + pub cxx_toolchain_dir: Option<String>, + pub skip: Vec<String>, +} + +impl ProcessedCli { + pub fn new(cli_options: Cli) -> Self { + let filename = cli_options.input; + let c_runner = cli_options.runner.unwrap_or_default(); + let target = cli_options.target; + let linker = cli_options.linker; + let cxx_toolchain_dir = cli_options.cxx_toolchain_dir; + + let skip = if let Some(filename) = cli_options.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 (toolchain, cpp_compiler) = if cli_options.generate_only { + (None, None) + } else { + ( + Some( + cli_options + .toolchain + .map_or_else(String::new, |t| format!("+{t}")), + ), + Some(cli_options.cppcompiler), + ) + }; + + Self { + toolchain, + cpp_compiler, + c_runner, + target, + linker, + cxx_toolchain_dir, + skip, + filename, + } + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/compare.rs b/library/stdarch/crates/intrinsic-test/src/common/compare.rs new file mode 100644 index 00000000000..815ccf89fc6 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/compare.rs @@ -0,0 +1,90 @@ +use super::cli::FailureReason; +use rayon::prelude::*; +use std::process::Command; + +pub fn compare_outputs( + intrinsic_name_list: &Vec<String>, + toolchain: &str, + runner: &str, + target: &str, +) -> bool { + let intrinsics = intrinsic_name_list + .par_iter() + .filter_map(|intrinsic_name| { + let c = Command::new("sh") + .arg("-c") + .arg(format!("{runner} ./c_programs/{intrinsic_name}")) + .output(); + + let rust = Command::new("sh") + .current_dir("rust_programs") + .arg("-c") + .arg(format!( + "cargo {toolchain} run --target {target} --bin {intrinsic_name} --release", + )) + .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_name}\nstdout: {stdout}\nstderr: {stderr}", + 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_name}\nstdout: {stdout}\nstderr: {stderr}", + 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::<Vec<_>>(); + + 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/common/compile_c.rs b/library/stdarch/crates/intrinsic-test/src/common/compile_c.rs new file mode 100644 index 00000000000..aebb7b111e2 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/compile_c.rs @@ -0,0 +1,154 @@ +#[derive(Clone)] +pub struct CompilationCommandBuilder { + compiler: String, + target: Option<String>, + cxx_toolchain_dir: Option<String>, + arch_flags: Vec<String>, + optimization: String, + include_paths: Vec<String>, + project_root: Option<String>, + output: String, + input: String, + linker: Option<String>, + extra_flags: Vec<String>, +} + +impl CompilationCommandBuilder { + pub fn new() -> Self { + Self { + compiler: String::new(), + target: None, + cxx_toolchain_dir: None, + arch_flags: Vec::new(), + optimization: "2".to_string(), + include_paths: Vec::new(), + project_root: None, + output: String::new(), + input: String::new(), + linker: None, + extra_flags: Vec::new(), + } + } + + pub fn set_compiler(mut self, compiler: &str) -> Self { + self.compiler = compiler.to_string(); + self + } + + pub fn set_target(mut self, target: &str) -> Self { + self.target = Some(target.to_string()); + self + } + + pub fn set_cxx_toolchain_dir(mut self, path: Option<&str>) -> Self { + self.cxx_toolchain_dir = path.map(|p| p.to_string()); + self + } + + pub fn add_arch_flags(mut self, flags: Vec<&str>) -> Self { + let mut new_arch_flags = flags.into_iter().map(|v| v.to_string()).collect(); + self.arch_flags.append(&mut new_arch_flags); + + self + } + + pub fn set_opt_level(mut self, optimization: &str) -> Self { + self.optimization = optimization.to_string(); + self + } + + /// Sets a list of include paths for compilation. + /// The paths that are passed must be relative to the + /// "cxx_toolchain_dir" directory path. + pub fn set_include_paths(mut self, paths: Vec<&str>) -> Self { + self.include_paths = paths.into_iter().map(|path| path.to_string()).collect(); + self + } + + /// Sets the root path of all the generated test files. + pub fn set_project_root(mut self, path: &str) -> Self { + self.project_root = Some(path.to_string()); + self + } + + /// The name of the output executable, without any suffixes + pub fn set_output_name(mut self, path: &str) -> Self { + self.output = path.to_string(); + self + } + + /// The name of the input C file, without any suffixes + pub fn set_input_name(mut self, path: &str) -> Self { + self.input = path.to_string(); + self + } + + pub fn set_linker(mut self, linker: String) -> Self { + self.linker = Some(linker); + self + } + + pub fn add_extra_flags(mut self, flags: Vec<&str>) -> Self { + let mut flags: Vec<String> = flags.into_iter().map(|f| f.to_string()).collect(); + self.extra_flags.append(&mut flags); + self + } + + pub fn add_extra_flag(self, flag: &str) -> Self { + self.add_extra_flags(vec![flag]) + } +} + +impl CompilationCommandBuilder { + pub fn make_string(self) -> String { + let arch_flags = self.arch_flags.join("+"); + let flags = std::env::var("CPPFLAGS").unwrap_or("".into()); + let project_root = self.project_root.unwrap_or_default(); + let project_root_str = project_root.as_str(); + let mut output = self.output.clone(); + if self.linker.is_some() { + output += ".o" + }; + let mut command = format!( + "{} {flags} -march={arch_flags} \ + -O{} \ + -o {project_root}/{} \ + {project_root}/{}.cpp", + self.compiler, self.optimization, output, self.input, + ); + + command = command + " " + self.extra_flags.join(" ").as_str(); + + if let Some(target) = &self.target { + command = command + " --target=" + target; + } + + if let (Some(linker), Some(cxx_toolchain_dir)) = (&self.linker, &self.cxx_toolchain_dir) { + let include_args = self + .include_paths + .iter() + .map(|path| "--include-directory=".to_string() + cxx_toolchain_dir + path) + .collect::<Vec<_>>() + .join(" "); + + command = command + + " -c " + + include_args.as_str() + + " && " + + linker + + " " + + project_root_str + + "/" + + &output + + " -o " + + project_root_str + + "/" + + &self.output + + " && rm " + + project_root_str + + "/" + + &output; + } + command + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/constraint.rs b/library/stdarch/crates/intrinsic-test/src/common/constraint.rs new file mode 100644 index 00000000000..269fb7f90cb --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/constraint.rs @@ -0,0 +1,17 @@ +use serde::Deserialize; +use std::ops::Range; + +#[derive(Debug, PartialEq, Clone, Deserialize)] +pub enum Constraint { + Equal(i64), + Range(Range<i64>), +} + +impl Constraint { + pub fn to_range(&self) -> Range<i64> { + match self { + Constraint::Equal(eq) => *eq..*eq + 1, + Constraint::Range(range) => range.clone(), + } + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/gen_c.rs b/library/stdarch/crates/intrinsic-test/src/common/gen_c.rs new file mode 100644 index 00000000000..84c28cc4bf4 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/gen_c.rs @@ -0,0 +1,198 @@ +use itertools::Itertools; +use rayon::prelude::*; +use std::collections::BTreeMap; +use std::process::Command; + +use super::argument::Argument; +use super::indentation::Indentation; +use super::intrinsic::IntrinsicDefinition; +use super::intrinsic_helpers::IntrinsicTypeDefinition; + +// The number of times each intrinsic will be called. +const PASSES: u32 = 20; + +// Formats the main C program template with placeholders +pub fn format_c_main_template( + notices: &str, + header_files: &[&str], + arch_identifier: &str, + arch_specific_definitions: &[&str], + arglists: &str, + passes: &str, +) -> String { + format!( + r#"{notices}{header_files} +#include <iostream> +#include <cstring> +#include <iomanip> +#include <sstream> + +template<typename T1, typename T2> 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; +}} + +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; +}} + +#ifdef __{arch_identifier}__ +{arch_specific_definitions} +#endif + +{arglists} + +int main(int argc, char **argv) {{ +{passes} + return 0; +}}"#, + header_files = header_files + .iter() + .map(|header| format!("#include <{header}>")) + .collect::<Vec<_>>() + .join("\n"), + arch_specific_definitions = arch_specific_definitions.join("\n"), + ) +} + +pub fn compile_c_programs(compiler_commands: &[String]) -> bool { + compiler_commands + .par_iter() + .map(|compiler_command| { + 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 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 + } + }) + .find_any(|x| !x) + .is_none() +} + +// Creates directory structure and file path mappings +pub fn setup_c_file_paths(identifiers: &Vec<String>) -> BTreeMap<&String, String> { + let _ = std::fs::create_dir("c_programs"); + identifiers + .par_iter() + .map(|identifier| { + let c_filename = format!(r#"c_programs/{identifier}.cpp"#); + + (identifier, c_filename) + }) + .collect::<BTreeMap<&String, String>>() +} + +pub fn generate_c_test_loop<T: IntrinsicTypeDefinition + Sized>( + intrinsic: &dyn IntrinsicDefinition<T>, + 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 = intrinsic.arguments().load_values_c(body_indentation), + intrinsic_call = intrinsic.name(), + args = intrinsic.arguments().as_call_param_c(), + print_result = intrinsic.print_result_c(body_indentation, additional) + ) +} + +pub fn generate_c_constraint_blocks<T: IntrinsicTypeDefinition>( + intrinsic: &dyn IntrinsicDefinition<T>, + indentation: Indentation, + constraints: &[&Argument<T>], + name: String, + target: &str, +) -> String { + if let Some((current, constraints)) = constraints.split_last() { + let range = current + .constraint + .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 = generate_c_constraint_blocks( + intrinsic, + body_indentation, + constraints, + format!("{name}-{i}"), + target, + ) + ) + }) + .join("\n") + } else { + generate_c_test_loop(intrinsic, indentation, &name, PASSES, target) + } +} + +// Compiles C test programs using specified compiler +pub fn create_c_test_program<T: IntrinsicTypeDefinition>( + intrinsic: &dyn IntrinsicDefinition<T>, + header_files: &[&str], + target: &str, + c_target: &str, + notices: &str, + arch_specific_definitions: &[&str], +) -> String { + let arguments = intrinsic.arguments(); + let constraints = arguments + .iter() + .filter(|&i| i.has_constraint()) + .collect_vec(); + + let indentation = Indentation::default(); + format_c_main_template( + notices, + header_files, + c_target, + arch_specific_definitions, + intrinsic + .arguments() + .gen_arglists_c(indentation, PASSES) + .as_str(), + generate_c_constraint_blocks( + intrinsic, + indentation.nested(), + constraints.as_slice(), + Default::default(), + target, + ) + .as_str(), + ) +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/gen_rust.rs b/library/stdarch/crates/intrinsic-test/src/common/gen_rust.rs new file mode 100644 index 00000000000..a2878502ac9 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/gen_rust.rs @@ -0,0 +1,243 @@ +use itertools::Itertools; +use rayon::prelude::*; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::Write; +use std::process::Command; + +use super::argument::Argument; +use super::indentation::Indentation; +use super::intrinsic::{IntrinsicDefinition, format_f16_return_value}; +use super::intrinsic_helpers::IntrinsicTypeDefinition; + +// The number of times each intrinsic will be called. +const PASSES: u32 = 20; + +pub fn format_rust_main_template( + notices: &str, + definitions: &str, + configurations: &str, + arch_definition: &str, + arglists: &str, + passes: &str, +) -> String { + format!( + r#"{notices}#![feature(simd_ffi)] +#![feature(link_llvm_intrinsics)] +#![feature(f16)] +{configurations} +{definitions} + +use core_arch::arch::{arch_definition}::*; + +fn main() {{ +{arglists} +{passes} +}} +"#, + ) +} + +pub fn compile_rust_programs( + binaries: Vec<String>, + toolchain: Option<&str>, + target: &str, + linker: Option<&str>, +) -> bool { + 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 = binaries + .iter() + .map(|binary| { + format!( + r#"[[bin]] +name = "{binary}" +path = "{binary}/main.rs""#, + ) + }) + .collect::<Vec<_>>() + .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"); + + 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 + } +} + +// Creates directory structure and file path mappings +pub fn setup_rust_file_paths(identifiers: &Vec<String>) -> BTreeMap<&String, String> { + identifiers + .par_iter() + .map(|identifier| { + let rust_dir = format!("rust_programs/{identifier}"); + let _ = std::fs::create_dir_all(&rust_dir); + let rust_filename = format!("{rust_dir}/main.rs"); + + (identifier, rust_filename) + }) + .collect::<BTreeMap<&String, String>>() +} + +pub fn generate_rust_test_loop<T: IntrinsicTypeDefinition>( + intrinsic: &dyn IntrinsicDefinition<T>, + indentation: Indentation, + additional: &str, + passes: u32, +) -> String { + let constraints = intrinsic.arguments().as_constraint_parameters_rust(); + let constraints = if !constraints.is_empty() { + format!("::<{constraints}>") + } else { + constraints + }; + + let return_value = format_f16_return_value(intrinsic); + 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 = intrinsic.arguments().load_values_rust(indentation3), + intrinsic_call = intrinsic.name(), + const = constraints, + args = intrinsic.arguments().as_call_param_rust(), + ) +} + +pub fn generate_rust_constraint_blocks<T: IntrinsicTypeDefinition>( + intrinsic: &dyn IntrinsicDefinition<T>, + indentation: Indentation, + constraints: &[&Argument<T>], + name: String, +) -> String { + if let Some((current, constraints)) = constraints.split_last() { + let range = current + .constraint + .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 = generate_rust_constraint_blocks( + intrinsic, + body_indentation, + constraints, + format!("{name}-{i}") + ) + ) + }) + .join("\n") + } else { + generate_rust_test_loop(intrinsic, indentation, &name, PASSES) + } +} + +// Top-level function to create complete test program +pub fn create_rust_test_program<T: IntrinsicTypeDefinition>( + intrinsic: &dyn IntrinsicDefinition<T>, + target: &str, + notice: &str, + definitions: &str, + cfg: &str, +) -> String { + let arguments = intrinsic.arguments(); + let constraints = arguments + .iter() + .filter(|i| i.has_constraint()) + .collect_vec(); + + let indentation = Indentation::default(); + format_rust_main_template( + notice, + definitions, + cfg, + target, + intrinsic + .arguments() + .gen_arglists_rust(indentation.nested(), PASSES) + .as_str(), + generate_rust_constraint_blocks( + intrinsic, + indentation.nested(), + &constraints, + Default::default(), + ) + .as_str(), + ) +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/indentation.rs b/library/stdarch/crates/intrinsic-test/src/common/indentation.rs new file mode 100644 index 00000000000..9ee331d7f7a --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/indentation.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/common/intrinsic.rs b/library/stdarch/crates/intrinsic-test/src/common/intrinsic.rs new file mode 100644 index 00000000000..bc46ccfbac4 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/intrinsic.rs @@ -0,0 +1,51 @@ +use super::argument::ArgumentList; +use super::indentation::Indentation; +use super::intrinsic_helpers::{IntrinsicTypeDefinition, TypeKind}; + +/// An intrinsic +#[derive(Debug, PartialEq, Clone)] +pub struct Intrinsic<T: IntrinsicTypeDefinition> { + /// The function name of this intrinsic. + pub name: String, + + /// Any arguments for this intrinsic. + pub arguments: ArgumentList<T>, + + /// The return type of this intrinsic. + pub results: T, + + /// Any architecture-specific tags. + pub arch_tags: Vec<String>, +} + +pub trait IntrinsicDefinition<T> +where + T: IntrinsicTypeDefinition, +{ + fn arguments(&self) -> ArgumentList<T>; + + fn results(&self) -> T; + + fn name(&self) -> String; + + /// 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. + fn print_result_c(&self, _indentation: Indentation, _additional: &str) -> String; +} + +pub fn format_f16_return_value<T: IntrinsicTypeDefinition>( + intrinsic: &dyn IntrinsicDefinition<T>, +) -> String { + // 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 intrinsic.results().kind() { + TypeKind::Float if intrinsic.results().inner_size() == 16 => "debug_f16(__return_value)", + _ => "format_args!(\"{__return_value:.150?}\")", + }; + + String::from(return_value) +} diff --git a/library/stdarch/crates/intrinsic-test/src/common/intrinsic_helpers.rs b/library/stdarch/crates/intrinsic-test/src/common/intrinsic_helpers.rs new file mode 100644 index 00000000000..3d200b19461 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/intrinsic_helpers.rs @@ -0,0 +1,296 @@ +use std::fmt; +use std::ops::Deref; +use std::str::FromStr; + +use itertools::Itertools as _; + +use super::cli::Language; +use super::indentation::Indentation; +use super::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<Self, Self::Err> { + 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 struct IntrinsicType { + pub constant: bool, + + /// whether this object is a const pointer + pub ptr_constant: bool, + + pub ptr: bool, + + pub kind: TypeKind, + /// The bit length of this type (e.g. 32 for u32). + pub bit_len: Option<u32>, + + /// 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. + pub simd_len: Option<u32>, + + /// 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. + pub vec_len: Option<u32>, + + pub target: String, +} + +impl IntrinsicType { + pub fn kind(&self) -> TypeKind { + self.kind + } + + pub fn inner_size(&self) -> u32 { + if let Some(bl) = self.bit_len { + bl + } else { + unreachable!("") + } + } + + pub fn num_lanes(&self) -> u32 { + self.simd_len.unwrap_or(1) + } + + pub fn num_vectors(&self) -> u32 { + self.vec_len.unwrap_or(1) + } + + pub fn is_simd(&self) -> bool { + self.simd_len.is_some() || self.vec_len.is_some() + } + + pub fn is_ptr(&self) -> bool { + self.ptr + } + + 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() + ) + } + + pub fn c_promotion(&self) -> &str { + match *self { + IntrinsicType { + kind, + bit_len: Some(8), + .. + } => match kind { + TypeKind::Int => "(int)", + TypeKind::UInt => "(unsigned int)", + TypeKind::Poly => "(unsigned int)(uint8_t)", + _ => "", + }, + IntrinsicType { + 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"), + }, + _ => "", + } + } + + pub fn populate_random( + &self, + indentation: Indentation, + loads: u32, + language: &Language, + ) -> String { + match self { + IntrinsicType { + 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 { + 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<float16_t, uint16_t>(", ")", "}"), + (&Language::C, 32) => ("{", "cast<float, uint32_t>(", ")", "}"), + (&Language::C, 64) => ("{", "cast<double, uint64_t>(", ")", "}"), + _ => 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), + } + } + + pub fn is_rust_vals_array_const(&self) -> bool { + match self { + // Floats have to be loaded at runtime for stable NaN conversion. + IntrinsicType { + kind: TypeKind::Float, + .. + } => false, + IntrinsicType { + kind: TypeKind::Int | TypeKind::UInt | TypeKind::Poly, + .. + } => true, + _ => unimplemented!(), + } + } + + pub fn as_call_param_c(&self, name: &String) -> String { + if self.ptr { + format!("&{name}") + } else { + name.clone() + } + } +} + +pub trait IntrinsicTypeDefinition: Deref<Target = IntrinsicType> { + /// Determines the load function for this type. + /// can be implemented in an `impl` block + fn get_load_function(&self, _language: Language) -> String; + + /// can be implemented in an `impl` block + fn get_lane_function(&self) -> String; + + /// can be implemented in an `impl` block + fn from_c(_s: &str, _target: &str) -> Result<Box<Self>, String>; + + /// Gets a string containing the typename for this type in C format. + /// can be directly defined in `impl` blocks + fn c_type(&self) -> String; + + /// can be directly defined in `impl` blocks + fn c_single_vector_type(&self) -> String; + + /// can be defined in `impl` blocks + fn rust_type(&self) -> String; +} 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..5d51d3460ec --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/mod.rs @@ -0,0 +1,25 @@ +use cli::ProcessedCli; + +pub mod argument; +pub mod cli; +pub mod compare; +pub mod compile_c; +pub mod constraint; +pub mod gen_c; +pub mod gen_rust; +pub mod indentation; +pub mod intrinsic; +pub mod intrinsic_helpers; +pub mod values; +pub mod write_file; + +/// Architectures must support this trait +/// to be successfully tested. +pub trait SupportedArchitectureTest { + fn create(cli_options: ProcessedCli) -> Box<Self> + where + Self: Sized; + fn build_c_file(&self) -> bool; + fn build_rust_file(&self) -> bool; + fn compare_outputs(&self) -> bool; +} 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/common/write_file.rs b/library/stdarch/crates/intrinsic-test/src/common/write_file.rs new file mode 100644 index 00000000000..0ba3e829a6b --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/common/write_file.rs @@ -0,0 +1,66 @@ +use super::gen_c::create_c_test_program; +use super::gen_c::setup_c_file_paths; +use super::gen_rust::{create_rust_test_program, setup_rust_file_paths}; +use super::intrinsic::IntrinsicDefinition; +use super::intrinsic_helpers::IntrinsicTypeDefinition; +use std::fs::File; +use std::io::Write; + +pub fn write_file(filename: &String, code: String) { + let mut file = File::create(filename).unwrap(); + file.write_all(code.into_bytes().as_slice()).unwrap(); +} + +pub fn write_c_testfiles<T: IntrinsicTypeDefinition + Sized>( + intrinsics: &Vec<&dyn IntrinsicDefinition<T>>, + target: &str, + c_target: &str, + headers: &[&str], + notice: &str, + arch_specific_definitions: &[&str], +) -> Vec<String> { + let intrinsics_name_list = intrinsics + .iter() + .map(|i| i.name().clone()) + .collect::<Vec<_>>(); + let filename_mapping = setup_c_file_paths(&intrinsics_name_list); + + intrinsics.iter().for_each(|&i| { + let c_code = create_c_test_program( + i, + headers, + target, + c_target, + notice, + arch_specific_definitions, + ); + if let Some(filename) = filename_mapping.get(&i.name()) { + write_file(filename, c_code) + }; + }); + + intrinsics_name_list +} + +pub fn write_rust_testfiles<T: IntrinsicTypeDefinition>( + intrinsics: Vec<&dyn IntrinsicDefinition<T>>, + rust_target: &str, + notice: &str, + definitions: &str, + cfg: &str, +) -> Vec<String> { + let intrinsics_name_list = intrinsics + .iter() + .map(|i| i.name().clone()) + .collect::<Vec<_>>(); + let filename_mapping = setup_rust_file_paths(&intrinsics_name_list); + + intrinsics.iter().for_each(|&i| { + let rust_code = create_rust_test_program(i, rust_target, notice, definitions, cfg); + if let Some(filename) = filename_mapping.get(&i.name()) { + write_file(filename, rust_code) + } + }); + + intrinsics_name_list +} diff --git a/library/stdarch/crates/intrinsic-test/src/main.rs b/library/stdarch/crates/intrinsic-test/src/main.rs new file mode 100644 index 00000000000..054138a0dba --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/main.rs @@ -0,0 +1,42 @@ +#[macro_use] +extern crate log; + +mod arm; +mod common; + +use arm::ArmArchitectureTest; +use common::SupportedArchitectureTest; +use common::cli::{Cli, ProcessedCli}; + +fn main() { + pretty_env_logger::init(); + let args: Cli = clap::Parser::parse(); + let processed_cli_options = ProcessedCli::new(args); + + let test_environment_result: Option<Box<dyn SupportedArchitectureTest>> = + match processed_cli_options.target.as_str() { + "aarch64-unknown-linux-gnu" + | "armv7-unknown-linux-gnueabihf" + | "aarch64_be-unknown-linux-gnu" => { + Some(ArmArchitectureTest::create(processed_cli_options)) + } + + _ => None, + }; + + if test_environment_result.is_none() { + std::process::exit(0); + } + + let test_environment = test_environment_result.unwrap(); + + if !test_environment.build_c_file() { + std::process::exit(2); + } + if !test_environment.build_rust_file() { + std::process::exit(3); + } + if !test_environment.compare_outputs() { + std::process::exit(1); + } +} |
