rustc_parse/parser/
asm.rs

1use rustc_ast::{self as ast, AsmMacro};
2use rustc_span::{Span, Symbol, kw};
3
4use super::{ExpKeywordPair, ForceCollect, IdentIsRaw, Trailing, UsePreAttrPos};
5use crate::{PResult, Parser, errors, exp, token};
6
7/// An argument to one of the `asm!` macros. The argument is syntactically valid, but is otherwise
8/// not validated at all.
9pub struct AsmArg {
10    pub kind: AsmArgKind,
11    pub attributes: AsmAttrVec,
12    pub span: Span,
13}
14
15pub enum AsmArgKind {
16    Template(Box<ast::Expr>),
17    Operand(Option<Symbol>, ast::InlineAsmOperand),
18    Options(Vec<AsmOption>),
19    ClobberAbi(Vec<(Symbol, Span)>),
20}
21
22pub struct AsmOption {
23    pub symbol: Symbol,
24    pub span: Span,
25    // A bitset, with only the bit for this option's symbol set.
26    pub options: ast::InlineAsmOptions,
27    // Used when suggesting to remove an option.
28    pub span_with_comma: Span,
29}
30
31/// A parsed list of attributes that is not attached to any item.
32/// Used to check whether `asm!` arguments are configured out.
33pub struct AsmAttrVec(pub ast::AttrVec);
34
35impl AsmAttrVec {
36    fn parse<'a>(p: &mut Parser<'a>) -> PResult<'a, Self> {
37        let attrs = p.parse_outer_attributes()?;
38
39        p.collect_tokens(None, attrs, ForceCollect::No, |_, attrs| {
40            Ok((Self(attrs), Trailing::No, UsePreAttrPos::No))
41        })
42    }
43}
44impl ast::HasAttrs for AsmAttrVec {
45    // Follows `ast::Expr`.
46    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
47
48    fn attrs(&self) -> &[rustc_ast::Attribute] {
49        &self.0
50    }
51
52    fn visit_attrs(&mut self, f: impl FnOnce(&mut rustc_ast::AttrVec)) {
53        f(&mut self.0)
54    }
55}
56
57impl ast::HasTokens for AsmAttrVec {
58    fn tokens(&self) -> Option<&rustc_ast::tokenstream::LazyAttrTokenStream> {
59        None
60    }
61
62    fn tokens_mut(&mut self) -> Option<&mut Option<rustc_ast::tokenstream::LazyAttrTokenStream>> {
63        None
64    }
65}
66
67/// Used for better error messages when operand types are used that are not
68/// supported by the current macro (e.g. `in` or `out` for `global_asm!`)
69///
70/// returns
71///
72/// - `Ok(true)` if the current token matches the keyword, and was expected
73/// - `Ok(false)` if the current token does not match the keyword
74/// - `Err(_)` if the current token matches the keyword, but was not expected
75fn eat_operand_keyword<'a>(
76    p: &mut Parser<'a>,
77    exp: ExpKeywordPair,
78    asm_macro: AsmMacro,
79) -> PResult<'a, bool> {
80    if matches!(asm_macro, AsmMacro::Asm) {
81        Ok(p.eat_keyword(exp))
82    } else {
83        let span = p.token.span;
84        if p.eat_keyword_noexpect(exp.kw) {
85            // in gets printed as `r#in` otherwise
86            let symbol = if exp.kw == kw::In { "in" } else { exp.kw.as_str() };
87            Err(p.dcx().create_err(errors::AsmUnsupportedOperand {
88                span,
89                symbol,
90                macro_name: asm_macro.macro_name(),
91            }))
92        } else {
93            Ok(false)
94        }
95    }
96}
97
98fn parse_asm_operand<'a>(
99    p: &mut Parser<'a>,
100    asm_macro: AsmMacro,
101) -> PResult<'a, Option<ast::InlineAsmOperand>> {
102    let dcx = p.dcx();
103
104    Ok(Some(if eat_operand_keyword(p, exp!(In), asm_macro)? {
105        let reg = parse_reg(p)?;
106        if p.eat_keyword(exp!(Underscore)) {
107            let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
108            return Err(err);
109        }
110        let expr = p.parse_expr()?;
111        ast::InlineAsmOperand::In { reg, expr }
112    } else if eat_operand_keyword(p, exp!(Out), asm_macro)? {
113        let reg = parse_reg(p)?;
114        let expr = if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
115        ast::InlineAsmOperand::Out { reg, expr, late: false }
116    } else if eat_operand_keyword(p, exp!(Lateout), asm_macro)? {
117        let reg = parse_reg(p)?;
118        let expr = if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
119        ast::InlineAsmOperand::Out { reg, expr, late: true }
120    } else if eat_operand_keyword(p, exp!(Inout), asm_macro)? {
121        let reg = parse_reg(p)?;
122        if p.eat_keyword(exp!(Underscore)) {
123            let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
124            return Err(err);
125        }
126        let expr = p.parse_expr()?;
127        if p.eat(exp!(FatArrow)) {
128            let out_expr =
129                if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
130            ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false }
131        } else {
132            ast::InlineAsmOperand::InOut { reg, expr, late: false }
133        }
134    } else if eat_operand_keyword(p, exp!(Inlateout), asm_macro)? {
135        let reg = parse_reg(p)?;
136        if p.eat_keyword(exp!(Underscore)) {
137            let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
138            return Err(err);
139        }
140        let expr = p.parse_expr()?;
141        if p.eat(exp!(FatArrow)) {
142            let out_expr =
143                if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
144            ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true }
145        } else {
146            ast::InlineAsmOperand::InOut { reg, expr, late: true }
147        }
148    } else if eat_operand_keyword(p, exp!(Label), asm_macro)? {
149        let block = p.parse_block()?;
150        ast::InlineAsmOperand::Label { block }
151    } else if p.eat_keyword(exp!(Const)) {
152        let anon_const = p.parse_expr_anon_const()?;
153        ast::InlineAsmOperand::Const { anon_const }
154    } else if p.eat_keyword(exp!(Sym)) {
155        let expr = p.parse_expr()?;
156        let ast::ExprKind::Path(qself, path) = &expr.kind else {
157            let err = dcx.create_err(errors::AsmSymNoPath { span: expr.span });
158            return Err(err);
159        };
160        let sym =
161            ast::InlineAsmSym { id: ast::DUMMY_NODE_ID, qself: qself.clone(), path: path.clone() };
162        ast::InlineAsmOperand::Sym { sym }
163    } else {
164        return Ok(None);
165    }))
166}
167
168// Public for rustfmt.
169pub fn parse_asm_args<'a>(
170    p: &mut Parser<'a>,
171    sp: Span,
172    asm_macro: AsmMacro,
173) -> PResult<'a, Vec<AsmArg>> {
174    let dcx = p.dcx();
175
176    if p.token == token::Eof {
177        return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp }));
178    }
179
180    let mut args = Vec::new();
181
182    let attributes = AsmAttrVec::parse(p)?;
183    let first_template = p.parse_expr()?;
184    args.push(AsmArg {
185        span: first_template.span,
186        kind: AsmArgKind::Template(first_template),
187        attributes,
188    });
189
190    let mut allow_templates = true;
191
192    while p.token != token::Eof {
193        if !p.eat(exp!(Comma)) {
194            if allow_templates {
195                // After a template string, we always expect *only* a comma...
196                return Err(dcx.create_err(errors::AsmExpectedComma { span: p.token.span }));
197            } else {
198                // ...after that delegate to `expect` to also include the other expected tokens.
199                return Err(p.expect(exp!(Comma)).err().unwrap());
200            }
201        }
202
203        // Accept trailing commas.
204        if p.token == token::Eof {
205            break;
206        }
207
208        let attributes = AsmAttrVec::parse(p)?;
209        let span_start = p.token.span;
210
211        // Parse `clobber_abi`.
212        if p.eat_keyword(exp!(ClobberAbi)) {
213            allow_templates = false;
214
215            args.push(AsmArg {
216                kind: AsmArgKind::ClobberAbi(parse_clobber_abi(p)?),
217                span: span_start.to(p.prev_token.span),
218                attributes,
219            });
220
221            continue;
222        }
223
224        // Parse `options`.
225        if p.eat_keyword(exp!(Options)) {
226            allow_templates = false;
227
228            args.push(AsmArg {
229                kind: AsmArgKind::Options(parse_options(p, asm_macro)?),
230                span: span_start.to(p.prev_token.span),
231                attributes,
232            });
233
234            continue;
235        }
236
237        // Parse operand names.
238        let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
239            let (ident, _) = p.token.ident().unwrap();
240            p.bump();
241            p.expect(exp!(Eq))?;
242            allow_templates = false;
243            Some(ident.name)
244        } else {
245            None
246        };
247
248        if let Some(op) = parse_asm_operand(p, asm_macro)? {
249            allow_templates = false;
250
251            args.push(AsmArg {
252                span: span_start.to(p.prev_token.span),
253                kind: AsmArgKind::Operand(name, op),
254                attributes,
255            });
256        } else if allow_templates {
257            let template = p.parse_expr()?;
258            // If it can't possibly expand to a string, provide diagnostics here to include other
259            // things it could have been.
260            match template.kind {
261                ast::ExprKind::Lit(token_lit)
262                    if matches!(
263                        token_lit.kind,
264                        token::LitKind::Str | token::LitKind::StrRaw(_)
265                    ) => {}
266                ast::ExprKind::MacCall(..) => {}
267                _ => {
268                    let err = dcx.create_err(errors::AsmExpectedOther {
269                        span: template.span,
270                        is_inline_asm: matches!(asm_macro, AsmMacro::Asm),
271                    });
272                    return Err(err);
273                }
274            }
275
276            args.push(AsmArg {
277                span: template.span,
278                kind: AsmArgKind::Template(template),
279                attributes,
280            });
281        } else {
282            p.unexpected_any()?
283        }
284    }
285
286    Ok(args)
287}
288
289fn parse_options<'a>(p: &mut Parser<'a>, asm_macro: AsmMacro) -> PResult<'a, Vec<AsmOption>> {
290    p.expect(exp!(OpenParen))?;
291
292    let mut asm_options = Vec::new();
293
294    while !p.eat(exp!(CloseParen)) {
295        const OPTIONS: [(ExpKeywordPair, ast::InlineAsmOptions); ast::InlineAsmOptions::COUNT] = [
296            (exp!(Pure), ast::InlineAsmOptions::PURE),
297            (exp!(Nomem), ast::InlineAsmOptions::NOMEM),
298            (exp!(Readonly), ast::InlineAsmOptions::READONLY),
299            (exp!(PreservesFlags), ast::InlineAsmOptions::PRESERVES_FLAGS),
300            (exp!(Noreturn), ast::InlineAsmOptions::NORETURN),
301            (exp!(Nostack), ast::InlineAsmOptions::NOSTACK),
302            (exp!(MayUnwind), ast::InlineAsmOptions::MAY_UNWIND),
303            (exp!(AttSyntax), ast::InlineAsmOptions::ATT_SYNTAX),
304            (exp!(Raw), ast::InlineAsmOptions::RAW),
305        ];
306
307        'blk: {
308            for (exp, options) in OPTIONS {
309                // Gives a more accurate list of expected next tokens.
310                let kw_matched = if asm_macro.is_supported_option(options) {
311                    p.eat_keyword(exp)
312                } else {
313                    p.eat_keyword_noexpect(exp.kw)
314                };
315
316                if kw_matched {
317                    let span = p.prev_token.span;
318                    let span_with_comma =
319                        if p.token == token::Comma { span.to(p.token.span) } else { span };
320
321                    asm_options.push(AsmOption { symbol: exp.kw, span, options, span_with_comma });
322                    break 'blk;
323                }
324            }
325
326            return p.unexpected_any();
327        }
328
329        // Allow trailing commas.
330        if p.eat(exp!(CloseParen)) {
331            break;
332        }
333        p.expect(exp!(Comma))?;
334    }
335
336    Ok(asm_options)
337}
338
339fn parse_clobber_abi<'a>(p: &mut Parser<'a>) -> PResult<'a, Vec<(Symbol, Span)>> {
340    p.expect(exp!(OpenParen))?;
341
342    if p.eat(exp!(CloseParen)) {
343        return Err(p.dcx().create_err(errors::NonABI { span: p.token.span }));
344    }
345
346    let mut new_abis = Vec::new();
347    while !p.eat(exp!(CloseParen)) {
348        match p.parse_str_lit() {
349            Ok(str_lit) => {
350                new_abis.push((str_lit.symbol_unescaped, str_lit.span));
351            }
352            Err(opt_lit) => {
353                let span = opt_lit.map_or(p.token.span, |lit| lit.span);
354                return Err(p.dcx().create_err(errors::AsmExpectedStringLiteral { span }));
355            }
356        };
357
358        // Allow trailing commas
359        if p.eat(exp!(CloseParen)) {
360            break;
361        }
362        p.expect(exp!(Comma))?;
363    }
364
365    Ok(new_abis)
366}
367
368fn parse_reg<'a>(p: &mut Parser<'a>) -> PResult<'a, ast::InlineAsmRegOrRegClass> {
369    p.expect(exp!(OpenParen))?;
370    let result = match p.token.uninterpolate().kind {
371        token::Ident(name, IdentIsRaw::No) => ast::InlineAsmRegOrRegClass::RegClass(name),
372        token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => {
373            ast::InlineAsmRegOrRegClass::Reg(symbol)
374        }
375        _ => {
376            return Err(p.dcx().create_err(errors::ExpectedRegisterClassOrExplicitRegister {
377                span: p.token.span,
378            }));
379        }
380    };
381    p.bump();
382    p.expect(exp!(CloseParen))?;
383    Ok(result)
384}