#![deny(rust_2018_idioms)] #[macro_use] extern crate quote; #[macro_use] extern crate syn; use proc_macro::TokenStream; use std::{fs::File, io::Read, path::Path}; use syn::ext::IdentExt; #[proc_macro] pub fn x86_functions(input: TokenStream) -> TokenStream { functions(input, &["core_arch/src/x86", "core_arch/src/x86_64"]) } #[proc_macro] pub fn arm_functions(input: TokenStream) -> TokenStream { functions( input, &[ "core_arch/src/arm", "core_arch/src/aarch64", "core_arch/src/arm_shared/neon", ], ) } #[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"); let mut files = Vec::new(); for dir in dirs { walk(&root.join(dir), &mut files); } assert!(!files.is_empty()); let mut functions = Vec::new(); for &mut (ref mut file, ref path) in &mut files { for mut item in file.items.drain(..) { match item { syn::Item::Fn(f) => functions.push((f, path)), syn::Item::Mod(ref mut m) => { if let Some(ref mut m) = m.content { for i in m.1.drain(..) { if let syn::Item::Fn(f) = i { functions.push((f, path)) } } } } _ => (), } } } assert!(!functions.is_empty()); let mut tests = std::collections::HashSet::::new(); for f in &functions { let id = format!("{}", f.0.sig.ident); if id.starts_with("test_") { tests.insert(id); } } assert!(!tests.is_empty()); functions.retain(|&(ref f, _)| { if let syn::Visibility::Public(_) = f.vis { if f.sig.unsafety.is_some() { return true; } } false }); assert!(!functions.is_empty()); let input = proc_macro2::TokenStream::from(input); let functions = functions .iter() .map(|&(ref f, path)| { let name = &f.sig.ident; // println!("{}", name); let mut arguments = Vec::new(); let mut const_arguments = Vec::new(); for input in f.sig.inputs.iter() { let ty = match *input { syn::FnArg::Typed(ref c) => &c.ty, _ => panic!("invalid argument on {}", name), }; arguments.push(to_type(ty)); } for generic in f.sig.generics.params.iter() { let ty = match *generic { syn::GenericParam::Const(ref c) => &c.ty, _ => panic!("invalid generic argument on {}", name), }; const_arguments.push(to_type(ty)); } let ret = match f.sig.output { syn::ReturnType::Default => quote! { None }, syn::ReturnType::Type(_, ref t) => { let ty = to_type(t); quote! { Some(#ty) } } }; let instrs = find_instrs(&f.attrs); let target_feature = if let Some(i) = find_target_feature(&f.attrs) { quote! { Some(#i) } } else { quote! { None } }; let required_const = find_required_const("rustc_args_required_const", &f.attrs); let mut legacy_const_generics = find_required_const("rustc_legacy_const_generics", &f.attrs); if !required_const.is_empty() && !legacy_const_generics.is_empty() { panic!( "Can't have both #[rustc_args_required_const] and \ #[rustc_legacy_const_generics]" ); } // The list of required consts, used to verify the arguments, comes from either the // `rustc_args_required_const` or the `rustc_legacy_const_generics` attribute. let required_const = if required_const.is_empty() { legacy_const_generics.clone() } else { required_const }; legacy_const_generics.sort(); for (idx, ty) in legacy_const_generics .into_iter() .zip(const_arguments.into_iter()) { arguments.insert(idx, ty); } // strip leading underscore from fn name when building a test // _mm_foo -> mm_foo such that the test name is test_mm_foo. let test_name_string = format!("{}", name); let mut test_name_id = test_name_string.as_str(); while test_name_id.starts_with('_') { test_name_id = &test_name_id[1..]; } let has_test = tests.contains(&format!("test_{}", test_name_id)); quote! { Function { name: stringify!(#name), arguments: &[#(#arguments),*], ret: #ret, target_feature: #target_feature, instrs: &[#(#instrs),*], file: stringify!(#path), required_const: &[#(#required_const),*], has_test: #has_test, } } }) .collect::>(); let ret = quote! { #input: &[Function] = &[#(#functions),*]; }; // println!("{}", ret); ret.into() } 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 }, "__m128bh" => quote! { &M128BH }, "__m128d" => quote! { &M128D }, "__m128i" => quote! { &M128I }, "__m256" => quote! { &M256 }, "__m256bh" => quote! { &M256BH }, "__m256d" => quote! { &M256D }, "__m256i" => quote! { &M256I }, "__m512" => quote! { &M512 }, "__m512bh" => quote! { &M512BH }, "__m512d" => quote! { &M512D }, "__m512i" => quote! { &M512I }, "__mmask8" => quote! { &MMASK8 }, "__mmask16" => quote! { &MMASK16 }, "__mmask32" => quote! { &MMASK32 }, "__mmask64" => quote! { &MMASK64 }, "_MM_CMPINT_ENUM" => quote! { &MM_CMPINT_ENUM }, "_MM_MANTISSA_NORM_ENUM" => quote! { &MM_MANTISSA_NORM_ENUM }, "_MM_MANTISSA_SIGN_ENUM" => quote! { &MM_MANTISSA_SIGN_ENUM }, "_MM_PERM_ENUM" => quote! { &MM_PERM_ENUM }, "__m64" => quote! { &M64 }, "bool" => quote! { &BOOL }, "f32" => quote! { &F32 }, "f64" => quote! { &F64 }, "i16" => quote! { &I16 }, "i32" => quote! { &I32 }, "i64" => quote! { &I64 }, "i8" => quote! { &I8 }, "u16" => quote! { &U16 }, "u32" => quote! { &U32 }, "u64" => quote! { &U64 }, "u128" => quote! { &U128 }, "u8" => quote! { &U8 }, "p8" => quote! { &P8 }, "p16" => quote! { &P16 }, "Ordering" => quote! { &ORDERING }, "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 }, "int16x4x2_t" => quote! { &I16X4X2 }, "int16x4x3_t" => quote! { &I16X4X3 }, "int16x4x4_t" => quote! { &I16X4X4 }, "int16x8_t" => quote! { &I16X8 }, "int16x8x2_t" => quote! { &I16X8X2 }, "int16x8x3_t" => quote! { &I16X8X3 }, "int16x8x4_t" => quote! { &I16X8X4 }, "int32x2_t" => quote! { &I32X2 }, "int32x2x2_t" => quote! { &I32X2X2 }, "int32x2x3_t" => quote! { &I32X2X3 }, "int32x2x4_t" => quote! { &I32X2X4 }, "int32x4_t" => quote! { &I32X4 }, "int32x4x2_t" => quote! { &I32X4X2 }, "int32x4x3_t" => quote! { &I32X4X3 }, "int32x4x4_t" => quote! { &I32X4X4 }, "int64x1_t" => quote! { &I64X1 }, "int64x1x2_t" => quote! { &I64X1X2 }, "int64x1x3_t" => quote! { &I64X1X3 }, "int64x1x4_t" => quote! { &I64X1X4 }, "int64x2_t" => quote! { &I64X2 }, "int64x2x2_t" => quote! { &I64X2X2 }, "int64x2x3_t" => quote! { &I64X2X3 }, "int64x2x4_t" => quote! { &I64X2X4 }, "uint8x8_t" => quote! { &U8X8 }, "uint8x4_t" => quote! { &U8X4 }, "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 }, "uint16x4x2_t" => quote! { &U16X4X2 }, "uint16x4x3_t" => quote! { &U16X4X3 }, "uint16x4x4_t" => quote! { &U16X4X4 }, "uint16x8_t" => quote! { &U16X8 }, "uint16x8x2_t" => quote! { &U16X8X2 }, "uint16x8x3_t" => quote! { &U16X8X3 }, "uint16x8x4_t" => quote! { &U16X8X4 }, "uint32x2_t" => quote! { &U32X2 }, "uint32x2x2_t" => quote! { &U32X2X2 }, "uint32x2x3_t" => quote! { &U32X2X3 }, "uint32x2x4_t" => quote! { &U32X2X4 }, "uint32x4_t" => quote! { &U32X4 }, "uint32x4x2_t" => quote! { &U32X4X2 }, "uint32x4x3_t" => quote! { &U32X4X3 }, "uint32x4x4_t" => quote! { &U32X4X4 }, "uint64x1_t" => quote! { &U64X1 }, "uint64x1x2_t" => quote! { &U64X1X2 }, "uint64x1x3_t" => quote! { &U64X1X3 }, "uint64x1x4_t" => quote! { &U64X1X4 }, "uint64x2_t" => quote! { &U64X2 }, "uint64x2x2_t" => quote! { &U64X2X2 }, "uint64x2x3_t" => quote! { &U64X2X3 }, "uint64x2x4_t" => quote! { &U64X2X4 }, "float32x2_t" => quote! { &F32X2 }, "float32x2x2_t" => quote! { &F32X2X2 }, "float32x2x3_t" => quote! { &F32X2X3 }, "float32x2x4_t" => quote! { &F32X2X4 }, "float32x4_t" => quote! { &F32X4 }, "float32x4x2_t" => quote! { &F32X4X2 }, "float32x4x3_t" => quote! { &F32X4X3 }, "float32x4x4_t" => quote! { &F32X4X4 }, "float64x1_t" => quote! { &F64X1 }, "float64x1x2_t" => quote! { &F64X1X2 }, "float64x1x3_t" => quote! { &F64X1X3 }, "float64x1x4_t" => quote! { &F64X1X4 }, "float64x2_t" => quote! { &F64X2 }, "float64x2x2_t" => quote! { &F64X2X2 }, "float64x2x3_t" => quote! { &F64X2X3 }, "float64x2x4_t" => quote! { &F64X2X4 }, "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 }, "p64" => quote! { &P64 }, "poly64x1_t" => quote! { &POLY64X1 }, "poly64x2_t" => quote! { &POLY64X2 }, "poly8x16_t" => quote! { &POLY8X16 }, "poly16x4_t" => quote! { &POLY16X4 }, "poly16x4x2_t" => quote! { &P16X4X2 }, "poly16x4x3_t" => quote! { &P16X4X3 }, "poly16x4x4_t" => quote! { &P16X4X4 }, "poly16x8_t" => quote! { &POLY16X8 }, "poly16x8x2_t" => quote! { &P16X8X2 }, "poly16x8x3_t" => quote! { &P16X8X3 }, "poly16x8x4_t" => quote! { &P16X8X4 }, "poly64x1x2_t" => quote! { &P64X1X2 }, "poly64x1x3_t" => quote! { &P64X1X3 }, "poly64x1x4_t" => quote! { &P64X1X4 }, "poly64x2x2_t" => quote! { &P64X2X2 }, "poly64x2x3_t" => quote! { &P64X2X3 }, "poly64x2x4_t" => quote! { &P64X2X4 }, "p128" => quote! { &P128 }, "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!("unsupported type: \"{}\"", s), }, syn::Type::Ptr(syn::TypePtr { ref elem, ref mutability, .. }) | syn::Type::Reference(syn::TypeReference { ref elem, ref mutability, .. }) => { // Both pointers and references can have a mut token (*mut and &mut) if mutability.is_some() { let tokens = to_type(&elem); quote! { &Type::MutPtr(#tokens) } } else { // If they don't (*const or &) then they are "const" let tokens = to_type(&elem); quote! { &Type::ConstPtr(#tokens) } } } syn::Type::Slice(_) => panic!("unsupported slice"), syn::Type::Array(_) => panic!("unsupported array"), syn::Type::Tuple(_) => quote! { &TUPLE }, syn::Type::Never(_) => quote! { &NEVER }, _ => panic!("unsupported type"), } } fn extract_path_ident(path: &syn::Path) -> syn::Ident { if path.leading_colon.is_some() { panic!("unsupported leading colon in path") } if path.segments.len() != 1 { panic!("unsupported path that needs name resolution") } match path.segments.first().expect("segment not found").arguments { syn::PathArguments::None => {} _ => panic!("unsupported path that has path arguments"), } path.segments .first() .expect("segment not found") .ident .clone() } fn walk(root: &Path, files: &mut Vec<(syn::File, String)>) { for file in root.read_dir().unwrap() { let file = file.unwrap(); if file.file_type().unwrap().is_dir() { walk(&file.path(), files); continue; } let path = file.path(); if path.extension().and_then(std::ffi::OsStr::to_str) != Some("rs") { continue; } if path.file_name().and_then(std::ffi::OsStr::to_str) == Some("test.rs") { continue; } let mut contents = String::new(); File::open(&path) .unwrap_or_else(|_| panic!("can't open file at path: {}", path.display())) .read_to_string(&mut contents) .expect("failed to read file to string"); files.push(( syn::parse_str::(&contents).expect("failed to parse"), path.display().to_string(), )); } } fn find_instrs(attrs: &[syn::Attribute]) -> Vec { struct AssertInstr { instr: String, } // A small custom parser to parse out the instruction in `assert_instr`. // // TODO: should probably just reuse `Invoc` from the `assert-instr-macro` // crate. impl syn::parse::Parse for AssertInstr { fn parse(content: syn::parse::ParseStream<'_>) -> syn::Result { let input; parenthesized!(input in content); let _ = input.parse::()?; let _ = input.parse::()?; let ident = input.parse::()?; if ident != "assert_instr" { return Err(input.error("expected `assert_instr`")); } let instrs; parenthesized!(instrs in input); let mut instr = String::new(); while !instrs.is_empty() { if let Ok(lit) = instrs.parse::() { instr.push_str(&lit.value()); } else if let Ok(ident) = instrs.call(syn::Ident::parse_any) { instr.push_str(&ident.to_string()); } else if instrs.parse::().is_ok() { instr.push('.'); } else if instrs.parse::().is_ok() { // consume everything remaining drop(instrs.parse::()); break; } else { return Err(input.error("failed to parse instruction")); } } Ok(Self { instr }) } } attrs .iter() .filter(|a| a.path.is_ident("cfg_attr")) .filter_map(|a| { syn::parse2::(a.tokens.clone()) .ok() .map(|a| a.instr) }) .collect() } fn find_target_feature(attrs: &[syn::Attribute]) -> Option { attrs .iter() .flat_map(|a| { if let Ok(a) = a.parse_meta() { if let syn::Meta::List(i) = a { if i.path.is_ident("target_feature") { return i.nested; } } } syn::punctuated::Punctuated::new() }) .filter_map(|nested| match nested { syn::NestedMeta::Meta(m) => Some(m), syn::NestedMeta::Lit(_) => None, }) .find_map(|m| match m { syn::Meta::NameValue(ref i) if i.path.is_ident("enable") => Some(i.clone().lit), _ => None, }) } fn find_required_const(name: &str, attrs: &[syn::Attribute]) -> Vec { attrs .iter() .flat_map(|a| { if a.path.segments[0].ident == name { syn::parse::(a.tokens.clone().into()) .unwrap() .args } else { Vec::new() } }) .collect() } struct RustcArgsRequiredConst { args: Vec, } impl syn::parse::Parse for RustcArgsRequiredConst { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let content; parenthesized!(content in input); let list = syn::punctuated::Punctuated::::parse_terminated(&content)?; Ok(Self { args: list .into_iter() .map(|a| a.base10_parse::()) .collect::>()?, }) } }