rustc_builtin_macros/
asm.rs

1use rustc_ast::tokenstream::TokenStream;
2use rustc_ast::{AsmMacro, token};
3use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
4use rustc_errors::PResult;
5use rustc_expand::base::*;
6use rustc_index::bit_set::GrowableBitSet;
7use rustc_parse::parser::asm::*;
8use rustc_session::lint;
9use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, sym};
10use rustc_target::asm::InlineAsmArch;
11use smallvec::smallvec;
12use {rustc_ast as ast, rustc_parse_format as parse};
13
14use crate::errors;
15use crate::util::{ExprToSpannedString, expr_to_spanned_string};
16
17/// Validated assembly arguments, ready for macro expansion.
18struct ValidatedAsmArgs {
19    pub templates: Vec<Box<ast::Expr>>,
20    pub operands: Vec<(ast::InlineAsmOperand, Span)>,
21    named_args: FxIndexMap<Symbol, usize>,
22    reg_args: GrowableBitSet<usize>,
23    pub clobber_abis: Vec<(Symbol, Span)>,
24    options: ast::InlineAsmOptions,
25    pub options_spans: Vec<Span>,
26}
27
28fn parse_args<'a>(
29    ecx: &ExtCtxt<'a>,
30    sp: Span,
31    tts: TokenStream,
32    asm_macro: AsmMacro,
33) -> PResult<'a, ValidatedAsmArgs> {
34    let args = parse_asm_args(&mut ecx.new_parser_from_tts(tts), sp, asm_macro)?;
35    validate_asm_args(ecx, asm_macro, args)
36}
37
38fn validate_asm_args<'a>(
39    ecx: &ExtCtxt<'a>,
40    asm_macro: AsmMacro,
41    args: Vec<AsmArg>,
42) -> PResult<'a, ValidatedAsmArgs> {
43    let dcx = ecx.dcx();
44
45    let strip_unconfigured = rustc_expand::config::StripUnconfigured {
46        sess: ecx.sess,
47        features: Some(ecx.ecfg.features),
48        config_tokens: false,
49        lint_node_id: ecx.current_expansion.lint_node_id,
50    };
51
52    let mut validated = ValidatedAsmArgs {
53        templates: vec![],
54        operands: vec![],
55        named_args: Default::default(),
56        reg_args: Default::default(),
57        clobber_abis: Vec::new(),
58        options: ast::InlineAsmOptions::empty(),
59        options_spans: vec![],
60    };
61
62    let mut allow_templates = true;
63
64    for arg in args {
65        for attr in arg.attributes.0.iter() {
66            if !matches!(attr.name(), Some(sym::cfg | sym::cfg_attr)) {
67                ecx.dcx().emit_err(errors::AsmAttributeNotSupported { span: attr.span() });
68            }
69        }
70
71        // Skip arguments that are configured out.
72        if strip_unconfigured.configure(arg.attributes).is_none() {
73            continue;
74        }
75
76        match arg.kind {
77            AsmArgKind::Template(template) => {
78                // The error for the first template is delayed.
79                if !allow_templates {
80                    match template.kind {
81                        ast::ExprKind::Lit(token_lit)
82                            if matches!(
83                                token_lit.kind,
84                                token::LitKind::Str | token::LitKind::StrRaw(_)
85                            ) => {}
86                        ast::ExprKind::MacCall(..) => {}
87                        _ => {
88                            let err = dcx.create_err(errors::AsmExpectedOther {
89                                span: template.span,
90                                is_inline_asm: matches!(asm_macro, AsmMacro::Asm),
91                            });
92                            return Err(err);
93                        }
94                    }
95                }
96
97                validated.templates.push(template);
98            }
99            AsmArgKind::Operand(name, op) => {
100                allow_templates = false;
101
102                let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_)));
103                let span = arg.span;
104                let slot = validated.operands.len();
105                validated.operands.push((op, span));
106
107                // Validate the order of named, positional & explicit register operands and
108                // clobber_abi/options. We do this at the end once we have the full span
109                // of the argument available.
110
111                if explicit_reg {
112                    if name.is_some() {
113                        dcx.emit_err(errors::AsmExplicitRegisterName { span });
114                    }
115                    validated.reg_args.insert(slot);
116                } else if let Some(name) = name {
117                    if let Some(&prev) = validated.named_args.get(&name) {
118                        dcx.emit_err(errors::AsmDuplicateArg {
119                            span,
120                            name,
121                            prev: validated.operands[prev].1,
122                        });
123                        continue;
124                    }
125                    validated.named_args.insert(name, slot);
126                } else if !validated.named_args.is_empty() || !validated.reg_args.is_empty() {
127                    let named =
128                        validated.named_args.values().map(|p| validated.operands[*p].1).collect();
129                    let explicit =
130                        validated.reg_args.iter().map(|p| validated.operands[p].1).collect();
131
132                    dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit });
133                }
134            }
135            AsmArgKind::Options(new_options) => {
136                allow_templates = false;
137
138                for asm_option in new_options {
139                    let AsmOption { span, symbol, span_with_comma, options } = asm_option;
140
141                    if !asm_macro.is_supported_option(options) {
142                        // Tool-only output.
143                        dcx.emit_err(errors::AsmUnsupportedOption {
144                            span,
145                            symbol,
146                            span_with_comma,
147                            macro_name: asm_macro.macro_name(),
148                        });
149                    } else if validated.options.contains(options) {
150                        // Tool-only output.
151                        dcx.emit_err(errors::AsmOptAlreadyprovided {
152                            span,
153                            symbol,
154                            span_with_comma,
155                        });
156                    } else {
157                        validated.options |= asm_option.options;
158                    }
159                }
160
161                validated.options_spans.push(arg.span);
162            }
163            AsmArgKind::ClobberAbi(new_abis) => {
164                allow_templates = false;
165
166                match &new_abis[..] {
167                    // This should have errored above during parsing.
168                    [] => unreachable!(),
169                    [(abi, _span)] => validated.clobber_abis.push((*abi, arg.span)),
170                    _ => validated.clobber_abis.extend(new_abis),
171                }
172            }
173        }
174    }
175
176    if validated.options.contains(ast::InlineAsmOptions::NOMEM)
177        && validated.options.contains(ast::InlineAsmOptions::READONLY)
178    {
179        let spans = validated.options_spans.clone();
180        dcx.emit_err(errors::AsmMutuallyExclusive { spans, opt1: "nomem", opt2: "readonly" });
181    }
182    if validated.options.contains(ast::InlineAsmOptions::PURE)
183        && validated.options.contains(ast::InlineAsmOptions::NORETURN)
184    {
185        let spans = validated.options_spans.clone();
186        dcx.emit_err(errors::AsmMutuallyExclusive { spans, opt1: "pure", opt2: "noreturn" });
187    }
188    if validated.options.contains(ast::InlineAsmOptions::PURE)
189        && !validated
190            .options
191            .intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY)
192    {
193        let spans = validated.options_spans.clone();
194        dcx.emit_err(errors::AsmPureCombine { spans });
195    }
196
197    let mut have_real_output = false;
198    let mut outputs_sp = vec![];
199    let mut regclass_outputs = vec![];
200    let mut labels_sp = vec![];
201    for (op, op_sp) in &validated.operands {
202        match op {
203            ast::InlineAsmOperand::Out { reg, expr, .. }
204            | ast::InlineAsmOperand::SplitInOut { reg, out_expr: expr, .. } => {
205                outputs_sp.push(*op_sp);
206                have_real_output |= expr.is_some();
207                if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
208                    regclass_outputs.push(*op_sp);
209                }
210            }
211            ast::InlineAsmOperand::InOut { reg, .. } => {
212                outputs_sp.push(*op_sp);
213                have_real_output = true;
214                if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
215                    regclass_outputs.push(*op_sp);
216                }
217            }
218            ast::InlineAsmOperand::Label { .. } => {
219                labels_sp.push(*op_sp);
220            }
221            _ => {}
222        }
223    }
224    if validated.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output {
225        dcx.emit_err(errors::AsmPureNoOutput { spans: validated.options_spans.clone() });
226    }
227    if validated.options.contains(ast::InlineAsmOptions::NORETURN)
228        && !outputs_sp.is_empty()
229        && labels_sp.is_empty()
230    {
231        let err = dcx.create_err(errors::AsmNoReturn { outputs_sp });
232        // Bail out now since this is likely to confuse MIR
233        return Err(err);
234    }
235    if validated.options.contains(ast::InlineAsmOptions::MAY_UNWIND) && !labels_sp.is_empty() {
236        dcx.emit_err(errors::AsmMayUnwind { labels_sp });
237    }
238
239    if !validated.clobber_abis.is_empty() {
240        match asm_macro {
241            AsmMacro::GlobalAsm | AsmMacro::NakedAsm => {
242                let err = dcx.create_err(errors::AsmUnsupportedClobberAbi {
243                    spans: validated.clobber_abis.iter().map(|(_, span)| *span).collect(),
244                    macro_name: asm_macro.macro_name(),
245                });
246
247                // Bail out now since this is likely to confuse later stages
248                return Err(err);
249            }
250            AsmMacro::Asm => {
251                if !regclass_outputs.is_empty() {
252                    dcx.emit_err(errors::AsmClobberNoReg {
253                        spans: regclass_outputs,
254                        clobbers: validated.clobber_abis.iter().map(|(_, span)| *span).collect(),
255                    });
256                }
257            }
258        }
259    }
260
261    Ok(validated)
262}
263
264fn expand_preparsed_asm(
265    ecx: &mut ExtCtxt<'_>,
266    asm_macro: AsmMacro,
267    args: ValidatedAsmArgs,
268) -> ExpandResult<Result<ast::InlineAsm, ErrorGuaranteed>, ()> {
269    let mut template = vec![];
270    // Register operands are implicitly used since they are not allowed to be
271    // referenced in the template string.
272    let mut used = vec![false; args.operands.len()];
273    for pos in args.reg_args.iter() {
274        used[pos] = true;
275    }
276    let named_pos: FxHashMap<usize, Symbol> =
277        args.named_args.iter().map(|(&sym, &idx)| (idx, sym)).collect();
278    let mut line_spans = Vec::with_capacity(args.templates.len());
279    let mut curarg = 0;
280
281    let mut template_strs = Vec::with_capacity(args.templates.len());
282
283    for (i, template_expr) in args.templates.into_iter().enumerate() {
284        if i != 0 {
285            template.push(ast::InlineAsmTemplatePiece::String("\n".into()));
286        }
287
288        let msg = "asm template must be a string literal";
289        let template_sp = template_expr.span;
290        let template_is_mac_call = matches!(template_expr.kind, ast::ExprKind::MacCall(_));
291        let ExprToSpannedString {
292            symbol: template_str,
293            style: template_style,
294            span: template_span,
295            ..
296        } = {
297            let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, template_expr, msg) else {
298                return ExpandResult::Retry(());
299            };
300            match mac {
301                Ok(template_part) => template_part,
302                Err(err) => {
303                    return ExpandResult::Ready(Err(match err {
304                        Ok((err, _)) => err.emit(),
305                        Err(guar) => guar,
306                    }));
307                }
308            }
309        };
310
311        let str_style = match template_style {
312            ast::StrStyle::Cooked => None,
313            ast::StrStyle::Raw(raw) => Some(raw as usize),
314        };
315
316        let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok();
317        template_strs.push((
318            template_str,
319            template_snippet.as_deref().map(Symbol::intern),
320            template_sp,
321        ));
322        let template_str = template_str.as_str();
323
324        if let Some(InlineAsmArch::X86 | InlineAsmArch::X86_64) = ecx.sess.asm_arch {
325            let find_span = |needle: &str| -> Span {
326                if let Some(snippet) = &template_snippet {
327                    if let Some(pos) = snippet.find(needle) {
328                        let end = pos
329                            + snippet[pos..]
330                                .find(|c| matches!(c, '\n' | ';' | '\\' | '"'))
331                                .unwrap_or(snippet[pos..].len() - 1);
332                        let inner = InnerSpan::new(pos, end);
333                        return template_sp.from_inner(inner);
334                    }
335                }
336                template_sp
337            };
338
339            if template_str.contains(".intel_syntax") {
340                ecx.psess().buffer_lint(
341                    lint::builtin::BAD_ASM_STYLE,
342                    find_span(".intel_syntax"),
343                    ecx.current_expansion.lint_node_id,
344                    errors::AvoidIntelSyntax,
345                );
346            }
347            if template_str.contains(".att_syntax") {
348                ecx.psess().buffer_lint(
349                    lint::builtin::BAD_ASM_STYLE,
350                    find_span(".att_syntax"),
351                    ecx.current_expansion.lint_node_id,
352                    errors::AvoidAttSyntax,
353                );
354            }
355        }
356
357        // Don't treat raw asm as a format string.
358        if args.options.contains(ast::InlineAsmOptions::RAW) {
359            template.push(ast::InlineAsmTemplatePiece::String(template_str.to_string().into()));
360            let template_num_lines = 1 + template_str.matches('\n').count();
361            line_spans.extend(std::iter::repeat_n(template_sp, template_num_lines));
362            continue;
363        }
364
365        let mut parser = parse::Parser::new(
366            template_str,
367            str_style,
368            template_snippet,
369            false,
370            parse::ParseMode::InlineAsm,
371        );
372        parser.curarg = curarg;
373
374        let mut unverified_pieces = Vec::new();
375        while let Some(piece) = parser.next() {
376            if !parser.errors.is_empty() {
377                break;
378            } else {
379                unverified_pieces.push(piece);
380            }
381        }
382
383        if !parser.errors.is_empty() {
384            let err = parser.errors.remove(0);
385            let err_sp = if template_is_mac_call {
386                // If the template is a macro call we can't reliably point to the error's
387                // span so just use the template's span as the error span (fixes #129503)
388                template_span
389            } else {
390                template_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
391            };
392
393            let msg = format!("invalid asm template string: {}", err.description);
394            let mut e = ecx.dcx().struct_span_err(err_sp, msg);
395            e.span_label(err_sp, err.label + " in asm template string");
396            if let Some(note) = err.note {
397                e.note(note);
398            }
399            if let Some((label, span)) = err.secondary_label {
400                let err_sp = template_span.from_inner(InnerSpan::new(span.start, span.end));
401                e.span_label(err_sp, label);
402            }
403            let guar = e.emit();
404            return ExpandResult::Ready(Err(guar));
405        }
406
407        curarg = parser.curarg;
408
409        let mut arg_spans = parser
410            .arg_places
411            .iter()
412            .map(|span| template_span.from_inner(InnerSpan::new(span.start, span.end)));
413        for piece in unverified_pieces {
414            match piece {
415                parse::Piece::Lit(s) => {
416                    template.push(ast::InlineAsmTemplatePiece::String(s.to_string().into()))
417                }
418                parse::Piece::NextArgument(arg) => {
419                    let span = arg_spans.next().unwrap_or(template_sp);
420
421                    let operand_idx = match arg.position {
422                        parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => {
423                            if idx >= args.operands.len()
424                                || named_pos.contains_key(&idx)
425                                || args.reg_args.contains(idx)
426                            {
427                                let msg = format!("invalid reference to argument at index {idx}");
428                                let mut err = ecx.dcx().struct_span_err(span, msg);
429                                err.span_label(span, "from here");
430
431                                let positional_args = args.operands.len()
432                                    - args.named_args.len()
433                                    - args.reg_args.len();
434                                let positional = if positional_args != args.operands.len() {
435                                    "positional "
436                                } else {
437                                    ""
438                                };
439                                let msg = match positional_args {
440                                    0 => format!("no {positional}arguments were given"),
441                                    1 => format!("there is 1 {positional}argument"),
442                                    x => format!("there are {x} {positional}arguments"),
443                                };
444                                err.note(msg);
445
446                                if named_pos.contains_key(&idx) {
447                                    err.span_label(args.operands[idx].1, "named argument");
448                                    err.span_note(
449                                        args.operands[idx].1,
450                                        "named arguments cannot be referenced by position",
451                                    );
452                                } else if args.reg_args.contains(idx) {
453                                    err.span_label(
454                                        args.operands[idx].1,
455                                        "explicit register argument",
456                                    );
457                                    err.span_note(
458                                        args.operands[idx].1,
459                                        "explicit register arguments cannot be used in the asm template",
460                                    );
461                                    err.span_help(
462                                        args.operands[idx].1,
463                                        "use the register name directly in the assembly code",
464                                    );
465                                }
466                                err.emit();
467                                None
468                            } else {
469                                Some(idx)
470                            }
471                        }
472                        parse::ArgumentNamed(name) => {
473                            match args.named_args.get(&Symbol::intern(name)) {
474                                Some(&idx) => Some(idx),
475                                None => {
476                                    let span = arg.position_span;
477                                    ecx.dcx()
478                                        .create_err(errors::AsmNoMatchedArgumentName {
479                                            name: name.to_owned(),
480                                            span: template_span
481                                                .from_inner(InnerSpan::new(span.start, span.end)),
482                                        })
483                                        .emit();
484                                    None
485                                }
486                            }
487                        }
488                    };
489
490                    let mut chars = arg.format.ty.chars();
491                    let mut modifier = chars.next();
492                    if chars.next().is_some() {
493                        let span = arg
494                            .format
495                            .ty_span
496                            .map(|sp| template_sp.from_inner(InnerSpan::new(sp.start, sp.end)))
497                            .unwrap_or(template_sp);
498                        ecx.dcx().emit_err(errors::AsmModifierInvalid { span });
499                        modifier = None;
500                    }
501
502                    if let Some(operand_idx) = operand_idx {
503                        used[operand_idx] = true;
504                        template.push(ast::InlineAsmTemplatePiece::Placeholder {
505                            operand_idx,
506                            modifier,
507                            span,
508                        });
509                    }
510                }
511            }
512        }
513
514        if parser.line_spans.is_empty() {
515            let template_num_lines = 1 + template_str.matches('\n').count();
516            line_spans.extend(std::iter::repeat_n(template_sp, template_num_lines));
517        } else {
518            line_spans.extend(
519                parser
520                    .line_spans
521                    .iter()
522                    .map(|span| template_span.from_inner(InnerSpan::new(span.start, span.end))),
523            );
524        };
525    }
526
527    let mut unused_operands = vec![];
528    let mut help_str = String::new();
529    for (idx, used) in used.into_iter().enumerate() {
530        if !used {
531            let msg = if let Some(sym) = named_pos.get(&idx) {
532                help_str.push_str(&format!(" {{{}}}", sym));
533                "named argument never used"
534            } else {
535                help_str.push_str(&format!(" {{{}}}", idx));
536                "argument never used"
537            };
538            unused_operands.push((args.operands[idx].1, msg));
539        }
540    }
541    match unused_operands[..] {
542        [] => {}
543        [(sp, msg)] => {
544            ecx.dcx()
545                .struct_span_err(sp, msg)
546                .with_span_label(sp, msg)
547                .with_help(format!(
548                    "if this argument is intentionally unused, \
549                     consider using it in an asm comment: `\"/*{help_str} */\"`"
550                ))
551                .emit();
552        }
553        _ => {
554            let mut err = ecx.dcx().struct_span_err(
555                unused_operands.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
556                "multiple unused asm arguments",
557            );
558            for (sp, msg) in unused_operands {
559                err.span_label(sp, msg);
560            }
561            err.help(format!(
562                "if these arguments are intentionally unused, \
563                 consider using them in an asm comment: `\"/*{help_str} */\"`"
564            ));
565            err.emit();
566        }
567    }
568
569    ExpandResult::Ready(Ok(ast::InlineAsm {
570        asm_macro,
571        template,
572        template_strs: template_strs.into_boxed_slice(),
573        operands: args.operands,
574        clobber_abis: args.clobber_abis,
575        options: args.options,
576        line_spans,
577    }))
578}
579
580pub(super) fn expand_asm<'cx>(
581    ecx: &'cx mut ExtCtxt<'_>,
582    sp: Span,
583    tts: TokenStream,
584) -> MacroExpanderResult<'cx> {
585    ExpandResult::Ready(match parse_args(ecx, sp, tts, AsmMacro::Asm) {
586        Ok(args) => {
587            let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, AsmMacro::Asm, args) else {
588                return ExpandResult::Retry(());
589            };
590            let expr = match mac {
591                Ok(inline_asm) => Box::new(ast::Expr {
592                    id: ast::DUMMY_NODE_ID,
593                    kind: ast::ExprKind::InlineAsm(Box::new(inline_asm)),
594                    span: sp,
595                    attrs: ast::AttrVec::new(),
596                    tokens: None,
597                }),
598                Err(guar) => DummyResult::raw_expr(sp, Some(guar)),
599            };
600            MacEager::expr(expr)
601        }
602        Err(err) => {
603            let guar = err.emit();
604            DummyResult::any(sp, guar)
605        }
606    })
607}
608
609pub(super) fn expand_naked_asm<'cx>(
610    ecx: &'cx mut ExtCtxt<'_>,
611    sp: Span,
612    tts: TokenStream,
613) -> MacroExpanderResult<'cx> {
614    ExpandResult::Ready(match parse_args(ecx, sp, tts, AsmMacro::NakedAsm) {
615        Ok(args) => {
616            let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, AsmMacro::NakedAsm, args)
617            else {
618                return ExpandResult::Retry(());
619            };
620            let expr = match mac {
621                Ok(inline_asm) => Box::new(ast::Expr {
622                    id: ast::DUMMY_NODE_ID,
623                    kind: ast::ExprKind::InlineAsm(Box::new(inline_asm)),
624                    span: sp,
625                    attrs: ast::AttrVec::new(),
626                    tokens: None,
627                }),
628                Err(guar) => DummyResult::raw_expr(sp, Some(guar)),
629            };
630            MacEager::expr(expr)
631        }
632        Err(err) => {
633            let guar = err.emit();
634            DummyResult::any(sp, guar)
635        }
636    })
637}
638
639pub(super) fn expand_global_asm<'cx>(
640    ecx: &'cx mut ExtCtxt<'_>,
641    sp: Span,
642    tts: TokenStream,
643) -> MacroExpanderResult<'cx> {
644    ExpandResult::Ready(match parse_args(ecx, sp, tts, AsmMacro::GlobalAsm) {
645        Ok(args) => {
646            let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, AsmMacro::GlobalAsm, args)
647            else {
648                return ExpandResult::Retry(());
649            };
650            match mac {
651                Ok(inline_asm) => MacEager::items(smallvec![Box::new(ast::Item {
652                    attrs: ast::AttrVec::new(),
653                    id: ast::DUMMY_NODE_ID,
654                    kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)),
655                    vis: ast::Visibility {
656                        span: sp.shrink_to_lo(),
657                        kind: ast::VisibilityKind::Inherited,
658                        tokens: None,
659                    },
660                    span: sp,
661                    tokens: None,
662                })]),
663                Err(guar) => DummyResult::any(sp, guar),
664            }
665        }
666        Err(err) => {
667            let guar = err.emit();
668            DummyResult::any(sp, guar)
669        }
670    })
671}