Update test runner to support big endian

This commit is contained in:
James Barford-Evans 2025-01-31 11:16:48 +00:00 committed by Amanieu d'Antras
parent d12027810c
commit 7294081fda
5 changed files with 150 additions and 68 deletions

View file

@ -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"

View file

@ -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()
},

View file

@ -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)

View file

@ -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(&notices, &intrinsics, cpp_compiler.as_deref(), a32) {
if !build_c(
&notices,
&intrinsics,
cpp_compiler.as_deref(),
target,
cxx_toolchain_dir.as_deref(),
) {
std::process::exit(2);
}
if !build_rust(&notices, &intrinsics, toolchain.as_deref(), a32) {
if !build_rust(&notices, &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) {

View file

@ -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,