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
17struct 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 if strip_unconfigured.configure(arg.attributes).is_none() {
73 continue;
74 }
75
76 match arg.kind {
77 AsmArgKind::Template(template) => {
78 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 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 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 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 [] => 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 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 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 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 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 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}