Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Intrinsic test tool to compare neon intrinsics with C (#1170)
- Loading branch information
1 parent
7fd93e3
commit 1a7d0ce
Showing
13 changed files
with
5,672 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,5 @@ target | |
tags | ||
crates/stdarch-gen/aarch64.rs | ||
crates/stdarch-gen/arm.rs | ||
c_programs/* | ||
rust_programs/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "intrinsic-test" | ||
version = "0.1.0" | ||
authors = ["Jamie Cunliffe <Jamie.Cunliffe@arm.com>"] | ||
edition = "2018" | ||
|
||
[dependencies] | ||
lazy_static = "1.4.0" | ||
serde = { version = "1", features = ["derive"] } | ||
csv = "1.1" | ||
clap = "2.33.3" | ||
regex = "1.4.2" | ||
log = "0.4.11" | ||
pretty_env_logger = "0.4.0" | ||
rayon = "1.5.0" | ||
diff = "0.1.12" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Generate and run programs using equivalent C and Rust intrinsics, checking that | ||
each produces the same result from random inputs. | ||
|
||
# Usage | ||
``` | ||
USAGE: | ||
intrinsic-test [OPTIONS] <INPUT> | ||
FLAGS: | ||
-h, --help Prints help information | ||
-V, --version Prints version information | ||
OPTIONS: | ||
--cppcompiler <CPPCOMPILER> The C++ compiler to use for compiling the c++ code [default: clang++] | ||
--runner <RUNNER> Run the C programs under emulation with this command | ||
--toolchain <TOOLCHAIN> The rust toolchain to use for building the rust code | ||
ARGS: | ||
<INPUT> The input file containing the intrinsics | ||
``` | ||
|
||
The intrinsic.csv is the arm neon tracking google sheet (https://docs.google.com/spreadsheets/d/1MqW1g8c7tlhdRWQixgdWvR4uJHNZzCYAf4V0oHjZkwA/edit#gid=0) | ||
that contains the intrinsic list. The done percentage column should be renamed to "enabled". | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
use serde::{Deserialize, Deserializer}; | ||
|
||
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, | ||
} | ||
|
||
impl Argument { | ||
/// Creates an argument from a Rust style signature i.e. `name: type` | ||
fn from_rust(pos: usize, arg: &str) -> Result<Self, String> { | ||
let mut parts = arg.split(':'); | ||
let name = parts.next().unwrap().trim().to_string(); | ||
let ty = IntrinsicType::from_rust(parts.next().unwrap().trim())?; | ||
|
||
Ok(Self { pos, name, ty }) | ||
} | ||
|
||
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() | ||
} | ||
} | ||
|
||
#[derive(Debug, PartialEq, Clone)] | ||
pub struct ArgumentList { | ||
pub args: Vec<Argument>, | ||
} | ||
|
||
impl ArgumentList { | ||
/// Creates an argument list from a Rust function signature, the data for | ||
/// this function should only be the arguments. | ||
/// e.g. for `fn test(a: u32, b: u32) -> u32` data should just be `a: u32, b: u32` | ||
fn from_rust_arguments(data: &str) -> Result<Self, String> { | ||
let args = data | ||
.split(',') | ||
.enumerate() | ||
.map(|(idx, arg)| Argument::from_rust(idx, arg)) | ||
.collect::<Result<_, _>>()?; | ||
|
||
Ok(Self { args }) | ||
} | ||
|
||
/// Converts the argument list into the call paramters 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::<Vec<String>>() | ||
.join(", ") | ||
} | ||
|
||
/// Converts the argument list into the call paramters 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() | ||
.map(|arg| arg.name.clone()) | ||
.collect::<Vec<String>>() | ||
.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() | ||
.map(|arg| { | ||
format!( | ||
"{ty} {name} = {{ {values} }};", | ||
ty = arg.to_c_type(), | ||
name = arg.name, | ||
values = arg.ty.populate_random(pass, &Language::C) | ||
) | ||
}) | ||
.collect::<Vec<_>>() | ||
.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() | ||
.map(|arg| { | ||
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::<Vec<_>>() | ||
.join("\n ") | ||
} | ||
|
||
pub fn iter(&self) -> std::slice::Iter<'_, Argument> { | ||
self.args.iter() | ||
} | ||
} | ||
|
||
impl<'de> Deserialize<'de> for ArgumentList { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
use serde::de::Error; | ||
let s = String::deserialize(deserializer)?; | ||
Self::from_rust_arguments(&s).map_err(Error::custom) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
use crate::types::{IntrinsicType, TypeKind}; | ||
|
||
use super::argument::ArgumentList; | ||
use serde::de::Unexpected; | ||
use serde::{de, Deserialize, Deserializer}; | ||
|
||
/// An intrinsic | ||
#[derive(Deserialize, Debug, PartialEq, Clone)] | ||
pub struct Intrinsic { | ||
/// If the intrinsic should be tested. | ||
#[serde(deserialize_with = "bool_from_string")] | ||
pub enabled: bool, | ||
|
||
/// The function name of this intrinsic. | ||
pub name: String, | ||
|
||
/// Any arguments for this intrinsinc. | ||
#[serde(rename = "args")] | ||
pub arguments: ArgumentList, | ||
|
||
/// The return type of this intrinsic. | ||
#[serde(rename = "return")] | ||
pub results: IntrinsicType, | ||
} | ||
|
||
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) -> String { | ||
let lanes = if self.results.num_lanes() > 1 { | ||
(0..self.results.num_lanes()) | ||
.map(|idx| -> std::string::String { | ||
format!( | ||
"{cast}{lane_fn}(__return_value, {lane})", | ||
cast = self.results.c_promotion(), | ||
lane_fn = self.results.get_lane_function(), | ||
lane = idx | ||
) | ||
}) | ||
.collect::<Vec<_>>() | ||
.join(r#" << ", " << "#) | ||
} else { | ||
format!( | ||
"{promote}cast<{cast}>(__return_value)", | ||
cast = match self.results.kind() { | ||
TypeKind::Float if self.results.inner_size() == 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 {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, | ||
idx = index, | ||
) | ||
} | ||
|
||
pub fn generate_pass_rust(&self, index: usize) -> String { | ||
format!( | ||
r#" | ||
unsafe {{ | ||
{initialized_args} | ||
let res = {intrinsic_call}({args}); | ||
println!("Result {idx}: {{:.150?}}", res); | ||
}}"#, | ||
initialized_args = self.arguments.init_random_values_rust(index), | ||
intrinsic_call = self.name, | ||
args = self.arguments.as_call_param_rust(), | ||
idx = index, | ||
) | ||
} | ||
|
||
pub fn generate_pass_c(&self, index: usize) -> 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) | ||
) | ||
} | ||
} | ||
|
||
fn bool_from_string<'de, D>(deserializer: D) -> Result<bool, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
match String::deserialize(deserializer)?.to_uppercase().as_ref() { | ||
"TRUE" => Ok(true), | ||
"FALSE" => Ok(false), | ||
other => Err(de::Error::invalid_value( | ||
Unexpected::Str(other), | ||
&"TRUE or FALSE", | ||
)), | ||
} | ||
} |
Oops, something went wrong.