Enhance #[assert_instr] with constant arguments
Some intrinsics need to be invoked with constant arguments to get the right
instruction to get generated, so this commit enhances the `assert_instr` macro
to enable this ability. Namely you pass constant arguments like:
#[assert_instr(foo, a = b)]
where this will assert that the intrinsic, when invoked with argument `a` equal
to the value `b` and all other arguments passed from the outside, will generate
the instruction `foo`.
Closes #49
This commit is contained in:
parent
25cef3696a
commit
24f08cd458
4 changed files with 114 additions and 57 deletions
|
|
@ -84,6 +84,7 @@ pub unsafe fn _mm256_sub_ps(a: f32x8, b: f32x8) -> f32x8 {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
#[target_feature = "+avx"]
|
||||
#[cfg_attr(test, assert_instr(vroundpd, b = 0x3))]
|
||||
pub unsafe fn _mm256_round_pd(a: f64x4, b: i32) -> f64x4 {
|
||||
macro_rules! call {
|
||||
($imm8:expr) => { roundpd256(a, $imm8) }
|
||||
|
|
@ -91,13 +92,6 @@ pub unsafe fn _mm256_round_pd(a: f64x4, b: i32) -> f64x4 {
|
|||
constify_imm8!(b, call)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(test, assert_instr(vroundpd))]
|
||||
#[target_feature = "+avx"]
|
||||
fn test_mm256_round_pd(a: f64x4) -> f64x4 {
|
||||
unsafe { _mm256_round_pd(a, 0x3) }
|
||||
}
|
||||
|
||||
/// Round packed double-precision (64-bit) floating point elements in `a` toward
|
||||
/// positive infinity.
|
||||
#[inline(always)]
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ pub unsafe fn _mm_max_ps(a: f32x4, b: f32x4) -> f32x4 {
|
|||
/// `b`. Mask is split to 2 control bits each to index the element from inputs.
|
||||
#[inline(always)]
|
||||
#[target_feature = "+sse"]
|
||||
#[cfg_attr(test, assert_instr(shufps, mask = 3))]
|
||||
pub unsafe fn _mm_shuffle_ps(a: f32x4, b: f32x4, mask: i32) -> f32x4 {
|
||||
let mask = (mask & 0xFF) as u8;
|
||||
|
||||
|
|
@ -217,13 +218,6 @@ pub unsafe fn _mm_shuffle_ps(a: f32x4, b: f32x4, mask: i32) -> f32x4 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[target_feature = "+sse"]
|
||||
#[cfg_attr(test, assert_instr(shufps))]
|
||||
fn _test_mm_shuffle_ps(a: f32x4, b: f32x4) -> f32x4 {
|
||||
unsafe { _mm_shuffle_ps(a, b, 3) }
|
||||
}
|
||||
|
||||
/// Unpack and interleave single-precision (32-bit) floating-point elements
|
||||
/// from the higher half of `a` and `b`.
|
||||
#[inline(always)]
|
||||
|
|
|
|||
|
|
@ -5,3 +5,9 @@ authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
|||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "0.1", features = ["unstable"] }
|
||||
quote = { git = 'https://github.com/dtolnay/quote' }
|
||||
syn = { git = 'https://github.com/dtolnay/syn', features = ["full"] }
|
||||
synom = { git = 'https://github.com/dtolnay/syn' }
|
||||
|
|
|
|||
|
|
@ -10,62 +10,125 @@
|
|||
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate proc_macro2;
|
||||
extern crate proc_macro;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate synom;
|
||||
|
||||
use proc_macro::{TokenStream, Term, TokenNode, Delimiter};
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn assert_instr(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let name = find_name(item.clone());
|
||||
let tokens = attr.into_iter().collect::<Vec<_>>();
|
||||
if tokens.len() != 1 {
|
||||
panic!("expected #[assert_instr(foo)]");
|
||||
}
|
||||
let tokens = match tokens[0].kind {
|
||||
TokenNode::Group(Delimiter::Parenthesis, ref rest) => rest.clone(),
|
||||
_ => panic!("expected #[assert_instr(foo)]"),
|
||||
};
|
||||
let tokens = tokens.into_iter().collect::<Vec<_>>();
|
||||
if tokens.len() != 1 {
|
||||
panic!("expected #[assert_instr(foo)]");
|
||||
}
|
||||
let instr = match tokens[0].kind {
|
||||
TokenNode::Term(term) => term,
|
||||
_ => panic!("expected #[assert_instr(foo)]"),
|
||||
pub fn assert_instr(attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream)
|
||||
-> proc_macro::TokenStream
|
||||
{
|
||||
let invoc = syn::parse::<Invoc>(attr)
|
||||
.expect("expected #[assert_instr(instr, a = b, ...)]");
|
||||
let item = syn::parse::<syn::Item>(item).expect("must be attached to an item");
|
||||
let func = match item.node {
|
||||
syn::ItemKind::Fn(ref f) => f,
|
||||
_ => panic!("must be attached to a function"),
|
||||
};
|
||||
|
||||
let ignore = if cfg!(optimized) {
|
||||
""
|
||||
let instr = &invoc.instr;
|
||||
let maybe_ignore = if cfg!(optimized) {
|
||||
TokenStream::empty()
|
||||
} else {
|
||||
"#[ignore]"
|
||||
(quote! { #[ignore] }).into()
|
||||
};
|
||||
let test = format!("
|
||||
let name = &func.ident;
|
||||
let assert_name = syn::Ident::from(&format!("assert_{}", name.sym.as_str())[..]);
|
||||
let shim_name = syn::Ident::from(&format!("{}_shim", name.sym.as_str())[..]);
|
||||
let (to_test, test_name) = if invoc.args.len() == 0 {
|
||||
(TokenStream::empty(), &func.ident)
|
||||
} else {
|
||||
let mut inputs = Vec::new();
|
||||
let mut input_vals = Vec::new();
|
||||
let ret = &func.decl.output;
|
||||
for arg in func.decl.inputs.iter() {
|
||||
let capture = match **arg.item() {
|
||||
syn::FnArg::Captured(ref c) => c,
|
||||
_ => panic!("arguments must not have patterns"),
|
||||
};
|
||||
let ident = match capture.pat {
|
||||
syn::Pat::Ident(ref i) => &i.ident,
|
||||
_ => panic!("must have bare arguments"),
|
||||
};
|
||||
match invoc.args.iter().find(|a| a.0 == ident.sym.as_str()) {
|
||||
Some(&(_, ref tts)) => {
|
||||
input_vals.push(quote! { #tts });
|
||||
}
|
||||
None => {
|
||||
inputs.push(capture);
|
||||
input_vals.push(quote! { #ident });
|
||||
}
|
||||
};
|
||||
}
|
||||
let attrs = Append(&item.attrs);
|
||||
(quote! {
|
||||
#attrs
|
||||
unsafe fn #shim_name(#(#inputs),*) #ret {
|
||||
#name(#(#input_vals),*)
|
||||
}
|
||||
}.into(), &shim_name)
|
||||
};
|
||||
|
||||
let tts: TokenStream = quote! {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
{ignore}
|
||||
fn assert_instr_{name}() {{
|
||||
::stdsimd_test::assert({name} as usize,
|
||||
\"{name}\",
|
||||
\"{instr}\");
|
||||
}}
|
||||
", name = name.as_str(), instr = instr.as_str(), ignore = ignore);
|
||||
let test: TokenStream = test.parse().unwrap();
|
||||
#maybe_ignore
|
||||
fn #assert_name() {
|
||||
#to_test
|
||||
|
||||
item.into_iter().chain(test.into_iter()).collect()
|
||||
::stdsimd_test::assert(#test_name as usize,
|
||||
stringify!(#test_name),
|
||||
stringify!(#instr));
|
||||
}
|
||||
}.into();
|
||||
// why? necessary now to get tests to work?
|
||||
let tts: TokenStream = tts.to_string().parse().unwrap();
|
||||
|
||||
let tts: TokenStream = quote! {
|
||||
#item
|
||||
#tts
|
||||
}.into();
|
||||
tts.into()
|
||||
}
|
||||
|
||||
fn find_name(item: TokenStream) -> Term {
|
||||
let mut tokens = item.into_iter();
|
||||
while let Some(tok) = tokens.next() {
|
||||
if let TokenNode::Term(word) = tok.kind {
|
||||
if word.as_str() == "fn" {
|
||||
break
|
||||
}
|
||||
struct Invoc {
|
||||
instr: syn::Ident,
|
||||
args: Vec<(syn::Ident, syn::Expr)>,
|
||||
}
|
||||
|
||||
impl synom::Synom for Invoc {
|
||||
named!(parse -> Self, map!(parens!(do_parse!(
|
||||
instr: syn!(syn::Ident) >>
|
||||
args: many0!(do_parse!(
|
||||
syn!(syn::tokens::Comma) >>
|
||||
name: syn!(syn::Ident) >>
|
||||
syn!(syn::tokens::Eq) >>
|
||||
expr: syn!(syn::Expr) >>
|
||||
(name, expr)
|
||||
)) >>
|
||||
(Invoc {
|
||||
instr,
|
||||
args,
|
||||
})
|
||||
)), |p| p.0));
|
||||
}
|
||||
|
||||
struct Append<T>(T);
|
||||
|
||||
impl<T> quote::ToTokens for Append<T>
|
||||
where T: Clone + IntoIterator,
|
||||
T::Item: quote::ToTokens
|
||||
{
|
||||
fn to_tokens(&self, tokens: &mut quote::Tokens) {
|
||||
for item in self.0.clone() {
|
||||
item.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
match tokens.next().map(|t| t.kind) {
|
||||
Some(TokenNode::Term(word)) => word,
|
||||
_ => panic!("failed to find function name"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue