Skip to content

intrinsic-test: streamline c compilation #1861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 35 additions & 48 deletions crates/intrinsic-test/src/arm/compile.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,51 @@
use crate::common::compile_c::CompilationCommandBuilder;
use crate::common::gen_c::compile_c_programs;
use crate::common::cli::ProcessedCli;
use crate::common::compile_c::{CompilationCommandBuilder, CppCompilation};

pub fn build_cpp_compilation(config: &ProcessedCli) -> Option<CppCompilation> {
let cpp_compiler = config.cpp_compiler.as_ref()?;

pub fn compile_c_arm(
intrinsics_name_list: &[String],
compiler: &str,
target: &str,
cxx_toolchain_dir: Option<&str>,
) -> bool {
// -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations
let mut command = CompilationCommandBuilder::new()
.add_arch_flags(vec!["armv8.6-a", "crypto", "crc", "dotprod", "fp16"])
.set_compiler(compiler)
.set_target(target)
.set_compiler(cpp_compiler)
.set_target(&config.target)
.set_opt_level("2")
.set_cxx_toolchain_dir(cxx_toolchain_dir)
.set_cxx_toolchain_dir(config.cxx_toolchain_dir.as_deref())
.set_project_root("c_programs")
.add_extra_flags(vec!["-ffp-contract=off", "-Wno-narrowing"]);

if !target.contains("v7") {
if !config.target.contains("v7") {
command = command.add_arch_flags(vec!["faminmax", "lut", "sha3"]);
}

/*
* 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...
*/
if target.contains("aarch64_be") {
command = command
.set_linker(
cxx_toolchain_dir.unwrap_or("").to_string() + "/bin/aarch64_be-none-linux-gnu-g++",
)
.set_include_paths(vec![
"/include",
"/aarch64_be-none-linux-gnu/include",
"/aarch64_be-none-linux-gnu/include/c++/14.3.1",
"/aarch64_be-none-linux-gnu/include/c++/14.3.1/aarch64_be-none-linux-gnu",
"/aarch64_be-none-linux-gnu/include/c++/14.3.1/backward",
"/aarch64_be-none-linux-gnu/libc/usr/include",
]);
}

if !compiler.contains("clang") {
if !cpp_compiler.contains("clang") {
command = command.add_extra_flag("-flax-vector-conversions");
}

let compiler_commands = intrinsics_name_list
.iter()
.map(|intrinsic_name| {
command
.clone()
.set_input_name(intrinsic_name)
.set_output_name(intrinsic_name)
.make_string()
})
.collect::<Vec<_>>();
let mut cpp_compiler = command.into_cpp_compilation();

if config.target.contains("aarch64_be") {
let Some(ref cxx_toolchain_dir) = config.cxx_toolchain_dir else {
panic!(
"target `{}` must specify `cxx_toolchain_dir`",
config.target
)
};

cpp_compiler.command_mut().args([
&format!("--sysroot={cxx_toolchain_dir}/aarch64_be-none-linux-gnu/libc"),
"--include-directory",
&format!("{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.3.1"),
"--include-directory",
&format!("{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.3.1/aarch64_be-none-linux-gnu"),
"-L",
&format!("{cxx_toolchain_dir}/lib/gcc/aarch64_be-none-linux-gnu/14.3.1"),
"-L",
&format!("{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/libc/usr/lib"),
"-B",
&format!("{cxx_toolchain_dir}/lib/gcc/aarch64_be-none-linux-gnu/14.3.1"),
]);
}

compile_c_programs(&compiler_commands)
Some(cpp_compiler)
}
15 changes: 3 additions & 12 deletions crates/intrinsic-test/src/arm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ mod types;
use crate::common::SupportedArchitectureTest;
use crate::common::cli::ProcessedCli;
use crate::common::compare::compare_outputs;
use crate::common::gen_c::compile_c_programs;
use crate::common::gen_rust::compile_rust_programs;
use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition};
use crate::common::intrinsic_helpers::TypeKind;
use crate::common::write_file::{write_c_testfiles, write_rust_testfiles};
use compile::compile_c_arm;
use config::{AARCH_CONFIGURATIONS, F16_FORMATTING_DEF, POLY128_OSTREAM_DEF, build_notices};
use intrinsic::ArmIntrinsicType;
use json_parser::get_neon_intrinsics;
Expand Down Expand Up @@ -51,9 +51,7 @@ impl SupportedArchitectureTest for ArmArchitectureTest {
}

fn build_c_file(&self) -> bool {
let compiler = self.cli_options.cpp_compiler.as_deref();
let target = &self.cli_options.target;
let cxx_toolchain_dir = self.cli_options.cxx_toolchain_dir.as_deref();
let c_target = "aarch64";

let intrinsics_name_list = write_c_testfiles(
Expand All @@ -69,15 +67,8 @@ impl SupportedArchitectureTest for ArmArchitectureTest {
&[POLY128_OSTREAM_DEF],
);

match compiler {
None => true,
Some(compiler) => compile_c_arm(
intrinsics_name_list.as_slice(),
compiler,
target,
cxx_toolchain_dir,
),
}
let pipeline = compile::build_cpp_compilation(&self.cli_options).unwrap();
compile_c_programs(&pipeline, &intrinsics_name_list)
}

fn build_rust_file(&self) -> bool {
Expand Down
124 changes: 46 additions & 78 deletions crates/intrinsic-test/src/common/compile_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ pub struct CompilationCommandBuilder {
cxx_toolchain_dir: Option<String>,
arch_flags: Vec<String>,
optimization: String,
include_paths: Vec<String>,
project_root: Option<String>,
output: String,
input: String,
linker: Option<String>,
extra_flags: Vec<String>,
}

Expand All @@ -21,11 +17,7 @@ impl CompilationCommandBuilder {
cxx_toolchain_dir: None,
arch_flags: Vec::new(),
optimization: "2".to_string(),
include_paths: Vec::new(),
project_root: None,
output: String::new(),
input: String::new(),
linker: None,
extra_flags: Vec::new(),
}
}
Expand Down Expand Up @@ -57,37 +49,12 @@ impl CompilationCommandBuilder {
self
}

/// Sets a list of include paths for compilation.
/// The paths that are passed must be relative to the
/// "cxx_toolchain_dir" directory path.
pub fn set_include_paths(mut self, paths: Vec<&str>) -> Self {
self.include_paths = paths.into_iter().map(|path| path.to_string()).collect();
self
}

/// Sets the root path of all the generated test files.
pub fn set_project_root(mut self, path: &str) -> Self {
self.project_root = Some(path.to_string());
self
}

/// The name of the output executable, without any suffixes
pub fn set_output_name(mut self, path: &str) -> Self {
self.output = path.to_string();
self
}

/// The name of the input C file, without any suffixes
pub fn set_input_name(mut self, path: &str) -> Self {
self.input = path.to_string();
self
}

pub fn set_linker(mut self, linker: String) -> Self {
self.linker = Some(linker);
self
}

pub fn add_extra_flags(mut self, flags: Vec<&str>) -> Self {
let mut flags: Vec<String> = flags.into_iter().map(|f| f.to_string()).collect();
self.extra_flags.append(&mut flags);
Expand All @@ -100,55 +67,56 @@ impl CompilationCommandBuilder {
}

impl CompilationCommandBuilder {
pub fn make_string(self) -> String {
let arch_flags = self.arch_flags.join("+");
pub fn into_cpp_compilation(self) -> CppCompilation {
let mut cpp_compiler = std::process::Command::new(self.compiler);

if let Some(project_root) = self.project_root {
cpp_compiler.current_dir(project_root);
}

let flags = std::env::var("CPPFLAGS").unwrap_or("".into());
let project_root = self.project_root.unwrap_or_default();
let project_root_str = project_root.as_str();
let mut output = self.output.clone();
if self.linker.is_some() {
output += ".o"
};
let mut command = format!(
"{} {flags} -march={arch_flags} \
-O{} \
-o {project_root}/{} \
{project_root}/{}.cpp",
self.compiler, self.optimization, output, self.input,
);

command = command + " " + self.extra_flags.join(" ").as_str();
cpp_compiler.args(flags.split_whitespace());

cpp_compiler.arg(format!("-march={}", self.arch_flags.join("+")));

cpp_compiler.arg(format!("-O{}", self.optimization));

cpp_compiler.args(self.extra_flags);

if let Some(target) = &self.target {
command = command + " --target=" + target;
cpp_compiler.arg(format!("--target={target}"));
}

if let (Some(linker), Some(cxx_toolchain_dir)) = (&self.linker, &self.cxx_toolchain_dir) {
let include_args = self
.include_paths
.iter()
.map(|path| "--include-directory=".to_string() + cxx_toolchain_dir + path)
.collect::<Vec<_>>()
.join(" ");

command = command
+ " -c "
+ include_args.as_str()
+ " && "
+ linker
+ " "
+ project_root_str
+ "/"
+ &output
+ " -o "
+ project_root_str
+ "/"
+ &self.output
+ " && rm "
+ project_root_str
+ "/"
+ &output;
}
command
CppCompilation(cpp_compiler)
}
}

pub struct CppCompilation(std::process::Command);

fn clone_command(command: &std::process::Command) -> std::process::Command {
let mut cmd = std::process::Command::new(command.get_program());
if let Some(current_dir) = command.get_current_dir() {
cmd.current_dir(current_dir);
}
cmd.args(command.get_args());

for (key, val) in command.get_envs() {
cmd.env(key, val.unwrap_or_default());
}

cmd
}

impl CppCompilation {
pub fn command_mut(&mut self) -> &mut std::process::Command {
&mut self.0
}

pub fn run(&self, inputs: &[String], output: &str) -> std::io::Result<std::process::Output> {
let mut cmd = clone_command(&self.0);
cmd.args(inputs);
cmd.args(["-o", output]);

cmd.output()
}
}
40 changes: 21 additions & 19 deletions crates/intrinsic-test/src/common/gen_c.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use itertools::Itertools;
use rayon::prelude::*;
use std::collections::BTreeMap;
use std::process::Command;

use crate::common::compile_c::CppCompilation;

use super::argument::Argument;
use super::indentation::Indentation;
Expand Down Expand Up @@ -62,29 +63,30 @@ int main(int argc, char **argv) {{
)
}

pub fn compile_c_programs(compiler_commands: &[String]) -> bool {
compiler_commands
pub fn compile_c_programs(pipeline: &CppCompilation, intrinsics: &[String]) -> bool {
intrinsics
.par_iter()
.map(|compiler_command| {
let output = Command::new("sh").arg("-c").arg(compiler_command).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{}",
.map(
|intrinsic| match pipeline.run(&[format!("{intrinsic}.cpp")], intrinsic) {
Ok(output) if output.status.success() => Ok(()),
Ok(output) => {
let msg = format!(
"Failed to compile code for intrinsic `{intrinsic}`: \n\nstdout:\n{}\n\nstderr:\n{}",
std::str::from_utf8(&output.stdout).unwrap_or(""),
std::str::from_utf8(&output.stderr).unwrap_or("")
);
false
error!("{msg}");

Err(msg)
}
} else {
error!("Command failed: {output:#?}");
false
}
})
.find_any(|x| !x)
.is_none()
Err(e) => {
error!("command for `{intrinsic}` failed with IO error: {e:?}");
Err(e.to_string())
}
},
)
.collect::<Result<(), String>>()
.is_ok()
}

// Creates directory structure and file path mappings
Expand Down