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:
Alex Crichton 2017-09-26 19:03:38 -07:00
parent 25cef3696a
commit 24f08cd458
4 changed files with 114 additions and 57 deletions

View file

@ -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)]

View file

@ -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)]

View file

@ -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' }

View file

@ -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"),
}
}