Update test runner to support big endian
This commit is contained in:
parent
d12027810c
commit
7294081fda
5 changed files with 150 additions and 68 deletions
|
|
@ -4,7 +4,9 @@ version = "0.1.0"
|
|||
authors = ["Jamie Cunliffe <Jamie.Cunliffe@arm.com>",
|
||||
"James McGregor <James.McGregor2@arm.com",
|
||||
"Adam Gemmell <Adam.Gemmell@arm.com",
|
||||
"Jacob Bramley <jacob.bramley@arm.com>"]
|
||||
"Jacob Bramley <jacob.bramley@arm.com>",
|
||||
"James Barford-Evans <james.barford-evans@arm.com>"
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
|
||||
|
|
|
|||
|
|
@ -209,13 +209,13 @@ 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, p64_armv7_workaround: bool) -> String {
|
||||
pub fn load_values_c(&self, indentation: Indentation, target: &str) -> 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() {
|
||||
p64_armv7_workaround
|
||||
target.contains("v7")
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
|
@ -226,7 +226,7 @@ impl ArgumentList {
|
|||
ty = arg.to_c_type(),
|
||||
name = arg.name,
|
||||
load = if arg.is_simd() {
|
||||
arg.ty.get_load_function(p64_armv7_workaround)
|
||||
arg.ty.get_load_function(target)
|
||||
} else {
|
||||
"*".to_string()
|
||||
},
|
||||
|
|
@ -258,7 +258,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("__")
|
||||
} else {
|
||||
"*".to_string()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ impl Intrinsic {
|
|||
indentation: Indentation,
|
||||
additional: &str,
|
||||
passes: u32,
|
||||
p64_armv7_workaround: bool,
|
||||
target: &str,
|
||||
) -> String {
|
||||
let body_indentation = indentation.nested();
|
||||
format!(
|
||||
|
|
@ -100,9 +100,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, p64_armv7_workaround),
|
||||
loaded_args = self.arguments.load_values_c(body_indentation, target),
|
||||
intrinsic_call = self.name,
|
||||
args = self.arguments.as_call_param_c(),
|
||||
print_result = self.print_result_c(body_indentation, additional)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ fn gen_code_c(
|
|||
intrinsic: &Intrinsic,
|
||||
constraints: &[&Argument],
|
||||
name: String,
|
||||
p64_armv7_workaround: bool,
|
||||
target: &str,
|
||||
) -> String {
|
||||
if let Some((current, constraints)) = constraints.split_last() {
|
||||
let range = current
|
||||
|
|
@ -62,13 +62,13 @@ fn gen_code_c(
|
|||
intrinsic,
|
||||
constraints,
|
||||
format!("{name}-{i}"),
|
||||
p64_armv7_workaround
|
||||
target,
|
||||
)
|
||||
)
|
||||
})
|
||||
.join("\n")
|
||||
} else {
|
||||
intrinsic.generate_loop_c(indentation, &name, PASSES, p64_armv7_workaround)
|
||||
intrinsic.generate_loop_c(indentation, &name, PASSES, target)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ fn generate_c_program(
|
|||
notices: &str,
|
||||
header_files: &[&str],
|
||||
intrinsic: &Intrinsic,
|
||||
p64_armv7_workaround: bool,
|
||||
target: &str,
|
||||
) -> String {
|
||||
let constraints = intrinsic
|
||||
.arguments
|
||||
|
|
@ -131,7 +131,7 @@ int main(int argc, char **argv) {{
|
|||
intrinsic,
|
||||
constraints.as_slice(),
|
||||
Default::default(),
|
||||
p64_armv7_workaround
|
||||
target,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ fn gen_code_rust(
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, a32: bool) -> String {
|
||||
fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, target: &str) -> String {
|
||||
let constraints = intrinsic
|
||||
.arguments
|
||||
.iter()
|
||||
|
|
@ -201,7 +201,11 @@ fn main() {{
|
|||
{passes}
|
||||
}}
|
||||
"#,
|
||||
target_arch = if a32 { "arm" } else { "aarch64" },
|
||||
target_arch = if target.starts_with("aarch64") {
|
||||
"aarch64"
|
||||
} else {
|
||||
"arm"
|
||||
},
|
||||
arglists = intrinsic
|
||||
.arguments
|
||||
.gen_arglists_rust(indentation.nested(), PASSES),
|
||||
|
|
@ -214,22 +218,68 @@ fn main() {{
|
|||
)
|
||||
}
|
||||
|
||||
fn compile_c(c_filename: &str, intrinsic: &Intrinsic, compiler: &str, a32: bool) -> bool {
|
||||
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.starts_with("aarch64") {
|
||||
"-march=armv8.6-a+crypto+sha3+crc+dotprod"
|
||||
} else {
|
||||
"-march=armv8.6-a+crypto+crc+dotprod"
|
||||
};
|
||||
|
||||
let output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(format!(
|
||||
// -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations
|
||||
"{cpp} {cppflags} {arch_flags} -ffp-contract=off -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();
|
||||
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 <dest>`");
|
||||
};
|
||||
|
||||
/* 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 <stdlib.h>`
|
||||
* 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
|
||||
|
|
@ -258,7 +308,13 @@ fn build_notices(line_prefix: &str) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
fn build_c(notices: &str, intrinsics: &Vec<Intrinsic>, compiler: Option<&str>, a32: bool) -> bool {
|
||||
fn build_c(
|
||||
notices: &str,
|
||||
intrinsics: &Vec<Intrinsic>,
|
||||
compiler: Option<&str>,
|
||||
target: &str,
|
||||
cxx_toolchain_dir: Option<&str>,
|
||||
) -> bool {
|
||||
let _ = std::fs::create_dir("c_programs");
|
||||
intrinsics
|
||||
.par_iter()
|
||||
|
|
@ -266,25 +322,31 @@ fn build_c(notices: &str, intrinsics: &Vec<Intrinsic>, compiler: Option<&str>, a
|
|||
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"], i, a32);
|
||||
let c_code = generate_c_program(notices, &["arm_neon.h", "arm_acle.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, a32),
|
||||
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>, a32: bool) -> bool {
|
||||
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, a32);
|
||||
let c_code = generate_rust_program(notices, i, target);
|
||||
file.write_all(c_code.into_bytes().as_slice()).unwrap();
|
||||
});
|
||||
|
||||
|
|
@ -330,26 +392,33 @@ path = "{intrinsic}/main.rs""#,
|
|||
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 mut rust_flags = "-Cdebuginfo=0".to_string();
|
||||
if let Some(linker) = linker {
|
||||
rust_flags.push_str(" -Clinker=");
|
||||
rust_flags.push_str(linker);
|
||||
rust_flags.push_str(" -Clink-args=-static");
|
||||
}
|
||||
|
||||
let cargo_command = format!(
|
||||
"cargo {toolchain} build --target {target} --release",
|
||||
toolchain = toolchain,
|
||||
target = target
|
||||
);
|
||||
|
||||
let output = Command::new("sh")
|
||||
.current_dir("rust_programs")
|
||||
.arg("-c")
|
||||
.arg(format!(
|
||||
"cargo {toolchain} build --target {target} --release",
|
||||
toolchain = toolchain,
|
||||
target = if a32 {
|
||||
"armv7-unknown-linux-gnueabihf"
|
||||
} else {
|
||||
"aarch64-unknown-linux-gnu"
|
||||
},
|
||||
))
|
||||
.env("RUSTFLAGS", "-Cdebuginfo=0")
|
||||
.arg(cargo_command)
|
||||
.env("RUSTFLAGS", rust_flags)
|
||||
.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{}",
|
||||
"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("")
|
||||
);
|
||||
|
|
@ -387,13 +456,21 @@ struct Cli {
|
|||
#[arg(long)]
|
||||
skip: Option<PathBuf>,
|
||||
|
||||
/// Run tests for A32 instrinsics instead of A64
|
||||
#[arg(long)]
|
||||
a32: bool,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Set the sysroot for the C++ compiler
|
||||
#[arg(long)]
|
||||
cxx_toolchain_dir: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -403,6 +480,10 @@ fn main() {
|
|||
|
||||
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()
|
||||
|
|
@ -413,7 +494,7 @@ fn main() {
|
|||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let a32 = args.a32;
|
||||
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));
|
||||
|
|
@ -450,16 +531,22 @@ fn main() {
|
|||
|
||||
let notices = build_notices("// ");
|
||||
|
||||
if !build_c(¬ices, &intrinsics, cpp_compiler.as_deref(), a32) {
|
||||
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(), a32) {
|
||||
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, a32) {
|
||||
if let Some(ref _toolchain) = toolchain {
|
||||
if !compare_outputs(&intrinsics, &c_runner, target) {
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -471,7 +558,7 @@ enum FailureReason {
|
|||
Difference(String, String, String),
|
||||
}
|
||||
|
||||
fn compare_outputs(intrinsics: &Vec<Intrinsic>, toolchain: &str, runner: &str, a32: bool) -> bool {
|
||||
fn compare_outputs(intrinsics: &Vec<Intrinsic>, runner: &str, target: &str) -> bool {
|
||||
let intrinsics = intrinsics
|
||||
.par_iter()
|
||||
.filter_map(|intrinsic| {
|
||||
|
|
@ -483,20 +570,15 @@ fn compare_outputs(intrinsics: &Vec<Intrinsic>, toolchain: &str, runner: &str, a
|
|||
intrinsic = intrinsic.name,
|
||||
))
|
||||
.output();
|
||||
|
||||
let rust = Command::new("sh")
|
||||
.current_dir("rust_programs")
|
||||
.arg("-c")
|
||||
.arg(format!(
|
||||
"cargo {toolchain} run --target {target} --bin {intrinsic} --release",
|
||||
"{runner} ./rust_programs/target/{target}/release/{intrinsic}",
|
||||
runner = runner,
|
||||
target = target,
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -375,9 +375,9 @@ impl IntrinsicType {
|
|||
}
|
||||
|
||||
/// Determines the load function for this type.
|
||||
pub fn get_load_function(&self, armv7_p64_workaround: bool) -> String {
|
||||
pub fn get_load_function(&self, target: &str) -> String {
|
||||
match self {
|
||||
IntrinsicType::Ptr { child, .. } => child.get_load_function(armv7_p64_workaround),
|
||||
IntrinsicType::Ptr { child, .. } => child.get_load_function(target),
|
||||
IntrinsicType::Type {
|
||||
kind: k,
|
||||
bit_len: Some(bl),
|
||||
|
|
@ -397,7 +397,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 target.starts_with("armv7") && *bl == 64 {"s"} else {"p"},
|
||||
x => todo!("get_load_function TypeKind: {:#?}", x),
|
||||
},
|
||||
size = bl,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue