mirror of
https://git.proxmox.com/git/rustc
synced 2025-05-05 18:22:26 +00:00
367 lines
11 KiB
Rust
367 lines
11 KiB
Rust
//! 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(())
|
|
}
|