From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- .../crates/intrinsic-test/src/acle_csv_parser.rs | 319 ++++++++++++++ .../stdarch/crates/intrinsic-test/src/argument.rs | 139 ++++++ .../stdarch/crates/intrinsic-test/src/intrinsic.rs | 125 ++++++ library/stdarch/crates/intrinsic-test/src/main.rs | 479 +++++++++++++++++++++ library/stdarch/crates/intrinsic-test/src/types.rs | 436 +++++++++++++++++++ .../stdarch/crates/intrinsic-test/src/values.rs | 126 ++++++ 6 files changed, 1624 insertions(+) create mode 100644 library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/argument.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/intrinsic.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/main.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/types.rs create mode 100644 library/stdarch/crates/intrinsic-test/src/values.rs (limited to 'library/stdarch/crates/intrinsic-test/src') diff --git a/library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs b/library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs new file mode 100644 index 000000000..d7b066485 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs @@ -0,0 +1,319 @@ +use itertools::Itertools; +use regex::Regex; +use serde::Deserialize; + +use crate::argument::{Argument, ArgumentList, Constraint}; +use crate::intrinsic::Intrinsic; +use crate::types::{IntrinsicType, TypeKind}; + +pub fn get_acle_intrinsics(filename: &str) -> Vec { + let data = std::fs::read_to_string(filename).expect("Failed to open ACLE intrinsics file"); + + let data = data + .lines() + .filter_map(|l| { + (!(l.starts_with("") || l.is_empty() || l.starts_with("
"))) + .then(|| l.replace("
\t", "")) + }) + .join("\n"); + + let mut csv_reader = csv::ReaderBuilder::new() + .delimiter(b'\t') + .from_reader(data.as_bytes()); + + let mut intrinsics: Vec = csv_reader + .deserialize() + .filter_map(|x: Result| x.ok().map(|i| i.into())) + .collect::>(); + + // Intrinsics such as vshll_n_s8 exist twice in the ACLE with different constraints. + intrinsics.sort_by(|a, b| a.name.cmp(&b.name)); + let (intrinsics, duplicates) = intrinsics.partition_dedup_by(|a, b| a.name == b.name); + for duplicate in duplicates { + let name = &duplicate.name; + let constraints = duplicate + .arguments + .args + .drain(..) + .filter(|a| a.has_constraint()); + let intrinsic = intrinsics.iter_mut().find(|i| &i.name == name).unwrap(); + + for mut constraint in constraints { + let real_constraint = intrinsic + .arguments + .args + .iter_mut() + .find(|a| a.name == constraint.name) + .unwrap(); + real_constraint + .constraints + .push(constraint.constraints.pop().unwrap()); + } + } + + intrinsics.to_vec() +} + +impl Into for ACLEIntrinsicLine { + fn into(self) -> Intrinsic { + let signature = self.intrinsic; + let (ret_ty, remaining) = signature.split_once(' ').unwrap(); + + let results = type_from_c(ret_ty) + .unwrap_or_else(|_| panic!("Failed to parse return type: {}", ret_ty)); + + let (name, args) = remaining.split_once('(').unwrap(); + let args = args.trim_end_matches(')'); + + // Typo in ACLE data + let args = args.replace("int16x8q_t", "int16x8_t"); + + let arg_prep = self.argument_preparation.as_str(); + let args = args + .split(',') + .enumerate() + .map(move |(idx, arg)| { + let arg = arg.trim(); + if arg.starts_with("__builtin_constant_p") { + handle_constraint(idx, arg, arg_prep) + } else { + from_c(idx, arg) + } + }) + .collect(); + let arguments = ArgumentList { args }; + let a64_only = match &*self.supported_architectures { + "A64" => true, + "v7/A32/A64" | "A32/A64" => false, + _ => panic!("Invalid supported architectures"), + }; + + Intrinsic { + name: name.to_string(), + arguments, + results, + a64_only, + } + } +} + +fn handle_constraint(idx: usize, arg: &str, prep: &str) -> Argument { + let prep = prep.replace(' ', ""); + + let name = arg + .trim_start_matches("__builtin_constant_p") + .trim_start_matches(|ref c| c == &' ' || c == &'(') + .trim_end_matches(')') + .to_string(); + + let ty = IntrinsicType::Type { + constant: true, + kind: TypeKind::Int, + bit_len: Some(32), + simd_len: None, + vec_len: None, + }; + + let constraints = prep + .split(';') + .find_map(|p| handle_range_constraint(&name, p).or_else(|| handle_eq_constraint(&name, p))) + .map(|c| vec![c]) + .unwrap_or_default(); + + Argument { + pos: idx, + name, + ty, + constraints, + } +} + +fn handle_range_constraint(name: &str, data: &str) -> Option { + lazy_static! { + static ref RANGE_CONSTRAINT: Regex = + Regex::new(r#"([0-9]+)<=([[:alnum:]]+)<=([0-9]+)"#).unwrap(); + } + + let captures = RANGE_CONSTRAINT.captures(data)?; + if captures.get(2).map(|c| c.as_str() == name).unwrap_or(false) { + match (captures.get(1), captures.get(3)) { + (Some(start), Some(end)) => { + let start = start.as_str().parse::().unwrap(); + let end = end.as_str().parse::().unwrap() + 1; + Some(Constraint::Range(start..end)) + } + _ => panic!("Invalid constraint"), + } + } else { + None + } +} + +fn handle_eq_constraint(name: &str, data: &str) -> Option { + lazy_static! { + static ref EQ_CONSTRAINT: Regex = Regex::new(r#"([[:alnum:]]+)==([0-9]+)"#).unwrap(); + } + let captures = EQ_CONSTRAINT.captures(data)?; + if captures.get(1).map(|c| c.as_str() == name).unwrap_or(false) { + captures + .get(2) + .map(|c| Constraint::Equal(c.as_str().parse::().unwrap())) + } else { + None + } +} + +fn from_c(pos: usize, s: &str) -> Argument { + let name_index = s + .chars() + .rev() + .take_while(|c| c != &'*' && c != &' ') + .count(); + + let name_start = s.len() - name_index; + let name = s[name_start..].to_string(); + let s = s[..name_start].trim(); + + Argument { + pos, + name, + ty: type_from_c(s).unwrap_or_else(|_| panic!("Failed to parse type: {}", s)), + constraints: vec![], + } +} + +fn type_from_c(s: &str) -> Result { + const CONST_STR: &str = "const "; + + if let Some(s) = s.strip_suffix('*') { + let (s, constant) = if s.ends_with(CONST_STR) { + (&s[..s.len() - (CONST_STR.len() + 1)], true) + } else { + (s, false) + }; + + let s = s.trim_end(); + + Ok(IntrinsicType::Ptr { + constant, + child: Box::new(type_from_c(s)?), + }) + } else { + // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t] + + let (mut s, constant) = if let Some(s) = s.strip_prefix(CONST_STR) { + (s, true) + } else { + (s, false) + }; + s = s.strip_suffix("_t").unwrap_or(s); + + let mut parts = s.split('x'); // [[{bitlen}], [{simdlen}], [{vec_len}] ] + + let start = parts.next().ok_or("Impossible to parse type")?; + + if let Some(digit_start) = start.find(|c: char| c.is_ascii_digit()) { + let (arg_kind, bit_len) = start.split_at(digit_start); + + let arg_kind = arg_kind.parse::()?; + let bit_len = bit_len.parse::().map_err(|err| err.to_string())?; + + let simd_len = parts.next().map(|part| part.parse::().ok()).flatten(); + let vec_len = parts.next().map(|part| part.parse::().ok()).flatten(); + + Ok(IntrinsicType::Type { + constant, + kind: arg_kind, + bit_len: Some(bit_len), + simd_len, + vec_len, + }) + } else { + Ok(IntrinsicType::Type { + constant, + kind: start.parse::()?, + bit_len: None, + simd_len: None, + vec_len: None, + }) + } + } +} + +#[derive(Deserialize, Debug, PartialEq, Clone)] +struct ACLEIntrinsicLine { + #[serde(rename = "Intrinsic")] + intrinsic: String, + #[serde(rename = "Argument preparation")] + argument_preparation: String, + #[serde(rename = "AArch64 Instruction")] + aarch64_instruction: String, + #[serde(rename = "Result")] + result: String, + #[serde(rename = "Supported architectures")] + supported_architectures: String, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::argument::Argument; + use crate::types::{IntrinsicType, TypeKind}; + + #[test] + fn parse_simd() { + let expected = Argument { + pos: 0, + name: "a".into(), + ty: IntrinsicType::Type { + constant: false, + kind: TypeKind::Int, + bit_len: Some(32), + simd_len: Some(4), + vec_len: None, + }, + constraints: vec![], + }; + let actual = from_c(0, "int32x4_t a"); + assert_eq!(expected, actual); + } + + #[test] + fn parse_simd_with_vec() { + let expected = Argument { + pos: 0, + name: "a".into(), + ty: IntrinsicType::Type { + constant: false, + kind: TypeKind::Int, + bit_len: Some(32), + simd_len: Some(4), + vec_len: Some(2), + }, + constraints: vec![], + }; + let actual = from_c(0, "int32x4x2_t a"); + assert_eq!(expected, actual); + } + + #[test] + fn test_ptr() { + let expected = Argument { + pos: 0, + name: "ptr".into(), + ty: crate::types::IntrinsicType::Ptr { + constant: true, + child: Box::new(IntrinsicType::Type { + constant: false, + kind: TypeKind::Int, + bit_len: Some(8), + simd_len: None, + vec_len: None, + }), + }, + constraints: vec![], + }; + let actual = from_c(0, "int8_t const *ptr"); + assert_eq!(expected, actual); + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/argument.rs b/library/stdarch/crates/intrinsic-test/src/argument.rs new file mode 100644 index 000000000..f4cb77992 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/argument.rs @@ -0,0 +1,139 @@ +use std::ops::Range; + +use crate::types::IntrinsicType; +use crate::Language; + +/// An argument for the intrinsic. +#[derive(Debug, PartialEq, Clone)] +pub struct Argument { + /// The argument's index in the intrinsic function call. + pub pos: usize, + /// The argument name. + pub name: String, + /// The type of the argument. + pub ty: IntrinsicType, + /// Any constraints that are on this argument + pub constraints: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Constraint { + Equal(i64), + Range(Range), +} + +impl Constraint { + pub fn to_range(&self) -> Range { + match self { + Constraint::Equal(eq) => *eq..*eq + 1, + Constraint::Range(range) => range.clone(), + } + } +} + +impl Argument { + fn to_c_type(&self) -> String { + self.ty.c_type() + } + + fn is_simd(&self) -> bool { + self.ty.is_simd() + } + + pub fn is_ptr(&self) -> bool { + self.ty.is_ptr() + } + + pub fn has_constraint(&self) -> bool { + !self.constraints.is_empty() + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ArgumentList { + pub args: Vec, +} + +impl ArgumentList { + /// Converts the argument list into the call parameters for a C function call. + /// e.g. this would generate something like `a, &b, c` + pub fn as_call_param_c(&self) -> String { + self.args + .iter() + .map(|arg| match arg.ty { + IntrinsicType::Ptr { .. } => { + format!("&{}", arg.name) + } + IntrinsicType::Type { .. } => arg.name.clone(), + }) + .collect::>() + .join(", ") + } + + /// Converts the argument list into the call parameters for a Rust function. + /// e.g. this would generate something like `a, b, c` + pub fn as_call_param_rust(&self) -> String { + self.args + .iter() + .filter(|a| !a.has_constraint()) + .map(|arg| arg.name.clone()) + .collect::>() + .join(", ") + } + + pub fn as_constraint_parameters_rust(&self) -> String { + self.args + .iter() + .filter(|a| a.has_constraint()) + .map(|arg| arg.name.clone()) + .collect::>() + .join(", ") + } + + /// Creates a line that initializes this argument for C code. + /// e.g. `int32x2_t a = { 0x1, 0x2 };` + pub fn init_random_values_c(&self, pass: usize) -> String { + self.iter() + .filter_map(|arg| { + (!arg.has_constraint()).then(|| { + format!( + "{ty} {name} = {{ {values} }};", + ty = arg.to_c_type(), + name = arg.name, + values = arg.ty.populate_random(pass, &Language::C) + ) + }) + }) + .collect::>() + .join("\n ") + } + + /// Creates a line that initializes this argument for Rust code. + /// e.g. `let a = transmute([0x1, 0x2]);` + pub fn init_random_values_rust(&self, pass: usize) -> String { + self.iter() + .filter_map(|arg| { + (!arg.has_constraint()).then(|| { + if arg.is_simd() { + format!( + "let {name} = ::std::mem::transmute([{values}]);", + name = arg.name, + values = arg.ty.populate_random(pass, &Language::Rust), + ) + } else { + format!( + "let {name} = {value};", + name = arg.name, + value = arg.ty.populate_random(pass, &Language::Rust) + ) + } + }) + }) + .collect::>() + .join("\n ") + } + + pub fn iter(&self) -> std::slice::Iter<'_, Argument> { + self.args.iter() + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/intrinsic.rs b/library/stdarch/crates/intrinsic-test/src/intrinsic.rs new file mode 100644 index 000000000..2b7130440 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/intrinsic.rs @@ -0,0 +1,125 @@ +use crate::types::{IntrinsicType, TypeKind}; + +use super::argument::ArgumentList; + +/// An intrinsic +#[derive(Debug, PartialEq, Clone)] +pub struct Intrinsic { + /// The function name of this intrinsic. + pub name: String, + + /// Any arguments for this intrinsic. + pub arguments: ArgumentList, + + /// The return type of this intrinsic. + pub results: IntrinsicType, + + /// Whether this intrinsic is only available on A64. + pub a64_only: bool, +} + +impl Intrinsic { + /// Generates a std::cout for the intrinsics results that will match the + /// rust debug output format for the return type. + pub fn print_result_c(&self, index: usize, additional: &str) -> String { + let lanes = if self.results.num_vectors() > 1 { + (0..self.results.num_vectors()) + .map(|vector| { + format!( + r#""{ty}(" << {lanes} << ")""#, + ty = self.results.c_single_vector_type(), + lanes = (0..self.results.num_lanes()) + .map(move |idx| -> std::string::String { + format!( + "{cast}{lane_fn}(__return_value.val[{vector}], {lane})", + cast = self.results.c_promotion(), + lane_fn = self.results.get_lane_function(), + lane = idx, + vector = vector, + ) + }) + .collect::>() + .join(r#" << ", " << "#) + ) + }) + .collect::>() + .join(r#" << ", " << "#) + } else if self.results.num_lanes() > 1 { + (0..self.results.num_lanes()) + .map(|idx| -> std::string::String { + format!( + "{cast}{lane_fn}(__return_value, {lane})", + cast = self.results.c_promotion(), + lane_fn = self.results.get_lane_function(), + lane = idx + ) + }) + .collect::>() + .join(r#" << ", " << "#) + } else { + format!( + "{promote}cast<{cast}>(__return_value)", + cast = match self.results.kind() { + TypeKind::Float if self.results.inner_size() == 32 => "float".to_string(), + TypeKind::Float if self.results.inner_size() == 64 => "double".to_string(), + TypeKind::Int => format!("int{}_t", self.results.inner_size()), + TypeKind::UInt => format!("uint{}_t", self.results.inner_size()), + TypeKind::Poly => format!("poly{}_t", self.results.inner_size()), + ty => todo!("print_result_c - Unknown type: {:#?}", ty), + }, + promote = self.results.c_promotion(), + ) + }; + + format!( + r#"std::cout << "Result {additional}-{idx}: {ty}" << std::fixed << std::setprecision(150) << {lanes} << "{close}" << std::endl;"#, + ty = if self.results.is_simd() { + format!("{}(", self.results.c_type()) + } else { + String::from("") + }, + close = if self.results.is_simd() { ")" } else { "" }, + lanes = lanes, + additional = additional, + idx = index, + ) + } + + pub fn generate_pass_rust(&self, index: usize, additional: &str) -> String { + let constraints = self.arguments.as_constraint_parameters_rust(); + let constraints = if !constraints.is_empty() { + format!("::<{}>", constraints) + } else { + constraints + }; + + format!( + r#" + unsafe {{ + {initialized_args} + let res = {intrinsic_call}{const}({args}); + println!("Result {additional}-{idx}: {{:.150?}}", res); + }}"#, + initialized_args = self.arguments.init_random_values_rust(index), + intrinsic_call = self.name, + args = self.arguments.as_call_param_rust(), + additional = additional, + idx = index, + const = constraints, + ) + } + + pub fn generate_pass_c(&self, index: usize, additional: &str) -> String { + format!( + r#" {{ + {initialized_args} + auto __return_value = {intrinsic_call}({args}); + {print_result} + }}"#, + initialized_args = self.arguments.init_random_values_c(index), + intrinsic_call = self.name, + args = self.arguments.as_call_param_c(), + print_result = self.print_result_c(index, additional) + ) + } +} 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::>() + .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 +#include +#include +#include + +template 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::>() + .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::>() + .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, 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, 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::>() + .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::>(); + 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, 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::>(); + + intrinsics.iter().for_each(|reason| match reason { + FailureReason::Difference(intrinsic, c, rust) => { + println!("Difference for intrinsic: {}", intrinsic); + let diff = diff::lines(c, rust); + diff.iter().for_each(|diff| match diff { + diff::Result::Left(c) => println!("C: {}", c), + diff::Result::Right(rust) => println!("Rust: {}", rust), + diff::Result::Both(_, _) => (), + }); + println!("****************************************************************"); + } + FailureReason::RunC(intrinsic) => { + println!("Failed to run C program for intrinsic {}", intrinsic) + } + FailureReason::RunRust(intrinsic) => { + println!("Failed to run rust program for intrinsic {}", intrinsic) + } + }); + println!("{} differences found", intrinsics.len()); + intrinsics.is_empty() +} diff --git a/library/stdarch/crates/intrinsic-test/src/types.rs b/library/stdarch/crates/intrinsic-test/src/types.rs new file mode 100644 index 000000000..e51e61649 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/types.rs @@ -0,0 +1,436 @@ +use std::fmt; +use std::str::FromStr; + +use crate::values::values_for_pass; +use crate::Language; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum TypeKind { + BFloat, + Float, + Int, + UInt, + Poly, + Void, +} + +impl FromStr for TypeKind { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "bfloat" => Ok(Self::BFloat), + "float" => Ok(Self::Float), + "int" => Ok(Self::Int), + "poly" => Ok(Self::Poly), + "uint" | "unsigned" => Ok(Self::UInt), + "void" => Ok(Self::Void), + _ => Err(format!("Impossible to parse argument kind {}", s)), + } + } +} + +impl fmt::Display for TypeKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::BFloat => "bfloat", + Self::Float => "float", + Self::Int => "int", + Self::UInt => "uint", + Self::Poly => "poly", + Self::Void => "void", + } + ) + } +} + +impl TypeKind { + /// Gets the type part of a c typedef for a type that's in the form of {type}{size}_t. + pub fn c_prefix(&self) -> &str { + match self { + Self::Float => "float", + Self::Int => "int", + Self::UInt => "uint", + Self::Poly => "poly", + _ => unreachable!("Not used: {:#?}", self), + } + } + + /// Gets the rust prefix for the type kind i.e. i, u, f. + pub fn rust_prefix(&self) -> &str { + match self { + Self::Float => "f", + Self::Int => "i", + Self::UInt => "u", + Self::Poly => "u", + _ => unreachable!("Unused type kind: {:#?}", self), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum IntrinsicType { + Ptr { + constant: bool, + child: Box, + }, + Type { + constant: bool, + kind: TypeKind, + /// The bit length of this type (e.g. 32 for u32). + bit_len: Option, + + /// Length of the SIMD vector (i.e. 4 for uint32x4_t), A value of `None` + /// means this is not a simd type. A `None` can be assumed to be 1, + /// although in some places a distinction is needed between `u64` and + /// `uint64x1_t` this signals that. + simd_len: Option, + + /// The number of rows for SIMD matrices (i.e. 2 for uint8x8x2_t). + /// A value of `None` represents a type that does not contain any + /// rows encoded in the type (e.g. uint8x8_t). + /// A value of `None` can be assumed to be 1 though. + vec_len: Option, + }, +} + +impl IntrinsicType { + /// Get the TypeKind for this type, recursing into pointers. + pub fn kind(&self) -> TypeKind { + match *self { + IntrinsicType::Ptr { ref child, .. } => child.kind(), + IntrinsicType::Type { kind, .. } => kind, + } + } + + /// Get the size of a single element inside this type, recursing into + /// pointers, i.e. a pointer to a u16 would be 16 rather than the size + /// of a pointer. + pub fn inner_size(&self) -> u32 { + match *self { + IntrinsicType::Ptr { ref 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, + } + } + + #[allow(unused)] + 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() + ) + } + + /// Gets a string containing the typename for this type in C format. + pub fn c_type(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.c_type(), + IntrinsicType::Type { + constant, + kind, + bit_len: Some(bit_len), + simd_len: None, + vec_len: None, + .. + } => format!( + "{}{}{}_t", + if *constant { "const " } else { "" }, + kind.c_prefix(), + bit_len + ), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: None, + .. + } => format!("{}{}x{}_t", kind.c_prefix(), bit_len, simd_len), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: Some(vec_len), + .. + } => format!("{}{}x{}x{}_t", kind.c_prefix(), bit_len, simd_len, vec_len), + _ => todo!("{:#?}", self), + } + } + + pub fn c_single_vector_type(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.c_single_vector_type(), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: Some(_), + .. + } => format!("{}{}x{}_t", kind.c_prefix(), bit_len, simd_len), + _ => unreachable!("Shouldn't be called on this type"), + } + } + + pub fn rust_type(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.c_type(), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: None, + vec_len: None, + .. + } => format!("{}{}", kind.rust_prefix(), bit_len), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: None, + .. + } => format!("{}{}x{}_t", kind.c_prefix(), bit_len, simd_len), + IntrinsicType::Type { + kind, + bit_len: Some(bit_len), + simd_len: Some(simd_len), + vec_len: Some(vec_len), + .. + } => format!("{}{}x{}x{}_t", kind.c_prefix(), bit_len, simd_len, vec_len), + _ => 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(bit_len), + .. + } if bit_len == 8 => match kind { + TypeKind::Int => "(int)", + TypeKind::UInt => "(unsigned int)", + TypeKind::Poly => "(unsigned int)(uint8_t)", + _ => "", + }, + IntrinsicType::Type { + kind: TypeKind::Poly, + bit_len: Some(bit_len), + .. + } => match bit_len { + 8 => unreachable!("handled above"), + 16 => "(uint16_t)", + 32 => "(uint32_t)", + 64 => "(uint64_t)", + 128 => "", + _ => panic!("invalid bit_len"), + }, + _ => "", + } + } + + /// Generates a comma list of values that can be used to initialize an + /// argument for the intrinsic call. + /// This is determistic based on the pass number. + /// + /// * `pass`: The pass index, i.e. the iteration index for the call to an intrinsic + /// + /// 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, pass: usize, language: &Language) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.populate_random(pass, language), + IntrinsicType::Type { + bit_len: Some(bit_len), + kind, + simd_len, + vec_len, + .. + } if kind == &TypeKind::Int || kind == &TypeKind::UInt || kind == &TypeKind::Poly => (0 + ..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1))) + .map(|i| { + format!( + "{}{}", + values_for_pass(*bit_len, i, pass), + match language { + &Language::Rust => format!(" as {ty} ", ty = self.rust_scalar_type()), + &Language::C => String::from(""), + } + ) + }) + .collect::>() + .join(","), + IntrinsicType::Type { + kind: TypeKind::Float, + bit_len: Some(32), + simd_len, + vec_len, + .. + } => (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1))) + .map(|i| { + format!( + "{}({})", + match language { + &Language::Rust => "f32::from_bits", + &Language::C => "cast", + }, + values_for_pass(32, i, pass), + ) + }) + .collect::>() + .join(","), + IntrinsicType::Type { + kind: TypeKind::Float, + bit_len: Some(64), + simd_len, + vec_len, + .. + } => (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1))) + .map(|i| { + format!( + "{}({}{})", + match language { + &Language::Rust => "f64::from_bits", + &Language::C => "cast", + }, + values_for_pass(64, i, pass), + match language { + &Language::Rust => " as u64", + &Language::C => "", + } + ) + }) + .collect::>() + .join(","), + _ => unreachable!("populate random: {:#?}", self), + } + } + + /// Determines the load function for this type. + #[allow(unused)] + pub fn get_load_function(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.get_load_function(), + IntrinsicType::Type { + kind: k, + bit_len: Some(bl), + simd_len, + vec_len, + .. + } => { + let quad = if (simd_len.unwrap_or(1) * bl) > 64 { + "q" + } else { + "" + }; + format!( + "vld{len}{quad}_{type}{size}", + type = match k { + TypeKind::UInt => "u", + TypeKind::Int => "s", + TypeKind::Float => "f", + TypeKind::Poly => "p", + x => todo!("get_load_function TypeKind: {:#?}", x), + }, + size = bl, + quad = quad, + len = vec_len.unwrap_or(1), + ) + } + _ => todo!("get_load_function IntrinsicType: {:#?}", self), + } + } + + /// Determines the get lane function for this type. + pub fn get_lane_function(&self) -> String { + match self { + IntrinsicType::Ptr { child, .. } => child.get_lane_function(), + IntrinsicType::Type { + kind: k, + bit_len: Some(bl), + simd_len, + .. + } => { + let quad = if (simd_len.unwrap_or(1) * bl) > 64 { + "q" + } else { + "" + }; + format!( + "vget{quad}_lane_{type}{size}", + type = match k { + TypeKind::UInt => "u", + TypeKind::Int => "s", + TypeKind::Float => "f", + TypeKind::Poly => "p", + x => todo!("get_load_function TypeKind: {:#?}", x), + }, + size = bl, + quad = quad, + ) + } + _ => todo!("get_lane_function IntrinsicType: {:#?}", self), + } + } +} diff --git a/library/stdarch/crates/intrinsic-test/src/values.rs b/library/stdarch/crates/intrinsic-test/src/values.rs new file mode 100644 index 000000000..4565edca0 --- /dev/null +++ b/library/stdarch/crates/intrinsic-test/src/values.rs @@ -0,0 +1,126 @@ +/// Gets a hex constant value for a single lane in in a determistic way +/// * `bits`: The number of bits for the type, only 8, 16, 32, 64 are valid values +/// * `simd`: The index of the simd lane we are generating for +/// * `pass`: The index of the pass we are generating the values for +pub fn values_for_pass(bits: u32, simd: u32, pass: usize) -> String { + let index = pass + (simd as usize); + + if bits == 8 { + format!("{:#X}", VALUES_8[index % VALUES_8.len()]) + } else if bits == 16 { + format!("{:#X}", VALUES_16[index % VALUES_16.len()]) + } else if bits == 32 { + format!("{:#X}", VALUES_32[index % VALUES_32.len()]) + } else if bits == 64 { + format!("{:#X}", VALUES_64[index % VALUES_64.len()]) + } else { + panic!("Unknown size: {}", bits); + } +} + +pub const VALUES_8: &[u8] = &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0xf0, 0x80, 0x3b, 0xff, +]; + +pub const VALUES_16: &[u16] = &[ + 0x0000, // 0.0 + 0x0400, // The smallest normal value. + 0x37ff, // The value just below 0.5. + 0x3800, // 0.5 + 0x3801, // The value just above 0.5. + 0x3bff, // The value just below 1.0. + 0x3c00, // 1.0 + 0x3c01, // The value just above 1.0. + 0x3e00, // 1.5 + 0x4900, // 10 + 0x7bff, // The largest finite value. + 0x7c00, // Infinity. + // NaNs. + // - Quiet NaNs + 0x7f23, 0x7e00, // - Signalling NaNs + 0x7d23, 0x7c01, // Subnormals. + // - A recognisable bit pattern. + 0x0012, // - The largest subnormal value. + 0x03ff, // - The smallest subnormal value. + 0x0001, // The same values again, but negated. + 0x8000, 0x8400, 0xb7ff, 0xb800, 0xb801, 0xbbff, 0xbc00, 0xbc01, 0xbe00, 0xc900, 0xfbff, 0xfc00, + 0xff23, 0xfe00, 0xfd23, 0xfc01, 0x8012, 0x83ff, 0x8001, +]; + +pub const VALUES_32: &[u32] = &[ + // Simple values. + 0x00000000, // 0.0 + 0x00800000, // The smallest normal value. + 0x3effffff, // The value just below 0.5. + 0x3f000000, // 0.5 + 0x3f000001, // The value just above 0.5. + 0x3f7fffff, // The value just below 1.0. + 0x3f800000, // 1.0 + 0x3f800001, // The value just above 1.0. + 0x3fc00000, // 1.5 + 0x41200000, // 10 + 0x7f8fffff, // The largest finite value. + 0x7f800000, // Infinity. + // NaNs. + // - Quiet NaNs + 0x7fd23456, 0x7fc00000, // - Signalling NaNs + 0x7f923456, 0x7f800001, // Subnormals. + // - A recognisable bit pattern. + 0x00123456, // - The largest subnormal value. + 0x007fffff, // - The smallest subnormal value. + 0x00000001, // The same values again, but negated. + 0x80000000, 0x80800000, 0xbeffffff, 0xbf000000, 0xbf000001, 0xbf7fffff, 0xbf800000, 0xbf800001, + 0xbfc00000, 0xc1200000, 0xff8fffff, 0xff800000, 0xffd23456, 0xffc00000, 0xff923456, 0xff800001, + 0x80123456, 0x807fffff, 0x80000001, +]; + +pub const VALUES_64: &[u64] = &[ + // Simple values. + 0x0000000000000000, // 0.0 + 0x0010000000000000, // The smallest normal value. + 0x3fdfffffffffffff, // The value just below 0.5. + 0x3fe0000000000000, // 0.5 + 0x3fe0000000000001, // The value just above 0.5. + 0x3fefffffffffffff, // The value just below 1.0. + 0x3ff0000000000000, // 1.0 + 0x3ff0000000000001, // The value just above 1.0. + 0x3ff8000000000000, // 1.5 + 0x4024000000000000, // 10 + 0x7fefffffffffffff, // The largest finite value. + 0x7ff0000000000000, // Infinity. + // NaNs. + // - Quiet NaNs + 0x7ff923456789abcd, + 0x7ff8000000000000, + // - Signalling NaNs + 0x7ff123456789abcd, + 0x7ff0000000000000, + // Subnormals. + // - A recognisable bit pattern. + 0x000123456789abcd, + // - The largest subnormal value. + 0x000fffffffffffff, + // - The smallest subnormal value. + 0x0000000000000001, + // The same values again, but negated. + 0x8000000000000000, + 0x8010000000000000, + 0xbfdfffffffffffff, + 0xbfe0000000000000, + 0xbfe0000000000001, + 0xbfefffffffffffff, + 0xbff0000000000000, + 0xbff0000000000001, + 0xbff8000000000000, + 0xc024000000000000, + 0xffefffffffffffff, + 0xfff0000000000000, + 0xfff923456789abcd, + 0xfff8000000000000, + 0xfff123456789abcd, + 0xfff0000000000000, + 0x800123456789abcd, + 0x800fffffffffffff, + 0x8000000000000001, +]; -- cgit v1.2.3