mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-28 11:38:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			368 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! 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(())
 | |
| }
 | 
