rustc_builtin_macros/
asm.rs

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