about summary refs log tree commit diff
diff options
context:
space:
mode:
authorManish Goregaokar <manishsmail@gmail.com>2020-06-19 09:15:14 -0700
committerGitHub <noreply@github.com>2020-06-19 09:15:14 -0700
commit687f929c9b320fcd11097aee2778e2fc81fa8536 (patch)
treeb976f46a42678bc48c3441491a6396e4124d76bc
parent65c33ed7986d76fdefee6cf93081f77fdec2e0c8 (diff)
parentfd9ed30e4b19457cc7059d1b08832edd6d2cbfb8 (diff)
downloadrust-687f929c9b320fcd11097aee2778e2fc81fa8536.tar.gz
rust-687f929c9b320fcd11097aee2778e2fc81fa8536.zip
Rollup merge of #73364 - joshtriplett:inline-asm, r=Amanieu
asm: Allow multiple template string arguments; 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.
-rw-r--r--src/doc/unstable-book/src/library-features/asm.md57
-rw-r--r--src/librustc_ast/ast.rs2
-rw-r--r--src/librustc_builtin_macros/asm.rs332
-rw-r--r--src/librustc_parse_format/lib.rs10
-rw-r--r--src/test/pretty/asm.pp8
-rw-r--r--src/test/pretty/asm.rs9
-rw-r--r--src/test/ui/asm/parse-error.rs10
-rw-r--r--src/test/ui/asm/parse-error.stderr34
-rw-r--r--src/test/ui/asm/srcloc.rs80
-rw-r--r--src/test/ui/asm/srcloc.stderr206
10 files changed, 570 insertions, 178 deletions
diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md
index e8fb7716cb1..a941bc9348f 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, Hexagon, 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.
diff --git a/src/librustc_ast/ast.rs b/src/librustc_ast/ast.rs
index ce186c4834d..e98d709539d 100644
--- a/src/librustc_ast/ast.rs
+++ b/src/librustc_ast/ast.rs
@@ -1905,7 +1905,7 @@ impl fmt::Display for InlineAsmTemplatePiece {
                     match c {
                         '{' => f.write_str("{{")?,
                         '}' => f.write_str("}}")?,
-                        _ => write!(f, "{}", c.escape_debug())?,
+                        _ => c.fmt(f)?,
                     }
                 }
                 Ok(())
diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs
index 480ee97f205..29885679604 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<ast::Expr>,
+    templates: Vec<P<ast::Expr>>,
     operands: Vec<(ast::InlineAsmOperand, Span)>,
     named_args: FxHashMap<Symbol, usize>,
     reg_args: FxHashSet<usize>,
@@ -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
@@ -135,8 +136,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,8 +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));
@@ -330,155 +349,180 @@ fn parse_reg<'a>(
 }
 
 fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P<ast::Expr> {
-    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(&note);
-        }
-        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<usize, Symbol> =
         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);
             }
-            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 "
+        }
+
+        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(&note);
+            }
+            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);
+        }
+
+        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![];
@@ -525,12 +569,6 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P<ast
         }
     }
 
-    let line_spans = if parser.line_spans.is_empty() {
-        vec![template_sp]
-    } else {
-        parser.line_spans.iter().map(|span| template_span.from_inner(*span)).collect()
-    };
-
     let inline_asm =
         ast::InlineAsm { template, operands: args.operands, options: args.options, line_spans };
     P(ast::Expr {
diff --git a/src/librustc_parse_format/lib.rs b/src/librustc_parse_format/lib.rs
index 23bf7b35419..a5b5a1090cb 100644
--- a/src/librustc_parse_format/lib.rs
+++ b/src/librustc_parse_format/lib.rs
@@ -178,7 +178,7 @@ pub struct Parser<'a> {
     /// Error messages accumulated during parsing
     pub errors: Vec<ParseError>,
     /// 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<usize>,
     /// 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 4903050e08e..b3d188dd708 100644
--- a/src/test/pretty/asm.pp
+++ b/src/test/pretty/asm.pp
@@ -22,5 +22,13 @@ pub fn main() {
         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 12c32e6721b..33f25e5216b 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 2b1f018f364..fbf399d8b07 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 583a1057036..ba7e8f7a03c 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 402adc50d5b..1477e3dd566 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 d5d12b00481..b62c8948289 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
+  --> <inline asm>:2:2
+   |
+LL |     invalid_instruction
+   |     ^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction'
+  --> $DIR/srcloc.rs:51:14
+   |
+LL |             "invalid_instruction",
+   |              ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:3:1
+   |
+LL | invalid_instruction
+   | ^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction'
+  --> $DIR/srcloc.rs:58:14
+   |
+LL |             "invalid_instruction",
+   |              ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>: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
+  --> <inline asm>: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
+  --> <inline asm>:3:1
+   |
+LL | invalid_instruction
+   | ^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction1'
+  --> $DIR/srcloc.rs:79:14
+   |
+LL |             "invalid_instruction1",
+   |              ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:2:2
+   |
+LL |     invalid_instruction1
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction2'
+  --> $DIR/srcloc.rs:80:14
+   |
+LL |             "invalid_instruction2",
+   |              ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:3:1
+   |
+LL | invalid_instruction2
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction1'
+  --> $DIR/srcloc.rs:86:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:2:2
+   |
+LL |     invalid_instruction1
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction2'
+  --> $DIR/srcloc.rs:86:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:3:1
+   |
+LL | invalid_instruction2
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction1'
+  --> $DIR/srcloc.rs:95:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:2:2
+   |
+LL |     invalid_instruction1
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction2'
+  --> $DIR/srcloc.rs:95:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:3:1
+   |
+LL | invalid_instruction2
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction3'
+  --> $DIR/srcloc.rs:99:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:4:1
+   |
+LL | invalid_instruction3
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction4'
+  --> $DIR/srcloc.rs:99:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:5:1
+   |
+LL | invalid_instruction4
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction1'
+  --> $DIR/srcloc.rs:110:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:2:2
+   |
+LL |     invalid_instruction1
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction2'
+  --> $DIR/srcloc.rs:110:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:3:1
+   |
+LL | invalid_instruction2
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction3'
+  --> $DIR/srcloc.rs:114:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:5:1
+   |
+LL | invalid_instruction3
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: invalid instruction mnemonic 'invalid_instruction4'
+  --> $DIR/srcloc.rs:114:13
+   |
+LL |             concat!(
+   |             ^
+   |
+note: instantiated into assembly here
+  --> <inline asm>:6:1
+   |
+LL | invalid_instruction4
+   | ^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 23 previous errors; 1 warning emitted