From bd0a67574760de6b350351c8c156dc08b321ee96 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Tue, 25 Mar 2025 13:32:33 +0400 Subject: [PATCH 01/20] 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) --- .../intrinsic-test/src/{ => arm}/argument.rs | 8 +- crates/intrinsic-test/src/{ => arm}/format.rs | 0 .../intrinsic-test/src/{ => arm}/intrinsic.rs | 4 +- .../src/{ => arm}/json_parser.rs | 6 +- crates/intrinsic-test/src/arm/mod.rs | 665 +++++++++++++++++ crates/intrinsic-test/src/{ => arm}/types.rs | 6 +- crates/intrinsic-test/src/common/mod.rs | 2 + crates/intrinsic-test/src/common/types.rs | 5 + .../intrinsic-test/src/{ => common}/values.rs | 0 crates/intrinsic-test/src/main.rs | 673 +----------------- 10 files changed, 687 insertions(+), 682 deletions(-) rename crates/intrinsic-test/src/{ => arm}/argument.rs (98%) rename crates/intrinsic-test/src/{ => arm}/format.rs (100%) rename crates/intrinsic-test/src/{ => arm}/intrinsic.rs (98%) rename crates/intrinsic-test/src/{ => arm}/json_parser.rs (95%) create mode 100644 crates/intrinsic-test/src/arm/mod.rs rename crates/intrinsic-test/src/{ => arm}/types.rs (99%) create mode 100644 crates/intrinsic-test/src/common/mod.rs create mode 100644 crates/intrinsic-test/src/common/types.rs rename crates/intrinsic-test/src/{ => common}/values.rs (100%) diff --git a/crates/intrinsic-test/src/argument.rs b/crates/intrinsic-test/src/arm/argument.rs similarity index 98% rename from crates/intrinsic-test/src/argument.rs rename to crates/intrinsic-test/src/arm/argument.rs index 3011bbf4a3..adc93da37f 100644 --- a/crates/intrinsic-test/src/argument.rs +++ b/crates/intrinsic-test/src/arm/argument.rs @@ -1,9 +1,9 @@ use std::ops::Range; -use crate::Language; -use crate::format::Indentation; -use crate::json_parser::ArgPrep; -use crate::types::{IntrinsicType, TypeKind}; +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)] diff --git a/crates/intrinsic-test/src/format.rs b/crates/intrinsic-test/src/arm/format.rs similarity index 100% rename from crates/intrinsic-test/src/format.rs rename to crates/intrinsic-test/src/arm/format.rs diff --git a/crates/intrinsic-test/src/intrinsic.rs b/crates/intrinsic-test/src/arm/intrinsic.rs similarity index 98% rename from crates/intrinsic-test/src/intrinsic.rs rename to crates/intrinsic-test/src/arm/intrinsic.rs index b96edf1852..fce73ff4fb 100644 --- a/crates/intrinsic-test/src/intrinsic.rs +++ b/crates/intrinsic-test/src/arm/intrinsic.rs @@ -1,5 +1,5 @@ -use crate::format::Indentation; -use crate::types::{IntrinsicType, TypeKind}; +use super::format::Indentation; +use super::types::{IntrinsicType, TypeKind}; use super::argument::ArgumentList; diff --git a/crates/intrinsic-test/src/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs similarity index 95% rename from crates/intrinsic-test/src/json_parser.rs rename to crates/intrinsic-test/src/arm/json_parser.rs index 70ab51561b..32bb10e6f6 100644 --- a/crates/intrinsic-test/src/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -3,9 +3,9 @@ use std::path::Path; use serde::Deserialize; -use crate::argument::{Argument, ArgumentList}; -use crate::intrinsic::Intrinsic; -use crate::types::IntrinsicType; +use super::argument::{Argument, ArgumentList}; +use super::intrinsic::Intrinsic; +use super::types::IntrinsicType; #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs new file mode 100644 index 0000000000..2d1846e14e --- /dev/null +++ b/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/crates/intrinsic-test/src/types.rs b/crates/intrinsic-test/src/arm/types.rs similarity index 99% rename from crates/intrinsic-test/src/types.rs rename to crates/intrinsic-test/src/arm/types.rs index d5bf7c8c64..98fcac4e00 100644 --- a/crates/intrinsic-test/src/types.rs +++ b/crates/intrinsic-test/src/arm/types.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use itertools::Itertools as _; -use crate::Language; -use crate::format::Indentation; -use crate::values::value_for_array; +use super::format::Indentation; +use crate::common::types::Language; +use crate::common::values::value_for_array; #[derive(Debug, PartialEq, Copy, Clone)] pub enum TypeKind { diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs new file mode 100644 index 0000000000..f5710ca82b --- /dev/null +++ b/crates/intrinsic-test/src/common/mod.rs @@ -0,0 +1,2 @@ +pub mod types; +pub mod values; diff --git a/crates/intrinsic-test/src/common/types.rs b/crates/intrinsic-test/src/common/types.rs new file mode 100644 index 0000000000..8b3b46818e --- /dev/null +++ b/crates/intrinsic-test/src/common/types.rs @@ -0,0 +1,5 @@ +#[derive(Debug, PartialEq)] +pub enum Language { + Rust, + C, +} diff --git a/crates/intrinsic-test/src/values.rs b/crates/intrinsic-test/src/common/values.rs similarity index 100% rename from crates/intrinsic-test/src/values.rs rename to crates/intrinsic-test/src/common/values.rs diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 2d04f1cf34..a383c5304c 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -2,677 +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(); - - 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, -} +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 = 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() + arm::test() } From 7ff849714d7e75292d21102905ac7d26126e04f9 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Tue, 25 Mar 2025 21:17:56 +0400 Subject: [PATCH 02/20] Chore: Added `SupportedArchitectureTest` trait which must be implemented for different architectures. Next steps: Move the existing ARM-specific implementation into one that fits well with this trait. --- crates/intrinsic-test/src/arm/mod.rs | 45 +------------------ crates/intrinsic-test/src/common/cli.rs | 44 ++++++++++++++++++ crates/intrinsic-test/src/common/mod.rs | 2 + .../src/common/supporting_test.rs | 13 ++++++ 4 files changed, 60 insertions(+), 44 deletions(-) create mode 100644 crates/intrinsic-test/src/common/cli.rs create mode 100644 crates/intrinsic-test/src/common/supporting_test.rs diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 2d1846e14e..1131858c0d 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -6,7 +6,6 @@ pub(crate) mod types; use std::fs::File; use std::io::Write; -use std::path::PathBuf; use std::process::Command; use intrinsic::Intrinsic; @@ -17,6 +16,7 @@ use types::TypeKind; use argument::Argument; use format::Indentation; use json_parser::get_neon_intrinsics; +use crate::common::cli::Cli; // The number of times each intrinsic will be called. const PASSES: u32 = 20; @@ -443,49 +443,6 @@ path = "{intrinsic}/main.rs""#, } } -/// 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(); diff --git a/crates/intrinsic-test/src/common/cli.rs b/crates/intrinsic-test/src/common/cli.rs new file mode 100644 index 0000000000..92f0e86e81 --- /dev/null +++ b/crates/intrinsic-test/src/common/cli.rs @@ -0,0 +1,44 @@ +use std::path::PathBuf; + +/// 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, + + /// 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, + + /// Filename for a list of intrinsics to skip (one per line) + #[arg(long)] + pub skip: Option, + + /// 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("aarch64-unknown-linux-gnu"))] + pub target: String, + + /// Set the linker + #[arg(long)] + pub linker: Option, + + /// Set the sysroot for the C++ compiler + #[arg(long)] + pub cxx_toolchain_dir: Option, +} diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index f5710ca82b..4e378c9c6d 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -1,2 +1,4 @@ pub mod types; +pub mod supporting_test; pub mod values; +pub mod cli; diff --git a/crates/intrinsic-test/src/common/supporting_test.rs b/crates/intrinsic-test/src/common/supporting_test.rs new file mode 100644 index 0000000000..37a63c7a55 --- /dev/null +++ b/crates/intrinsic-test/src/common/supporting_test.rs @@ -0,0 +1,13 @@ +/// Architectures must support this trait +/// to be successfully tested. +pub trait SupportedArchitectureTest { + fn write_c_file(filename: &str); + + fn write_rust_file(filename: &str); + + fn build_c_file(filename: &str); + + fn build_rust_file(filename: &str); + + fn read_intrinsic_source_file(filename: &str); +} From 399f37b8ca072e9f33b66cf9dc8306e7ee1fe9cc Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 26 Mar 2025 19:32:49 +0400 Subject: [PATCH 03/20] chore: Added `ProcessedCli` to extract the logic to pre-process CLI struct args --- crates/intrinsic-test/src/arm/functions.rs | 532 ++++++++++++++ crates/intrinsic-test/src/arm/mod.rs | 665 ++---------------- crates/intrinsic-test/src/common/cli.rs | 57 ++ crates/intrinsic-test/src/common/mod.rs | 4 +- .../src/common/supporting_test.rs | 15 +- crates/intrinsic-test/src/main.rs | 20 +- 6 files changed, 685 insertions(+), 608 deletions(-) create mode 100644 crates/intrinsic-test/src/arm/functions.rs diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs new file mode 100644 index 0000000000..e8b6d0f0e4 --- /dev/null +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -0,0 +1,532 @@ +use std::fs::File; +use std::io::Write; +use std::process::Command; + +use itertools::Itertools; +use rayon::prelude::*; + +use super::argument::Argument; +use super::format::Indentation; +use super::intrinsic::Intrinsic; + +// 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 + } +} + +pub 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() +} + +pub 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 + } +} + +enum FailureReason { + RunC(String), + RunRust(String), + Difference(String, String, String), +} + +pub 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/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 1131858c0d..96ac3ca785 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -1,304 +1,16 @@ -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::process::Command; - +mod argument; +mod format; +mod functions; +mod intrinsic; +mod json_parser; +mod types; + +use crate::common::cli::ProcessedCli; +use crate::common::supporting_test::SupportedArchitectureTest; +use functions::{build_c, build_rust, compare_outputs}; use intrinsic::Intrinsic; -use itertools::Itertools; -use rayon::prelude::*; -use types::TypeKind; - -use argument::Argument; -use format::Indentation; use json_parser::get_neon_intrinsics; -use crate::common::cli::Cli; - -// 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 - } -} +use types::TypeKind; fn build_notices(line_prefix: &str) -> String { format!( @@ -310,313 +22,74 @@ fn build_notices(line_prefix: &str) -> String { ) } -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() +pub struct ArmTestProcessor { + intrinsics: Vec, + notices: String, + cli_options: ProcessedCli, } -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 +impl SupportedArchitectureTest for ArmTestProcessor { + fn create(cli_options: ProcessedCli) -> Self { + let a32 = cli_options.target.contains("v7"); + let mut intrinsics = + get_neon_intrinsics(&cli_options.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| !cli_options.skip.contains(&i.name)) + .filter(|i| !(a32 && i.a64_only)) + .collect::>(); + intrinsics.dedup(); + + let notices = build_notices("// "); + + Self { + intrinsics: intrinsics, + notices: notices, + cli_options: cli_options, } - } else { - error!("Command failed: {:#?}", output); - false } -} - -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), + fn build_c_file(&self) -> bool { + build_c( + &self.notices, + &self.intrinsics, + self.cli_options.cpp_compiler.as_deref(), + &self.cli_options.target, + self.cli_options.cxx_toolchain_dir.as_deref(), ) - }; - - 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); + fn build_rust_file(&self) -> bool { + build_rust( + &self.notices, + &self.intrinsics, + self.cli_options.toolchain.as_deref(), + &self.cli_options.target, + self.cli_options.linker.as_deref(), + ) } - if let Some(ref toolchain) = toolchain { - if !compare_outputs(&intrinsics, toolchain, &c_runner, target) { - std::process::exit(1) + fn compare_outputs(&self) -> bool { + if let Some(ref toolchain) = self.cli_options.toolchain { + compare_outputs( + &self.intrinsics, + toolchain, + &self.cli_options.c_runner, + &self.cli_options.target, + ) + } else { + true } } } - -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/crates/intrinsic-test/src/common/cli.rs b/crates/intrinsic-test/src/common/cli.rs index 92f0e86e81..baa21961e1 100644 --- a/crates/intrinsic-test/src/common/cli.rs +++ b/crates/intrinsic-test/src/common/cli.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use std::path::PathBuf; /// Intrinsic test tool @@ -42,3 +43,59 @@ pub struct Cli { #[arg(long)] pub cxx_toolchain_dir: Option, } + +pub struct ProcessedCli { + pub filename: PathBuf, + pub toolchain: Option, + pub cpp_compiler: Option, + pub c_runner: String, + pub target: String, + pub linker: Option, + pub cxx_toolchain_dir: Option, + pub skip: Vec, +} + +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: toolchain, + cpp_compiler: cpp_compiler, + c_runner: c_runner, + target: target, + linker: linker, + cxx_toolchain_dir: cxx_toolchain_dir, + skip: skip, + filename: filename, + } + } +} diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index 4e378c9c6d..098451d81b 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -1,4 +1,4 @@ -pub mod types; +pub mod cli; pub mod supporting_test; +pub mod types; pub mod values; -pub mod cli; diff --git a/crates/intrinsic-test/src/common/supporting_test.rs b/crates/intrinsic-test/src/common/supporting_test.rs index 37a63c7a55..92d71d89df 100644 --- a/crates/intrinsic-test/src/common/supporting_test.rs +++ b/crates/intrinsic-test/src/common/supporting_test.rs @@ -1,13 +1,10 @@ +use crate::common::cli::ProcessedCli; + /// Architectures must support this trait /// to be successfully tested. pub trait SupportedArchitectureTest { - fn write_c_file(filename: &str); - - fn write_rust_file(filename: &str); - - fn build_c_file(filename: &str); - - fn build_rust_file(filename: &str); - - fn read_intrinsic_source_file(filename: &str); + fn create(cli_options: ProcessedCli) -> Self; + fn build_c_file(&self) -> bool; + fn build_rust_file(&self) -> bool; + fn compare_outputs(&self) -> bool; } diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index a383c5304c..0bb8035b25 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -5,7 +5,25 @@ extern crate log; mod arm; mod common; +use arm::ArmTestProcessor; +use common::cli::{Cli, ProcessedCli}; +use common::supporting_test::SupportedArchitectureTest; + fn main() { pretty_env_logger::init(); - arm::test() + let args: Cli = clap::Parser::parse(); + let processed_cli_options = ProcessedCli::new(args); + + // TODO: put this in a match block to support more architectures + let test_environment = ArmTestProcessor::create(processed_cli_options); + + 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); + } } From 3e46d8021cd3d88ddcf3884973488477df7861d1 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Thu, 27 Mar 2025 22:10:00 +0400 Subject: [PATCH 04/20] chore: separated common logic within file creations, compile_c, compile_rust and compare_outputs --- crates/intrinsic-test/src/arm/config.rs | 35 ++ crates/intrinsic-test/src/arm/functions.rs | 497 +++++-------------- crates/intrinsic-test/src/arm/mod.rs | 28 +- crates/intrinsic-test/src/common/compare.rs | 109 ++++ crates/intrinsic-test/src/common/gen_c.rs | 92 ++++ crates/intrinsic-test/src/common/gen_rust.rs | 134 +++++ crates/intrinsic-test/src/common/mod.rs | 3 + 7 files changed, 501 insertions(+), 397 deletions(-) create mode 100644 crates/intrinsic-test/src/arm/config.rs create mode 100644 crates/intrinsic-test/src/common/compare.rs create mode 100644 crates/intrinsic-test/src/common/gen_c.rs create mode 100644 crates/intrinsic-test/src/common/gen_rust.rs diff --git a/crates/intrinsic-test/src/arm/config.rs b/crates/intrinsic-test/src/arm/config.rs new file mode 100644 index 0000000000..8f7467e3a7 --- /dev/null +++ b/crates/intrinsic-test/src/arm/config.rs @@ -0,0 +1,35 @@ +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; +}"#; + +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(stdarch_neon_f16)] +"#; diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index e8b6d0f0e4..995a9ae941 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -1,13 +1,13 @@ -use std::fs::File; -use std::io::Write; -use std::process::Command; - use itertools::Itertools; use rayon::prelude::*; +use std::io::Write; use super::argument::Argument; +use super::config::{AARCH_CONFIGURATIONS, POLY128_OSTREAM_DEF, build_notices}; use super::format::Indentation; use super::intrinsic::Intrinsic; +use crate::common::gen_c::{compile_c, create_c_files, generate_c_program}; +use crate::common::gen_rust::{compile_rust, create_rust_files, generate_rust_program}; // The number of times each intrinsic will be called. const PASSES: u32 = 20; @@ -52,12 +52,7 @@ fn gen_code_c( } } -fn generate_c_program( - notices: &str, - header_files: &[&str], - intrinsic: &Intrinsic, - target: &str, -) -> String { +fn generate_c_program_arm(header_files: &[&str], intrinsic: &Intrinsic, target: &str) -> String { let constraints = intrinsic .arguments .iter() @@ -65,63 +60,23 @@ fn generate_c_program( .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( + generate_c_program( + build_notices("// ").as_str(), + header_files, + "aarch64", + &[POLY128_OSTREAM_DEF], + intrinsic + .arguments + .gen_arglists_c(indentation, PASSES) + .as_str(), + gen_code_c( indentation.nested(), intrinsic, constraints.as_slice(), Default::default(), target, - ), + ) + .as_str(), ) } @@ -163,7 +118,7 @@ fn gen_code_rust( } } -fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, target: &str) -> String { +fn generate_rust_program_arm(intrinsic: &Intrinsic, target: &str) -> String { let constraints = intrinsic .arguments .iter() @@ -171,362 +126,146 @@ fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, target: &str) -> .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 + let final_target = if target.contains("v7") { + "arm" + } else { + "aarch64" + }; + generate_rust_program( + build_notices("// ").as_str(), + AARCH_CONFIGURATIONS, + final_target, + intrinsic .arguments - .gen_arglists_rust(indentation.nested(), PASSES), - passes = gen_code_rust( + .gen_arglists_rust(indentation.nested(), PASSES) + .as_str(), + gen_code_rust( indentation.nested(), intrinsic, &constraints, - Default::default() + Default::default(), ) + .as_str(), ) } -fn compile_c( - c_filename: &str, - intrinsic: &Intrinsic, +fn compile_c_arm( + intrinsics_name_list: Vec, 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 compiler_commands = intrinsics_name_list.iter().map(|intrinsic_name|{ + let c_filename = format!(r#"c_programs/{}.cpp"#, intrinsic_name); + 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 `" + ); + }; - 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" ); - }; - /* 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") + } + }; - /* `-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") - } - }; + compiler_command + }) + .collect::>(); - 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 - } + compile_c(&compiler_commands) } pub 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 + let intrinsics_name_list = intrinsics .par_iter() - .map(|i| { - let c_filename = format!(r#"c_programs/{}.cpp"#, i.name); - let mut file = File::create(&c_filename).unwrap(); + .map(|i| i.name.clone()) + .collect::>(); + let file_mapping = create_c_files(&intrinsics_name_list); - 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() + intrinsics.par_iter().for_each(|i| { + let c_code = generate_c_program_arm(&["arm_neon.h", "arm_acle.h", "arm_fp16.h"], i, target); + match file_mapping.get(&i.name) { + Some(mut file) => file.write_all(c_code.into_bytes().as_slice()).unwrap(), + None => {} + }; + }); + + match compiler { + None => true, + Some(compiler) => compile_c_arm(intrinsics_name_list, compiler, target, cxx_toolchain_dir), + } } pub 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 - } -} - -enum FailureReason { - RunC(String), - RunRust(String), - Difference(String, String, String), -} - -pub fn compare_outputs( - intrinsics: &Vec, - toolchain: &str, - runner: &str, - target: &str, -) -> bool { - let intrinsics = intrinsics + let intrinsics_name_list = 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)) - } - }) + .map(|i| i.name.clone()) .collect::>(); + let file_mapping = create_rust_files(&intrinsics_name_list); - 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}") + intrinsics.par_iter().for_each(|i| { + let c_code = generate_rust_program_arm(i, target); + match file_mapping.get(&i.name) { + Some(mut file) => file.write_all(c_code.into_bytes().as_slice()).unwrap(), + None => {} } }); - println!("{} differences found", intrinsics.len()); - intrinsics.is_empty() + + let intrinsics_name_list = intrinsics.iter().map(|i| i.name.as_str()).collect_vec(); + + compile_rust(&intrinsics_name_list, toolchain, target, linker) } diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 96ac3ca785..3f2a346daa 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -1,4 +1,5 @@ mod argument; +mod config; mod format; mod functions; mod intrinsic; @@ -6,25 +7,15 @@ mod json_parser; mod types; use crate::common::cli::ProcessedCli; +use crate::common::compare::compare_outputs; use crate::common::supporting_test::SupportedArchitectureTest; -use functions::{build_c, build_rust, compare_outputs}; +use functions::{build_c, build_rust}; use intrinsic::Intrinsic; use json_parser::get_neon_intrinsics; use types::TypeKind; -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 struct ArmTestProcessor { intrinsics: Vec, - notices: String, cli_options: ProcessedCli, } @@ -51,18 +42,14 @@ impl SupportedArchitectureTest for ArmTestProcessor { .collect::>(); intrinsics.dedup(); - let notices = build_notices("// "); - Self { intrinsics: intrinsics, - notices: notices, cli_options: cli_options, } } fn build_c_file(&self) -> bool { build_c( - &self.notices, &self.intrinsics, self.cli_options.cpp_compiler.as_deref(), &self.cli_options.target, @@ -72,7 +59,6 @@ impl SupportedArchitectureTest for ArmTestProcessor { fn build_rust_file(&self) -> bool { build_rust( - &self.notices, &self.intrinsics, self.cli_options.toolchain.as_deref(), &self.cli_options.target, @@ -82,8 +68,14 @@ impl SupportedArchitectureTest for ArmTestProcessor { 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::>(); + compare_outputs( - &self.intrinsics, + &intrinsics_name_list, toolchain, &self.cli_options.c_runner, &self.cli_options.target, diff --git a/crates/intrinsic-test/src/common/compare.rs b/crates/intrinsic-test/src/common/compare.rs new file mode 100644 index 0000000000..4ff319be8c --- /dev/null +++ b/crates/intrinsic-test/src/common/compare.rs @@ -0,0 +1,109 @@ +use rayon::prelude::*; +use std::process::Command; + +enum FailureReason { + RunC(String), + RunRust(String), + Difference(String, String, String), +} + +pub fn compare_outputs( + intrinsic_name_list: &Vec, + 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}", + runner = runner, + intrinsic_name = 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_name} --release", + intrinsic_name = 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_name}", + runner = runner, + target = target, + intrinsic_name = 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/crates/intrinsic-test/src/common/gen_c.rs b/crates/intrinsic-test/src/common/gen_c.rs new file mode 100644 index 0000000000..58ab5823ef --- /dev/null +++ b/crates/intrinsic-test/src/common/gen_c.rs @@ -0,0 +1,92 @@ +use itertools::Itertools; +use rayon::prelude::*; +use std::collections::BTreeMap; +use std::fs::File; +use std::process::Command; + +pub fn generate_c_program( + notices: &str, + header_files: &[&str], + arch_identifier: &str, + arch_specific_definitions: &[&str], + arglists: &str, + passes: &str, +) -> String { + 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; +}} + +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::>() + .join("\n"), + arch_specific_definitions = arch_specific_definitions.into_iter().join("\n"), + ) +} + +pub fn compile_c(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() +} + +pub fn create_c_files(identifiers: &Vec) -> BTreeMap<&String, File> { + identifiers + .par_iter() + .map(|identifier| { + let c_filename = format!(r#"c_programs/{}.cpp"#, identifier); + let file = File::create(&c_filename).unwrap(); + + (identifier, file) + }) + .collect::>() +} diff --git a/crates/intrinsic-test/src/common/gen_rust.rs b/crates/intrinsic-test/src/common/gen_rust.rs new file mode 100644 index 0000000000..e405ab4e64 --- /dev/null +++ b/crates/intrinsic-test/src/common/gen_rust.rs @@ -0,0 +1,134 @@ +use itertools::Itertools; +use rayon::prelude::*; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::Write; +use std::process::Command; + +pub fn generate_rust_program( + notices: &str, + configurations: &str, + arch_definition: &str, + arglists: &str, + passes: &str, +) -> String { + format!( + r#"{notices}#![feature(simd_ffi)] +#![feature(link_llvm_intrinsics)] +#![feature(f16)] +{configurations} +#![allow(non_upper_case_globals)] +use core_arch::arch::{arch_definition}::*; + +fn main() {{ +{arglists} +{passes} +}} +"#, + ) +} + +pub fn compile_rust( + binaries: &[&str], + 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::>() + .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 + } +} + +pub fn create_rust_files(identifiers: &Vec) -> BTreeMap<&String, File> { + identifiers + .par_iter() + .map(|identifier| { + let rust_dir = format!(r#"rust_programs/{}"#, identifier); + let _ = std::fs::create_dir_all(&rust_dir); + let rust_filename = format!(r#"{rust_dir}/main.rs"#); + let file = File::create(&rust_filename).unwrap(); + + (identifier, file) + }) + .collect::>() +} diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index 098451d81b..13b2854f2e 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -1,4 +1,7 @@ pub mod cli; +pub mod compare; +pub mod gen_c; +pub mod gen_rust; pub mod supporting_test; pub mod types; pub mod values; From 2777ceb809a13f53ca7d43f55b714d4bcd562c4e Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Thu, 27 Mar 2025 22:36:52 +0400 Subject: [PATCH 05/20] chore: code consolidation --- crates/intrinsic-test/src/arm/argument.rs | 3 +- crates/intrinsic-test/src/arm/config.rs | 3 +- crates/intrinsic-test/src/arm/functions.rs | 9 +- crates/intrinsic-test/src/arm/intrinsic.rs | 6 +- crates/intrinsic-test/src/arm/json_parser.rs | 8 +- crates/intrinsic-test/src/arm/mod.rs | 4 +- crates/intrinsic-test/src/common/cli.rs | 101 ---------------- crates/intrinsic-test/src/common/compare.rs | 19 +-- crates/intrinsic-test/src/common/gen_c.rs | 2 +- crates/intrinsic-test/src/common/gen_rust.rs | 6 +- crates/intrinsic-test/src/common/mod.rs | 13 ++- .../src/common/supporting_test.rs | 10 -- crates/intrinsic-test/src/common/types.rs | 108 ++++++++++++++++++ crates/intrinsic-test/src/main.rs | 4 +- 14 files changed, 137 insertions(+), 159 deletions(-) delete mode 100644 crates/intrinsic-test/src/common/cli.rs delete mode 100644 crates/intrinsic-test/src/common/supporting_test.rs diff --git a/crates/intrinsic-test/src/arm/argument.rs b/crates/intrinsic-test/src/arm/argument.rs index adc93da37f..e354ba765a 100644 --- a/crates/intrinsic-test/src/arm/argument.rs +++ b/crates/intrinsic-test/src/arm/argument.rs @@ -1,9 +1,8 @@ -use std::ops::Range; - use super::format::Indentation; use super::json_parser::ArgPrep; use super::types::{IntrinsicType, TypeKind}; use crate::common::types::Language; +use std::ops::Range; /// An argument for the intrinsic. #[derive(Debug, PartialEq, Clone)] diff --git a/crates/intrinsic-test/src/arm/config.rs b/crates/intrinsic-test/src/arm/config.rs index 8f7467e3a7..6faaf57d6c 100644 --- a/crates/intrinsic-test/src/arm/config.rs +++ b/crates/intrinsic-test/src/arm/config.rs @@ -8,8 +8,7 @@ pub fn build_notices(line_prefix: &str) -> String { ) } -pub const POLY128_OSTREAM_DEF: &str = -r#"std::ostream& operator<<(std::ostream& os, poly128_t value) { +pub const POLY128_OSTREAM_DEF: &str = r#"std::ostream& operator<<(std::ostream& os, poly128_t value) { std::stringstream temp; do { int n = value % 10; diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index 995a9ae941..a529f37aac 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -1,13 +1,12 @@ -use itertools::Itertools; -use rayon::prelude::*; -use std::io::Write; - use super::argument::Argument; use super::config::{AARCH_CONFIGURATIONS, POLY128_OSTREAM_DEF, build_notices}; use super::format::Indentation; use super::intrinsic::Intrinsic; use crate::common::gen_c::{compile_c, create_c_files, generate_c_program}; use crate::common::gen_rust::{compile_rust, create_rust_files, generate_rust_program}; +use itertools::Itertools; +use rayon::prelude::*; +use std::io::Write; // The number of times each intrinsic will be called. const PASSES: u32 = 20; @@ -156,7 +155,7 @@ fn compile_c_arm( cxx_toolchain_dir: Option<&str>, ) -> bool { let compiler_commands = intrinsics_name_list.iter().map(|intrinsic_name|{ - let c_filename = format!(r#"c_programs/{}.cpp"#, intrinsic_name); + let c_filename = format!(r#"c_programs/{intrinsic_name}.cpp"#); let flags = std::env::var("CPPFLAGS").unwrap_or("".into()); let arch_flags = if target.contains("v7") { "-march=armv8.6-a+crypto+crc+dotprod+fp16" diff --git a/crates/intrinsic-test/src/arm/intrinsic.rs b/crates/intrinsic-test/src/arm/intrinsic.rs index fce73ff4fb..6e3721e076 100644 --- a/crates/intrinsic-test/src/arm/intrinsic.rs +++ b/crates/intrinsic-test/src/arm/intrinsic.rs @@ -1,8 +1,7 @@ +use super::argument::ArgumentList; use super::format::Indentation; use super::types::{IntrinsicType, TypeKind}; -use super::argument::ArgumentList; - /// An intrinsic #[derive(Debug, PartialEq, Clone)] pub struct Intrinsic { @@ -82,8 +81,6 @@ impl Intrinsic { String::from("") }, close = if self.results.is_simd() { ")" } else { "" }, - lanes = lanes, - additional = additional, ) } @@ -135,7 +132,6 @@ impl Intrinsic { intrinsic_call = self.name, const = constraints, args = self.arguments.as_call_param_rust(), - additional = additional, ) } } diff --git a/crates/intrinsic-test/src/arm/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs index 32bb10e6f6..630af311d1 100644 --- a/crates/intrinsic-test/src/arm/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -1,11 +1,9 @@ -use std::collections::HashMap; -use std::path::Path; - -use serde::Deserialize; - use super::argument::{Argument, ArgumentList}; use super::intrinsic::Intrinsic; use super::types::IntrinsicType; +use serde::Deserialize; +use std::collections::HashMap; +use std::path::Path; #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 3f2a346daa..69c3d864d8 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -6,9 +6,9 @@ mod intrinsic; mod json_parser; mod types; -use crate::common::cli::ProcessedCli; +use crate::common::SupportedArchitectureTest; use crate::common::compare::compare_outputs; -use crate::common::supporting_test::SupportedArchitectureTest; +use crate::common::types::ProcessedCli; use functions::{build_c, build_rust}; use intrinsic::Intrinsic; use json_parser::get_neon_intrinsics; diff --git a/crates/intrinsic-test/src/common/cli.rs b/crates/intrinsic-test/src/common/cli.rs deleted file mode 100644 index baa21961e1..0000000000 --- a/crates/intrinsic-test/src/common/cli.rs +++ /dev/null @@ -1,101 +0,0 @@ -use itertools::Itertools; -use std::path::PathBuf; - -/// 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, - - /// 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, - - /// Filename for a list of intrinsics to skip (one per line) - #[arg(long)] - pub skip: Option, - - /// 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("aarch64-unknown-linux-gnu"))] - pub target: String, - - /// Set the linker - #[arg(long)] - pub linker: Option, - - /// Set the sysroot for the C++ compiler - #[arg(long)] - pub cxx_toolchain_dir: Option, -} - -pub struct ProcessedCli { - pub filename: PathBuf, - pub toolchain: Option, - pub cpp_compiler: Option, - pub c_runner: String, - pub target: String, - pub linker: Option, - pub cxx_toolchain_dir: Option, - pub skip: Vec, -} - -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: toolchain, - cpp_compiler: cpp_compiler, - c_runner: c_runner, - target: target, - linker: linker, - cxx_toolchain_dir: cxx_toolchain_dir, - skip: skip, - filename: filename, - } - } -} diff --git a/crates/intrinsic-test/src/common/compare.rs b/crates/intrinsic-test/src/common/compare.rs index 4ff319be8c..e0586af8a8 100644 --- a/crates/intrinsic-test/src/common/compare.rs +++ b/crates/intrinsic-test/src/common/compare.rs @@ -1,12 +1,7 @@ +use super::types::FailureReason; use rayon::prelude::*; use std::process::Command; -enum FailureReason { - RunC(String), - RunRust(String), - Difference(String, String, String), -} - pub fn compare_outputs( intrinsic_name_list: &Vec, toolchain: &str, @@ -18,11 +13,7 @@ pub fn compare_outputs( .filter_map(|intrinsic_name| { let c = Command::new("sh") .arg("-c") - .arg(format!( - "{runner} ./c_programs/{intrinsic_name}", - runner = runner, - intrinsic_name = intrinsic_name, - )) + .arg(format!("{runner} ./c_programs/{intrinsic_name}")) .output(); let rust = if target != "aarch64_be-unknown-linux-gnu" { @@ -31,9 +22,6 @@ pub fn compare_outputs( .arg("-c") .arg(format!( "cargo {toolchain} run --target {target} --bin {intrinsic_name} --release", - intrinsic_name = intrinsic_name, - toolchain = toolchain, - target = target )) .env("RUSTFLAGS", "-Cdebuginfo=0") .output() @@ -42,9 +30,6 @@ pub fn compare_outputs( .arg("-c") .arg(format!( "{runner} ./rust_programs/target/{target}/release/{intrinsic_name}", - runner = runner, - target = target, - intrinsic_name = intrinsic_name, )) .output() }; diff --git a/crates/intrinsic-test/src/common/gen_c.rs b/crates/intrinsic-test/src/common/gen_c.rs index 58ab5823ef..b58b68ff61 100644 --- a/crates/intrinsic-test/src/common/gen_c.rs +++ b/crates/intrinsic-test/src/common/gen_c.rs @@ -83,7 +83,7 @@ pub fn create_c_files(identifiers: &Vec) -> BTreeMap<&String, File> { identifiers .par_iter() .map(|identifier| { - let c_filename = format!(r#"c_programs/{}.cpp"#, identifier); + let c_filename = format!(r#"c_programs/{identifier}.cpp"#); let file = File::create(&c_filename).unwrap(); (identifier, file) diff --git a/crates/intrinsic-test/src/common/gen_rust.rs b/crates/intrinsic-test/src/common/gen_rust.rs index e405ab4e64..00186359f1 100644 --- a/crates/intrinsic-test/src/common/gen_rust.rs +++ b/crates/intrinsic-test/src/common/gen_rust.rs @@ -78,11 +78,7 @@ path = "{binary}/main.rs""#, /* 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 cargo_command = format!("cargo {toolchain} build --target {target} --release"); let mut command = Command::new("sh"); command diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index 13b2854f2e..b4e6a16a4e 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -1,7 +1,16 @@ -pub mod cli; +use crate::common::types::ProcessedCli; + pub mod compare; pub mod gen_c; pub mod gen_rust; -pub mod supporting_test; pub mod types; pub mod values; + +/// Architectures must support this trait +/// to be successfully tested. +pub trait SupportedArchitectureTest { + fn create(cli_options: ProcessedCli) -> Self; + fn build_c_file(&self) -> bool; + fn build_rust_file(&self) -> bool; + fn compare_outputs(&self) -> bool; +} diff --git a/crates/intrinsic-test/src/common/supporting_test.rs b/crates/intrinsic-test/src/common/supporting_test.rs deleted file mode 100644 index 92d71d89df..0000000000 --- a/crates/intrinsic-test/src/common/supporting_test.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::common::cli::ProcessedCli; - -/// Architectures must support this trait -/// to be successfully tested. -pub trait SupportedArchitectureTest { - fn create(cli_options: ProcessedCli) -> Self; - fn build_c_file(&self) -> bool; - fn build_rust_file(&self) -> bool; - fn compare_outputs(&self) -> bool; -} diff --git a/crates/intrinsic-test/src/common/types.rs b/crates/intrinsic-test/src/common/types.rs index 8b3b46818e..53bda97df4 100644 --- a/crates/intrinsic-test/src/common/types.rs +++ b/crates/intrinsic-test/src/common/types.rs @@ -1,5 +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, + + /// 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, + + /// Filename for a list of intrinsics to skip (one per line) + #[arg(long)] + pub skip: Option, + + /// 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("aarch64-unknown-linux-gnu"))] + pub target: String, + + /// Set the linker + #[arg(long)] + pub linker: Option, + + /// Set the sysroot for the C++ compiler + #[arg(long)] + pub cxx_toolchain_dir: Option, +} + +pub struct ProcessedCli { + pub filename: PathBuf, + pub toolchain: Option, + pub cpp_compiler: Option, + pub c_runner: String, + pub target: String, + pub linker: Option, + pub cxx_toolchain_dir: Option, + pub skip: Vec, +} + +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: toolchain, + cpp_compiler: cpp_compiler, + c_runner: c_runner, + target: target, + linker: linker, + cxx_toolchain_dir: cxx_toolchain_dir, + skip: skip, + filename: filename, + } + } +} diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 0bb8035b25..1234e2b558 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -6,8 +6,8 @@ mod arm; mod common; use arm::ArmTestProcessor; -use common::cli::{Cli, ProcessedCli}; -use common::supporting_test::SupportedArchitectureTest; +use common::SupportedArchitectureTest; +use common::types::{Cli, ProcessedCli}; fn main() { pretty_env_logger::init(); From a2ce02c747c4ec50d9f6a68c01025033e1d40e35 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Thu, 27 Mar 2025 23:19:23 +0400 Subject: [PATCH 06/20] chore: added match block in `src/main.rs` --- crates/intrinsic-test/src/main.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 1234e2b558..02b8740f02 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -14,8 +14,19 @@ fn main() { let args: Cli = clap::Parser::parse(); let processed_cli_options = ProcessedCli::new(args); - // TODO: put this in a match block to support more architectures - let test_environment = ArmTestProcessor::create(processed_cli_options); + let test_environment_result = match processed_cli_options.target.as_str() { + "aarch64-unknown-linux-gnu" + | "armv7-unknown-linux-gnueabihf" + | "aarch64_be-unknown-linux-gnu" => Some(ArmTestProcessor::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); From 5b20da38f61e32629d437383a9d055bbbfa2f06d Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Sun, 30 Mar 2025 12:15:29 +0400 Subject: [PATCH 07/20] fixed `too many files open` issue --- crates/intrinsic-test/src/arm/functions.rs | 37 ++++++++++++-------- crates/intrinsic-test/src/common/gen_c.rs | 9 +++-- crates/intrinsic-test/src/common/gen_rust.rs | 7 ++-- crates/intrinsic-test/src/common/mod.rs | 7 ++++ 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index a529f37aac..a23bac991f 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -2,11 +2,12 @@ use super::argument::Argument; use super::config::{AARCH_CONFIGURATIONS, POLY128_OSTREAM_DEF, build_notices}; use super::format::Indentation; use super::intrinsic::Intrinsic; -use crate::common::gen_c::{compile_c, create_c_files, generate_c_program}; -use crate::common::gen_rust::{compile_rust, create_rust_files, generate_rust_program}; +use crate::common::gen_c::{compile_c, create_c_filenames, generate_c_program}; +use crate::common::gen_rust::{compile_rust, create_rust_filenames, generate_rust_program}; +use crate::common::write_file; use itertools::Itertools; use rayon::prelude::*; -use std::io::Write; +use std::collections::BTreeMap; // The number of times each intrinsic will be called. const PASSES: u32 = 20; @@ -149,13 +150,14 @@ fn generate_rust_program_arm(intrinsic: &Intrinsic, target: &str) -> String { } fn compile_c_arm( - intrinsics_name_list: Vec, + intrinsics_name_list: &Vec, + filename_mapping: BTreeMap<&String, String>, compiler: &str, target: &str, cxx_toolchain_dir: Option<&str>, ) -> bool { - let compiler_commands = intrinsics_name_list.iter().map(|intrinsic_name|{ - let c_filename = format!(r#"c_programs/{intrinsic_name}.cpp"#); + let compiler_commands = intrinsics_name_list.iter().map(|intrinsic_name| { + let c_filename = filename_mapping.get(intrinsic_name).unwrap(); let flags = std::env::var("CPPFLAGS").unwrap_or("".into()); let arch_flags = if target.contains("v7") { "-march=armv8.6-a+crypto+crc+dotprod+fp16" @@ -223,24 +225,29 @@ pub fn build_c( target: &str, cxx_toolchain_dir: Option<&str>, ) -> bool { - let _ = std::fs::create_dir("c_programs"); let intrinsics_name_list = intrinsics .par_iter() .map(|i| i.name.clone()) .collect::>(); - let file_mapping = create_c_files(&intrinsics_name_list); + let filename_mapping = create_c_filenames(&intrinsics_name_list); intrinsics.par_iter().for_each(|i| { let c_code = generate_c_program_arm(&["arm_neon.h", "arm_acle.h", "arm_fp16.h"], i, target); - match file_mapping.get(&i.name) { - Some(mut file) => file.write_all(c_code.into_bytes().as_slice()).unwrap(), + match filename_mapping.get(&i.name) { + Some(filename) => write_file(filename, c_code), None => {} }; }); match compiler { None => true, - Some(compiler) => compile_c_arm(intrinsics_name_list, compiler, target, cxx_toolchain_dir), + Some(compiler) => compile_c_arm( + &intrinsics_name_list, + filename_mapping, + compiler, + target, + cxx_toolchain_dir, + ), } } @@ -254,12 +261,12 @@ pub fn build_rust( .par_iter() .map(|i| i.name.clone()) .collect::>(); - let file_mapping = create_rust_files(&intrinsics_name_list); + let filename_mapping = create_rust_filenames(&intrinsics_name_list); intrinsics.par_iter().for_each(|i| { - let c_code = generate_rust_program_arm(i, target); - match file_mapping.get(&i.name) { - Some(mut file) => file.write_all(c_code.into_bytes().as_slice()).unwrap(), + let rust_code = generate_rust_program_arm(i, target); + match filename_mapping.get(&i.name) { + Some(filename) => write_file(filename, rust_code), None => {} } }); diff --git a/crates/intrinsic-test/src/common/gen_c.rs b/crates/intrinsic-test/src/common/gen_c.rs index b58b68ff61..a2a4115da6 100644 --- a/crates/intrinsic-test/src/common/gen_c.rs +++ b/crates/intrinsic-test/src/common/gen_c.rs @@ -1,7 +1,6 @@ use itertools::Itertools; use rayon::prelude::*; use std::collections::BTreeMap; -use std::fs::File; use std::process::Command; pub fn generate_c_program( @@ -79,14 +78,14 @@ pub fn compile_c(compiler_commands: &[String]) -> bool { .is_none() } -pub fn create_c_files(identifiers: &Vec) -> BTreeMap<&String, File> { +pub fn create_c_filenames(identifiers: &Vec) -> BTreeMap<&String, String> { + let _ = std::fs::create_dir("c_programs"); identifiers .par_iter() .map(|identifier| { let c_filename = format!(r#"c_programs/{identifier}.cpp"#); - let file = File::create(&c_filename).unwrap(); - (identifier, file) + (identifier, c_filename) }) - .collect::>() + .collect::>() } diff --git a/crates/intrinsic-test/src/common/gen_rust.rs b/crates/intrinsic-test/src/common/gen_rust.rs index 00186359f1..3059914684 100644 --- a/crates/intrinsic-test/src/common/gen_rust.rs +++ b/crates/intrinsic-test/src/common/gen_rust.rs @@ -115,16 +115,15 @@ path = "{binary}/main.rs""#, } } -pub fn create_rust_files(identifiers: &Vec) -> BTreeMap<&String, File> { +pub fn create_rust_filenames(identifiers: &Vec) -> BTreeMap<&String, String> { identifiers .par_iter() .map(|identifier| { let rust_dir = format!(r#"rust_programs/{}"#, identifier); let _ = std::fs::create_dir_all(&rust_dir); let rust_filename = format!(r#"{rust_dir}/main.rs"#); - let file = File::create(&rust_filename).unwrap(); - (identifier, file) + (identifier, rust_filename) }) - .collect::>() + .collect::>() } diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index b4e6a16a4e..1c9f802776 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -1,4 +1,6 @@ use crate::common::types::ProcessedCli; +use std::fs::File; +use std::io::Write; pub mod compare; pub mod gen_c; @@ -14,3 +16,8 @@ pub trait SupportedArchitectureTest { fn build_rust_file(&self) -> bool; fn compare_outputs(&self) -> bool; } + +pub fn write_file(filename: &String, code: String) { + let mut file = File::create(&filename).unwrap(); + file.write_all(code.into_bytes().as_slice()).unwrap(); +} From a6c6e5ee4ddfc5eabe31d40d78f8d11eb91f15a6 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 2 Apr 2025 21:16:27 +0530 Subject: [PATCH 08/20] maintaining special list of targets which need different execution command --- crates/intrinsic-test/src/common/compare.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/intrinsic-test/src/common/compare.rs b/crates/intrinsic-test/src/common/compare.rs index e0586af8a8..72355c9fee 100644 --- a/crates/intrinsic-test/src/common/compare.rs +++ b/crates/intrinsic-test/src/common/compare.rs @@ -2,6 +2,8 @@ use super::types::FailureReason; use rayon::prelude::*; use std::process::Command; +static SPECIAL_TARGETS: [&str; 1] = ["aarch64_be-unknown-linux-gnu"]; + pub fn compare_outputs( intrinsic_name_list: &Vec, toolchain: &str, @@ -16,21 +18,25 @@ pub fn compare_outputs( .arg(format!("{runner} ./c_programs/{intrinsic_name}")) .output(); - let rust = if target != "aarch64_be-unknown-linux-gnu" { + let rust = if SPECIAL_TARGETS + .into_iter() + .find(|&special_target| special_target == target) + .is_some() + { Command::new("sh") - .current_dir("rust_programs") .arg("-c") .arg(format!( - "cargo {toolchain} run --target {target} --bin {intrinsic_name} --release", + "{runner} ./rust_programs/target/{target}/release/{intrinsic_name}", )) - .env("RUSTFLAGS", "-Cdebuginfo=0") .output() } else { Command::new("sh") + .current_dir("rust_programs") .arg("-c") .arg(format!( - "{runner} ./rust_programs/target/{target}/release/{intrinsic_name}", + "cargo {toolchain} run --target {target} --bin {intrinsic_name} --release", )) + .env("RUSTFLAGS", "-Cdebuginfo=0") .output() }; From 66d21bd89dc3cd76c52532a9bfc8f806ef814ebd Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 2 Apr 2025 21:38:05 +0530 Subject: [PATCH 09/20] rename struct for naming consistency --- crates/intrinsic-test/src/arm/mod.rs | 4 ++-- crates/intrinsic-test/src/main.rs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 69c3d864d8..770fbb2479 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -14,12 +14,12 @@ use intrinsic::Intrinsic; use json_parser::get_neon_intrinsics; use types::TypeKind; -pub struct ArmTestProcessor { +pub struct ArmArchitectureTest { intrinsics: Vec, cli_options: ProcessedCli, } -impl SupportedArchitectureTest for ArmTestProcessor { +impl SupportedArchitectureTest for ArmArchitectureTest { fn create(cli_options: ProcessedCli) -> Self { let a32 = cli_options.target.contains("v7"); let mut intrinsics = diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 02b8740f02..0816c6c39c 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -5,7 +5,7 @@ extern crate log; mod arm; mod common; -use arm::ArmTestProcessor; +use arm::ArmArchitectureTest; use common::SupportedArchitectureTest; use common::types::{Cli, ProcessedCli}; @@ -17,7 +17,9 @@ fn main() { let test_environment_result = match processed_cli_options.target.as_str() { "aarch64-unknown-linux-gnu" | "armv7-unknown-linux-gnueabihf" - | "aarch64_be-unknown-linux-gnu" => Some(ArmTestProcessor::create(processed_cli_options)), + | "aarch64_be-unknown-linux-gnu" => { + Some(ArmArchitectureTest::create(processed_cli_options)) + } _ => None, }; From 7b1b6840aeab09189fa5a08019c05abca74ebd20 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Mon, 14 Apr 2025 00:13:05 +0530 Subject: [PATCH 10/20] test commit to check if `load_Values_c` can be dissociated from target logic --- crates/intrinsic-test/src/arm/argument.rs | 27 +++++++--------------- crates/intrinsic-test/src/arm/intrinsic.rs | 4 ++-- crates/intrinsic-test/src/arm/types.rs | 25 +++++++++++++++++--- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/crates/intrinsic-test/src/arm/argument.rs b/crates/intrinsic-test/src/arm/argument.rs index e354ba765a..5edfca0b36 100644 --- a/crates/intrinsic-test/src/arm/argument.rs +++ b/crates/intrinsic-test/src/arm/argument.rs @@ -23,6 +23,7 @@ pub enum Constraint { Range(Range), } +/// ARM-specific impl TryFrom for Constraint { type Error = (); @@ -77,6 +78,7 @@ impl Argument { (arg[..split_index + 1].trim_end(), &arg[split_index + 1..]) } + // ARM-specific pub fn from_c(pos: usize, arg: &str, arg_prep: Option) -> Argument { let (ty, var_name) = Self::type_and_name_from_c(arg); @@ -208,36 +210,23 @@ impl ArgumentList { /// 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 { + /// + /// ARM-specific + pub fn load_values_c(&self, indentation: Indentation) -> 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", + "{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(armv7_p64) + arg.ty.get_load_function_c() } 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() } ) }) @@ -257,7 +246,7 @@ impl ArgumentList { name = arg.name, vals_name = arg.rust_vals_array_name(), load = if arg.is_simd() { - arg.ty.get_load_function(false) + arg.ty.get_load_function_rust() } else { "*".to_string() }, diff --git a/crates/intrinsic-test/src/arm/intrinsic.rs b/crates/intrinsic-test/src/arm/intrinsic.rs index 6e3721e076..6edd19ac10 100644 --- a/crates/intrinsic-test/src/arm/intrinsic.rs +++ b/crates/intrinsic-test/src/arm/intrinsic.rs @@ -89,7 +89,7 @@ impl Intrinsic { indentation: Indentation, additional: &str, passes: u32, - target: &str, + _target: &str, ) -> String { let body_indentation = indentation.nested(); format!( @@ -98,7 +98,7 @@ impl Intrinsic { {body_indentation}auto __return_value = {intrinsic_call}({args});\n\ {print_result}\n\ {indentation}}}", - loaded_args = self.arguments.load_values_c(body_indentation, target), + loaded_args = self.arguments.load_values_c(body_indentation), intrinsic_call = self.name, args = self.arguments.as_call_param_c(), print_result = self.print_result_c(body_indentation, additional) diff --git a/crates/intrinsic-test/src/arm/types.rs b/crates/intrinsic-test/src/arm/types.rs index 98fcac4e00..a27651a3c0 100644 --- a/crates/intrinsic-test/src/arm/types.rs +++ b/crates/intrinsic-test/src/arm/types.rs @@ -163,6 +163,7 @@ impl IntrinsicType { } } + /// Move to Argument pub fn c_scalar_type(&self) -> String { format!( "{prefix}{bits}_t", @@ -171,6 +172,7 @@ impl IntrinsicType { ) } + /// Move to Argument pub fn rust_scalar_type(&self) -> String { format!( "{prefix}{bits}", @@ -180,6 +182,8 @@ impl IntrinsicType { } /// Gets a string containing the typename for this type in C format. + /// + /// ARM-specific pub fn c_type(&self) -> String { match self { IntrinsicType::Ptr { child, .. } => child.c_type(), @@ -214,6 +218,7 @@ impl IntrinsicType { } } + /// ARM-specific pub fn c_single_vector_type(&self) -> String { match self { IntrinsicType::Ptr { child, .. } => child.c_single_vector_type(), @@ -228,6 +233,7 @@ impl IntrinsicType { } } + /// ARM-specific pub fn rust_type(&self) -> String { match self { IntrinsicType::Ptr { child, .. } => child.c_type(), @@ -377,9 +383,11 @@ impl IntrinsicType { } /// Determines the load function for this type. - pub fn get_load_function(&self, armv7_p64_workaround: bool) -> String { + /// + /// ARM-specific + fn get_load_function(&self, language: Language) -> String { match self { - IntrinsicType::Ptr { child, .. } => child.get_load_function(armv7_p64_workaround), + IntrinsicType::Ptr { child, .. } => child.get_load_function(language), IntrinsicType::Type { kind: k, bit_len: Some(bl), @@ -399,7 +407,7 @@ impl IntrinsicType { 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"}, + TypeKind::Poly => if language == Language::C && *bl == 64 {"s"} else {"p"}, x => todo!("get_load_function TypeKind: {:#?}", x), }, size = bl, @@ -411,7 +419,17 @@ impl IntrinsicType { } } + pub fn get_load_function_c(&self) -> String { + self.get_load_function(Language::C) + } + + pub fn get_load_function_rust(&self) -> String { + self.get_load_function(Language::Rust) + } + /// Determines the get lane function for this type. + /// + /// ARM-specific pub fn get_lane_function(&self) -> String { match self { IntrinsicType::Ptr { child, .. } => child.get_lane_function(), @@ -443,6 +461,7 @@ impl IntrinsicType { } } + /// ARM-specific pub fn from_c(s: &str) -> Result { const CONST_STR: &str = "const"; if let Some(s) = s.strip_suffix('*') { From 90249a3d1a13c380c1f1706701d453d75bd73109 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Mon, 14 Apr 2025 06:10:27 +0530 Subject: [PATCH 11/20] added target field within `IntrinsicType` to perform target level checking cleanly --- crates/intrinsic-test/src/arm/argument.rs | 4 ++-- crates/intrinsic-test/src/arm/json_parser.rs | 16 +++++++++++----- crates/intrinsic-test/src/arm/mod.rs | 4 ++-- crates/intrinsic-test/src/arm/types.rs | 14 +++++++++++--- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/intrinsic-test/src/arm/argument.rs b/crates/intrinsic-test/src/arm/argument.rs index 5edfca0b36..43976b8149 100644 --- a/crates/intrinsic-test/src/arm/argument.rs +++ b/crates/intrinsic-test/src/arm/argument.rs @@ -79,10 +79,10 @@ impl Argument { } // ARM-specific - pub fn from_c(pos: usize, arg: &str, arg_prep: Option) -> Argument { + pub fn from_c(pos: usize, arg: &str, arg_prep: Option, target: &String) -> Argument { let (ty, var_name) = Self::type_and_name_from_c(arg); - let ty = IntrinsicType::from_c(ty) + let ty = IntrinsicType::from_c(ty, target) .unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); let constraint = arg_prep.and_then(|a| a.try_into().ok()); diff --git a/crates/intrinsic-test/src/arm/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs index 630af311d1..b914c4fffa 100644 --- a/crates/intrinsic-test/src/arm/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -41,7 +41,10 @@ struct JsonIntrinsic { architectures: Vec, } -pub fn get_neon_intrinsics(filename: &Path) -> Result, Box> { +pub fn get_neon_intrinsics( + filename: &Path, + target: &String, +) -> 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"); @@ -50,7 +53,7 @@ pub fn get_neon_intrinsics(filename: &Path) -> Result, Box Result, Box Result> { +fn json_to_intrinsic( + mut intr: JsonIntrinsic, + target: &String, +) -> Result> { let name = intr.name.replace(['[', ']'], ""); - let results = IntrinsicType::from_c(&intr.return_type.value)?; + let results = IntrinsicType::from_c(&intr.return_type.value, target)?; let mut args_prep = intr.args_prep.as_mut(); let args = intr @@ -72,7 +78,7 @@ fn json_to_intrinsic(mut intr: JsonIntrinsic) -> Result Self { let a32 = cli_options.target.contains("v7"); - let mut intrinsics = - get_neon_intrinsics(&cli_options.filename).expect("Error parsing input file"); + 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)); diff --git a/crates/intrinsic-test/src/arm/types.rs b/crates/intrinsic-test/src/arm/types.rs index a27651a3c0..a579e9699d 100644 --- a/crates/intrinsic-test/src/arm/types.rs +++ b/crates/intrinsic-test/src/arm/types.rs @@ -97,6 +97,8 @@ pub enum IntrinsicType { /// rows encoded in the type (e.g. uint8x8_t). /// A value of `None` can be assumed to be 1 though. vec_len: Option, + + target: String, }, } @@ -393,6 +395,7 @@ impl IntrinsicType { bit_len: Some(bl), simd_len, vec_len, + target, .. } => { let quad = if simd_len.unwrap_or(1) * bl > 64 { @@ -400,6 +403,8 @@ impl IntrinsicType { } else { "" }; + + let choose_workaround = language == Language::C && target.contains("v7"); format!( "vld{len}{quad}_{type}{size}", type = match k { @@ -407,7 +412,8 @@ impl IntrinsicType { TypeKind::Int => "s", TypeKind::Float => "f", // The ACLE doesn't support 64-bit polynomial loads on Armv7 - TypeKind::Poly => if language == Language::C && *bl == 64 {"s"} else {"p"}, + // 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, @@ -462,7 +468,7 @@ impl IntrinsicType { } /// ARM-specific - pub fn from_c(s: &str) -> Result { + pub fn from_c(s: &str, target: &String) -> Result { const CONST_STR: &str = "const"; if let Some(s) = s.strip_suffix('*') { let (s, constant) = match s.trim().strip_suffix(CONST_STR) { @@ -472,7 +478,7 @@ impl IntrinsicType { let s = s.trim_end(); Ok(IntrinsicType::Ptr { constant, - child: Box::new(IntrinsicType::from_c(s)?), + child: Box::new(IntrinsicType::from_c(s, target)?), }) } else { // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t] @@ -507,6 +513,7 @@ impl IntrinsicType { bit_len: Some(bit_len), simd_len, vec_len, + target: target.to_string(), }) } else { let kind = start.parse::()?; @@ -520,6 +527,7 @@ impl IntrinsicType { bit_len, simd_len: None, vec_len: None, + target: target.to_string(), }) } } From 1791b35a22045cce4eb59e40a17a781b96a8bacc Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Mon, 14 Apr 2025 13:35:05 +0530 Subject: [PATCH 12/20] Updated `Argument::from_c` to remove `ArgPrep` specific argument --- crates/intrinsic-test/src/arm/argument.rs | 21 ++++++++++++++++++-- crates/intrinsic-test/src/arm/json_parser.rs | 17 +++++++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/crates/intrinsic-test/src/arm/argument.rs b/crates/intrinsic-test/src/arm/argument.rs index 43976b8149..24fe8c9a93 100644 --- a/crates/intrinsic-test/src/arm/argument.rs +++ b/crates/intrinsic-test/src/arm/argument.rs @@ -2,6 +2,9 @@ use super::format::Indentation; use super::json_parser::ArgPrep; use super::types::{IntrinsicType, TypeKind}; use crate::common::types::Language; +use serde::Deserialize; +use serde_json::Value; +use std::collections::HashMap; use std::ops::Range; /// An argument for the intrinsic. @@ -17,7 +20,7 @@ pub struct Argument { pub constraints: Vec, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Deserialize)] pub enum Constraint { Equal(i64), Range(Range), @@ -79,12 +82,26 @@ impl Argument { } // ARM-specific - pub fn from_c(pos: usize, arg: &str, arg_prep: Option, target: &String) -> Argument { + pub fn from_c( + pos: usize, + arg: &str, + target: &String, + metadata: Option<&mut HashMap>, + ) -> Argument { let (ty, var_name) = Self::type_and_name_from_c(arg); let ty = IntrinsicType::from_c(ty, target) .unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); + let arg_name = Argument::type_and_name_from_c(&arg).1; + let arg = metadata.and_then(|a| a.remove(arg_name)); + let arg_prep: Option = arg.and_then(|a| { + if let Value::Object(_) = a { + a.try_into().ok() + } else { + None + } + }); let constraint = arg_prep.and_then(|a| a.try_into().ok()); Argument { diff --git a/crates/intrinsic-test/src/arm/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs index b914c4fffa..001d721fa6 100644 --- a/crates/intrinsic-test/src/arm/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -2,6 +2,7 @@ use super::argument::{Argument, ArgumentList}; use super::intrinsic::Intrinsic; use super::types::IntrinsicType; use serde::Deserialize; +use serde_json::Value; use std::collections::HashMap; use std::path::Path; @@ -28,6 +29,14 @@ pub enum ArgPrep { Nothing {}, } +impl TryFrom for ArgPrep { + type Error = serde_json::Error; + + fn try_from(value: Value) -> Result { + serde_json::from_value(value) + } +} + #[derive(Deserialize, Debug)] struct JsonIntrinsic { #[serde(rename = "SIMD_ISA")] @@ -36,7 +45,7 @@ struct JsonIntrinsic { arguments: Vec, return_type: ReturnType, #[serde(rename = "Arguments_Preparation")] - args_prep: Option>, + args_prep: Option>, #[serde(rename = "Architectures")] architectures: Vec, } @@ -70,15 +79,13 @@ fn json_to_intrinsic( let results = IntrinsicType::from_c(&intr.return_type.value, target)?; - 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, target); + // let arg_name = Argument::type_and_name_from_c(&arg).1; + let mut arg = Argument::from_c(i, &arg, target, intr.args_prep.as_mut()); // The JSON doesn't list immediates as const if let IntrinsicType::Type { ref mut constant, .. From 2058ab6e377a454b49dbd3691e11d49a0995daa2 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 16 Apr 2025 14:31:42 +0530 Subject: [PATCH 13/20] introduced generic types and code refactor --- crates/intrinsic-test/src/arm/constraint.rs | 60 ++ crates/intrinsic-test/src/arm/functions.rs | 38 +- crates/intrinsic-test/src/arm/intrinsic.rs | 168 +++-- crates/intrinsic-test/src/arm/json_parser.rs | 36 +- crates/intrinsic-test/src/arm/mod.rs | 11 +- crates/intrinsic-test/src/arm/types.rs | 574 ++++-------------- .../src/{arm => common}/argument.rs | 167 ++--- .../src/{arm => common}/format.rs | 0 crates/intrinsic-test/src/common/intrinsic.rs | 91 +++ .../src/common/intrinsic_types.rs | 352 +++++++++++ crates/intrinsic-test/src/common/mod.rs | 4 + 11 files changed, 809 insertions(+), 692 deletions(-) create mode 100644 crates/intrinsic-test/src/arm/constraint.rs rename crates/intrinsic-test/src/{arm => common}/argument.rs (65%) rename crates/intrinsic-test/src/{arm => common}/format.rs (100%) create mode 100644 crates/intrinsic-test/src/common/intrinsic.rs create mode 100644 crates/intrinsic-test/src/common/intrinsic_types.rs diff --git a/crates/intrinsic-test/src/arm/constraint.rs b/crates/intrinsic-test/src/arm/constraint.rs new file mode 100644 index 0000000000..777cbd0099 --- /dev/null +++ b/crates/intrinsic-test/src/arm/constraint.rs @@ -0,0 +1,60 @@ +use super::json_parser::ArgPrep; + +use crate::common::argument::MetadataDefinition; +use serde::Deserialize; +use serde_json::Value; +use std::ops::Range; + +#[derive(Debug, PartialEq, Clone, Deserialize)] +pub enum Constraint { + Equal(i64), + Range(Range), +} + +impl Constraint { + pub fn to_range(&self) -> Range { + match self { + Constraint::Equal(eq) => *eq..*eq + 1, + Constraint::Range(range) => range.clone(), + } + } +} + +impl MetadataDefinition for Constraint { + fn from_metadata(metadata: Option) -> Vec> { + let arg_prep: Option = metadata.and_then(|a| { + if let Value::Object(_) = a { + a.try_into().ok() + } else { + None + } + }); + let constraint: Option = arg_prep.and_then(|a| a.try_into().ok()); + vec![constraint] + .into_iter() + .filter_map(|a| a) + .map(|a| Box::new(a)) + .collect() + } +} + +/// ARM-specific +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(()) + } + } +} diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index a23bac991f..8158dfd88d 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -1,9 +1,12 @@ -use super::argument::Argument; use super::config::{AARCH_CONFIGURATIONS, POLY128_OSTREAM_DEF, build_notices}; -use super::format::Indentation; -use super::intrinsic::Intrinsic; +use super::intrinsic::ArmIntrinsicType; +use crate::arm::constraint::Constraint; +use crate::common::argument::Argument; +use crate::common::format::Indentation; use crate::common::gen_c::{compile_c, create_c_filenames, generate_c_program}; use crate::common::gen_rust::{compile_rust, create_rust_filenames, generate_rust_program}; +use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; +use crate::common::intrinsic_types::IntrinsicTypeDefinition; use crate::common::write_file; use itertools::Itertools; use rayon::prelude::*; @@ -14,14 +17,14 @@ const PASSES: u32 = 20; fn gen_code_c( indentation: Indentation, - intrinsic: &Intrinsic, - constraints: &[&Argument], + intrinsic: &Intrinsic, + constraints: &[&Argument], name: String, target: &str, ) -> String { if let Some((current, constraints)) = constraints.split_last() { let range = current - .constraints + .metadata .iter() .map(|c| c.to_range()) .flat_map(|r| r.into_iter()); @@ -52,11 +55,15 @@ fn gen_code_c( } } -fn generate_c_program_arm(header_files: &[&str], intrinsic: &Intrinsic, target: &str) -> String { +fn generate_c_program_arm( + header_files: &[&str], + intrinsic: &Intrinsic, + target: &str, +) -> String { let constraints = intrinsic .arguments .iter() - .filter(|i| i.has_constraint()) + .filter(|&i| i.has_constraint()) .collect_vec(); let indentation = Indentation::default(); @@ -82,13 +89,13 @@ fn generate_c_program_arm(header_files: &[&str], intrinsic: &Intrinsic, target: fn gen_code_rust( indentation: Indentation, - intrinsic: &Intrinsic, - constraints: &[&Argument], + intrinsic: &Intrinsic, + constraints: &[&Argument], name: String, ) -> String { if let Some((current, constraints)) = constraints.split_last() { let range = current - .constraints + .metadata .iter() .map(|c| c.to_range()) .flat_map(|r| r.into_iter()); @@ -118,7 +125,10 @@ fn gen_code_rust( } } -fn generate_rust_program_arm(intrinsic: &Intrinsic, target: &str) -> String { +fn generate_rust_program_arm( + intrinsic: &Intrinsic, + target: &str, +) -> String { let constraints = intrinsic .arguments .iter() @@ -220,7 +230,7 @@ fn compile_c_arm( } pub fn build_c( - intrinsics: &Vec, + intrinsics: &Vec>, compiler: Option<&str>, target: &str, cxx_toolchain_dir: Option<&str>, @@ -252,7 +262,7 @@ pub fn build_c( } pub fn build_rust( - intrinsics: &[Intrinsic], + intrinsics: &[Intrinsic], toolchain: Option<&str>, target: &str, linker: Option<&str>, diff --git a/crates/intrinsic-test/src/arm/intrinsic.rs b/crates/intrinsic-test/src/arm/intrinsic.rs index 6edd19ac10..da20751a07 100644 --- a/crates/intrinsic-test/src/arm/intrinsic.rs +++ b/crates/intrinsic-test/src/arm/intrinsic.rs @@ -1,40 +1,83 @@ -use super::argument::ArgumentList; -use super::format::Indentation; -use super::types::{IntrinsicType, TypeKind}; +use super::constraint::Constraint; +use crate::common::argument::ArgumentList; +use crate::common::format::Indentation; +use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; +use crate::common::intrinsic_types::{ + BaseIntrinsicTypeDefinition, IntrinsicType, IntrinsicTypeDefinition, TypeKind, +}; +use crate::common::types::Language; -/// An intrinsic -#[derive(Debug, PartialEq, Clone)] -pub struct Intrinsic { - /// The function name of this intrinsic. - pub name: String, +#[derive(Debug, Clone, PartialEq)] +pub struct ArmIntrinsicType(pub IntrinsicType); - /// Any arguments for this intrinsic. - pub arguments: ArgumentList, +impl BaseIntrinsicTypeDefinition for ArmIntrinsicType { + fn kind(&self) -> TypeKind { + self.0.kind() + } + fn inner_size(&self) -> u32 { + self.0.inner_size() + } + fn num_lanes(&self) -> u32 { + self.0.num_lanes() + } + fn num_vectors(&self) -> u32 { + self.0.num_vectors() + } + fn is_simd(&self) -> bool { + self.0.is_simd() + } + fn is_ptr(&self) -> bool { + self.0.is_ptr() + } + fn c_scalar_type(&self) -> String { + self.0.c_scalar_type() + } + fn rust_scalar_type(&self) -> String { + self.0.rust_scalar_type() + } + fn c_promotion(&self) -> &str { + self.0.c_promotion() + } + fn populate_random(&self, indentation: Indentation, loads: u32, language: &Language) -> String { + self.0.populate_random(indentation, loads, language) + } + fn is_rust_vals_array_const(&self) -> bool { + self.0.is_rust_vals_array_const() + } + fn as_call_param_c(&self, name: &String) -> String { + self.0.as_call_param_c(name) + } +} - /// The return type of this intrinsic. - pub results: IntrinsicType, +impl IntrinsicDefinition for Intrinsic { + fn arguments(&self) -> ArgumentList { + self.arguments.clone() + } - /// Whether this intrinsic is only available on A64. - pub a64_only: bool, -} + fn results(&self) -> ArmIntrinsicType { + self.results.clone() + } + + fn name(&self) -> String { + self.name.clone() + } -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()) + 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()) + 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(), + cast = self.results().c_promotion(), + lane_fn = self.results().get_lane_function(), lane = idx, vector = vector, ) @@ -45,13 +88,13 @@ impl Intrinsic { }) .collect::>() .join(r#" << ", " << "#) - } else if self.results.num_lanes() > 1 { - (0..self.results.num_lanes()) + } 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(), + cast = self.results().c_promotion(), + lane_fn = self.results().get_lane_function(), lane = idx ) }) @@ -61,77 +104,26 @@ impl Intrinsic { 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()), + 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(), + 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()) + ty = if self.results().is_simd() { + format!("{}(", self.results().c_type()) } else { String::from("") }, - close = if self.results.is_simd() { ")" } else { "" }, - ) - } - - 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), - 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 - }; - - 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}-{{}}: {{:.150?}}\", 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(), + close = if self.results().is_simd() { ")" } else { "" }, ) } } diff --git a/crates/intrinsic-test/src/arm/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs index 001d721fa6..2f49b84c67 100644 --- a/crates/intrinsic-test/src/arm/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -1,6 +1,8 @@ -use super::argument::{Argument, ArgumentList}; -use super::intrinsic::Intrinsic; -use super::types::IntrinsicType; +use super::constraint::Constraint; +use super::intrinsic::ArmIntrinsicType; +use crate::common::argument::{Argument, ArgumentList}; +use crate::common::intrinsic::Intrinsic; +use crate::common::intrinsic_types::{IntrinsicType, IntrinsicTypeDefinition}; use serde::Deserialize; use serde_json::Value; use std::collections::HashMap; @@ -53,7 +55,7 @@ struct JsonIntrinsic { pub fn get_neon_intrinsics( filename: &Path, target: &String, -) -> Result, Box> { +) -> 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"); @@ -74,37 +76,39 @@ pub fn get_neon_intrinsics( fn json_to_intrinsic( mut intr: JsonIntrinsic, target: &String, -) -> Result> { +) -> Result, Box> { let name = intr.name.replace(['[', ']'], ""); - let results = IntrinsicType::from_c(&intr.return_type.value, target)?; + let results = ArmIntrinsicType::from_c(&intr.return_type.value, target)?; let args = intr .arguments .into_iter() .enumerate() .map(|(i, arg)| { - // let arg_name = Argument::type_and_name_from_c(&arg).1; - let mut arg = Argument::from_c(i, &arg, target, intr.args_prep.as_mut()); + let arg_name = Argument::::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 mut arg = + Argument::::from_c(i, &arg, target, metadata); + // The JSON doesn't list immediates as const - if let IntrinsicType::Type { + let IntrinsicType { ref mut constant, .. - } = arg.ty - { - if arg.name.starts_with("imm") { - *constant = true - } + } = arg.ty.0; + if arg.name.starts_with("imm") { + *constant = true } arg }) .collect(); - let arguments = ArgumentList { args }; + let arguments = ArgumentList:: { args }; Ok(Intrinsic { name, arguments, - results, + results: *results, a64_only: intr.architectures == vec!["A64".to_string()], }) } diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 8d94250c53..cef32c3fb6 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -1,21 +1,22 @@ -mod argument; mod config; -mod format; +mod constraint; mod functions; mod intrinsic; mod json_parser; mod types; +use crate::arm::constraint::Constraint; +use crate::arm::intrinsic::ArmIntrinsicType; use crate::common::SupportedArchitectureTest; use crate::common::compare::compare_outputs; +use crate::common::intrinsic::Intrinsic; +use crate::common::intrinsic_types::{BaseIntrinsicTypeDefinition, TypeKind}; use crate::common::types::ProcessedCli; use functions::{build_c, build_rust}; -use intrinsic::Intrinsic; use json_parser::get_neon_intrinsics; -use types::TypeKind; pub struct ArmArchitectureTest { - intrinsics: Vec, + intrinsics: Vec>, cli_options: ProcessedCli, } diff --git a/crates/intrinsic-test/src/arm/types.rs b/crates/intrinsic-test/src/arm/types.rs index a579e9699d..db08c3a52d 100644 --- a/crates/intrinsic-test/src/arm/types.rs +++ b/crates/intrinsic-test/src/arm/types.rs @@ -1,474 +1,125 @@ -use std::fmt; -use std::str::FromStr; - -use itertools::Itertools as _; - -use super::format::Indentation; +use super::intrinsic::ArmIntrinsicType; +use crate::common::intrinsic_types::{IntrinsicType, IntrinsicTypeDefinition, TypeKind}; 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, - - target: String, - }, -} - -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, - } - } - - /// Move to Argument - pub fn c_scalar_type(&self) -> String { - format!( - "{prefix}{bits}_t", - prefix = self.kind().c_prefix(), - bits = self.inner_size() - ) - } - - /// Move to Argument - pub fn rust_scalar_type(&self) -> String { - format!( - "{prefix}{bits}", - prefix = self.kind().rust_prefix(), - bits = self.inner_size() - ) - } +impl IntrinsicTypeDefinition for ArmIntrinsicType { /// Gets a string containing the typename for this type in C format. - /// - /// ARM-specific - 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), - } - } - - /// ARM-specific - 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"), - } - } - - /// ARM-specific - 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"), - }, - _ => "", + 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!("{}{}{}_t", const_prefix, prefix, bit_len), + (Some(simd), None) => format!("{}{bit_len}x{simd}_t", prefix), + (Some(simd), Some(vec)) => format!("{}{bit_len}x{simd}x{vec}_t", prefix), + (None, Some(_)) => todo!("{:#?}", self), // Likely an invalid case + } + } else { + todo!("{:#?}", self) } } - /// 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) - ))) - ) + fn c_single_vector_type(&self) -> String { + if let (Some(bit_len), Some(simd_len)) = (self.0.bit_len, self.0.simd_len) { + let prefix = self.0.kind.c_prefix(); + format!("{}{bit_len}x{simd_len}_t", 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.rust_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!("{}{bit_len}", rust_prefix), + (Some(simd), None) => format!("{}{bit_len}x{simd}_t", c_prefix), + (Some(simd), Some(vec)) => format!("{}{bit_len}x{simd}x{vec}_t", c_prefix), + (None, Some(_)) => todo!("{:#?}", self), // Likely an invalid case } - _ => unimplemented!("populate random: {:#?}", self), + } else { + todo!("{:#?}", self) } } /// Determines the load function for this type. - /// - /// ARM-specific fn get_load_function(&self, language: Language) -> String { - match self { - IntrinsicType::Ptr { child, .. } => child.get_load_function(language), - IntrinsicType::Type { - kind: k, - bit_len: Some(bl), - simd_len, - vec_len, - target, - .. - } => { - let quad = if simd_len.unwrap_or(1) * bl > 64 { - "q" - } else { - "" - }; + 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), - ) - } - _ => todo!("get_load_function IntrinsicType: {:#?}", self), + 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) } } - pub fn get_load_function_c(&self) -> String { - self.get_load_function(Language::C) - } - - pub fn get_load_function_rust(&self) -> String { - self.get_load_function(Language::Rust) - } - /// Determines the get lane function for this type. - /// - /// ARM-specific - 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), + 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) } } - /// ARM-specific - pub fn from_c(s: &str, target: &String) -> Result { + fn from_c(s: &str, target: &String) -> Result, String> { const CONST_STR: &str = "const"; if let Some(s) = s.strip_suffix('*') { let (s, constant) = match s.trim().strip_suffix(CONST_STR) { @@ -476,9 +127,12 @@ impl IntrinsicType { None => (s, false), }; let s = s.trim_end(); - Ok(IntrinsicType::Ptr { - constant, - child: Box::new(IntrinsicType::from_c(s, target)?), + let temp_return = ArmIntrinsicType::from_c(s, target); + temp_return.and_then(|mut op| { + let edited = op.as_mut(); + edited.0.ptr = true; + edited.0.ptr_constant = constant; + Ok(op) }) } else { // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t] @@ -507,28 +161,32 @@ impl IntrinsicType { ), None => None, }; - Ok(IntrinsicType::Type { + 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::()?; let bit_len = match kind { TypeKind::Int => Some(32), _ => None, }; - Ok(IntrinsicType::Type { + Ok(Box::new(ArmIntrinsicType(IntrinsicType { + ptr: false, + ptr_constant: false, constant, kind: start.parse::()?, bit_len, simd_len: None, vec_len: None, target: target.to_string(), - }) + }))) } } } diff --git a/crates/intrinsic-test/src/arm/argument.rs b/crates/intrinsic-test/src/common/argument.rs similarity index 65% rename from crates/intrinsic-test/src/arm/argument.rs rename to crates/intrinsic-test/src/common/argument.rs index 24fe8c9a93..08d6ca1523 100644 --- a/crates/intrinsic-test/src/arm/argument.rs +++ b/crates/intrinsic-test/src/common/argument.rs @@ -1,67 +1,35 @@ -use super::format::Indentation; -use super::json_parser::ArgPrep; -use super::types::{IntrinsicType, TypeKind}; +use crate::common::format::Indentation; +use crate::common::intrinsic_types::IntrinsicTypeDefinition; use crate::common::types::Language; -use serde::Deserialize; use serde_json::Value; -use std::collections::HashMap; -use std::ops::Range; /// An argument for the intrinsic. #[derive(Debug, PartialEq, Clone)] -pub struct Argument { +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, + pub ty: T, /// Any constraints that are on this argument - pub constraints: Vec, + pub metadata: Vec, } -#[derive(Debug, PartialEq, Clone, Deserialize)] -pub enum Constraint { - Equal(i64), - Range(Range), +pub trait MetadataDefinition { + fn from_metadata(metadata: Option) -> Vec>; } -/// ARM-specific -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 { +impl Argument +where + T: IntrinsicTypeDefinition, + M: MetadataDefinition, +{ + pub fn to_c_type(&self) -> String { self.ty.c_type() } - fn is_simd(&self) -> bool { + pub fn is_simd(&self) -> bool { self.ty.is_simd() } @@ -70,7 +38,7 @@ impl Argument { } pub fn has_constraint(&self) -> bool { - !self.constraints.is_empty() + !self.metadata.is_empty() } pub fn type_and_name_from_c(arg: &str) -> (&str, &str) { @@ -81,86 +49,65 @@ impl Argument { (arg[..split_index + 1].trim_end(), &arg[split_index + 1..]) } - // ARM-specific + /// 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: &String, - metadata: Option<&mut HashMap>, - ) -> Argument { + metadata: Option, + ) -> Argument { let (ty, var_name) = Self::type_and_name_from_c(arg); - let ty = IntrinsicType::from_c(ty, target) - .unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); + let ty = + T::from_c(ty, target).unwrap_or_else(|_| panic!("Failed to parse argument '{arg}'")); - let arg_name = Argument::type_and_name_from_c(&arg).1; - let arg = metadata.and_then(|a| a.remove(arg_name)); - let arg_prep: Option = arg.and_then(|a| { - if let Value::Object(_) = a { - a.try_into().ok() - } else { - None - } - }); - let constraint = arg_prep.and_then(|a| a.try_into().ok()); + let metadata: Vec = M::from_metadata(metadata).into_iter().map(|b| *b).collect(); Argument { pos, name: String::from(var_name), - ty, - constraints: constraint.map_or(vec![], |r| vec![r]), + ty: *ty, + metadata, } } - 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()) - } + fn as_call_param_c(&self) -> String { + self.ty.as_call_param_c(&self.name) } } #[derive(Debug, PartialEq, Clone)] -pub struct ArgumentList { - pub args: Vec, +pub struct ArgumentList { + pub args: Vec>, } -impl ArgumentList { +impl ArgumentList +where + T: IntrinsicTypeDefinition, + M: MetadataDefinition, +{ /// 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(), - }) + self.iter() + .map(|arg| arg.as_call_param_c()) .collect::>() .join(", ") } @@ -168,8 +115,7 @@ impl ArgumentList { /// 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() + self.iter() .filter(|a| !a.has_constraint()) .map(|arg| arg.name.clone()) .collect::>() @@ -177,8 +123,7 @@ impl ArgumentList { } pub fn as_constraint_parameters_rust(&self) -> String { - self.args - .iter() + self.iter() .filter(|a| a.has_constraint()) .map(|arg| arg.name.clone()) .collect::>() @@ -241,7 +186,7 @@ impl ArgumentList { ty = arg.to_c_type(), name = arg.name, load = if arg.is_simd() { - arg.ty.get_load_function_c() + arg.ty.get_load_function(Language::C) } else { "*".to_string() } @@ -263,7 +208,7 @@ impl ArgumentList { name = arg.name, vals_name = arg.rust_vals_array_name(), load = if arg.is_simd() { - arg.ty.get_load_function_rust() + arg.ty.get_load_function(Language::Rust) } else { "*".to_string() }, @@ -273,7 +218,7 @@ impl ArgumentList { .collect() } - pub fn iter(&self) -> std::slice::Iter<'_, Argument> { + pub fn iter(&self) -> std::slice::Iter<'_, Argument> { self.args.iter() } } diff --git a/crates/intrinsic-test/src/arm/format.rs b/crates/intrinsic-test/src/common/format.rs similarity index 100% rename from crates/intrinsic-test/src/arm/format.rs rename to crates/intrinsic-test/src/common/format.rs diff --git a/crates/intrinsic-test/src/common/intrinsic.rs b/crates/intrinsic-test/src/common/intrinsic.rs new file mode 100644 index 0000000000..e2124897f3 --- /dev/null +++ b/crates/intrinsic-test/src/common/intrinsic.rs @@ -0,0 +1,91 @@ +use crate::common::argument::ArgumentList; +use crate::common::format::Indentation; +use crate::common::intrinsic_types::IntrinsicTypeDefinition; + +use super::argument::MetadataDefinition; + +/// 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: T, + + /// Whether this intrinsic is only available on A64. + pub a64_only: bool, +} + +pub trait IntrinsicDefinition +where + T: IntrinsicTypeDefinition, + M: MetadataDefinition, +{ + fn arguments(&self) -> ArgumentList; + + 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 { + unimplemented!("Architectures need to implement print_result_c!") + } + + 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), + intrinsic_call = self.name(), + args = self.arguments().as_call_param_c(), + print_result = self.print_result_c(body_indentation, additional) + ) + } + + 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 + }; + + 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}-{{}}: {{:.150?}}\", 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(), + ) + } +} diff --git a/crates/intrinsic-test/src/common/intrinsic_types.rs b/crates/intrinsic-test/src/common/intrinsic_types.rs new file mode 100644 index 0000000000..9a08ec066c --- /dev/null +++ b/crates/intrinsic-test/src/common/intrinsic_types.rs @@ -0,0 +1,352 @@ +use std::fmt; +use std::str::FromStr; + +use itertools::Itertools as _; + +use crate::common::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 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, + + /// 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, + + /// 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, + + pub target: String, +} + +pub trait BaseIntrinsicTypeDefinition { + /// Get the TypeKind for this type, recursing into pointers. + fn kind(&self) -> TypeKind; + + /// 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. + fn inner_size(&self) -> u32; + + fn num_lanes(&self) -> u32; + + fn num_vectors(&self) -> u32; + + /// Determine if the type is a simd type, this will treat a type such as + /// `uint64x1` as simd. + fn is_simd(&self) -> bool; + + fn is_ptr(&self) -> bool; + + fn c_scalar_type(&self) -> String; + + fn rust_scalar_type(&self) -> String; + + /// 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. + fn c_promotion(&self) -> &str; + + /// 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` + fn populate_random(&self, indentation: Indentation, loads: u32, language: &Language) -> String; + + fn is_rust_vals_array_const(&self) -> bool; + + fn as_call_param_c(&self, name: &String) -> String; +} + +impl BaseIntrinsicTypeDefinition for IntrinsicType { + fn kind(&self) -> TypeKind { + self.kind + } + + fn inner_size(&self) -> u32 { + if let Some(bl) = self.bit_len { + bl + } else { + unreachable!("") + } + } + + fn num_lanes(&self) -> u32 { + if let Some(sl) = self.simd_len { sl } else { 1 } + } + + fn num_vectors(&self) -> u32 { + if let Some(vl) = self.vec_len { vl } else { 1 } + } + + fn is_simd(&self) -> bool { + self.simd_len.is_some() || self.vec_len.is_some() + } + + fn is_ptr(&self) -> bool { + self.ptr + } + + fn c_scalar_type(&self) -> String { + format!( + "{prefix}{bits}_t", + prefix = self.kind().c_prefix(), + bits = self.inner_size() + ) + } + + fn rust_scalar_type(&self) -> String { + format!( + "{prefix}{bits}", + prefix = self.kind().rust_prefix(), + bits = self.inner_size() + ) + } + + 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"), + }, + _ => "", + } + } + + 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(", ")", "}"), + (&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), + } + } + + 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!(), + } + } + + fn as_call_param_c(&self, name: &String) -> String { + if self.ptr { + format!("&{}", name) + } else { + name.clone() + } + } +} + +pub trait IntrinsicTypeDefinition: BaseIntrinsicTypeDefinition { + /// Determines the load function for this type. + /// can be implemented in an `impl` block + fn get_load_function(&self, _language: Language) -> String { + unimplemented!("Different architectures must implement get_load_function!") + } + + /// can be implemented in an `impl` block + fn get_lane_function(&self) -> String { + unimplemented!("Different architectures must implement get_lane_function!") + } + + /// can be implemented in an `impl` block + fn from_c(_s: &str, _target: &String) -> Result, String> { + unimplemented!("Different architectures must implement from_c!") + } + + /// Gets a string containing the typename for this type in C format. + /// can be directly defined in `impl` blocks + fn c_type(&self) -> String { + unimplemented!("Different architectures must implement c_type!") + } + + /// can be directly defined in `impl` blocks + fn c_single_vector_type(&self) -> String { + unimplemented!("Different architectures must implement c_single_vector_type!") + } + + /// can be defined in `impl` blocks + fn rust_type(&self) -> String { + unimplemented!("Different architectures must implement rust_type!") + } +} diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index 1c9f802776..7db1166c7d 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -2,9 +2,13 @@ use crate::common::types::ProcessedCli; use std::fs::File; use std::io::Write; +pub mod argument; pub mod compare; +pub mod format; pub mod gen_c; pub mod gen_rust; +pub mod intrinsic; +pub mod intrinsic_types; pub mod types; pub mod values; From 358016a9a38933e91fddbd0c2af469640205e77e Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 16 Apr 2025 16:02:11 +0530 Subject: [PATCH 14/20] Added a macro to simplify IntrinsicType definitions --- crates/intrinsic-test/src/arm/intrinsic.rs | 45 +-------------- .../src/common/intrinsic_types.rs | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/crates/intrinsic-test/src/arm/intrinsic.rs b/crates/intrinsic-test/src/arm/intrinsic.rs index da20751a07..201fa7eec7 100644 --- a/crates/intrinsic-test/src/arm/intrinsic.rs +++ b/crates/intrinsic-test/src/arm/intrinsic.rs @@ -1,53 +1,14 @@ use super::constraint::Constraint; +use crate::base_intrinsictype_trait_def_macro; use crate::common::argument::ArgumentList; use crate::common::format::Indentation; use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; use crate::common::intrinsic_types::{ - BaseIntrinsicTypeDefinition, IntrinsicType, IntrinsicTypeDefinition, TypeKind, + BaseIntrinsicTypeDefinition, IntrinsicTypeDefinition, TypeKind, }; use crate::common::types::Language; -#[derive(Debug, Clone, PartialEq)] -pub struct ArmIntrinsicType(pub IntrinsicType); - -impl BaseIntrinsicTypeDefinition for ArmIntrinsicType { - fn kind(&self) -> TypeKind { - self.0.kind() - } - fn inner_size(&self) -> u32 { - self.0.inner_size() - } - fn num_lanes(&self) -> u32 { - self.0.num_lanes() - } - fn num_vectors(&self) -> u32 { - self.0.num_vectors() - } - fn is_simd(&self) -> bool { - self.0.is_simd() - } - fn is_ptr(&self) -> bool { - self.0.is_ptr() - } - fn c_scalar_type(&self) -> String { - self.0.c_scalar_type() - } - fn rust_scalar_type(&self) -> String { - self.0.rust_scalar_type() - } - fn c_promotion(&self) -> &str { - self.0.c_promotion() - } - fn populate_random(&self, indentation: Indentation, loads: u32, language: &Language) -> String { - self.0.populate_random(indentation, loads, language) - } - fn is_rust_vals_array_const(&self) -> bool { - self.0.is_rust_vals_array_const() - } - fn as_call_param_c(&self, name: &String) -> String { - self.0.as_call_param_c(name) - } -} +base_intrinsictype_trait_def_macro! {ArmIntrinsicType} impl IntrinsicDefinition for Intrinsic { fn arguments(&self) -> ArgumentList { diff --git a/crates/intrinsic-test/src/common/intrinsic_types.rs b/crates/intrinsic-test/src/common/intrinsic_types.rs index 9a08ec066c..9cd74fa71f 100644 --- a/crates/intrinsic-test/src/common/intrinsic_types.rs +++ b/crates/intrinsic-test/src/common/intrinsic_types.rs @@ -350,3 +350,59 @@ pub trait IntrinsicTypeDefinition: BaseIntrinsicTypeDefinition { unimplemented!("Different architectures must implement rust_type!") } } + +/// Defines the basic structure of achitecture-specific derivatives +/// of IntrinsicType. +#[macro_export] +macro_rules! base_intrinsictype_trait_def_macro { + ($T:ident) => { + use crate::common::intrinsic_types::IntrinsicType; + + #[derive(Debug, Clone, PartialEq)] + pub struct $T(pub IntrinsicType); + + impl BaseIntrinsicTypeDefinition for $T { + fn kind(&self) -> TypeKind { + self.0.kind() + } + fn inner_size(&self) -> u32 { + self.0.inner_size() + } + fn num_lanes(&self) -> u32 { + self.0.num_lanes() + } + fn num_vectors(&self) -> u32 { + self.0.num_vectors() + } + fn is_simd(&self) -> bool { + self.0.is_simd() + } + fn is_ptr(&self) -> bool { + self.0.is_ptr() + } + fn c_scalar_type(&self) -> String { + self.0.c_scalar_type() + } + fn rust_scalar_type(&self) -> String { + self.0.rust_scalar_type() + } + fn c_promotion(&self) -> &str { + self.0.c_promotion() + } + fn populate_random( + &self, + indentation: Indentation, + loads: u32, + language: &Language, + ) -> String { + self.0.populate_random(indentation, loads, language) + } + fn is_rust_vals_array_const(&self) -> bool { + self.0.is_rust_vals_array_const() + } + fn as_call_param_c(&self, name: &String) -> String { + self.0.as_call_param_c(name) + } + } + }; +} From cc615b6056070d94886fccfe15d9669833f95185 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 16 Apr 2025 19:48:17 +0530 Subject: [PATCH 15/20] renamed `a64_only` data member in `Intrinsic` to `arch_tags` --- crates/intrinsic-test/src/arm/json_parser.rs | 2 +- crates/intrinsic-test/src/arm/mod.rs | 2 +- crates/intrinsic-test/src/common/intrinsic.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/intrinsic-test/src/arm/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs index 2f49b84c67..4415459a3e 100644 --- a/crates/intrinsic-test/src/arm/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -109,6 +109,6 @@ fn json_to_intrinsic( name, arguments, results: *results, - a64_only: intr.architectures == vec!["A64".to_string()], + arch_tags: intr.architectures, }) } diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index cef32c3fb6..6a7c5d5a39 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -39,7 +39,7 @@ impl SupportedArchitectureTest for ArmArchitectureTest { .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.a64_only)) + .filter(|i| !(a32 && i.arch_tags == vec!["A64".to_string()])) .collect::>(); intrinsics.dedup(); diff --git a/crates/intrinsic-test/src/common/intrinsic.rs b/crates/intrinsic-test/src/common/intrinsic.rs index e2124897f3..5322ebf914 100644 --- a/crates/intrinsic-test/src/common/intrinsic.rs +++ b/crates/intrinsic-test/src/common/intrinsic.rs @@ -16,8 +16,8 @@ pub struct Intrinsic { /// The return type of this intrinsic. pub results: T, - /// Whether this intrinsic is only available on A64. - pub a64_only: bool, + /// Any architecture-specific tags. + pub arch_tags: Vec, } pub trait IntrinsicDefinition From d395e9c51ff791f5154f3b3b02c90552d938577b Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Thu, 17 Apr 2025 14:23:26 +0530 Subject: [PATCH 16/20] Removed aarch64-be specific execution command for rust test files --- crates/intrinsic-test/src/common/compare.rs | 31 ++++++--------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/crates/intrinsic-test/src/common/compare.rs b/crates/intrinsic-test/src/common/compare.rs index 72355c9fee..8884380c27 100644 --- a/crates/intrinsic-test/src/common/compare.rs +++ b/crates/intrinsic-test/src/common/compare.rs @@ -2,8 +2,6 @@ use super::types::FailureReason; use rayon::prelude::*; use std::process::Command; -static SPECIAL_TARGETS: [&str; 1] = ["aarch64_be-unknown-linux-gnu"]; - pub fn compare_outputs( intrinsic_name_list: &Vec, toolchain: &str, @@ -18,27 +16,14 @@ pub fn compare_outputs( .arg(format!("{runner} ./c_programs/{intrinsic_name}")) .output(); - let rust = if SPECIAL_TARGETS - .into_iter() - .find(|&special_target| special_target == target) - .is_some() - { - Command::new("sh") - .arg("-c") - .arg(format!( - "{runner} ./rust_programs/target/{target}/release/{intrinsic_name}", - )) - .output() - } else { - 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 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), From 08325f351b29297b1cc281b7cbc6614495849419 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Fri, 18 Apr 2025 22:35:30 +0530 Subject: [PATCH 17/20] moved the C compilation commands into a struct for easier handling --- crates/intrinsic-test/src/arm/functions.rs | 96 +++++------ crates/intrinsic-test/src/common/compile_c.rs | 151 ++++++++++++++++++ crates/intrinsic-test/src/common/mod.rs | 1 + 3 files changed, 193 insertions(+), 55 deletions(-) create mode 100644 crates/intrinsic-test/src/common/compile_c.rs diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index 8158dfd88d..6f39e4a658 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -2,6 +2,7 @@ use super::config::{AARCH_CONFIGURATIONS, POLY128_OSTREAM_DEF, build_notices}; use super::intrinsic::ArmIntrinsicType; use crate::arm::constraint::Constraint; use crate::common::argument::Argument; +use crate::common::compile_c::CompilationCommandBuilder; use crate::common::format::Indentation; use crate::common::gen_c::{compile_c, create_c_filenames, generate_c_program}; use crate::common::gen_rust::{compile_rust, create_rust_filenames, generate_rust_program}; @@ -161,70 +162,55 @@ fn generate_rust_program_arm( fn compile_c_arm( intrinsics_name_list: &Vec, - filename_mapping: BTreeMap<&String, String>, + _filename_mapping: BTreeMap<&String, String>, compiler: &str, target: &str, cxx_toolchain_dir: Option<&str>, ) -> bool { - let compiler_commands = intrinsics_name_list.iter().map(|intrinsic_name| { - let c_filename = filename_mapping.get(intrinsic_name).unwrap(); - 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 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"]); - 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 `" - ); - }; + 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 ` - * 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", + command = if target == "aarch64_be-unknown-linux-gnu" { + 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", + ]) + } else { + if compiler.contains("clang") { + command.add_extra_flag(format!("-target {target}").as_str()) } 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") - } - }; + command.add_extra_flag("-flax-vector-conversions") + } + }; - compiler_command - }) - .collect::>(); + let compiler_commands = intrinsics_name_list + .iter() + .map(|intrinsic_name| { + command + .clone() + .set_input_name(intrinsic_name) + .set_output_name(intrinsic_name) + .to_string() + }) + .collect::>(); compile_c(&compiler_commands) } diff --git a/crates/intrinsic-test/src/common/compile_c.rs b/crates/intrinsic-test/src/common/compile_c.rs new file mode 100644 index 0000000000..f018e02fb2 --- /dev/null +++ b/crates/intrinsic-test/src/common/compile_c.rs @@ -0,0 +1,151 @@ +#[derive(Clone)] +pub struct CompilationCommandBuilder { + compiler: String, + target: Option, + cxx_toolchain_dir: Option, + arch_flags: Vec, + optimization: String, + include_paths: Vec, + project_root: Option, + output: String, + input: String, + linker: Option, + extra_flags: Vec, +} + +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.output += ".o"; + self + } + + pub fn add_extra_flags(mut self, flags: Vec<&str>) -> Self { + let mut flags: Vec = 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 to_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(String::new()); + let project_root_str = project_root.as_str(); + let mut command = format!( + "{} {flags} -march={arch_flags} \ + -O{} \ + -o {project_root}/{} \ + {project_root}/{}.cpp", + self.compiler, self.optimization, self.output, self.input, + ); + + command = command + " " + self.extra_flags.join(" ").as_str(); + + if let (Some(linker), Some(cxx_toolchain_dir)) = (&self.linker, &self.cxx_toolchain_dir) { + if let Some(target) = &self.target { + command = command + " --target=" + target; + } + + let include_args = self + .include_paths + .iter() + .map(|path| "--include-directory=".to_string() + cxx_toolchain_dir + path) + .collect::>() + .join(" "); + + command = command + + " -c " + + include_args.as_str() + + " && " + + linker + + project_root_str + + "/" + + self.output.as_str() + + " -o " + + project_root_str + + "/" + + self.output.strip_suffix(".o").unwrap() + + " && rm " + + project_root_str + + "/" + + self.output.as_str(); + } + + command + } +} diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index 7db1166c7d..ae44eb06b2 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -4,6 +4,7 @@ use std::io::Write; pub mod argument; pub mod compare; +pub mod compile_c; pub mod format; pub mod gen_c; pub mod gen_rust; From 420f2ee17c0c5a44c39e611d5a75301c3d340c92 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Sat, 19 Apr 2025 23:19:07 +0530 Subject: [PATCH 18/20] Added dynamic dispatch for easier management of `ArchitectureTest` structs --- crates/intrinsic-test/src/arm/mod.rs | 6 +++--- crates/intrinsic-test/src/common/mod.rs | 4 +++- crates/intrinsic-test/src/main.rs | 19 ++++++++++--------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 6a7c5d5a39..9bee5a1d7b 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -21,7 +21,7 @@ pub struct ArmArchitectureTest { } impl SupportedArchitectureTest for ArmArchitectureTest { - fn create(cli_options: ProcessedCli) -> Self { + fn create(cli_options: ProcessedCli) -> Box { let a32 = cli_options.target.contains("v7"); let mut intrinsics = get_neon_intrinsics(&cli_options.filename, &cli_options.target) .expect("Error parsing input file"); @@ -43,10 +43,10 @@ impl SupportedArchitectureTest for ArmArchitectureTest { .collect::>(); intrinsics.dedup(); - Self { + Box::new(Self { intrinsics: intrinsics, cli_options: cli_options, - } + }) } fn build_c_file(&self) -> bool { diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index ae44eb06b2..606ab4fdf3 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -16,7 +16,9 @@ pub mod values; /// Architectures must support this trait /// to be successfully tested. pub trait SupportedArchitectureTest { - fn create(cli_options: ProcessedCli) -> Self; + fn create(cli_options: ProcessedCli) -> Box + where + Self: Sized; fn build_c_file(&self) -> bool; fn build_rust_file(&self) -> bool; fn compare_outputs(&self) -> bool; diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 0816c6c39c..0e168a53f5 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -14,15 +14,16 @@ fn main() { let args: Cli = clap::Parser::parse(); let processed_cli_options = ProcessedCli::new(args); - let test_environment_result = 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, - }; + let test_environment_result: Option> = + 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); From f1fa439b34978d24ab4da2e39018f837cf55296c Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Sat, 19 Apr 2025 23:41:25 +0530 Subject: [PATCH 19/20] code cleanup --- crates/intrinsic-test/src/arm/functions.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index 6f39e4a658..d9741d3a85 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -11,7 +11,6 @@ use crate::common::intrinsic_types::IntrinsicTypeDefinition; use crate::common::write_file; use itertools::Itertools; use rayon::prelude::*; -use std::collections::BTreeMap; // The number of times each intrinsic will be called. const PASSES: u32 = 20; @@ -162,7 +161,6 @@ fn generate_rust_program_arm( fn compile_c_arm( intrinsics_name_list: &Vec, - _filename_mapping: BTreeMap<&String, String>, compiler: &str, target: &str, cxx_toolchain_dir: Option<&str>, @@ -237,13 +235,7 @@ pub fn build_c( match compiler { None => true, - Some(compiler) => compile_c_arm( - &intrinsics_name_list, - filename_mapping, - compiler, - target, - cxx_toolchain_dir, - ), + Some(compiler) => compile_c_arm(&intrinsics_name_list, compiler, target, cxx_toolchain_dir), } } From f2923faff0d5c0f93a72d9e3f0be28f82f673903 Mon Sep 17 00:00:00 2001 From: Madhav Madhusoodanan Date: Wed, 23 Apr 2025 19:25:53 +0530 Subject: [PATCH 20/20] chore: file renaming --- crates/intrinsic-test/src/arm/functions.rs | 2 +- crates/intrinsic-test/src/arm/intrinsic.rs | 4 ++-- crates/intrinsic-test/src/arm/mod.rs | 2 +- crates/intrinsic-test/src/arm/types.rs | 2 +- crates/intrinsic-test/src/common/argument.rs | 4 ++-- crates/intrinsic-test/src/common/{types.rs => cli.rs} | 2 +- crates/intrinsic-test/src/common/compare.rs | 2 +- .../intrinsic-test/src/common/{format.rs => indentation.rs} | 0 crates/intrinsic-test/src/common/intrinsic.rs | 2 +- crates/intrinsic-test/src/common/intrinsic_types.rs | 4 ++-- crates/intrinsic-test/src/common/mod.rs | 6 +++--- crates/intrinsic-test/src/main.rs | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) rename crates/intrinsic-test/src/common/{types.rs => cli.rs} (97%) rename crates/intrinsic-test/src/common/{format.rs => indentation.rs} (100%) diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs index d9741d3a85..f0fe22dc42 100644 --- a/crates/intrinsic-test/src/arm/functions.rs +++ b/crates/intrinsic-test/src/arm/functions.rs @@ -3,9 +3,9 @@ use super::intrinsic::ArmIntrinsicType; use crate::arm::constraint::Constraint; use crate::common::argument::Argument; use crate::common::compile_c::CompilationCommandBuilder; -use crate::common::format::Indentation; use crate::common::gen_c::{compile_c, create_c_filenames, generate_c_program}; use crate::common::gen_rust::{compile_rust, create_rust_filenames, generate_rust_program}; +use crate::common::indentation::Indentation; use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; use crate::common::intrinsic_types::IntrinsicTypeDefinition; use crate::common::write_file; diff --git a/crates/intrinsic-test/src/arm/intrinsic.rs b/crates/intrinsic-test/src/arm/intrinsic.rs index 201fa7eec7..0cb1d2f204 100644 --- a/crates/intrinsic-test/src/arm/intrinsic.rs +++ b/crates/intrinsic-test/src/arm/intrinsic.rs @@ -1,12 +1,12 @@ use super::constraint::Constraint; use crate::base_intrinsictype_trait_def_macro; use crate::common::argument::ArgumentList; -use crate::common::format::Indentation; +use crate::common::cli::Language; +use crate::common::indentation::Indentation; use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition}; use crate::common::intrinsic_types::{ BaseIntrinsicTypeDefinition, IntrinsicTypeDefinition, TypeKind, }; -use crate::common::types::Language; base_intrinsictype_trait_def_macro! {ArmIntrinsicType} diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 9bee5a1d7b..9e35cff1a2 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -8,10 +8,10 @@ mod types; use crate::arm::constraint::Constraint; use crate::arm::intrinsic::ArmIntrinsicType; use crate::common::SupportedArchitectureTest; +use crate::common::cli::ProcessedCli; use crate::common::compare::compare_outputs; use crate::common::intrinsic::Intrinsic; use crate::common::intrinsic_types::{BaseIntrinsicTypeDefinition, TypeKind}; -use crate::common::types::ProcessedCli; use functions::{build_c, build_rust}; use json_parser::get_neon_intrinsics; diff --git a/crates/intrinsic-test/src/arm/types.rs b/crates/intrinsic-test/src/arm/types.rs index db08c3a52d..a5d46890d8 100644 --- a/crates/intrinsic-test/src/arm/types.rs +++ b/crates/intrinsic-test/src/arm/types.rs @@ -1,6 +1,6 @@ use super::intrinsic::ArmIntrinsicType; +use crate::common::cli::Language; use crate::common::intrinsic_types::{IntrinsicType, IntrinsicTypeDefinition, TypeKind}; -use crate::common::types::Language; impl IntrinsicTypeDefinition for ArmIntrinsicType { /// Gets a string containing the typename for this type in C format. diff --git a/crates/intrinsic-test/src/common/argument.rs b/crates/intrinsic-test/src/common/argument.rs index 08d6ca1523..f61a39f625 100644 --- a/crates/intrinsic-test/src/common/argument.rs +++ b/crates/intrinsic-test/src/common/argument.rs @@ -1,6 +1,6 @@ -use crate::common::format::Indentation; +use crate::common::cli::Language; +use crate::common::indentation::Indentation; use crate::common::intrinsic_types::IntrinsicTypeDefinition; -use crate::common::types::Language; use serde_json::Value; /// An argument for the intrinsic. diff --git a/crates/intrinsic-test/src/common/types.rs b/crates/intrinsic-test/src/common/cli.rs similarity index 97% rename from crates/intrinsic-test/src/common/types.rs rename to crates/intrinsic-test/src/common/cli.rs index 53bda97df4..9345761cf1 100644 --- a/crates/intrinsic-test/src/common/types.rs +++ b/crates/intrinsic-test/src/common/cli.rs @@ -44,7 +44,7 @@ pub struct Cli { pub generate_only: bool, /// Pass a target the test suite - #[arg(long, default_value_t = String::from("aarch64-unknown-linux-gnu"))] + #[arg(long, default_value_t = String::from("armv7-unknown-linux-gnueabihf"))] pub target: String, /// Set the linker diff --git a/crates/intrinsic-test/src/common/compare.rs b/crates/intrinsic-test/src/common/compare.rs index 8884380c27..885ca06da3 100644 --- a/crates/intrinsic-test/src/common/compare.rs +++ b/crates/intrinsic-test/src/common/compare.rs @@ -1,4 +1,4 @@ -use super::types::FailureReason; +use super::cli::FailureReason; use rayon::prelude::*; use std::process::Command; diff --git a/crates/intrinsic-test/src/common/format.rs b/crates/intrinsic-test/src/common/indentation.rs similarity index 100% rename from crates/intrinsic-test/src/common/format.rs rename to crates/intrinsic-test/src/common/indentation.rs diff --git a/crates/intrinsic-test/src/common/intrinsic.rs b/crates/intrinsic-test/src/common/intrinsic.rs index 5322ebf914..0a398df66f 100644 --- a/crates/intrinsic-test/src/common/intrinsic.rs +++ b/crates/intrinsic-test/src/common/intrinsic.rs @@ -1,5 +1,5 @@ use crate::common::argument::ArgumentList; -use crate::common::format::Indentation; +use crate::common::indentation::Indentation; use crate::common::intrinsic_types::IntrinsicTypeDefinition; use super::argument::MetadataDefinition; diff --git a/crates/intrinsic-test/src/common/intrinsic_types.rs b/crates/intrinsic-test/src/common/intrinsic_types.rs index 9cd74fa71f..473585c814 100644 --- a/crates/intrinsic-test/src/common/intrinsic_types.rs +++ b/crates/intrinsic-test/src/common/intrinsic_types.rs @@ -3,8 +3,8 @@ use std::str::FromStr; use itertools::Itertools as _; -use crate::common::format::Indentation; -use crate::common::types::Language; +use crate::common::cli::Language; +use crate::common::indentation::Indentation; use crate::common::values::value_for_array; #[derive(Debug, PartialEq, Copy, Clone)] diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs index 606ab4fdf3..c3888b68da 100644 --- a/crates/intrinsic-test/src/common/mod.rs +++ b/crates/intrinsic-test/src/common/mod.rs @@ -1,16 +1,16 @@ -use crate::common::types::ProcessedCli; +use crate::common::cli::ProcessedCli; use std::fs::File; use std::io::Write; pub mod argument; +pub mod cli; pub mod compare; pub mod compile_c; -pub mod format; pub mod gen_c; pub mod gen_rust; +pub mod indentation; pub mod intrinsic; pub mod intrinsic_types; -pub mod types; pub mod values; /// Architectures must support this trait diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 0e168a53f5..686f750e32 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -7,7 +7,7 @@ mod common; use arm::ArmArchitectureTest; use common::SupportedArchitectureTest; -use common::types::{Cli, ProcessedCli}; +use common::cli::{Cli, ProcessedCli}; fn main() { pretty_env_logger::init();