summaryrefslogtreecommitdiffstats
path: root/library/stdarch/crates/intrinsic-test/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/stdarch/crates/intrinsic-test/src/main.rs')
-rw-r--r--library/stdarch/crates/intrinsic-test/src/main.rs479
1 files changed, 479 insertions, 0 deletions
diff --git a/library/stdarch/crates/intrinsic-test/src/main.rs b/library/stdarch/crates/intrinsic-test/src/main.rs
new file mode 100644
index 000000000..1b58da2fd
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/main.rs
@@ -0,0 +1,479 @@
+#![feature(slice_partition_dedup)]
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate log;
+
+use std::fs::File;
+use std::io::Write;
+use std::process::Command;
+
+use clap::{App, Arg};
+use intrinsic::Intrinsic;
+use itertools::Itertools;
+use rayon::prelude::*;
+use types::TypeKind;
+
+use crate::acle_csv_parser::get_acle_intrinsics;
+use crate::argument::Argument;
+
+mod acle_csv_parser;
+mod argument;
+mod intrinsic;
+mod types;
+mod values;
+
+#[derive(Debug, PartialEq)]
+pub enum Language {
+ Rust,
+ C,
+}
+
+fn gen_code_c(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());
+
+ range
+ .map(|i| {
+ format!(
+ r#" {{
+ {ty} {name} = {val};
+{pass}
+ }}"#,
+ name = current.name,
+ ty = current.ty.c_type(),
+ val = i,
+ pass = gen_code_c(intrinsic, constraints, format!("{}-{}", name, i))
+ )
+ })
+ .collect()
+ } else {
+ (1..20)
+ .map(|idx| intrinsic.generate_pass_c(idx, &name))
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+}
+
+fn generate_c_program(header_files: &[&str], intrinsic: &Intrinsic) -> String {
+ let constraints = intrinsic
+ .arguments
+ .iter()
+ .filter(|i| i.has_constraint())
+ .collect_vec();
+
+ format!(
+ r#"{header_files}
+#include <iostream>
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+
+template<typename T1, typename T2> T1 cast(T2 x) {{
+ static_assert(sizeof(T1) == sizeof(T2), "sizeof T1 and T2 must be the same");
+ T1 ret = 0;
+ 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
+
+int main(int argc, char **argv) {{
+{passes}
+ return 0;
+}}"#,
+ header_files = header_files
+ .iter()
+ .map(|header| format!("#include <{}>", header))
+ .collect::<Vec<_>>()
+ .join("\n"),
+ passes = gen_code_c(intrinsic, constraints.as_slice(), Default::default()),
+ )
+}
+
+fn gen_code_rust(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());
+
+ range
+ .map(|i| {
+ format!(
+ r#" {{
+ const {name}: {ty} = {val};
+{pass}
+ }}"#,
+ name = current.name,
+ ty = current.ty.rust_type(),
+ val = i,
+ pass = gen_code_rust(intrinsic, constraints, format!("{}-{}", name, i))
+ )
+ })
+ .collect()
+ } else {
+ (1..20)
+ .map(|idx| intrinsic.generate_pass_rust(idx, &name))
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+}
+
+fn generate_rust_program(intrinsic: &Intrinsic, a32: bool) -> String {
+ let constraints = intrinsic
+ .arguments
+ .iter()
+ .filter(|i| i.has_constraint())
+ .collect_vec();
+
+ format!(
+ r#"#![feature(simd_ffi)]
+#![feature(link_llvm_intrinsics)]
+#![feature(stdsimd)]
+#![allow(overflowing_literals)]
+#![allow(non_upper_case_globals)]
+use core_arch::arch::{target_arch}::*;
+
+fn main() {{
+{passes}
+}}
+"#,
+ target_arch = if a32 { "arm" } else { "aarch64" },
+ passes = gen_code_rust(intrinsic, &constraints, Default::default())
+ )
+}
+
+fn compile_c(c_filename: &str, intrinsic: &Intrinsic, compiler: &str, a32: bool) -> bool {
+ let flags = std::env::var("CPPFLAGS").unwrap_or("".into());
+
+ let output = Command::new("sh")
+ .arg("-c")
+ .arg(format!(
+ "{cpp} {cppflags} {arch_flags} -Wno-narrowing -O2 -target {target} -o c_programs/{intrinsic} {filename}",
+ target = if a32 { "armv7-unknown-linux-gnueabihf" } else { "aarch64-unknown-linux-gnu" },
+ arch_flags = if a32 { "-march=armv8.6-a+crypto+crc+dotprod" } else { "-march=armv8.6-a+crypto+sha3+crc+dotprod" },
+ filename = c_filename,
+ intrinsic = intrinsic.name,
+ cpp = compiler,
+ cppflags = flags,
+ ))
+ .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_c(intrinsics: &Vec<Intrinsic>, compiler: &str, a32: bool) -> 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(&["arm_neon.h", "arm_acle.h"], &i);
+ file.write_all(c_code.into_bytes().as_slice()).unwrap();
+ compile_c(&c_filename, &i, compiler, a32)
+ })
+ .find_any(|x| !x)
+ .is_none()
+}
+
+fn build_rust(intrinsics: &Vec<Intrinsic>, toolchain: &str, a32: bool) -> 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#"{}/main.rs"#, rust_dir);
+ let mut file = File::create(&rust_filename).unwrap();
+
+ let c_code = generate_rust_program(&i, a32);
+ 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"
+version = "{version}"
+authors = ["{authors}"]
+edition = "2018"
+[workspace]
+[dependencies]
+core_arch = {{ path = "../crates/core_arch" }}
+{binaries}"#,
+ version = env!("CARGO_PKG_VERSION"),
+ authors = env!("CARGO_PKG_AUTHORS"),
+ binaries = intrinsics
+ .iter()
+ .map(|i| {
+ format!(
+ r#"[[bin]]
+name = "{intrinsic}"
+path = "{intrinsic}/main.rs""#,
+ intrinsic = i.name
+ )
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+ )
+ .into_bytes()
+ .as_slice(),
+ )
+ .unwrap();
+
+ let output = Command::new("sh")
+ .current_dir("rust_programs")
+ .arg("-c")
+ .arg(format!(
+ "cargo {toolchain} build --target {target}",
+ toolchain = toolchain,
+ target = if a32 {
+ "armv7-unknown-linux-gnueabihf"
+ } else {
+ "aarch64-unknown-linux-gnu"
+ },
+ ))
+ .env("RUSTFLAGS", "-Cdebuginfo=0")
+ .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
+ }
+}
+
+fn main() {
+ pretty_env_logger::init();
+
+ let matches = App::new("Intrinsic test tool")
+ .about("Generates Rust and C programs for intrinsics and compares the output")
+ .arg(
+ Arg::with_name("INPUT")
+ .help("The input file containing the intrinsics")
+ .required(true)
+ .index(1),
+ )
+ .arg(
+ Arg::with_name("TOOLCHAIN")
+ .takes_value(true)
+ .long("toolchain")
+ .help("The rust toolchain to use for building the rust code"),
+ )
+ .arg(
+ Arg::with_name("CPPCOMPILER")
+ .takes_value(true)
+ .default_value("clang++")
+ .long("cppcompiler")
+ .help("The C++ compiler to use for compiling the c++ code"),
+ )
+ .arg(
+ Arg::with_name("RUNNER")
+ .takes_value(true)
+ .long("runner")
+ .help("Run the C programs under emulation with this command"),
+ )
+ .arg(
+ Arg::with_name("SKIP")
+ .takes_value(true)
+ .long("skip")
+ .help("Filename for a list of intrinsics to skip (one per line)"),
+ )
+ .arg(
+ Arg::with_name("A32")
+ .takes_value(false)
+ .long("a32")
+ .help("Run tests for A32 instrinsics instead of A64"),
+ )
+ .get_matches();
+
+ let filename = matches.value_of("INPUT").unwrap();
+ let toolchain = matches
+ .value_of("TOOLCHAIN")
+ .map_or("".into(), |t| format!("+{}", t));
+
+ let cpp_compiler = matches.value_of("CPPCOMPILER").unwrap();
+ let c_runner = matches.value_of("RUNNER").unwrap_or("");
+ let skip = if let Some(filename) = matches.value_of("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 = matches.is_present("A32");
+
+ let intrinsics = get_acle_intrinsics(filename);
+
+ 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.results.kind() == TypeKind::Float && i.results.inner_size() == 16))
+ .filter(|i| !i.arguments.iter().any(|a| a.ty.kind() == TypeKind::BFloat))
+ .filter(|i| {
+ !i.arguments
+ .iter()
+ .any(|a| a.ty.kind() == TypeKind::Float && a.ty.inner_size() == 16)
+ })
+ // 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::<Vec<_>>();
+ intrinsics.dedup();
+
+ if !build_c(&intrinsics, cpp_compiler, a32) {
+ std::process::exit(2);
+ }
+
+ if !build_rust(&intrinsics, &toolchain, a32) {
+ std::process::exit(3);
+ }
+
+ if !compare_outputs(&intrinsics, &toolchain, &c_runner, a32) {
+ std::process::exit(1)
+ }
+}
+
+enum FailureReason {
+ RunC(String),
+ RunRust(String),
+ Difference(String, String, String),
+}
+
+fn compare_outputs(intrinsics: &Vec<Intrinsic>, toolchain: &str, runner: &str, a32: bool) -> bool {
+ let intrinsics = intrinsics
+ .par_iter()
+ .filter_map(|intrinsic| {
+ let c = Command::new("sh")
+ .arg("-c")
+ .arg(format!(
+ "{runner} ./c_programs/{intrinsic}",
+ runner = runner,
+ intrinsic = intrinsic.name,
+ ))
+ .output();
+ let rust = Command::new("sh")
+ .current_dir("rust_programs")
+ .arg("-c")
+ .arg(format!(
+ "cargo {toolchain} run --target {target} --bin {intrinsic}",
+ intrinsic = intrinsic.name,
+ toolchain = toolchain,
+ target = if a32 {
+ "armv7-unknown-linux-gnueabihf"
+ } else {
+ "aarch64-unknown-linux-gnu"
+ },
+ ))
+ .env("RUSTFLAGS", "-Cdebuginfo=0")
+ .output();
+
+ let (c, rust) = match (c, rust) {
+ (Ok(c), Ok(rust)) => (c, rust),
+ a => panic!("{:#?}", a),
+ };
+
+ if !c.status.success() {
+ error!("Failed to run C program for intrinsic {}", intrinsic.name);
+ 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::<Vec<_>>();
+
+ intrinsics.iter().for_each(|reason| match reason {
+ FailureReason::Difference(intrinsic, c, rust) => {
+ println!("Difference for intrinsic: {}", intrinsic);
+ let diff = diff::lines(c, rust);
+ diff.iter().for_each(|diff| match diff {
+ diff::Result::Left(c) => println!("C: {}", c),
+ diff::Result::Right(rust) => println!("Rust: {}", rust),
+ diff::Result::Both(_, _) => (),
+ });
+ println!("****************************************************************");
+ }
+ FailureReason::RunC(intrinsic) => {
+ println!("Failed to run C program for intrinsic {}", intrinsic)
+ }
+ FailureReason::RunRust(intrinsic) => {
+ println!("Failed to run rust program for intrinsic {}", intrinsic)
+ }
+ });
+ println!("{} differences found", intrinsics.len());
+ intrinsics.is_empty()
+}