Implement automatic verification for ARM/AArch64 intrinsics (#626)
This commit implements automatic verification of implement ARM/AArch64 intrinsics. Or it's at least a start! This downloads a snapshot of ARM's [online documentation][docs] and implements necessary logic to parse that and use it to verify all the intrinsics. Almost everything checked out A-OK but a few minor tweaks were needed to the neon intrinsics and the crc ones needed some renaming. [docs]: https://developer.arm.com/technologies/neon/intrinsics
This commit is contained in:
parent
10d166db0e
commit
9ab261c3d7
9 changed files with 94107 additions and 56 deletions
|
|
@ -25,7 +25,7 @@ use stdsimd_test::assert_instr;
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32b))]
|
||||
pub unsafe fn crc32b(crc: u32, data: u8) -> u32 {
|
||||
pub unsafe fn __crc32b(crc: u32, data: u8) -> u32 {
|
||||
crc32b_(crc, data as u32)
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ pub unsafe fn crc32b(crc: u32, data: u8) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32h))]
|
||||
pub unsafe fn crc32h(crc: u32, data: u16) -> u32 {
|
||||
pub unsafe fn __crc32h(crc: u32, data: u16) -> u32 {
|
||||
crc32h_(crc, data as u32)
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ pub unsafe fn crc32h(crc: u32, data: u16) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32w))]
|
||||
pub unsafe fn crc32w(crc: u32, data: u32) -> u32 {
|
||||
pub unsafe fn __crc32w(crc: u32, data: u32) -> u32 {
|
||||
crc32w_(crc, data)
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ pub unsafe fn crc32w(crc: u32, data: u32) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32x))]
|
||||
pub unsafe fn crc32x(crc: u32, data: u64) -> u32 {
|
||||
pub unsafe fn __crc32d(crc: u32, data: u64) -> u32 {
|
||||
crc32x_(crc, data)
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ pub unsafe fn crc32x(crc: u32, data: u64) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32cb))]
|
||||
pub unsafe fn crc32cb(crc: u32, data: u8) -> u32 {
|
||||
pub unsafe fn __crc32cb(crc: u32, data: u8) -> u32 {
|
||||
crc32cb_(crc, data as u32)
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ pub unsafe fn crc32cb(crc: u32, data: u8) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32ch))]
|
||||
pub unsafe fn crc32ch(crc: u32, data: u16) -> u32 {
|
||||
pub unsafe fn __crc32ch(crc: u32, data: u16) -> u32 {
|
||||
crc32ch_(crc, data as u32)
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ pub unsafe fn crc32ch(crc: u32, data: u16) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32cw))]
|
||||
pub unsafe fn crc32cw(crc: u32, data: u32) -> u32 {
|
||||
pub unsafe fn __crc32cw(crc: u32, data: u32) -> u32 {
|
||||
crc32cw_(crc, data)
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ pub unsafe fn crc32cw(crc: u32, data: u32) -> u32 {
|
|||
#[inline]
|
||||
#[target_feature(enable = "crc")]
|
||||
#[cfg_attr(test, assert_instr(crc32cx))]
|
||||
pub unsafe fn crc32cx(crc: u32, data: u64) -> u32 {
|
||||
pub unsafe fn __crc32cd(crc: u32, data: u64) -> u32 {
|
||||
crc32cx_(crc, data)
|
||||
}
|
||||
|
||||
|
|
@ -94,50 +94,50 @@ mod tests {
|
|||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32b() {
|
||||
assert_eq!(crc32b(0, 0), 0);
|
||||
assert_eq!(crc32b(0, 255), 755167117);
|
||||
assert_eq!(__crc32b(0, 0), 0);
|
||||
assert_eq!(__crc32b(0, 255), 755167117);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32h() {
|
||||
assert_eq!(crc32h(0, 0), 0);
|
||||
assert_eq!(crc32h(0, 16384), 1994146192);
|
||||
assert_eq!(__crc32h(0, 0), 0);
|
||||
assert_eq!(__crc32h(0, 16384), 1994146192);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32w() {
|
||||
assert_eq!(crc32w(0, 0), 0);
|
||||
assert_eq!(crc32w(0, 4294967295), 3736805603);
|
||||
assert_eq!(__crc32w(0, 0), 0);
|
||||
assert_eq!(__crc32w(0, 4294967295), 3736805603);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32x() {
|
||||
assert_eq!(crc32x(0, 0), 0);
|
||||
assert_eq!(crc32x(0, 18446744073709551615), 1147535477);
|
||||
unsafe fn test_crc32d() {
|
||||
assert_eq!(__crc32d(0, 0), 0);
|
||||
assert_eq!(__crc32d(0, 18446744073709551615), 1147535477);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32cb() {
|
||||
assert_eq!(crc32cb(0, 0), 0);
|
||||
assert_eq!(crc32cb(0, 255), 2910671697);
|
||||
assert_eq!(__crc32cb(0, 0), 0);
|
||||
assert_eq!(__crc32cb(0, 255), 2910671697);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32ch() {
|
||||
assert_eq!(crc32ch(0, 0), 0);
|
||||
assert_eq!(crc32ch(0, 16384), 1098587580);
|
||||
assert_eq!(__crc32ch(0, 0), 0);
|
||||
assert_eq!(__crc32ch(0, 16384), 1098587580);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32cw() {
|
||||
assert_eq!(crc32cw(0, 0), 0);
|
||||
assert_eq!(crc32cw(0, 4294967295), 3080238136);
|
||||
assert_eq!(__crc32cw(0, 0), 0);
|
||||
assert_eq!(__crc32cw(0, 4294967295), 3080238136);
|
||||
}
|
||||
|
||||
#[simd_test(enable = "crc")]
|
||||
unsafe fn test_crc32cx() {
|
||||
assert_eq!(crc32cx(0, 0), 0);
|
||||
assert_eq!(crc32cx(0, 18446744073709551615), 3293575501);
|
||||
unsafe fn test_crc32cd() {
|
||||
assert_eq!(__crc32cd(0, 0), 0);
|
||||
assert_eq!(__crc32cd(0, 18446744073709551615), 3293575501);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use coresimd::simd_llvm::*;
|
|||
#[cfg(test)]
|
||||
use stdsimd_test::assert_instr;
|
||||
|
||||
use mem;
|
||||
|
||||
types! {
|
||||
/// ARM-specific 64-bit wide vector of one packed `f64`.
|
||||
pub struct float64x1_t(f64); // FIXME: check this!
|
||||
|
|
@ -250,16 +252,20 @@ pub unsafe fn vaddq_f64(a: float64x2_t, b: float64x2_t) -> float64x2_t {
|
|||
#[inline]
|
||||
#[target_feature(enable = "neon")]
|
||||
#[cfg_attr(test, assert_instr(add))]
|
||||
pub unsafe fn vaddd_s64(a: int64x1_t, b: int64x1_t) -> int64x1_t {
|
||||
simd_add(a, b)
|
||||
pub unsafe fn vaddd_s64(a: i64, b: i64) -> i64 {
|
||||
let a: int64x1_t = mem::transmute(a);
|
||||
let b: int64x1_t = mem::transmute(b);
|
||||
simd_extract(simd_add(a, b), 0)
|
||||
}
|
||||
|
||||
/// Vector add.
|
||||
#[inline]
|
||||
#[target_feature(enable = "neon")]
|
||||
#[cfg_attr(test, assert_instr(add))]
|
||||
pub unsafe fn vaddd_u64(a: uint64x1_t, b: uint64x1_t) -> uint64x1_t {
|
||||
simd_add(a, b)
|
||||
pub unsafe fn vaddd_u64(a: u64, b: u64) -> u64 {
|
||||
let a: uint64x1_t = mem::transmute(a);
|
||||
let b: uint64x1_t = mem::transmute(b);
|
||||
simd_extract(simd_add(a, b), 0)
|
||||
}
|
||||
|
||||
/// Horizontal vector max.
|
||||
|
|
|
|||
|
|
@ -702,7 +702,7 @@ pub unsafe fn vtbl1_u8(a: uint8x8_t, b: uint8x8_t) -> uint8x8_t {
|
|||
#[cfg(target_endian = "little")]
|
||||
#[target_feature(enable = "neon,v7")]
|
||||
#[cfg_attr(test, assert_instr(vtbl))]
|
||||
pub unsafe fn vtbl1_p8(a: poly8x8_t, b: poly8x8_t) -> poly8x8_t {
|
||||
pub unsafe fn vtbl1_p8(a: poly8x8_t, b: uint8x8_t) -> poly8x8_t {
|
||||
::mem::transmute(vtbl1(::mem::transmute(a), ::mem::transmute(b)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
name = "stdsimd-verify"
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "0.4"
|
||||
|
|
@ -13,6 +14,6 @@ proc-macro = true
|
|||
test = false
|
||||
|
||||
[dev-dependencies]
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde = { version = "1.0", features = ['derive'] }
|
||||
serde-xml-rs = "0.2"
|
||||
html5ever = "0.22.5"
|
||||
|
|
|
|||
93399
library/stdarch/crates/stdsimd-verify/arm-intrinsics.html
Normal file
93399
library/stdarch/crates/stdsimd-verify/arm-intrinsics.html
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -3,8 +3,10 @@ use std::path::Path;
|
|||
fn main() {
|
||||
let dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let root = dir.parent().unwrap();
|
||||
let root = root.join("../coresimd/x86");
|
||||
walk(&root);
|
||||
walk(&root.join("../coresimd/x86"));
|
||||
walk(&root.join("../coresimd/x86_64"));
|
||||
walk(&root.join("../coresimd/arm"));
|
||||
walk(&root.join("../coresimd/aarch64"));
|
||||
}
|
||||
|
||||
fn walk(root: &Path) {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,22 @@ use proc_macro::TokenStream;
|
|||
|
||||
#[proc_macro]
|
||||
pub fn x86_functions(input: TokenStream) -> TokenStream {
|
||||
functions(input, &["../coresimd/x86", "../coresimd/x86_64"])
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn arm_functions(input: TokenStream) -> TokenStream {
|
||||
functions(input, &["../coresimd/arm", "../coresimd/aarch64"])
|
||||
}
|
||||
|
||||
fn functions(input: TokenStream, dirs: &[&str]) -> TokenStream {
|
||||
let dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let root = dir.parent().unwrap();
|
||||
|
||||
let mut files = Vec::new();
|
||||
walk(&root.join("../coresimd/x86"), &mut files);
|
||||
walk(&root.join("../coresimd/x86_64"), &mut files);
|
||||
for dir in dirs {
|
||||
walk(&root.join(dir), &mut files);
|
||||
}
|
||||
assert!(!files.is_empty());
|
||||
|
||||
let mut functions = Vec::new();
|
||||
|
|
@ -92,6 +102,7 @@ pub fn x86_functions(input: TokenStream) -> TokenStream {
|
|||
fn to_type(t: &syn::Type) -> proc_macro2::TokenStream {
|
||||
match *t {
|
||||
syn::Type::Path(ref p) => match extract_path_ident(&p.path).to_string().as_ref() {
|
||||
// x86 ...
|
||||
"__m128" => quote! { &M128 },
|
||||
"__m128d" => quote! { &M128D },
|
||||
"__m128i" => quote! { &M128I },
|
||||
|
|
@ -115,6 +126,55 @@ fn to_type(t: &syn::Type) -> proc_macro2::TokenStream {
|
|||
"u64" => quote! { &U64 },
|
||||
"u8" => quote! { &U8 },
|
||||
"CpuidResult" => quote! { &CPUID },
|
||||
|
||||
// arm ...
|
||||
"int8x4_t" => quote! { &I8X4 },
|
||||
"int8x8_t" => quote! { &I8X8 },
|
||||
"int8x8x2_t" => quote! { &I8X8X2 },
|
||||
"int8x8x3_t" => quote! { &I8X8X3 },
|
||||
"int8x8x4_t" => quote! { &I8X8X4 },
|
||||
"int8x16x2_t" => quote! { &I8X16X2 },
|
||||
"int8x16x3_t" => quote! { &I8X16X3 },
|
||||
"int8x16x4_t" => quote! { &I8X16X4 },
|
||||
"int8x16_t" => quote! { &I8X16 },
|
||||
"int16x2_t" => quote! { &I16X2 },
|
||||
"int16x4_t" => quote! { &I16X4 },
|
||||
"int16x8_t" => quote! { &I16X8 },
|
||||
"int32x2_t" => quote! { &I32X2 },
|
||||
"int32x4_t" => quote! { &I32X4 },
|
||||
"int64x1_t" => quote! { &I64X1 },
|
||||
"int64x2_t" => quote! { &I64X2 },
|
||||
"uint8x8_t" => quote! { &U8X8 },
|
||||
"uint8x8x2_t" => quote! { &U8X8X2 },
|
||||
"uint8x16x2_t" => quote! { &U8X16X2 },
|
||||
"uint8x16x3_t" => quote! { &U8X16X3 },
|
||||
"uint8x16x4_t" => quote! { &U8X16X4 },
|
||||
"uint8x8x3_t" => quote! { &U8X8X3 },
|
||||
"uint8x8x4_t" => quote! { &U8X8X4 },
|
||||
"uint8x16_t" => quote! { &U8X16 },
|
||||
"uint16x4_t" => quote! { &U16X4 },
|
||||
"uint16x8_t" => quote! { &U16X8 },
|
||||
"uint32x2_t" => quote! { &U32X2 },
|
||||
"uint32x4_t" => quote! { &U32X4 },
|
||||
"uint64x1_t" => quote! { &U64X1 },
|
||||
"uint64x2_t" => quote! { &U64X2 },
|
||||
"float32x2_t" => quote! { &F32X2 },
|
||||
"float32x4_t" => quote! { &F32X4 },
|
||||
"float64x1_t" => quote! { &F64X1 },
|
||||
"float64x2_t" => quote! { &F64X2 },
|
||||
"poly8x8_t" => quote! { &POLY8X8 },
|
||||
"poly8x8x2_t" => quote! { &POLY8X8X2 },
|
||||
"poly8x8x3_t" => quote! { &POLY8X8X3 },
|
||||
"poly8x8x4_t" => quote! { &POLY8X8X4 },
|
||||
"poly8x16x2_t" => quote! { &POLY8X16X2 },
|
||||
"poly8x16x3_t" => quote! { &POLY8X16X3 },
|
||||
"poly8x16x4_t" => quote! { &POLY8X16X4 },
|
||||
"poly64x1_t" => quote! { &POLY64X1 },
|
||||
"poly64x2_t" => quote! { &POLY64X2 },
|
||||
"poly8x16_t" => quote! { &POLY8X16 },
|
||||
"poly16x4_t" => quote! { &POLY16X4 },
|
||||
"poly16x8_t" => quote! { &POLY16X8 },
|
||||
|
||||
s => panic!("unspported type: \"{}\"", s),
|
||||
},
|
||||
syn::Type::Ptr(syn::TypePtr { ref elem, .. })
|
||||
|
|
|
|||
591
library/stdarch/crates/stdsimd-verify/tests/arm.rs
Normal file
591
library/stdarch/crates/stdsimd-verify/tests/arm.rs
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
#![allow(bad_style)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use html5ever::driver::ParseOpts;
|
||||
use html5ever::parse_document;
|
||||
use html5ever::rcdom::{Node, NodeData, RcDom};
|
||||
use html5ever::tendril::TendrilSink;
|
||||
use html5ever::tree_builder::TreeBuilderOpts;
|
||||
|
||||
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 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 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,
|
||||
}
|
||||
|
||||
stdsimd_verify::arm_functions!(static FUNCTIONS);
|
||||
|
||||
macro_rules! bail {
|
||||
($($t:tt)*) => (return Err(format!($($t)*)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_all_signatures() {
|
||||
// This is a giant HTML blob downloaded from
|
||||
// https://developer.arm.com/technologies/neon/intrinsics which contains all
|
||||
// NEON intrinsics at least. We do manual HTML parsing below.
|
||||
let html = include_bytes!("../arm-intrinsics.html");
|
||||
let mut html = &html[..];
|
||||
let opts = ParseOpts {
|
||||
tree_builder: TreeBuilderOpts {
|
||||
drop_doctype: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let dom = parse_document(RcDom::default(), opts)
|
||||
.from_utf8()
|
||||
.read_from(&mut html)
|
||||
.unwrap();
|
||||
|
||||
let accordion = find_accordion(&dom.document).unwrap();
|
||||
let map = parse_intrinsics(&accordion);
|
||||
|
||||
let mut all_valid = true;
|
||||
'outer: for rust in FUNCTIONS {
|
||||
// Skip some intrinsics that aren't NEON and are located in different
|
||||
// places than the whitelists below.
|
||||
match rust.name {
|
||||
"brk" | "__breakpoint" | "udf" => continue,
|
||||
_ => {}
|
||||
}
|
||||
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("cmsis.rs\"")
|
||||
&& !rust.file.ends_with("v6.rs\"")
|
||||
&& !rust.file.ends_with("v7.rs\"")
|
||||
&& !rust.file.ends_with("v8.rs\"")
|
||||
{
|
||||
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")
|
||||
}
|
||||
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
|
||||
/* not super reliable, but can be used to manually check */
|
||||
{
|
||||
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(())
|
||||
}
|
||||
|
||||
fn find_accordion(node: &Rc<Node>) -> Option<Rc<Node>> {
|
||||
if let NodeData::Element { attrs, .. } = &node.data {
|
||||
for attr in attrs.borrow().iter() {
|
||||
if attr.name.local.eq_str_ignore_ascii_case("class") {
|
||||
if attr.value.to_string() == "intrinsic-accordion" {
|
||||
return Some(node.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.children
|
||||
.borrow()
|
||||
.iter()
|
||||
.filter_map(|node| find_accordion(node))
|
||||
.next()
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct Intrinsic {
|
||||
name: String,
|
||||
ret: Option<Type>,
|
||||
arguments: Vec<(Type, bool)>,
|
||||
instruction: String,
|
||||
}
|
||||
|
||||
fn parse_intrinsics(node: &Rc<Node>) -> HashMap<String, Intrinsic> {
|
||||
let mut ret = HashMap::new();
|
||||
for child in node.children.borrow().iter() {
|
||||
if let NodeData::Element { .. } = child.data {
|
||||
let f = parse_intrinsic(child);
|
||||
ret.insert(f.name.clone(), f);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn parse_intrinsic(node: &Rc<Node>) -> Intrinsic {
|
||||
// <div class='intrinsic'>
|
||||
// <input>...</input>
|
||||
// <label for=$name>
|
||||
// <div>
|
||||
// $signature...
|
||||
// <article>
|
||||
// ...
|
||||
|
||||
let children = node.children.borrow();
|
||||
let mut children = children.iter().filter(|node| match node.data {
|
||||
NodeData::Element { .. } => true,
|
||||
_ => false,
|
||||
});
|
||||
let _input = children.next().expect("no <input>");
|
||||
let label = children.next().expect("no <label>");
|
||||
let article = children.next().expect("no <article>");
|
||||
assert!(children.next().is_none());
|
||||
|
||||
// Find `for="..."` in `<label>`
|
||||
let name = match &label.data {
|
||||
NodeData::Element { attrs, .. } => attrs
|
||||
.borrow()
|
||||
.iter()
|
||||
.filter(|attr| attr.name.local.eq_str_ignore_ascii_case("for"))
|
||||
.map(|attr| attr.value.to_string())
|
||||
.next()
|
||||
.expect("no `for` attribute"),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
// Find contents of inner `<div>` in `<label>`
|
||||
let label_children = label.children.borrow();
|
||||
let mut label_children = label_children.iter().filter(|node| match node.data {
|
||||
NodeData::Element { .. } => true,
|
||||
_ => false,
|
||||
});
|
||||
let label_div = label_children.next().expect("no <div> in <label>");
|
||||
assert!(label_children.next().is_none());
|
||||
let text = label_div.children.borrow();
|
||||
let mut text = text.iter().filter_map(|node| match &node.data {
|
||||
NodeData::Text { contents } => Some(contents.borrow().to_string()),
|
||||
_ => None,
|
||||
});
|
||||
let ret = text.next().unwrap();
|
||||
let ret = ret.trim();
|
||||
let args = text.next().unwrap();
|
||||
let args = args.trim();
|
||||
assert!(text.next().is_none());
|
||||
|
||||
// Find the instruction within the article
|
||||
let article_children = article.children.borrow();
|
||||
let mut article_children = article_children.iter().filter(|node| match node.data {
|
||||
NodeData::Element { .. } => true,
|
||||
_ => false,
|
||||
});
|
||||
let mut instruction = None;
|
||||
while let Some(child) = article_children.next() {
|
||||
let mut header = String::new();
|
||||
collect_text(&mut header, child);
|
||||
if !header.ends_with(" Instruction") {
|
||||
continue;
|
||||
}
|
||||
let next = article_children.next().expect("no next child");
|
||||
assert!(instruction.is_none());
|
||||
let mut instr = String::new();
|
||||
collect_text(&mut instr, &next);
|
||||
instruction = Some(instr);
|
||||
}
|
||||
|
||||
let instruction = match instruction {
|
||||
Some(s) => s.trim().to_lowercase(),
|
||||
None => panic!("can't find instruction for `{}`", name),
|
||||
};
|
||||
|
||||
Intrinsic {
|
||||
name,
|
||||
ret: if ret == "void" {
|
||||
None
|
||||
} else {
|
||||
Some(parse_ty(ret))
|
||||
},
|
||||
instruction,
|
||||
arguments: args // "(...)"
|
||||
.trim_start_matches("(") // "...)"
|
||||
.trim_end_matches(")") // "..."
|
||||
.split(",") // " Type name ", ".."
|
||||
.map(|s| s.trim()) // "Type name"
|
||||
.map(|s| s.rsplitn(2, ' ').nth(1).unwrap()) // "Type"
|
||||
.map(|s| {
|
||||
let const_ = "const ";
|
||||
if s.starts_with(const_) {
|
||||
(parse_ty(&s[const_.len()..]), true)
|
||||
} else {
|
||||
(parse_ty(s), false)
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ty(s: &str) -> Type {
|
||||
let suffix = " const *";
|
||||
if s.ends_with(suffix) {
|
||||
Type::ConstPtr(parse_ty_base(&s[..s.len() - suffix.len()]))
|
||||
} else if s.ends_with(" *") {
|
||||
Type::MutPtr(parse_ty_base(&s[..s.len() - 2]))
|
||||
} 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 html type {:?}", s),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_text(s: &mut String, node: &Node) {
|
||||
if let NodeData::Text { contents } = &node.data {
|
||||
s.push_str(" ");
|
||||
s.push_str(&contents.borrow().to_string());
|
||||
}
|
||||
for child in node.children.borrow().iter() {
|
||||
collect_text(s, child);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,18 @@
|
|||
#![allow(bad_style)]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(
|
||||
clippy::shadow_reuse,
|
||||
clippy::cast_lossless,
|
||||
clippy::match_same_arms,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::print_stdout,
|
||||
clippy::use_debug,
|
||||
clippy::eq_op,
|
||||
clippy::useless_format
|
||||
)
|
||||
#![allow(
|
||||
clippy::shadow_reuse,
|
||||
clippy::cast_lossless,
|
||||
clippy::match_same_arms,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::print_stdout,
|
||||
clippy::use_debug,
|
||||
clippy::eq_op,
|
||||
clippy::useless_format
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_xml_rs;
|
||||
extern crate stdsimd_verify;
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use stdsimd_verify::x86_functions;
|
||||
use serde::Deserialize;
|
||||
|
||||
const PRINT_INSTRUCTION_VIOLATIONS: bool = false;
|
||||
const PRINT_MISSING_LISTS: bool = false;
|
||||
|
|
@ -85,7 +77,7 @@ enum Type {
|
|||
Never,
|
||||
}
|
||||
|
||||
x86_functions!(static FUNCTIONS);
|
||||
stdsimd_verify::x86_functions!(static FUNCTIONS);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Data {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue