llvm_plugin_macros/
lib.rs

1use proc_macro::TokenStream;
2
3use proc_macro2::Span;
4use proc_macro2::TokenStream as TokenStream2;
5
6use quote::{format_ident, quote, quote_spanned};
7
8use syn::ItemFn;
9use syn::{AttributeArgs, Error};
10
11/// Macro for defining a new LLVM plugin.
12///
13/// This macro must be used on a function, and needs a `name` and `version`
14/// parameters.
15///
16/// The annotated function will be used as the plugin's entrypoint, and must
17/// take a `PassBuilder` as argument.
18///
19/// # Warning
20///
21/// This macro should be used on `cdylib` crates **only**. Also, since it generates
22/// an export symbol, it should be used **once** for the whole dylib being compiled.
23///
24/// # Example
25///
26/// ```
27/// # use llvm_plugin::PassBuilder;
28/// #[llvm_plugin::plugin(name = "plugin_name", version = "0.1")]
29/// fn plugin_registrar(builder: &mut PassBuilder) {
30///     builder.add_module_pipeline_parsing_callback(|name, pass_manager| {
31///         // add passes to the pass manager
32///         # todo!()
33///     });
34///
35///     builder.add_module_analysis_registration_callback(|analysis_manager| {
36///         // register analyses to the analysis manager
37///         # todo!()
38///     });
39/// }
40/// ```
41#[proc_macro_attribute]
42pub fn plugin(attrs: TokenStream, input: TokenStream) -> TokenStream {
43    match plugin_impl(attrs, input) {
44        Ok(ts) => ts.into(),
45        Err(e) => {
46            let msg = e.to_string();
47            quote_spanned! { e.span() => fn error() { std::compile_error!(#msg) } }.into()
48        }
49    }
50}
51
52fn plugin_impl(attrs: TokenStream, input: TokenStream) -> syn::Result<TokenStream2> {
53    let args = syn::parse_macro_input::parse(attrs)?;
54    let (name, version) = match parse_plugin_args(args) {
55        Some(parsed) => parsed?,
56        None => return Err(Error::new(Span::call_site(), "`plugin` attr missing args")),
57    };
58
59    let func = syn::parse::<ItemFn>(input)?;
60    let registrar_name = &func.sig.ident;
61    let registrar_name_sys = format_ident!("{}_sys", registrar_name);
62
63    let name = name + "\0";
64    let version = version + "\0";
65
66    Ok(quote! {
67        #func
68
69        extern "C" fn #registrar_name_sys(builder: *mut std::ffi::c_void) {
70            let mut builder = unsafe { llvm_plugin::PassBuilder::from_raw(builder) };
71            #registrar_name(&mut builder);
72        }
73
74        #[no_mangle]
75        extern "C" fn llvmGetPassPluginInfo() -> llvm_plugin::PassPluginLibraryInfo {
76            llvm_plugin::PassPluginLibraryInfo {
77                api_version: llvm_plugin::get_llvm_plugin_api_version__(),
78                plugin_name: #name.as_ptr(),
79                plugin_version: #version.as_ptr(),
80                plugin_registrar: #registrar_name_sys,
81            }
82        }
83    })
84}
85
86fn parse_plugin_args(args: AttributeArgs) -> Option<syn::Result<(String, String)>> {
87    let mut args_iter = args.iter();
88
89    let arg = args_iter.next()?;
90    let name = match arg {
91        syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
92            path,
93            lit: syn::Lit::Str(s),
94            ..
95        })) if path.is_ident("name") => s.value(),
96        _ => {
97            return Some(Err(Error::new_spanned(
98                arg,
99                "expected arg `name=\"value\"`",
100            )))
101        }
102    };
103
104    let arg = args_iter.next()?;
105    let version = match arg {
106        syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
107            path,
108            lit: syn::Lit::Str(s),
109            ..
110        })) if path.is_ident("version") => s.value(),
111        _ => {
112            return Some(Err(Error::new_spanned(
113                arg,
114                "expected arg `version=\"value\"`",
115            )))
116        }
117    };
118
119    Some(Ok((name, version)))
120}