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
19struct 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 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 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 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 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 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 [] => 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 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 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 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 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 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}