Skip to main content

llvm_mca_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2#![feature(proc_macro_quote)]
3use proc_macro::{Span, TokenStream};
4use quote::{quote, ToTokens as _};
5use syn::{
6    parse::{Parse, ParseStream},
7    parse_quote, Item, ItemFn,
8};
9
10struct MacroArgs {
11    allow_inline: bool,
12}
13
14impl Parse for MacroArgs {
15    fn parse(input: ParseStream) -> syn::Result<Self> {
16        if input.is_empty() {
17            return Ok(Self {
18                allow_inline: false,
19            });
20        }
21
22        // TODO: Allow specifying regions names in arguments, to match
23        // `llvm_mca_begin!(...)` and `llvm_mca_end!(...)`
24        let arg = input.parse::<syn::Ident>()?;
25        if arg == "allow_inline" {
26            Ok(Self { allow_inline: true })
27        } else {
28            Err(syn::Error::new(arg.span(), "expected `allow_inline`"))
29        }
30    }
31}
32
33/// Wrap the body of a function with `LLVM-MCA-BEGIN` and `LLVM-MCA-END` markers.
34///
35/// The markers are inserted as inline assembly, after the function prologue and
36/// before the function epilogue.
37///
38/// # Examples
39///
40/// This:
41/// ```
42/// use llvm_mca_macros::llvm_mca;
43/// #[llvm_mca]
44/// fn quadruple(x: u32) -> u32 {
45///     let doubled = x + x;
46///     doubled + doubled
47/// }
48/// ```
49///
50/// is equivalent to:
51/// ```
52/// #[inline(never)]
53/// fn quadruple(x: u32) -> u32 {
54///     // emit LLVM-MCA-BEGIN marker
55///     let ret = {
56///         let doubled = x + x;
57///         doubled + doubled
58///     };
59///     // emit LLVM-MCA-END marker
60///     ret
61/// }
62/// ```
63///
64/// If inlining is desired, specify the `allow_inline` argument:
65/// ```
66/// use llvm_mca_macros::llvm_mca;
67/// #[llvm_mca(allow_inline)]
68/// fn quadruple(x: u32) -> u32 {
69///     let doubled = x + x;
70///     doubled + doubled
71/// }
72/// ```
73///
74/// which is equivalent to:
75/// ```
76/// fn quadruple(x: u32) -> u32 {
77///     // emit LLVM-MCA-BEGIN marker
78///     let ret = {
79///         let doubled = x + x;
80///         doubled + doubled
81///     };
82///     // emit LLVM-MCA-END marker
83///     ret
84/// }
85/// ```
86#[proc_macro_attribute]
87pub fn llvm_mca(attrs: TokenStream, input: TokenStream) -> TokenStream {
88    let function = match syn::parse(input) {
89        Ok(Item::Fn(function)) => function,
90        _ => {
91            return syn::Error::new(Span::call_site().into(), "expected function")
92                .to_compile_error()
93                .into()
94        }
95    };
96
97    // Take the original block and wedge it between the two markers. By default,
98    //
99    // `rustc` assumes that an `asm!(..)` block requires a stack frame so it
100    // includes stack-frame setup/teardown in the function prologue/epilogue. We
101    // can avoid this by adding the `options(nostack)` attribute to the
102    // `asm!(..)` block.
103    //
104    //Use the `options(nostack)` attribute to prevent this
105    let original_block = function.block;
106    let block = syn::parse(
107        quote! {{
108            unsafe {
109                std::arch::asm!(";# LLVM-MCA-BEGIN", options(nostack));
110            }
111            let ret = #original_block;
112            unsafe {
113                std::arch::asm!(";# LLVM-MCA-END", options(nostack));
114            }
115            ret
116        }}
117        .into(),
118    )
119    .unwrap();
120
121    let args = match syn::parse::<MacroArgs>(attrs) {
122        Ok(args) => args,
123        Err(err) => return err.to_compile_error().into(),
124    };
125
126    // Add `#[inline(never)]` to the function attributes if `allow_inline` is
127    // _not_ specified
128    let attrs = if args.allow_inline {
129        function.attrs
130    } else {
131        function
132            .attrs
133            .into_iter()
134            .chain(std::iter::once(parse_quote! {
135                #[inline(never)]
136            }))
137            .collect()
138    };
139
140    let result = ItemFn {
141        attrs,
142        block,
143        ..function
144    };
145
146    result.into_token_stream().into()
147}