naked_function_macro/
asm.rs

1use quote::ToTokens;
2use syn::{
3    ext::IdentExt,
4    parenthesized,
5    parse::{Parse, ParseStream},
6    punctuated::Punctuated,
7    token::Paren,
8    Expr, Ident, ItemFn, Result, Stmt, Token,
9};
10
11pub mod kw {
12    syn::custom_keyword!(sym);
13    syn::custom_keyword!(options);
14    syn::custom_keyword!(out);
15    syn::custom_keyword!(lateout);
16    syn::custom_keyword!(inout);
17    syn::custom_keyword!(inlateout);
18    syn::custom_keyword!(clobber_abi);
19}
20
21/// Representation of one argument of the `asm!` macro.
22pub enum AsmOperand {
23    Template(Expr),
24    Const {
25        name: Option<(Ident, Token![=])>,
26        token: Token![const],
27        expr: Expr,
28    },
29    Sym {
30        name: Option<(Ident, Token![=])>,
31        token: kw::sym,
32        expr: Expr,
33    },
34    Options {
35        token: kw::options,
36        paren_token: Paren,
37        options: Punctuated<Ident, Token![,]>,
38    },
39}
40
41impl Parse for AsmOperand {
42    fn parse(input: ParseStream) -> Result<Self> {
43        if input.peek(kw::options) {
44            let token = input.parse::<kw::options>()?;
45            let content;
46            let paren_token = parenthesized!(content in input);
47            let options = content.parse_terminated(Ident::parse, Token![,])?;
48            return Ok(Self::Options {
49                token,
50                paren_token,
51                options,
52            });
53        }
54
55        let mut name = None;
56        if input.peek(Ident::peek_any) && input.peek2(Token![=]) {
57            let ident = input.call(Ident::parse_any)?;
58            let token = input.parse()?;
59            name = Some((ident, token));
60        }
61
62        if input.peek(kw::sym) {
63            let token = input.parse()?;
64            let expr = input.parse()?;
65            return Ok(Self::Sym { name, token, expr });
66        }
67
68        if input.peek(Token![const]) {
69            let token = input.parse()?;
70            let expr = input.parse()?;
71            return Ok(Self::Const { name, token, expr });
72        }
73
74        if input.peek(Token![in])
75            || input.peek(kw::out)
76            || input.peek(kw::lateout)
77            || input.peek(kw::inout)
78            || input.peek(kw::inlateout)
79        {
80            return Err(syn::Error::new(
81                input.span(),
82                "only `const` and `sym` operands may be used in naked functions",
83            ));
84        }
85
86        if input.peek(kw::clobber_abi) {
87            return Err(syn::Error::new(
88                input.span(),
89                "`clobber_abi` cannot be used in naked functions",
90            ));
91        }
92
93        // Assume anything else is a template string. global_asm! will properly
94        // validate this for us later.
95        if let Some((ident, _token)) = name {
96            bail!(ident, "invalid asm! syntax");
97        }
98        Ok(Self::Template(input.parse()?))
99    }
100}
101
102impl ToTokens for AsmOperand {
103    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
104        match self {
105            AsmOperand::Template(expr) => expr.to_tokens(tokens),
106            AsmOperand::Const { name, token, expr } => {
107                if let Some((ident, token)) = name {
108                    ident.to_tokens(tokens);
109                    token.to_tokens(tokens);
110                }
111                token.to_tokens(tokens);
112                expr.to_tokens(tokens);
113            }
114            AsmOperand::Sym { name, token, expr } => {
115                if let Some((ident, token)) = name {
116                    ident.to_tokens(tokens);
117                    token.to_tokens(tokens);
118                }
119                token.to_tokens(tokens);
120                expr.to_tokens(tokens);
121            }
122            AsmOperand::Options {
123                token,
124                paren_token,
125                options,
126            } => {
127                token.to_tokens(tokens);
128                paren_token.surround(tokens, |tokens| {
129                    options.to_tokens(tokens);
130                })
131            }
132        }
133    }
134}
135
136/// Extracts the `AsmOperand`s from the `asm!` in the body of the function.
137pub fn extract_asm(func: &ItemFn) -> Result<Punctuated<AsmOperand, Token![,]>> {
138    if func.block.stmts.len() != 1 {
139        bail!(
140            func,
141            "naked functions may only contain a single asm! statement"
142        );
143    }
144    let (mac, attrs) = match &func.block.stmts[0] {
145        Stmt::Macro(macro_) => (&macro_.mac, &macro_.attrs),
146        Stmt::Expr(Expr::Macro(macro_), _) => (&macro_.mac, &macro_.attrs),
147        _ => bail!(
148            func,
149            "naked functions may only contain a single asm! statement"
150        ),
151    };
152    if !attrs.is_empty() || !mac.path.is_ident("asm") {
153        bail!(
154            func,
155            "naked functions may only contain a single asm! statement"
156        );
157    }
158    mac.parse_body_with(Punctuated::parse_terminated)
159}