diff --git a/Cargo.lock b/Cargo.lock index 9df0791b86..d3f2509cd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ dependencies = [ "log", "pretty_env_logger", "rayon", + "regex", "serde", "serde_json", ] diff --git a/crates/intrinsic-test/Cargo.toml b/crates/intrinsic-test/Cargo.toml index fbbf90e140..b59e216678 100644 --- a/crates/intrinsic-test/Cargo.toml +++ b/crates/intrinsic-test/Cargo.toml @@ -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" diff --git a/crates/intrinsic-test/src/loongarch/compile.rs b/crates/intrinsic-test/src/loongarch/compile.rs new file mode 100644 index 0000000000..f8a2cb0149 --- /dev/null +++ b/crates/intrinsic-test/src/loongarch/compile.rs @@ -0,0 +1,29 @@ +use crate::common::cli::ProcessedCli; +use crate::common::compile_c::{CompilationCommandBuilder, CppCompilation}; + +pub fn build_cpp_compilation(config: &ProcessedCli) -> Option { + 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) +} diff --git a/crates/intrinsic-test/src/loongarch/config.rs b/crates/intrinsic-test/src/loongarch/config.rs new file mode 100644 index 0000000000..5c22a64802 --- /dev/null +++ b/crates/intrinsic-test/src/loongarch/config.rs @@ -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( + 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); + +impl core::fmt::Debug for Hex { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(&self.0, f) + } +} + +fn debug_f16(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()) + } +} + "#; diff --git a/crates/intrinsic-test/src/loongarch/intrinsic.rs b/crates/intrinsic-test/src/loongarch/intrinsic.rs new file mode 100644 index 0000000000..ddd96250d7 --- /dev/null +++ b/crates/intrinsic-test/src/loongarch/intrinsic.rs @@ -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 for Intrinsic { + fn arguments(&self) -> ArgumentList { + 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 is not defined!" + ) + } +} diff --git a/crates/intrinsic-test/src/loongarch/mod.rs b/crates/intrinsic-test/src/loongarch/mod.rs new file mode 100644 index 0000000000..67409033c1 --- /dev/null +++ b/crates/intrinsic-test/src/loongarch/mod.rs @@ -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>, + cli_options: ProcessedCli, +} + +impl SupportedArchitectureTest for LoongArchArchitectureTest { + fn create(cli_options: ProcessedCli) -> Box { + 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::>(); + 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::>() + .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::>() + .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::>(); + + compare_outputs( + &intrinsics_name_list, + &self.cli_options.runner, + &self.cli_options.target, + ) + } else { + true + } + } +} diff --git a/crates/intrinsic-test/src/loongarch/parser.rs b/crates/intrinsic-test/src/loongarch/parser.rs new file mode 100644 index 0000000000..d21d924d9e --- /dev/null +++ b/crates/intrinsic-test/src/loongarch/parser.rs @@ -0,0 +1,142 @@ +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +use crate::common::argument::{Argument, ArgumentList}; +use crate::common::intrinsic::Intrinsic; +use crate::loongarch::intrinsic::LoongArchIntrinsicType; + +pub fn get_loongson_intrinsics( + path: &Path, + target: &str, +) -> Result>, Box> { + let f = File::open(path).unwrap_or_else(|_| panic!("Failed to open {}", path.display())); + let f = BufReader::new(f); + + let mut current_name: Option = None; + let mut asm_fmts: Vec = Vec::new(); + + let mut intrinsics: Vec> = Vec::new(); + for line in f.lines() { + let line = line.unwrap(); + if line.is_empty() { + continue; + } + if let Some(name) = line.strip_prefix("name = ") { + current_name = Some(String::from(name)); + } else if line.starts_with("asm-fmts = ") { + asm_fmts = line[10..] + .split(',') + .map(|v| v.trim().to_string()) + .collect(); + } else if line.starts_with("data-types = ") { + let current_name = current_name.clone().unwrap(); + let mut data_types: Vec = line + .get(12..) + .unwrap() + .split(',') + .map(|e| e.trim().to_string()) + .collect(); + let arguments; + let return_type; + let data_types_len = data_types.len(); + if data_types_len > 0 && data_types_len < 6 { + arguments = data_types.split_off(1); + + // Being explicit here with the variable name + return_type = data_types.get(0).unwrap(); + } else { + panic!("DEBUG: line: {0} len: {1}", line, data_types.len()); + } + + let intrinsic = gen_intrinsic( + current_name.as_str(), + asm_fmts.clone(), + arguments, + return_type, + target, + ); + if intrinsic.is_ok() { + intrinsics.push(intrinsic.unwrap()); + } + } + } + return Ok(intrinsics); +} + +fn gen_intrinsic( + current_name: &str, + asm_fmts: Vec, + args: Vec, + return_type: &String, + target: &str, +) -> Result, Box> { + let para_num = args.len(); + let mut arguments = asm_fmts + .iter() + .zip(args.iter()) + .enumerate() + .map(|(i, (asm_fmt, arg_type))| { + let ty = LoongArchIntrinsicType::from_values(asm_fmt, arg_type).unwrap(); + let arg = + Argument::::new(i, format!("_{i}_{}", arg_type), ty, None); + return arg; + }) + .collect::>>(); + + if para_num == 1 && args[0] == "HI" { + match asm_fmts[1].as_str() { + "si13" | "i13" => arguments[0].ty.constant = true, + "si10" => arguments[0].ty.constant = true, + _ => panic!("unsupported assembly format: {:?}", asm_fmts), + }; + } else if para_num == 2 && (args[1] == "UQI" || args[1] == "USI") { + if asm_fmts[2].starts_with("ui") { + arguments[1].ty.constant = true; + } else { + panic!("unsupported assembly format: {:?}", asm_fmts); + }; + } else if para_num == 2 && args[1] == "QI" { + if asm_fmts[2].starts_with("si") { + arguments[1].ty.constant = true; + } else { + panic!("unsupported assembly format: {:?}", asm_fmts); + }; + } else if para_num == 2 && args[0] == "CVPOINTER" && args[1] == "SI" { + if asm_fmts[2].starts_with("si") { + arguments[1].ty.constant = true; + } else { + panic!("unsupported assembly format: {:?}", asm_fmts); + }; + } else if para_num == 3 && (args[2] == "USI" || args[2] == "UQI") { + if asm_fmts[2].starts_with("ui") { + arguments[2].ty.constant = true; + } else { + panic!("unsupported assembly format: {:?}", asm_fmts); + }; + } else if para_num == 3 && args[1] == "CVPOINTER" && args[2] == "SI" { + match asm_fmts[2].as_str() { + "si12" => arguments[2].ty.constant = true, + _ => panic!("unsupported assembly format: {:?}", asm_fmts), + }; + } else if para_num == 4 { + match (asm_fmts[3].as_str(), current_name.chars().last().unwrap()) { + ("si8", t) => { + arguments[2].ty.constant = true; + arguments[3].ty.constant = true; + } + (_, _) => panic!( + "unsupported assembly format: {:?} for {}", + asm_fmts, current_name + ), + }; + } + let results = LoongArchIntrinsicType::from_values(return_type, &asm_fmts[0])?; + let arguments = ArgumentList:: { args: arguments }; + Ok(Intrinsic { + name: current_name.to_string(), + arguments, + results: results, + arch_tags: vec![target.to_string()], + }) +} diff --git a/crates/intrinsic-test/src/loongarch/types.rs b/crates/intrinsic-test/src/loongarch/types.rs new file mode 100644 index 0000000000..341759059f --- /dev/null +++ b/crates/intrinsic-test/src/loongarch/types.rs @@ -0,0 +1,75 @@ +use super::intrinsic::LoongArchIntrinsicType; +use crate::common::cli::Language; +use crate::common::intrinsic_helpers::Sign; +use crate::common::intrinsic_helpers::{IntrinsicType, IntrinsicTypeDefinition, TypeKind}; + +impl IntrinsicTypeDefinition for LoongArchIntrinsicType { + /// Gets a string containing the type in C format. + /// This function assumes that this value is present in the metadata hashmap. + fn c_type(&self) -> String { + unimplemented!("c_type for LoongArchIntrinsicType is not implemented!") + } + + fn c_single_vector_type(&self) -> String { + unimplemented!("c_single_vector_type for LoongArchIntrinsicType is not implemented!") + } + + /// Determines the load function for this type. + fn get_load_function(&self, _language: Language) -> String { + unimplemented!("get_load_function for LoongArchIntrinsicType is not implemented!") + } + + /// Determines the get lane function for this type. + fn get_lane_function(&self) -> String { + todo!("get_lane_function for LoongArchIntrinsicType needs to be implemented!"); + } +} + +impl LoongArchIntrinsicType { + /// Accepts X, Y and Z. + /// Returns a `LoongArchType` + pub fn from_values(asm_fmt: &String, data_type: &String) -> Result { + let (bit_len, vec_len, type_kind) = match data_type.as_str() { + "A16QI" => (Some(8), Some(16), TypeKind::Int(Sign::Signed)), + "AM16QI" => (Some(8), Some(16), TypeKind::Int(Sign::Signed)), + "V16QI" => (Some(8), Some(16), TypeKind::Int(Sign::Signed)), + "V32QI" => (Some(8), Some(32), TypeKind::Int(Sign::Signed)), + "A32QI" => (Some(8), Some(32), TypeKind::Int(Sign::Signed)), + "AM32QI" => (Some(8), Some(32), TypeKind::Int(Sign::Signed)), + "V8HI" => (Some(16), Some(8), TypeKind::Int(Sign::Signed)), + "V16HI" => (Some(16), Some(16), TypeKind::Int(Sign::Signed)), + "V4SI" => (Some(32), Some(4), TypeKind::Int(Sign::Signed)), + "V8SI" => (Some(32), Some(8), TypeKind::Int(Sign::Signed)), + "V2DI" => (Some(64), Some(2), TypeKind::Int(Sign::Signed)), + "V4DI" => (Some(64), Some(4), TypeKind::Int(Sign::Signed)), + "UV16QI" => (Some(8), Some(16), TypeKind::Int(Sign::Unsigned)), + "UV32QI" => (Some(8), Some(32), TypeKind::Int(Sign::Unsigned)), + "UV8HI" => (Some(16), Some(8), TypeKind::Int(Sign::Unsigned)), + "UV16HI" => (Some(16), Some(16), TypeKind::Int(Sign::Unsigned)), + "UV4SI" => (Some(32), Some(4), TypeKind::Int(Sign::Unsigned)), + "UV8SI" => (Some(32), Some(8), TypeKind::Int(Sign::Unsigned)), + "UV2DI" => (Some(64), Some(2), TypeKind::Int(Sign::Unsigned)), + "UV4DI" => (Some(64), Some(4), TypeKind::Int(Sign::Unsigned)), + "V4SF" => (Some(32), Some(4), TypeKind::Float), + "V8SF" => (Some(32), Some(8), TypeKind::Float), + "V2DF" => (Some(64), Some(2), TypeKind::Float), + "V4DF" => (Some(64), Some(4), TypeKind::Float), + "SI" | "DI" | "USI" | "UDI" | "UQI" | "QI" | "CVPOINTER" | "HI" => { + (None, None, TypeKind::Int(Sign::Signed)) + } + _ => panic!("unknown type {data_type} with ASM {asm_fmt}"), + }; + + Ok(LoongArchIntrinsicType { + data: IntrinsicType { + constant: false, + ptr_constant: false, + ptr: false, + kind: TypeKind::Mask, + bit_len, + vec_len, + simd_len: None, + }, + }) + } +} diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 538f317a29..7faf4a6a1f 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -3,8 +3,11 @@ extern crate log; mod arm; mod common; +mod loongarch; use arm::ArmArchitectureTest; +use loongarch::LoongArchArchitectureTest; + use common::SupportedArchitectureTest; use common::cli::{Cli, ProcessedCli}; @@ -21,6 +24,9 @@ fn main() { Some(ArmArchitectureTest::create(processed_cli_options)) } + "loongarch64-unknown-linux-gnu" => { + Some(LoongArchArchitectureTest::create(processed_cli_options)) + } _ => None, };