use std::collections::HashMap; use serde::Deserialize; #[allow(unused)] 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 I16: Type = Type::PrimSigned(16); static I32: Type = Type::PrimSigned(32); static I64: Type = Type::PrimSigned(64); static I8: Type = Type::PrimSigned(8); static U16: Type = Type::PrimUnsigned(16); static U32: Type = Type::PrimUnsigned(32); static U64: Type = Type::PrimUnsigned(64); static U8: Type = Type::PrimUnsigned(8); static NEVER: Type = Type::Never; static F16X4: Type = Type::F(16, 4, 1); static F16X4X2: Type = Type::F(16, 4, 2); static F16X4X3: Type = Type::F(16, 4, 3); static F16X4X4: Type = Type::F(16, 4, 4); static F16X8: Type = Type::F(16, 8, 1); static F16X8X2: Type = Type::F(16, 8, 2); static F16X8X3: Type = Type::F(16, 8, 3); static F16X8X4: Type = Type::F(16, 8, 4); static F32X2: Type = Type::F(32, 2, 1); static F32X2X2: Type = Type::F(32, 2, 2); static F32X2X3: Type = Type::F(32, 2, 3); static F32X2X4: Type = Type::F(32, 2, 4); static F32X4: Type = Type::F(32, 4, 1); static F32X4X2: Type = Type::F(32, 4, 2); static F32X4X3: Type = Type::F(32, 4, 3); static F32X4X4: Type = Type::F(32, 4, 4); static F64X1: Type = Type::F(64, 1, 1); static F64X1X2: Type = Type::F(64, 1, 2); static F64X1X3: Type = Type::F(64, 1, 3); static F64X1X4: Type = Type::F(64, 1, 4); static F64X2: Type = Type::F(64, 2, 1); static F64X2X2: Type = Type::F(64, 2, 2); static F64X2X3: Type = Type::F(64, 2, 3); static F64X2X4: Type = Type::F(64, 2, 4); static I16X2: Type = Type::I(16, 2, 1); static I16X4: Type = Type::I(16, 4, 1); static I16X4X2: Type = Type::I(16, 4, 2); static I16X4X3: Type = Type::I(16, 4, 3); static I16X4X4: Type = Type::I(16, 4, 4); static I16X8: Type = Type::I(16, 8, 1); static I16X8X2: Type = Type::I(16, 8, 2); static I16X8X3: Type = Type::I(16, 8, 3); static I16X8X4: Type = Type::I(16, 8, 4); static I32X2: Type = Type::I(32, 2, 1); static I32X2X2: Type = Type::I(32, 2, 2); static I32X2X3: Type = Type::I(32, 2, 3); static I32X2X4: Type = Type::I(32, 2, 4); static I32X4: Type = Type::I(32, 4, 1); static I32X4X2: Type = Type::I(32, 4, 2); static I32X4X3: Type = Type::I(32, 4, 3); static I32X4X4: Type = Type::I(32, 4, 4); static I64X1: Type = Type::I(64, 1, 1); static I64X1X2: Type = Type::I(64, 1, 2); static I64X1X3: Type = Type::I(64, 1, 3); static I64X1X4: Type = Type::I(64, 1, 4); static I64X2: Type = Type::I(64, 2, 1); static I64X2X2: Type = Type::I(64, 2, 2); static I64X2X3: Type = Type::I(64, 2, 3); static I64X2X4: Type = Type::I(64, 2, 4); static I8X16: Type = Type::I(8, 16, 1); static I8X16X2: Type = Type::I(8, 16, 2); static I8X16X3: Type = Type::I(8, 16, 3); static I8X16X4: Type = Type::I(8, 16, 4); static I8X4: Type = Type::I(8, 4, 1); static I8X8: Type = Type::I(8, 8, 1); static I8X8X2: Type = Type::I(8, 8, 2); static I8X8X3: Type = Type::I(8, 8, 3); static I8X8X4: Type = Type::I(8, 8, 4); static P128: Type = Type::PrimPoly(128); static P16: Type = Type::PrimPoly(16); static P16X4X2: Type = Type::P(16, 4, 2); static P16X4X3: Type = Type::P(16, 4, 3); static P16X4X4: Type = Type::P(16, 4, 4); static P16X8X2: Type = Type::P(16, 8, 2); static P16X8X3: Type = Type::P(16, 8, 3); static P16X8X4: Type = Type::P(16, 8, 4); static P64: Type = Type::PrimPoly(64); static P64X1X2: Type = Type::P(64, 1, 2); static P64X1X3: Type = Type::P(64, 1, 3); static P64X1X4: Type = Type::P(64, 1, 4); static P64X2X2: Type = Type::P(64, 2, 2); static P64X2X3: Type = Type::P(64, 2, 3); static P64X2X4: Type = Type::P(64, 2, 4); static P8: Type = Type::PrimPoly(8); static POLY16X4: Type = Type::P(16, 4, 1); static POLY16X8: Type = Type::P(16, 8, 1); static POLY64X1: Type = Type::P(64, 1, 1); static POLY64X2: Type = Type::P(64, 2, 1); static POLY8X16: Type = Type::P(8, 16, 1); static POLY8X16X2: Type = Type::P(8, 16, 2); static POLY8X16X3: Type = Type::P(8, 16, 3); static POLY8X16X4: Type = Type::P(8, 16, 4); static POLY8X8: Type = Type::P(8, 8, 1); static POLY8X8X2: Type = Type::P(8, 8, 2); static POLY8X8X3: Type = Type::P(8, 8, 3); static POLY8X8X4: Type = Type::P(8, 8, 4); static U16X4: Type = Type::U(16, 4, 1); static U16X4X2: Type = Type::U(16, 4, 2); static U16X4X3: Type = Type::U(16, 4, 3); static U16X4X4: Type = Type::U(16, 4, 4); static U16X8: Type = Type::U(16, 8, 1); static U16X8X2: Type = Type::U(16, 8, 2); static U16X8X3: Type = Type::U(16, 8, 3); static U16X8X4: Type = Type::U(16, 8, 4); static U32X2: Type = Type::U(32, 2, 1); static U32X2X2: Type = Type::U(32, 2, 2); static U32X2X3: Type = Type::U(32, 2, 3); static U32X2X4: Type = Type::U(32, 2, 4); static U32X4: Type = Type::U(32, 4, 1); static U32X4X2: Type = Type::U(32, 4, 2); static U32X4X3: Type = Type::U(32, 4, 3); static U32X4X4: Type = Type::U(32, 4, 4); static U64X1: Type = Type::U(64, 1, 1); static U64X1X2: Type = Type::U(64, 1, 2); static U64X1X3: Type = Type::U(64, 1, 3); static U64X1X4: Type = Type::U(64, 1, 4); static U64X2: Type = Type::U(64, 2, 1); static U64X2X2: Type = Type::U(64, 2, 2); static U64X2X3: Type = Type::U(64, 2, 3); static U64X2X4: Type = Type::U(64, 2, 4); static U8X16: Type = Type::U(8, 16, 1); static U8X16X2: Type = Type::U(8, 16, 2); static U8X16X3: Type = Type::U(8, 16, 3); static U8X16X4: Type = Type::U(8, 16, 4); static U8X8: Type = Type::U(8, 8, 1); static U8X4: Type = Type::U(8, 4, 1); static U8X8X2: Type = Type::U(8, 8, 2); static U8X8X3: Type = Type::U(8, 8, 3); static U8X8X4: Type = Type::U(8, 8, 4); #[derive(Debug, Copy, Clone, PartialEq)] enum Type { PrimFloat(u8), PrimSigned(u8), PrimUnsigned(u8), PrimPoly(u8), MutPtr(&'static Type), ConstPtr(&'static Type), I(u8, u8, u8), U(u8, u8, u8), P(u8, u8, u8), F(u8, u8, u8), Never, } stdarch_verify::arm_functions!(static FUNCTIONS); macro_rules! bail { ($($t:tt)*) => (return Err(format!($($t)*))) } #[test] fn verify_all_signatures() { // Reference: https://developer.arm.com/architectures/instruction-sets/intrinsics let json = include_bytes!("../../../intrinsics_data/arm_intrinsics.json"); let intrinsics: Vec = serde_json::from_slice(json).unwrap(); let map = parse_intrinsics(intrinsics); let mut all_valid = true; for rust in FUNCTIONS { if !rust.has_test { let skip = [ "vaddq_s64", "vaddq_u64", "vrsqrte_f32", "vtbl1_s8", "vtbl1_u8", "vtbl1_p8", "vtbl2_s8", "vtbl2_u8", "vtbl2_p8", "vtbl3_s8", "vtbl3_u8", "vtbl3_p8", "vtbl4_s8", "vtbl4_u8", "vtbl4_p8", "vtbx1_s8", "vtbx1_u8", "vtbx1_p8", "vtbx2_s8", "vtbx2_u8", "vtbx2_p8", "vtbx3_s8", "vtbx3_u8", "vtbx3_p8", "vtbx4_s8", "vtbx4_u8", "vtbx4_p8", "udf", "_clz_u8", "_clz_u16", "_clz_u32", "_rbit_u32", "_rev_u16", "_rev_u32", "__breakpoint", "vpminq_f32", "vpminq_f64", "vpmaxq_f32", "vpmaxq_f64", "vcombine_s8", "vcombine_s16", "vcombine_s32", "vcombine_s64", "vcombine_u8", "vcombine_u16", "vcombine_u32", "vcombine_u64", "vcombine_p64", "vcombine_f32", "vcombine_p8", "vcombine_p16", "vcombine_f64", "vtbl1_s8", "vtbl1_u8", "vtbl1_p8", "vtbl2_s8", "vtbl2_u8", "vtbl2_p8", "vtbl3_s8", "vtbl3_u8", "vtbl3_p8", "vtbl4_s8", "vtbl4_u8", "vtbl4_p8", "vtbx1_s8", "vtbx1_u8", "vtbx1_p8", "vtbx2_s8", "vtbx2_u8", "vtbx2_p8", "vtbx3_s8", "vtbx3_u8", "vtbx3_p8", "vtbx4_s8", "vtbx4_u8", "vtbx4_p8", "vqtbl1_s8", "vqtbl1q_s8", "vqtbl1_u8", "vqtbl1q_u8", "vqtbl1_p8", "vqtbl1q_p8", "vqtbx1_s8", "vqtbx1q_s8", "vqtbx1_u8", "vqtbx1q_u8", "vqtbx1_p8", "vqtbx1q_p8", "vqtbl2_s8", "vqtbl2q_s8", "vqtbl2_u8", "vqtbl2q_u8", "vqtbl2_p8", "vqtbl2q_p8", "vqtbx2_s8", "vqtbx2q_s8", "vqtbx2_u8", "vqtbx2q_u8", "vqtbx2_p8", "vqtbx2q_p8", "vqtbl3_s8", "vqtbl3q_s8", "vqtbl3_u8", "vqtbl3q_u8", "vqtbl3_p8", "vqtbl3q_p8", "vqtbx3_s8", "vqtbx3q_s8", "vqtbx3_u8", "vqtbx3q_u8", "vqtbx3_p8", "vqtbx3q_p8", "vqtbl4_s8", "vqtbl4q_s8", "vqtbl4_u8", "vqtbl4q_u8", "vqtbl4_p8", "vqtbl4q_p8", "vqtbx4_s8", "vqtbx4q_s8", "vqtbx4_u8", "vqtbx4q_u8", "vqtbx4_p8", "vqtbx4q_p8", "brk", "_rev_u64", "_clz_u64", "_rbit_u64", "_cls_u32", "_cls_u64", "_prefetch", "vsli_n_s8", "vsliq_n_s8", "vsli_n_s16", "vsliq_n_s16", "vsli_n_s32", "vsliq_n_s32", "vsli_n_s64", "vsliq_n_s64", "vsli_n_u8", "vsliq_n_u8", "vsli_n_u16", "vsliq_n_u16", "vsli_n_u32", "vsliq_n_u32", "vsli_n_u64", "vsliq_n_u64", "vsli_n_p8", "vsliq_n_p8", "vsli_n_p16", "vsliq_n_p16", "vsli_n_p64", "vsliq_n_p64", "vsri_n_s8", "vsriq_n_s8", "vsri_n_s16", "vsriq_n_s16", "vsri_n_s32", "vsriq_n_s32", "vsri_n_s64", "vsriq_n_s64", "vsri_n_u8", "vsriq_n_u8", "vsri_n_u16", "vsriq_n_u16", "vsri_n_u32", "vsriq_n_u32", "vsri_n_u64", "vsriq_n_u64", "vsri_n_p8", "vsriq_n_p8", "vsri_n_p16", "vsriq_n_p16", "vsri_n_p64", "vsriq_n_p64", "__smulbb", "__smultb", "__smulbt", "__smultt", "__smulwb", "__smulwt", "__qadd", "__qsub", "__qdbl", "__smlabb", "__smlabt", "__smlatb", "__smlatt", "__smlawb", "__smlawt", "__qadd8", "__qsub8", "__qsub16", "__qadd16", "__qasx", "__qsax", "__sadd16", "__sadd8", "__smlad", "__smlsd", "__sasx", "__sel", "__shadd8", "__shadd16", "__shsub8", "__usub8", "__ssub8", "__shsub16", "__smuad", "__smuadx", "__smusd", "__smusdx", "__usad8", "__usada8", "__ldrex", "__strex", "__ldrexb", "__strexb", "__ldrexh", "__strexh", "__clrex", "__dbg", ]; 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 NEON and are located in different // places than the whitelists below. match rust.name { "brk" | "__breakpoint" | "udf" | "_prefetch" => continue, _ => {} } // Skip some intrinsics that are present in GCC and Clang but // are missing from the official documentation. let skip_intrinsic_verify = [ "vmov_n_p64", "vmovq_n_p64", "vreinterpret_p64_s64", "vreinterpret_f32_p64", "vreinterpretq_f32_p64", "vreinterpretq_p64_p128", "vreinterpretq_p128_p64", "vreinterpretq_f32_p128", "vtst_p16", "vtstq_p16", "__dbg", ]; let arm = match map.get(rust.name) { Some(i) => i, None => { // Skip all these intrinsics as they're not listed in NEON // descriptions online. // // TODO: we still need to verify these intrinsics or find a // reference for them, need to figure out where though! if !rust.file.ends_with("dsp.rs\"") && !rust.file.ends_with("simd32.rs\"") && !rust.file.ends_with("v6.rs\"") && !rust.file.ends_with("v7.rs\"") && !rust.file.ends_with("v8.rs\"") && !rust.file.ends_with("tme.rs\"") && !rust.file.ends_with("ex.rs\"") && !skip_intrinsic_verify.contains(&rust.name) { println!( "missing arm definition for {:?} in {}", rust.name, rust.file ); all_valid = false; } continue; } }; if let Err(e) = matches(rust, arm) { println!("failed to verify `{}`", rust.name); println!(" * {e}"); all_valid = false; } } assert!(all_valid); } fn matches(rust: &Function, arm: &Intrinsic) -> Result<(), String> { if rust.ret != arm.ret.as_ref() { bail!("mismatched return value") } if rust.arguments.len() != arm.arguments.len() { bail!("mismatched argument lengths"); } let mut nconst = 0; let iter = rust.arguments.iter().zip(&arm.arguments).enumerate(); for (i, (rust_ty, (arm, arm_const))) in iter { if *rust_ty != arm { bail!("mismatched arguments: {rust_ty:?} != {arm:?}") } if *arm_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.instrs.is_empty() { bail!( "instruction not listed for `{}`, but arm lists {:?}", rust.name, arm.instruction ); } else if false // TODO: This instruction checking logic needs work to handle multiple instructions and to only // look at aarch64 insructions. // The ACLE's listed instructions are a guideline only and compilers have the freedom to use // different instructions in dfferent cases which makes this an unreliable testing method. It // is of questionable value given the intrinsic test tool. { for instr in rust.instrs { if arm.instruction.starts_with(instr) { continue; } // sometimes arm says `foo` and disassemblers say `vfoo`, or // sometimes disassemblers say `vfoo` and arm says `sfoo` or `ffoo` if instr.starts_with('v') && (arm.instruction.starts_with(&instr[1..]) || arm.instruction[1..].starts_with(&instr[1..])) { continue; } bail!( "arm failed to list `{}` as an instruction for `{}` in {:?}", instr, rust.name, arm.instruction, ); } } // TODO: verify `target_feature`. Ok(()) } #[derive(PartialEq)] struct Intrinsic { name: String, ret: Option, arguments: Vec<(Type, bool)>, instruction: String, } // These structures are similar to those in json_parser.rs in intrinsics-test #[derive(Deserialize, Debug)] struct JsonIntrinsic { name: String, arguments: Vec, return_type: ReturnType, #[serde(default)] instructions: Vec>, } #[derive(Deserialize, Debug)] struct ReturnType { value: String, } fn parse_intrinsics(intrinsics: Vec) -> HashMap { let mut ret = HashMap::new(); for intr in intrinsics.into_iter() { let f = parse_intrinsic(intr); ret.insert(f.name.clone(), f); } ret } fn parse_intrinsic(mut intr: JsonIntrinsic) -> Intrinsic { let name = intr.name; let ret = if intr.return_type.value == "void" { None } else { Some(parse_ty(&intr.return_type.value)) }; // This ignores multiple instructions and different optional sequences for now to mimic // the old HTML scraping behaviour let instruction = intr.instructions.swap_remove(0).swap_remove(0); let arguments = intr .arguments .iter() .map(|s| { let (ty, konst) = match s.strip_prefix("const") { Some(stripped) => (stripped.trim_start(), true), None => (s.as_str(), false), }; let ty = ty.rsplit_once(' ').unwrap().0; (parse_ty(ty), konst) }) .collect::>(); Intrinsic { name, ret, instruction, arguments, } } fn parse_ty(s: &str) -> Type { let suffix = " const *"; if let Some(base) = s.strip_suffix(suffix) { Type::ConstPtr(parse_ty_base(base)) } else if let Some(base) = s.strip_suffix(" *") { Type::MutPtr(parse_ty_base(base)) } else { *parse_ty_base(s) } } fn parse_ty_base(s: &str) -> &'static Type { match s { "float16_t" => &F16, "float16x4_t" => &F16X4, "float16x4x2_t" => &F16X4X2, "float16x4x3_t" => &F16X4X3, "float16x4x4_t" => &F16X4X4, "float16x8_t" => &F16X8, "float16x8x2_t" => &F16X8X2, "float16x8x3_t" => &F16X8X3, "float16x8x4_t" => &F16X8X4, "float32_t" => &F32, "float32x2_t" => &F32X2, "float32x2x2_t" => &F32X2X2, "float32x2x3_t" => &F32X2X3, "float32x2x4_t" => &F32X2X4, "float32x4_t" => &F32X4, "float32x4x2_t" => &F32X4X2, "float32x4x3_t" => &F32X4X3, "float32x4x4_t" => &F32X4X4, "float64_t" => &F64, "float64x1_t" => &F64X1, "float64x1x2_t" => &F64X1X2, "float64x1x3_t" => &F64X1X3, "float64x1x4_t" => &F64X1X4, "float64x2_t" => &F64X2, "float64x2x2_t" => &F64X2X2, "float64x2x3_t" => &F64X2X3, "float64x2x4_t" => &F64X2X4, "int16_t" => &I16, "int16x2_t" => &I16X2, "int16x4_t" => &I16X4, "int16x4x2_t" => &I16X4X2, "int16x4x3_t" => &I16X4X3, "int16x4x4_t" => &I16X4X4, "int16x8_t" => &I16X8, "int16x8x2_t" => &I16X8X2, "int16x8x3_t" => &I16X8X3, "int16x8x4_t" => &I16X8X4, "int32_t" | "int" => &I32, "int32x2_t" => &I32X2, "int32x2x2_t" => &I32X2X2, "int32x2x3_t" => &I32X2X3, "int32x2x4_t" => &I32X2X4, "int32x4_t" => &I32X4, "int32x4x2_t" => &I32X4X2, "int32x4x3_t" => &I32X4X3, "int32x4x4_t" => &I32X4X4, "int64_t" => &I64, "int64x1_t" => &I64X1, "int64x1x2_t" => &I64X1X2, "int64x1x3_t" => &I64X1X3, "int64x1x4_t" => &I64X1X4, "int64x2_t" => &I64X2, "int64x2x2_t" => &I64X2X2, "int64x2x3_t" => &I64X2X3, "int64x2x4_t" => &I64X2X4, "int8_t" => &I8, "int8x16_t" => &I8X16, "int8x16x2_t" => &I8X16X2, "int8x16x3_t" => &I8X16X3, "int8x16x4_t" => &I8X16X4, "int8x4_t" => &I8X4, "int8x8_t" => &I8X8, "int8x8x2_t" => &I8X8X2, "int8x8x3_t" => &I8X8X3, "int8x8x4_t" => &I8X8X4, "poly128_t" => &P128, "poly16_t" => &P16, "poly16x4_t" => &POLY16X4, "poly16x4x2_t" => &P16X4X2, "poly16x4x3_t" => &P16X4X3, "poly16x4x4_t" => &P16X4X4, "poly16x8_t" => &POLY16X8, "poly16x8x2_t" => &P16X8X2, "poly16x8x3_t" => &P16X8X3, "poly16x8x4_t" => &P16X8X4, "poly64_t" => &P64, "poly64x1_t" => &POLY64X1, "poly64x1x2_t" => &P64X1X2, "poly64x1x3_t" => &P64X1X3, "poly64x1x4_t" => &P64X1X4, "poly64x2_t" => &POLY64X2, "poly64x2x2_t" => &P64X2X2, "poly64x2x3_t" => &P64X2X3, "poly64x2x4_t" => &P64X2X4, "poly8_t" => &P8, "poly8x16_t" => &POLY8X16, "poly8x16x2_t" => &POLY8X16X2, "poly8x16x3_t" => &POLY8X16X3, "poly8x16x4_t" => &POLY8X16X4, "poly8x8_t" => &POLY8X8, "poly8x8x2_t" => &POLY8X8X2, "poly8x8x3_t" => &POLY8X8X3, "poly8x8x4_t" => &POLY8X8X4, "uint16_t" => &U16, "uint16x4_t" => &U16X4, "uint16x4x2_t" => &U16X4X2, "uint16x4x3_t" => &U16X4X3, "uint16x4x4_t" => &U16X4X4, "uint16x8_t" => &U16X8, "uint16x8x2_t" => &U16X8X2, "uint16x8x3_t" => &U16X8X3, "uint16x8x4_t" => &U16X8X4, "uint32_t" => &U32, "uint32x2_t" => &U32X2, "uint32x2x2_t" => &U32X2X2, "uint32x2x3_t" => &U32X2X3, "uint32x2x4_t" => &U32X2X4, "uint32x4_t" => &U32X4, "uint32x4x2_t" => &U32X4X2, "uint32x4x3_t" => &U32X4X3, "uint32x4x4_t" => &U32X4X4, "uint64_t" => &U64, "uint64x1_t" => &U64X1, "uint64x1x2_t" => &U64X1X2, "uint64x1x3_t" => &U64X1X3, "uint64x1x4_t" => &U64X1X4, "uint64x2_t" => &U64X2, "uint64x2x2_t" => &U64X2X2, "uint64x2x3_t" => &U64X2X3, "uint64x2x4_t" => &U64X2X4, "uint8_t" => &U8, "uint8x16_t" => &U8X16, "uint8x16x2_t" => &U8X16X2, "uint8x16x3_t" => &U8X16X3, "uint8x16x4_t" => &U8X16X4, "uint8x8_t" => &U8X8, "uint8x8x2_t" => &U8X8X2, "uint8x8x3_t" => &U8X8X3, "uint8x8x4_t" => &U8X8X4, _ => panic!("failed to parse json type {s:?}"), } }