Skip to content

Automatic verification of MIPS MSA intrinsics #711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 11, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -89,7 +89,7 @@ matrix:
- name: "Documentation"
install: true
script: ci/dox.sh
- name: "Automatic verification: x86 and x86_64"
- name: "Automatic verification: x86 / x86_64 / arm / aarch64 / mips*"
script: cargo test --manifest-path crates/stdsimd-verify/Cargo.toml
install: true
- name: "rustfmt"
1,100 changes: 550 additions & 550 deletions crates/core_arch/src/mips/msa.rs

Large diffs are not rendered by default.

707 changes: 707 additions & 0 deletions crates/stdsimd-verify/mips-msa.h

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions crates/stdsimd-verify/src/lib.rs
Original file line number Diff line number Diff line change
@@ -21,6 +21,11 @@ pub fn arm_functions(input: TokenStream) -> TokenStream {
functions(input, &["core_arch/src/arm", "core_arch/src/aarch64"])
}

#[proc_macro]
pub fn mips_functions(input: TokenStream) -> TokenStream {
functions(input, &["core_arch/src/mips"])
}

fn functions(input: TokenStream, dirs: &[&str]) -> TokenStream {
let dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let root = dir.parent().expect("root-dir not found");
@@ -177,6 +182,18 @@ fn to_type(t: &syn::Type) -> proc_macro2::TokenStream {
"poly16x4_t" => quote! { &POLY16X4 },
"poly16x8_t" => quote! { &POLY16X8 },

"v16i8" => quote! { &v16i8 },
"v8i16" => quote! { &v8i16 },
"v4i32" => quote! { &v4i32 },
"v2i64" => quote! { &v2i64 },
"v16u8" => quote! { &v16u8 },
"v8u16" => quote! { &v8u16 },
"v4u32" => quote! { &v4u32 },
"v2u64" => quote! { &v2u64 },
"v8f16" => quote! { &v8f16 },
"v4f32" => quote! { &v4f32 },
"v2f64" => quote! { &v2f64 },

s => panic!("unspported type: \"{}\"", s),
},
syn::Type::Ptr(syn::TypePtr { ref elem, .. })
328 changes: 328 additions & 0 deletions crates/stdsimd-verify/tests/mips.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
//! Verification of MIPS MSA intrinsics
#![feature(try_trait)]
#![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");

stdsimd_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],
}

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),
Ptr(&'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,
VoidPtr,
}

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::VoidPtr,
v => panic!("unknown ty: \"{}\"", v),
}
}
}

#[derive(Debug, Clone)]
struct MsaIntrinsic {
id: String,
arg_tys: Vec<MsaTy>,
ret_ty: MsaTy,
instruction: String,
}

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 = std::option::NoneError;
fn try_from(line: &'static str) -> Result<Self, Self::Error> {
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;
}

Ok(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).expect(&format!("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 {
// 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::VoidPtr if **rust_arg == Type::Ptr(&U8) => (),
m => bail!(
"mismatched argument \"{}\"= \"{:?}\" != \"{:?}\"",
i,
m,
*rust_arg
),
}

let is_const = match 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 => true,
_ => false,
};
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");
}

/* FIXME:
if !rust.instrs.is_empty() {
if rust.instrs[0] != mips.instruction {
bail!("wrong instruction: \"{}\" != \"{}\"", rust.instrs[0], mips.instruction);
}
} else {
bail!(
"missing assert_instr for \"{}\" (should be \"{}\")",
mips.id, mips.instruction);
}*/

Ok(())
}