Skip to content
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/intrinsic-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ pretty_env_logger = "0.5.0"
rayon = "1.5.0"
diff = "0.1.12"
itertools = "0.14.0"
regex = "1.11.1"
29 changes: 29 additions & 0 deletions crates/intrinsic-test/src/loongarch/compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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()?;

// -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations
let mut command = CompilationCommandBuilder::new()
.set_compiler(cpp_compiler)
.set_target(&config.target)
.set_opt_level("2")
.set_cxx_toolchain_dir(config.cxx_toolchain_dir.as_deref())
.set_project_root("c_programs")
.add_extra_flags(vec![
"-ffp-contract=off",
"-Wno-narrowing",
"-mlasx",
"-mlsx",
"-mfrecipe",
]);

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

let cpp_compiler = command.into_cpp_compilation();

Some(cpp_compiler)
}
54 changes: 54 additions & 0 deletions crates/intrinsic-test/src/loongarch/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
pub fn build_notices(line_prefix: &str) -> String {
format!(
"\
{line_prefix}This is a transient test file, not intended for distribution. Some aspects of the
{line_prefix}test are derived from `stdarch-gen-loongarch/lsx.spec` and `stdarch-gen-loongarch/lasx.spec`,
{line_prefix}published under the same license as the `intrinsic-test` crate.\n
"
)
}

pub const LOONGARCH_CONFIGURATIONS: &str = r#"
#![cfg_attr(any(target_arch = "loongarch64", target_arch = "loongarch32"), feature(stdarch_loongarch))]
"#;

// Format f16 values (and vectors containing them) in a way that is consistent with C.
pub const F16_FORMATTING_DEF: &str = r#"
/// Used to continue `Debug`ging SIMD types as `MySimd(1, 2, 3, 4)`, as they
/// were before moving to array-based simd.
#[inline]
fn debug_simd_finish<T: core::fmt::Debug, const N: usize>(
formatter: &mut core::fmt::Formatter<'_>,
type_name: &str,
array: &[T; N],
) -> core::fmt::Result {
core::fmt::Formatter::debug_tuple_fields_finish(
formatter,
type_name,
&core::array::from_fn::<&dyn core::fmt::Debug, N, _>(|i| &array[i]),
)
}

#[repr(transparent)]
struct Hex<T>(T);

impl<T: DebugHexF16> core::fmt::Debug for Hex<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
<T as DebugHexF16>::fmt(&self.0, f)
}
}

fn debug_f16<T: DebugHexF16>(x: T) -> impl core::fmt::Debug {
Hex(x)
}

trait DebugHexF16 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
}

impl DebugHexF16 for f16 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#06x?}", self.to_bits())
}
}
"#;
47 changes: 47 additions & 0 deletions crates/intrinsic-test/src/loongarch/intrinsic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::common::argument::ArgumentList;
use crate::common::indentation::Indentation;
use crate::common::intrinsic::{Intrinsic, IntrinsicDefinition};
use crate::common::intrinsic_helpers::{IntrinsicType, IntrinsicTypeDefinition, Sign, TypeKind};
use std::ops::{Deref, DerefMut};

#[derive(Debug, Clone, PartialEq)]
pub struct LoongArchIntrinsicType {
pub data: IntrinsicType,
}

impl Deref for LoongArchIntrinsicType {
type Target = IntrinsicType;

fn deref(&self) -> &Self::Target {
&self.data
}
}

impl DerefMut for LoongArchIntrinsicType {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}

impl IntrinsicDefinition<LoongArchIntrinsicType> for Intrinsic<LoongArchIntrinsicType> {
fn arguments(&self) -> ArgumentList<LoongArchIntrinsicType> {
self.arguments.clone()
}

fn results(&self) -> LoongArchIntrinsicType {
self.results.clone()
}

fn name(&self) -> String {
self.name.clone()
}

/// Generates a std::cout for the intrinsics results that will match the
/// rust debug output format for the return type. The generated line assumes
/// there is an int i in scope which is the current pass number.
fn print_result_c(&self, indentation: Indentation, additional: &str) -> String {
unimplemented!(
"print_result_c of IntrinsicDefinition<LoongArchIntrinsicType> is not defined!"
)
}
}
186 changes: 186 additions & 0 deletions crates/intrinsic-test/src/loongarch/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
mod compile;
mod config;
mod intrinsic;
mod parser;
mod types;

use std::fs::{self, File};

use rayon::prelude::*;

use crate::common::cli::ProcessedCli;
use crate::common::compare::compare_outputs;
use crate::common::gen_c::{write_main_cpp, write_mod_cpp};
use crate::common::gen_rust::{
compile_rust_programs, write_bin_cargo_toml, write_lib_cargo_toml, write_lib_rs, write_main_rs,
};
use crate::common::intrinsic_helpers::TypeKind;
use crate::common::{SupportedArchitectureTest, chunk_info};

use crate::common::intrinsic::Intrinsic;
use crate::loongarch::config::{F16_FORMATTING_DEF, LOONGARCH_CONFIGURATIONS, build_notices};
use crate::loongarch::parser::get_loongson_intrinsics;
use intrinsic::LoongArchIntrinsicType;

pub struct LoongArchArchitectureTest {
intrinsics: Vec<Intrinsic<LoongArchIntrinsicType>>,
cli_options: ProcessedCli,
}

impl SupportedArchitectureTest for LoongArchArchitectureTest {
fn create(cli_options: ProcessedCli) -> Box<Self> {
let mut intrinsics = get_loongson_intrinsics(&cli_options.filename, &cli_options.target)
.expect("Error parsing input file");

intrinsics.sort_by(|a, b| a.name.cmp(&b.name));

let mut intrinsics = intrinsics
.into_iter()
.filter(|i| i.results.kind() != TypeKind::Void)
.filter(|i| !i.arguments.iter().any(|a| a.is_ptr()))
.filter(|i| !cli_options.skip.contains(&i.name))
.collect::<Vec<_>>();
intrinsics.dedup();

Box::new(Self {
intrinsics,
cli_options,
})
}

fn build_c_file(&self) -> bool {
let c_target = "loongarch";
let platform_headers = &["lasxintrin.h", "lsxintrin.h"];

let (chunk_size, chunk_count) = chunk_info(self.intrinsics.len());

let cpp_compiler_wrapped = compile::build_cpp_compilation(&self.cli_options);

let notice = &build_notices("// ");
fs::create_dir_all("c_programs").unwrap();
self.intrinsics
.par_chunks(chunk_size)
.enumerate()
.map(|(i, chunk)| {
let c_filename = format!("c_programs/mod_{i}.cpp");
let mut file = File::create(&c_filename).unwrap();
write_mod_cpp(&mut file, notice, c_target, platform_headers, chunk).unwrap();

// compile this cpp file into a .o file.
//
// This is done because `cpp_compiler_wrapped` is None when
// the --generate-only flag is passed
if let Some(cpp_compiler) = cpp_compiler_wrapped.as_ref() {
let output = cpp_compiler
.compile_object_file(&format!("mod_{i}.cpp"), &format!("mod_{i}.o"))?;
assert!(output.status.success(), "{output:?}");
}

Ok(())
})
.collect::<Result<(), std::io::Error>>()
.unwrap();

let mut file = File::create("c_programs/main.cpp").unwrap();
write_main_cpp(
&mut file,
c_target,
"\n",
self.intrinsics.iter().map(|i| i.name.as_str()),
)
.unwrap();

// This is done because `cpp_compiler_wrapped` is None when
// the --generate-only flag is passed
if let Some(cpp_compiler) = cpp_compiler_wrapped.as_ref() {
// compile this cpp file into a .o file
info!("compiling main.cpp");
let output = cpp_compiler
.compile_object_file("main.cpp", "intrinsic-test-programs.o")
.unwrap();
assert!(output.status.success(), "{output:?}");

let object_files = (0..chunk_count)
.map(|i| format!("mod_{i}.o"))
.chain(["intrinsic-test-programs.o".to_owned()]);

let output = cpp_compiler
.link_executable(object_files, "intrinsic-test-programs")
.unwrap();
assert!(output.status.success(), "{output:?}");
}

true
}

fn build_rust_file(&self) -> bool {
std::fs::create_dir_all("rust_programs/src").unwrap();

let architecture = "loongarch64";

let (chunk_size, chunk_count) = chunk_info(self.intrinsics.len());

let mut cargo = File::create("rust_programs/Cargo.toml").unwrap();
write_bin_cargo_toml(&mut cargo, chunk_count).unwrap();

let mut main_rs = File::create("rust_programs/src/main.rs").unwrap();
write_main_rs(
&mut main_rs,
chunk_count,
LOONGARCH_CONFIGURATIONS,
"",
self.intrinsics.iter().map(|i| i.name.as_str()),
)
.unwrap();

let target = &self.cli_options.target;
let toolchain = self.cli_options.toolchain.as_deref();
let linker = self.cli_options.linker.as_deref();

let notice = &build_notices("// ");
self.intrinsics
.par_chunks(chunk_size)
.enumerate()
.map(|(i, chunk)| {
std::fs::create_dir_all(format!("rust_programs/mod_{i}/src"))?;

let rust_filename = format!("rust_programs/mod_{i}/src/lib.rs");
trace!("generating `{rust_filename}`");
let mut file = File::create(rust_filename)?;

let cfg = LOONGARCH_CONFIGURATIONS;
let definitions = F16_FORMATTING_DEF;
write_lib_rs(&mut file, architecture, notice, cfg, definitions, chunk)?;

let toml_filename = format!("rust_programs/mod_{i}/Cargo.toml");
trace!("generating `{toml_filename}`");
let mut file = File::create(toml_filename).unwrap();

write_lib_cargo_toml(&mut file, &format!("mod_{i}"))?;

Ok(())
})
.collect::<Result<(), std::io::Error>>()
.unwrap();

compile_rust_programs(toolchain, target, linker)
}

fn compare_outputs(&self) -> bool {
if self.cli_options.toolchain.is_some() {
let intrinsics_name_list = self
.intrinsics
.iter()
.map(|i| i.name.clone())
.collect::<Vec<_>>();

compare_outputs(
&intrinsics_name_list,
&self.cli_options.runner,
&self.cli_options.target,
)
} else {
true
}
}
}
Loading
Loading