]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/asm.rs
Auto merge of #86808 - fee1-dead:constify-1, r=oli-obk
[rust.git] / compiler / rustc_builtin_macros / src / asm.rs
1 use rustc_ast as ast;
2 use rustc_ast::ptr::P;
3 use rustc_ast::token;
4 use rustc_ast::tokenstream::TokenStream;
5 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
6 use rustc_errors::{Applicability, DiagnosticBuilder};
7 use rustc_expand::base::{self, *};
8 use rustc_parse::parser::Parser;
9 use rustc_parse_format as parse;
10 use rustc_session::lint::{self, BuiltinLintDiagnostics};
11 use rustc_span::symbol::Ident;
12 use rustc_span::symbol::{kw, sym, Symbol};
13 use rustc_span::{InnerSpan, MultiSpan, Span};
14 use rustc_target::asm::InlineAsmArch;
15 use smallvec::smallvec;
16
17 struct AsmArgs {
18     templates: Vec<P<ast::Expr>>,
19     operands: Vec<(ast::InlineAsmOperand, Span)>,
20     named_args: FxHashMap<Symbol, usize>,
21     reg_args: FxHashSet<usize>,
22     clobber_abi: Option<(Symbol, Span)>,
23     options: ast::InlineAsmOptions,
24     options_spans: Vec<Span>,
25 }
26
27 fn parse_args<'a>(
28     ecx: &mut ExtCtxt<'a>,
29     sp: Span,
30     tts: TokenStream,
31     is_global_asm: bool,
32 ) -> Result<AsmArgs, DiagnosticBuilder<'a>> {
33     let mut p = ecx.new_parser_from_tts(tts);
34
35     if p.token == token::Eof {
36         return Err(ecx.struct_span_err(sp, "requires at least a template string argument"));
37     }
38
39     // Detect use of the legacy llvm_asm! syntax (which used to be called asm!)
40     if !is_global_asm && p.look_ahead(1, |t| *t == token::Colon || *t == token::ModSep) {
41         let mut err =
42             ecx.struct_span_err(sp, "the legacy LLVM-style asm! syntax is no longer supported");
43         err.note("consider migrating to the new asm! syntax specified in RFC 2873");
44         err.note("alternatively, switch to llvm_asm! to keep your code working as it is");
45
46         // Find the span of the "asm!" so that we can offer an automatic suggestion
47         let asm_span = sp.from_inner(InnerSpan::new(0, 4));
48         if let Ok(s) = ecx.source_map().span_to_snippet(asm_span) {
49             if s == "asm!" {
50                 err.span_suggestion(
51                     asm_span,
52                     "replace with",
53                     "llvm_asm!".into(),
54                     Applicability::MachineApplicable,
55                 );
56             }
57         }
58         return Err(err);
59     }
60
61     let first_template = p.parse_expr()?;
62     let mut args = AsmArgs {
63         templates: vec![first_template],
64         operands: vec![],
65         named_args: FxHashMap::default(),
66         reg_args: FxHashSet::default(),
67         clobber_abi: None,
68         options: ast::InlineAsmOptions::empty(),
69         options_spans: vec![],
70     };
71
72     let mut allow_templates = true;
73     while p.token != token::Eof {
74         if !p.eat(&token::Comma) {
75             if allow_templates {
76                 // After a template string, we always expect *only* a comma...
77                 let mut err = ecx.struct_span_err(p.token.span, "expected token: `,`");
78                 err.span_label(p.token.span, "expected `,`");
79                 p.maybe_annotate_with_ascription(&mut err, false);
80                 return Err(err);
81             } else {
82                 // ...after that delegate to `expect` to also include the other expected tokens.
83                 return Err(p.expect(&token::Comma).err().unwrap());
84             }
85         }
86         if p.token == token::Eof {
87             break;
88         } // accept trailing commas
89
90         // Parse clobber_abi
91         if p.eat_keyword(sym::clobber_abi) {
92             parse_clobber_abi(&mut p, &mut args)?;
93             allow_templates = false;
94             continue;
95         }
96
97         // Parse options
98         if p.eat_keyword(sym::options) {
99             parse_options(&mut p, &mut args, is_global_asm)?;
100             allow_templates = false;
101             continue;
102         }
103
104         let span_start = p.token.span;
105
106         // Parse operand names
107         let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
108             let (ident, _) = p.token.ident().unwrap();
109             p.bump();
110             p.expect(&token::Eq)?;
111             allow_templates = false;
112             Some(ident.name)
113         } else {
114             None
115         };
116
117         let mut explicit_reg = false;
118         let op = if !is_global_asm && p.eat_keyword(kw::In) {
119             let reg = parse_reg(&mut p, &mut explicit_reg)?;
120             let expr = p.parse_expr()?;
121             ast::InlineAsmOperand::In { reg, expr }
122         } else if !is_global_asm && p.eat_keyword(sym::out) {
123             let reg = parse_reg(&mut p, &mut explicit_reg)?;
124             let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
125             ast::InlineAsmOperand::Out { reg, expr, late: false }
126         } else if !is_global_asm && p.eat_keyword(sym::lateout) {
127             let reg = parse_reg(&mut p, &mut explicit_reg)?;
128             let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
129             ast::InlineAsmOperand::Out { reg, expr, late: true }
130         } else if !is_global_asm && p.eat_keyword(sym::inout) {
131             let reg = parse_reg(&mut p, &mut explicit_reg)?;
132             let expr = p.parse_expr()?;
133             if p.eat(&token::FatArrow) {
134                 let out_expr =
135                     if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
136                 ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false }
137             } else {
138                 ast::InlineAsmOperand::InOut { reg, expr, late: false }
139             }
140         } else if !is_global_asm && p.eat_keyword(sym::inlateout) {
141             let reg = parse_reg(&mut p, &mut explicit_reg)?;
142             let expr = p.parse_expr()?;
143             if p.eat(&token::FatArrow) {
144                 let out_expr =
145                     if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
146                 ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true }
147             } else {
148                 ast::InlineAsmOperand::InOut { reg, expr, late: true }
149             }
150         } else if p.eat_keyword(kw::Const) {
151             let anon_const = p.parse_anon_const_expr()?;
152             ast::InlineAsmOperand::Const { anon_const }
153         } else if !is_global_asm && p.eat_keyword(sym::sym) {
154             let expr = p.parse_expr()?;
155             match expr.kind {
156                 ast::ExprKind::Path(..) => {}
157                 _ => {
158                     let err = ecx
159                         .struct_span_err(expr.span, "argument to `sym` must be a path expression");
160                     return Err(err);
161                 }
162             }
163             ast::InlineAsmOperand::Sym { expr }
164         } else if allow_templates {
165             let template = p.parse_expr()?;
166             // If it can't possibly expand to a string, provide diagnostics here to include other
167             // things it could have been.
168             match template.kind {
169                 ast::ExprKind::Lit(ast::Lit { kind: ast::LitKind::Str(..), .. }) => {}
170                 ast::ExprKind::MacCall(..) => {}
171                 _ => {
172                     let errstr = if is_global_asm {
173                         "expected operand, options, or additional template string"
174                     } else {
175                         "expected operand, clobber_abi, options, or additional template string"
176                     };
177                     let mut err = ecx.struct_span_err(template.span, errstr);
178                     err.span_label(template.span, errstr);
179                     return Err(err);
180                 }
181             }
182             args.templates.push(template);
183             continue;
184         } else {
185             return p.unexpected();
186         };
187
188         allow_templates = false;
189         let span = span_start.to(p.prev_token.span);
190         let slot = args.operands.len();
191         args.operands.push((op, span));
192
193         // Validate the order of named, positional & explicit register operands and
194         // clobber_abi/options. We do this at the end once we have the full span
195         // of the argument available.
196         if !args.options_spans.is_empty() {
197             ecx.struct_span_err(span, "arguments are not allowed after options")
198                 .span_labels(args.options_spans.clone(), "previous options")
199                 .span_label(span, "argument")
200                 .emit();
201         } else if let Some((_, abi_span)) = args.clobber_abi {
202             ecx.struct_span_err(span, "arguments are not allowed after clobber_abi")
203                 .span_label(abi_span, "clobber_abi")
204                 .span_label(span, "argument")
205                 .emit();
206         }
207         if explicit_reg {
208             if name.is_some() {
209                 ecx.struct_span_err(span, "explicit register arguments cannot have names").emit();
210             }
211             args.reg_args.insert(slot);
212         } else if let Some(name) = name {
213             if let Some(&prev) = args.named_args.get(&name) {
214                 ecx.struct_span_err(span, &format!("duplicate argument named `{}`", name))
215                     .span_label(args.operands[prev].1, "previously here")
216                     .span_label(span, "duplicate argument")
217                     .emit();
218                 continue;
219             }
220             if !args.reg_args.is_empty() {
221                 let mut err = ecx.struct_span_err(
222                     span,
223                     "named arguments cannot follow explicit register arguments",
224                 );
225                 err.span_label(span, "named argument");
226                 for pos in &args.reg_args {
227                     err.span_label(args.operands[*pos].1, "explicit register argument");
228                 }
229                 err.emit();
230             }
231             args.named_args.insert(name, slot);
232         } else {
233             if !args.named_args.is_empty() || !args.reg_args.is_empty() {
234                 let mut err = ecx.struct_span_err(
235                     span,
236                     "positional arguments cannot follow named arguments \
237                      or explicit register arguments",
238                 );
239                 err.span_label(span, "positional argument");
240                 for pos in args.named_args.values() {
241                     err.span_label(args.operands[*pos].1, "named argument");
242                 }
243                 for pos in &args.reg_args {
244                     err.span_label(args.operands[*pos].1, "explicit register argument");
245                 }
246                 err.emit();
247             }
248         }
249     }
250
251     if args.options.contains(ast::InlineAsmOptions::NOMEM)
252         && args.options.contains(ast::InlineAsmOptions::READONLY)
253     {
254         let spans = args.options_spans.clone();
255         ecx.struct_span_err(spans, "the `nomem` and `readonly` options are mutually exclusive")
256             .emit();
257     }
258     if args.options.contains(ast::InlineAsmOptions::PURE)
259         && args.options.contains(ast::InlineAsmOptions::NORETURN)
260     {
261         let spans = args.options_spans.clone();
262         ecx.struct_span_err(spans, "the `pure` and `noreturn` options are mutually exclusive")
263             .emit();
264     }
265     if args.options.contains(ast::InlineAsmOptions::PURE)
266         && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY)
267     {
268         let spans = args.options_spans.clone();
269         ecx.struct_span_err(
270             spans,
271             "the `pure` option must be combined with either `nomem` or `readonly`",
272         )
273         .emit();
274     }
275
276     let mut have_real_output = false;
277     let mut outputs_sp = vec![];
278     let mut regclass_outputs = vec![];
279     for (op, op_sp) in &args.operands {
280         match op {
281             ast::InlineAsmOperand::Out { reg, expr, .. }
282             | ast::InlineAsmOperand::SplitInOut { reg, out_expr: expr, .. } => {
283                 outputs_sp.push(*op_sp);
284                 have_real_output |= expr.is_some();
285                 if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
286                     regclass_outputs.push(*op_sp);
287                 }
288             }
289             ast::InlineAsmOperand::InOut { reg, .. } => {
290                 outputs_sp.push(*op_sp);
291                 have_real_output = true;
292                 if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
293                     regclass_outputs.push(*op_sp);
294                 }
295             }
296             _ => {}
297         }
298     }
299     if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output {
300         ecx.struct_span_err(
301             args.options_spans.clone(),
302             "asm with the `pure` option must have at least one output",
303         )
304         .emit();
305     }
306     if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() {
307         let err = ecx
308             .struct_span_err(outputs_sp, "asm outputs are not allowed with the `noreturn` option");
309
310         // Bail out now since this is likely to confuse MIR
311         return Err(err);
312     }
313     if let Some((_, abi_span)) = args.clobber_abi {
314         if is_global_asm {
315             let err =
316                 ecx.struct_span_err(abi_span, "`clobber_abi` cannot be used with `global_asm!`");
317
318             // Bail out now since this is likely to confuse later stages
319             return Err(err);
320         }
321         if !regclass_outputs.is_empty() {
322             ecx.struct_span_err(
323                 regclass_outputs.clone(),
324                 "asm with `clobber_abi` must specify explicit registers for outputs",
325             )
326             .span_label(abi_span, "clobber_abi")
327             .span_labels(regclass_outputs, "generic outputs")
328             .emit();
329         }
330     }
331
332     Ok(args)
333 }
334
335 /// Report a duplicate option error.
336 ///
337 /// This function must be called immediately after the option token is parsed.
338 /// Otherwise, the suggestion will be incorrect.
339 fn err_duplicate_option<'a>(p: &mut Parser<'a>, symbol: Symbol, span: Span) {
340     let mut err = p
341         .sess
342         .span_diagnostic
343         .struct_span_err(span, &format!("the `{}` option was already provided", symbol));
344     err.span_label(span, "this option was already provided");
345
346     // Tool-only output
347     let mut full_span = span;
348     if p.token.kind == token::Comma {
349         full_span = full_span.to(p.token.span);
350     }
351     err.tool_only_span_suggestion(
352         full_span,
353         "remove this option",
354         String::new(),
355         Applicability::MachineApplicable,
356     );
357
358     err.emit();
359 }
360
361 /// Try to set the provided option in the provided `AsmArgs`.
362 /// If it is already set, report a duplicate option error.
363 ///
364 /// This function must be called immediately after the option token is parsed.
365 /// Otherwise, the error will not point to the correct spot.
366 fn try_set_option<'a>(
367     p: &mut Parser<'a>,
368     args: &mut AsmArgs,
369     symbol: Symbol,
370     option: ast::InlineAsmOptions,
371 ) {
372     if !args.options.contains(option) {
373         args.options |= option;
374     } else {
375         err_duplicate_option(p, symbol, p.prev_token.span);
376     }
377 }
378
379 fn parse_options<'a>(
380     p: &mut Parser<'a>,
381     args: &mut AsmArgs,
382     is_global_asm: bool,
383 ) -> Result<(), DiagnosticBuilder<'a>> {
384     let span_start = p.prev_token.span;
385
386     p.expect(&token::OpenDelim(token::DelimToken::Paren))?;
387
388     while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) {
389         if !is_global_asm && p.eat_keyword(sym::pure) {
390             try_set_option(p, args, sym::pure, ast::InlineAsmOptions::PURE);
391         } else if !is_global_asm && p.eat_keyword(sym::nomem) {
392             try_set_option(p, args, sym::nomem, ast::InlineAsmOptions::NOMEM);
393         } else if !is_global_asm && p.eat_keyword(sym::readonly) {
394             try_set_option(p, args, sym::readonly, ast::InlineAsmOptions::READONLY);
395         } else if !is_global_asm && p.eat_keyword(sym::preserves_flags) {
396             try_set_option(p, args, sym::preserves_flags, ast::InlineAsmOptions::PRESERVES_FLAGS);
397         } else if !is_global_asm && p.eat_keyword(sym::noreturn) {
398             try_set_option(p, args, sym::noreturn, ast::InlineAsmOptions::NORETURN);
399         } else if !is_global_asm && p.eat_keyword(sym::nostack) {
400             try_set_option(p, args, sym::nostack, ast::InlineAsmOptions::NOSTACK);
401         } else if p.eat_keyword(sym::att_syntax) {
402             try_set_option(p, args, sym::att_syntax, ast::InlineAsmOptions::ATT_SYNTAX);
403         } else if p.eat_keyword(kw::Raw) {
404             try_set_option(p, args, kw::Raw, ast::InlineAsmOptions::RAW);
405         } else {
406             return p.unexpected();
407         }
408
409         // Allow trailing commas
410         if p.eat(&token::CloseDelim(token::DelimToken::Paren)) {
411             break;
412         }
413         p.expect(&token::Comma)?;
414     }
415
416     let new_span = span_start.to(p.prev_token.span);
417     args.options_spans.push(new_span);
418
419     Ok(())
420 }
421
422 fn parse_clobber_abi<'a>(
423     p: &mut Parser<'a>,
424     args: &mut AsmArgs,
425 ) -> Result<(), DiagnosticBuilder<'a>> {
426     let span_start = p.prev_token.span;
427
428     p.expect(&token::OpenDelim(token::DelimToken::Paren))?;
429
430     let clobber_abi = match p.parse_str_lit() {
431         Ok(str_lit) => str_lit.symbol_unescaped,
432         Err(opt_lit) => {
433             let span = opt_lit.map_or(p.token.span, |lit| lit.span);
434             let mut err = p.sess.span_diagnostic.struct_span_err(span, "expected string literal");
435             err.span_label(span, "not a string literal");
436             return Err(err);
437         }
438     };
439
440     p.expect(&token::CloseDelim(token::DelimToken::Paren))?;
441
442     let new_span = span_start.to(p.prev_token.span);
443
444     if let Some((_, prev_span)) = args.clobber_abi {
445         let mut err = p
446             .sess
447             .span_diagnostic
448             .struct_span_err(new_span, "clobber_abi specified multiple times");
449         err.span_label(prev_span, "clobber_abi previously specified here");
450         return Err(err);
451     } else if !args.options_spans.is_empty() {
452         let mut err = p
453             .sess
454             .span_diagnostic
455             .struct_span_err(new_span, "clobber_abi is not allowed after options");
456         err.span_labels(args.options_spans.clone(), "options");
457         return Err(err);
458     }
459
460     args.clobber_abi = Some((clobber_abi, new_span));
461
462     Ok(())
463 }
464
465 fn parse_reg<'a>(
466     p: &mut Parser<'a>,
467     explicit_reg: &mut bool,
468 ) -> Result<ast::InlineAsmRegOrRegClass, DiagnosticBuilder<'a>> {
469     p.expect(&token::OpenDelim(token::DelimToken::Paren))?;
470     let result = match p.token.uninterpolate().kind {
471         token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name),
472         token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => {
473             *explicit_reg = true;
474             ast::InlineAsmRegOrRegClass::Reg(symbol)
475         }
476         _ => {
477             return Err(
478                 p.struct_span_err(p.token.span, "expected register class or explicit register")
479             );
480         }
481     };
482     p.bump();
483     p.expect(&token::CloseDelim(token::DelimToken::Paren))?;
484     Ok(result)
485 }
486
487 fn expand_preparsed_asm(
488     ecx: &mut ExtCtxt<'_>,
489     args: AsmArgs,
490     is_local_asm: bool,
491 ) -> Option<ast::InlineAsm> {
492     let mut template = vec![];
493     // Register operands are implicitly used since they are not allowed to be
494     // referenced in the template string.
495     let mut used = vec![false; args.operands.len()];
496     for pos in &args.reg_args {
497         used[*pos] = true;
498     }
499     let named_pos: FxHashMap<usize, Symbol> =
500         args.named_args.iter().map(|(&sym, &idx)| (idx, sym)).collect();
501     let mut line_spans = Vec::with_capacity(args.templates.len());
502     let mut curarg = 0;
503
504     for template_expr in args.templates.into_iter() {
505         if !template.is_empty() {
506             template.push(ast::InlineAsmTemplatePiece::String("\n".to_string()));
507         }
508
509         let msg = "asm template must be a string literal";
510         let template_sp = template_expr.span;
511         let (template_str, template_style, template_span) =
512             match expr_to_spanned_string(ecx, template_expr, msg) {
513                 Ok(template_part) => template_part,
514                 Err(err) => {
515                     if let Some(mut err) = err {
516                         err.emit();
517                     }
518                     return None;
519                 }
520             };
521
522         let str_style = match template_style {
523             ast::StrStyle::Cooked => None,
524             ast::StrStyle::Raw(raw) => Some(raw as usize),
525         };
526
527         let template_str = &template_str.as_str();
528         let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok();
529
530         if let Some(InlineAsmArch::X86 | InlineAsmArch::X86_64) = ecx.sess.asm_arch {
531             let find_span = |needle: &str| -> Span {
532                 if let Some(snippet) = &template_snippet {
533                     if let Some(pos) = snippet.find(needle) {
534                         let end = pos
535                             + &snippet[pos..]
536                                 .find(|c| matches!(c, '\n' | ';' | '\\' | '"'))
537                                 .unwrap_or(snippet[pos..].len() - 1);
538                         let inner = InnerSpan::new(pos, end);
539                         return template_sp.from_inner(inner);
540                     }
541                 }
542                 template_sp
543             };
544
545             if template_str.contains(".intel_syntax") {
546                 ecx.parse_sess().buffer_lint(
547                     lint::builtin::BAD_ASM_STYLE,
548                     find_span(".intel_syntax"),
549                     ecx.current_expansion.lint_node_id,
550                     "avoid using `.intel_syntax`, Intel syntax is the default",
551                 );
552             }
553             if template_str.contains(".att_syntax") {
554                 ecx.parse_sess().buffer_lint(
555                     lint::builtin::BAD_ASM_STYLE,
556                     find_span(".att_syntax"),
557                     ecx.current_expansion.lint_node_id,
558                     "avoid using `.att_syntax`, prefer using `options(att_syntax)` instead",
559                 );
560             }
561         }
562
563         // Lint against the use of named labels in inline `asm!` but not `global_asm!`
564         if is_local_asm {
565             let find_label_span = |needle: &str| -> Option<Span> {
566                 if let Some(snippet) = &template_snippet {
567                     if let Some(pos) = snippet.find(needle) {
568                         let end = pos
569                             + &snippet[pos..]
570                                 .find(|c| c == ':')
571                                 .unwrap_or(snippet[pos..].len() - 1);
572                         let inner = InnerSpan::new(pos, end);
573                         return Some(template_sp.from_inner(inner));
574                     }
575                 }
576
577                 None
578             };
579
580             let mut found_labels = Vec::new();
581
582             // A semicolon might not actually be specified as a separator for all targets, but it seems like LLVM accepts it always
583             let statements = template_str.split(|c| matches!(c, '\n' | ';'));
584             for statement in statements {
585                 // If there's a comment, trim it from the statement
586                 let statement = statement.find("//").map_or(statement, |idx| &statement[..idx]);
587                 let mut start_idx = 0;
588                 for (idx, _) in statement.match_indices(':') {
589                     let possible_label = statement[start_idx..idx].trim();
590                     let mut chars = possible_label.chars();
591                     if let Some(c) = chars.next() {
592                         // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $
593                         if (c.is_alphabetic() || matches!(c, '.' | '_'))
594                             && chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '$'))
595                         {
596                             found_labels.push(possible_label);
597                         } else {
598                             // If we encounter a non-label, there cannot be any further labels, so stop checking
599                             break;
600                         }
601                     } else {
602                         // Empty string means a leading ':' in this section, which is not a label
603                         break;
604                     }
605
606                     start_idx = idx + 1;
607                 }
608             }
609
610             if found_labels.len() > 0 {
611                 let spans =
612                     found_labels.into_iter().filter_map(find_label_span).collect::<Vec<Span>>();
613                 // If there were labels but we couldn't find a span, combine the warnings and use the template span
614                 let target_spans: MultiSpan =
615                     if spans.len() > 0 { spans.into() } else { template_sp.into() };
616                 ecx.parse_sess().buffer_lint_with_diagnostic(
617                     lint::builtin::NAMED_ASM_LABELS,
618                     target_spans,
619                     ecx.current_expansion.lint_node_id,
620                     "avoid using named labels in inline assembly",
621                     BuiltinLintDiagnostics::NamedAsmLabel(
622                         "only local labels of the form `<number>:` should be used in inline asm"
623                             .to_string(),
624                     ),
625                 );
626             }
627         }
628
629         // Don't treat raw asm as a format string.
630         if args.options.contains(ast::InlineAsmOptions::RAW) {
631             template.push(ast::InlineAsmTemplatePiece::String(template_str.to_string()));
632             let template_num_lines = 1 + template_str.matches('\n').count();
633             line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines));
634             continue;
635         }
636
637         let mut parser = parse::Parser::new(
638             template_str,
639             str_style,
640             template_snippet,
641             false,
642             parse::ParseMode::InlineAsm,
643         );
644         parser.curarg = curarg;
645
646         let mut unverified_pieces = Vec::new();
647         while let Some(piece) = parser.next() {
648             if !parser.errors.is_empty() {
649                 break;
650             } else {
651                 unverified_pieces.push(piece);
652             }
653         }
654
655         if !parser.errors.is_empty() {
656             let err = parser.errors.remove(0);
657             let err_sp = template_span.from_inner(err.span);
658             let msg = &format!("invalid asm template string: {}", err.description);
659             let mut e = ecx.struct_span_err(err_sp, msg);
660             e.span_label(err_sp, err.label + " in asm template string");
661             if let Some(note) = err.note {
662                 e.note(&note);
663             }
664             if let Some((label, span)) = err.secondary_label {
665                 let err_sp = template_span.from_inner(span);
666                 e.span_label(err_sp, label);
667             }
668             e.emit();
669             return None;
670         }
671
672         curarg = parser.curarg;
673
674         let mut arg_spans = parser.arg_places.iter().map(|span| template_span.from_inner(*span));
675         for piece in unverified_pieces {
676             match piece {
677                 parse::Piece::String(s) => {
678                     template.push(ast::InlineAsmTemplatePiece::String(s.to_string()))
679                 }
680                 parse::Piece::NextArgument(arg) => {
681                     let span = arg_spans.next().unwrap_or(template_sp);
682
683                     let operand_idx = match arg.position {
684                         parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => {
685                             if idx >= args.operands.len()
686                                 || named_pos.contains_key(&idx)
687                                 || args.reg_args.contains(&idx)
688                             {
689                                 let msg = format!("invalid reference to argument at index {}", idx);
690                                 let mut err = ecx.struct_span_err(span, &msg);
691                                 err.span_label(span, "from here");
692
693                                 let positional_args = args.operands.len()
694                                     - args.named_args.len()
695                                     - args.reg_args.len();
696                                 let positional = if positional_args != args.operands.len() {
697                                     "positional "
698                                 } else {
699                                     ""
700                                 };
701                                 let msg = match positional_args {
702                                     0 => format!("no {}arguments were given", positional),
703                                     1 => format!("there is 1 {}argument", positional),
704                                     x => format!("there are {} {}arguments", x, positional),
705                                 };
706                                 err.note(&msg);
707
708                                 if named_pos.contains_key(&idx) {
709                                     err.span_label(args.operands[idx].1, "named argument");
710                                     err.span_note(
711                                         args.operands[idx].1,
712                                         "named arguments cannot be referenced by position",
713                                     );
714                                 } else if args.reg_args.contains(&idx) {
715                                     err.span_label(
716                                         args.operands[idx].1,
717                                         "explicit register argument",
718                                     );
719                                     err.span_note(
720                                         args.operands[idx].1,
721                                         "explicit register arguments cannot be used in the asm template",
722                                     );
723                                 }
724                                 err.emit();
725                                 None
726                             } else {
727                                 Some(idx)
728                             }
729                         }
730                         parse::ArgumentNamed(name) => match args.named_args.get(&name) {
731                             Some(&idx) => Some(idx),
732                             None => {
733                                 let msg = format!("there is no argument named `{}`", name);
734                                 ecx.struct_span_err(span, &msg[..]).emit();
735                                 None
736                             }
737                         },
738                     };
739
740                     let mut chars = arg.format.ty.chars();
741                     let mut modifier = chars.next();
742                     if chars.next().is_some() {
743                         let span = arg
744                             .format
745                             .ty_span
746                             .map(|sp| template_sp.from_inner(sp))
747                             .unwrap_or(template_sp);
748                         ecx.struct_span_err(
749                             span,
750                             "asm template modifier must be a single character",
751                         )
752                         .emit();
753                         modifier = None;
754                     }
755
756                     if let Some(operand_idx) = operand_idx {
757                         used[operand_idx] = true;
758                         template.push(ast::InlineAsmTemplatePiece::Placeholder {
759                             operand_idx,
760                             modifier,
761                             span,
762                         });
763                     }
764                 }
765             }
766         }
767
768         if parser.line_spans.is_empty() {
769             let template_num_lines = 1 + template_str.matches('\n').count();
770             line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines));
771         } else {
772             line_spans.extend(parser.line_spans.iter().map(|span| template_span.from_inner(*span)));
773         };
774     }
775
776     let mut unused_operands = vec![];
777     let mut help_str = String::new();
778     for (idx, used) in used.into_iter().enumerate() {
779         if !used {
780             let msg = if let Some(sym) = named_pos.get(&idx) {
781                 help_str.push_str(&format!(" {{{}}}", sym));
782                 "named argument never used"
783             } else {
784                 help_str.push_str(&format!(" {{{}}}", idx));
785                 "argument never used"
786             };
787             unused_operands.push((args.operands[idx].1, msg));
788         }
789     }
790     match unused_operands.len() {
791         0 => {}
792         1 => {
793             let (sp, msg) = unused_operands.into_iter().next().unwrap();
794             let mut err = ecx.struct_span_err(sp, msg);
795             err.span_label(sp, msg);
796             err.help(&format!(
797                 "if this argument is intentionally unused, \
798                  consider using it in an asm comment: `\"/*{} */\"`",
799                 help_str
800             ));
801             err.emit();
802         }
803         _ => {
804             let mut err = ecx.struct_span_err(
805                 unused_operands.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
806                 "multiple unused asm arguments",
807             );
808             for (sp, msg) in unused_operands {
809                 err.span_label(sp, msg);
810             }
811             err.help(&format!(
812                 "if these arguments are intentionally unused, \
813                  consider using them in an asm comment: `\"/*{} */\"`",
814                 help_str
815             ));
816             err.emit();
817         }
818     }
819
820     Some(ast::InlineAsm {
821         template,
822         operands: args.operands,
823         clobber_abi: args.clobber_abi,
824         options: args.options,
825         line_spans,
826     })
827 }
828
829 pub fn expand_asm<'cx>(
830     ecx: &'cx mut ExtCtxt<'_>,
831     sp: Span,
832     tts: TokenStream,
833 ) -> Box<dyn base::MacResult + 'cx> {
834     match parse_args(ecx, sp, tts, false) {
835         Ok(args) => {
836             let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args, true) {
837                 P(ast::Expr {
838                     id: ast::DUMMY_NODE_ID,
839                     kind: ast::ExprKind::InlineAsm(P(inline_asm)),
840                     span: sp,
841                     attrs: ast::AttrVec::new(),
842                     tokens: None,
843                 })
844             } else {
845                 DummyResult::raw_expr(sp, true)
846             };
847             MacEager::expr(expr)
848         }
849         Err(mut err) => {
850             err.emit();
851             DummyResult::any(sp)
852         }
853     }
854 }
855
856 pub fn expand_global_asm<'cx>(
857     ecx: &'cx mut ExtCtxt<'_>,
858     sp: Span,
859     tts: TokenStream,
860 ) -> Box<dyn base::MacResult + 'cx> {
861     match parse_args(ecx, sp, tts, true) {
862         Ok(args) => {
863             if let Some(inline_asm) = expand_preparsed_asm(ecx, args, false) {
864                 MacEager::items(smallvec![P(ast::Item {
865                     ident: Ident::invalid(),
866                     attrs: Vec::new(),
867                     id: ast::DUMMY_NODE_ID,
868                     kind: ast::ItemKind::GlobalAsm(inline_asm),
869                     vis: ast::Visibility {
870                         span: sp.shrink_to_lo(),
871                         kind: ast::VisibilityKind::Inherited,
872                         tokens: None,
873                     },
874                     span: ecx.with_def_site_ctxt(sp),
875                     tokens: None,
876                 })])
877             } else {
878                 DummyResult::any(sp)
879             }
880         }
881         Err(mut err) => {
882             err.emit();
883             DummyResult::any(sp)
884         }
885     }
886 }