about summary refs log tree commit diff
path: root/library/stdarch/crates/stdarch-verify/tests/mips.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/stdarch/crates/stdarch-verify/tests/mips.rs')
-rw-r--r--library/stdarch/crates/stdarch-verify/tests/mips.rs367
1 files changed, 367 insertions, 0 deletions
diff --git a/library/stdarch/crates/stdarch-verify/tests/mips.rs b/library/stdarch/crates/stdarch-verify/tests/mips.rs
new file mode 100644
index 00000000000..ba639c3f92f
--- /dev/null
+++ b/library/stdarch/crates/stdarch-verify/tests/mips.rs
@@ -0,0 +1,367 @@
+//! Verification of MIPS MSA intrinsics
+#![allow(unused, non_upper_case_globals, clippy::single_match)]
+
+// 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,
+    doc: &'static str,
+}
+
+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(())
+}