rustc_builtin_macros/
asm.rs

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