rustc_parse/parser/
asm.rs

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