Skip to content

Commit

Permalink
Add match_target macro
Browse files Browse the repository at this point in the history
  • Loading branch information
calebzulawski committed Dec 8, 2022
1 parent 29d6694 commit 99ba374
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 5 deletions.
5 changes: 5 additions & 0 deletions multiversion-macros/src/dispatcher.rs
Expand Up @@ -104,9 +104,14 @@ impl Dispatcher {
{ [$cfg:meta, $attr:meta] $($attached:tt)* } => { #[multiversion::target::target_cfg_attr_impl(#features, $cfg, $attr)] $($attached)* };
}

macro_rules! match_target {
{ $($arms:tt)* } => { multiversion::target::match_target_impl!{ #features $($arms)* } }
}

pub(crate) use inherit_target;
pub(crate) use target_cfg;
pub(crate) use target_cfg_attr;
pub(crate) use match_target;
}
#block
}
Expand Down
16 changes: 16 additions & 0 deletions multiversion-macros/src/lib.rs
Expand Up @@ -3,6 +3,7 @@ extern crate proc_macro;

mod cfg;
mod dispatcher;
mod match_target;
mod multiversion;
mod target;
mod util;
Expand Down Expand Up @@ -118,3 +119,18 @@ pub fn target_cfg_attr_impl(
}
.into()
}

#[proc_macro]
pub fn match_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = TokenStream::from(input);
quote! {
__multiversion::match_target!{ #input }
}
.into()
}

#[proc_macro]
pub fn match_target_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let match_target = parse_macro_input!(input as match_target::MatchTarget);
match_target.into_token_stream().into()
}
82 changes: 82 additions & 0 deletions multiversion-macros/src/match_target.rs
@@ -0,0 +1,82 @@
use crate::target::Target;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned,
Error, Expr, LitStr, Result,
};

pub struct MatchTarget {
features: LitStr,
arms: Vec<(Target, Expr)>,
default_target: Option<Expr>,
}

impl Parse for MatchTarget {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let features = input.parse()?;
let mut arms = Vec::new();
let mut default_target = None;

while !input.is_empty() {
let arm: syn::Arm = input.parse()?;
if !arm.attrs.is_empty() {
return Err(Error::new(arm.attrs[0].span(), "unexpected attribute"));
}
let pat = arm.pat;
if let Some(guard) = arm.guard {
return Err(Error::new(guard.0.span(), "unexpected guard"));
}
if pat == parse_quote!(_) {
default_target = Some(*arm.body);
if !input.is_empty() {
return Err(Error::new(input.span(), "unreachable targets"));
}
} else {
arms.push((parse_quote!(#pat), *arm.body));
}
}

Ok(MatchTarget {
features,
arms,
default_target,
})
}
}

impl ToTokens for MatchTarget {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut exprs = Vec::new();
let mut not_targets = Vec::new();
for (target, expr) in &self.arms {
let arch = target.arch();
let features = target.features();
let selected_features = &self.features;
let cfg = crate::cfg::transform(
parse_quote! { #selected_features, all(target_arch = #arch #(, target_feature = #features)*) },
);
exprs.push(quote! {
#[cfg(all(#cfg, not(any(#(#not_targets),*))))]
{ #expr }
});
not_targets.push(cfg);
}
let default_expr = if let Some(expr) = &self.default_target {
quote! { #expr }
} else {
Error::new(Span::call_site(), "no matching target").to_compile_error()
};

quote! {
{
#(#exprs)*
#[cfg(not(any(#(#not_targets),*)))]
#default_expr
}
}
.to_tokens(tokens)
}
}
26 changes: 23 additions & 3 deletions multiversion-macros/src/target.rs
@@ -1,6 +1,9 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_quote, Attribute, Error, ItemFn, Lit, LitStr, Result};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_quote, Attribute, Error, ItemFn, Lit, LitStr, Result,
};
use target_features::{Architecture, Feature};

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -147,6 +150,23 @@ impl std::convert::TryFrom<&Lit> for Target {
}
}

impl Parse for Target {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Target::parse(&input.parse()?)
}
}

impl ToTokens for Target {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut s = self.architecture.clone();
for feature in &self.features {
s.push('+');
s.push_str(feature);
}
LitStr::new(&s, Span::call_site()).to_tokens(tokens);
}
}

pub(crate) fn make_target_fn(target: LitStr, func: ItemFn) -> Result<TokenStream> {
let target = Target::parse(&target)?;
let target_arch = target.target_arch();
Expand Down
23 changes: 22 additions & 1 deletion multiversion/src/lib.rs
Expand Up @@ -179,8 +179,29 @@ pub mod target {
/// Equivalent to `#[cfg_attr]`, but considers `target_feature`s detected at runtime.
pub use multiversion_macros::target_cfg_attr;

/// Match the selected target.
///
/// Matching is done at compile time, as if by `#[cfg]`. Target matching considers both
/// detected features and statically-enabled features. Arms that do not match are not
/// compiled.
///
/// # Example
/// ```
/// use multiversion::{multiversion, target::match_target};
///
/// #[multiversion(targets = "simd")]
/// fn foo() {
/// match_target! {
/// "x86_64+avx" => println!("x86-64 with AVX"),
/// "aarch64+neon" => println!("AArch64 with Neon"),
/// _ => println!("another architecture"),
/// }
/// }
/// ```
pub use multiversion_macros::match_target;

#[doc(hidden)]
pub use multiversion_macros::{target_cfg_attr_impl, target_cfg_impl};
pub use multiversion_macros::{match_target_impl, target_cfg_attr_impl, target_cfg_impl};

#[doc(no_inline)]
pub use target_features::Target;
Expand Down
20 changes: 19 additions & 1 deletion multiversion/tests/cfg.rs
@@ -1,6 +1,6 @@
use multiversion::{
multiversion,
target::{selected_target, target_cfg, target_cfg_attr},
target::{match_target, selected_target, target_cfg, target_cfg_attr},
};

#[test]
Expand Down Expand Up @@ -48,3 +48,21 @@ fn cfg_attr() {

foo();
}

#[test]
fn match_target() {
#[multiversion(targets = "simd")]
fn foo() {
let match_avx = match_target! {
"x86_64+avx" => true,
_ => false,
};

let has_avx =
std::env::consts::ARCH == "x86_64" && selected_target!().supports_feature_str("avx");

assert_eq!(match_avx, has_avx);
}

foo();
}

0 comments on commit 99ba374

Please sign in to comment.