From 840176ab6f9fdda0878bd0ba1297437b506c6b7c Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 14 Jun 2020 14:17:51 -0700 Subject: [PATCH 1/4] asm: Unify pseudo-keyword parsing using `eat`, rather than a final `expect` Currently, `asm!` parsing uses an `expect` for the last parsed pseudo-keyword (`sym`), which makes it difficult to extend without simultaneously refactoring. Use `eat` for the last pseudo-keyword, and then add an `else` that fails parsing. No change to error output. --- src/librustc_builtin_macros/asm.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs index 480ee97f20552..afe2231cec67f 100644 --- a/src/librustc_builtin_macros/asm.rs +++ b/src/librustc_builtin_macros/asm.rs @@ -135,8 +135,7 @@ fn parse_args<'a>( } else if p.eat(&token::Ident(kw::Const, false)) { let expr = p.parse_expr()?; ast::InlineAsmOperand::Const { expr } - } else { - p.expect(&token::Ident(sym::sym, false))?; + } else if p.eat(&token::Ident(sym::sym, false)) { let expr = p.parse_expr()?; match expr.kind { ast::ExprKind::Path(..) => {} @@ -147,6 +146,8 @@ fn parse_args<'a>( } } ast::InlineAsmOperand::Sym { expr } + } else { + return Err(p.expect_one_of(&[], &[]).unwrap_err()); }; let span = span_start.to(p.prev_token.span); From 50d6d4de67d76f03f8a8b209881a924f2d69922f Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 14 Jun 2020 23:30:28 -0700 Subject: [PATCH 2/4] asm: When pretty-printing, don't escape characters twice pprust uses `print_string` to write out the template string, and `print_string` already calls `escape_debug`, so `impl fmt::Display for InlineAsmTemplatePiece` shouldn't do an additional `escape_debug`. This fixes a pretty-printing bug that translated `asm!("...\n...")` to `asm!("...\\n...")` --- src/librustc_ast/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc_ast/ast.rs b/src/librustc_ast/ast.rs index d3e3546cf31e4..e68cd043a9a58 100644 --- a/src/librustc_ast/ast.rs +++ b/src/librustc_ast/ast.rs @@ -1914,7 +1914,7 @@ impl fmt::Display for InlineAsmTemplatePiece { match c { '{' => f.write_str("{{")?, '}' => f.write_str("}}")?, - _ => write!(f, "{}", c.escape_debug())?, + _ => c.fmt(f)?, } } Ok(()) From 1078b6f9420c47fe99553d9dd8a58b232ba84b5e Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 14 Jun 2020 23:33:55 -0700 Subject: [PATCH 3/4] asm: Allow multiple template strings; interpret them as newline-separated Allow the `asm!` macro to accept a series of template arguments, and interpret them as if they were concatenated with a '\n' between them. This allows writing an `asm!` where each line of assembly appears in a separate template string argument. This syntax makes it possible for rustfmt to reliably format and indent each line of assembly, without risking changes to the inside of a template string. It also avoids the complexity of having the user carefully format and indent a multi-line string (including where to put the surrounding quotes), and avoids the extra indentation and lines of a call to `concat!`. For example, rewriting the second example from the [blog post on the new inline assembly syntax](https://blog.rust-lang.org/inside-rust/2020/06/08/new-inline-asm.html) using multiple template strings: ```rust fn main() { let mut bits = [0u8; 64]; for value in 0..=1024u64 { let popcnt; unsafe { asm!( " popcnt {popcnt}, {v}", "2:", " blsi rax, {v}", " jz 1f", " xor {v}, rax", " tzcnt rax, rax", " stosb", " jmp 2b", "1:", v = inout(reg) value => _, popcnt = out(reg) popcnt, out("rax") _, // scratch inout("rdi") bits.as_mut_ptr() => _, ); } println!("bits of {}: {:?}", value, &bits[0..popcnt]); } } ``` Note that all the template strings must appear before all other arguments; you cannot, for instance, provide a series of template strings intermixed with the corresponding operands. In order to get srcloc mappings right for macros that generate multi-line string literals, create one line_span for each line in the string literal, each pointing to the macro. Make `rustc_parse_format::Parser::curarg` `pub`, so that we can propagate it from one template string argument to the next. --- src/librustc_builtin_macros/asm.rs | 327 ++++++++++++++++------------- src/librustc_parse_format/lib.rs | 10 +- src/test/pretty/asm.pp | 8 + src/test/pretty/asm.rs | 9 + src/test/ui/asm/parse-error.rs | 10 +- src/test/ui/asm/parse-error.stderr | 34 ++- src/test/ui/asm/srcloc.rs | 80 +++++++ src/test/ui/asm/srcloc.stderr | 206 +++++++++++++++++- 8 files changed, 530 insertions(+), 154 deletions(-) diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs index afe2231cec67f..2988567960464 100644 --- a/src/librustc_builtin_macros/asm.rs +++ b/src/librustc_builtin_macros/asm.rs @@ -11,7 +11,7 @@ use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::{InnerSpan, Span}; struct AsmArgs { - template: P, + templates: Vec>, operands: Vec<(ast::InlineAsmOperand, Span)>, named_args: FxHashMap, reg_args: FxHashSet, @@ -52,9 +52,9 @@ fn parse_args<'a>( return Err(err); } - let template = p.parse_expr()?; + let first_template = p.parse_expr()?; let mut args = AsmArgs { - template, + templates: vec![first_template], operands: vec![], named_args: FxHashMap::default(), reg_args: FxHashSet::default(), @@ -62,11 +62,11 @@ fn parse_args<'a>( options_span: None, }; - let mut first = true; + let mut allow_templates = true; while p.token != token::Eof { if !p.eat(&token::Comma) { - if first { - // After `asm!(""` we always expect *only* a comma... + if allow_templates { + // After a template string, we always expect *only* a comma... let mut err = ecx.struct_span_err(p.token.span, "expected token: `,`"); err.span_label(p.token.span, "expected `,`"); p.maybe_annotate_with_ascription(&mut err, false); @@ -76,7 +76,6 @@ fn parse_args<'a>( return Err(p.expect(&token::Comma).err().unwrap()); } } - first = false; if p.token == token::Eof { break; } // accept trailing commas @@ -84,6 +83,7 @@ fn parse_args<'a>( // Parse options if p.eat(&token::Ident(sym::options, false)) { parse_options(&mut p, &mut args)?; + allow_templates = false; continue; } @@ -94,6 +94,7 @@ fn parse_args<'a>( let (ident, _) = p.token.ident().unwrap(); p.bump(); p.expect(&token::Eq)?; + allow_templates = false; Some(ident.name) } else { None @@ -146,10 +147,27 @@ fn parse_args<'a>( } } ast::InlineAsmOperand::Sym { expr } + } else if allow_templates { + let template = p.parse_expr()?; + // If it can't possibly expand to a string, provide diagnostics here to include other + // things it could have been. + match template.kind { + ast::ExprKind::Lit(ast::Lit { kind: ast::LitKind::Str(..), .. }) => {} + ast::ExprKind::MacCall(..) => {} + _ => { + let errstr = "expected operand, options, or additional template string"; + let mut err = ecx.struct_span_err(template.span, errstr); + err.span_label(template.span, errstr); + return Err(err); + } + } + args.templates.push(template); + continue; } else { return Err(p.expect_one_of(&[], &[]).unwrap_err()); }; + allow_templates = false; let span = span_start.to(p.prev_token.span); let slot = args.operands.len(); args.operands.push((op, span)); @@ -331,155 +349,180 @@ fn parse_reg<'a>( } fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P { - let msg = "asm template must be a string literal"; - let template_sp = args.template.span; - let (template_str, template_style, template_span) = - match expr_to_spanned_string(ecx, args.template, msg) { - Ok(template) => template, - Err(err) => { - if let Some(mut err) = err { - err.emit(); - } - return DummyResult::raw_expr(sp, true); - } - }; - - let str_style = match template_style { - ast::StrStyle::Cooked => None, - ast::StrStyle::Raw(raw) => Some(raw as usize), - }; - - let template_str = &template_str.as_str(); - let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok(); - let mut parser = parse::Parser::new( - template_str, - str_style, - template_snippet, - false, - parse::ParseMode::InlineAsm, - ); - - let mut unverified_pieces = Vec::new(); - while let Some(piece) = parser.next() { - if !parser.errors.is_empty() { - break; - } else { - unverified_pieces.push(piece); - } - } - - if !parser.errors.is_empty() { - let err = parser.errors.remove(0); - let err_sp = template_span.from_inner(err.span); - let mut e = ecx - .struct_span_err(err_sp, &format!("invalid asm template string: {}", err.description)); - e.span_label(err_sp, err.label + " in asm template string"); - if let Some(note) = err.note { - e.note(¬e); - } - if let Some((label, span)) = err.secondary_label { - let err_sp = template_span.from_inner(span); - e.span_label(err_sp, label); - } - e.emit(); - return DummyResult::raw_expr(sp, true); - } - + let mut template = vec![]; // Register operands are implicitly used since they are not allowed to be // referenced in the template string. let mut used = vec![false; args.operands.len()]; for pos in &args.reg_args { used[*pos] = true; } - let named_pos: FxHashMap = args.named_args.iter().map(|(&sym, &idx)| (idx, sym)).collect(); - let mut arg_spans = parser.arg_places.iter().map(|span| template_span.from_inner(*span)); - let mut template = vec![]; - for piece in unverified_pieces { - match piece { - parse::Piece::String(s) => { - template.push(ast::InlineAsmTemplatePiece::String(s.to_string())) + let mut line_spans = Vec::with_capacity(args.templates.len()); + let mut curarg = 0; + + for template_expr in args.templates.into_iter() { + if !template.is_empty() { + template.push(ast::InlineAsmTemplatePiece::String("\n".to_string())); + } + + let msg = "asm template must be a string literal"; + let template_sp = template_expr.span; + let (template_str, template_style, template_span) = + match expr_to_spanned_string(ecx, template_expr, msg) { + Ok(template_part) => template_part, + Err(err) => { + if let Some(mut err) = err { + err.emit(); + } + return DummyResult::raw_expr(sp, true); + } + }; + + let str_style = match template_style { + ast::StrStyle::Cooked => None, + ast::StrStyle::Raw(raw) => Some(raw as usize), + }; + + let template_str = &template_str.as_str(); + let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok(); + let mut parser = parse::Parser::new( + template_str, + str_style, + template_snippet, + false, + parse::ParseMode::InlineAsm, + ); + parser.curarg = curarg; + + let mut unverified_pieces = Vec::new(); + while let Some(piece) = parser.next() { + if !parser.errors.is_empty() { + break; + } else { + unverified_pieces.push(piece); + } + } + + if !parser.errors.is_empty() { + let err = parser.errors.remove(0); + let err_sp = template_span.from_inner(err.span); + let msg = &format!("invalid asm template string: {}", err.description); + let mut e = ecx.struct_span_err(err_sp, msg); + e.span_label(err_sp, err.label + " in asm template string"); + if let Some(note) = err.note { + e.note(¬e); + } + if let Some((label, span)) = err.secondary_label { + let err_sp = template_span.from_inner(span); + e.span_label(err_sp, label); } - parse::Piece::NextArgument(arg) => { - let span = arg_spans.next().unwrap_or(template_sp); - - let operand_idx = match arg.position { - parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => { - if idx >= args.operands.len() - || named_pos.contains_key(&idx) - || args.reg_args.contains(&idx) - { - let msg = format!("invalid reference to argument at index {}", idx); - let mut err = ecx.struct_span_err(span, &msg); - err.span_label(span, "from here"); - - let positional_args = - args.operands.len() - args.named_args.len() - args.reg_args.len(); - let positional = if positional_args != args.operands.len() { - "positional " + e.emit(); + return DummyResult::raw_expr(sp, true); + } + + curarg = parser.curarg; + + let mut arg_spans = parser.arg_places.iter().map(|span| template_span.from_inner(*span)); + for piece in unverified_pieces { + match piece { + parse::Piece::String(s) => { + template.push(ast::InlineAsmTemplatePiece::String(s.to_string())) + } + parse::Piece::NextArgument(arg) => { + let span = arg_spans.next().unwrap_or(template_sp); + + let operand_idx = match arg.position { + parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => { + if idx >= args.operands.len() + || named_pos.contains_key(&idx) + || args.reg_args.contains(&idx) + { + let msg = format!("invalid reference to argument at index {}", idx); + let mut err = ecx.struct_span_err(span, &msg); + err.span_label(span, "from here"); + + let positional_args = args.operands.len() + - args.named_args.len() + - args.reg_args.len(); + let positional = if positional_args != args.operands.len() { + "positional " + } else { + "" + }; + let msg = match positional_args { + 0 => format!("no {}arguments were given", positional), + 1 => format!("there is 1 {}argument", positional), + x => format!("there are {} {}arguments", x, positional), + }; + err.note(&msg); + + if named_pos.contains_key(&idx) { + err.span_label(args.operands[idx].1, "named argument"); + err.span_note( + args.operands[idx].1, + "named arguments cannot be referenced by position", + ); + } else if args.reg_args.contains(&idx) { + err.span_label( + args.operands[idx].1, + "explicit register argument", + ); + err.span_note( + args.operands[idx].1, + "explicit register arguments cannot be used in the asm template", + ); + } + err.emit(); + None } else { - "" - }; - let msg = match positional_args { - 0 => format!("no {}arguments were given", positional), - 1 => format!("there is 1 {}argument", positional), - x => format!("there are {} {}arguments", x, positional), - }; - err.note(&msg); - - if named_pos.contains_key(&idx) { - err.span_label(args.operands[idx].1, "named argument"); - err.span_note( - args.operands[idx].1, - "named arguments cannot be referenced by position", - ); - } else if args.reg_args.contains(&idx) { - err.span_label(args.operands[idx].1, "explicit register argument"); - err.span_note( - args.operands[idx].1, - "explicit register arguments cannot be used in the asm template", - ); + Some(idx) } - err.emit(); - None - } else { - Some(idx) - } - } - parse::ArgumentNamed(name) => match args.named_args.get(&name) { - Some(&idx) => Some(idx), - None => { - let msg = format!("there is no argument named `{}`", name); - ecx.struct_span_err(span, &msg[..]).emit(); - None } - }, - }; - - let mut chars = arg.format.ty.chars(); - let mut modifier = chars.next(); - if chars.next().is_some() { - let span = arg - .format - .ty_span - .map(|sp| template_sp.from_inner(sp)) - .unwrap_or(template_sp); - ecx.struct_span_err(span, "asm template modifier must be a single character") + parse::ArgumentNamed(name) => match args.named_args.get(&name) { + Some(&idx) => Some(idx), + None => { + let msg = format!("there is no argument named `{}`", name); + ecx.struct_span_err(span, &msg[..]).emit(); + None + } + }, + }; + + let mut chars = arg.format.ty.chars(); + let mut modifier = chars.next(); + if chars.next().is_some() { + let span = arg + .format + .ty_span + .map(|sp| template_sp.from_inner(sp)) + .unwrap_or(template_sp); + ecx.struct_span_err( + span, + "asm template modifier must be a single character", + ) .emit(); - modifier = None; - } + modifier = None; + } - if let Some(operand_idx) = operand_idx { - used[operand_idx] = true; - template.push(ast::InlineAsmTemplatePiece::Placeholder { - operand_idx, - modifier, - span, - }); + if let Some(operand_idx) = operand_idx { + used[operand_idx] = true; + template.push(ast::InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier, + span, + }); + } } } } + + if parser.line_spans.is_empty() { + let template_num_lines = 1 + template_str.matches('\n').count(); + line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines)); + } else { + line_spans.extend(parser.line_spans.iter().map(|span| template_span.from_inner(*span))); + }; } let mut unused_operands = vec![]; @@ -526,12 +569,6 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P { /// Error messages accumulated during parsing pub errors: Vec, /// Current position of implicit positional argument pointer - curarg: usize, + pub curarg: usize, /// `Some(raw count)` when the string is "raw", used to position spans correctly style: Option, /// Start and end byte offset of every successfully parsed argument @@ -243,11 +243,13 @@ impl<'a> Iterator for Parser<'a> { _ => Some(String(self.string(pos))), } } else { - if self.is_literal && self.cur_line_start != self.input.len() { + if self.is_literal { let start = self.to_span_index(self.cur_line_start); let end = self.to_span_index(self.input.len()); - self.line_spans.push(start.to(end)); - self.cur_line_start = self.input.len(); + let span = start.to(end); + if self.line_spans.last() != Some(&span) { + self.line_spans.push(span); + } } None } diff --git a/src/test/pretty/asm.pp b/src/test/pretty/asm.pp index 4903050e08ed2..b3d188dd70881 100644 --- a/src/test/pretty/asm.pp +++ b/src/test/pretty/asm.pp @@ -22,5 +22,13 @@ asm!("{0}", inout(reg) b); asm!("{0} {1}", out(reg) _, inlateout(reg) b => _); asm!("", out("al") _, lateout("rbx") _); + asm!("inst1\ninst2"); + asm!("inst1 {0}, 42\ninst2 {1}, 24", in(reg) a, out(reg) b); + asm!("inst2 {1}, 24\ninst1 {0}, 42", in(reg) a, out(reg) b); + asm!("inst1 {0}, 42\ninst2 {1}, 24", in(reg) a, out(reg) b); + asm!("inst1\ninst2"); + asm!("inst1\ninst2"); + asm!("inst1\n\tinst2"); + asm!("inst1\ninst2\ninst3\ninst4"); } } diff --git a/src/test/pretty/asm.rs b/src/test/pretty/asm.rs index 12c32e6721b33..33f25e5216b4e 100644 --- a/src/test/pretty/asm.rs +++ b/src/test/pretty/asm.rs @@ -16,5 +16,14 @@ pub fn main() { asm!("{name}", name = inout(reg) b); asm!("{} {}", out(reg) _, inlateout(reg) b => _); asm!("", out("al") _, lateout("rbx") _); + asm!("inst1", "inst2"); + asm!("inst1 {}, 42", "inst2 {}, 24", in(reg) a, out(reg) b); + asm!("inst2 {1}, 24", "inst1 {0}, 42", in(reg) a, out(reg) b); + asm!("inst1 {}, 42", "inst2 {name}, 24", in(reg) a, name = out(reg) b); + asm!("inst1 +inst2"); + asm!("inst1\ninst2"); + asm!("inst1\n\tinst2"); + asm!("inst1\ninst2", "inst3\ninst4"); } } diff --git a/src/test/ui/asm/parse-error.rs b/src/test/ui/asm/parse-error.rs index 2b1f018f3642e..fbf399d8b075c 100644 --- a/src/test/ui/asm/parse-error.rs +++ b/src/test/ui/asm/parse-error.rs @@ -13,7 +13,7 @@ fn main() { asm!("{}" foo); //~^ ERROR expected token: `,` asm!("{}", foo); - //~^ ERROR expected one of + //~^ ERROR expected operand, options, or additional template string asm!("{}", in foo); //~^ ERROR expected `(`, found `foo` asm!("{}", in(reg foo)); @@ -52,5 +52,13 @@ fn main() { //~^ ERROR named arguments cannot follow explicit register arguments asm!("{1}", in("eax") foo, const bar); //~^ ERROR positional arguments cannot follow named arguments or explicit register arguments + asm!("", options(), ""); + //~^ ERROR expected one of + asm!("{}", in(reg) foo, "{}", out(reg) foo); + //~^ ERROR expected one of + asm!(format!("{{{}}}", 0), in(reg) foo); + //~^ ERROR asm template must be a string literal + asm!("{1}", format!("{{{}}}", 0), in(reg) foo, out(reg) bar); + //~^ ERROR asm template must be a string literal } } diff --git a/src/test/ui/asm/parse-error.stderr b/src/test/ui/asm/parse-error.stderr index 583a10570360b..ba7e8f7a03cca 100644 --- a/src/test/ui/asm/parse-error.stderr +++ b/src/test/ui/asm/parse-error.stderr @@ -16,11 +16,11 @@ error: expected token: `,` LL | asm!("{}" foo); | ^^^ expected `,` -error: expected one of `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `foo` +error: expected operand, options, or additional template string --> $DIR/parse-error.rs:15:20 | LL | asm!("{}", foo); - | ^^^ expected one of 8 possible tokens + | ^^^ expected operand, options, or additional template string error: expected `(`, found `foo` --> $DIR/parse-error.rs:17:23 @@ -160,5 +160,33 @@ LL | asm!("{1}", in("eax") foo, const bar); | | | explicit register argument -error: aborting due to 24 previous errors +error: expected one of `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `""` + --> $DIR/parse-error.rs:55:29 + | +LL | asm!("", options(), ""); + | ^^ expected one of 8 possible tokens + +error: expected one of `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `"{}"` + --> $DIR/parse-error.rs:57:33 + | +LL | asm!("{}", in(reg) foo, "{}", out(reg) foo); + | ^^^^ expected one of 8 possible tokens + +error: asm template must be a string literal + --> $DIR/parse-error.rs:59:14 + | +LL | asm!(format!("{{{}}}", 0), in(reg) foo); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: asm template must be a string literal + --> $DIR/parse-error.rs:61:21 + | +LL | asm!("{1}", format!("{{{}}}", 0), in(reg) foo, out(reg) bar); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 28 previous errors diff --git a/src/test/ui/asm/srcloc.rs b/src/test/ui/asm/srcloc.rs index 402adc50d5b44..1477e3dd5665c 100644 --- a/src/test/ui/asm/srcloc.rs +++ b/src/test/ui/asm/srcloc.rs @@ -40,5 +40,85 @@ fn main() { asm!("movaps %xmm3, (%esi, 2)", options(att_syntax)); //~^ WARN: scale factor without index register is ignored + + asm!( + "invalid_instruction", + ); + //~^^ ERROR: invalid instruction mnemonic 'invalid_instruction' + + asm!( + "mov eax, eax", + "invalid_instruction", + "mov eax, eax", + ); + //~^^^ ERROR: invalid instruction mnemonic 'invalid_instruction' + + asm!( + "mov eax, eax\n", + "invalid_instruction", + "mov eax, eax", + ); + //~^^^ ERROR: invalid instruction mnemonic 'invalid_instruction' + + asm!( + "mov eax, eax", + concat!("invalid", "_", "instruction"), + "mov eax, eax", + ); + //~^^^ ERROR: invalid instruction mnemonic 'invalid_instruction' + + asm!( + concat!("mov eax", ", ", "eax"), + concat!("invalid", "_", "instruction"), + concat!("mov eax", ", ", "eax"), + ); + //~^^^ ERROR: invalid instruction mnemonic 'invalid_instruction' + + // Make sure template strings get separated + asm!( + "invalid_instruction1", + "invalid_instruction2", + ); + //~^^^ ERROR: invalid instruction mnemonic 'invalid_instruction1' + //~^^^ ERROR: invalid instruction mnemonic 'invalid_instruction2' + + asm!( + concat!( + "invalid", "_", "instruction1", "\n", + "invalid", "_", "instruction2", + ), + ); + //~^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction1' + //~^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction2' + + asm!( + concat!( + "invalid", "_", "instruction1", "\n", + "invalid", "_", "instruction2", + ), + concat!( + "invalid", "_", "instruction3", "\n", + "invalid", "_", "instruction4", + ), + ); + //~^^^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction1' + //~^^^^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction2' + //~^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction3' + //~^^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction4' + + asm!( + concat!( + "invalid", "_", "instruction1", "\n", + "invalid", "_", "instruction2", "\n", + ), + concat!( + "invalid", "_", "instruction3", "\n", + "invalid", "_", "instruction4", "\n", + ), + ); + //~^^^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction1' + //~^^^^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction2' + //~^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction3' + //~^^^^^^^^ ERROR: invalid instruction mnemonic 'invalid_instruction4' } } diff --git a/src/test/ui/asm/srcloc.stderr b/src/test/ui/asm/srcloc.stderr index d5d12b004816f..b62c8948289dd 100644 --- a/src/test/ui/asm/srcloc.stderr +++ b/src/test/ui/asm/srcloc.stderr @@ -82,5 +82,209 @@ note: instantiated into assembly here LL | movaps %xmm3, (%esi, 2) | ^ -error: aborting due to 6 previous errors; 1 warning emitted +error: invalid instruction mnemonic 'invalid_instruction' + --> $DIR/srcloc.rs:45:14 + | +LL | "invalid_instruction", + | ^ + | +note: instantiated into assembly here + --> :2:2 + | +LL | invalid_instruction + | ^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction' + --> $DIR/srcloc.rs:51:14 + | +LL | "invalid_instruction", + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction + | ^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction' + --> $DIR/srcloc.rs:58:14 + | +LL | "invalid_instruction", + | ^ + | +note: instantiated into assembly here + --> :4:1 + | +LL | invalid_instruction + | ^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction' + --> $DIR/srcloc.rs:65:13 + | +LL | concat!("invalid", "_", "instruction"), + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction + | ^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction' + --> $DIR/srcloc.rs:72:13 + | +LL | concat!("invalid", "_", "instruction"), + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction + | ^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction1' + --> $DIR/srcloc.rs:79:14 + | +LL | "invalid_instruction1", + | ^ + | +note: instantiated into assembly here + --> :2:2 + | +LL | invalid_instruction1 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction2' + --> $DIR/srcloc.rs:80:14 + | +LL | "invalid_instruction2", + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction2 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction1' + --> $DIR/srcloc.rs:86:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :2:2 + | +LL | invalid_instruction1 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction2' + --> $DIR/srcloc.rs:86:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction2 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction1' + --> $DIR/srcloc.rs:95:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :2:2 + | +LL | invalid_instruction1 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction2' + --> $DIR/srcloc.rs:95:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction2 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction3' + --> $DIR/srcloc.rs:99:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :4:1 + | +LL | invalid_instruction3 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction4' + --> $DIR/srcloc.rs:99:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :5:1 + | +LL | invalid_instruction4 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction1' + --> $DIR/srcloc.rs:110:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :2:2 + | +LL | invalid_instruction1 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction2' + --> $DIR/srcloc.rs:110:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :3:1 + | +LL | invalid_instruction2 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction3' + --> $DIR/srcloc.rs:114:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :5:1 + | +LL | invalid_instruction3 + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid instruction mnemonic 'invalid_instruction4' + --> $DIR/srcloc.rs:114:13 + | +LL | concat!( + | ^ + | +note: instantiated into assembly here + --> :6:1 + | +LL | invalid_instruction4 + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 23 previous errors; 1 warning emitted From fd9ed30e4b19457cc7059d1b08832edd6d2cbfb8 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 15 Jun 2020 13:30:13 -0700 Subject: [PATCH 4/4] asm: Update chapter in unstable book for multiple template string arguments Update all examples to use the new formatting, and update explanations to document it. --- .../unstable-book/src/library-features/asm.md | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md index fbb40f1d2f3d4..ed3f8508669d2 100644 --- a/src/doc/unstable-book/src/library-features/asm.md +++ b/src/doc/unstable-book/src/library-features/asm.md @@ -68,10 +68,13 @@ Let us see another example that also uses an input: let i: u64 = 3; let o: u64; unsafe { - asm!(" - mov {0}, {1} - add {0}, {number} - ", out(reg) o, in(reg) i, number = const 5); + asm!( + "mov {0}, {1}", + "add {0}, {number}", + out(reg) o, + in(reg) i, + number = const 5, + ); } assert_eq!(o, 8); ``` @@ -82,13 +85,18 @@ and then adding `5` to it. The example shows a few things: -First we can see that inputs are declared by writing `in` instead of `out`. +First, we can see that `asm!` allows multiple template string arguments; each +one is treated as a separate line of assembly code, as if they were all joined +together with newlines between them. This makes it easy to format assembly +code. + +Second, we can see that inputs are declared by writing `in` instead of `out`. -Second one of our operands has a type we haven't seen yet, `const`. +Third, one of our operands has a type we haven't seen yet, `const`. This tells the compiler to expand this argument to value directly inside the assembly template. This is only possible for constants and literals. -Third we can see that we can specify an argument number, or name as in any format string. +Fourth, we can see that we can specify an argument number, or name as in any format string. For inline assembly templates this is particularly useful as arguments are often used more than once. For more complex inline assembly using this facility is generally recommended, as it improves readability, and allows reordering instructions without changing the argument order. @@ -137,10 +145,13 @@ let mut a: u64 = 4; let b: u64 = 4; let c: u64 = 4; unsafe { - asm!(" - add {0}, {1} - add {0}, {2} - ", inout(reg) a, in(reg) b, in(reg) c); + asm!( + "add {0}, {1}", + "add {0}, {2}", + inout(reg) a, + in(reg) b, + in(reg) c, + ); } assert_eq!(a, 12); ``` @@ -233,7 +244,7 @@ unsafe { // ECX 0 selects the L0 cache information. inout("ecx") 0 => ecx, lateout("ebx") ebx, - lateout("edx") _ + lateout("edx") _, ); } @@ -255,12 +266,14 @@ This can also be used with a general register class (e.g. `reg`) to obtain a scr // Multiply x by 6 using shifts and adds let mut x: u64 = 4; unsafe { - asm!(" - mov {tmp}, {x} - shl {tmp}, 1 - shl {x}, 2 - add {x}, {tmp} - ", x = inout(reg) x, tmp = out(reg) _); + asm!( + "mov {tmp}, {x}", + "shl {tmp}, 1", + "shl {x}, 2", + "add {x}, {tmp}", + x = inout(reg) x, + tmp = out(reg) _, + ); } assert_eq!(x, 4 * 6); ``` @@ -338,7 +351,7 @@ unsafe { asm!( "add {0}, {1}", inlateout(reg) a, in(reg) b, - options(pure, nomem, nostack) + options(pure, nomem, nostack), ); } assert_eq!(a, 8); @@ -371,17 +384,19 @@ reg_operand := dir_spec "(" reg_spec ")" operand_expr operand := reg_operand / "const" const_expr / "sym" path option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "att_syntax" options := "options(" option *["," option] [","] ")" -asm := "asm!(" format_string *("," [ident "="] operand) ["," options] [","] ")" +asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) ["," options] [","] ")" ``` The macro will initially be supported only on ARM, AArch64, x86, x86-64 and RISC-V targets. Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target. [format-syntax]: https://doc.rust-lang.org/std/fmt/#syntax -## Template string +## Template string arguments The assembler template uses the same syntax as [format strings][format-syntax] (i.e. placeholders are specified by curly braces). The corresponding arguments are accessed in order, by index, or by name. However, implicit named arguments (introduced by [RFC #2795][rfc-2795]) are not supported. +An `asm!` invocation may have one or more template string arguments; an `asm!` with multiple template string arguments is treated as if all the strings were concatenated with a `\n` between them. The expected usage is for each template string argument to correspond to a line of assembly code. All template string arguments must appear before any other arguments. + As with format strings, named arguments must appear after positional arguments. Explicit register operands must appear at the end of the operand list, after named arguments if any. Explicit register operands cannot be used by placeholders in the template string. All other named and positional operands must appear at least once in the template string, otherwise a compiler error is generated.