| //! 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<MsaTy>, |
| 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<Self, Self::Error> { |
| return inner(line).ok_or(NoneError); |
| |
| fn inner(line: &'static str) -> Option<MsaIntrinsic> { |
| 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::<String, MsaIntrinsic>::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(()) |
| } |