Skip to content

Commit 1a7d0ce

Browse files
Intrinsic test tool to compare neon intrinsics with C (rust-lang#1170)
1 parent 7fd93e3 commit 1a7d0ce

File tree

13 files changed

+5672
-2
lines changed

13 files changed

+5672
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ target
44
tags
55
crates/stdarch-gen/aarch64.rs
66
crates/stdarch-gen/arm.rs
7+
c_programs/*
8+
rust_programs/*

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"crates/core_arch",
55
"crates/std_detect",
66
"crates/stdarch-gen",
7+
"crates/intrinsic-test",
78
"examples/"
89
]
910
exclude = [

ci/docker/aarch64-unknown-linux-gnu/Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
FROM ubuntu:20.04
22
RUN apt-get update && apt-get install -y --no-install-recommends \
33
gcc \
4+
g++ \
45
ca-certificates \
56
libc6-dev \
67
gcc-aarch64-linux-gnu \
8+
g++-aarch64-linux-gnu \
79
libc6-dev-arm64-cross \
810
qemu-user \
911
make \
10-
file
12+
file \
13+
clang-12 \
14+
lld
1115

1216
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
1317
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="qemu-aarch64 -L /usr/aarch64-linux-gnu" \

ci/run-docker.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ run() {
99
target=$(echo "${1}" | sed 's/-emulated//')
1010
echo "Building docker container for TARGET=${1}"
1111
docker build -t stdarch -f "ci/docker/${1}/Dockerfile" ci/
12-
mkdir -p target
12+
mkdir -p target c_programs rust_programs
1313
echo "Running docker"
1414
# shellcheck disable=SC2016
1515
docker run \
@@ -29,6 +29,8 @@ run() {
2929
--volume "$(rustc --print sysroot)":/rust:ro \
3030
--volume "$(pwd)":/checkout:ro \
3131
--volume "$(pwd)"/target:/checkout/target \
32+
--volume "$(pwd)"/c_programs:/checkout/c_programs \
33+
--volume "$(pwd)"/rust_programs:/checkout/rust_programs \
3234
--init \
3335
--workdir /checkout \
3436
--privileged \

ci/run.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ cargo_test() {
6565
CORE_ARCH="--manifest-path=crates/core_arch/Cargo.toml"
6666
STD_DETECT="--manifest-path=crates/std_detect/Cargo.toml"
6767
STDARCH_EXAMPLES="--manifest-path=examples/Cargo.toml"
68+
INTRINSIC_TEST="--manifest-path=crates/intrinsic-test/Cargo.toml"
69+
6870
cargo_test "${CORE_ARCH} --release"
6971

7072
if [ "$NOSTD" != "1" ]; then
@@ -111,6 +113,11 @@ case ${TARGET} in
111113

112114
esac
113115

116+
if [ "${TARGET}" = "aarch64-unknown-linux-gnu" ]; then
117+
export CPPFLAGS="-fuse-ld=lld -I/usr/aarch64-linux-gnu/include/ -I/usr/aarch64-linux-gnu/include/c++/9/aarch64-linux-gnu/"
118+
cargo run ${INTRINSIC_TEST} --release --bin intrinsic-test -- crates/intrinsic-test/neon-intrinsics.csv --runner "${CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER}" --cppcompiler "clang++-12"
119+
fi
120+
114121
if [ "$NORUN" != "1" ] && [ "$NOSTD" != 1 ]; then
115122
# Test examples
116123
(

crates/intrinsic-test/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "intrinsic-test"
3+
version = "0.1.0"
4+
authors = ["Jamie Cunliffe <[email protected]>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
lazy_static = "1.4.0"
9+
serde = { version = "1", features = ["derive"] }
10+
csv = "1.1"
11+
clap = "2.33.3"
12+
regex = "1.4.2"
13+
log = "0.4.11"
14+
pretty_env_logger = "0.4.0"
15+
rayon = "1.5.0"
16+
diff = "0.1.12"

crates/intrinsic-test/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Generate and run programs using equivalent C and Rust intrinsics, checking that
2+
each produces the same result from random inputs.
3+
4+
# Usage
5+
```
6+
USAGE:
7+
intrinsic-test [OPTIONS] <INPUT>
8+
9+
FLAGS:
10+
-h, --help Prints help information
11+
-V, --version Prints version information
12+
13+
OPTIONS:
14+
--cppcompiler <CPPCOMPILER> The C++ compiler to use for compiling the c++ code [default: clang++]
15+
--runner <RUNNER> Run the C programs under emulation with this command
16+
--toolchain <TOOLCHAIN> The rust toolchain to use for building the rust code
17+
18+
ARGS:
19+
<INPUT> The input file containing the intrinsics
20+
```
21+
22+
The intrinsic.csv is the arm neon tracking google sheet (https://docs.google.com/spreadsheets/d/1MqW1g8c7tlhdRWQixgdWvR4uJHNZzCYAf4V0oHjZkwA/edit#gid=0)
23+
that contains the intrinsic list. The done percentage column should be renamed to "enabled".
24+

crates/intrinsic-test/neon-intrinsics.csv

Lines changed: 4356 additions & 0 deletions
Large diffs are not rendered by default.

crates/intrinsic-test/src/argument.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use serde::{Deserialize, Deserializer};
2+
3+
use crate::types::IntrinsicType;
4+
use crate::Language;
5+
6+
/// An argument for the intrinsic.
7+
#[derive(Debug, PartialEq, Clone)]
8+
pub struct Argument {
9+
/// The argument's index in the intrinsic function call.
10+
pub pos: usize,
11+
/// The argument name.
12+
pub name: String,
13+
/// The type of the argument.
14+
pub ty: IntrinsicType,
15+
}
16+
17+
impl Argument {
18+
/// Creates an argument from a Rust style signature i.e. `name: type`
19+
fn from_rust(pos: usize, arg: &str) -> Result<Self, String> {
20+
let mut parts = arg.split(':');
21+
let name = parts.next().unwrap().trim().to_string();
22+
let ty = IntrinsicType::from_rust(parts.next().unwrap().trim())?;
23+
24+
Ok(Self { pos, name, ty })
25+
}
26+
27+
fn to_c_type(&self) -> String {
28+
self.ty.c_type()
29+
}
30+
31+
fn is_simd(&self) -> bool {
32+
self.ty.is_simd()
33+
}
34+
35+
pub fn is_ptr(&self) -> bool {
36+
self.ty.is_ptr()
37+
}
38+
}
39+
40+
#[derive(Debug, PartialEq, Clone)]
41+
pub struct ArgumentList {
42+
pub args: Vec<Argument>,
43+
}
44+
45+
impl ArgumentList {
46+
/// Creates an argument list from a Rust function signature, the data for
47+
/// this function should only be the arguments.
48+
/// e.g. for `fn test(a: u32, b: u32) -> u32` data should just be `a: u32, b: u32`
49+
fn from_rust_arguments(data: &str) -> Result<Self, String> {
50+
let args = data
51+
.split(',')
52+
.enumerate()
53+
.map(|(idx, arg)| Argument::from_rust(idx, arg))
54+
.collect::<Result<_, _>>()?;
55+
56+
Ok(Self { args })
57+
}
58+
59+
/// Converts the argument list into the call paramters for a C function call.
60+
/// e.g. this would generate something like `a, &b, c`
61+
pub fn as_call_param_c(&self) -> String {
62+
self.args
63+
.iter()
64+
.map(|arg| match arg.ty {
65+
IntrinsicType::Ptr { .. } => {
66+
format!("&{}", arg.name)
67+
}
68+
IntrinsicType::Type { .. } => arg.name.clone(),
69+
})
70+
.collect::<Vec<String>>()
71+
.join(", ")
72+
}
73+
74+
/// Converts the argument list into the call paramters for a Rust function.
75+
/// e.g. this would generate something like `a, b, c`
76+
pub fn as_call_param_rust(&self) -> String {
77+
self.args
78+
.iter()
79+
.map(|arg| arg.name.clone())
80+
.collect::<Vec<String>>()
81+
.join(", ")
82+
}
83+
84+
/// Creates a line that initializes this argument for C code.
85+
/// e.g. `int32x2_t a = { 0x1, 0x2 };`
86+
pub fn init_random_values_c(&self, pass: usize) -> String {
87+
self.iter()
88+
.map(|arg| {
89+
format!(
90+
"{ty} {name} = {{ {values} }};",
91+
ty = arg.to_c_type(),
92+
name = arg.name,
93+
values = arg.ty.populate_random(pass, &Language::C)
94+
)
95+
})
96+
.collect::<Vec<_>>()
97+
.join("\n ")
98+
}
99+
100+
/// Creates a line that initializes this argument for Rust code.
101+
/// e.g. `let a = transmute([0x1, 0x2]);`
102+
pub fn init_random_values_rust(&self, pass: usize) -> String {
103+
self.iter()
104+
.map(|arg| {
105+
if arg.is_simd() {
106+
format!(
107+
"let {name} = ::std::mem::transmute([{values}]);",
108+
name = arg.name,
109+
values = arg.ty.populate_random(pass, &Language::Rust),
110+
)
111+
} else {
112+
format!(
113+
"let {name} = {value};",
114+
name = arg.name,
115+
value = arg.ty.populate_random(pass, &Language::Rust)
116+
)
117+
}
118+
})
119+
.collect::<Vec<_>>()
120+
.join("\n ")
121+
}
122+
123+
pub fn iter(&self) -> std::slice::Iter<'_, Argument> {
124+
self.args.iter()
125+
}
126+
}
127+
128+
impl<'de> Deserialize<'de> for ArgumentList {
129+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130+
where
131+
D: Deserializer<'de>,
132+
{
133+
use serde::de::Error;
134+
let s = String::deserialize(deserializer)?;
135+
Self::from_rust_arguments(&s).map_err(Error::custom)
136+
}
137+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use crate::types::{IntrinsicType, TypeKind};
2+
3+
use super::argument::ArgumentList;
4+
use serde::de::Unexpected;
5+
use serde::{de, Deserialize, Deserializer};
6+
7+
/// An intrinsic
8+
#[derive(Deserialize, Debug, PartialEq, Clone)]
9+
pub struct Intrinsic {
10+
/// If the intrinsic should be tested.
11+
#[serde(deserialize_with = "bool_from_string")]
12+
pub enabled: bool,
13+
14+
/// The function name of this intrinsic.
15+
pub name: String,
16+
17+
/// Any arguments for this intrinsinc.
18+
#[serde(rename = "args")]
19+
pub arguments: ArgumentList,
20+
21+
/// The return type of this intrinsic.
22+
#[serde(rename = "return")]
23+
pub results: IntrinsicType,
24+
}
25+
26+
impl Intrinsic {
27+
/// Generates a std::cout for the intrinsics results that will match the
28+
/// rust debug output format for the return type.
29+
pub fn print_result_c(&self, index: usize) -> String {
30+
let lanes = if self.results.num_lanes() > 1 {
31+
(0..self.results.num_lanes())
32+
.map(|idx| -> std::string::String {
33+
format!(
34+
"{cast}{lane_fn}(__return_value, {lane})",
35+
cast = self.results.c_promotion(),
36+
lane_fn = self.results.get_lane_function(),
37+
lane = idx
38+
)
39+
})
40+
.collect::<Vec<_>>()
41+
.join(r#" << ", " << "#)
42+
} else {
43+
format!(
44+
"{promote}cast<{cast}>(__return_value)",
45+
cast = match self.results.kind() {
46+
TypeKind::Float if self.results.inner_size() == 32 => "float".to_string(),
47+
TypeKind::Float if self.results.inner_size() == 64 => "double".to_string(),
48+
TypeKind::Int => format!("int{}_t", self.results.inner_size()),
49+
TypeKind::UInt => format!("uint{}_t", self.results.inner_size()),
50+
TypeKind::Poly => format!("poly{}_t", self.results.inner_size()),
51+
ty => todo!("print_result_c - Unknown type: {:#?}", ty),
52+
},
53+
promote = self.results.c_promotion(),
54+
)
55+
};
56+
57+
format!(
58+
r#"std::cout << "Result {idx}: {ty}" << std::fixed << std::setprecision(150) << {lanes} << "{close}" << std::endl;"#,
59+
ty = if self.results.is_simd() {
60+
format!("{}(", self.results.c_type())
61+
} else {
62+
String::from("")
63+
},
64+
close = if self.results.is_simd() { ")" } else { "" },
65+
lanes = lanes,
66+
idx = index,
67+
)
68+
}
69+
70+
pub fn generate_pass_rust(&self, index: usize) -> String {
71+
format!(
72+
r#"
73+
unsafe {{
74+
{initialized_args}
75+
let res = {intrinsic_call}({args});
76+
println!("Result {idx}: {{:.150?}}", res);
77+
}}"#,
78+
initialized_args = self.arguments.init_random_values_rust(index),
79+
intrinsic_call = self.name,
80+
args = self.arguments.as_call_param_rust(),
81+
idx = index,
82+
)
83+
}
84+
85+
pub fn generate_pass_c(&self, index: usize) -> String {
86+
format!(
87+
r#" {{
88+
{initialized_args}
89+
auto __return_value = {intrinsic_call}({args});
90+
{print_result}
91+
}}"#,
92+
initialized_args = self.arguments.init_random_values_c(index),
93+
intrinsic_call = self.name,
94+
args = self.arguments.as_call_param_c(),
95+
print_result = self.print_result_c(index)
96+
)
97+
}
98+
}
99+
100+
fn bool_from_string<'de, D>(deserializer: D) -> Result<bool, D::Error>
101+
where
102+
D: Deserializer<'de>,
103+
{
104+
match String::deserialize(deserializer)?.to_uppercase().as_ref() {
105+
"TRUE" => Ok(true),
106+
"FALSE" => Ok(false),
107+
other => Err(de::Error::invalid_value(
108+
Unexpected::Str(other),
109+
&"TRUE or FALSE",
110+
)),
111+
}
112+
}

0 commit comments

Comments
 (0)