pulp-macro-0.1.1/.cargo_vcs_info.json0000644000000001500000000000100131060ustar { "git": { "sha1": "7e96a0ba34e5700ffe3939075c19c2b0c1257206" }, "path_in_vcs": "pulp-macro" }pulp-macro-0.1.1/Cargo.toml0000644000000015430000000000100111130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "pulp-macro" version = "0.1.1" authors = ["sarah <>"] description = "Safe generic simd" readme = "README.md" keywords = ["simd"] license = "MIT" repository = "https://github.com/sarah-ek/pulp/" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0.69" [dependencies.quote] version = "1.0.33" [dependencies.syn] version = "2.0.39" features = ["full"] pulp-macro-0.1.1/Cargo.toml.orig000064400000000000000000000005521046102023000145730ustar 00000000000000[package] name = "pulp-macro" version = "0.1.1" edition = "2021" authors = ["sarah <>"] description = "Safe generic simd" readme = "../README.md" repository = "https://github.com/sarah-ek/pulp/" license = "MIT" keywords = ["simd"] [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.69" quote = "1.0.33" syn = { version = "2.0.39", features = ["full"] } pulp-macro-0.1.1/README.md000064400000000000000000000040161046102023000131620ustar 00000000000000`pulp` is a safe abstraction over SIMD instructions, that allows you to write a function once and dispatch to equivalent vectorized versions based on the features detected at runtime. [![Documentation](https://docs.rs/pulp/badge.svg)](https://docs.rs/pulp) [![Crate](https://img.shields.io/crates/v/pulp.svg)](https://crates.io/crates/pulp) # Autovectorization example ```rust use pulp::Arch; let mut v = (0..1000).map(|i| i as f64).collect::>(); let arch = Arch::new(); arch.dispatch(|| { for x in &mut v { *x *= 2.0; } }); for (i, x) in v.into_iter().enumerate() { assert_eq!(x, 2.0 * i as f64); } ``` # Manual vectorization example ```rust use pulp::{Arch, Simd, WithSimd}; struct TimesThree<'a>(&'a mut [f64]); impl<'a> WithSimd for TimesThree<'a> { type Output = (); #[inline(always)] fn with_simd(self, simd: S) -> Self::Output { let v = self.0; let (head, tail) = S::f64s_as_mut_simd(v); let three = simd.f64s_splat(3.0); for x in head { *x = simd.f64s_mul(three, *x); } for x in tail { *x = *x * 3.0; } } } let mut v = (0..1000).map(|i| i as f64).collect::>(); let arch = Arch::new(); arch.dispatch(TimesThree(&mut v)); for (i, x) in v.into_iter().enumerate() { assert_eq!(x, 3.0 * i as f64); } ``` # Less boilerplate using `pulp::with_simd` Only available with the `macro` feature. Requires the first non-lifetime generic parameter, as well as the function's first input parameter to be the SIMD type. ```rust #[pulp::with_simd(sum = pulp::Arch::new())] #[inline(always)] fn sum_with_simd<'a, S: Simd>(simd: S, v: &'a mut [f64]) { let (head, tail) = S::f64s_as_mut_simd(v); let three = simd.f64s_splat(3.0); for x in head { *x = simd.f64s_mul(three, *x); } for x in tail { *x = *x * 3.0; } } let mut v = (0..1000).map(|i| i as f64).collect::>(); sum(&mut v); for (i, x) in v.into_iter().enumerate() { assert_eq!(x, 3.0 * i as f64); } ``` pulp-macro-0.1.1/src/lib.rs000064400000000000000000000146351046102023000136160ustar 00000000000000use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::punctuated::Punctuated; use syn::token::{Colon, PathSep}; use syn::{ ConstParam, FnArg, GenericParam, ItemFn, LifetimeParam, Pat, PatIdent, PatType, Path, PathSegment, Type, TypeParam, TypePath, }; #[proc_macro_attribute] pub fn with_simd( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let attr: TokenStream = attr.into(); let item: TokenStream = item.into(); let Ok(syn::Meta::NameValue(attr)) = syn::parse2::(attr.clone()) else { return quote! { ::core::compile_error!("pulp::with_simd expected function name and arch expression"); #item } .into(); }; let Some(name) = attr.path.get_ident() else { return quote! { ::core::compile_error!("pulp::with_simd expected function name and arch expression"); #item } .into(); }; let Ok(item) = syn::parse2::(item.clone()) else { return quote! { ::core::compile_error!("pulp::with_simd expected function"); #item } .into(); }; let ItemFn { attrs, vis, sig, block, } = item.clone(); let mut struct_generics = Vec::new(); let mut struct_field_names = Vec::new(); let mut struct_field_types = Vec::new(); let mut first_non_lifetime = usize::MAX; for (idx, param) in sig.generics.params.clone().into_pairs().enumerate() { let (param, _) = param.into_tuple(); match ¶m { syn::GenericParam::Lifetime(_) => {} _ => { if first_non_lifetime == usize::MAX { first_non_lifetime = idx; continue; } } } } let mut new_fn_sig = sig.clone(); new_fn_sig.generics.params = new_fn_sig .generics .params .into_iter() .enumerate() .filter(|(idx, _)| *idx != first_non_lifetime) .map(|(_, arg)| arg) .collect(); new_fn_sig.inputs = new_fn_sig .inputs .into_iter() .skip(1) .enumerate() .map(|(idx, arg)| { FnArg::Typed(PatType { attrs: Vec::new(), pat: Box::new(Pat::Ident(PatIdent { attrs: Vec::new(), by_ref: None, mutability: None, ident: Ident::new(&format!("__{idx}"), Span::call_site()), subpat: None, })), colon_token: Colon { spans: [Span::call_site()], }, ty: match arg { FnArg::Typed(ty) => ty.ty, FnArg::Receiver(_) => panic!(), }, }) }) .collect(); new_fn_sig.ident = name.clone(); let mut param_ty = Vec::new(); for (idx, param) in new_fn_sig.inputs.clone().into_pairs().enumerate() { let (param, _) = param.into_tuple(); let FnArg::Typed(param) = param.clone() else { panic!(); }; let name = *param.pat; let syn::Pat::Ident(name) = name else { panic!(); }; let anon_ty = Ident::new(&format!("__T{idx}"), Span::call_site()); struct_field_names.push(name.ident.clone()); let mut ty = Punctuated::<_, PathSep>::new(); ty.push_value(PathSegment { ident: anon_ty.clone(), arguments: syn::PathArguments::None, }); struct_field_types.push(Type::Path(TypePath { qself: None, path: Path { leading_colon: None, segments: ty, }, })); struct_generics.push(anon_ty); param_ty.push(*param.ty); } let output_ty = match sig.output.clone() { syn::ReturnType::Default => quote! { () }, syn::ReturnType::Type(_, ty) => quote! { #ty }, }; let fn_name = sig.ident.clone(); let arch = attr.value; let new_fn_generics = new_fn_sig.generics.clone(); let params = new_fn_generics.params.clone(); let generics = params.into_iter().collect::>(); let non_lt_generics_names = generics .iter() .map(|p| match p { GenericParam::Type(TypeParam { ident, .. }) | GenericParam::Const(ConstParam { ident, .. }) => { quote! { #ident, } } _ => quote! {}, }) .collect::>(); let generics_decl = generics .iter() .map(|p| match p { GenericParam::Lifetime(LifetimeParam { lifetime, colon_token, bounds, .. }) => { quote! { #lifetime #colon_token #bounds } } GenericParam::Type(TypeParam { ident, colon_token, bounds, .. }) => { quote! { #ident #colon_token #bounds } } GenericParam::Const(ConstParam { const_token, ident, colon_token, ty, .. }) => { quote! { #const_token #ident #colon_token #ty } } }) .collect::>(); let generics_where_clause = new_fn_generics.where_clause; let code = quote! { #(#attrs)* #vis #new_fn_sig { #[allow(non_camel_case_types)] struct #name<#(#struct_generics,)*> (#(#struct_field_types,)*); impl<#(#generics_decl,)*> ::pulp::WithSimd for #name< #(#param_ty,)* > #generics_where_clause { type Output = #output_ty; #[inline(always)] fn with_simd<__S: ::pulp::Simd>(self, __simd: __S) -> ::Output { let Self ( #(#struct_field_names,)* ) = self; #[allow(unused_unsafe)] unsafe { #fn_name::<__S, #(#non_lt_generics_names)* >(__simd, #(#struct_field_names,)*) } } } (#arch).dispatch( #name ( #(#struct_field_names,)* ) ) } #(#attrs)* #vis #sig #block }; code.into() }