//! Verification of MIPS MSA intrinsics #![allow(bad_style, unused)] // This file is obtained from // https://gcc.gnu.org/onlinedocs//gcc/MIPS-SIMD-Architecture-Built-in-Functions.html static HEADER: &str = include_str!("../mips-msa.h"); stdarch_verify::mips_functions!(static FUNCTIONS); struct Function { name: &'static str, arguments: &'static [&'static Type], ret: Option<&'static Type>, target_feature: Option<&'static str>, instrs: &'static [&'static str], file: &'static str, required_const: &'static [usize], has_test: bool, } static F16: Type = Type::PrimFloat(16); static F32: Type = Type::PrimFloat(32); static F64: Type = Type::PrimFloat(64); static I8: Type = Type::PrimSigned(8); static I16: Type = Type::PrimSigned(16); static I32: Type = Type::PrimSigned(32); static I64: Type = Type::PrimSigned(64); static U8: Type = Type::PrimUnsigned(8); static U16: Type = Type::PrimUnsigned(16); static U32: Type = Type::PrimUnsigned(32); static U64: Type = Type::PrimUnsigned(64); static NEVER: Type = Type::Never; static TUPLE: Type = Type::Tuple; static v16i8: Type = Type::I(8, 16, 1); static v8i16: Type = Type::I(16, 8, 1); static v4i32: Type = Type::I(32, 4, 1); static v2i64: Type = Type::I(64, 2, 1); static v16u8: Type = Type::U(8, 16, 1); static v8u16: Type = Type::U(16, 8, 1); static v4u32: Type = Type::U(32, 4, 1); static v2u64: Type = Type::U(64, 2, 1); static v8f16: Type = Type::F(16, 8, 1); static v4f32: Type = Type::F(32, 4, 1); static v2f64: Type = Type::F(64, 2, 1); #[derive(Debug, Copy, Clone, PartialEq)] enum Type { PrimFloat(u8), PrimSigned(u8), PrimUnsigned(u8), PrimPoly(u8), MutPtr(&'static Type), ConstPtr(&'static Type), Tuple, I(u8, u8, u8), U(u8, u8, u8), P(u8, u8, u8), F(u8, u8, u8), Never, } #[derive(Copy, Clone, Debug, PartialEq)] #[allow(non_camel_case_types)] enum MsaTy { v16i8, v8i16, v4i32, v2i64, v16u8, v8u16, v4u32, v2u64, v8f16, v4f32, v2f64, imm0_1, imm0_3, imm0_7, imm0_15, imm0_31, imm0_63, imm0_255, imm_n16_15, imm_n512_511, imm_n1024_1022, imm_n2048_2044, imm_n4096_4088, i32, u32, i64, u64, Void, MutVoidPtr, } impl<'a> From<&'a str> for MsaTy { fn from(s: &'a str) -> MsaTy { match s { "v16i8" => MsaTy::v16i8, "v8i16" => MsaTy::v8i16, "v4i32" => MsaTy::v4i32, "v2i64" => MsaTy::v2i64, "v16u8" => MsaTy::v16u8, "v8u16" => MsaTy::v8u16, "v4u32" => MsaTy::v4u32, "v2u64" => MsaTy::v2u64, "v8f16" => MsaTy::v8f16, "v4f32" => MsaTy::v4f32, "v2f64" => MsaTy::v2f64, "imm0_1" => MsaTy::imm0_1, "imm0_3" => MsaTy::imm0_3, "imm0_7" => MsaTy::imm0_7, "imm0_15" => MsaTy::imm0_15, "imm0_31" => MsaTy::imm0_31, "imm0_63" => MsaTy::imm0_63, "imm0_255" => MsaTy::imm0_255, "imm_n16_15" => MsaTy::imm_n16_15, "imm_n512_511" => MsaTy::imm_n512_511, "imm_n1024_1022" => MsaTy::imm_n1024_1022, "imm_n2048_2044" => MsaTy::imm_n2048_2044, "imm_n4096_4088" => MsaTy::imm_n4096_4088, "i32" => MsaTy::i32, "u32" => MsaTy::u32, "i64" => MsaTy::i64, "u64" => MsaTy::u64, "void" => MsaTy::Void, "void *" => MsaTy::MutVoidPtr, v => panic!("unknown ty: \"{v}\""), } } } #[derive(Debug, Clone)] struct MsaIntrinsic { id: String, arg_tys: Vec, ret_ty: MsaTy, instruction: String, } struct NoneError; impl std::convert::TryFrom<&'static str> for MsaIntrinsic { // The intrinsics are just C function declarations of the form: // $ret_ty __builtin_${fn_id}($($arg_ty),*); type Error = NoneError; fn try_from(line: &'static str) -> Result { return inner(line).ok_or(NoneError); fn inner(line: &'static str) -> Option { let first_whitespace = line.find(char::is_whitespace)?; let ret_ty = &line[0..first_whitespace]; let ret_ty = MsaTy::from(ret_ty); let first_parentheses = line.find('(')?; assert!(first_parentheses > first_whitespace); let id = &line[first_whitespace + 1..first_parentheses].trim(); assert!(id.starts_with("__builtin")); let mut id_str = "_".to_string(); id_str += &id[9..]; let id = id_str; let mut arg_tys = Vec::new(); let last_parentheses = line.find(')')?; for arg in (&line[first_parentheses + 1..last_parentheses]).split(',') { let arg = arg.trim(); arg_tys.push(MsaTy::from(arg)); } // The instruction is the intrinsic name without the __msa_ prefix. let instruction = &id[6..]; let mut instruction = instruction.to_string(); // With all underscores but the first one replaced with a `.` if let Some(first_underscore) = instruction.find('_') { let postfix = instruction[first_underscore + 1..].replace('_', "."); instruction = instruction[0..=first_underscore].to_string(); instruction += &postfix; } Some(MsaIntrinsic { id, ret_ty, arg_tys, instruction, }) } } } #[test] fn verify_all_signatures() { // Parse the C intrinsic header file: let mut intrinsics = std::collections::HashMap::::new(); for line in HEADER.lines() { if line.is_empty() { continue; } use std::convert::TryFrom; let intrinsic: MsaIntrinsic = TryFrom::try_from(line).unwrap_or_else(|_| panic!("failed to parse line: \"{line}\"")); assert!(!intrinsics.contains_key(&intrinsic.id)); intrinsics.insert(intrinsic.id.clone(), intrinsic); } let mut all_valid = true; for rust in FUNCTIONS { if !rust.has_test { let skip = [ "__msa_ceqi_d", "__msa_cfcmsa", "__msa_clei_s_d", "__msa_clti_s_d", "__msa_ctcmsa", "__msa_ldi_d", "__msa_maxi_s_d", "__msa_mini_s_d", "break_", ]; if !skip.contains(&rust.name) { println!( "missing run-time test named `test_{}` for `{}`", { let mut id = rust.name; while id.starts_with('_') { id = &id[1..]; } id }, rust.name ); all_valid = false; } } // Skip some intrinsics that aren't part of MSA match rust.name { "break_" => continue, _ => {} } let mips = match intrinsics.get(rust.name) { Some(i) => i, None => { eprintln!( "missing mips definition for {:?} in {}", rust.name, rust.file ); all_valid = false; continue; } }; if let Err(e) = matches(rust, mips) { println!("failed to verify `{}`", rust.name); println!(" * {e}"); all_valid = false; } } assert!(all_valid); } fn matches(rust: &Function, mips: &MsaIntrinsic) -> Result<(), String> { macro_rules! bail { ($($t:tt)*) => (return Err(format!($($t)*))) } if rust.ret.is_none() && mips.ret_ty != MsaTy::Void { bail!("mismatched return value") } if rust.arguments.len() != mips.arg_tys.len() { bail!("mismatched argument lengths"); } let mut nconst = 0; for (i, (rust_arg, mips_arg)) in rust.arguments.iter().zip(mips.arg_tys.iter()).enumerate() { match mips_arg { MsaTy::v16i8 if **rust_arg == v16i8 => (), MsaTy::v8i16 if **rust_arg == v8i16 => (), MsaTy::v4i32 if **rust_arg == v4i32 => (), MsaTy::v2i64 if **rust_arg == v2i64 => (), MsaTy::v16u8 if **rust_arg == v16u8 => (), MsaTy::v8u16 if **rust_arg == v8u16 => (), MsaTy::v4u32 if **rust_arg == v4u32 => (), MsaTy::v2u64 if **rust_arg == v2u64 => (), MsaTy::v4f32 if **rust_arg == v4f32 => (), MsaTy::v2f64 if **rust_arg == v2f64 => (), MsaTy::imm0_1 | MsaTy::imm0_3 | MsaTy::imm0_7 | MsaTy::imm0_15 | MsaTy::imm0_31 | MsaTy::imm0_63 | MsaTy::imm0_255 | MsaTy::imm_n16_15 | MsaTy::imm_n512_511 | MsaTy::imm_n1024_1022 | MsaTy::imm_n2048_2044 | MsaTy::imm_n4096_4088 if **rust_arg == I32 => {} MsaTy::i32 if **rust_arg == I32 => (), MsaTy::i64 if **rust_arg == I64 => (), MsaTy::u32 if **rust_arg == U32 => (), MsaTy::u64 if **rust_arg == U64 => (), MsaTy::MutVoidPtr if **rust_arg == Type::MutPtr(&U8) => (), m => bail!( "mismatched argument \"{}\"= \"{:?}\" != \"{:?}\"", i, m, *rust_arg ), } let is_const = matches!( mips_arg, MsaTy::imm0_1 | MsaTy::imm0_3 | MsaTy::imm0_7 | MsaTy::imm0_15 | MsaTy::imm0_31 | MsaTy::imm0_63 | MsaTy::imm0_255 | MsaTy::imm_n16_15 | MsaTy::imm_n512_511 | MsaTy::imm_n1024_1022 | MsaTy::imm_n2048_2044 | MsaTy::imm_n4096_4088 ); if is_const { nconst += 1; if !rust.required_const.contains(&i) { bail!("argument const mismatch"); } } } if nconst != rust.required_const.len() { bail!("wrong number of const arguments"); } if rust.target_feature != Some("msa") { bail!("wrong target_feature"); } if !rust.instrs.is_empty() { // Normalize slightly to get rid of assembler differences let actual = rust.instrs[0].replace(".", "_"); let expected = mips.instruction.replace(".", "_"); if actual != expected { bail!( "wrong instruction: \"{}\" != \"{}\"", rust.instrs[0], mips.instruction ); } } else { bail!( "missing assert_instr for \"{}\" (should be \"{}\")", mips.id, mips.instruction ); } Ok(()) }