diff --git a/Cargo.lock b/Cargo.lock index 22baa79dc6240..6295b40e97629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "compiler_builtins" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f18416546abfbf8d801c555a0e99524453e7214f9cc9107ad49de3d5948ccc" +checksum = "439a6fab343b1dab347823537734a5cd4ae6ae2000b465ab886f64cdb723bd14" dependencies = [ "cc", "rustc-std-workspace-core", @@ -1012,9 +1012,9 @@ dependencies = [ [[package]] name = "dlmalloc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f283302e035e61c23f2b86b3093e8c6273a4c3125742d6087e96ade001ca5e63" +checksum = "35055b1021724f4eb5262eb49130eebff23fc59fc5a14160e05faad8eeb36673" dependencies = [ "compiler_builtins", "libc", @@ -3625,6 +3625,7 @@ dependencies = [ name = "rustc_ast" version = "0.0.0" dependencies = [ + "bitflags", "log", "rustc_data_structures", "rustc_index", @@ -3678,6 +3679,7 @@ dependencies = [ "log", "rustc_ast", "rustc_span", + "rustc_target", ] [[package]] diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md new file mode 100644 index 0000000000000..0b68991fce2a1 --- /dev/null +++ b/src/doc/unstable-book/src/library-features/asm.md @@ -0,0 +1,699 @@ +# `asm` + +The tracking issue for this feature is: [#72016] + +[#72016]: https://github.com/rust-lang/rust/issues/72016 + +------------------------ + +For extremely low-level manipulations and performance reasons, one +might wish to control the CPU directly. Rust supports using inline +assembly to do this via the `asm!` macro. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Rust provides support for inline assembly via the `asm!` macro. +It can be used to embed handwritten assembly in the assembly output generated by the compiler. +Generally this should not be necessary, but might be where the required performance or timing +cannot be otherwise achieved. Accessing low level hardware primitives, e.g. in kernel code, may also demand this functionality. + +> **Note**: the examples here are given in x86/x86-64 assembly, but ARM, AArch64 and RISC-V are also supported. + +## Basic usage + +Let us start with the simplest possible example: + +```rust,allow_fail +# #![feature(asm)] +unsafe { + asm!("nop"); +} +``` + +This will insert a NOP (no operation) instruction into the assembly generated by the compiler. +Note that all `asm!` invocations have to be inside an `unsafe` block, as they could insert +arbitrary instructions and break various invariants. The instructions to be inserted are listed +in the first argument of the `asm!` macro as a string literal. + +## Inputs and outputs + +Now inserting an instruction that does nothing is rather boring. Let us do something that +actually acts on data: + +```rust,allow_fail +# #![feature(asm)] +let x: u64; +unsafe { + asm!("mov {}, 5", out(reg) x); +} +assert_eq!(x, 5); +``` + +This will write the value `5` into the `u64` variable `x`. +You can see that the string literal we use to specify instructions is actually a template string. +It is governed by the same rules as Rust [format strings][format-syntax]. +The arguments that are inserted into the template however look a bit different then you may +be familiar with. First we need to specify if the variable is an input or an output of the +inline assembly. In this case it is an output. We declared this by writing `out`. +We also need to specify in what kind of register the assembly expects the variable. +In this case we put it in an arbitrary general purpose register by specifying `reg`. +The compiler will choose an appropriate register to insert into +the template and will read the variable from there after the inline assembly finishes executing. + +Let us see another example that also uses an input: + +```rust,allow_fail +# #![feature(asm)] +let i: u64 = 3; +let o: u64; +unsafe { + asm!(" + mov {0}, {1} + add {0}, {number} + ", out(reg) o, in(reg) i, number = const 5); +} +assert_eq!(o, 8); +``` + +This will add `5` to the input in variable `i` and write the result to variable `o`. +The particular way this assembly does this is first copying the value from `i` to the output, +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`. + +Second 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. +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. + +We can further refine the above example to avoid the `mov` instruction: + +```rust,allow_fail +# #![feature(asm)] +let mut x: u64 = 3; +unsafe { + asm!("add {0}, {number}", inout(reg) x, number = const 5); +} +assert_eq!(x, 8); +``` + +We can see that `inout` is used to specify an argument that is both input and output. +This is different from specifying an input and output separately in that it is guaranteed to assign both to the same register. + +It is also possible to specify different variables for the input and output parts of an `inout` operand: + +```rust,allow_fail +# #![feature(asm)] +let x: u64 = 3; +let y: u64; +unsafe { + asm!("add {0}, {number}", inout(reg) x => y, number = const 5); +} +assert_eq!(y, 8); +``` + +## Late output operands + +The Rust compiler is conservative with its allocation of operands. It is assumed that an `out` +can be written at any time, and can therefore not share its location with any other argument. +However, to guarantee optimal performance it is important to use as few registers as possible, +so they won't have to be saved and reloaded around the inline assembly block. +To achieve this Rust provides a `lateout` specifier. This can be used on any output that is +written only after all inputs have been consumed. +There is also a `inlateout` variant of this specifier. + +Here is an example where `inlateout` *cannot* be used: + +```rust,allow_fail +# #![feature(asm)] +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); +} +assert_eq!(a, 12); +``` + +Here the compiler is free to allocate the same register for inputs `b` and `c` since it knows they have the same value. However it must allocate a separate register for `a` since it uses `inout` and not `inlateout`. If `inlateout` was used, then `a` and `c` could be allocated to the same register, in which case the first instruction to overwrite the value of `c` and cause the assembly code to produce the wrong result. + +However the following example can use `inlateout` since the output is only modified after all input registers have been read: + +```rust,allow_fail +# #![feature(asm)] +let mut a: u64 = 4; +let b: u64 = 4; +unsafe { + asm!("add {0}, {1}", inlateout(reg) a, in(reg) b); +} +assert_eq!(a, 8); +``` + +As you can see, this assembly fragment will still work correctly if `a` and `b` are assigned to the same register. + +## Explicit register operands + +Some instructions require that the operands be in a specific register. +Therefore, Rust inline assembly provides some more specific constraint specifiers. +While `reg` is generally available on any architecture, these are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi` +among others can be addressed by their name. + +```rust,allow_fail,no_run +# #![feature(asm)] +let cmd = 0xd1; +unsafe { + asm!("out 0x64, eax", in("eax") cmd); +} +``` + +In this example we call the `out` instruction to output the content of the `cmd` variable +to port `0x64`. Since the `out` instruction only accepts `eax` (and its sub registers) as operand +we had to use the `eax` constraint specifier. + +Note that unlike other operand types, explicit register operands cannot be used in the template string: you can't use `{}` and should write the register name directly instead. Also, they must appear at the end of the operand list after all other operand types. + +Consider this example which uses the x86 `mul` instruction: + +```rust,allow_fail +# #![feature(asm)] +fn mul(a: u64, b: u64) -> u128 { + let lo: u64; + let hi: u64; + + unsafe { + asm!( + // The x86 mul instruction takes rax as an implicit input and writes + // the 128-bit result of the multiplication to rax:rdx. + "mul {}", + in(reg) a, + inlateout("rax") b => lo, + lateout("rdx") hi + ); + } + + (hi as u128) << 64 + lo as u128 +} +``` + +This uses the `mul` instruction to multiply two 64-bit inputs with a 128-bit result. +The only explicit operand is a register, that we fill from the variable `a`. +The second operand is implicit, and must be the `rax` register, which we fill from the variable `b`. +The lower 64 bits of the result are stored in `rax` from which we fill the variable `lo`. +The higher 64 bits are stored in `rdx` from which we fill the variable `hi`. + +## Clobbered registers + +In many cases inline assembly will modify state that is not needed as an output. +Usually this is either because we have to use a scratch register in the assembly, +or instructions modify state that we don't need to further examine. +This state is generally referred to as being "clobbered". +We need to tell the compiler about this since it may need to save and restore this state +around the inline assembly block. + +```rust,allow_fail +# #![feature(asm)] +let ebx: u32; +let ecx: u32; + +unsafe { + asm!( + "cpuid", + // EAX 4 selects the "Deterministic Cache Parameters" CPUID leaf + inout("eax") 4 => _, + // ECX 0 selects the L0 cache information. + inout("ecx") 0 => ecx, + lateout("ebx") ebx, + lateout("edx") _ + ); +} + +println!( + "L1 Cache: {}", + ((ebx >> 22) + 1) * (((ebx >> 12) & 0x3ff) + 1) * ((ebx & 0xfff) + 1) * (ecx + 1) +); +``` + +In the example above we use the `cpuid` instruction to get the L1 cache size. +This instruction writes to `eax`, `ebx`, `ecx`, and `edx`, but for the cache size we only care about the contents of `ebx` and `ecx`. + +However we still need to tell the compiler that `eax` and `edx` have been modified so that it can save any values that were in these registers before the asm. This is done by declaring these as outputs but with `_` instead of a variable name, which indicates that the output value is to be discarded. + +This can also be used with a general register class (e.g. `reg`) to obtain a scratch register for use inside the asm code: + +```rust,allow_fail +# #![feature(asm)] +// 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) _); +} +assert_eq!(x, 4 * 6); +``` + +## Symbol operands + +A special operand type, `sym`, allows you to use the symbol name of a `fn` or `static` in inline assembly code. +This allows you to call a function or access a global variable without needing to keep its address in a register. + +```rust,allow_fail +# #![feature(asm)] +extern "C" fn foo(arg: i32) { + println!("arg = {}", arg); +} + +fn call_foo(arg: i32) { + unsafe { + asm!( + "call {}", + sym foo, + // 1st argument in rdi, which is caller-saved + inout("rdi") arg => _, + // All caller-saved registers must be marked as clobberred + out("rax") _, out("rcx") _, out("rdx") _, out("rsi") _, + out("r8") _, out("r9") _, out("r10") _, out("r11") _, + out("xmm0") _, out("xmm1") _, out("xmm2") _, out("xmm3") _, + out("xmm4") _, out("xmm5") _, out("xmm6") _, out("xmm7") _, + out("xmm8") _, out("xmm9") _, out("xmm10") _, out("xmm11") _, + out("xmm12") _, out("xmm13") _, out("xmm14") _, out("xmm15") _, + ) + } +} +``` + +Note that the `fn` or `static` item does not need to be public or `#[no_mangle]`: +the compiler will automatically insert the appropriate mangled symbol name into the assembly code. + +## Register template modifiers + +In some cases, fine control is needed over the way a register name is formatted when inserted into the template string. This is needed when an architecture's assembly language has several names for the same register, each typically being a "view" over a subset of the register (e.g. the low 32 bits of a 64-bit register). + +By default the compiler will always choose the name that refers to the full register size (e.g. `rax` on x86-64, `eax` on x86, etc). + +This default can be overriden by using modifiers on the template string operands, just like you would with format strings: + +```rust,allow_fail +# #![feature(asm)] +let mut x: u16 = 0xab; + +unsafe { + asm!("mov {0:h}, {0:l}", inout(reg_abcd) x); +} + +assert_eq!(x, 0xabab); +``` + +In this example, we use the `reg_abcd` register class to restrict the register allocator to the 4 legacy x86 register (`ax`, `bx`, `cx`, `dx`) of which the first two bytes can be addressed independently. + +Let us assume that the register allocator has chosen to allocate `x` in the `ax` register. +The `h` modifier will emit the register name for the high byte of that register and the `l` modifier will emit the register name for the low byte. The asm code will therefore be expanded as `mov ah, al` which copies the low byte of the value into the high byte. + +If you use a smaller data type (e.g. `u16`) with an operand and forget the use template modifiers, the compiler will emit a warning and suggest the correct modifier to use. + +## Options + +By default, an inline assembly block is treated the same way as an external FFI function call with a custom calling convention: it may read/write memory, have observable side effects, etc. However in many cases, it is desirable to give the compiler more information about what the assembly code is actually doing so that it can optimize better. + +Let's take our previous example of an `add` instruction: + +```rust,allow_fail +# #![feature(asm)] +let mut a: u64 = 4; +let b: u64 = 4; +unsafe { + asm!( + "add {0}, {1}", + inlateout(reg) a, in(reg) b, + options(pure, nomem, nostack) + ); +} +assert_eq!(a, 8); +``` + +Options can be provided as an optional final argument to the `asm!` macro. We specified three options here: +- `pure` means that the asm code has no observable side effects and that its output depends only on its inputs. This allows the compiler optimizer to call the inline asm fewer times or even eliminate it entirely. +- `nomem` means that the asm code does not read or write to memory. By default the compiler will assume that inline assembly can read or write any memory address that is accessible to it (e.g. through a pointer passed as an operand, or a global). +- `nostack` means that the asm code does not push any data onto the stack. This allows the compiler to use optimizations such as the stack red zone on x86-64 to avoid stack pointer adjustments. + +These allow the compiler to better optimize code using `asm!`, for example by eliminating pure `asm!` blocks whose outputs are not needed. + +See the reference for the full list of available options and their effects. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +Inline assembler is implemented as an unsafe macro `asm!()`. +The first argument to this macro is a template string literal used to build the final assembly. +The following arguments specify input and output operands. +When required, options are specified as the final argument. + +The following ABNF specifies the general syntax: + +```ignore +dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout" +reg_spec := / "" +operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" +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] [","] ")" +``` + +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 + +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. + +As with format strings, named arguments must appear after positional arguments. Explicit register operands must appear at the end of the operand list, after any named arguments if any. Explicit register operands cannot be used by placeholders in the template string. All other operands must appear at least once in the template string, otherwise a compiler error is generated. + +The exact assembly code syntax is target-specific and opaque to the compiler except for the way operands are substituted into the template string to form the code passed to the assembler. + +The 4 targets specified in this RFC (x86, ARM, AArch64, RISC-V) all use the assembly code syntax of the GNU assembler (GAS). On x86, the `.intel_syntax noprefix` mode of GAS is used by default. On ARM, the `.syntax unified` mode is used. These targets impose an additional restriction on the assembly code: any assembler state (e.g. the current section which can be changed with `.section`) must be restored to its original value at the end of the asm string. Assembly code that does not conform to the GAS syntax will result in assembler-specific behavior. + +[rfc-2795]: https://github.com/rust-lang/rfcs/pull/2795 + +## Operand type + +Several types of operands are supported: + +* `in() ` + - `` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string. + - The allocated register will contain the value of `` at the start of the asm code. + - The allocated register must contain the same value at the end of the asm code (except if a `lateout` is allocated to the same register). +* `out() ` + - `` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string. + - The allocated register will contain an undefined value at the start of the asm code. + - `` must be a (possibly uninitialized) place expression, to which the contents of the allocated register is written to at the end of the asm code. + - An underscore (`_`) may be specified instead of an expression, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber). +* `lateout() ` + - Identical to `out` except that the register allocator can reuse a register allocated to an `in`. + - You should only write to the register after all inputs are read, otherwise you may clobber an input. +* `inout() ` + - `` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string. + - The allocated register will contain the value of `` at the start of the asm code. + - `` must be a mutable initialized place expression, to which the contents of the allocated register is written to at the end of the asm code. +* `inout() => ` + - Same as `inout` except that the initial value of the register is taken from the value of ``. + - `` must be a (possibly uninitialized) place expression, to which the contents of the allocated register is written to at the end of the asm code. + - An underscore (`_`) may be specified instead of an expression for ``, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber). + - `` and `` may have different types. +* `inlateout() ` / `inlateout() => ` + - Identical to `inout` except that the register allocator can reuse a register allocated to an `in` (this can happen if the compiler knows the `in` has the same initial value as the `inlateout`). + - You should only write to the register after all inputs are read, otherwise you may clobber an input. +* `const ` + - `` must be an integer or floating-point constant expression. + - The value of the expression is formatted as a string and substituted directly into the asm template string. +* `sym ` + - `` must refer to a `fn` or `static`. + - A mangled symbol name referring to the item is substituted into the asm template string. + - The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc). + - `` is allowed to point to a `#[thread_local]` static, in which case the asm code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data. + +Operand expressions are evaluated from left to right, just like function call arguments. After the `asm!` has executed, outputs are written to in left to right order. This is significant if two outputs point to the same place: that place will contain the value of the rightmost output. + +## Register operands + +Input and output operands can be specified either as an explicit register or as a register class from which the register allocator can select a register. Explicit registers are specified as string literals (e.g. `"eax"`) while register classes are specified as identifiers (e.g. `reg`). Using string literals for register names enables support for architectures that use special characters in register names, such as MIPS (`$0`, `$1`, etc). + +Note that explicit registers treat register aliases (e.g. `r14` vs `lr` on ARM) and smaller views of a register (e.g. `eax` vs `rax`) as equivalent to the base register. It is a compile-time error to use the same explicit register for two input operands or two output operands. Additionally, it is also a compile-time error to use overlapping registers (e.g. ARM VFP) in input operands or in output operands. + +Only the following types are allowed as operands for inline assembly: +- Integers (signed and unsigned) +- Floating-point numbers +- Pointers (thin only) +- Function pointers +- SIMD vectors (structs defined with `#[repr(simd)]` and which implement `Copy`). This includes architecture-specific vector types defined in `std::arch` such as `__m128` (x86) or `int8x16_t` (ARM). + +Here is the list of currently supported register classes: + +| Architecture | Register class | Registers | LLVM constraint code | +| ------------ | -------------- | --------- | -------------------- | +| x86 | `reg` | `ax`, `bx`, `cx`, `dx`, `si`, `di`, `r[8-15]` (x86-64 only) | `r` | +| x86 | `reg_abcd` | `ax`, `bx`, `cx`, `dx` | `Q` | +| x86-32 | `reg_byte` | `al`, `bl`, `cl`, `dl`, `ah`, `bh`, `ch`, `dh` | `q` | +| x86-64 | `reg_byte` | `al`, `bl`, `cl`, `dl`, `sil`, `dil`, `r[8-15]b`, `ah`\*, `bh`\*, `ch`\*, `dh`\* | `q` | +| x86 | `xmm_reg` | `xmm[0-7]` (x86) `xmm[0-15]` (x86-64) | `x` | +| x86 | `ymm_reg` | `ymm[0-7]` (x86) `ymm[0-15]` (x86-64) | `x` | +| x86 | `zmm_reg` | `zmm[0-7]` (x86) `zmm[0-31]` (x86-64) | `v` | +| x86 | `kreg` | `k[1-7]` | `Yk` | +| AArch64 | `reg` | `x[0-28]`, `x30` | `r` | +| AArch64 | `vreg` | `v[0-31]` | `w` | +| AArch64 | `vreg_low16` | `v[0-15]` | `x` | +| ARM | `reg` | `r[0-r10]`, `r12`, `r14` | `r` | +| ARM (Thumb) | `reg_thumb` | `r[0-r7]` | `l` | +| ARM (ARM) | `reg_thumb` | `r[0-r10]`, `r12`, `r14` | `l` | +| ARM | `sreg` | `s[0-31]` | `t` | +| ARM | `sreg_low16` | `s[0-15]` | `x` | +| ARM | `dreg` | `d[0-31]` | `w` | +| ARM | `dreg_low16` | `d[0-15]` | `t` | +| ARM | `dreg_low8` | `d[0-8]` | `x` | +| ARM | `qreg` | `q[0-15]` | `w` | +| ARM | `qreg_low8` | `q[0-7]` | `t` | +| ARM | `qreg_low4` | `q[0-3]` | `x` | +| RISC-V | `reg` | `x1`, `x[5-7]`, `x[9-15]`, `x[16-31]` (non-RV32E) | `r` | +| RISC-V | `freg` | `f[0-31]` | `f` | + +> **Note**: On x86 we treat `reg_byte` differently from `reg` because the compiler can allocate `al` and `ah` separately whereas `reg` reserves the whole register. +> +> Note #2: On x86-64 the high byte registers (e.g. `ah`) are only available when used as an explicit register. Specifying the `reg_byte` register class for an operand will always allocate a low byte register. + +Additional register classes may be added in the future based on demand (e.g. MMX, x87, etc). + +Each register class has constraints on which value types they can be used with. This is necessary because the way a value is loaded into a register depends on its type. For example, on big-endian systems, loading a `i32x4` and a `i8x16` into a SIMD register may result in different register contents even if the byte-wise memory representation of both values is identical. The availability of supported types for a particular register class may depend on what target features are currently enabled. + +| Architecture | Register class | Target feature | Allowed types | +| ------------ | -------------- | -------------- | ------------- | +| x86-32 | `reg` | None | `i16`, `i32`, `f32` | +| x86-64 | `reg` | None | `i16`, `i32`, `f32`, `i64`, `f64` | +| x86 | `reg_byte` | None | `i8` | +| x86 | `xmm_reg` | `sse` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` | +| x86 | `ymm_reg` | `avx` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2`
`i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4` | +| x86 | `zmm_reg` | `avx512f` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2`
`i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4`
`i8x64`, `i16x32`, `i32x16`, `i64x8`, `f32x16`, `f64x8` | +| x86 | `kreg` | `axv512f` | `i8`, `i16` | +| x86 | `kreg` | `axv512bw` | `i32`, `i64` | +| AArch64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | +| AArch64 | `vreg` | `fp` | `i8`, `i16`, `i32`, `f32`, `i64`, `f64`,
`i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2`, `f64x1`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` | +| ARM | `reg` | None | `i8`, `i16`, `i32`, `f32` | +| ARM | `sreg` | `vfp2` | `i32`, `f32` | +| ARM | `dreg` | `vfp2` | `i64`, `f64`, `i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2` | +| ARM | `qreg` | `neon` | `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4` | +| RISC-V32 | `reg` | None | `i8`, `i16`, `i32`, `f32` | +| RISC-V64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | +| RISC-V | `freg` | `f` | `f32` | +| RISC-V | `freg` | `d` | `f64` | + +> **Note**: For the purposes of the above table pointers, function pointers and `isize`/`usize` are treated as the equivalent integer type (`i16`/`i32`/`i64` depending on the target). + +If a value is of a smaller size than the register it is allocated in then the upper bits of that register will have an undefined value for inputs and will be ignored for outputs. The only exception is the `freg` register class on RISC-V where `f32` values are NaN-boxed in a `f64` as required by the RISC-V architecture. + +When separate input and output expressions are specified for an `inout` operand, both expressions must have the same type. The only exception is if both operands are pointers or integers, in which case they are only required to have the same size. This restriction exists because the register allocators in LLVM and GCC sometimes cannot handle tied operands with different types. + +## Register names + +Some registers have multiple names. These are all treated by the compiler as identical to the base register name. Here is the list of all supported register aliases: + +| Architecture | Base register | Aliases | +| ------------ | ------------- | ------- | +| x86 | `ax` | `eax`, `rax` | +| x86 | `bx` | `ebx`, `rbx` | +| x86 | `cx` | `ecx`, `rcx` | +| x86 | `dx` | `edx`, `rdx` | +| x86 | `si` | `esi`, `rsi` | +| x86 | `di` | `edi`, `rdi` | +| x86 | `bp` | `bpl`, `ebp`, `rbp` | +| x86 | `sp` | `spl`, `esp`, `rsp` | +| x86 | `ip` | `eip`, `rip` | +| x86 | `st(0)` | `st` | +| x86 | `r[8-15]` | `r[8-15]b`, `r[8-15]w`, `r[8-15]d` | +| x86 | `xmm[0-31]` | `ymm[0-31]`, `zmm[0-31]` | +| AArch64 | `x[0-30]` | `w[0-30]` | +| AArch64 | `x29` | `fp` | +| AArch64 | `x30` | `lr` | +| AArch64 | `sp` | `wsp` | +| AArch64 | `xzr` | `wzr` | +| AArch64 | `v[0-31]` | `b[0-31]`, `h[0-31]`, `s[0-31]`, `d[0-31]`, `q[0-31]` | +| ARM | `r[0-3]` | `a[1-4]` | +| ARM | `r[4-9]` | `v[1-6]` | +| ARM | `r9` | `rfp` | +| ARM | `r10` | `sl` | +| ARM | `r11` | `fp` | +| ARM | `r12` | `ip` | +| ARM | `r13` | `sp` | +| ARM | `r14` | `lr` | +| ARM | `r15` | `pc` | +| RISC-V | `x0` | `zero` | +| RISC-V | `x1` | `ra` | +| RISC-V | `x2` | `sp` | +| RISC-V | `x3` | `gp` | +| RISC-V | `x4` | `tp` | +| RISC-V | `x[5-7]` | `t[0-2]` | +| RISC-V | `x8` | `fp`, `s0` | +| RISC-V | `x9` | `s1` | +| RISC-V | `x[10-17]` | `a[0-7]` | +| RISC-V | `x[18-27]` | `s[2-11]` | +| RISC-V | `x[28-31]` | `t[3-6]` | +| RISC-V | `f[0-7]` | `ft[0-7]` | +| RISC-V | `f[8-9]` | `fs[0-1]` | +| RISC-V | `f[10-17]` | `fa[0-7]` | +| RISC-V | `f[18-27]` | `fs[2-11]` | +| RISC-V | `f[28-31]` | `ft[8-11]` | + +Some registers cannot be used for input or output operands: + +| Architecture | Unsupported register | Reason | +| ------------ | -------------------- | ------ | +| All | `sp` | The stack pointer must be restored to its original value at the end of an asm code block. | +| All | `bp` (x86), `r11` (ARM), `x29` (AArch64), `x8` (RISC-V) | The frame pointer cannot be used as an input or output. | +| x86 | `k0` | This is a constant zero register which can't be modified. | +| x86 | `ip` | This is the program counter, not a real register. | +| x86 | `mm[0-7]` | MMX registers are not currently supported (but may be in the future). | +| x86 | `st([0-7])` | x87 registers are not currently supported (but may be in the future). | +| AArch64 | `xzr` | This is a constant zero register which can't be modified. | +| ARM | `pc` | This is the program counter, not a real register. | +| RISC-V | `x0` | This is a constant zero register which can't be modified. | +| RISC-V | `gp`, `tp` | These registers are reserved and cannot be used as inputs or outputs. | + +## Template modifiers + +The placeholders can be augmented by modifiers which are specified after the `:` in the curly braces. These modifiers do not affect register allocation, but change the way operands are formatted when inserted into the template string. Only one modifier is allowed per template placeholder. + +The supported modifiers are a subset of LLVM's (and GCC's) [asm template argument modifiers][llvm-argmod], but do not use the same letter codes. + +| Architecture | Register class | Modifier | Example output | LLVM modifier | +| ------------ | -------------- | -------- | -------------- | ------------- | +| x86-32 | `reg` | None | `eax` | `k` | +| x86-64 | `reg` | None | `rax` | `q` | +| x86-32 | `reg_abcd` | `l` | `al` | `b` | +| x86-64 | `reg` | `l` | `al` | `b` | +| x86 | `reg_abcd` | `h` | `ah` | `h` | +| x86 | `reg` | `x` | `ax` | `w` | +| x86 | `reg` | `e` | `eax` | `k` | +| x86-64 | `reg` | `r` | `rax` | `q` | +| x86 | `reg_byte` | None | `al` / `ah` | None | +| x86 | `xmm_reg` | None | `xmm0` | `x` | +| x86 | `ymm_reg` | None | `ymm0` | `t` | +| x86 | `zmm_reg` | None | `zmm0` | `g` | +| x86 | `*mm_reg` | `x` | `xmm0` | `x` | +| x86 | `*mm_reg` | `y` | `ymm0` | `t` | +| x86 | `*mm_reg` | `z` | `zmm0` | `g` | +| x86 | `kreg` | None | `k1` | None | +| AArch64 | `reg` | None | `x0` | `x` | +| AArch64 | `reg` | `w` | `w0` | `w` | +| AArch64 | `reg` | `x` | `x0` | `x` | +| AArch64 | `vreg` | None | `v0` | None | +| AArch64 | `vreg` | `v` | `v0` | None | +| AArch64 | `vreg` | `b` | `b0` | `b` | +| AArch64 | `vreg` | `h` | `h0` | `h` | +| AArch64 | `vreg` | `s` | `s0` | `s` | +| AArch64 | `vreg` | `d` | `d0` | `d` | +| AArch64 | `vreg` | `q` | `q0` | `q` | +| ARM | `reg` | None | `r0` | None | +| ARM | `sreg` | None | `s0` | None | +| ARM | `dreg` | None | `d0` | `P` | +| ARM | `qreg` | None | `q0` | `q` | +| ARM | `qreg` | `e` / `f` | `d0` / `d1` | `e` / `f` | +| RISC-V | `reg` | None | `x1` | None | +| RISC-V | `freg` | None | `f0` | None | + +> Notes: +> - on ARM `e` / `f`: this prints the low or high doubleword register name of a NEON quad (128-bit) register. +> - on x86: our behavior for `reg` with no modifiers differs from what GCC does. GCC will infer the modifier based on the operand value type, while we default to the full register size. +> - on x86 `xmm_reg`: the `x`, `t` and `g` LLVM modifiers are not yet implemented in LLVM (they are supported by GCC only), but this should be a simple change. + +As stated in the previous section, passing an input value smaller than the register width will result in the upper bits of the register containing undefined values. This is not a problem if the inline asm only accesses the lower bits of the register, which can be done by using a template modifier to use a subregister name in the asm code (e.g. `ax` instead of `rax`). Since this an easy pitfall, the compiler will suggest a template modifier to use where appropriate given the input type. If all references to an operand already have modifiers then the warning is suppressed for that operand. + +[llvm-argmod]: http://llvm.org/docs/LangRef.html#asm-template-argument-modifiers + +## Options + +Flags are used to further influence the behavior of the inline assembly block. +Currently the following options are defined: +- `pure`: The `asm` block has no side effects, and its outputs depend only on its direct inputs (i.e. the values themselves, not what they point to) or values read from memory (unless the `nomem` options is also set). This allows the compiler to execute the `asm` block fewer times than specified in the program (e.g. by hoisting it out of a loop) or even eliminate it entirely if the outputs are not used. +- `nomem`: The `asm` blocks does not read or write to any memory. This allows the compiler to cache the values of modified global variables in registers across the `asm` block since it knows that they are not read or written to by the `asm`. +- `readonly`: The `asm` block does not write to any memory. This allows the compiler to cache the values of unmodified global variables in registers across the `asm` block since it knows that they are not written to by the `asm`. +- `preserves_flags`: The `asm` block does not modify the flags register (defined in the rules below). This allows the compiler to avoid recomputing the condition flags after the `asm` block. +- `noreturn`: The `asm` block never returns, and its return type is defined as `!` (never). Behavior is undefined if execution falls through past the end of the asm code. A `noreturn` asm block behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked. +- `nostack`: The `asm` block does not push data to the stack, or write to the stack red-zone (if supported by the target). If this option is *not* used then the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call. +- `att_syntax`: This option is only valid on x86, and causes the assembler to use the `.att_syntax prefix` mode of the GNU assembler. Register operands are substituted in with a leading `%`. + +The compiler performs some additional checks on options: +- The `nomem` and `readonly` options are mutually exclusive: it is a compile-time error to specify both. +- The `pure` option must be combined with either the `nomem` or `readonly` options, otherwise a compile-time error is emitted. +- It is a compile-time error to specify `pure` on an asm block with no outputs or only discarded outputs (`_`). +- It is a compile-time error to specify `noreturn` on an asm block with outputs. + +## Rules for inline assembly + +- Any registers not specified as inputs will contain an undefined value on entry to the asm block. + - An "undefined value" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture. Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code). +- Any registers not specified as outputs must have the same value upon exiting the asm block as they had on entry, otherwise behavior is undefined. + - This only applies to registers which can be specified as an input or output. Other registers follow target-specific rules. + - Note that a `lateout` may be allocated to the same register as an `in`, in which case this rule does not apply. Code should not rely on this however since it depends on the results of register allocation. +- Behavior is undefined if execution unwinds out of an asm block. + - This also applies if the assembly code calls a function which then unwinds. +- The set of memory locations that assembly code is allowed the read and write are the same as those allowed for an FFI function. + - Refer to the unsafe code guidelines for the exact rules. + - If the `readonly` option is set, then only memory reads are allowed. + - If the `nomem` option is set then no reads or writes to memory are allowed. + - These rules do not apply to memory which is private to the asm code, such as stack space allocated within the asm block. +- The compiler cannot assume that the instructions in the asm are the ones that will actually end up executed. + - This effectively means that the compiler must treat the `asm!` as a black box and only take the interface specification into account, not the instructions themselves. + - Runtime code patching is allowed, via target-specific mechanisms (outside the scope of this RFC). +- Unless the `nostack` option is set, asm code is allowed to use stack space below the stack pointer. + - On entry to the asm block the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call. + - You are responsible for making sure you don't overflow the stack (e.g. use stack probing to ensure you hit a guard page). + - You should adjust the stack pointer when allocating stack memory as required by the target ABI. + - The stack pointer must be restored to its original value before leaving the asm block. +- If the `noreturn` option is set then behavior is undefined if execution falls through to the end of the asm block. +- If the `pure` option is set then behavior is undefined if the `asm` has side-effects other than its direct outputs. Behavior is also undefined if two executions of the `asm` code with the same inputs result in different outputs. + - When used with the `nomem` option, "inputs" are just the direct inputs of the `asm!`. + - When used with the `readonly` option, "inputs" comprise the direct inputs of the `asm!` and any memory that the `asm!` block is allowed to read. +- These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set: + - x86 + - Status flags in `EFLAGS` (CF, PF, AF, ZF, SF, OF). + - Floating-point status word (all). + - Floating-point exception flags in `MXCSR` (PE, UE, OE, ZE, DE, IE). + - ARM + - Condition flags in `CPSR` (N, Z, C, V) + - Saturation flag in `CPSR` (Q) + - Greater than or equal flags in `CPSR` (GE). + - Condition flags in `FPSCR` (N, Z, C, V) + - Saturation flag in `FPSCR` (QC) + - Floating-point exception flags in `FPSCR` (IDC, IXC, UFC, OFC, DZC, IOC). + - AArch64 + - Condition flags (`NZCV` register). + - Floating-point status (`FPSR` register). + - RISC-V + - Floating-point exception flags in `fcsr` (`fflags`). +- On x86, the direction flag (DF in `EFLAGS`) is clear on entry to an asm block and must be clear on exit. + - Behavior is undefined if the direction flag is set on exiting an asm block. +- The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting an `asm!` block. + - This means that `asm!` blocks that never return (even if not marked `noreturn`) don't need to preserve these registers. + - When returning to a different `asm!` block than you entered (e.g. for context switching), these registers must contain the value they had upon entering the `asm!` block that you are *exiting*. + - You cannot exit an `asm!` block that has not been entered. Neither can you exit an `asm!` block that has already been exited. + - You are responsible for switching any target-specific state (e.g. thread-local storage, stack bounds). + - The set of memory locations that you may access is the intersection of those allowed by the `asm!` blocks you entered and exited. +- You cannot assume that an `asm!` block will appear exactly once in the output binary. The compiler is allowed to instantiate multiple copies of the `asm!` block, for example when the function containing it is inlined in multiple places. + - As a consequence, you should only use [local labels] inside inline assembly code. Defining symbols in assembly code may lead to assembler and/or linker errors due to duplicate symbol definitions. + +> **Note**: As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call. + +[local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels diff --git a/src/libcore/macros/mod.rs b/src/libcore/macros/mod.rs index 14bfa8bab8939..1d4d7013e9427 100644 --- a/src/libcore/macros/mod.rs +++ b/src/libcore/macros/mod.rs @@ -1293,30 +1293,21 @@ pub(crate) mod builtin { /// [unstable book]: ../unstable-book/library-features/asm.html #[unstable( feature = "asm", - issue = "70173", + issue = "72016", reason = "inline assembly is not stable enough for use and is subject to change" )] - #[cfg_attr( - not(bootstrap), - rustc_deprecated( - since = "1.44.0", - reason = "the syntax of asm! will change soon, use llvm_asm! to avoid breakage", - suggestion = "llvm_asm", - ) - )] #[rustc_builtin_macro] #[macro_export] macro_rules! asm { - ("assembly template" - : $("output"(operand),)* - : $("input"(operand),)* - : $("clobbers",)* - : $("options",)*) => { + ("assembly template", + $(operands,)* + $(options($(option),*))? + ) => { /* compiler built-in */ }; } - /// Inline assembly. + /// LLVM-style inline assembly. /// /// Read the [unstable book] for the usage. /// @@ -1324,7 +1315,7 @@ pub(crate) mod builtin { #[unstable( feature = "llvm_asm", issue = "70173", - reason = "inline assembly is not stable enough for use and is subject to change" + reason = "LLVM-style inline assembly will never be stabilized, prefer using asm! instead" )] #[rustc_builtin_macro] #[macro_export] diff --git a/src/libfmt_macros/lib.rs b/src/libfmt_macros/lib.rs index 29276eead71de..677c027f17b54 100644 --- a/src/libfmt_macros/lib.rs +++ b/src/libfmt_macros/lib.rs @@ -27,6 +27,15 @@ use std::string; use rustc_span::{InnerSpan, Symbol}; +/// The type of format string that we are parsing. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ParseMode { + /// A normal format string as per `format_args!`. + Format, + /// An inline assembly template string for `asm!`. + InlineAsm, +} + #[derive(Copy, Clone)] struct InnerOffset(usize); @@ -163,6 +172,7 @@ pub struct ParseError { /// This is a recursive-descent parser for the sake of simplicity, and if /// necessary there's probably lots of room for improvement performance-wise. pub struct Parser<'a> { + mode: ParseMode, input: &'a str, cur: iter::Peekable>, /// Error messages accumulated during parsing @@ -179,6 +189,8 @@ pub struct Parser<'a> { last_opening_brace: Option, /// Whether the source string is comes from `println!` as opposed to `format!` or `print!` append_newline: bool, + /// Whether this formatting string is a literal or it comes from a macro. + is_literal: bool, } impl<'a> Iterator for Parser<'a> { @@ -201,7 +213,9 @@ impl<'a> Iterator for Parser<'a> { if let Some(end) = self.must_consume('}') { let start = self.to_span_index(pos); let end = self.to_span_index(end + 1); - self.arg_places.push(start.to(end)); + if self.is_literal { + self.arg_places.push(start.to(end)); + } } Some(NextArgument(arg)) } @@ -235,10 +249,13 @@ impl<'a> Parser<'a> { pub fn new( s: &'a str, style: Option, - skips: Vec, + snippet: Option, append_newline: bool, + mode: ParseMode, ) -> Parser<'a> { + let (skips, is_literal) = find_skips_from_snippet(snippet, style); Parser { + mode, input: s, cur: s.char_indices().peekable(), errors: vec![], @@ -248,6 +265,7 @@ impl<'a> Parser<'a> { skips, last_opening_brace: None, append_newline, + is_literal, } } @@ -426,7 +444,10 @@ impl<'a> Parser<'a> { /// Parses an `Argument` structure, or what's contained within braces inside the format string. fn argument(&mut self) -> Argument<'a> { let pos = self.position(); - let format = self.format(); + let format = match self.mode { + ParseMode::Format => self.format(), + ParseMode::InlineAsm => self.inline_asm(), + }; // Resolve position after parsing format spec. let pos = match pos { @@ -574,6 +595,36 @@ impl<'a> Parser<'a> { spec } + /// Parses an inline assembly template modifier at the current position, returning the modifier + /// in the `ty` field of the `FormatSpec` struct. + fn inline_asm(&mut self) -> FormatSpec<'a> { + let mut spec = FormatSpec { + fill: None, + align: AlignUnknown, + flags: 0, + precision: CountImplied, + precision_span: None, + width: CountImplied, + width_span: None, + ty: &self.input[..0], + ty_span: None, + }; + if !self.consume(':') { + return spec; + } + + let ty_span_start = self.cur.peek().map(|(pos, _)| *pos); + spec.ty = self.word(); + let ty_span_end = self.cur.peek().map(|(pos, _)| *pos); + if !spec.ty.is_empty() { + spec.ty_span = ty_span_start + .and_then(|s| ty_span_end.map(|e| (s, e))) + .map(|(start, end)| self.to_span_index(start).to(self.to_span_index(end))); + } + + spec + } + /// Parses a `Count` parameter at the current position. This does not check /// for 'CountIsNextParam' because that is only used in precision, not /// width. @@ -652,5 +703,103 @@ impl<'a> Parser<'a> { } } +/// Finds the indices of all characters that have been processed and differ between the actual +/// written code (code snippet) and the `InternedString` that gets processed in the `Parser` +/// in order to properly synthethise the intra-string `Span`s for error diagnostics. +fn find_skips_from_snippet( + snippet: Option, + str_style: Option, +) -> (Vec, bool) { + let snippet = match snippet { + Some(ref s) if s.starts_with('"') || s.starts_with("r#") => s, + _ => return (vec![], false), + }; + + fn find_skips(snippet: &str, is_raw: bool) -> Vec { + let mut eat_ws = false; + let mut s = snippet.chars().enumerate().peekable(); + let mut skips = vec![]; + while let Some((pos, c)) = s.next() { + match (c, s.peek()) { + // skip whitespace and empty lines ending in '\\' + ('\\', Some((next_pos, '\n'))) if !is_raw => { + eat_ws = true; + skips.push(pos); + skips.push(*next_pos); + let _ = s.next(); + } + ('\\', Some((next_pos, '\n' | 'n' | 't'))) if eat_ws => { + skips.push(pos); + skips.push(*next_pos); + let _ = s.next(); + } + (' ' | '\n' | '\t', _) if eat_ws => { + skips.push(pos); + } + ('\\', Some((next_pos, 'n' | 't' | '0' | '\\' | '\'' | '\"'))) => { + skips.push(*next_pos); + let _ = s.next(); + } + ('\\', Some((_, 'x'))) if !is_raw => { + for _ in 0..3 { + // consume `\xAB` literal + if let Some((pos, _)) = s.next() { + skips.push(pos); + } else { + break; + } + } + } + ('\\', Some((_, 'u'))) if !is_raw => { + if let Some((pos, _)) = s.next() { + skips.push(pos); + } + if let Some((next_pos, next_c)) = s.next() { + if next_c == '{' { + skips.push(next_pos); + let mut i = 0; // consume up to 6 hexanumeric chars + closing `}` + while let (Some((next_pos, c)), true) = (s.next(), i < 7) { + if c.is_digit(16) { + skips.push(next_pos); + } else if c == '}' { + skips.push(next_pos); + break; + } else { + break; + } + i += 1; + } + } else if next_c.is_digit(16) { + skips.push(next_pos); + // We suggest adding `{` and `}` when appropriate, accept it here as if + // it were correct + let mut i = 0; // consume up to 6 hexanumeric chars + while let (Some((next_pos, c)), _) = (s.next(), i < 6) { + if c.is_digit(16) { + skips.push(next_pos); + } else { + break; + } + i += 1; + } + } + } + } + _ if eat_ws => { + // `take_while(|c| c.is_whitespace())` + eat_ws = false; + } + _ => {} + } + } + skips + } + + let r_start = str_style.map(|r| r + 1).unwrap_or(0); + let r_end = str_style.map(|r| r).unwrap_or(0); + let s = &snippet[r_start + 1..snippet.len() - r_end - 1]; + (find_skips(s, str_style.is_some()), true) +} + #[cfg(test)] mod tests; diff --git a/src/libfmt_macros/tests.rs b/src/libfmt_macros/tests.rs index 98c2a17f0dd54..9932c1df7a935 100644 --- a/src/libfmt_macros/tests.rs +++ b/src/libfmt_macros/tests.rs @@ -1,7 +1,7 @@ use super::*; fn same(fmt: &'static str, p: &[Piece<'static>]) { - let parser = Parser::new(fmt, None, vec![], false); + let parser = Parser::new(fmt, None, None, false, ParseMode::Format); assert_eq!(parser.collect::>>(), p); } @@ -20,7 +20,7 @@ fn fmtdflt() -> FormatSpec<'static> { } fn musterr(s: &str) { - let mut p = Parser::new(s, None, vec![], false); + let mut p = Parser::new(s, None, None, false, ParseMode::Format); p.next(); assert!(!p.errors.is_empty()); } diff --git a/src/librustc_ast/Cargo.toml b/src/librustc_ast/Cargo.toml index 867bcf7923264..7d105f9e8863d 100644 --- a/src/librustc_ast/Cargo.toml +++ b/src/librustc_ast/Cargo.toml @@ -19,3 +19,4 @@ rustc_index = { path = "../librustc_index" } rustc_lexer = { path = "../librustc_lexer" } rustc_macros = { path = "../librustc_macros" } smallvec = { version = "1.0", features = ["union", "may_dangle"] } +bitflags = "1.2.1" diff --git a/src/librustc_ast/ast.rs b/src/librustc_ast/ast.rs index 15bf4b70e2fdc..7ff835725073d 100644 --- a/src/librustc_ast/ast.rs +++ b/src/librustc_ast/ast.rs @@ -1121,7 +1121,7 @@ impl Expr { ExprKind::Break(..) => ExprPrecedence::Break, ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, - ExprKind::LlvmInlineAsm(..) => ExprPrecedence::InlineAsm, + ExprKind::InlineAsm(..) | ExprKind::LlvmInlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::MacCall(..) => ExprPrecedence::Mac, ExprKind::Struct(..) => ExprPrecedence::Struct, ExprKind::Repeat(..) => ExprPrecedence::Repeat, @@ -1250,6 +1250,8 @@ pub enum ExprKind { /// A `return`, with an optional value to be returned. Ret(Option>), + /// Output of the `asm!()` macro. + InlineAsm(InlineAsm), /// Output of the `llvm_asm!()` macro. LlvmInlineAsm(P), @@ -1864,6 +1866,112 @@ pub enum TraitObjectSyntax { None, } +/// Inline assembly operand explicit register or register class. +/// +/// E.g., `"eax"` as in `asm!("mov eax, 2", out("eax") result)`. +#[derive(Clone, Copy, RustcEncodable, RustcDecodable, Debug)] +pub enum InlineAsmRegOrRegClass { + Reg(Symbol), + RegClass(Symbol), +} + +bitflags::bitflags! { + #[derive(RustcEncodable, RustcDecodable, HashStable_Generic)] + pub struct InlineAsmOptions: u8 { + const PURE = 1 << 0; + const NOMEM = 1 << 1; + const READONLY = 1 << 2; + const PRESERVES_FLAGS = 1 << 3; + const NORETURN = 1 << 4; + const NOSTACK = 1 << 5; + const ATT_SYNTAX = 1 << 6; + } +} + +#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub enum InlineAsmTemplatePiece { + String(String), + Placeholder { operand_idx: usize, modifier: Option, span: Span }, +} + +impl fmt::Display for InlineAsmTemplatePiece { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::String(s) => { + for c in s.chars() { + match c { + '{' => f.write_str("{{")?, + '}' => f.write_str("}}")?, + _ => write!(f, "{}", c.escape_debug())?, + } + } + Ok(()) + } + Self::Placeholder { operand_idx, modifier: Some(modifier), .. } => { + write!(f, "{{{}:{}}}", operand_idx, modifier) + } + Self::Placeholder { operand_idx, modifier: None, .. } => { + write!(f, "{{{}}}", operand_idx) + } + } + } +} + +impl InlineAsmTemplatePiece { + /// Rebuilds the asm template string from its pieces. + pub fn to_string(s: &[Self]) -> String { + use fmt::Write; + let mut out = String::new(); + for p in s.iter() { + let _ = write!(out, "{}", p); + } + out + } +} + +/// Inline assembly operand. +/// +/// E.g., `out("eax") result` as in `asm!("mov eax, 2", out("eax") result)`. +#[derive(Clone, RustcEncodable, RustcDecodable, Debug)] +pub enum InlineAsmOperand { + In { + reg: InlineAsmRegOrRegClass, + expr: P, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: P, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: P, + out_expr: Option>, + }, + Const { + expr: P, + }, + Sym { + expr: P, + }, +} + +/// Inline assembly. +/// +/// E.g., `asm!("NOP");`. +#[derive(Clone, RustcEncodable, RustcDecodable, Debug)] +pub struct InlineAsm { + pub template: Vec, + pub operands: Vec<(InlineAsmOperand, Span)>, + pub options: InlineAsmOptions, +} + /// Inline assembly dialect. /// /// E.g., `"intel"` as in `llvm_asm!("mov eax, 2" : "={eax}"(result) : : : "intel")`. diff --git a/src/librustc_ast/mut_visit.rs b/src/librustc_ast/mut_visit.rs index d533aecf2df6b..2c575c3e28861 100644 --- a/src/librustc_ast/mut_visit.rs +++ b/src/librustc_ast/mut_visit.rs @@ -1205,6 +1205,27 @@ pub fn noop_visit_expr(Expr { kind, id, span, attrs }: &mut Expr, ExprKind::Ret(expr) => { visit_opt(expr, |expr| vis.visit_expr(expr)); } + ExprKind::InlineAsm(asm) => { + for (op, _) in &mut asm.operands { + match op { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr, .. } + | InlineAsmOperand::Sym { expr, .. } => vis.visit_expr(expr), + InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + vis.visit_expr(expr); + } + } + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + vis.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + vis.visit_expr(out_expr); + } + } + } + } + } ExprKind::LlvmInlineAsm(asm) => { let LlvmInlineAsm { asm: _, diff --git a/src/librustc_ast/visit.rs b/src/librustc_ast/visit.rs index 63f483663bfa6..41c02734442a4 100644 --- a/src/librustc_ast/visit.rs +++ b/src/librustc_ast/visit.rs @@ -818,6 +818,27 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { } ExprKind::MacCall(ref mac) => visitor.visit_mac(mac), ExprKind::Paren(ref subexpression) => visitor.visit_expr(subexpression), + ExprKind::InlineAsm(ref ia) => { + for (op, _) in &ia.operands { + match op { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr, .. } + | InlineAsmOperand::Sym { expr, .. } => visitor.visit_expr(expr), + InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + visitor.visit_expr(expr); + } + } + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + visitor.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + visitor.visit_expr(out_expr); + } + } + } + } + } ExprKind::LlvmInlineAsm(ref ia) => { for &(_, ref input) in &ia.inputs { visitor.visit_expr(input) diff --git a/src/librustc_ast_lowering/expr.rs b/src/librustc_ast_lowering/expr.rs index 8da3aa633b85f..856387421d90c 100644 --- a/src/librustc_ast_lowering/expr.rs +++ b/src/librustc_ast_lowering/expr.rs @@ -3,6 +3,7 @@ use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericAr use rustc_ast::ast::*; use rustc_ast::attr; use rustc_ast::ptr::P as AstP; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::thin_vec::ThinVec; use rustc_errors::struct_span_err; @@ -10,6 +11,9 @@ use rustc_hir as hir; use rustc_hir::def::Res; use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned}; use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_target::asm; +use std::collections::hash_map::Entry; +use std::fmt::Write; impl<'hir> LoweringContext<'_, 'hir> { fn lower_exprs(&mut self, exprs: &[AstP]) -> &'hir [hir::Expr<'hir>] { @@ -175,7 +179,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let e = e.as_ref().map(|x| self.lower_expr(x)); hir::ExprKind::Ret(e) } - ExprKind::LlvmInlineAsm(ref asm) => self.lower_expr_asm(asm), + ExprKind::InlineAsm(ref asm) => self.lower_expr_asm(e.span, asm), + ExprKind::LlvmInlineAsm(ref asm) => self.lower_expr_llvm_asm(asm), ExprKind::Struct(ref path, ref fields, ref maybe_expr) => { let maybe_expr = maybe_expr.as_ref().map(|x| self.lower_expr(x)); hir::ExprKind::Struct( @@ -968,7 +973,303 @@ impl<'hir> LoweringContext<'_, 'hir> { result } - fn lower_expr_asm(&mut self, asm: &LlvmInlineAsm) -> hir::ExprKind<'hir> { + fn lower_expr_asm(&mut self, sp: Span, asm: &InlineAsm) -> hir::ExprKind<'hir> { + let asm_arch = if let Some(asm_arch) = self.sess.asm_arch { + asm_arch + } else { + struct_span_err!(self.sess, sp, E0472, "asm! is unsupported on this target").emit(); + return hir::ExprKind::Err; + }; + if asm.options.contains(InlineAsmOptions::ATT_SYNTAX) { + match asm_arch { + asm::InlineAsmArch::X86 | asm::InlineAsmArch::X86_64 => {} + _ => self + .sess + .struct_span_err(sp, "the `att_syntax` option is only supported on x86") + .emit(), + } + } + + // Lower operands to HIR, filter_map skips any operands with invalid + // register classes. + let sess = self.sess; + let operands: Vec<_> = asm + .operands + .iter() + .filter_map(|(op, op_sp)| { + let lower_reg = |reg| { + Some(match reg { + InlineAsmRegOrRegClass::Reg(s) => asm::InlineAsmRegOrRegClass::Reg( + asm::InlineAsmReg::parse( + asm_arch, + |feature| { + self.sess.target_features.contains(&Symbol::intern(feature)) + }, + s, + ) + .map_err(|e| { + let msg = format!("invalid register `{}`: {}", s.as_str(), e); + sess.struct_span_err(*op_sp, &msg).emit(); + }) + .ok()?, + ), + InlineAsmRegOrRegClass::RegClass(s) => { + asm::InlineAsmRegOrRegClass::RegClass( + asm::InlineAsmRegClass::parse(asm_arch, s) + .map_err(|e| { + let msg = format!( + "invalid register class `{}`: {}", + s.as_str(), + e + ); + sess.struct_span_err(*op_sp, &msg).emit(); + }) + .ok()?, + ) + } + }) + }; + let op = match op { + InlineAsmOperand::In { reg, expr } => hir::InlineAsmOperand::In { + reg: lower_reg(*reg)?, + expr: self.lower_expr_mut(expr), + }, + InlineAsmOperand::Out { reg, late, expr } => hir::InlineAsmOperand::Out { + reg: lower_reg(*reg)?, + late: *late, + expr: expr.as_ref().map(|expr| self.lower_expr_mut(expr)), + }, + InlineAsmOperand::InOut { reg, late, expr } => hir::InlineAsmOperand::InOut { + reg: lower_reg(*reg)?, + late: *late, + expr: self.lower_expr_mut(expr), + }, + InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + hir::InlineAsmOperand::SplitInOut { + reg: lower_reg(*reg)?, + late: *late, + in_expr: self.lower_expr_mut(in_expr), + out_expr: out_expr.as_ref().map(|expr| self.lower_expr_mut(expr)), + } + } + InlineAsmOperand::Const { expr } => { + hir::InlineAsmOperand::Const { expr: self.lower_expr_mut(expr) } + } + InlineAsmOperand::Sym { expr } => { + hir::InlineAsmOperand::Sym { expr: self.lower_expr_mut(expr) } + } + }; + Some(op) + }) + .collect(); + + // Stop if there were any errors when lowering the register classes + if operands.len() != asm.operands.len() { + return hir::ExprKind::Err; + } + + // Validate template modifiers against the register classes for the operands + for p in &asm.template { + if let InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier: Some(modifier), + span: placeholder_span, + } = *p + { + let op_sp = asm.operands[operand_idx].1; + match &operands[operand_idx] { + hir::InlineAsmOperand::In { reg, .. } + | hir::InlineAsmOperand::Out { reg, .. } + | hir::InlineAsmOperand::InOut { reg, .. } + | hir::InlineAsmOperand::SplitInOut { reg, .. } => { + let class = reg.reg_class(); + let valid_modifiers = class.valid_modifiers(asm_arch); + if !valid_modifiers.contains(&modifier) { + let mut err = sess.struct_span_err( + placeholder_span, + "invalid asm template modifier for this register class", + ); + err.span_label(placeholder_span, "template modifier"); + err.span_label(op_sp, "argument"); + if !valid_modifiers.is_empty() { + let mut mods = format!("`{}`", valid_modifiers[0]); + for m in &valid_modifiers[1..] { + let _ = write!(mods, ", `{}`", m); + } + err.note(&format!( + "the `{}` register class supports \ + the following template modifiers: {}", + class.name(), + mods + )); + } else { + err.note(&format!( + "the `{}` register class does not support template modifiers", + class.name() + )); + } + err.emit(); + } + } + hir::InlineAsmOperand::Const { .. } => { + let mut err = sess.struct_span_err( + placeholder_span, + "asm template modifiers are not allowed for `const` arguments", + ); + err.span_label(placeholder_span, "template modifier"); + err.span_label(op_sp, "argument"); + err.emit(); + } + hir::InlineAsmOperand::Sym { .. } => { + let mut err = sess.struct_span_err( + placeholder_span, + "asm template modifiers are not allowed for `sym` arguments", + ); + err.span_label(placeholder_span, "template modifier"); + err.span_label(op_sp, "argument"); + err.emit(); + } + } + } + } + + let mut used_input_regs = FxHashMap::default(); + let mut used_output_regs = FxHashMap::default(); + for (idx, op) in operands.iter().enumerate() { + let op_sp = asm.operands[idx].1; + if let Some(reg) = op.reg() { + // Validate register classes against currently enabled target + // features. We check that at least one type is available for + // the current target. + let reg_class = reg.reg_class(); + let mut required_features = vec![]; + for &(_, feature) in reg_class.supported_types(asm_arch) { + if let Some(feature) = feature { + if self.sess.target_features.contains(&Symbol::intern(feature)) { + required_features.clear(); + break; + } else { + required_features.push(feature); + } + } else { + required_features.clear(); + break; + } + } + required_features.sort(); + required_features.dedup(); + match &required_features[..] { + [] => {} + [feature] => { + let msg = format!( + "register class `{}` requires the `{}` target feature", + reg_class.name(), + feature + ); + sess.struct_span_err(op_sp, &msg).emit(); + } + features => { + let msg = format!( + "register class `{}` requires at least one target feature: {}", + reg_class.name(), + features.join(", ") + ); + sess.struct_span_err(op_sp, &msg).emit(); + } + } + + // Check for conflicts between explicit register operands. + if let asm::InlineAsmRegOrRegClass::Reg(reg) = reg { + let (input, output) = match op { + hir::InlineAsmOperand::In { .. } => (true, false), + // Late output do not conflict with inputs, but normal outputs do + hir::InlineAsmOperand::Out { late, .. } => (!late, true), + hir::InlineAsmOperand::InOut { .. } + | hir::InlineAsmOperand::SplitInOut { .. } => (true, true), + hir::InlineAsmOperand::Const { .. } | hir::InlineAsmOperand::Sym { .. } => { + unreachable!() + } + }; + + // Flag to output the error only once per operand + let mut skip = false; + reg.overlapping_regs(|r| { + let mut check = |used_regs: &mut FxHashMap, + input| { + match used_regs.entry(r) { + Entry::Occupied(o) => { + if !skip { + skip = true; + + let idx2 = *o.get(); + let op2 = &operands[idx2]; + let op_sp2 = asm.operands[idx2].1; + let reg2 = match op2.reg() { + Some(asm::InlineAsmRegOrRegClass::Reg(r)) => r, + _ => unreachable!(), + }; + + let msg = format!( + "register `{}` conflicts with register `{}`", + reg.name(), + reg2.name() + ); + let mut err = sess.struct_span_err(op_sp, &msg); + err.span_label( + op_sp, + &format!("register `{}`", reg.name()), + ); + err.span_label( + op_sp2, + &format!("register `{}`", reg2.name()), + ); + + match (op, op2) { + ( + hir::InlineAsmOperand::In { .. }, + hir::InlineAsmOperand::Out { late, .. }, + ) + | ( + hir::InlineAsmOperand::Out { late, .. }, + hir::InlineAsmOperand::In { .. }, + ) => { + assert!(!*late); + let out_op_sp = if input { op_sp2 } else { op_sp }; + let msg = &format!( + "use `lateout` instead of \ + `out` to avoid conflict" + ); + err.span_help(out_op_sp, msg); + } + _ => {} + } + + err.emit(); + } + } + Entry::Vacant(v) => { + v.insert(idx); + } + } + }; + if input { + check(&mut used_input_regs, true); + } + if output { + check(&mut used_output_regs, false); + } + }); + } + } + } + + let operands = self.arena.alloc_from_iter(operands); + let template = self.arena.alloc_from_iter(asm.template.iter().cloned()); + let hir_asm = hir::InlineAsm { template, operands, options: asm.options }; + hir::ExprKind::InlineAsm(self.arena.alloc(hir_asm)) + } + + fn lower_expr_llvm_asm(&mut self, asm: &LlvmInlineAsm) -> hir::ExprKind<'hir> { let inner = hir::LlvmInlineAsmInner { inputs: asm.inputs.iter().map(|&(c, _)| c).collect(), outputs: asm diff --git a/src/librustc_ast_pretty/Cargo.toml b/src/librustc_ast_pretty/Cargo.toml index 81d98721089f5..6c076d2c5b866 100644 --- a/src/librustc_ast_pretty/Cargo.toml +++ b/src/librustc_ast_pretty/Cargo.toml @@ -13,3 +13,4 @@ doctest = false log = "0.4" rustc_span = { path = "../librustc_span" } rustc_ast = { path = "../librustc_ast" } +rustc_target = { path = "../librustc_target" } diff --git a/src/librustc_ast_pretty/pprust.rs b/src/librustc_ast_pretty/pprust.rs index e6803fdd7f157..872126646f33d 100644 --- a/src/librustc_ast_pretty/pprust.rs +++ b/src/librustc_ast_pretty/pprust.rs @@ -4,6 +4,8 @@ use crate::pp::{self, Breaks}; use rustc_ast::ast::{self, BlockCheckMode, PatKind, RangeEnd, RangeSyntax}; use rustc_ast::ast::{Attribute, GenericArg, MacArgs}; use rustc_ast::ast::{GenericBound, SelfKind, TraitBoundModifier}; +use rustc_ast::ast::{InlineAsmOperand, InlineAsmRegOrRegClass}; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_ast::attr; use rustc_ast::ptr::P; use rustc_ast::token::{self, BinOpToken, DelimToken, Nonterminal, Token, TokenKind}; @@ -2014,6 +2016,119 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(expr, parser::PREC_JUMP); } } + ast::ExprKind::InlineAsm(ref a) => { + enum AsmArg<'a> { + Template(String), + Operand(&'a InlineAsmOperand), + Options(InlineAsmOptions), + } + + let mut args = vec![]; + args.push(AsmArg::Template(InlineAsmTemplatePiece::to_string(&a.template))); + args.extend(a.operands.iter().map(|(o, _)| AsmArg::Operand(o))); + if !a.options.is_empty() { + args.push(AsmArg::Options(a.options)); + } + + self.word("asm!"); + self.popen(); + self.commasep(Consistent, &args, |s, arg| match arg { + AsmArg::Template(template) => s.print_string(&template, ast::StrStyle::Cooked), + AsmArg::Operand(op) => { + let print_reg_or_class = |s: &mut Self, r: &InlineAsmRegOrRegClass| match r + { + InlineAsmRegOrRegClass::Reg(r) => { + s.print_string(&r.as_str(), ast::StrStyle::Cooked) + } + InlineAsmRegOrRegClass::RegClass(r) => s.word(r.to_string()), + }; + match op { + InlineAsmOperand::In { reg, expr } => { + s.word("in"); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + s.print_expr(expr); + } + InlineAsmOperand::Out { reg, late, expr } => { + s.word(if *late { "lateout" } else { "out" }); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + match expr { + Some(expr) => s.print_expr(expr), + None => s.word("_"), + } + } + InlineAsmOperand::InOut { reg, late, expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + s.print_expr(expr); + } + InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + s.print_expr(in_expr); + s.space(); + s.word_space("=>"); + match out_expr { + Some(out_expr) => s.print_expr(out_expr), + None => s.word("_"), + } + } + InlineAsmOperand::Const { expr } => { + s.word("const"); + s.space(); + s.print_expr(expr); + } + InlineAsmOperand::Sym { expr } => { + s.word("sym"); + s.space(); + s.print_expr(expr); + } + } + } + AsmArg::Options(opts) => { + s.word("options"); + s.popen(); + let mut options = vec![]; + if opts.contains(InlineAsmOptions::PURE) { + options.push("pure"); + } + if opts.contains(InlineAsmOptions::NOMEM) { + options.push("nomem"); + } + if opts.contains(InlineAsmOptions::READONLY) { + options.push("readonly"); + } + if opts.contains(InlineAsmOptions::PRESERVES_FLAGS) { + options.push("preserves_flags"); + } + if opts.contains(InlineAsmOptions::NORETURN) { + options.push("noreturn"); + } + if opts.contains(InlineAsmOptions::NOSTACK) { + options.push("nostack"); + } + if opts.contains(InlineAsmOptions::ATT_SYNTAX) { + options.push("att_syntax"); + } + s.commasep(Inconsistent, &options, |s, &opt| { + s.word(opt); + }); + s.pclose(); + } + }); + self.pclose(); + } ast::ExprKind::LlvmInlineAsm(ref a) => { self.s.word("llvm_asm!"); self.popen(); diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs new file mode 100644 index 0000000000000..c29739248976c --- /dev/null +++ b/src/librustc_builtin_macros/asm.rs @@ -0,0 +1,537 @@ +use fmt_macros as parse; + +use rustc_ast::ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_expand::base::{self, *}; +use rustc_parse::parser::Parser; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::{InnerSpan, Span}; + +struct AsmArgs { + template: P, + operands: Vec<(ast::InlineAsmOperand, Span)>, + named_args: FxHashMap, + reg_args: FxHashSet, + options: ast::InlineAsmOptions, + options_span: Option, +} + +fn parse_args<'a>( + ecx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result> { + let mut p = ecx.new_parser_from_tts(tts); + + if p.token == token::Eof { + return Err(ecx.struct_span_err(sp, "requires at least a template string argument")); + } + + // Detect use of the legacy llvm_asm! syntax (which used to be called asm!) + if p.look_ahead(1, |t| *t == token::Colon || *t == token::ModSep) { + let mut err = ecx.struct_span_err(sp, "legacy asm! syntax is no longer supported"); + + // Find the span of the "asm!" so that we can offer an automatic suggestion + let asm_span = sp.from_inner(InnerSpan::new(0, 4)); + if let Ok(s) = ecx.source_map().span_to_snippet(asm_span) { + if s == "asm!" { + err.span_suggestion( + asm_span, + "replace with", + "llvm_asm!".into(), + Applicability::MachineApplicable, + ); + } + } + return Err(err); + } + + let template = p.parse_expr()?; + let mut args = AsmArgs { + template, + operands: vec![], + named_args: FxHashMap::default(), + reg_args: FxHashSet::default(), + options: ast::InlineAsmOptions::empty(), + options_span: None, + }; + + let mut first = true; + while p.token != token::Eof { + if !p.eat(&token::Comma) { + if first { + // After `asm!(""` 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); + return Err(err); + } else { + // ...after that delegate to `expect` to also include the other expected tokens. + return Err(p.expect(&token::Comma).err().unwrap()); + } + } + first = false; + if p.token == token::Eof { + break; + } // accept trailing commas + + // Parse options + if p.eat(&token::Ident(sym::options, false)) { + parse_options(&mut p, &mut args)?; + continue; + } + + let span_start = p.token.span; + + // Parse operand names + let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) { + let (ident, _) = p.token.ident().unwrap(); + p.bump(); + p.expect(&token::Eq)?; + Some(ident.name) + } else { + None + }; + + let mut explicit_reg = false; + let op = if p.eat(&token::Ident(kw::In, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + ast::InlineAsmOperand::In { reg, expr } + } else if p.eat(&token::Ident(sym::out, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::Out { reg, expr, late: false } + } else if p.eat(&token::Ident(sym::lateout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::Out { reg, expr, late: true } + } else if p.eat(&token::Ident(sym::inout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + if p.eat(&token::FatArrow) { + let out_expr = + if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false } + } else { + ast::InlineAsmOperand::InOut { reg, expr, late: false } + } + } else if p.eat(&token::Ident(sym::inlateout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + if p.eat(&token::FatArrow) { + let out_expr = + if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true } + } else { + ast::InlineAsmOperand::InOut { reg, expr, late: true } + } + } 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))?; + let expr = p.parse_expr()?; + match expr.kind { + ast::ExprKind::Path(..) => {} + _ => { + let err = ecx + .struct_span_err(expr.span, "argument to `sym` must be a path expression"); + return Err(err); + } + } + ast::InlineAsmOperand::Sym { expr } + }; + + let span = span_start.to(p.prev_token.span); + let slot = args.operands.len(); + args.operands.push((op, span)); + + // Validate the order of named, positional & explicit register operands and options. We do + // this at the end once we have the full span of the argument available. + if let Some(options_span) = args.options_span { + ecx.struct_span_err(span, "arguments are not allowed after options") + .span_label(options_span, "previous options") + .span_label(span, "argument") + .emit(); + } + if explicit_reg { + if name.is_some() { + ecx.struct_span_err(span, "explicit register arguments cannot have names").emit(); + } + args.reg_args.insert(slot); + } else if let Some(name) = name { + if let Some(&prev) = args.named_args.get(&name) { + ecx.struct_span_err(span, &format!("duplicate argument named `{}`", name)) + .span_label(args.operands[prev].1, "previously here") + .span_label(span, "duplicate argument") + .emit(); + continue; + } + if !args.reg_args.is_empty() { + let mut err = ecx.struct_span_err( + span, + "named arguments cannot follow explicit register arguments", + ); + err.span_label(span, "named argument"); + for pos in &args.reg_args { + err.span_label(args.operands[*pos].1, "explicit register argument"); + } + err.emit(); + } + args.named_args.insert(name, slot); + } else { + if !args.named_args.is_empty() || !args.reg_args.is_empty() { + let mut err = ecx.struct_span_err( + span, + "positional arguments cannot follow named arguments \ + or explicit register arguments", + ); + err.span_label(span, "positional argument"); + for pos in args.named_args.values() { + err.span_label(args.operands[*pos].1, "named argument"); + } + for pos in &args.reg_args { + err.span_label(args.operands[*pos].1, "explicit register argument"); + } + err.emit(); + } + } + } + + if args.options.contains(ast::InlineAsmOptions::NOMEM) + && args.options.contains(ast::InlineAsmOptions::READONLY) + { + let span = args.options_span.unwrap(); + ecx.struct_span_err(span, "the `nomem` and `readonly` options are mutually exclusive") + .emit(); + } + if args.options.contains(ast::InlineAsmOptions::PURE) + && args.options.contains(ast::InlineAsmOptions::NORETURN) + { + let span = args.options_span.unwrap(); + ecx.struct_span_err(span, "the `pure` and `noreturn` options are mutually exclusive") + .emit(); + } + if args.options.contains(ast::InlineAsmOptions::PURE) + && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY) + { + let span = args.options_span.unwrap(); + ecx.struct_span_err( + span, + "the `pure` option must be combined with either `nomem` or `readonly`", + ) + .emit(); + } + + let mut have_real_output = false; + let mut outputs_sp = vec![]; + for (op, op_sp) in &args.operands { + match op { + ast::InlineAsmOperand::Out { expr, .. } + | ast::InlineAsmOperand::SplitInOut { out_expr: expr, .. } => { + outputs_sp.push(*op_sp); + have_real_output |= expr.is_some(); + } + ast::InlineAsmOperand::InOut { .. } => { + outputs_sp.push(*op_sp); + have_real_output = true; + } + _ => {} + } + } + if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output { + ecx.struct_span_err( + args.options_span.unwrap(), + "asm with `pure` option must have at least one output", + ) + .emit(); + } + if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() { + let err = ecx + .struct_span_err(outputs_sp, "asm outputs are not allowed with the `noreturn` option"); + + // Bail out now since this is likely to confuse MIR + return Err(err); + } + + Ok(args) +} + +fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), DiagnosticBuilder<'a>> { + let span_start = p.prev_token.span; + + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + + while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + if p.eat(&token::Ident(sym::pure, false)) { + args.options |= ast::InlineAsmOptions::PURE; + } else if p.eat(&token::Ident(sym::nomem, false)) { + args.options |= ast::InlineAsmOptions::NOMEM; + } else if p.eat(&token::Ident(sym::readonly, false)) { + args.options |= ast::InlineAsmOptions::READONLY; + } else if p.eat(&token::Ident(sym::preserves_flags, false)) { + args.options |= ast::InlineAsmOptions::PRESERVES_FLAGS; + } else if p.eat(&token::Ident(sym::noreturn, false)) { + args.options |= ast::InlineAsmOptions::NORETURN; + } else if p.eat(&token::Ident(sym::nostack, false)) { + args.options |= ast::InlineAsmOptions::NOSTACK; + } else { + p.expect(&token::Ident(sym::att_syntax, false))?; + args.options |= ast::InlineAsmOptions::ATT_SYNTAX; + } + + // Allow trailing commas + if p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + break; + } + p.expect(&token::Comma)?; + } + + let new_span = span_start.to(p.prev_token.span); + if let Some(options_span) = args.options_span { + p.struct_span_err(new_span, "asm options cannot be specified multiple times") + .span_label(options_span, "previously here") + .span_label(new_span, "duplicate options") + .emit(); + } else { + args.options_span = Some(new_span); + } + + Ok(()) +} + +fn parse_reg<'a>( + p: &mut Parser<'a>, + explicit_reg: &mut bool, +) -> Result> { + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + let result = match p.token.kind { + token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name), + token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { + *explicit_reg = true; + ast::InlineAsmRegOrRegClass::Reg(symbol) + } + _ => { + return Err( + p.struct_span_err(p.token.span, "expected register class or explicit register") + ); + } + }; + p.bump(); + p.expect(&token::CloseDelim(token::DelimToken::Paren))?; + Ok(result) +} + +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); + } + + // 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: FxHashSet = args.named_args.values().cloned().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())) + } + 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(&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(&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 { + 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_none() { + 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; + } + + if let Some(operand_idx) = operand_idx { + used[operand_idx] = true; + template.push(ast::InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier, + span, + }); + } + } + } + } + + let operands = args.operands; + let unused_operands: Vec<_> = used + .into_iter() + .enumerate() + .filter(|&(_, used)| !used) + .map(|(idx, _)| { + if named_pos.contains(&idx) { + // named argument + (operands[idx].1, "named argument never used") + } else { + // positional argument + (operands[idx].1, "argument never used") + } + }) + .collect(); + match unused_operands.len() { + 0 => {} + 1 => { + let (sp, msg) = unused_operands.into_iter().next().unwrap(); + let mut err = ecx.struct_span_err(sp, msg); + err.span_label(sp, msg); + err.emit(); + } + _ => { + let mut err = ecx.struct_span_err( + unused_operands.iter().map(|&(sp, _)| sp).collect::>(), + "multiple unused asm arguments", + ); + for (sp, msg) in unused_operands { + err.span_label(sp, msg); + } + err.emit(); + } + } + + let inline_asm = ast::InlineAsm { template, operands, options: args.options }; + P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::InlineAsm(inline_asm), + span: sp, + attrs: ast::AttrVec::new(), + }) +} + +pub fn expand_asm<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box { + match parse_args(ecx, sp, tts) { + Ok(args) => MacEager::expr(expand_preparsed_asm(ecx, sp, args)), + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} diff --git a/src/librustc_builtin_macros/format.rs b/src/librustc_builtin_macros/format.rs index efce62881985e..eed01b262bf0c 100644 --- a/src/librustc_builtin_macros/format.rs +++ b/src/librustc_builtin_macros/format.rs @@ -108,8 +108,6 @@ struct Context<'a, 'b> { arg_spans: Vec, /// All the formatting arguments that have formatting flags set, in order for diagnostics. arg_with_formatting: Vec>, - /// Whether this formatting string is a literal or it comes from a macro. - is_literal: bool, } /// Parses the arguments from the given list of tokens, returning the diagnostic @@ -324,7 +322,7 @@ impl<'a, 'b> Context<'a, 'b> { /// format string. fn report_invalid_references(&self, numbered_position_args: bool) { let mut e; - let sp = if self.is_literal { + let sp = if !self.arg_spans.is_empty() { // Point at the formatting arguments. MultiSpan::from_spans(self.arg_spans.clone()) } else { @@ -372,7 +370,7 @@ impl<'a, 'b> Context<'a, 'b> { let reg = refs.pop().unwrap(); (format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg,), pos) }; - if !self.is_literal { + if self.arg_spans.is_empty() { sp = MultiSpan::from_span(self.fmtsp); } @@ -502,11 +500,7 @@ impl<'a, 'b> Context<'a, 'b> { } None => { let msg = format!("there is no argument named `{}`", name); - let sp = if self.is_literal { - *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp) - } else { - self.fmtsp - }; + let sp = *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp); let mut err = self.ecx.struct_span_err(sp, &msg[..]); err.emit(); } @@ -892,110 +886,20 @@ pub fn expand_preparsed_format_args( } }; - let (is_literal, fmt_snippet) = match ecx.source_map().span_to_snippet(fmt_sp) { - Ok(s) => (s.starts_with('"') || s.starts_with("r#"), Some(s)), - _ => (false, None), - }; - let str_style = match fmt_style { ast::StrStyle::Cooked => None, ast::StrStyle::Raw(raw) => Some(raw as usize), }; - /// Finds the indices of all characters that have been processed and differ between the actual - /// written code (code snippet) and the `InternedString` that gets processed in the `Parser` - /// in order to properly synthethise the intra-string `Span`s for error diagnostics. - fn find_skips(snippet: &str, is_raw: bool) -> Vec { - let mut eat_ws = false; - let mut s = snippet.chars().enumerate().peekable(); - let mut skips = vec![]; - while let Some((pos, c)) = s.next() { - match (c, s.peek()) { - // skip whitespace and empty lines ending in '\\' - ('\\', Some((next_pos, '\n'))) if !is_raw => { - eat_ws = true; - skips.push(pos); - skips.push(*next_pos); - let _ = s.next(); - } - ('\\', Some((next_pos, '\n' | 'n' | 't'))) if eat_ws => { - skips.push(pos); - skips.push(*next_pos); - let _ = s.next(); - } - (' ' | '\n' | '\t', _) if eat_ws => { - skips.push(pos); - } - ('\\', Some((next_pos, 'n' | 't' | '0' | '\\' | '\'' | '\"'))) => { - skips.push(*next_pos); - let _ = s.next(); - } - ('\\', Some((_, 'x'))) if !is_raw => { - for _ in 0..3 { - // consume `\xAB` literal - if let Some((pos, _)) = s.next() { - skips.push(pos); - } else { - break; - } - } - } - ('\\', Some((_, 'u'))) if !is_raw => { - if let Some((pos, _)) = s.next() { - skips.push(pos); - } - if let Some((next_pos, next_c)) = s.next() { - if next_c == '{' { - skips.push(next_pos); - let mut i = 0; // consume up to 6 hexanumeric chars + closing `}` - while let (Some((next_pos, c)), true) = (s.next(), i < 7) { - if c.is_digit(16) { - skips.push(next_pos); - } else if c == '}' { - skips.push(next_pos); - break; - } else { - break; - } - i += 1; - } - } else if next_c.is_digit(16) { - skips.push(next_pos); - // We suggest adding `{` and `}` when appropriate, accept it here as if - // it were correct - let mut i = 0; // consume up to 6 hexanumeric chars - while let (Some((next_pos, c)), _) = (s.next(), i < 6) { - if c.is_digit(16) { - skips.push(next_pos); - } else { - break; - } - i += 1; - } - } - } - } - _ if eat_ws => { - // `take_while(|c| c.is_whitespace())` - eat_ws = false; - } - _ => {} - } - } - skips - } - - let skips = if let (true, Some(ref snippet)) = (is_literal, fmt_snippet.as_ref()) { - let r_start = str_style.map(|r| r + 1).unwrap_or(0); - let r_end = str_style.map(|r| r).unwrap_or(0); - let s = &snippet[r_start + 1..snippet.len() - r_end - 1]; - find_skips(s, str_style.is_some()) - } else { - vec![] - }; - let fmt_str = &fmt_str.as_str(); // for the suggestions below - let mut parser = parse::Parser::new(fmt_str, str_style, skips, append_newline); + let fmt_snippet = ecx.source_map().span_to_snippet(fmt_sp).ok(); + let mut parser = parse::Parser::new( + fmt_str, + str_style, + fmt_snippet, + append_newline, + parse::ParseMode::Format, + ); let mut unverified_pieces = Vec::new(); while let Some(piece) = parser.next() { @@ -1048,7 +952,6 @@ pub fn expand_preparsed_format_args( invalid_refs: Vec::new(), arg_spans, arg_with_formatting: Vec::new(), - is_literal, }; // This needs to happen *after* the Parser has consumed all pieces to create all the spans diff --git a/src/librustc_builtin_macros/lib.rs b/src/librustc_builtin_macros/lib.rs index cc77bb73c5ae1..a0f82d65618f4 100644 --- a/src/librustc_builtin_macros/lib.rs +++ b/src/librustc_builtin_macros/lib.rs @@ -19,6 +19,7 @@ use rustc_expand::proc_macro::BangProcMacro; use rustc_span::edition::Edition; use rustc_span::symbol::{sym, Ident}; +mod asm; mod assert; mod cfg; mod cfg_accessible; @@ -61,7 +62,7 @@ pub fn register_builtin_macros(resolver: &mut dyn Resolver, edition: Edition) { } register_bang! { - asm: llvm_asm::expand_llvm_asm, + asm: asm::expand_asm, assert: assert::expand_assert, cfg: cfg::expand_cfg, column: source_util::expand_column, diff --git a/src/librustc_codegen_llvm/asm.rs b/src/librustc_codegen_llvm/asm.rs index 30bf3ce752859..8986ab322c07f 100644 --- a/src/librustc_codegen_llvm/asm.rs +++ b/src/librustc_codegen_llvm/asm.rs @@ -1,14 +1,22 @@ use crate::builder::Builder; use crate::context::CodegenCx; use crate::llvm; +use crate::type_::Type; use crate::type_of::LayoutLlvmExt; use crate::value::Value; +use rustc_ast::ast::LlvmAsmDialect; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::mir::operand::OperandValue; use rustc_codegen_ssa::mir::place::PlaceRef; use rustc_codegen_ssa::traits::*; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; +use rustc_middle::span_bug; +use rustc_middle::ty::layout::TyAndLayout; use rustc_span::Span; +use rustc_target::abi::*; +use rustc_target::asm::*; use libc::{c_char, c_uint}; use log::debug; @@ -40,7 +48,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { indirect_outputs.push(operand.immediate()); } } else { - output_types.push(place.layout.llvm_type(self.cx())); + output_types.push(place.layout.llvm_type(self.cx)); } } if !indirect_outputs.is_empty() { @@ -89,6 +97,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { ia.volatile, ia.alignstack, ia.dialect, + span, ); if r.is_none() { return false; @@ -102,22 +111,214 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { OperandValue::Immediate(v).store(self, place); } - // Store mark in a metadata node so we can map LLVM errors - // back to source locations. See #17552. - unsafe { - let key = "srcloc"; - let kind = llvm::LLVMGetMDKindIDInContext( - self.llcx, - key.as_ptr() as *const c_char, - key.len() as c_uint, - ); + true + } - let val: &'ll Value = self.const_i32(span.ctxt().outer_expn().as_u32() as i32); + fn codegen_inline_asm( + &mut self, + template: &[InlineAsmTemplatePiece], + operands: &[InlineAsmOperandRef<'tcx, Self>], + options: InlineAsmOptions, + span: Span, + ) { + let asm_arch = self.tcx.sess.asm_arch.unwrap(); - llvm::LLVMSetMetadata(r, kind, llvm::LLVMMDNodeInContext(self.llcx, &val, 1)); + // Collect the types of output operands + let mut constraints = vec![]; + let mut output_types = vec![]; + let mut op_idx = FxHashMap::default(); + for (idx, op) in operands.iter().enumerate() { + match *op { + InlineAsmOperandRef::Out { reg, late, place } => { + let ty = if let Some(place) = place { + llvm_fixup_output_type(self.cx, reg.reg_class(), &place.layout) + } else { + // If the output is discarded, we don't really care what + // type is used. We're just using this to tell LLVM to + // reserve the register. + dummy_output_type(self.cx, reg.reg_class()) + }; + output_types.push(ty); + op_idx.insert(idx, constraints.len()); + let prefix = if late { "=" } else { "=&" }; + constraints.push(format!("{}{}", prefix, reg_to_llvm(reg))); + } + InlineAsmOperandRef::InOut { reg, late, in_value, out_place } => { + let ty = if let Some(ref out_place) = out_place { + llvm_fixup_output_type(self.cx, reg.reg_class(), &out_place.layout) + } else { + // LLVM required tied operands to have the same type, + // so we just use the type of the input. + llvm_fixup_output_type(self.cx, reg.reg_class(), &in_value.layout) + }; + output_types.push(ty); + op_idx.insert(idx, constraints.len()); + let prefix = if late { "=" } else { "=&" }; + constraints.push(format!("{}{}", prefix, reg_to_llvm(reg))); + } + _ => {} + } } - true + // Collect input operands + let mut inputs = vec![]; + for (idx, op) in operands.iter().enumerate() { + match *op { + InlineAsmOperandRef::In { reg, value } => { + let value = + llvm_fixup_input(self, value.immediate(), reg.reg_class(), &value.layout); + inputs.push(value); + op_idx.insert(idx, constraints.len()); + constraints.push(reg_to_llvm(reg)); + } + InlineAsmOperandRef::InOut { reg, late: _, in_value, out_place: _ } => { + let value = llvm_fixup_input( + self, + in_value.immediate(), + reg.reg_class(), + &in_value.layout, + ); + inputs.push(value); + constraints.push(format!("{}", op_idx[&idx])); + } + InlineAsmOperandRef::SymFn { instance } => { + inputs.push(self.cx.get_fn(instance)); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); + } + InlineAsmOperandRef::SymStatic { def_id } => { + inputs.push(self.cx.get_static(def_id)); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); + } + _ => {} + } + } + + // Build the template string + let mut template_str = String::new(); + for piece in template { + match *piece { + InlineAsmTemplatePiece::String(ref s) => { + if s.contains('$') { + for c in s.chars() { + if c == '$' { + template_str.push_str("$$"); + } else { + template_str.push(c); + } + } + } else { + template_str.push_str(s) + } + } + InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span: _ } => { + match operands[operand_idx] { + InlineAsmOperandRef::In { reg, .. } + | InlineAsmOperandRef::Out { reg, .. } + | InlineAsmOperandRef::InOut { reg, .. } => { + let modifier = modifier_to_llvm(asm_arch, reg.reg_class(), modifier); + if let Some(modifier) = modifier { + template_str.push_str(&format!( + "${{{}:{}}}", + op_idx[&operand_idx], modifier + )); + } else { + template_str.push_str(&format!("${{{}}}", op_idx[&operand_idx])); + } + } + InlineAsmOperandRef::Const { ref string } => { + // Const operands get injected directly into the template + template_str.push_str(string); + } + InlineAsmOperandRef::SymFn { .. } + | InlineAsmOperandRef::SymStatic { .. } => { + // Only emit the raw symbol name + template_str.push_str(&format!("${{{}:c}}", op_idx[&operand_idx])); + } + } + } + } + } + + if !options.contains(InlineAsmOptions::PRESERVES_FLAGS) { + match asm_arch { + InlineAsmArch::AArch64 | InlineAsmArch::Arm => { + constraints.push("~{cc}".to_string()); + } + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + constraints.extend_from_slice(&[ + "~{dirflag}".to_string(), + "~{fpsr}".to_string(), + "~{flags}".to_string(), + ]); + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => {} + } + } + if !options.contains(InlineAsmOptions::NOMEM) { + // This is actually ignored by LLVM, but it's probably best to keep + // it just in case. LLVM instead uses the ReadOnly/ReadNone + // attributes on the call instruction to optimize. + constraints.push("~{memory}".to_string()); + } + let volatile = !options.contains(InlineAsmOptions::PURE); + let alignstack = !options.contains(InlineAsmOptions::NOSTACK); + let output_type = match &output_types[..] { + [] => self.type_void(), + [ty] => ty, + tys => self.type_struct(&tys, false), + }; + let dialect = match asm_arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 + if !options.contains(InlineAsmOptions::ATT_SYNTAX) => + { + LlvmAsmDialect::Intel + } + _ => LlvmAsmDialect::Att, + }; + let result = inline_asm_call( + self, + &template_str, + &constraints.join(","), + &inputs, + output_type, + volatile, + alignstack, + dialect, + span, + ) + .unwrap_or_else(|| span_bug!(span, "LLVM asm constraint validation failed")); + + if options.contains(InlineAsmOptions::PURE) { + if options.contains(InlineAsmOptions::NOMEM) { + llvm::Attribute::ReadNone.apply_callsite(llvm::AttributePlace::Function, result); + } else if options.contains(InlineAsmOptions::READONLY) { + llvm::Attribute::ReadOnly.apply_callsite(llvm::AttributePlace::Function, result); + } + } else { + if options.contains(InlineAsmOptions::NOMEM) { + llvm::Attribute::InaccessibleMemOnly + .apply_callsite(llvm::AttributePlace::Function, result); + } else { + // LLVM doesn't have an attribute to represent ReadOnly + SideEffect + } + } + + // Write results to outputs + for (idx, op) in operands.iter().enumerate() { + if let InlineAsmOperandRef::Out { reg, place: Some(place), .. } + | InlineAsmOperandRef::InOut { reg, out_place: Some(place), .. } = *op + { + let value = if output_types.len() == 1 { + result + } else { + self.extract_value(result, op_idx[&idx] as u64) + }; + let value = llvm_fixup_output(self, value, reg.reg_class(), &place.layout); + OperandValue::Immediate(value).store(self, place); + } + } } } @@ -138,7 +339,8 @@ fn inline_asm_call( output: &'ll llvm::Type, volatile: bool, alignstack: bool, - dia: ::rustc_ast::ast::LlvmAsmDialect, + dia: LlvmAsmDialect, + span: Span, ) -> Option<&'ll Value> { let volatile = if volatile { llvm::True } else { llvm::False }; let alignstack = if alignstack { llvm::True } else { llvm::False }; @@ -168,10 +370,339 @@ fn inline_asm_call( alignstack, llvm::AsmDialect::from_generic(dia), ); - Some(bx.call(v, inputs, None)) + let call = bx.call(v, inputs, None); + + // Store mark in a metadata node so we can map LLVM errors + // back to source locations. See #17552. + let key = "srcloc"; + let kind = llvm::LLVMGetMDKindIDInContext( + bx.llcx, + key.as_ptr() as *const c_char, + key.len() as c_uint, + ); + + let val: &'ll Value = bx.const_i32(span.ctxt().outer_expn().as_u32() as i32); + llvm::LLVMSetMetadata(call, kind, llvm::LLVMMDNodeInContext(bx.llcx, &val, 1)); + + Some(call) } else { // LLVM has detected an issue with our constraints, bail out None } } } + +/// Converts a register class to an LLVM constraint code. +fn reg_to_llvm(reg: InlineAsmRegOrRegClass) -> String { + match reg { + InlineAsmRegOrRegClass::Reg(reg) => format!("{{{}}}", reg.name()), + InlineAsmRegOrRegClass::RegClass(reg) => match reg { + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => "r", + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) => "w", + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => "x", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg) => "r", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => "l", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8) => "t", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => "x", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg) => "w", + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => "r", + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => "f", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) => "r", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => "Q", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => "q", + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) => "x", + InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => "v", + InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => "^Yk", + } + .to_string(), + } +} + +/// Converts a modifier into LLVM's equivalent modifier. +fn modifier_to_llvm( + arch: InlineAsmArch, + reg: InlineAsmRegClass, + modifier: Option, +) -> Option { + match reg { + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => modifier, + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) + | InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => { + if modifier == Some('v') { None } else { modifier } + } + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => None, + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) => None, + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) => Some('P'), + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => { + if modifier.is_none() { + Some('q') + } else { + modifier + } + } + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) + | InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => None, + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => match modifier { + None if arch == InlineAsmArch::X86_64 => Some('q'), + None => Some('k'), + Some('l') => Some('b'), + Some('h') => Some('h'), + Some('x') => Some('w'), + Some('e') => Some('k'), + Some('r') => Some('q'), + _ => unreachable!(), + }, + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => None, + InlineAsmRegClass::X86(reg @ X86InlineAsmRegClass::xmm_reg) + | InlineAsmRegClass::X86(reg @ X86InlineAsmRegClass::ymm_reg) + | InlineAsmRegClass::X86(reg @ X86InlineAsmRegClass::zmm_reg) => match (reg, modifier) { + (X86InlineAsmRegClass::xmm_reg, None) => Some('x'), + (X86InlineAsmRegClass::ymm_reg, None) => Some('t'), + (X86InlineAsmRegClass::zmm_reg, None) => Some('g'), + (_, Some('x')) => Some('x'), + (_, Some('y')) => Some('t'), + (_, Some('z')) => Some('g'), + _ => unreachable!(), + }, + InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => None, + } +} + +/// Type to use for outputs that are discarded. It doesn't really matter what +/// the type is, as long as it is valid for the constraint code. +fn dummy_output_type(cx: &CodegenCx<'ll, 'tcx>, reg: InlineAsmRegClass) -> &'ll Type { + match reg { + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => cx.type_i32(), + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) + | InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => { + cx.type_vector(cx.type_i64(), 2) + } + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => cx.type_i32(), + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) => cx.type_f32(), + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) => cx.type_f64(), + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => { + cx.type_vector(cx.type_i64(), 2) + } + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => cx.type_i32(), + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => cx.type_f32(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => cx.type_i32(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => cx.type_i8(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => cx.type_f32(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => cx.type_i16(), + } +} + +/// Helper function to get the LLVM type for a Scalar. Pointers are returned as +/// the equivalent integer type. +fn llvm_asm_scalar_type(cx: &CodegenCx<'ll, 'tcx>, scalar: &Scalar) -> &'ll Type { + match scalar.value { + Primitive::Int(Integer::I8, _) => cx.type_i8(), + Primitive::Int(Integer::I16, _) => cx.type_i16(), + Primitive::Int(Integer::I32, _) => cx.type_i32(), + Primitive::Int(Integer::I64, _) => cx.type_i64(), + Primitive::F32 => cx.type_f32(), + Primitive::F64 => cx.type_f64(), + Primitive::Pointer => cx.type_isize(), + _ => unreachable!(), + } +} + +/// Fix up an input value to work around LLVM bugs. +fn llvm_fixup_input( + bx: &mut Builder<'a, 'll, 'tcx>, + mut value: &'ll Value, + reg: InlineAsmRegClass, + layout: &TyAndLayout<'tcx>, +) -> &'ll Value { + match (reg, &layout.abi) { + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg), Abi::Scalar(s)) => { + if let Primitive::Int(Integer::I8, _) = s.value { + let vec_ty = bx.cx.type_vector(bx.cx.type_i8(), 8); + bx.insert_element(bx.const_undef(vec_ty), value, bx.const_i32(0)) + } else { + value + } + } + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), Abi::Scalar(s)) => { + let elem_ty = llvm_asm_scalar_type(bx.cx, s); + let count = 16 / layout.size.bytes(); + let vec_ty = bx.cx.type_vector(elem_ty, count); + if let Primitive::Pointer = s.value { + value = bx.ptrtoint(value, bx.cx.type_isize()); + } + bx.insert_element(bx.const_undef(vec_ty), value, bx.const_i32(0)) + } + ( + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), + Abi::Vector { element, count }, + ) if layout.size.bytes() == 8 => { + let elem_ty = llvm_asm_scalar_type(bx.cx, element); + let vec_ty = bx.cx.type_vector(elem_ty, *count); + let indices: Vec<_> = (0..count * 2).map(|x| bx.const_i32(x as i32)).collect(); + bx.shuffle_vector(value, bx.const_undef(vec_ty), bx.const_vector(&indices)) + } + (InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd), Abi::Scalar(s)) + if s.value == Primitive::F64 => + { + bx.bitcast(value, bx.cx.type_i64()) + } + ( + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg | X86InlineAsmRegClass::zmm_reg), + Abi::Vector { .. }, + ) if layout.size.bytes() == 64 => bx.bitcast(value, bx.cx.type_vector(bx.cx.type_f64(), 8)), + ( + InlineAsmRegClass::Arm( + ArmInlineAsmRegClass::sreg_low16 + | ArmInlineAsmRegClass::dreg_low8 + | ArmInlineAsmRegClass::qreg_low4 + | ArmInlineAsmRegClass::dreg + | ArmInlineAsmRegClass::qreg, + ), + Abi::Scalar(s), + ) => { + if let Primitive::Int(Integer::I32, _) = s.value { + bx.bitcast(value, bx.cx.type_f32()) + } else { + value + } + } + _ => value, + } +} + +/// Fix up an output value to work around LLVM bugs. +fn llvm_fixup_output( + bx: &mut Builder<'a, 'll, 'tcx>, + mut value: &'ll Value, + reg: InlineAsmRegClass, + layout: &TyAndLayout<'tcx>, +) -> &'ll Value { + match (reg, &layout.abi) { + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg), Abi::Scalar(s)) => { + if let Primitive::Int(Integer::I8, _) = s.value { + bx.extract_element(value, bx.const_i32(0)) + } else { + value + } + } + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), Abi::Scalar(s)) => { + value = bx.extract_element(value, bx.const_i32(0)); + if let Primitive::Pointer = s.value { + value = bx.inttoptr(value, layout.llvm_type(bx.cx)); + } + value + } + ( + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), + Abi::Vector { element, count }, + ) if layout.size.bytes() == 8 => { + let elem_ty = llvm_asm_scalar_type(bx.cx, element); + let vec_ty = bx.cx.type_vector(elem_ty, *count * 2); + let indices: Vec<_> = (0..*count).map(|x| bx.const_i32(x as i32)).collect(); + bx.shuffle_vector(value, bx.const_undef(vec_ty), bx.const_vector(&indices)) + } + (InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd), Abi::Scalar(s)) + if s.value == Primitive::F64 => + { + bx.bitcast(value, bx.cx.type_f64()) + } + ( + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg | X86InlineAsmRegClass::zmm_reg), + Abi::Vector { .. }, + ) if layout.size.bytes() == 64 => bx.bitcast(value, layout.llvm_type(bx.cx)), + ( + InlineAsmRegClass::Arm( + ArmInlineAsmRegClass::sreg_low16 + | ArmInlineAsmRegClass::dreg_low8 + | ArmInlineAsmRegClass::qreg_low4 + | ArmInlineAsmRegClass::dreg + | ArmInlineAsmRegClass::qreg, + ), + Abi::Scalar(s), + ) => { + if let Primitive::Int(Integer::I32, _) = s.value { + bx.bitcast(value, bx.cx.type_i32()) + } else { + value + } + } + _ => value, + } +} + +/// Output type to use for llvm_fixup_output. +fn llvm_fixup_output_type( + cx: &CodegenCx<'ll, 'tcx>, + reg: InlineAsmRegClass, + layout: &TyAndLayout<'tcx>, +) -> &'ll Type { + match (reg, &layout.abi) { + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg), Abi::Scalar(s)) => { + if let Primitive::Int(Integer::I8, _) = s.value { + cx.type_vector(cx.type_i8(), 8) + } else { + layout.llvm_type(cx) + } + } + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), Abi::Scalar(s)) => { + let elem_ty = llvm_asm_scalar_type(cx, s); + let count = 16 / layout.size.bytes(); + cx.type_vector(elem_ty, count) + } + ( + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), + Abi::Vector { element, count }, + ) if layout.size.bytes() == 8 => { + let elem_ty = llvm_asm_scalar_type(cx, element); + cx.type_vector(elem_ty, count * 2) + } + (InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd), Abi::Scalar(s)) + if s.value == Primitive::F64 => + { + cx.type_i64() + } + ( + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg | X86InlineAsmRegClass::zmm_reg), + Abi::Vector { .. }, + ) if layout.size.bytes() == 64 => cx.type_vector(cx.type_f64(), 8), + ( + InlineAsmRegClass::Arm( + ArmInlineAsmRegClass::sreg_low16 + | ArmInlineAsmRegClass::dreg_low8 + | ArmInlineAsmRegClass::qreg_low4 + | ArmInlineAsmRegClass::dreg + | ArmInlineAsmRegClass::qreg, + ), + Abi::Scalar(s), + ) => { + if let Primitive::Int(Integer::I32, _) = s.value { + cx.type_f32() + } else { + layout.llvm_type(cx) + } + } + _ => layout.llvm_type(cx), + } +} diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs index 21dfc113351ce..9cb0f0e0c2e67 100644 --- a/src/librustc_codegen_llvm/llvm/ffi.rs +++ b/src/librustc_codegen_llvm/llvm/ffi.rs @@ -124,6 +124,8 @@ pub enum Attribute { NonLazyBind = 23, OptimizeNone = 24, ReturnsTwice = 25, + ReadNone = 26, + InaccessibleMemOnly = 27, } /// LLVMIntPredicate diff --git a/src/librustc_codegen_llvm/llvm_util.rs b/src/librustc_codegen_llvm/llvm_util.rs index a36e95771e8b9..286d3630181cb 100644 --- a/src/librustc_codegen_llvm/llvm_util.rs +++ b/src/librustc_codegen_llvm/llvm_util.rs @@ -236,6 +236,15 @@ const POWERPC_WHITELIST: &[(&str, Option)] = &[ const MIPS_WHITELIST: &[(&str, Option)] = &[("fp64", Some(sym::mips_target_feature)), ("msa", Some(sym::mips_target_feature))]; +const RISCV_WHITELIST: &[(&str, Option)] = &[ + ("m", Some(sym::riscv_target_feature)), + ("a", Some(sym::riscv_target_feature)), + ("c", Some(sym::riscv_target_feature)), + ("f", Some(sym::riscv_target_feature)), + ("d", Some(sym::riscv_target_feature)), + ("e", Some(sym::riscv_target_feature)), +]; + const WASM_WHITELIST: &[(&str, Option)] = &[("simd128", Some(sym::wasm_target_feature)), ("atomics", Some(sym::wasm_target_feature))]; @@ -253,6 +262,7 @@ pub fn all_known_features() -> impl Iterator &'static [(&'static str, Opti "hexagon" => HEXAGON_WHITELIST, "mips" | "mips64" => MIPS_WHITELIST, "powerpc" | "powerpc64" => POWERPC_WHITELIST, + "riscv32" | "riscv64" => RISCV_WHITELIST, "wasm32" => WASM_WHITELIST, _ => &[], } diff --git a/src/librustc_codegen_ssa/mir/analyze.rs b/src/librustc_codegen_ssa/mir/analyze.rs index 9fcaf2818e831..5e3a37e20bd4f 100644 --- a/src/librustc_codegen_ssa/mir/analyze.rs +++ b/src/librustc_codegen_ssa/mir/analyze.rs @@ -358,7 +358,8 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec { /* nothing to do */ } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => { /* nothing to do */ } TerminatorKind::Call { cleanup: unwind, .. } | TerminatorKind::Assert { cleanup: unwind, .. } | TerminatorKind::DropAndReplace { unwind, .. } diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index dfb1656a6e0cb..b487ed8dea8b6 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -9,9 +9,11 @@ use crate::meth; use crate::traits::*; use crate::MemFlags; +use rustc_ast::ast; use rustc_hir::lang_items; use rustc_index::vec::Idx; use rustc_middle::mir; +use rustc_middle::mir::interpret::{AllocId, ConstValue, Pointer, Scalar}; use rustc_middle::mir::AssertKind; use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt}; use rustc_middle::ty::{self, Instance, Ty, TypeFoldable}; @@ -820,6 +822,123 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { cleanup, ); } + + fn codegen_asm_terminator( + &mut self, + helper: TerminatorCodegenHelper<'tcx>, + mut bx: Bx, + terminator: &mir::Terminator<'tcx>, + template: &[ast::InlineAsmTemplatePiece], + operands: &[mir::InlineAsmOperand<'tcx>], + options: ast::InlineAsmOptions, + destination: Option, + ) { + let span = terminator.source_info.span; + + let operands: Vec<_> = operands + .iter() + .map(|op| match *op { + mir::InlineAsmOperand::In { reg, ref value } => { + let value = self.codegen_operand(&mut bx, value); + InlineAsmOperandRef::In { reg, value } + } + mir::InlineAsmOperand::Out { reg, late, ref place } => { + let place = place.map(|place| self.codegen_place(&mut bx, place.as_ref())); + InlineAsmOperandRef::Out { reg, late, place } + } + mir::InlineAsmOperand::InOut { reg, late, ref in_value, ref out_place } => { + let in_value = self.codegen_operand(&mut bx, in_value); + let out_place = + out_place.map(|out_place| self.codegen_place(&mut bx, out_place.as_ref())); + InlineAsmOperandRef::InOut { reg, late, in_value, out_place } + } + mir::InlineAsmOperand::Const { ref value } => { + if let mir::Operand::Constant(constant) = value { + let const_value = self + .eval_mir_constant(constant) + .unwrap_or_else(|_| span_bug!(span, "asm const cannot be resolved")); + let ty = constant.literal.ty; + let size = bx.layout_of(ty).size; + let scalar = match const_value { + // Promoted constants are evaluated into a ByRef instead of a Scalar, + // but we want the scalar value here. + ConstValue::ByRef { alloc, offset } => { + let ptr = Pointer::new(AllocId(0), offset); + alloc + .read_scalar(&bx, ptr, size) + .and_then(|s| s.not_undef()) + .unwrap_or_else(|e| { + bx.tcx().sess.span_err( + span, + &format!("Could not evaluate asm const: {}", e), + ); + + // We are erroring out, just emit a dummy constant. + Scalar::from_u64(0) + }) + } + _ => span_bug!(span, "expected ByRef for promoted asm const"), + }; + let value = scalar.assert_bits(size); + let string = match ty.kind { + ty::Uint(_) => value.to_string(), + ty::Int(int_ty) => { + match int_ty.normalize(bx.tcx().sess.target.ptr_width) { + ast::IntTy::I8 => (value as i8).to_string(), + ast::IntTy::I16 => (value as i16).to_string(), + ast::IntTy::I32 => (value as i32).to_string(), + ast::IntTy::I64 => (value as i64).to_string(), + ast::IntTy::I128 => (value as i128).to_string(), + ast::IntTy::Isize => unreachable!(), + } + } + ty::Float(ast::FloatTy::F32) => { + f32::from_bits(value as u32).to_string() + } + ty::Float(ast::FloatTy::F64) => { + f64::from_bits(value as u64).to_string() + } + _ => span_bug!(span, "asm const has bad type {}", ty), + }; + InlineAsmOperandRef::Const { string } + } else { + span_bug!(span, "asm const is not a constant"); + } + } + mir::InlineAsmOperand::SymFn { ref value } => { + let literal = self.monomorphize(&value.literal); + if let ty::FnDef(def_id, substs) = literal.ty.kind { + let instance = ty::Instance::resolve( + bx.tcx(), + ty::ParamEnv::reveal_all(), + def_id, + substs, + ) + .unwrap() + .unwrap(); + InlineAsmOperandRef::SymFn { instance } + } else { + span_bug!(span, "invalid type for asm sym (fn)"); + } + } + mir::InlineAsmOperand::SymStatic { ref value } => { + if let Some(def_id) = value.check_static_ptr(bx.tcx()) { + InlineAsmOperandRef::SymStatic { def_id } + } else { + span_bug!(span, "invalid type for asm sym (static)"); + } + } + }) + .collect(); + + bx.codegen_inline_asm(template, &operands, options, span); + + if let Some(target) = destination { + helper.funclet_br(self, &mut bx, target); + } else { + bx.unreachable(); + } + } } impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { @@ -914,6 +1033,18 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::TerminatorKind::FalseEdges { .. } | mir::TerminatorKind::FalseUnwind { .. } => { bug!("borrowck false edges in codegen") } + + mir::TerminatorKind::InlineAsm { template, ref operands, options, destination } => { + self.codegen_asm_terminator( + helper, + bx, + terminator, + template, + operands, + options, + destination, + ); + } } } diff --git a/src/librustc_codegen_ssa/traits/asm.rs b/src/librustc_codegen_ssa/traits/asm.rs index 1cdfd3ae356dd..0abfdfde7801b 100644 --- a/src/librustc_codegen_ssa/traits/asm.rs +++ b/src/librustc_codegen_ssa/traits/asm.rs @@ -1,7 +1,40 @@ use super::BackendTypes; +use crate::mir::operand::OperandRef; use crate::mir::place::PlaceRef; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; +use rustc_hir::def_id::DefId; use rustc_hir::{GlobalAsm, LlvmInlineAsmInner}; +use rustc_middle::ty::Instance; use rustc_span::Span; +use rustc_target::asm::InlineAsmRegOrRegClass; + +#[derive(Debug)] +pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { + In { + reg: InlineAsmRegOrRegClass, + value: OperandRef<'tcx, B::Value>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + place: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_value: OperandRef<'tcx, B::Value>, + out_place: Option>, + }, + Const { + string: String, + }, + SymFn { + instance: Instance<'tcx>, + }, + SymStatic { + def_id: DefId, + }, +} pub trait AsmBuilderMethods<'tcx>: BackendTypes { /// Take an inline assembly expression and splat it out via LLVM @@ -12,6 +45,15 @@ pub trait AsmBuilderMethods<'tcx>: BackendTypes { inputs: Vec, span: Span, ) -> bool; + + /// Take an inline assembly expression and splat it out via LLVM + fn codegen_inline_asm( + &mut self, + template: &[InlineAsmTemplatePiece], + operands: &[InlineAsmOperandRef<'tcx, Self>], + options: InlineAsmOptions, + span: Span, + ); } pub trait AsmMethods { diff --git a/src/librustc_codegen_ssa/traits/mod.rs b/src/librustc_codegen_ssa/traits/mod.rs index f4c09a33285bd..6b782731d535c 100644 --- a/src/librustc_codegen_ssa/traits/mod.rs +++ b/src/librustc_codegen_ssa/traits/mod.rs @@ -28,7 +28,7 @@ mod type_; mod write; pub use self::abi::AbiBuilderMethods; -pub use self::asm::{AsmBuilderMethods, AsmMethods}; +pub use self::asm::{AsmBuilderMethods, AsmMethods, InlineAsmOperandRef}; pub use self::backend::{Backend, BackendTypes, CodegenBackend, ExtraBackendMethods}; pub use self::builder::{BuilderMethods, OverflowOp}; pub use self::consts::ConstMethods; diff --git a/src/librustc_error_codes/error_codes/E0660.md b/src/librustc_error_codes/error_codes/E0660.md index 732de6a381871..fccd1b96f60db 100644 --- a/src/librustc_error_codes/error_codes/E0660.md +++ b/src/librustc_error_codes/error_codes/E0660.md @@ -7,6 +7,6 @@ llvm_asm!("nop" "nop"); ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0661.md b/src/librustc_error_codes/error_codes/E0661.md index 3d5cd90f6a37a..f1debee7a18f1 100644 --- a/src/librustc_error_codes/error_codes/E0661.md +++ b/src/librustc_error_codes/error_codes/E0661.md @@ -8,6 +8,6 @@ llvm_asm!("nop" : "r"(a)); ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0662.md b/src/librustc_error_codes/error_codes/E0662.md index 9858abd10c532..d4765f078b0e6 100644 --- a/src/librustc_error_codes/error_codes/E0662.md +++ b/src/librustc_error_codes/error_codes/E0662.md @@ -11,6 +11,6 @@ llvm_asm!("xor %eax, %eax" ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0663.md b/src/librustc_error_codes/error_codes/E0663.md index 36b87be7d6e38..d5a85b275db63 100644 --- a/src/librustc_error_codes/error_codes/E0663.md +++ b/src/librustc_error_codes/error_codes/E0663.md @@ -11,6 +11,6 @@ llvm_asm!("xor %eax, %eax" ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0664.md b/src/librustc_error_codes/error_codes/E0664.md index 33924b14e6d66..ce9c9491df3d7 100644 --- a/src/librustc_error_codes/error_codes/E0664.md +++ b/src/librustc_error_codes/error_codes/E0664.md @@ -11,6 +11,6 @@ llvm_asm!("mov $$0x200, %eax" ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index a1dd7a5ca5225..7738a576ef75a 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -238,6 +238,7 @@ declare_features! ( (active, movbe_target_feature, "1.34.0", Some(44839), None), (active, rtm_target_feature, "1.35.0", Some(44839), None), (active, f16c_target_feature, "1.36.0", Some(44839), None), + (active, riscv_target_feature, "1.45.0", Some(44839), None), // ------------------------------------------------------------------------- // feature-group-end: actual feature gates (target features) diff --git a/src/librustc_hir/arena.rs b/src/librustc_hir/arena.rs index a0b19f61906ed..6ba396666070a 100644 --- a/src/librustc_hir/arena.rs +++ b/src/librustc_hir/arena.rs @@ -14,6 +14,8 @@ macro_rules! arena_types { // HIR types [few] hir_krate: rustc_hir::Crate<$tcx>, [] arm: rustc_hir::Arm<$tcx>, + [] asm_operand: rustc_hir::InlineAsmOperand<$tcx>, + [] asm_template: rustc_ast::ast::InlineAsmTemplatePiece, [] attribute: rustc_ast::ast::Attribute, [] block: rustc_hir::Block<$tcx>, [] bare_fn_ty: rustc_hir::BareFnTy<$tcx>, @@ -28,7 +30,8 @@ macro_rules! arena_types { [] fn_decl: rustc_hir::FnDecl<$tcx>, [] foreign_item: rustc_hir::ForeignItem<$tcx>, [] impl_item_ref: rustc_hir::ImplItemRef<$tcx>, - [] inline_asm: rustc_hir::LlvmInlineAsm<$tcx>, + [few] inline_asm: rustc_hir::InlineAsm<$tcx>, + [few] llvm_inline_asm: rustc_hir::LlvmInlineAsm<$tcx>, [] local: rustc_hir::Local<$tcx>, [few] macro_def: rustc_hir::MacroDef<$tcx>, [] param: rustc_hir::Param<$tcx>, diff --git a/src/librustc_hir/hir.rs b/src/librustc_hir/hir.rs index eafff6705ba6c..ef398ab25d3fb 100644 --- a/src/librustc_hir/hir.rs +++ b/src/librustc_hir/hir.rs @@ -7,6 +7,7 @@ use rustc_ast::ast::{self, CrateSugar, LlvmAsmDialect}; use rustc_ast::ast::{AttrVec, Attribute, FloatTy, IntTy, Label, LitKind, StrStyle, UintTy}; pub use rustc_ast::ast::{BorrowKind, ImplPolarity, IsAuto}; pub use rustc_ast::ast::{CaptureBy, Movability, Mutability}; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_ast::node_id::NodeMap; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxHashSet; @@ -15,6 +16,7 @@ use rustc_macros::HashStable_Generic; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{MultiSpan, Span, DUMMY_SP}; +use rustc_target::asm::InlineAsmRegOrRegClass; use rustc_target::spec::abi::Abi; use smallvec::SmallVec; @@ -1391,6 +1393,7 @@ impl Expr<'_> { ExprKind::Break(..) => ExprPrecedence::Break, ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, + ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::LlvmInlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::Struct(..) => ExprPrecedence::Struct, ExprKind::Repeat(..) => ExprPrecedence::Repeat, @@ -1446,6 +1449,7 @@ impl Expr<'_> { | ExprKind::Ret(..) | ExprKind::Loop(..) | ExprKind::Assign(..) + | ExprKind::InlineAsm(..) | ExprKind::LlvmInlineAsm(..) | ExprKind::AssignOp(..) | ExprKind::Lit(_) @@ -1622,6 +1626,8 @@ pub enum ExprKind<'hir> { /// A `return`, with an optional value to be returned. Ret(Option<&'hir Expr<'hir>>), + /// Inline assembly (from `asm!`), with its outputs and inputs. + InlineAsm(&'hir InlineAsm<'hir>), /// Inline assembly (from `llvm_asm!`), with its outputs and inputs. LlvmInlineAsm(&'hir LlvmInlineAsm<'hir>), @@ -2054,6 +2060,55 @@ pub enum TyKind<'hir> { Err, } +#[derive(RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub enum InlineAsmOperand<'hir> { + In { + reg: InlineAsmRegOrRegClass, + expr: Expr<'hir>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Expr<'hir>, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: Expr<'hir>, + out_expr: Option>, + }, + Const { + expr: Expr<'hir>, + }, + Sym { + expr: Expr<'hir>, + }, +} + +impl<'hir> InlineAsmOperand<'hir> { + pub fn reg(&self) -> Option { + match *self { + Self::In { reg, .. } + | Self::Out { reg, .. } + | Self::InOut { reg, .. } + | Self::SplitInOut { reg, .. } => Some(reg), + Self::Const { .. } | Self::Sym { .. } => None, + } + } +} + +#[derive(RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub struct InlineAsm<'hir> { + pub template: &'hir [InlineAsmTemplatePiece], + pub operands: &'hir [InlineAsmOperand<'hir>], + pub options: InlineAsmOptions, +} + #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, HashStable_Generic, PartialEq)] pub struct LlvmInlineAsmOutput { pub constraint: Symbol, diff --git a/src/librustc_hir/intravisit.rs b/src/librustc_hir/intravisit.rs index 0270d0de5c7d7..97601a3e1ac7b 100644 --- a/src/librustc_hir/intravisit.rs +++ b/src/librustc_hir/intravisit.rs @@ -1157,6 +1157,27 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) ExprKind::Ret(ref optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } + ExprKind::InlineAsm(ref asm) => { + for op in asm.operands { + match op { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr, .. } + | InlineAsmOperand::Sym { expr, .. } => visitor.visit_expr(expr), + InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + visitor.visit_expr(expr); + } + } + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + visitor.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + visitor.visit_expr(out_expr); + } + } + } + } + } ExprKind::LlvmInlineAsm(ref asm) => { walk_list!(visitor, visit_expr, asm.outputs_exprs); walk_list!(visitor, visit_expr, asm.inputs_exprs); diff --git a/src/librustc_hir_pretty/lib.rs b/src/librustc_hir_pretty/lib.rs index f8a42376d146a..8eb19cbb65a0a 100644 --- a/src/librustc_hir_pretty/lib.rs +++ b/src/librustc_hir_pretty/lib.rs @@ -1409,6 +1409,110 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(&expr, parser::PREC_JUMP); } } + hir::ExprKind::InlineAsm(ref a) => { + enum AsmArg<'a> { + Template(String), + Operand(&'a hir::InlineAsmOperand<'a>), + Options(ast::InlineAsmOptions), + } + + let mut args = vec![]; + args.push(AsmArg::Template(ast::InlineAsmTemplatePiece::to_string(&a.template))); + args.extend(a.operands.iter().map(|o| AsmArg::Operand(o))); + if !a.options.is_empty() { + args.push(AsmArg::Options(a.options)); + } + + self.word("asm!"); + self.popen(); + self.commasep(Consistent, &args, |s, arg| match arg { + AsmArg::Template(template) => s.print_string(&template, ast::StrStyle::Cooked), + AsmArg::Operand(op) => match op { + hir::InlineAsmOperand::In { reg, expr } => { + s.word("in"); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + s.print_expr(expr); + } + hir::InlineAsmOperand::Out { reg, late, expr } => { + s.word(if *late { "lateout" } else { "out" }); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + match expr { + Some(expr) => s.print_expr(expr), + None => s.word("_"), + } + } + hir::InlineAsmOperand::InOut { reg, late, expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + s.print_expr(expr); + } + hir::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + s.print_expr(in_expr); + s.space(); + s.word_space("=>"); + match out_expr { + Some(out_expr) => s.print_expr(out_expr), + None => s.word("_"), + } + } + hir::InlineAsmOperand::Const { expr } => { + s.word("const"); + s.space(); + s.print_expr(expr); + } + hir::InlineAsmOperand::Sym { expr } => { + s.word("sym"); + s.space(); + s.print_expr(expr); + } + }, + AsmArg::Options(opts) => { + s.word("options"); + s.popen(); + let mut options = vec![]; + if opts.contains(ast::InlineAsmOptions::PURE) { + options.push("pure"); + } + if opts.contains(ast::InlineAsmOptions::NOMEM) { + options.push("nomem"); + } + if opts.contains(ast::InlineAsmOptions::READONLY) { + options.push("readonly"); + } + if opts.contains(ast::InlineAsmOptions::PRESERVES_FLAGS) { + options.push("preserves_flags"); + } + if opts.contains(ast::InlineAsmOptions::NORETURN) { + options.push("noreturn"); + } + if opts.contains(ast::InlineAsmOptions::NOSTACK) { + options.push("nostack"); + } + if opts.contains(ast::InlineAsmOptions::ATT_SYNTAX) { + options.push("att_syntax"); + } + s.commasep(Inconsistent, &options, |s, &opt| { + s.word(opt); + }); + s.pclose(); + } + }); + self.pclose(); + } hir::ExprKind::LlvmInlineAsm(ref a) => { let i = &a.inner; self.s.word("llvm_asm!"); diff --git a/src/librustc_interface/util.rs b/src/librustc_interface/util.rs index 7637108e18b39..5a76802014e0d 100644 --- a/src/librustc_interface/util.rs +++ b/src/librustc_interface/util.rs @@ -42,12 +42,15 @@ use std::{panic, thread}; /// features is available on the target machine, by querying LLVM. pub fn add_configuration( cfg: &mut CrateConfig, - sess: &Session, + sess: &mut Session, codegen_backend: &dyn CodegenBackend, ) { let tf = sym::target_feature; - cfg.extend(codegen_backend.target_features(sess).into_iter().map(|feat| (tf, Some(feat)))); + let target_features = codegen_backend.target_features(sess); + sess.target_features.extend(target_features.iter().cloned()); + + cfg.extend(target_features.into_iter().map(|feat| (tf, Some(feat)))); if sess.crt_static(None) { cfg.insert((tf, Some(Symbol::intern("crt-static")))); @@ -75,7 +78,7 @@ pub fn create_session( let codegen_backend = get_codegen_backend(&sess); let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg)); - add_configuration(&mut cfg, &sess, &*codegen_backend); + add_configuration(&mut cfg, &mut sess, &*codegen_backend); sess.parse_sess.config = cfg; (Lrc::new(sess), Lrc::new(codegen_backend), source_map) diff --git a/src/librustc_middle/mir/mod.rs b/src/librustc_middle/mir/mod.rs index 62e6e1249818a..8247338ae0fad 100644 --- a/src/librustc_middle/mir/mod.rs +++ b/src/librustc_middle/mir/mod.rs @@ -19,6 +19,7 @@ use rustc_target::abi::VariantIdx; use polonius_engine::Atom; pub use rustc_ast::ast::Mutability; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::graph::dominators::{dominators, Dominators}; use rustc_data_structures::graph::{self, GraphSuccessors}; @@ -28,6 +29,7 @@ use rustc_macros::HashStable; use rustc_serialize::{Decodable, Encodable}; use rustc_span::symbol::Symbol; use rustc_span::{Span, DUMMY_SP}; +use rustc_target::asm::InlineAsmRegOrRegClass; use std::borrow::Cow; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::ops::{Index, IndexMut}; @@ -1178,6 +1180,23 @@ pub enum TerminatorKind<'tcx> { /// of the `remove_noop_landing_pads` and `no_landing_pads` passes. unwind: Option, }, + + /// Block ends with an inline assembly block. This is a terminator since + /// inline assembly is allowed to diverge. + InlineAsm { + /// The template for the inline assembly, with placeholders. + template: &'tcx [InlineAsmTemplatePiece], + + /// The operands for the inline assembly, as `Operand`s or `Place`s. + operands: Vec>, + + /// Miscellaneous options for the inline assembly. + options: InlineAsmOptions, + + /// Destination block after the inline assembly returns, unless it is + /// diverging (InlineAsmOptions::NORETURN). + destination: Option, + }, } /// Information about an assertion failure. @@ -1192,6 +1211,34 @@ pub enum AssertKind { ResumedAfterPanic(GeneratorKind), } +#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable, HashStable, TypeFoldable)] +pub enum InlineAsmOperand<'tcx> { + In { + reg: InlineAsmRegOrRegClass, + value: Operand<'tcx>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + place: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_value: Operand<'tcx>, + out_place: Option>, + }, + Const { + value: Operand<'tcx>, + }, + SymFn { + value: Box>, + }, + SymStatic { + value: Box>, + }, +} + /// Type for MIR `Assert` terminator error messages. pub type AssertMessage<'tcx> = AssertKind>; @@ -1242,7 +1289,8 @@ impl<'tcx> TerminatorKind<'tcx> { | GeneratorDrop | Return | Unreachable - | Call { destination: None, cleanup: None, .. } => None.into_iter().chain(&[]), + | Call { destination: None, cleanup: None, .. } + | InlineAsm { destination: None, .. } => None.into_iter().chain(&[]), Goto { target: ref t } | Call { destination: None, cleanup: Some(ref t), .. } | Call { destination: Some((_, ref t)), cleanup: None, .. } @@ -1250,7 +1298,8 @@ impl<'tcx> TerminatorKind<'tcx> { | DropAndReplace { target: ref t, unwind: None, .. } | Drop { target: ref t, unwind: None, .. } | Assert { target: ref t, cleanup: None, .. } - | FalseUnwind { real_target: ref t, unwind: None } => Some(t).into_iter().chain(&[]), + | FalseUnwind { real_target: ref t, unwind: None } + | InlineAsm { destination: Some(ref t), .. } => Some(t).into_iter().chain(&[]), Call { destination: Some((_, ref t)), cleanup: Some(ref u), .. } | Yield { resume: ref t, drop: Some(ref u), .. } | DropAndReplace { target: ref t, unwind: Some(ref u), .. } @@ -1274,7 +1323,8 @@ impl<'tcx> TerminatorKind<'tcx> { | GeneratorDrop | Return | Unreachable - | Call { destination: None, cleanup: None, .. } => None.into_iter().chain(&mut []), + | Call { destination: None, cleanup: None, .. } + | InlineAsm { destination: None, .. } => None.into_iter().chain(&mut []), Goto { target: ref mut t } | Call { destination: None, cleanup: Some(ref mut t), .. } | Call { destination: Some((_, ref mut t)), cleanup: None, .. } @@ -1282,9 +1332,8 @@ impl<'tcx> TerminatorKind<'tcx> { | DropAndReplace { target: ref mut t, unwind: None, .. } | Drop { target: ref mut t, unwind: None, .. } | Assert { target: ref mut t, cleanup: None, .. } - | FalseUnwind { real_target: ref mut t, unwind: None } => { - Some(t).into_iter().chain(&mut []) - } + | FalseUnwind { real_target: ref mut t, unwind: None } + | InlineAsm { destination: Some(ref mut t), .. } => Some(t).into_iter().chain(&mut []), Call { destination: Some((_, ref mut t)), cleanup: Some(ref mut u), .. } | Yield { resume: ref mut t, drop: Some(ref mut u), .. } | DropAndReplace { target: ref mut t, unwind: Some(ref mut u), .. } @@ -1310,7 +1359,8 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::FalseEdges { .. } => None, + | TerminatorKind::FalseEdges { .. } + | TerminatorKind::InlineAsm { .. } => None, TerminatorKind::Call { cleanup: ref unwind, .. } | TerminatorKind::Assert { cleanup: ref unwind, .. } | TerminatorKind::DropAndReplace { ref unwind, .. } @@ -1329,7 +1379,8 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::FalseEdges { .. } => None, + | TerminatorKind::FalseEdges { .. } + | TerminatorKind::InlineAsm { .. } => None, TerminatorKind::Call { cleanup: ref mut unwind, .. } | TerminatorKind::Assert { cleanup: ref mut unwind, .. } | TerminatorKind::DropAndReplace { ref mut unwind, .. } @@ -1544,6 +1595,50 @@ impl<'tcx> TerminatorKind<'tcx> { } FalseEdges { .. } => write!(fmt, "falseEdges"), FalseUnwind { .. } => write!(fmt, "falseUnwind"), + InlineAsm { template, ref operands, options, destination: _ } => { + write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; + for op in operands { + write!(fmt, ", ")?; + let print_late = |&late| if late { "late" } else { "" }; + match op { + InlineAsmOperand::In { reg, value } => { + write!(fmt, "in({}) {:?}", reg, value)?; + } + InlineAsmOperand::Out { reg, late, place: Some(place) } => { + write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; + } + InlineAsmOperand::Out { reg, late, place: None } => { + write!(fmt, "{}out({}) _", print_late(late), reg)?; + } + InlineAsmOperand::InOut { + reg, + late, + in_value, + out_place: Some(out_place), + } => { + write!( + fmt, + "in{}out({}) {:?} => {:?}", + print_late(late), + reg, + in_value, + out_place + )?; + } + InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { + write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; + } + InlineAsmOperand::Const { value } => { + write!(fmt, "const {:?}", value)?; + } + InlineAsmOperand::SymFn { value } + | InlineAsmOperand::SymStatic { value } => { + write!(fmt, "sym {:?}", value)?; + } + } + } + write!(fmt, ", options({:?}))", options) + } } } @@ -1586,6 +1681,8 @@ impl<'tcx> TerminatorKind<'tcx> { FalseEdges { .. } => vec!["real".into(), "imaginary".into()], FalseUnwind { unwind: Some(_), .. } => vec!["real".into(), "cleanup".into()], FalseUnwind { unwind: None, .. } => vec!["real".into()], + InlineAsm { destination: Some(_), .. } => vec!["".into()], + InlineAsm { destination: None, .. } => vec![], } } } diff --git a/src/librustc_middle/mir/type_foldable.rs b/src/librustc_middle/mir/type_foldable.rs index 9520f081b6bfb..bb7001c1207bf 100644 --- a/src/librustc_middle/mir/type_foldable.rs +++ b/src/librustc_middle/mir/type_foldable.rs @@ -78,6 +78,9 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> { FalseEdges { real_target, imaginary_target } } FalseUnwind { real_target, unwind } => FalseUnwind { real_target, unwind }, + InlineAsm { template, ref operands, options, destination } => { + InlineAsm { template, operands: operands.fold_with(folder), options, destination } + } }; Terminator { source_info: self.source_info, kind } } @@ -120,6 +123,7 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> { false } } + InlineAsm { ref operands, .. } => operands.visit_with(visitor), Goto { .. } | Resume | Abort diff --git a/src/librustc_middle/mir/visit.rs b/src/librustc_middle/mir/visit.rs index 1f097f24942de..02164244771c9 100644 --- a/src/librustc_middle/mir/visit.rs +++ b/src/librustc_middle/mir/visit.rs @@ -531,6 +531,44 @@ macro_rules! make_mir_visitor { ); } + TerminatorKind::InlineAsm { + template: _, + operands, + options: _, + destination: _, + } => { + for op in operands { + match op { + InlineAsmOperand::In { value, .. } + | InlineAsmOperand::Const { value } => { + self.visit_operand(value, source_location); + } + InlineAsmOperand::Out { place, .. } => { + if let Some(place) = place { + self.visit_place( + place, + PlaceContext::MutatingUse(MutatingUseContext::Store), + source_location, + ); + } + } + InlineAsmOperand::InOut { in_value, out_place, .. } => { + self.visit_operand(in_value, source_location); + if let Some(out_place) = out_place { + self.visit_place( + out_place, + PlaceContext::MutatingUse(MutatingUseContext::Store), + source_location, + ); + } + } + InlineAsmOperand::SymFn { value } + | InlineAsmOperand::SymStatic { value } => { + self.visit_constant(value, source_location); + } + } + } + } } } diff --git a/src/librustc_middle/traits/mod.rs b/src/librustc_middle/traits/mod.rs index 1254174a7a5f9..adbeac29bdd75 100644 --- a/src/librustc_middle/traits/mod.rs +++ b/src/librustc_middle/traits/mod.rs @@ -180,6 +180,8 @@ pub enum ObligationCauseCode<'tcx> { SizedReturnType, /// Yield type must be `Sized`. SizedYieldType, + /// Inline asm operand type must be `Sized`. + InlineAsmSized, /// `[T, ..n]` implies that `T` must be `Copy`. /// If `true`, suggest `const_in_array_repeat_expressions` feature flag. RepeatVec(bool), diff --git a/src/librustc_middle/traits/structural_impls.rs b/src/librustc_middle/traits/structural_impls.rs index 668c84ad5e6df..56744283b0c47 100644 --- a/src/librustc_middle/traits/structural_impls.rs +++ b/src/librustc_middle/traits/structural_impls.rs @@ -152,6 +152,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> { super::SizedArgumentType => Some(super::SizedArgumentType), super::SizedReturnType => Some(super::SizedReturnType), super::SizedYieldType => Some(super::SizedYieldType), + super::InlineAsmSized => Some(super::InlineAsmSized), super::RepeatVec(suggest_flag) => Some(super::RepeatVec(suggest_flag)), super::FieldSized { adt_kind, last } => Some(super::FieldSized { adt_kind, last }), super::ConstSized => Some(super::ConstSized), diff --git a/src/librustc_middle/ty/structural_impls.rs b/src/librustc_middle/ty/structural_impls.rs index 680b718792199..6d4b394f19698 100644 --- a/src/librustc_middle/ty/structural_impls.rs +++ b/src/librustc_middle/ty/structural_impls.rs @@ -259,6 +259,8 @@ CloneTypeFoldableAndLiftImpls! { String, crate::middle::region::Scope, ::rustc_ast::ast::FloatTy, + ::rustc_ast::ast::InlineAsmOptions, + ::rustc_ast::ast::InlineAsmTemplatePiece, ::rustc_ast::ast::NodeId, ::rustc_span::symbol::Symbol, ::rustc_hir::def::Res, @@ -267,6 +269,7 @@ CloneTypeFoldableAndLiftImpls! { ::rustc_hir::MatchSource, ::rustc_hir::Mutability, ::rustc_hir::Unsafety, + ::rustc_target::asm::InlineAsmRegOrRegClass, ::rustc_target::spec::abi::Abi, crate::mir::Local, crate::mir::Promoted, diff --git a/src/librustc_mir/borrow_check/invalidation.rs b/src/librustc_mir/borrow_check/invalidation.rs index a8c7a959b28e2..178e3db17cd32 100644 --- a/src/librustc_mir/borrow_check/invalidation.rs +++ b/src/librustc_mir/borrow_check/invalidation.rs @@ -1,8 +1,8 @@ use rustc_data_structures::graph::dominators::Dominators; use rustc_middle::mir::visit::Visitor; -use rustc_middle::mir::TerminatorKind; use rustc_middle::mir::{BasicBlock, Body, Location, Place, Rvalue}; use rustc_middle::mir::{BorrowKind, Mutability, Operand}; +use rustc_middle::mir::{InlineAsmOperand, TerminatorKind}; use rustc_middle::mir::{Statement, StatementKind}; use rustc_middle::ty::TyCtxt; @@ -183,6 +183,29 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> { } } } + TerminatorKind::InlineAsm { template: _, ref operands, options: _, destination: _ } => { + for op in operands { + match *op { + InlineAsmOperand::In { reg: _, ref value } + | InlineAsmOperand::Const { ref value } => { + self.consume_operand(location, value); + } + InlineAsmOperand::Out { reg: _, late: _, place, .. } => { + if let Some(place) = place { + self.mutate_place(location, place, Shallow(None), JustWrite); + } + } + InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => { + self.consume_operand(location, in_value); + if let Some(out_place) = out_place { + self.mutate_place(location, out_place, Shallow(None), JustWrite); + } + } + InlineAsmOperand::SymFn { value: _ } + | InlineAsmOperand::SymStatic { value: _ } => {} + } + } + } TerminatorKind::Goto { target: _ } | TerminatorKind::Abort | TerminatorKind::Unreachable diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs index a3ee49651ba7b..a0c1d96bb4743 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -17,7 +17,7 @@ use rustc_middle::mir::{ }; use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind}; use rustc_middle::mir::{Field, ProjectionElem, Promoted, Rvalue, Statement, StatementKind}; -use rustc_middle::mir::{Terminator, TerminatorKind}; +use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, RegionVid, TyCtxt}; use rustc_session::lint::builtin::{MUTABLE_BORROW_RESERVATION_CONFLICT, UNUSED_MUT}; @@ -724,6 +724,42 @@ impl<'cx, 'tcx> dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tc self.mutate_place(loc, (resume_arg, span), Deep, JustWrite, flow_state); } + TerminatorKind::InlineAsm { template: _, ref operands, options: _, destination: _ } => { + for op in operands { + match *op { + InlineAsmOperand::In { reg: _, ref value } + | InlineAsmOperand::Const { ref value } => { + self.consume_operand(loc, (value, span), flow_state); + } + InlineAsmOperand::Out { reg: _, late: _, place, .. } => { + if let Some(place) = place { + self.mutate_place( + loc, + (place, span), + Shallow(None), + JustWrite, + flow_state, + ); + } + } + InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => { + self.consume_operand(loc, (in_value, span), flow_state); + if let Some(out_place) = out_place { + self.mutate_place( + loc, + (out_place, span), + Shallow(None), + JustWrite, + flow_state, + ); + } + } + InlineAsmOperand::SymFn { value: _ } + | InlineAsmOperand::SymStatic { value: _ } => {} + } + } + } + TerminatorKind::Goto { target: _ } | TerminatorKind::Abort | TerminatorKind::Unreachable @@ -778,7 +814,8 @@ impl<'cx, 'tcx> dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tc | TerminatorKind::FalseUnwind { real_target: _, unwind: _ } | TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::Unreachable => {} + | TerminatorKind::Unreachable + | TerminatorKind::InlineAsm { .. } => {} } } } diff --git a/src/librustc_mir/borrow_check/type_check/mod.rs b/src/librustc_mir/borrow_check/type_check/mod.rs index bad176c603f35..d7705b8c6a175 100644 --- a/src/librustc_mir/borrow_check/type_check/mod.rs +++ b/src/librustc_mir/borrow_check/type_check/mod.rs @@ -1548,7 +1548,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::FalseEdges { .. } - | TerminatorKind::FalseUnwind { .. } => { + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => { // no checks needed for these } @@ -1855,6 +1856,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { self.assert_iscleanup(body, block_data, unwind, true); } } + TerminatorKind::InlineAsm { ref destination, .. } => { + if let &Some(target) = destination { + self.assert_iscleanup(body, block_data, target, is_cleanup); + } + } } } diff --git a/src/librustc_mir/dataflow/framework/direction.rs b/src/librustc_mir/dataflow/framework/direction.rs index 76f703ec9136f..97b14ea771b2f 100644 --- a/src/librustc_mir/dataflow/framework/direction.rs +++ b/src/librustc_mir/dataflow/framework/direction.rs @@ -482,6 +482,12 @@ impl Direction for Forward { } } + InlineAsm { template: _, operands: _, options: _, destination } => { + if let Some(target) = destination { + propagate(target, exit_state); + } + } + SwitchInt { ref targets, ref values, ref discr, switch_ty: _ } => { let enum_ = discr .place() diff --git a/src/librustc_mir/dataflow/impls/borrowed_locals.rs b/src/librustc_mir/dataflow/impls/borrowed_locals.rs index 6972a81cf1b0e..f929b2ddde0ba 100644 --- a/src/librustc_mir/dataflow/impls/borrowed_locals.rs +++ b/src/librustc_mir/dataflow/impls/borrowed_locals.rs @@ -203,6 +203,7 @@ where | TerminatorKind::FalseUnwind { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::Goto { .. } + | TerminatorKind::InlineAsm { .. } | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::SwitchInt { .. } diff --git a/src/librustc_mir/dataflow/impls/borrows.rs b/src/librustc_mir/dataflow/impls/borrows.rs index 0de8f45720e6f..dfca270396de9 100644 --- a/src/librustc_mir/dataflow/impls/borrows.rs +++ b/src/librustc_mir/dataflow/impls/borrows.rs @@ -317,10 +317,19 @@ impl<'tcx> dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { fn terminator_effect( &self, - _: &mut impl GenKill, - _: &mir::Terminator<'tcx>, - _: Location, + trans: &mut impl GenKill, + teminator: &mir::Terminator<'tcx>, + _location: Location, ) { + if let mir::TerminatorKind::InlineAsm { operands, .. } = &teminator.kind { + for op in operands { + if let mir::InlineAsmOperand::Out { place: Some(place), .. } + | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op + { + self.kill_borrows_on_place(trans, place); + } + } + } } fn call_return_effect( diff --git a/src/librustc_mir/dataflow/impls/storage_liveness.rs b/src/librustc_mir/dataflow/impls/storage_liveness.rs index 4c784c3f1a128..bbc4942030ef7 100644 --- a/src/librustc_mir/dataflow/impls/storage_liveness.rs +++ b/src/librustc_mir/dataflow/impls/storage_liveness.rs @@ -183,6 +183,23 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, // place to have storage *before* the yield, only after. TerminatorKind::Yield { .. } => {} + TerminatorKind::InlineAsm { operands, .. } => { + for op in operands { + match op { + InlineAsmOperand::Out { place, .. } + | InlineAsmOperand::InOut { out_place: place, .. } => { + if let Some(place) = place { + trans.gen(place.local); + } + } + InlineAsmOperand::In { .. } + | InlineAsmOperand::Const { .. } + | InlineAsmOperand::SymFn { .. } + | InlineAsmOperand::SymStatic { .. } => {} + } + } + } + // Nothing to do for these. Match exhaustively so this fails to compile when new // variants are added. TerminatorKind::Call { destination: None, .. } @@ -228,6 +245,7 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, | TerminatorKind::FalseUnwind { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::Goto { .. } + | TerminatorKind::InlineAsm { .. } | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::SwitchInt { .. } diff --git a/src/librustc_mir/dataflow/move_paths/builder.rs b/src/librustc_mir/dataflow/move_paths/builder.rs index ffe84e9b65512..0f2760b3f3b4e 100644 --- a/src/librustc_mir/dataflow/move_paths/builder.rs +++ b/src/librustc_mir/dataflow/move_paths/builder.rs @@ -411,6 +411,31 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); } } + TerminatorKind::InlineAsm { template: _, ref operands, options: _, destination: _ } => { + for op in operands { + match *op { + InlineAsmOperand::In { reg: _, ref value } + | InlineAsmOperand::Const { ref value } => { + self.gather_operand(value); + } + InlineAsmOperand::Out { reg: _, late: _, place, .. } => { + if let Some(place) = place { + self.create_move_path(place); + self.gather_init(place.as_ref(), InitKind::Deep); + } + } + InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => { + self.gather_operand(in_value); + if let Some(out_place) = out_place { + self.create_move_path(out_place); + self.gather_init(out_place.as_ref(), InitKind::Deep); + } + } + InlineAsmOperand::SymFn { value: _ } + | InlineAsmOperand::SymStatic { value: _ } => {} + } + } + } } } diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs index 9f9ec0f6a7834..b048240ca8dc1 100644 --- a/src/librustc_mir/interpret/terminator.rs +++ b/src/librustc_mir/interpret/terminator.rs @@ -131,6 +131,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { "{:#?} should have been eliminated by MIR pass", terminator.kind ), + + // Inline assembly can't be interpreted. + InlineAsm { .. } => throw_unsup_format!("inline assembly is not supported"), } Ok(()) diff --git a/src/librustc_mir/monomorphize/collector.rs b/src/librustc_mir/monomorphize/collector.rs index a8094990594f3..925b8d329668f 100644 --- a/src/librustc_mir/monomorphize/collector.rs +++ b/src/librustc_mir/monomorphize/collector.rs @@ -639,7 +639,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { | mir::TerminatorKind::Abort | mir::TerminatorKind::Return | mir::TerminatorKind::Unreachable - | mir::TerminatorKind::Assert { .. } => {} + | mir::TerminatorKind::Assert { .. } + | mir::TerminatorKind::InlineAsm { .. } => {} mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } | mir::TerminatorKind::FalseEdges { .. } diff --git a/src/librustc_mir/transform/check_consts/validation.rs b/src/librustc_mir/transform/check_consts/validation.rs index 8c005fdcdbf77..987c9e24fc3c3 100644 --- a/src/librustc_mir/transform/check_consts/validation.rs +++ b/src/librustc_mir/transform/check_consts/validation.rs @@ -603,6 +603,10 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { } } + TerminatorKind::InlineAsm { .. } => { + self.check_op(ops::InlineAsm); + } + // FIXME: Some of these are only caught by `min_const_fn`, but should error here // instead. TerminatorKind::Abort diff --git a/src/librustc_mir/transform/check_unsafety.rs b/src/librustc_mir/transform/check_unsafety.rs index f7c6396556d65..9bcb45f6493d1 100644 --- a/src/librustc_mir/transform/check_unsafety.rs +++ b/src/librustc_mir/transform/check_unsafety.rs @@ -95,6 +95,12 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { self.check_target_features(func_id); } } + + TerminatorKind::InlineAsm { .. } => self.require_unsafe( + "use of inline assembly", + "inline assembly is entirely unchecked and can cause undefined behavior", + UnsafetyViolationKind::General, + ), } self.super_terminator(terminator, location); } diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index e1be1daca45a1..f69343f4d7500 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -1014,7 +1014,8 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> { | TerminatorKind::Yield { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::FalseEdges { .. } - | TerminatorKind::FalseUnwind { .. } => {} + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {} // Every argument in our function calls can be const propagated. TerminatorKind::Call { ref mut args, .. } => { let mir_opt_level = self.tcx.sess.opts.debugging_opts.mir_opt_level; diff --git a/src/librustc_mir/transform/generator.rs b/src/librustc_mir/transform/generator.rs index 14faa5be02f4e..4bf2adcd450c0 100644 --- a/src/librustc_mir/transform/generator.rs +++ b/src/librustc_mir/transform/generator.rs @@ -981,7 +981,8 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::FalseEdges { .. } - | TerminatorKind::FalseUnwind { .. } => {} + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {} // Resume will *continue* unwinding, but if there's no other unwinding terminator it // will never be reached. diff --git a/src/librustc_mir/transform/inline.rs b/src/librustc_mir/transform/inline.rs index 632408fde749f..35d55c4cb9b6d 100644 --- a/src/librustc_mir/transform/inline.rs +++ b/src/librustc_mir/transform/inline.rs @@ -800,6 +800,11 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Integrator<'a, 'tcx> { { bug!("False unwinds should have been removed before inlining") } + TerminatorKind::InlineAsm { ref mut destination, .. } => { + if let Some(ref mut tgt) = *destination { + *tgt = self.update_target(*tgt); + } + } } } diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs index 6dade3c8dca6c..13a8b9a1000c9 100644 --- a/src/librustc_mir/transform/promote_consts.rs +++ b/src/librustc_mir/transform/promote_consts.rs @@ -113,6 +113,9 @@ pub enum Candidate { /// the attribute currently provides the semantic requirement that arguments /// must be constant. Argument { bb: BasicBlock, index: usize }, + + /// `const` operand in asm!. + InlineAsm { bb: BasicBlock, index: usize }, } impl Candidate { @@ -120,7 +123,7 @@ impl Candidate { fn forces_explicit_promotion(&self) -> bool { match self { Candidate::Ref(_) | Candidate::Repeat(_) => false, - Candidate::Argument { .. } => true, + Candidate::Argument { .. } | Candidate::InlineAsm { .. } => true, } } } @@ -216,25 +219,39 @@ impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> { fn visit_terminator_kind(&mut self, kind: &TerminatorKind<'tcx>, location: Location) { self.super_terminator_kind(kind, location); - if let TerminatorKind::Call { ref func, .. } = *kind { - if let ty::FnDef(def_id, _) = func.ty(self.ccx.body, self.ccx.tcx).kind { - let fn_sig = self.ccx.tcx.fn_sig(def_id); - if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() { - let name = self.ccx.tcx.item_name(def_id); - // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles. - if name.as_str().starts_with("simd_shuffle") { - self.candidates.push(Candidate::Argument { bb: location.block, index: 2 }); + match *kind { + TerminatorKind::Call { ref func, .. } => { + if let ty::FnDef(def_id, _) = func.ty(self.ccx.body, self.ccx.tcx).kind { + let fn_sig = self.ccx.tcx.fn_sig(def_id); + if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() { + let name = self.ccx.tcx.item_name(def_id); + // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles. + if name.as_str().starts_with("simd_shuffle") { + self.candidates + .push(Candidate::Argument { bb: location.block, index: 2 }); + + return; // Don't double count `simd_shuffle` candidates + } + } - return; // Don't double count `simd_shuffle` candidates + if let Some(constant_args) = args_required_const(self.ccx.tcx, def_id) { + for index in constant_args { + self.candidates.push(Candidate::Argument { bb: location.block, index }); + } } } - - if let Some(constant_args) = args_required_const(self.ccx.tcx, def_id) { - for index in constant_args { - self.candidates.push(Candidate::Argument { bb: location.block, index }); + } + TerminatorKind::InlineAsm { ref operands, .. } => { + for (index, op) in operands.iter().enumerate() { + match op { + InlineAsmOperand::Const { .. } => { + self.candidates.push(Candidate::InlineAsm { bb: location.block, index }) + } + _ => {} } } } + _ => {} } } @@ -402,6 +419,18 @@ impl<'tcx> Validator<'_, 'tcx> { _ => bug!(), } } + Candidate::InlineAsm { bb, index } => { + assert!(self.explicit); + + let terminator = self.body[bb].terminator(); + match &terminator.kind { + TerminatorKind::InlineAsm { operands, .. } => match &operands[index] { + InlineAsmOperand::Const { value } => self.validate_operand(value), + _ => bug!(), + }, + _ => bug!(), + } + } } } @@ -747,7 +776,9 @@ pub fn validate_candidates( } match candidate { - Candidate::Argument { bb, index } if !is_promotable => { + Candidate::Argument { bb, index } | Candidate::InlineAsm { bb, index } + if !is_promotable => + { let span = ccx.body[bb].terminator().source_info.span; let msg = format!("argument {} is required to be a constant", index + 1); ccx.tcx.sess.span_err(span, &msg); @@ -1024,6 +1055,24 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { _ => bug!(), } } + Candidate::InlineAsm { bb, index } => { + let terminator = blocks[bb].terminator_mut(); + match terminator.kind { + TerminatorKind::InlineAsm { ref mut operands, .. } => { + match &mut operands[index] { + InlineAsmOperand::Const { ref mut value } => { + let ty = value.ty(local_decls, self.tcx); + let span = terminator.source_info.span; + + Rvalue::Use(mem::replace(value, promoted_operand(ty, span))) + } + _ => bug!(), + } + } + + _ => bug!(), + } + } } }; @@ -1080,7 +1129,7 @@ pub fn promote_candidates<'tcx>( } } } - Candidate::Argument { .. } => {} + Candidate::Argument { .. } | Candidate::InlineAsm { .. } => {} } // Declare return place local so that `mir::Body::new` doesn't complain. diff --git a/src/librustc_mir/transform/qualify_min_const_fn.rs b/src/librustc_mir/transform/qualify_min_const_fn.rs index c9982aeaa0839..a9d6c95b257bc 100644 --- a/src/librustc_mir/transform/qualify_min_const_fn.rs +++ b/src/librustc_mir/transform/qualify_min_const_fn.rs @@ -391,5 +391,9 @@ fn check_terminator( TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => { check_operand(tcx, cond, span, def_id, body) } + + TerminatorKind::InlineAsm { .. } => { + Err((span, "cannot use inline assembly in const fn".into())) + } } } diff --git a/src/librustc_mir/transform/remove_noop_landing_pads.rs b/src/librustc_mir/transform/remove_noop_landing_pads.rs index a86ef7f2f522a..69c0163b649d6 100644 --- a/src/librustc_mir/transform/remove_noop_landing_pads.rs +++ b/src/librustc_mir/transform/remove_noop_landing_pads.rs @@ -77,7 +77,8 @@ impl RemoveNoopLandingPads { | TerminatorKind::Call { .. } | TerminatorKind::Assert { .. } | TerminatorKind::DropAndReplace { .. } - | TerminatorKind::Drop { .. } => false, + | TerminatorKind::Drop { .. } + | TerminatorKind::InlineAsm { .. } => false, } } diff --git a/src/librustc_mir_build/build/expr/as_place.rs b/src/librustc_mir_build/build/expr/as_place.rs index e08eedc6b6e91..16bba6ad14780 100644 --- a/src/librustc_mir_build/build/expr/as_place.rs +++ b/src/librustc_mir_build/build/expr/as_place.rs @@ -255,6 +255,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Return { .. } | ExprKind::Literal { .. } | ExprKind::StaticRef { .. } + | ExprKind::InlineAsm { .. } | ExprKind::LlvmInlineAsm { .. } | ExprKind::Yield { .. } | ExprKind::Call { .. } => { diff --git a/src/librustc_mir_build/build/expr/as_rvalue.rs b/src/librustc_mir_build/build/expr/as_rvalue.rs index d934ba1dc841e..0f15190975087 100644 --- a/src/librustc_mir_build/build/expr/as_rvalue.rs +++ b/src/librustc_mir_build/build/expr/as_rvalue.rs @@ -252,6 +252,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::InlineAsm { .. } | ExprKind::LlvmInlineAsm { .. } | ExprKind::PlaceTypeAscription { .. } | ExprKind::ValueTypeAscription { .. } => { diff --git a/src/librustc_mir_build/build/expr/category.rs b/src/librustc_mir_build/build/expr/category.rs index f8cae205453c3..59d3003c9f49a 100644 --- a/src/librustc_mir_build/build/expr/category.rs +++ b/src/librustc_mir_build/build/expr/category.rs @@ -51,7 +51,8 @@ impl Category { | ExprKind::Borrow { .. } | ExprKind::AddressOf { .. } | ExprKind::Yield { .. } - | ExprKind::Call { .. } => Some(Category::Rvalue(RvalueFunc::Into)), + | ExprKind::Call { .. } + | ExprKind::InlineAsm { .. } => Some(Category::Rvalue(RvalueFunc::Into)), ExprKind::Array { .. } | ExprKind::Tuple { .. } diff --git a/src/librustc_mir_build/build/expr/into.rs b/src/librustc_mir_build/build/expr/into.rs index 8734d39a6d07b..ff3c7ee3ee823 100644 --- a/src/librustc_mir_build/build/expr/into.rs +++ b/src/librustc_mir_build/build/expr/into.rs @@ -3,6 +3,7 @@ use crate::build::expr::category::{Category, RvalueFunc}; use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use crate::hair::*; +use rustc_ast::ast::InlineAsmOptions; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_middle::mir::*; @@ -53,7 +54,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::NeverToAny { source } => { let source = this.hir.mirror(source); let is_call = match source.kind { - ExprKind::Call { .. } => true, + ExprKind::Call { .. } | ExprKind::InlineAsm { .. } => true, _ => false, }; @@ -309,6 +310,73 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); block.unit() } + ExprKind::InlineAsm { template, operands, options } => { + use crate::hair; + use rustc_middle::mir; + let operands = operands + .into_iter() + .map(|op| match op { + hair::InlineAsmOperand::In { reg, expr } => mir::InlineAsmOperand::In { + reg, + value: unpack!(block = this.as_local_operand(block, expr)), + }, + hair::InlineAsmOperand::Out { reg, late, expr } => { + mir::InlineAsmOperand::Out { + reg, + late, + place: expr.map(|expr| unpack!(block = this.as_place(block, expr))), + } + } + hair::InlineAsmOperand::InOut { reg, late, expr } => { + let place = unpack!(block = this.as_place(block, expr)); + mir::InlineAsmOperand::InOut { + reg, + late, + // This works because asm operands must be Copy + in_value: Operand::Copy(place), + out_place: Some(place), + } + } + hair::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + mir::InlineAsmOperand::InOut { + reg, + late, + in_value: unpack!(block = this.as_local_operand(block, in_expr)), + out_place: out_expr.map(|out_expr| { + unpack!(block = this.as_place(block, out_expr)) + }), + } + } + hair::InlineAsmOperand::Const { expr } => mir::InlineAsmOperand::Const { + value: unpack!(block = this.as_local_operand(block, expr)), + }, + hair::InlineAsmOperand::SymFn { expr } => { + mir::InlineAsmOperand::SymFn { value: box this.as_constant(expr) } + } + hair::InlineAsmOperand::SymStatic { expr } => { + mir::InlineAsmOperand::SymStatic { value: box this.as_constant(expr) } + } + }) + .collect(); + + let destination = this.cfg.start_new_block(); + + this.cfg.terminate( + block, + source_info, + TerminatorKind::InlineAsm { + template, + operands, + options, + destination: if options.contains(InlineAsmOptions::NORETURN) { + None + } else { + Some(destination) + }, + }, + ); + destination.unit() + } // These cases don't actually need a destination ExprKind::Assign { .. } diff --git a/src/librustc_mir_build/build/scope.rs b/src/librustc_mir_build/build/scope.rs index 19b983018c958..868fb69abe80c 100644 --- a/src/librustc_mir_build/build/scope.rs +++ b/src/librustc_mir_build/build/scope.rs @@ -1388,7 +1388,8 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } | TerminatorKind::GeneratorDrop - | TerminatorKind::FalseEdges { .. } => { + | TerminatorKind::FalseEdges { .. } + | TerminatorKind::InlineAsm { .. } => { span_bug!(term.source_info.span, "cannot unwind from {:?}", term.kind) } } diff --git a/src/librustc_mir_build/hair/cx/expr.rs b/src/librustc_mir_build/hair/cx/expr.rs index 8d572465d62c1..99b59d16029ff 100644 --- a/src/librustc_mir_build/hair/cx/expr.rs +++ b/src/librustc_mir_build/hair/cx/expr.rs @@ -400,6 +400,121 @@ fn make_mirror_unadjusted<'a, 'tcx>( convert_path_expr(cx, expr, res) } + hir::ExprKind::InlineAsm(ref asm) => ExprKind::InlineAsm { + template: asm.template, + operands: asm + .operands + .iter() + .map(|op| { + match *op { + hir::InlineAsmOperand::In { reg, ref expr } => { + InlineAsmOperand::In { reg, expr: expr.to_ref() } + } + hir::InlineAsmOperand::Out { reg, late, ref expr } => { + InlineAsmOperand::Out { + reg, + late, + expr: expr.as_ref().map(|expr| expr.to_ref()), + } + } + hir::InlineAsmOperand::InOut { reg, late, ref expr } => { + InlineAsmOperand::InOut { reg, late, expr: expr.to_ref() } + } + hir::InlineAsmOperand::SplitInOut { + reg, + late, + ref in_expr, + ref out_expr, + } => InlineAsmOperand::SplitInOut { + reg, + late, + in_expr: in_expr.to_ref(), + out_expr: out_expr.as_ref().map(|expr| expr.to_ref()), + }, + hir::InlineAsmOperand::Const { ref expr } => { + InlineAsmOperand::Const { expr: expr.to_ref() } + } + hir::InlineAsmOperand::Sym { ref expr } => { + let qpath = match expr.kind { + hir::ExprKind::Path(ref qpath) => qpath, + _ => span_bug!( + expr.span, + "asm `sym` operand should be a path, found {:?}", + expr.kind + ), + }; + let temp_lifetime = + cx.region_scope_tree.temporary_scope(expr.hir_id.local_id); + let res = cx.tables().qpath_res(qpath, expr.hir_id); + let ty; + match res { + Res::Def(DefKind::Fn, _) | Res::Def(DefKind::AssocFn, _) => { + ty = cx.tables().node_type(expr.hir_id); + let user_ty = user_substs_applied_to_res(cx, expr.hir_id, res); + InlineAsmOperand::SymFn { + expr: Expr { + ty, + temp_lifetime, + span: expr.span, + kind: ExprKind::Literal { + literal: ty::Const::zero_sized(cx.tcx, ty), + user_ty, + }, + } + .to_ref(), + } + } + + Res::Def(DefKind::Static, id) => { + ty = cx.tcx.static_ptr_ty(id); + let ptr = cx.tcx.create_static_alloc(id); + InlineAsmOperand::SymStatic { + expr: Expr { + ty, + temp_lifetime, + span: expr.span, + kind: ExprKind::StaticRef { + literal: ty::Const::from_scalar( + cx.tcx, + Scalar::Ptr(ptr.into()), + ty, + ), + def_id: id, + }, + } + .to_ref(), + } + } + + _ => { + cx.tcx.sess.span_err( + expr.span, + "asm `sym` operand must point to a fn or static", + ); + + // Not a real fn, but we're not reaching codegen anyways... + ty = cx.tcx.types.err; + InlineAsmOperand::SymFn { + expr: Expr { + ty, + temp_lifetime, + span: expr.span, + kind: ExprKind::Literal { + literal: ty::Const::zero_sized(cx.tcx, ty), + user_ty: None, + }, + } + .to_ref(), + } + } + } + } + } + }) + .collect(), + options: asm.options, + }, + hir::ExprKind::LlvmInlineAsm(ref asm) => ExprKind::LlvmInlineAsm { asm: &asm.inner, outputs: asm.outputs_exprs.to_ref(), diff --git a/src/librustc_mir_build/hair/mod.rs b/src/librustc_mir_build/hair/mod.rs index 601e4412512ab..aba7a7a1b420c 100644 --- a/src/librustc_mir_build/hair/mod.rs +++ b/src/librustc_mir_build/hair/mod.rs @@ -5,6 +5,7 @@ //! structures. use self::cx::Cx; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_middle::infer::canonical::Canonical; @@ -15,6 +16,7 @@ use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{AdtDef, Const, Ty, UpvarSubsts, UserType}; use rustc_span::Span; use rustc_target::abi::VariantIdx; +use rustc_target::asm::InlineAsmRegOrRegClass; crate mod constant; crate mod cx; @@ -277,6 +279,11 @@ crate enum ExprKind<'tcx> { literal: &'tcx Const<'tcx>, def_id: DefId, }, + InlineAsm { + template: &'tcx [InlineAsmTemplatePiece], + operands: Vec>, + options: InlineAsmOptions, + }, LlvmInlineAsm { asm: &'tcx hir::LlvmInlineAsmInner, outputs: Vec>, @@ -335,6 +342,39 @@ impl<'tcx> ExprRef<'tcx> { } } +#[derive(Clone, Debug)] +crate enum InlineAsmOperand<'tcx> { + In { + reg: InlineAsmRegOrRegClass, + expr: ExprRef<'tcx>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: ExprRef<'tcx>, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: ExprRef<'tcx>, + out_expr: Option>, + }, + Const { + expr: ExprRef<'tcx>, + }, + SymFn { + expr: ExprRef<'tcx>, + }, + SymStatic { + expr: ExprRef<'tcx>, + }, +} + /////////////////////////////////////////////////////////////////////////// // The Mirror trait diff --git a/src/librustc_mir_build/lints.rs b/src/librustc_mir_build/lints.rs index 69bfad6b13999..dbafc98fb50f1 100644 --- a/src/librustc_mir_build/lints.rs +++ b/src/librustc_mir_build/lints.rs @@ -114,6 +114,15 @@ impl<'mir, 'tcx> TriColorVisitor<&'mir Body<'tcx>> for Search<'mir, 'tcx> { | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive), + // A diverging InlineAsm is treated as non-recursing + TerminatorKind::InlineAsm { destination, .. } => { + if destination.is_some() { + ControlFlow::Continue + } else { + ControlFlow::Break(NonRecursive) + } + } + // These do not. TerminatorKind::Assert { .. } | TerminatorKind::Call { .. } diff --git a/src/librustc_passes/intrinsicck.rs b/src/librustc_passes/intrinsicck.rs index b407276cfbecd..93344e907c344 100644 --- a/src/librustc_passes/intrinsicck.rs +++ b/src/librustc_passes/intrinsicck.rs @@ -1,3 +1,4 @@ +use rustc_ast::ast::{FloatTy, InlineAsmTemplatePiece, IntTy, UintTy}; use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -7,8 +8,10 @@ use rustc_index::vec::Idx; use rustc_middle::ty::layout::{LayoutError, SizeSkeleton}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::{sym, Span}; +use rustc_session::lint; +use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_target::abi::{Pointer, VariantIdx}; +use rustc_target::asm::{InlineAsmRegOrRegClass, InlineAsmType}; use rustc_target::spec::abi::Abi::RustIntrinsic; fn check_mod_intrinsics(tcx: TyCtxt<'_>, module_def_id: DefId) { @@ -119,6 +122,271 @@ impl ExprVisitor<'tcx> { } err.emit() } + + fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool { + if ty.is_sized(self.tcx.at(DUMMY_SP), self.param_env) { + return true; + } + if let ty::Foreign(..) = ty.kind { + return true; + } + false + } + + fn check_asm_operand_type( + &self, + idx: usize, + reg: InlineAsmRegOrRegClass, + expr: &hir::Expr<'tcx>, + template: &[InlineAsmTemplatePiece], + tied_input: Option<(&hir::Expr<'tcx>, Option)>, + ) -> Option { + // Check the type against the allowed types for inline asm. + let ty = self.tables.expr_ty_adjusted(expr); + let asm_ty_isize = match self.tcx.sess.target.ptr_width { + 16 => InlineAsmType::I16, + 32 => InlineAsmType::I32, + 64 => InlineAsmType::I64, + _ => unreachable!(), + }; + let asm_ty = match ty.kind { + ty::Never | ty::Error => return None, + ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Some(InlineAsmType::I8), + ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Some(InlineAsmType::I16), + ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Some(InlineAsmType::I32), + ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Some(InlineAsmType::I64), + ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => Some(InlineAsmType::I128), + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(asm_ty_isize), + ty::Float(FloatTy::F32) => Some(InlineAsmType::F32), + ty::Float(FloatTy::F64) => Some(InlineAsmType::F64), + ty::FnPtr(_) => Some(asm_ty_isize), + ty::RawPtr(ty::TypeAndMut { ty, mutbl: _ }) if self.is_thin_ptr_ty(ty) => { + Some(asm_ty_isize) + } + ty::Adt(adt, substs) if adt.repr.simd() => { + let fields = &adt.non_enum_variant().fields; + let elem_ty = fields[0].ty(self.tcx, substs); + match elem_ty.kind { + ty::Never | ty::Error => return None, + ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => { + Some(InlineAsmType::VecI8(fields.len() as u64)) + } + ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => { + Some(InlineAsmType::VecI16(fields.len() as u64)) + } + ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => { + Some(InlineAsmType::VecI32(fields.len() as u64)) + } + ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => { + Some(InlineAsmType::VecI64(fields.len() as u64)) + } + ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => { + Some(InlineAsmType::VecI128(fields.len() as u64)) + } + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => { + Some(match self.tcx.sess.target.ptr_width { + 16 => InlineAsmType::VecI16(fields.len() as u64), + 32 => InlineAsmType::VecI32(fields.len() as u64), + 64 => InlineAsmType::VecI64(fields.len() as u64), + _ => unreachable!(), + }) + } + ty::Float(FloatTy::F32) => Some(InlineAsmType::VecF32(fields.len() as u64)), + ty::Float(FloatTy::F64) => Some(InlineAsmType::VecF64(fields.len() as u64)), + _ => None, + } + } + _ => None, + }; + let asm_ty = match asm_ty { + Some(asm_ty) => asm_ty, + None => { + let msg = &format!("cannot use value of type `{}` for inline assembly", ty); + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + err.note( + "only integers, floats, SIMD vectors, pointers and function pointers \ + can be used as arguments for inline assembly", + ); + err.emit(); + return None; + } + }; + + // Check that the type implements Copy. The only case where this can + // possibly fail is for SIMD types which don't #[derive(Copy)]. + if !ty.is_copy_modulo_regions(self.tcx, self.param_env, DUMMY_SP) { + let msg = "arguments for inline assembly must be copyable"; + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + err.note(&format!("`{}` does not implement the Copy trait", ty)); + err.emit(); + } + + // Ideally we wouldn't need to do this, but LLVM's register allocator + // really doesn't like it when tied operands have different types. + // + // This is purely an LLVM limitation, but we have to live with it since + // there is no way to hide this with implicit conversions. + // + // For the purposes of this check we only look at the `InlineAsmType`, + // which means that pointers and integers are treated as identical (modulo + // size). + if let Some((in_expr, Some(in_asm_ty))) = tied_input { + if in_asm_ty != asm_ty { + let msg = &format!("incompatible types for asm inout argument"); + let mut err = self.tcx.sess.struct_span_err(vec![in_expr.span, expr.span], msg); + err.span_label( + in_expr.span, + &format!("type `{}`", self.tables.expr_ty_adjusted(in_expr)), + ); + err.span_label(expr.span, &format!("type `{}`", ty)); + err.note( + "asm inout arguments must have the same type, \ + unless they are both pointers or integers of the same size", + ); + err.emit(); + } + + // All of the later checks have already been done on the input, so + // let's not emit errors and warnings twice. + return Some(asm_ty); + } + + // Check the type against the list of types supported by the selected + // register class. + let asm_arch = self.tcx.sess.asm_arch.unwrap(); + let reg_class = reg.reg_class(); + let supported_tys = reg_class.supported_types(asm_arch); + let feature = match supported_tys.iter().find(|&&(t, _)| t == asm_ty) { + Some((_, feature)) => feature, + None => { + let msg = &format!("type `{}` cannot be used with this register class", ty); + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + let supported_tys: Vec<_> = + supported_tys.iter().map(|(t, _)| t.to_string()).collect(); + err.note(&format!( + "register class `{}` supports these types: {}", + reg_class.name(), + supported_tys.join(", "), + )); + if let Some(suggest) = reg_class.suggest_class(asm_arch, asm_ty) { + err.help(&format!( + "consider using the `{}` register class instead", + suggest.name() + )); + } + err.emit(); + return Some(asm_ty); + } + }; + + // Check whether the selected type requires a target feature. Note that + // this is different from the feature check we did earlier in AST + // lowering. While AST lowering checked that this register class is + // usable at all with the currently enabled features, some types may + // only be usable with a register class when a certain feature is + // enabled. We check this here since it depends on the results of typeck. + // + // Also note that this check isn't run when the operand type is never + // (!). In that case we still need the earlier check in AST lowering to + // verify that the register class is usable at all. + if let Some(feature) = feature { + if !self.tcx.sess.target_features.contains(&Symbol::intern(feature)) { + let msg = &format!("`{}` target feature is not enabled", feature); + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + err.note(&format!( + "this is required to use type `{}` with register class `{}`", + ty, + reg_class.name(), + )); + err.emit(); + return Some(asm_ty); + } + } + + // Check whether a modifier is suggested for using this type. + if let Some((suggested_modifier, suggested_result)) = + reg_class.suggest_modifier(asm_arch, asm_ty) + { + // Search for any use of this operand without a modifier and emit + // the suggestion for them. + let mut spans = vec![]; + for piece in template { + if let &InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } = piece + { + if operand_idx == idx && modifier.is_none() { + spans.push(span); + } + } + } + if !spans.is_empty() { + let (default_modifier, default_result) = + reg_class.default_modifier(asm_arch).unwrap(); + self.tcx.struct_span_lint_hir( + lint::builtin::ASM_SUB_REGISTER, + expr.hir_id, + spans, + |lint| { + let msg = "formatting may not be suitable for sub-register argument"; + let mut err = lint.build(msg); + err.span_label(expr.span, "for this argument"); + err.help(&format!( + "use the `{}` modifier to have the register formatted as `{}`", + suggested_modifier, suggested_result, + )); + err.help(&format!( + "or use the `{}` modifier to keep the default formatting of `{}`", + default_modifier, default_result, + )); + err.emit(); + }, + ); + } + } + + Some(asm_ty) + } + + fn check_asm(&self, asm: &hir::InlineAsm<'tcx>) { + for (idx, op) in asm.operands.iter().enumerate() { + match *op { + hir::InlineAsmOperand::In { reg, ref expr } => { + self.check_asm_operand_type(idx, reg, expr, asm.template, None); + } + hir::InlineAsmOperand::Out { reg, late: _, ref expr } => { + if let Some(expr) = expr { + self.check_asm_operand_type(idx, reg, expr, asm.template, None); + } + } + hir::InlineAsmOperand::InOut { reg, late: _, ref expr } => { + self.check_asm_operand_type(idx, reg, expr, asm.template, None); + } + hir::InlineAsmOperand::SplitInOut { reg, late: _, ref in_expr, ref out_expr } => { + let in_ty = self.check_asm_operand_type(idx, reg, in_expr, asm.template, None); + if let Some(out_expr) = out_expr { + self.check_asm_operand_type( + idx, + reg, + out_expr, + asm.template, + Some((in_expr, in_ty)), + ); + } + } + hir::InlineAsmOperand::Const { ref expr } => { + let ty = self.tables.expr_ty_adjusted(expr); + match ty.kind { + ty::Int(_) | ty::Uint(_) | ty::Float(_) => {} + _ => { + let msg = + "asm `const` arguments must be integer or floating-point values"; + self.tcx.sess.span_err(expr.span, msg); + } + } + } + hir::InlineAsmOperand::Sym { .. } => {} + } + } + } } impl Visitor<'tcx> for ItemVisitor<'tcx> { @@ -146,19 +414,23 @@ impl Visitor<'tcx> for ExprVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - let res = if let hir::ExprKind::Path(ref qpath) = expr.kind { - self.tables.qpath_res(qpath, expr.hir_id) - } else { - Res::Err - }; - if let Res::Def(DefKind::Fn, did) = res { - if self.def_id_is_transmute(did) { - let typ = self.tables.node_type(expr.hir_id); - let sig = typ.fn_sig(self.tcx); - let from = sig.inputs().skip_binder()[0]; - let to = *sig.output().skip_binder(); - self.check_transmute(expr.span, from, to); + match expr.kind { + hir::ExprKind::Path(ref qpath) => { + let res = self.tables.qpath_res(qpath, expr.hir_id); + if let Res::Def(DefKind::Fn, did) = res { + if self.def_id_is_transmute(did) { + let typ = self.tables.node_type(expr.hir_id); + let sig = typ.fn_sig(self.tcx); + let from = sig.inputs().skip_binder()[0]; + let to = *sig.output().skip_binder(); + self.check_transmute(expr.span, from, to); + } + } } + + hir::ExprKind::InlineAsm(asm) => self.check_asm(asm), + + _ => {} } intravisit::walk_expr(self, expr); diff --git a/src/librustc_passes/liveness.rs b/src/librustc_passes/liveness.rs index 6e7d116ce1d7e..21512c566e1c5 100644 --- a/src/librustc_passes/liveness.rs +++ b/src/librustc_passes/liveness.rs @@ -96,6 +96,7 @@ use self::LiveNodeKind::*; use self::VarKind::*; +use rustc_ast::ast::InlineAsmOptions; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir as hir; @@ -531,6 +532,7 @@ fn visit_expr<'tcx>(ir: &mut IrMaps<'tcx>, expr: &'tcx Expr<'tcx>) { | hir::ExprKind::AssignOp(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) + | hir::ExprKind::InlineAsm(..) | hir::ExprKind::LlvmInlineAsm(..) | hir::ExprKind::Box(..) | hir::ExprKind::Yield(..) @@ -1176,6 +1178,64 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { | hir::ExprKind::Yield(ref e, _) | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(&e, succ), + hir::ExprKind::InlineAsm(ref asm) => { + // Handle non-returning asm + let mut succ = if asm.options.contains(InlineAsmOptions::NORETURN) { + self.s.exit_ln + } else { + succ + }; + + // Do a first pass for writing outputs only + for op in asm.operands.iter().rev() { + match op { + hir::InlineAsmOperand::In { .. } + | hir::InlineAsmOperand::Const { .. } + | hir::InlineAsmOperand::Sym { .. } => {} + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + succ = self.write_place(expr, succ, ACC_WRITE); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE); + } + hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { + if let Some(expr) = out_expr { + succ = self.write_place(expr, succ, ACC_WRITE); + } + } + } + } + + // Then do a second pass for inputs + let mut succ = succ; + for op in asm.operands.iter().rev() { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::Const { expr, .. } + | hir::InlineAsmOperand::Sym { expr, .. } => { + succ = self.propagate_through_expr(expr, succ) + } + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + succ = self.propagate_through_place_components(expr, succ); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + succ = self.propagate_through_place_components(expr, succ); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + if let Some(expr) = out_expr { + succ = self.propagate_through_place_components(expr, succ); + } + succ = self.propagate_through_expr(in_expr, succ); + } + } + } + succ + } + hir::ExprKind::LlvmInlineAsm(ref asm) => { let ia = &asm.inner; let outputs = asm.outputs_exprs; @@ -1397,6 +1457,33 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { } } + hir::ExprKind::InlineAsm(ref asm) => { + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::Const { expr, .. } + | hir::InlineAsmOperand::Sym { expr, .. } => this.visit_expr(expr), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + this.check_place(expr); + this.visit_expr(expr); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + this.check_place(expr); + this.visit_expr(expr); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + this.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + this.check_place(out_expr); + this.visit_expr(out_expr); + } + } + } + } + } + hir::ExprKind::LlvmInlineAsm(ref asm) => { for input in asm.inputs_exprs { this.visit_expr(input); diff --git a/src/librustc_session/lint/builtin.rs b/src/librustc_session/lint/builtin.rs index 3ace9ecbd60e5..3d03e46683ed5 100644 --- a/src/librustc_session/lint/builtin.rs +++ b/src/librustc_session/lint/builtin.rs @@ -508,6 +508,12 @@ declare_lint! { "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`", } +declare_lint! { + pub ASM_SUB_REGISTER, + Warn, + "using only a subset of a register for inline asm inputs", +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -576,6 +582,7 @@ declare_lint_pass! { INDIRECT_STRUCTURAL_MATCH, SOFT_UNSTABLE, INLINE_NO_SANITIZE, + ASM_SUB_REGISTER, ] } diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs index 847b07f0e460f..143401dd3b622 100644 --- a/src/librustc_session/session.rs +++ b/src/librustc_session/session.rs @@ -23,6 +23,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticId, ErrorReported use rustc_span::edition::Edition; use rustc_span::source_map::{self, FileLoader, MultiSpan, RealFileLoader, SourceMap, Span}; use rustc_span::{SourceFileHashAlgorithm, Symbol}; +use rustc_target::asm::InlineAsmArch; use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel}; use rustc_target::spec::{Target, TargetTriple, TlsModel}; @@ -31,6 +32,7 @@ use std::env; use std::io::Write; use std::num::NonZeroU32; use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -158,6 +160,12 @@ pub struct Session { /// if Rust was built with path remapping to `/rustc/$hash` enabled /// (the `rust.remap-debuginfo` option in `config.toml`). pub real_rust_source_base_dir: Option, + + /// Architecture to use for interpreting asm!. + pub asm_arch: Option, + + /// Set of enabled features for the current target. + pub target_features: FxHashSet, } pub struct PerfStats { @@ -1183,6 +1191,12 @@ pub fn build_session_with_source_map( if candidate.join("src/libstd/lib.rs").is_file() { Some(candidate) } else { None } }; + let asm_arch = if target_cfg.target.options.allow_asm { + InlineAsmArch::from_str(&target_cfg.target.arch).ok() + } else { + None + }; + let sess = Session { target: target_cfg, host, @@ -1223,6 +1237,8 @@ pub fn build_session_with_source_map( ctfe_backtrace, miri_unleashed_features: Lock::new(Default::default()), real_rust_source_base_dir, + asm_arch, + target_features: FxHashSet::default(), }; validate_commandline_args_with_session_available(&sess); diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index a61647bfd655f..a38f594920410 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -160,6 +160,7 @@ symbols! { attr, attributes, attr_literals, + att_syntax, augmented_assignments, automatically_derived, avx512_target_feature, @@ -376,6 +377,8 @@ symbols! { if_let, if_while_or_patterns, ignore, + inlateout, + inout, impl_header_lifetime_elision, impl_lint_pass, impl_trait_in_bindings, @@ -411,6 +414,7 @@ symbols! { label_break_value, lang, lang_items, + lateout, let_chains, lhs, lib, @@ -495,12 +499,15 @@ symbols! { no_link, no_main, no_mangle, + nomem, non_ascii_idents, None, non_exhaustive, non_modrs_mods, - no_sanitize, + noreturn, no_niche, + no_sanitize, + nostack, no_stack_check, no_start, no_std, @@ -519,11 +526,13 @@ symbols! { option, Option, option_env, + options, opt_out_copy, or, or_patterns, Ord, Ordering, + out, Output, overlapping_marker_traits, packed, @@ -556,6 +565,7 @@ symbols! { pref_align_of, prelude, prelude_import, + preserves_flags, primitive, proc_dash_macro: "proc-macro", proc_macro, @@ -572,6 +582,7 @@ symbols! { profiler_runtime, ptr_offset_from, pub_restricted, + pure, pushpop_unsafe, quad_precision_float, question_mark, @@ -586,6 +597,7 @@ symbols! { raw_identifiers, raw_ref_op, Rc, + readonly, Ready, reason, recursion_limit, @@ -607,6 +619,7 @@ symbols! { Result, Return, rhs, + riscv_target_feature, rlib, rotate_left, rotate_right, @@ -722,6 +735,7 @@ symbols! { sty, sub_with_overflow, suggestion, + sym, sync_trait, target_feature, target_feature_11, @@ -1186,8 +1200,8 @@ pub mod sym { // have a static symbol and therefore are fast. pub fn integer + Copy + ToString>(n: N) -> Symbol { if let Result::Ok(idx) = n.try_into() { - if let Option::Some(&sym) = digits_array.get(idx) { - return sym; + if let Option::Some(&sym_) = digits_array.get(idx) { + return sym_; } } Symbol::intern(&n.to_string()) diff --git a/src/librustc_target/asm/aarch64.rs b/src/librustc_target/asm/aarch64.rs new file mode 100644 index 0000000000000..e7c9edea7653a --- /dev/null +++ b/src/librustc_target/asm/aarch64.rs @@ -0,0 +1,156 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + AArch64 AArch64InlineAsmRegClass { + reg, + vreg, + vreg_low16, + } +} + +impl AArch64InlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + match self { + Self::reg => &['w', 'x'], + Self::vreg | Self::vreg_low16 => &['b', 'h', 's', 'd', 'q', 'v'], + } + } + + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + ty: InlineAsmType, + ) -> Option<(char, &'static str)> { + match self { + Self::reg => match ty.size().bits() { + 64 => None, + _ => Some(('w', "w0")), + }, + Self::vreg | Self::vreg_low16 => match ty.size().bits() { + 8 => Some(('b', "b0")), + 16 => Some(('h', "h0")), + 32 => Some(('s', "s0")), + 64 => Some(('d', "d0")), + 128 => Some(('q', "q0")), + _ => None, + }, + } + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + match self { + Self::reg => Some(('x', "x0")), + Self::vreg | Self::vreg_low16 => Some(('v', "v0")), + } + } + + pub fn supported_types( + self, + _arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg => types! { _: I8, I16, I32, I64, F32, F64; }, + Self::vreg | Self::vreg_low16 => types! { + "fp": I8, I16, I32, I64, F32, F64, + VecI8(8), VecI16(4), VecI32(2), VecI64(1), VecF32(2), VecF64(1), + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); + }, + } + } +} + +def_regs! { + AArch64 AArch64InlineAsmReg AArch64InlineAsmRegClass { + x0: reg = ["x0", "w0"], + x1: reg = ["x1", "w1"], + x2: reg = ["x2", "w2"], + x3: reg = ["x3", "w3"], + x4: reg = ["x4", "w4"], + x5: reg = ["x5", "w5"], + x6: reg = ["x6", "w6"], + x7: reg = ["x7", "w7"], + x8: reg = ["x8", "w8"], + x9: reg = ["x9", "w9"], + x10: reg = ["x10", "w10"], + x11: reg = ["x11", "w11"], + x12: reg = ["x12", "w12"], + x13: reg = ["x13", "w13"], + x14: reg = ["x14", "w14"], + x15: reg = ["x15", "w15"], + x16: reg = ["x16", "w16"], + x17: reg = ["x17", "w17"], + x18: reg = ["x18", "w18"], + x19: reg = ["x19", "w19"], + x20: reg = ["x20", "w20"], + x21: reg = ["x21", "w21"], + x22: reg = ["x22", "w22"], + x23: reg = ["x23", "w23"], + x24: reg = ["x24", "w24"], + x25: reg = ["x25", "w25"], + x26: reg = ["x26", "w26"], + x27: reg = ["x27", "w27"], + x28: reg = ["x28", "w28"], + x30: reg = ["x30", "w30", "lr"], + v0: vreg, vreg_low16 = ["v0", "b0", "h0", "s0", "d0", "q0"], + v1: vreg, vreg_low16 = ["v1", "b1", "h1", "s1", "d1", "q1"], + v2: vreg, vreg_low16 = ["v2", "b2", "h2", "s2", "d2", "q2"], + v3: vreg, vreg_low16 = ["v3", "b3", "h3", "s3", "d3", "q3"], + v4: vreg, vreg_low16 = ["v4", "b4", "h4", "s4", "d4", "q4"], + v5: vreg, vreg_low16 = ["v5", "b5", "h5", "s5", "d5", "q5"], + v6: vreg, vreg_low16 = ["v6", "b6", "h6", "s6", "d6", "q6"], + v7: vreg, vreg_low16 = ["v7", "b7", "h7", "s7", "d7", "q7"], + v8: vreg, vreg_low16 = ["v8", "b8", "h8", "s8", "d8", "q8"], + v9: vreg, vreg_low16 = ["v9", "b9", "h9", "s9", "d9", "q9"], + v10: vreg, vreg_low16 = ["v10", "b10", "h10", "s10", "d10", "q10"], + v11: vreg, vreg_low16 = ["v11", "b11", "h11", "s11", "d11", "q11"], + v12: vreg, vreg_low16 = ["v12", "b12", "h12", "s12", "d12", "q12"], + v13: vreg, vreg_low16 = ["v13", "b13", "h13", "s13", "d13", "q13"], + v14: vreg, vreg_low16 = ["v14", "b14", "h14", "s14", "d14", "q14"], + v15: vreg, vreg_low16 = ["v15", "b15", "h15", "s15", "d15", "q15"], + v16: vreg = ["v16", "b16", "h16", "s16", "d16", "q16"], + v17: vreg = ["v17", "b17", "h17", "s17", "d17", "q17"], + v18: vreg = ["v18", "b18", "h18", "s18", "d18", "q18"], + v19: vreg = ["v19", "b19", "h19", "s19", "d19", "q19"], + v20: vreg = ["v20", "b20", "h20", "s20", "d20", "q20"], + v21: vreg = ["v21", "b21", "h21", "s21", "d21", "q21"], + v22: vreg = ["v22", "b22", "h22", "s22", "d22", "q22"], + v23: vreg = ["v23", "b23", "h23", "s23", "d23", "q23"], + v24: vreg = ["v24", "b24", "h24", "s24", "d24", "q24"], + v25: vreg = ["v25", "b25", "h25", "s25", "d25", "q25"], + v26: vreg = ["v26", "b26", "h26", "s26", "d26", "q26"], + v27: vreg = ["v27", "b27", "h27", "s27", "d27", "q27"], + v28: vreg = ["v28", "b28", "h28", "s28", "d28", "q28"], + v29: vreg = ["v29", "b29", "h29", "s29", "d29", "q29"], + v30: vreg = ["v30", "b30", "h30", "s30", "d30", "q30"], + v31: vreg = ["v31", "b31", "h31", "s31", "d31", "q31"], + #error = ["x29", "fp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["sp", "wsp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["xzr", "wzr"] => + "the zero register cannot be used as an operand for inline asm", + } +} + +impl AArch64InlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + let (prefix, index) = if (self as u32) < Self::v0 as u32 { + (modifier.unwrap_or('x'), self as u32 - Self::x0 as u32) + } else { + (modifier.unwrap_or('v'), self as u32 - Self::v0 as u32) + }; + assert!(index < 32); + write!(out, "{}{}", prefix, index) + } +} diff --git a/src/librustc_target/asm/arm.rs b/src/librustc_target/asm/arm.rs new file mode 100644 index 0000000000000..1798b2a094975 --- /dev/null +++ b/src/librustc_target/asm/arm.rs @@ -0,0 +1,266 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + Arm ArmInlineAsmRegClass { + reg, + reg_thumb, + sreg, + sreg_low16, + dreg, + dreg_low16, + dreg_low8, + qreg, + qreg_low8, + qreg_low4, + } +} + +impl ArmInlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + match self { + Self::qreg | Self::qreg_low8 | Self::qreg_low4 => &['e', 'f'], + _ => &[], + } + } + + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + _ty: InlineAsmType, + ) -> Option<(char, &'static str)> { + None + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + None + } + + pub fn supported_types( + self, + _arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg | Self::reg_thumb => types! { _: I8, I16, I32, F32; }, + Self::sreg | Self::sreg_low16 => types! { "vfp2": I32, F32; }, + Self::dreg | Self::dreg_low16 | Self::dreg_low8 => types! { + "vfp2": I64, F64, VecI8(8), VecI16(4), VecI32(2), VecI64(1), VecF32(2); + }, + Self::qreg | Self::qreg_low8 | Self::qreg_low4 => types! { + "neon": VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4); + }, + } + } +} + +def_regs! { + Arm ArmInlineAsmReg ArmInlineAsmRegClass { + r0: reg, reg_thumb = ["r0", "a1"], + r1: reg, reg_thumb = ["r1", "a2"], + r2: reg, reg_thumb = ["r2", "a3"], + r3: reg, reg_thumb = ["r3", "a4"], + r4: reg, reg_thumb = ["r4", "v1"], + r5: reg, reg_thumb = ["r5", "v2"], + r6: reg, reg_thumb = ["r6", "v3"], + r7: reg, reg_thumb = ["r7", "v4"], + r8: reg = ["r8", "v5"], + r9: reg = ["r9", "v6", "rfp"], + r10: reg = ["r10", "sl"], + r12: reg = ["r12", "ip"], + r14: reg = ["r14", "lr"], + s0: sreg, sreg_low16 = ["s0"], + s1: sreg, sreg_low16 = ["s1"], + s2: sreg, sreg_low16 = ["s2"], + s3: sreg, sreg_low16 = ["s3"], + s4: sreg, sreg_low16 = ["s4"], + s5: sreg, sreg_low16 = ["s5"], + s6: sreg, sreg_low16 = ["s6"], + s7: sreg, sreg_low16 = ["s7"], + s8: sreg, sreg_low16 = ["s8"], + s9: sreg, sreg_low16 = ["s9"], + s10: sreg, sreg_low16 = ["s10"], + s11: sreg, sreg_low16 = ["s11"], + s12: sreg, sreg_low16 = ["s12"], + s13: sreg, sreg_low16 = ["s13"], + s14: sreg, sreg_low16 = ["s14"], + s15: sreg, sreg_low16 = ["s15"], + s16: sreg = ["s16"], + s17: sreg = ["s17"], + s18: sreg = ["s18"], + s19: sreg = ["s19"], + s20: sreg = ["s20"], + s21: sreg = ["s21"], + s22: sreg = ["s22"], + s23: sreg = ["s23"], + s24: sreg = ["s24"], + s25: sreg = ["s25"], + s26: sreg = ["s26"], + s27: sreg = ["s27"], + s28: sreg = ["s28"], + s29: sreg = ["s29"], + s30: sreg = ["s30"], + s31: sreg = ["s31"], + d0: dreg, dreg_low16, dreg_low8 = ["d0"], + d1: dreg, dreg_low16, dreg_low8 = ["d1"], + d2: dreg, dreg_low16, dreg_low8 = ["d2"], + d3: dreg, dreg_low16, dreg_low8 = ["d3"], + d4: dreg, dreg_low16, dreg_low8 = ["d4"], + d5: dreg, dreg_low16, dreg_low8 = ["d5"], + d6: dreg, dreg_low16, dreg_low8 = ["d6"], + d7: dreg, dreg_low16, dreg_low8 = ["d7"], + d8: dreg, dreg_low16 = ["d8"], + d9: dreg, dreg_low16 = ["d9"], + d10: dreg, dreg_low16 = ["d10"], + d11: dreg, dreg_low16 = ["d11"], + d12: dreg, dreg_low16 = ["d12"], + d13: dreg, dreg_low16 = ["d13"], + d14: dreg, dreg_low16 = ["d14"], + d15: dreg, dreg_low16 = ["d15"], + d16: dreg = ["d16"], + d17: dreg = ["d17"], + d18: dreg = ["d18"], + d19: dreg = ["d19"], + d20: dreg = ["d20"], + d21: dreg = ["d21"], + d22: dreg = ["d22"], + d23: dreg = ["d23"], + d24: dreg = ["d24"], + d25: dreg = ["d25"], + d26: dreg = ["d26"], + d27: dreg = ["d27"], + d28: dreg = ["d28"], + d29: dreg = ["d29"], + d30: dreg = ["d30"], + d31: dreg = ["d31"], + q0: qreg, qreg_low8, qreg_low4 = ["q0"], + q1: qreg, qreg_low8, qreg_low4 = ["q1"], + q2: qreg, qreg_low8, qreg_low4 = ["q2"], + q3: qreg, qreg_low8, qreg_low4 = ["q3"], + q4: qreg, qreg_low8 = ["q4"], + q5: qreg, qreg_low8 = ["q5"], + q6: qreg, qreg_low8 = ["q6"], + q7: qreg, qreg_low8 = ["q7"], + q8: qreg = ["q8"], + q9: qreg = ["q9"], + q10: qreg = ["q10"], + q11: qreg = ["q11"], + q12: qreg = ["q12"], + q13: qreg = ["q13"], + q14: qreg = ["q14"], + q15: qreg = ["q15"], + #error = ["r11", "fp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["r13", "sp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["r15", "pc"] => + "the program pointer cannot be used as an operand for inline asm", + } +} + +impl ArmInlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + // Only qreg is allowed to have modifiers. This should have been + // validated already by now. + if let Some(modifier) = modifier { + let index = self as u32 - Self::q0 as u32; + assert!(index < 16); + let index = index * 2 + (modifier == 'f') as u32; + write!(out, "d{}", index) + } else { + out.write_str(self.name()) + } + } + + pub fn overlapping_regs(self, mut cb: impl FnMut(ArmInlineAsmReg)) { + cb(self); + + macro_rules! reg_conflicts { + ( + $( + $q:ident : $d0:ident $d1:ident : $s0:ident $s1:ident $s2:ident $s3:ident + ),*; + $( + $q_high:ident : $d0_high:ident $d1_high:ident + ),*; + ) => { + match self { + $( + Self::$q => { + cb(Self::$d0); + cb(Self::$d1); + cb(Self::$s0); + cb(Self::$s1); + cb(Self::$s2); + cb(Self::$s3); + } + Self::$d0 => { + cb(Self::$q); + cb(Self::$s0); + cb(Self::$s1); + } + Self::$d1 => { + cb(Self::$q); + cb(Self::$s2); + cb(Self::$s3); + } + Self::$s0 | Self::$s1 => { + cb(Self::$q); + cb(Self::$d0); + } + Self::$s2 | Self::$s3 => { + cb(Self::$q); + cb(Self::$d1); + } + )* + $( + Self::$q_high => { + cb(Self::$d0_high); + cb(Self::$d1_high); + } + Self::$d0_high | Self::$d1_high => { + cb(Self::$q_high); + } + )* + _ => {}, + } + }; + } + + // ARM's floating-point register file is interesting in that it can be + // viewed as 16 128-bit registers, 32 64-bit registers or 32 32-bit + // registers. Because these views overlap, the registers of different + // widths will conflict (e.g. d0 overlaps with s0 and s1, and q1 + // overlaps with d2 and d3). + // + // See section E1.3.1 of the ARM Architecture Reference Manual for + // ARMv8-A for more details. + reg_conflicts! { + q0 : d0 d1 : s0 s1 s2 s3, + q1 : d2 d3 : s4 s5 s6 s7, + q2 : d4 d5 : s8 s9 s10 s11, + q3 : d6 d7 : s12 s13 s14 s15, + q4 : d8 d9 : s16 s17 s18 s19, + q5 : d10 d11 : s20 s21 s22 s23, + q6 : d12 d13 : s24 s25 s26 s27, + q7 : d14 d15 : s28 s29 s30 s31; + q8 : d16 d17, + q9 : d18 d19, + q10 : d20 d21, + q11 : d22 d23, + q12 : d24 d25, + q13 : d26 d27, + q14 : d28 d29, + q15 : d30 d31; + } + } +} diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs new file mode 100644 index 0000000000000..05aa85ecb7448 --- /dev/null +++ b/src/librustc_target/asm/mod.rs @@ -0,0 +1,522 @@ +use crate::abi::Size; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_macros::HashStable_Generic; +use rustc_span::Symbol; +use std::fmt; +use std::str::FromStr; + +#[macro_use] +macro_rules! def_reg_class { + ($arch:ident $arch_regclass:ident { + $( + $class:ident, + )* + }) => { + #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash, HashStable_Generic)] + #[allow(non_camel_case_types)] + pub enum $arch_regclass { + $($class,)* + } + + impl $arch_regclass { + pub fn name(self) -> &'static str { + match self { + $(Self::$class => stringify!($class),)* + } + } + + pub fn parse(_arch: super::InlineAsmArch, name: &str) -> Result { + match name { + $( + stringify!($class) => Ok(Self::$class), + )* + _ => Err("unknown register class"), + } + } + } + + pub(super) fn regclass_map() -> rustc_data_structures::fx::FxHashMap< + super::InlineAsmRegClass, + rustc_data_structures::fx::FxHashSet, + > { + use rustc_data_structures::fx::{FxHashMap, FxHashSet}; + use super::InlineAsmRegClass; + let mut map = FxHashMap::default(); + $( + map.insert(InlineAsmRegClass::$arch($arch_regclass::$class), FxHashSet::default()); + )* + map + } + } +} + +#[macro_use] +macro_rules! def_regs { + ($arch:ident $arch_reg:ident $arch_regclass:ident { + $( + $reg:ident: $class:ident $(, $extra_class:ident)* = [$reg_name:literal $(, $alias:literal)*] $(% $filter:ident)?, + )* + $( + #error = [$($bad_reg:literal),+] => $error:literal, + )* + }) => { + #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash, HashStable_Generic)] + #[allow(non_camel_case_types)] + pub enum $arch_reg { + $($reg,)* + } + + impl $arch_reg { + pub fn name(self) -> &'static str { + match self { + $(Self::$reg => $reg_name,)* + } + } + + pub fn reg_class(self) -> $arch_regclass { + match self { + $(Self::$reg => $arch_regclass::$class,)* + } + } + + pub fn parse( + _arch: super::InlineAsmArch, + mut _has_feature: impl FnMut(&str) -> bool, + name: &str, + ) -> Result { + match name { + $( + $($alias)|* | $reg_name => { + $($filter(_arch, &mut _has_feature, false)?;)? + Ok(Self::$reg) + } + )* + $( + $($bad_reg)|* => Err($error), + )* + _ => Err("unknown register"), + } + } + } + + pub(super) fn fill_reg_map( + _arch: super::InlineAsmArch, + mut _has_feature: impl FnMut(&str) -> bool, + map: &mut rustc_data_structures::fx::FxHashMap< + super::InlineAsmRegClass, + rustc_data_structures::fx::FxHashSet, + >, + ) { + use super::{InlineAsmReg, InlineAsmRegClass}; + $( + if $($filter(_arch, &mut _has_feature, true).is_ok() &&)? true { + if let Some(set) = map.get_mut(&InlineAsmRegClass::$arch($arch_regclass::$class)) { + set.insert(InlineAsmReg::$arch($arch_reg::$reg)); + } + $( + if let Some(set) = map.get_mut(&InlineAsmRegClass::$arch($arch_regclass::$extra_class)) { + set.insert(InlineAsmReg::$arch($arch_reg::$reg)); + } + )* + } + )* + } + } +} + +#[macro_use] +macro_rules! types { + ( + $(_ : $($ty:expr),+;)? + $($feature:literal: $($ty2:expr),+;)* + ) => { + { + use super::InlineAsmType::*; + &[ + $($( + ($ty, None), + )*)? + $($( + ($ty2, Some($feature)), + )*)* + ] + } + }; +} + +mod aarch64; +mod arm; +mod riscv; +mod x86; + +pub use aarch64::{AArch64InlineAsmReg, AArch64InlineAsmRegClass}; +pub use arm::{ArmInlineAsmReg, ArmInlineAsmRegClass}; +pub use riscv::{RiscVInlineAsmReg, RiscVInlineAsmRegClass}; +pub use x86::{X86InlineAsmReg, X86InlineAsmRegClass}; + +#[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash)] +pub enum InlineAsmArch { + X86, + X86_64, + Arm, + AArch64, + RiscV32, + RiscV64, +} + +impl FromStr for InlineAsmArch { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "x86" => Ok(Self::X86), + "x86_64" => Ok(Self::X86_64), + "arm" => Ok(Self::Arm), + "aarch64" => Ok(Self::AArch64), + "riscv32" => Ok(Self::RiscV32), + "riscv64" => Ok(Self::RiscV64), + _ => Err(()), + } + } +} + +#[derive( + Copy, + Clone, + RustcEncodable, + RustcDecodable, + Debug, + Eq, + PartialEq, + Hash, + HashStable_Generic +)] +pub enum InlineAsmReg { + X86(X86InlineAsmReg), + Arm(ArmInlineAsmReg), + AArch64(AArch64InlineAsmReg), + RiscV(RiscVInlineAsmReg), +} + +impl InlineAsmReg { + pub fn name(self) -> &'static str { + match self { + Self::X86(r) => r.name(), + Self::Arm(r) => r.name(), + Self::AArch64(r) => r.name(), + Self::RiscV(r) => r.name(), + } + } + + pub fn reg_class(self) -> InlineAsmRegClass { + match self { + Self::X86(r) => InlineAsmRegClass::X86(r.reg_class()), + Self::Arm(r) => InlineAsmRegClass::Arm(r.reg_class()), + Self::AArch64(r) => InlineAsmRegClass::AArch64(r.reg_class()), + Self::RiscV(r) => InlineAsmRegClass::RiscV(r.reg_class()), + } + } + + pub fn parse( + arch: InlineAsmArch, + has_feature: impl FnMut(&str) -> bool, + name: Symbol, + ) -> Result { + // FIXME: use direct symbol comparison for register names + name.with(|name| { + Ok(match arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + Self::X86(X86InlineAsmReg::parse(arch, has_feature, name)?) + } + InlineAsmArch::Arm => Self::Arm(ArmInlineAsmReg::parse(arch, has_feature, name)?), + InlineAsmArch::AArch64 => { + Self::AArch64(AArch64InlineAsmReg::parse(arch, has_feature, name)?) + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => { + Self::RiscV(RiscVInlineAsmReg::parse(arch, has_feature, name)?) + } + }) + }) + } + + // NOTE: This function isn't used at the moment, but is needed to support + // falling back to an external assembler. + pub fn emit( + self, + out: &mut dyn fmt::Write, + arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + match self { + Self::X86(r) => r.emit(out, arch, modifier), + Self::Arm(r) => r.emit(out, arch, modifier), + Self::AArch64(r) => r.emit(out, arch, modifier), + Self::RiscV(r) => r.emit(out, arch, modifier), + } + } + + pub fn overlapping_regs(self, mut cb: impl FnMut(InlineAsmReg)) { + match self { + Self::X86(r) => r.overlapping_regs(|r| cb(Self::X86(r))), + Self::Arm(r) => r.overlapping_regs(|r| cb(Self::Arm(r))), + Self::AArch64(_) => cb(self), + Self::RiscV(_) => cb(self), + } + } +} + +#[derive( + Copy, + Clone, + RustcEncodable, + RustcDecodable, + Debug, + Eq, + PartialEq, + Hash, + HashStable_Generic +)] +pub enum InlineAsmRegClass { + X86(X86InlineAsmRegClass), + Arm(ArmInlineAsmRegClass), + AArch64(AArch64InlineAsmRegClass), + RiscV(RiscVInlineAsmRegClass), +} + +impl InlineAsmRegClass { + pub fn name(self) -> &'static str { + match self { + Self::X86(r) => r.name(), + Self::Arm(r) => r.name(), + Self::AArch64(r) => r.name(), + Self::RiscV(r) => r.name(), + } + } + + /// Returns a suggested register class to use for this type. This is called + /// after type checking via `supported_types` fails to give a better error + /// message to the user. + pub fn suggest_class(self, arch: InlineAsmArch, ty: InlineAsmType) -> Option { + match self { + Self::X86(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::X86), + Self::Arm(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Arm), + Self::AArch64(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::AArch64), + Self::RiscV(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::RiscV), + } + } + + /// Returns a suggested template modifier to use for this type and an + /// example of a register named formatted with it. + /// + /// Such suggestions are useful if a type smaller than the full register + /// size is used and a modifier can be used to point to the subregister of + /// the correct size. + pub fn suggest_modifier( + self, + arch: InlineAsmArch, + ty: InlineAsmType, + ) -> Option<(char, &'static str)> { + match self { + Self::X86(r) => r.suggest_modifier(arch, ty), + Self::Arm(r) => r.suggest_modifier(arch, ty), + Self::AArch64(r) => r.suggest_modifier(arch, ty), + Self::RiscV(r) => r.suggest_modifier(arch, ty), + } + } + + /// Returns the default modifier for this register and an example of a + /// register named formatted with it. + /// + /// This is only needed when the register class can suggest a modifier, so + /// that the user can be shown how to get the default behavior without a + /// warning. + pub fn default_modifier(self, arch: InlineAsmArch) -> Option<(char, &'static str)> { + match self { + Self::X86(r) => r.default_modifier(arch), + Self::Arm(r) => r.default_modifier(arch), + Self::AArch64(r) => r.default_modifier(arch), + Self::RiscV(r) => r.default_modifier(arch), + } + } + + /// Returns a list of supported types for this register class, each with a + /// options target feature required to use this type. + pub fn supported_types( + self, + arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::X86(r) => r.supported_types(arch), + Self::Arm(r) => r.supported_types(arch), + Self::AArch64(r) => r.supported_types(arch), + Self::RiscV(r) => r.supported_types(arch), + } + } + + pub fn parse(arch: InlineAsmArch, name: Symbol) -> Result { + // FIXME: use direct symbol comparison for register class names + name.with(|name| { + Ok(match arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + Self::X86(X86InlineAsmRegClass::parse(arch, name)?) + } + InlineAsmArch::Arm => Self::Arm(ArmInlineAsmRegClass::parse(arch, name)?), + InlineAsmArch::AArch64 => { + Self::AArch64(AArch64InlineAsmRegClass::parse(arch, name)?) + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => { + Self::RiscV(RiscVInlineAsmRegClass::parse(arch, name)?) + } + }) + }) + } + + /// Returns the list of template modifiers that can be used with this + /// register class. + pub fn valid_modifiers(self, arch: InlineAsmArch) -> &'static [char] { + match self { + Self::X86(r) => r.valid_modifiers(arch), + Self::Arm(r) => r.valid_modifiers(arch), + Self::AArch64(r) => r.valid_modifiers(arch), + Self::RiscV(r) => r.valid_modifiers(arch), + } + } +} + +#[derive( + Copy, + Clone, + RustcEncodable, + RustcDecodable, + Debug, + Eq, + PartialEq, + Hash, + HashStable_Generic +)] +pub enum InlineAsmRegOrRegClass { + Reg(InlineAsmReg), + RegClass(InlineAsmRegClass), +} + +impl InlineAsmRegOrRegClass { + pub fn reg_class(self) -> InlineAsmRegClass { + match self { + Self::Reg(r) => r.reg_class(), + Self::RegClass(r) => r, + } + } +} + +impl fmt::Display for InlineAsmRegOrRegClass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Reg(r) => write!(f, "\"{}\"", r.name()), + Self::RegClass(r) => f.write_str(r.name()), + } + } +} + +/// Set of types which can be used with a particular register class. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum InlineAsmType { + I8, + I16, + I32, + I64, + I128, + F32, + F64, + VecI8(u64), + VecI16(u64), + VecI32(u64), + VecI64(u64), + VecI128(u64), + VecF32(u64), + VecF64(u64), +} + +impl InlineAsmType { + pub fn is_integer(self) -> bool { + match self { + Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 => true, + _ => false, + } + } + + pub fn size(self) -> Size { + Size::from_bytes(match self { + Self::I8 => 1, + Self::I16 => 2, + Self::I32 => 4, + Self::I64 => 8, + Self::I128 => 16, + Self::F32 => 4, + Self::F64 => 8, + Self::VecI8(n) => n * 1, + Self::VecI16(n) => n * 2, + Self::VecI32(n) => n * 4, + Self::VecI64(n) => n * 8, + Self::VecI128(n) => n * 16, + Self::VecF32(n) => n * 4, + Self::VecF64(n) => n * 8, + }) + } +} + +impl fmt::Display for InlineAsmType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::I8 => f.write_str("i8"), + Self::I16 => f.write_str("i16"), + Self::I32 => f.write_str("i32"), + Self::I64 => f.write_str("i64"), + Self::I128 => f.write_str("i128"), + Self::F32 => f.write_str("f32"), + Self::F64 => f.write_str("f64"), + Self::VecI8(n) => write!(f, "i8x{}", n), + Self::VecI16(n) => write!(f, "i16x{}", n), + Self::VecI32(n) => write!(f, "i32x{}", n), + Self::VecI64(n) => write!(f, "i64x{}", n), + Self::VecI128(n) => write!(f, "i128x{}", n), + Self::VecF32(n) => write!(f, "f32x{}", n), + Self::VecF64(n) => write!(f, "f64x{}", n), + } + } +} + +/// Returns the full set of allocatable registers for a given architecture. +/// +/// The registers are structured as a map containing the set of allocatable +/// registers in each register class. A particular register may be allocatable +/// from multiple register classes, in which case it will appear multiple times +/// in the map. +// NOTE: This function isn't used at the moment, but is needed to support +// falling back to an external assembler. +pub fn allocatable_registers( + arch: InlineAsmArch, + has_feature: impl FnMut(&str) -> bool, +) -> FxHashMap> { + match arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + let mut map = x86::regclass_map(); + x86::fill_reg_map(arch, has_feature, &mut map); + map + } + InlineAsmArch::Arm => { + let mut map = arm::regclass_map(); + arm::fill_reg_map(arch, has_feature, &mut map); + map + } + InlineAsmArch::AArch64 => { + let mut map = aarch64::regclass_map(); + aarch64::fill_reg_map(arch, has_feature, &mut map); + map + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => { + let mut map = riscv::regclass_map(); + riscv::fill_reg_map(arch, has_feature, &mut map); + map + } + } +} diff --git a/src/librustc_target/asm/riscv.rs b/src/librustc_target/asm/riscv.rs new file mode 100644 index 0000000000000..3ff542247ff02 --- /dev/null +++ b/src/librustc_target/asm/riscv.rs @@ -0,0 +1,145 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + RiscV RiscVInlineAsmRegClass { + reg, + freg, + } +} + +impl RiscVInlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + &[] + } + + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + _ty: InlineAsmType, + ) -> Option<(char, &'static str)> { + None + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + None + } + + pub fn supported_types( + self, + arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg => { + if arch == InlineAsmArch::RiscV64 { + types! { _: I8, I16, I32, I64, F32, F64; } + } else { + types! { _: I8, I16, I32, F32; } + } + } + Self::freg => types! { "f": F32; "d": F64; }, + } + } +} + +fn not_e( + _arch: InlineAsmArch, + mut has_feature: impl FnMut(&str) -> bool, + _allocating: bool, +) -> Result<(), &'static str> { + if has_feature("e") { + Err("register can't be used with the `e` target feature") + } else { + Ok(()) + } +} + +def_regs! { + RiscV RiscVInlineAsmReg RiscVInlineAsmRegClass { + x1: reg = ["x1", "ra"], + x5: reg = ["x5", "t0"], + x6: reg = ["x6", "t1"], + x7: reg = ["x7", "t2"], + x9: reg = ["x9", "s1"], + x10: reg = ["x10", "a0"], + x11: reg = ["x11", "a1"], + x12: reg = ["x12", "a2"], + x13: reg = ["x13", "a3"], + x14: reg = ["x14", "a4"], + x15: reg = ["x15", "a5"], + x16: reg = ["x16", "a6"] % not_e, + x17: reg = ["x17", "a7"] % not_e, + x18: reg = ["x18", "s2"] % not_e, + x19: reg = ["x19", "s3"] % not_e, + x20: reg = ["x20", "s4"] % not_e, + x21: reg = ["x21", "s5"] % not_e, + x22: reg = ["x22", "s6"] % not_e, + x23: reg = ["x23", "s7"] % not_e, + x24: reg = ["x24", "s8"] % not_e, + x25: reg = ["x25", "s9"] % not_e, + x26: reg = ["x26", "s10"] % not_e, + x27: reg = ["x27", "s11"] % not_e, + x28: reg = ["x28", "t3"] % not_e, + x29: reg = ["x29", "t4"] % not_e, + x30: reg = ["x30", "t5"] % not_e, + x31: reg = ["x31", "t6"] % not_e, + f0: freg = ["f0", "ft0"], + f1: freg = ["f1", "ft1"], + f2: freg = ["f2", "ft2"], + f3: freg = ["f3", "ft3"], + f4: freg = ["f4", "ft4"], + f5: freg = ["f5", "ft5"], + f6: freg = ["f6", "ft6"], + f7: freg = ["f7", "ft7"], + f8: freg = ["f8", "fs0"], + f9: freg = ["f9", "fs1"], + f10: freg = ["f10", "fa0"], + f11: freg = ["f11", "fa1"], + f12: freg = ["f12", "fa2"], + f13: freg = ["f13", "fa3"], + f14: freg = ["f14", "fa4"], + f15: freg = ["f15", "fa5"], + f16: freg = ["f16", "fa6"], + f17: freg = ["f17", "fa7"], + f18: freg = ["f18", "fs2"], + f19: freg = ["f19", "fs3"], + f20: freg = ["f20", "fs4"], + f21: freg = ["f21", "fs5"], + f22: freg = ["f22", "fs6"], + f23: freg = ["f23", "fs7"], + f24: freg = ["f24", "fs8"], + f25: freg = ["f25", "fs9"], + f26: freg = ["f26", "fs10"], + f27: freg = ["f27", "fs11"], + f28: freg = ["f28", "ft8"], + f29: freg = ["f29", "ft9"], + f30: freg = ["f30", "ft10"], + f31: freg = ["f31", "ft11"], + #error = ["x8", "s0", "fp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["x2", "sp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["x3", "gp"] => + "the global pointer cannot be used as an operand for inline asm", + #error = ["x4", "tp"] => + "the thread pointer cannot be used as an operand for inline asm" , + #error = ["x0", "zero"] => + "the zero register cannot be used as an operand for inline asm", + } +} + +impl RiscVInlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + _modifier: Option, + ) -> fmt::Result { + out.write_str(self.name()) + } +} diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs new file mode 100644 index 0000000000000..ed51b526414d1 --- /dev/null +++ b/src/librustc_target/asm/x86.rs @@ -0,0 +1,424 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + X86 X86InlineAsmRegClass { + reg, + reg_abcd, + reg_byte, + xmm_reg, + ymm_reg, + zmm_reg, + kreg, + } +} + +impl X86InlineAsmRegClass { + pub fn valid_modifiers(self, arch: super::InlineAsmArch) -> &'static [char] { + match self { + Self::reg => { + if arch == InlineAsmArch::X86_64 { + &['l', 'x', 'e', 'r'] + } else { + &['x', 'e'] + } + } + Self::reg_abcd => { + if arch == InlineAsmArch::X86_64 { + &['l', 'h', 'x', 'e', 'r'] + } else { + &['l', 'h', 'x', 'e'] + } + } + Self::reg_byte => &[], + Self::xmm_reg | Self::ymm_reg | Self::zmm_reg => &['x', 'y', 'z'], + Self::kreg => &[], + } + } + + pub fn suggest_class(self, _arch: InlineAsmArch, ty: InlineAsmType) -> Option { + match self { + Self::reg | Self::reg_abcd if ty.size().bits() == 8 => Some(Self::reg_byte), + _ => None, + } + } + + pub fn suggest_modifier( + self, + arch: InlineAsmArch, + ty: InlineAsmType, + ) -> Option<(char, &'static str)> { + match self { + Self::reg => match ty.size().bits() { + 16 => Some(('x', "ax")), + 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax")), + _ => None, + }, + Self::reg_abcd => match ty.size().bits() { + 16 => Some(('x', "ax")), + 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax")), + _ => None, + }, + Self::reg_byte => None, + Self::xmm_reg => None, + Self::ymm_reg => match ty.size().bits() { + 256 => None, + _ => Some(('x', "xmm0")), + }, + Self::zmm_reg => match ty.size().bits() { + 512 => None, + 256 => Some(('y', "ymm0")), + _ => Some(('x', "xmm0")), + }, + Self::kreg => None, + } + } + + pub fn default_modifier(self, arch: InlineAsmArch) -> Option<(char, &'static str)> { + match self { + Self::reg | Self::reg_abcd => { + if arch == InlineAsmArch::X86_64 { + Some(('r', "rax")) + } else { + Some(('e', "eax")) + } + } + Self::reg_byte => None, + Self::xmm_reg => Some(('x', "xmm0")), + Self::ymm_reg => Some(('y', "ymm0")), + Self::zmm_reg => Some(('z', "zmm0")), + Self::kreg => None, + } + } + + pub fn supported_types( + self, + arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg | Self::reg_abcd => { + if arch == InlineAsmArch::X86_64 { + types! { _: I16, I32, I64, F32, F64; } + } else { + types! { _: I16, I32, F32; } + } + } + Self::reg_byte => types! { _: I8; }, + Self::xmm_reg => types! { + "sse": I32, I64, F32, F64, + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); + }, + Self::ymm_reg => types! { + "avx": I32, I64, F32, F64, + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), + VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4); + }, + Self::zmm_reg => types! { + "avx512f": I32, I64, F32, F64, + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), + VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4), + VecI8(64), VecI16(32), VecI32(16), VecI64(8), VecF32(16), VecF64(8); + }, + Self::kreg => types! { + "avx512f": I8, I16; + "avx512bw": I32, I64; + }, + } + } +} + +fn x86_64_only( + arch: InlineAsmArch, + _has_feature: impl FnMut(&str) -> bool, + _allocating: bool, +) -> Result<(), &'static str> { + match arch { + InlineAsmArch::X86 => Err("register is only available on x86_64"), + InlineAsmArch::X86_64 => Ok(()), + _ => unreachable!(), + } +} + +fn high_byte( + arch: InlineAsmArch, + _has_feature: impl FnMut(&str) -> bool, + allocating: bool, +) -> Result<(), &'static str> { + match arch { + InlineAsmArch::X86_64 if allocating => { + // The error message isn't actually used... + Err("high byte registers are not allocated by reg_byte") + } + _ => Ok(()), + } +} + +def_regs! { + X86 X86InlineAsmReg X86InlineAsmRegClass { + ax: reg, reg_abcd = ["ax", "eax", "rax"], + bx: reg, reg_abcd = ["bx", "ebx", "rbx"], + cx: reg, reg_abcd = ["cx", "ecx", "rcx"], + dx: reg, reg_abcd = ["dx", "edx", "rdx"], + si: reg = ["si", "esi", "rsi"], + di: reg = ["di", "edi", "rdi"], + r8: reg = ["r8", "r8w", "r8d"] % x86_64_only, + r9: reg = ["r9", "r9w", "r9d"] % x86_64_only, + r10: reg = ["r10", "r10w", "r10d"] % x86_64_only, + r11: reg = ["r11", "r11w", "r11d"] % x86_64_only, + r12: reg = ["r12", "r12w", "r12d"] % x86_64_only, + r13: reg = ["r13", "r13w", "r13d"] % x86_64_only, + r14: reg = ["r14", "r14w", "r14d"] % x86_64_only, + r15: reg = ["r15", "r15w", "r15d"] % x86_64_only, + al: reg_byte = ["al"], + ah: reg_byte = ["ah"] % high_byte, + bl: reg_byte = ["bl"], + bh: reg_byte = ["bh"] % high_byte, + cl: reg_byte = ["cl"], + ch: reg_byte = ["ch"] % high_byte, + dl: reg_byte = ["dl"], + dh: reg_byte = ["dh"] % high_byte, + sil: reg_byte = ["sil"] % x86_64_only, + dil: reg_byte = ["dil"] % x86_64_only, + r8b: reg_byte = ["r8b"] % x86_64_only, + r9b: reg_byte = ["r9b"] % x86_64_only, + r10b: reg_byte = ["r10b"] % x86_64_only, + r11b: reg_byte = ["r11b"] % x86_64_only, + r12b: reg_byte = ["r12b"] % x86_64_only, + r13b: reg_byte = ["r13b"] % x86_64_only, + r14b: reg_byte = ["r14b"] % x86_64_only, + r15b: reg_byte = ["r15b"] % x86_64_only, + xmm0: xmm_reg = ["xmm0"], + xmm1: xmm_reg = ["xmm1"], + xmm2: xmm_reg = ["xmm2"], + xmm3: xmm_reg = ["xmm3"], + xmm4: xmm_reg = ["xmm4"], + xmm5: xmm_reg = ["xmm5"], + xmm6: xmm_reg = ["xmm6"], + xmm7: xmm_reg = ["xmm7"], + xmm8: xmm_reg = ["xmm8"] % x86_64_only, + xmm9: xmm_reg = ["xmm9"] % x86_64_only, + xmm10: xmm_reg = ["xmm10"] % x86_64_only, + xmm11: xmm_reg = ["xmm11"] % x86_64_only, + xmm12: xmm_reg = ["xmm12"] % x86_64_only, + xmm13: xmm_reg = ["xmm13"] % x86_64_only, + xmm14: xmm_reg = ["xmm14"] % x86_64_only, + xmm15: xmm_reg = ["xmm15"] % x86_64_only, + ymm0: ymm_reg = ["ymm0"], + ymm1: ymm_reg = ["ymm1"], + ymm2: ymm_reg = ["ymm2"], + ymm3: ymm_reg = ["ymm3"], + ymm4: ymm_reg = ["ymm4"], + ymm5: ymm_reg = ["ymm5"], + ymm6: ymm_reg = ["ymm6"], + ymm7: ymm_reg = ["ymm7"], + ymm8: ymm_reg = ["ymm8"] % x86_64_only, + ymm9: ymm_reg = ["ymm9"] % x86_64_only, + ymm10: ymm_reg = ["ymm10"] % x86_64_only, + ymm11: ymm_reg = ["ymm11"] % x86_64_only, + ymm12: ymm_reg = ["ymm12"] % x86_64_only, + ymm13: ymm_reg = ["ymm13"] % x86_64_only, + ymm14: ymm_reg = ["ymm14"] % x86_64_only, + ymm15: ymm_reg = ["ymm15"] % x86_64_only, + zmm0: zmm_reg = ["zmm0"], + zmm1: zmm_reg = ["zmm1"], + zmm2: zmm_reg = ["zmm2"], + zmm3: zmm_reg = ["zmm3"], + zmm4: zmm_reg = ["zmm4"], + zmm5: zmm_reg = ["zmm5"], + zmm6: zmm_reg = ["zmm6"], + zmm7: zmm_reg = ["zmm7"], + zmm8: zmm_reg = ["zmm8"] % x86_64_only, + zmm9: zmm_reg = ["zmm9"] % x86_64_only, + zmm10: zmm_reg = ["zmm10"] % x86_64_only, + zmm11: zmm_reg = ["zmm11"] % x86_64_only, + zmm12: zmm_reg = ["zmm12"] % x86_64_only, + zmm13: zmm_reg = ["zmm13"] % x86_64_only, + zmm14: zmm_reg = ["zmm14"] % x86_64_only, + zmm15: zmm_reg = ["zmm15"] % x86_64_only, + zmm16: zmm_reg = ["zmm16", "xmm16", "ymm16"] % x86_64_only, + zmm17: zmm_reg = ["zmm17", "xmm17", "ymm17"] % x86_64_only, + zmm18: zmm_reg = ["zmm18", "xmm18", "ymm18"] % x86_64_only, + zmm19: zmm_reg = ["zmm19", "xmm19", "ymm19"] % x86_64_only, + zmm20: zmm_reg = ["zmm20", "xmm20", "ymm20"] % x86_64_only, + zmm21: zmm_reg = ["zmm21", "xmm21", "ymm21"] % x86_64_only, + zmm22: zmm_reg = ["zmm22", "xmm22", "ymm22"] % x86_64_only, + zmm23: zmm_reg = ["zmm23", "xmm23", "ymm23"] % x86_64_only, + zmm24: zmm_reg = ["zmm24", "xmm24", "ymm24"] % x86_64_only, + zmm25: zmm_reg = ["zmm25", "xmm25", "ymm25"] % x86_64_only, + zmm26: zmm_reg = ["zmm26", "xmm26", "ymm26"] % x86_64_only, + zmm27: zmm_reg = ["zmm27", "xmm27", "ymm27"] % x86_64_only, + zmm28: zmm_reg = ["zmm28", "xmm28", "ymm28"] % x86_64_only, + zmm29: zmm_reg = ["zmm29", "xmm29", "ymm29"] % x86_64_only, + zmm30: zmm_reg = ["zmm30", "xmm30", "ymm30"] % x86_64_only, + zmm31: zmm_reg = ["zmm31", "xmm31", "ymm31"] % x86_64_only, + k1: kreg = ["k1"], + k2: kreg = ["k2"], + k3: kreg = ["k3"], + k4: kreg = ["k4"], + k5: kreg = ["k5"], + k6: kreg = ["k6"], + k7: kreg = ["k7"], + #error = ["bp", "bpl", "ebp", "rbp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["sp", "spl", "esp", "rsp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["ip", "eip", "rip"] => + "the instruction pointer cannot be used as an operand for inline asm", + #error = ["st", "st(0)", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"] => + "x87 registers are not currently supported as operands for inline asm", + #error = ["mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7"] => + "MMX registers are not currently supported as operands for inline asm", + #error = ["k0"] => + "the k0 AVX mask register cannot be used as an operand for inline asm", + } +} + +impl X86InlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + let reg_default_modifier = match arch { + InlineAsmArch::X86 => 'e', + InlineAsmArch::X86_64 => 'r', + _ => unreachable!(), + }; + if self as u32 <= Self::dx as u32 { + let root = ['a', 'b', 'c', 'd'][self as usize - Self::ax as usize]; + match modifier.unwrap_or(reg_default_modifier) { + 'l' => write!(out, "{}l", root), + 'h' => write!(out, "{}h", root), + 'x' => write!(out, "{}x", root), + 'e' => write!(out, "e{}x", root), + 'r' => write!(out, "r{}x", root), + _ => unreachable!(), + } + } else if self as u32 <= Self::di as u32 { + let root = self.name(); + match modifier.unwrap_or(reg_default_modifier) { + 'l' => write!(out, "{}l", root), + 'x' => write!(out, "{}", root), + 'e' => write!(out, "e{}", root), + 'r' => write!(out, "r{}", root), + _ => unreachable!(), + } + } else if self as u32 <= Self::r15 as u32 { + let root = self.name(); + match modifier.unwrap_or(reg_default_modifier) { + 'l' => write!(out, "{}b", root), + 'x' => write!(out, "{}w", root), + 'e' => write!(out, "{}d", root), + 'r' => out.write_str(root), + _ => unreachable!(), + } + } else if self as u32 <= Self::r15b as u32 { + out.write_str(self.name()) + } else if self as u32 <= Self::xmm15 as u32 { + let prefix = modifier.unwrap_or('x'); + let index = self as u32 - Self::xmm0 as u32; + write!(out, "{}{}", prefix, index) + } else if self as u32 <= Self::ymm15 as u32 { + let prefix = modifier.unwrap_or('y'); + let index = self as u32 - Self::ymm0 as u32; + write!(out, "{}{}", prefix, index) + } else if self as u32 <= Self::zmm31 as u32 { + let prefix = modifier.unwrap_or('z'); + let index = self as u32 - Self::zmm0 as u32; + write!(out, "{}{}", prefix, index) + } else { + out.write_str(self.name()) + } + } + + pub fn overlapping_regs(self, mut cb: impl FnMut(X86InlineAsmReg)) { + macro_rules! reg_conflicts { + ( + $( + $w:ident : $l:ident $h:ident + ),*; + $( + $w2:ident : $l2:ident + ),*; + $( + $x:ident : $y:ident : $z:ident + ),*; + ) => { + match self { + $( + Self::$w => { + cb(Self::$w); + cb(Self::$l); + cb(Self::$h); + } + Self::$l => { + cb(Self::$w); + cb(Self::$l); + } + Self::$h => { + cb(Self::$w); + cb(Self::$h); + } + )* + $( + Self::$w2 | Self::$l2 => { + cb(Self::$w2); + cb(Self::$l2); + } + )* + $( + Self::$x | Self::$y | Self::$z => { + cb(Self::$x); + cb(Self::$y); + cb(Self::$z); + } + )* + r => cb(r), + } + }; + } + + // XMM*, YMM* and ZMM* are all different views of the same register. + // + // See section 15.5 of the combined Intel® 64 and IA-32 Architectures + // Software Developer’s Manual for more details. + // + // We don't need to specify conflicts for [x,y,z]mm[16-31] since these + // registers are only available with AVX-512, so we just specify them + // as aliases directly. + reg_conflicts! { + ax : al ah, + bx : bl bh, + cx : cl ch, + dx : dl dh; + si : sil, + di : dil, + r8 : r8b, + r9 : r9b, + r10 : r10b, + r11 : r11b, + r12 : r12b, + r13 : r13b, + r14 : r14b, + r15 : r15b; + xmm0 : ymm0 : zmm0, + xmm1 : ymm1 : zmm1, + xmm2 : ymm2 : zmm2, + xmm3 : ymm3 : zmm3, + xmm4 : ymm4 : zmm4, + xmm5 : ymm5 : zmm5, + xmm6 : ymm6 : zmm6, + xmm7 : ymm7 : zmm7, + xmm8 : ymm8 : zmm8, + xmm9 : ymm9 : zmm9, + xmm10 : ymm10 : zmm10, + xmm11 : ymm11 : zmm11, + xmm12 : ymm12 : zmm12, + xmm13 : ymm13 : zmm13, + xmm14 : ymm14 : zmm14, + xmm15 : ymm15 : zmm15; + } + } +} diff --git a/src/librustc_target/lib.rs b/src/librustc_target/lib.rs index a0229a5daf0dc..c2cdd2fd3ecf6 100644 --- a/src/librustc_target/lib.rs +++ b/src/librustc_target/lib.rs @@ -25,6 +25,7 @@ extern crate rustc_macros; extern crate log; pub mod abi; +pub mod asm; pub mod spec; /// Requirements for a `StableHashingContext` to be used in this crate. diff --git a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs index c098e44fa065c..bd58e86988a00 100644 --- a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs +++ b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs @@ -1746,6 +1746,9 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { ObligationCauseCode::ConstSized => { err.note("constant expressions must have a statically known size"); } + ObligationCauseCode::InlineAsmSized => { + err.note("all inline asm arguments must have a statically known size"); + } ObligationCauseCode::ConstPatternStructural => { err.note("constants used for pattern-matching must derive `PartialEq` and `Eq`"); } diff --git a/src/librustc_trait_selection/traits/on_unimplemented.rs b/src/librustc_trait_selection/traits/on_unimplemented.rs index 3fbc0b7f08e5f..7e66e08f7e6a6 100644 --- a/src/librustc_trait_selection/traits/on_unimplemented.rs +++ b/src/librustc_trait_selection/traits/on_unimplemented.rs @@ -1,4 +1,4 @@ -use fmt_macros::{Parser, Piece, Position}; +use fmt_macros::{ParseMode, Parser, Piece, Position}; use rustc_ast::ast::{MetaItem, NestedMetaItem}; use rustc_attr as attr; @@ -272,7 +272,7 @@ impl<'tcx> OnUnimplementedFormatString { let name = tcx.item_name(trait_def_id); let generics = tcx.generics_of(trait_def_id); let s = self.0.as_str(); - let parser = Parser::new(&s, None, vec![], false); + let parser = Parser::new(&s, None, None, false, ParseMode::Format); let mut result = Ok(()); for token in parser { match token { @@ -350,7 +350,7 @@ impl<'tcx> OnUnimplementedFormatString { let empty_string = String::new(); let s = self.0.as_str(); - let parser = Parser::new(&s, None, vec![], false); + let parser = Parser::new(&s, None, None, false, ParseMode::Format); let item_context = (options.get(&sym::item_context)).unwrap_or(&empty_string); parser .map(|p| match p { diff --git a/src/librustc_typeck/check/expr.rs b/src/librustc_typeck/check/expr.rs index 71e1b32aeaa90..266e9b21d69a9 100644 --- a/src/librustc_typeck/check/expr.rs +++ b/src/librustc_typeck/check/expr.rs @@ -232,6 +232,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr) } ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr), + ExprKind::InlineAsm(asm) => self.check_expr_asm(asm), ExprKind::LlvmInlineAsm(ref asm) => { for expr in asm.outputs_exprs.iter().chain(asm.inputs_exprs.iter()) { self.check_expr(expr); @@ -1811,6 +1812,72 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } + + fn check_expr_asm_operand(&self, expr: &'tcx hir::Expr<'tcx>, is_input: bool) { + let needs = if is_input { Needs::None } else { Needs::MutPlace }; + let ty = self.check_expr_with_needs(expr, needs); + self.require_type_is_sized(ty, expr.span, traits::InlineAsmSized); + + if !is_input && !expr.is_syntactic_place_expr() { + let mut err = self.tcx.sess.struct_span_err(expr.span, "invalid asm output"); + err.span_label(expr.span, "cannot assign to this expression"); + err.emit(); + } + + // If this is an input value, we require its type to be fully resolved + // at this point. This allows us to provide helpful coercions which help + // pass the type whitelist in a later pass. + // + // We don't require output types to be resolved at this point, which + // allows them to be inferred based on how they are used later in the + // function. + if is_input { + let ty = self.structurally_resolved_type(expr.span, &ty); + match ty.kind { + ty::FnDef(..) => { + let fnptr_ty = self.tcx.mk_fn_ptr(ty.fn_sig(self.tcx)); + self.demand_coerce(expr, ty, fnptr_ty, AllowTwoPhase::No); + } + ty::Ref(_, base_ty, mutbl) => { + let ptr_ty = self.tcx.mk_ptr(ty::TypeAndMut { ty: base_ty, mutbl }); + self.demand_coerce(expr, ty, ptr_ty, AllowTwoPhase::No); + } + _ => {} + } + } + } + + fn check_expr_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>) -> Ty<'tcx> { + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } | hir::InlineAsmOperand::Const { expr } => { + self.check_expr_asm_operand(expr, true); + } + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + self.check_expr_asm_operand(expr, false); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + self.check_expr_asm_operand(expr, false); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + self.check_expr_asm_operand(in_expr, true); + if let Some(out_expr) = out_expr { + self.check_expr_asm_operand(out_expr, false); + } + } + hir::InlineAsmOperand::Sym { expr } => { + self.check_expr(expr); + } + } + } + if asm.options.contains(ast::InlineAsmOptions::NORETURN) { + self.tcx.types.never + } else { + self.tcx.mk_unit() + } + } } pub(super) fn ty_kind_suggestion(ty: Ty<'_>) -> Option<&'static str> { diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 854bd03b26486..4bbb36c8742ae 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -2278,6 +2278,7 @@ fn from_target_feature( Some(sym::hexagon_target_feature) => rust_features.hexagon_target_feature, Some(sym::powerpc_target_feature) => rust_features.powerpc_target_feature, Some(sym::mips_target_feature) => rust_features.mips_target_feature, + Some(sym::riscv_target_feature) => rust_features.riscv_target_feature, Some(sym::avx512_target_feature) => rust_features.avx512_target_feature, Some(sym::mmx_target_feature) => rust_features.mmx_target_feature, Some(sym::sse4a_target_feature) => rust_features.sse4a_target_feature, diff --git a/src/librustc_typeck/expr_use_visitor.rs b/src/librustc_typeck/expr_use_visitor.rs index cdebd63b5942f..9ba00faec4978 100644 --- a/src/librustc_typeck/expr_use_visitor.rs +++ b/src/librustc_typeck/expr_use_visitor.rs @@ -220,6 +220,30 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { self.borrow_expr(&base, bk); } + hir::ExprKind::InlineAsm(ref asm) => { + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::Const { expr, .. } + | hir::InlineAsmOperand::Sym { expr, .. } => self.consume_expr(expr), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + self.mutate_expr(expr); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + self.mutate_expr(expr); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + self.consume_expr(in_expr); + if let Some(out_expr) = out_expr { + self.mutate_expr(out_expr); + } + } + } + } + } + hir::ExprKind::LlvmInlineAsm(ref ia) => { for (o, output) in ia.inner.outputs.iter().zip(ia.outputs_exprs) { if o.is_indirect { diff --git a/src/librustc_typeck/mem_categorization.rs b/src/librustc_typeck/mem_categorization.rs index ffe9f1c7d7a71..71f3e2d03c9ff 100644 --- a/src/librustc_typeck/mem_categorization.rs +++ b/src/librustc_typeck/mem_categorization.rs @@ -405,6 +405,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { | hir::ExprKind::Continue(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) + | hir::ExprKind::InlineAsm(..) | hir::ExprKind::LlvmInlineAsm(..) | hir::ExprKind::Box(..) | hir::ExprKind::Err => Ok(self.cat_rvalue(expr.hir_id, expr.span, expr_ty)), diff --git a/src/llvm-project b/src/llvm-project index 3ba91917e52bd..246dfcd1c864d 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 3ba91917e52bd66ac37161ad4a1bc87d32aa2e18 +Subproject commit 246dfcd1c864d4eab417f74d3599f061d01cb3ae diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp index 28efc8bf5dd54..b988d06871bf6 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -203,6 +203,10 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) { return Attribute::OptimizeNone; case ReturnsTwice: return Attribute::ReturnsTwice; + case ReadNone: + return Attribute::ReadNone; + case InaccessibleMemOnly: + return Attribute::InaccessibleMemOnly; } report_fatal_error("bad AttributeKind"); } diff --git a/src/rustllvm/rustllvm.h b/src/rustllvm/rustllvm.h index c3f0d174d4b41..da48048113bc2 100644 --- a/src/rustllvm/rustllvm.h +++ b/src/rustllvm/rustllvm.h @@ -82,6 +82,8 @@ enum LLVMRustAttribute { NonLazyBind = 23, OptimizeNone = 24, ReturnsTwice = 25, + ReadNone = 26, + InaccessibleMemOnly = 27, }; typedef struct OpaqueRustString *RustStringRef; diff --git a/src/test/assembly/asm/aarch64-modifiers.rs b/src/test/assembly/asm/aarch64-modifiers.rs new file mode 100644 index 0000000000000..c2484e9b6d0a6 --- /dev/null +++ b/src/test/assembly/asm/aarch64-modifiers.rs @@ -0,0 +1,145 @@ +// no-system-llvm +// assembly-output: emit-asm +// compile-flags: -O +// compile-flags: --target aarch64-unknown-linux-gnu + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +impl Copy for i32 {} + +macro_rules! check { + ($func:ident $reg:ident $code:literal) => { + // -O and extern "C" guarantee that the selected register is always r0/s0/d0/q0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> i32 { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!($code, out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: //APP +// CHECK: mov x0, x0 +// CHECK: //NO_APP +check!(reg reg "mov {0}, {0}"); + +// CHECK-LABEL: reg_w: +// CHECK: //APP +// CHECK: mov w0, w0 +// CHECK: //NO_APP +check!(reg_w reg "mov {0:w}, {0:w}"); + +// CHECK-LABEL: reg_x: +// CHECK: //APP +// CHECK: mov x0, x0 +// CHECK: //NO_APP +check!(reg_x reg "mov {0:x}, {0:x}"); + +// CHECK-LABEL: vreg: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg vreg "add {0}.4s, {0}.4s, {0}.4s"); + +// CHECK-LABEL: vreg_b: +// CHECK: //APP +// CHECK: ldr b0, [x0] +// CHECK: //NO_APP +check!(vreg_b vreg "ldr {:b}, [x0]"); + +// CHECK-LABEL: vreg_h: +// CHECK: //APP +// CHECK: ldr h0, [x0] +// CHECK: //NO_APP +check!(vreg_h vreg "ldr {:h}, [x0]"); + +// CHECK-LABEL: vreg_s: +// CHECK: //APP +// CHECK: ldr s0, [x0] +// CHECK: //NO_APP +check!(vreg_s vreg "ldr {:s}, [x0]"); + +// CHECK-LABEL: vreg_d: +// CHECK: //APP +// CHECK: ldr d0, [x0] +// CHECK: //NO_APP +check!(vreg_d vreg "ldr {:d}, [x0]"); + +// CHECK-LABEL: vreg_q: +// CHECK: //APP +// CHECK: ldr q0, [x0] +// CHECK: //NO_APP +check!(vreg_q vreg "ldr {:q}, [x0]"); + +// CHECK-LABEL: vreg_v: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg_v vreg "add {0:v}.4s, {0:v}.4s, {0:v}.4s"); + +// CHECK-LABEL: vreg_low16: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg_low16 vreg_low16 "add {0}.4s, {0}.4s, {0}.4s"); + +// CHECK-LABEL: vreg_low16_b: +// CHECK: //APP +// CHECK: ldr b0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_b vreg_low16 "ldr {:b}, [x0]"); + +// CHECK-LABEL: vreg_low16_h: +// CHECK: //APP +// CHECK: ldr h0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_h vreg_low16 "ldr {:h}, [x0]"); + +// CHECK-LABEL: vreg_low16_s: +// CHECK: //APP +// CHECK: ldr s0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_s vreg_low16 "ldr {:s}, [x0]"); + +// CHECK-LABEL: vreg_low16_d: +// CHECK: //APP +// CHECK: ldr d0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_d vreg_low16 "ldr {:d}, [x0]"); + +// CHECK-LABEL: vreg_low16_q: +// CHECK: //APP +// CHECK: ldr q0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_q vreg_low16 "ldr {:q}, [x0]"); + +// CHECK-LABEL: vreg_low16_v: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg_low16_v vreg_low16 "add {0:v}.4s, {0:v}.4s, {0:v}.4s"); diff --git a/src/test/assembly/asm/aarch64-types.rs b/src/test/assembly/asm/aarch64-types.rs new file mode 100644 index 0000000000000..ce2f0082a06b1 --- /dev/null +++ b/src/test/assembly/asm/aarch64-types.rs @@ -0,0 +1,381 @@ +// no-system-llvm +// assembly-output: emit-asm +// compile-flags: --target aarch64-unknown-linux-gnu + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +#[repr(simd)] +pub struct i8x8(i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x4(i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x2(i32, i32); +#[repr(simd)] +pub struct i64x1(i64); +#[repr(simd)] +pub struct f32x2(f32, f32); +#[repr(simd)] +pub struct f64x1(f64); +#[repr(simd)] +pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x2(i64, i64); +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x2(f64, f64); + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} +impl Copy for i8x8 {} +impl Copy for i16x4 {} +impl Copy for i32x2 {} +impl Copy for i64x1 {} +impl Copy for f32x2 {} +impl Copy for f64x1 {} +impl Copy for i8x16 {} +impl Copy for i16x8 {} +impl Copy for i32x4 {} +impl Copy for i64x2 {} +impl Copy for f32x4 {} +impl Copy for f64x2 {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: //APP +// CHECK: bl extern_func +// CHECK: //NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("bl {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: //APP +// CHECK: adr x0, extern_static +// CHECK: //NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("adr x0, {}", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal $modifier:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!( + concat!($mov, " {:", $modifier, "}, {:", $modifier, "}"), + out($class) y, + in($class) x + ); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i8 i8 reg "mov" ""); + +// CHECK-LABEL: reg_i16: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i16 i16 reg "mov" ""); + +// CHECK-LABEL: reg_i32: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i32 i32 reg "mov" ""); + +// CHECK-LABEL: reg_f32: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_f32 f32 reg "mov" ""); + +// CHECK-LABEL: reg_i64: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i64 i64 reg "mov" ""); + +// CHECK-LABEL: reg_f64: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_f64 f64 reg "mov" ""); + +// CHECK-LABEL: reg_ptr: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_ptr ptr reg "mov" ""); + +// CHECK-LABEL: vreg_i8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i8 i8 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i16 i16 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i32: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i32 i32 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f32: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f32 f32 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i64 i64 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f64 f64 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_ptr: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_ptr ptr vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i8x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i8x8 i8x8 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i16x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i16x4 i16x4 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i32x2 i32x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i64x1 i64x1 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f32x2 f32x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f64x1 f64x1 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i8x16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i8x16 i8x16 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i16x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i16x8 i16x8 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i32x4 i32x4 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i64x2 i64x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f32x4 f32x4 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f64x2 f64x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i8 i8 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i16 i16 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f32: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f32 f32 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i64 i64 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f64 f64 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_ptr: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_ptr ptr vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i8x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i8x8 i8x8 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i16x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i16x4 i16x4 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i32x2 i32x2 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i64x1 i64x1 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f32x2 f32x2 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f64x1 f64x1 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i8x16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i8x16 i8x16 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i16x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i16x8 i16x8 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i32x4 i32x4 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i64x2 i64x2 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f32x4 f32x4 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f64x2 f64x2 vreg_low16 "fmov" "s"); diff --git a/src/test/assembly/asm/arm-modifiers.rs b/src/test/assembly/asm/arm-modifiers.rs new file mode 100644 index 0000000000000..b71503d0a535e --- /dev/null +++ b/src/test/assembly/asm/arm-modifiers.rs @@ -0,0 +1,150 @@ +// no-system-llvm +// assembly-output: emit-asm +// compile-flags: -O +// compile-flags: --target armv7-unknown-linux-gnueabihf +// compile-flags: -C target-feature=+neon + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); + +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for f64 {} +impl Copy for f32x4 {} + +macro_rules! check { + ($func:ident $modifier:literal $reg:ident $ty:ident $mov:literal) => { + // -O and extern "C" guarantee that the selected register is always r0/s0/d0/q0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {0:", $modifier, "}, {0:", $modifier, "}"), out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: @APP +// CHECK: mov r0, r0 +// CHECK: @NO_APP +check!(reg "" reg i32 "mov"); + +// CHECK-LABEL: reg_thumb: +// CHECK: @APP +// CHECK: mov r0, r0 +// CHECK: @NO_APP +check!(reg_thumb "" reg_thumb i32 "mov"); + +// CHECK-LABEL: sreg: +// CHECK: @APP +// CHECK: vmov.f32 s0, s0 +// CHECK: @NO_APP +check!(sreg "" sreg f32 "vmov.f32"); + +// CHECK-LABEL: sreg_low16: +// CHECK: @APP +// CHECK: vmov.f32 s0, s0 +// CHECK: @NO_APP +check!(sreg_low16 "" sreg_low16 f32 "vmov.f32"); + +// CHECK-LABEL: dreg: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(dreg "" dreg f64 "vmov.f64"); + +// CHECK-LABEL: dreg_low16: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(dreg_low16 "" dreg_low16 f64 "vmov.f64"); + +// CHECK-LABEL: dreg_low8: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(dreg_low8 "" dreg_low8 f64 "vmov.f64"); + +// CHECK-LABEL: qreg: +// CHECK: @APP +// CHECK: vorr q0, q0, q0 +// CHECK: @NO_APP +check!(qreg "" qreg f32x4 "vmov"); + +// CHECK-LABEL: qreg_e: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(qreg_e "e" qreg f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_f: +// CHECK: @APP +// CHECK: vmov.f64 d1, d1 +// CHECK: @NO_APP +check!(qreg_f "f" qreg f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low8: +// CHECK: @APP +// CHECK: vorr q0, q0, q0 +// CHECK: @NO_APP +check!(qreg_low8 "" qreg_low8 f32x4 "vmov"); + +// CHECK-LABEL: qreg_low8_e: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(qreg_low8_e "e" qreg_low8 f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low8_f: +// CHECK: @APP +// CHECK: vmov.f64 d1, d1 +// CHECK: @NO_APP +check!(qreg_low8_f "f" qreg_low8 f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low4: +// CHECK: @APP +// CHECK: vorr q0, q0, q0 +// CHECK: @NO_APP +check!(qreg_low4 "" qreg_low4 f32x4 "vmov"); + +// CHECK-LABEL: qreg_low4_e: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(qreg_low4_e "e" qreg_low4 f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low4_f: +// CHECK: @APP +// CHECK: vmov.f64 d1, d1 +// CHECK: @NO_APP +check!(qreg_low4_f "f" qreg_low4 f32x4 "vmov.f64"); diff --git a/src/test/assembly/asm/arm-types.rs b/src/test/assembly/asm/arm-types.rs new file mode 100644 index 0000000000000..1e338f56c4dd7 --- /dev/null +++ b/src/test/assembly/asm/arm-types.rs @@ -0,0 +1,414 @@ +// no-system-llvm +// assembly-output: emit-asm +// compile-flags: --target armv7-unknown-linux-gnueabihf +// compile-flags: -C target-feature=+neon + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +#[repr(simd)] +pub struct i8x8(i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x4(i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x2(i32, i32); +#[repr(simd)] +pub struct i64x1(i64); +#[repr(simd)] +pub struct f32x2(f32, f32); +#[repr(simd)] +pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x2(i64, i64); +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} +impl Copy for i8x8 {} +impl Copy for i16x4 {} +impl Copy for i32x2 {} +impl Copy for i64x1 {} +impl Copy for f32x2 {} +impl Copy for i8x16 {} +impl Copy for i16x8 {} +impl Copy for i32x4 {} +impl Copy for i64x2 {} +impl Copy for f32x4 {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: @APP +// CHECK: bl extern_func +// CHECK: @NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("bl {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: @APP +// CHECK: adr r0, extern_static +// CHECK: @NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("adr r0, {}", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {}, {}"), out($class) y, in($class) x); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_i8 i8 reg "mov"); + +// CHECK-LABEL: reg_i16: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_i16 i16 reg "mov"); + +// CHECK-LABEL: reg_i32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_i32 i32 reg "mov"); + +// CHECK-LABEL: reg_f32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_f32 f32 reg "mov"); + +// CHECK-LABEL: reg_ptr: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_ptr ptr reg "mov"); + +// CHECK-LABEL: reg_thumb_i8: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_i8 i8 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_i16: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_i16 i16 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_i32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_i32 i32 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_f32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_f32 f32 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_ptr: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_ptr ptr reg_thumb "mov"); + +// CHECK-LABEL: sreg_i32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_i32 i32 sreg "vmov.f32"); + +// CHECK-LABEL: sreg_f32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_f32 f32 sreg "vmov.f32"); + +// CHECK-LABEL: sreg_ptr: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_ptr ptr sreg "vmov.f32"); + +// CHECK-LABEL: sreg_low16_i32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_low16_i32 i32 sreg_low16 "vmov.f32"); + +// CHECK-LABEL: sreg_low16_f32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_low16_f32 f32 sreg_low16 "vmov.f32"); + +// CHECK-LABEL: dreg_i64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i64 i64 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_f64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_f64 f64 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i8x8: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i8x8 i8x8 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i16x4: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i16x4 i16x4 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i32x2 i32x2 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i64x1: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i64x1 i64x1 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_f32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_f32x2 f32x2 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i64 i64 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_f64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_f64 f64 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i8x8: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i8x8 i8x8 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i16x4: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i16x4 i16x4 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i32x2 i32x2 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i64x1: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i64x1 i64x1 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_f32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_f32x2 f32x2 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i64 i64 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_f64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_f64 f64 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i8x8: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i8x8 i8x8 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i16x4: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i16x4 i16x4 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i32x2 i32x2 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i64x1: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i64x1 i64x1 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_f32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_f32x2 f32x2 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: qreg_i8x16: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i8x16 i8x16 qreg "vmov"); + +// CHECK-LABEL: qreg_i16x8: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i16x8 i16x8 qreg "vmov"); + +// CHECK-LABEL: qreg_i32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i32x4 i32x4 qreg "vmov"); + +// CHECK-LABEL: qreg_i64x2: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i64x2 i64x2 qreg "vmov"); + +// CHECK-LABEL: qreg_f32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_f32x4 f32x4 qreg "vmov"); + +// CHECK-LABEL: qreg_low8_i8x16: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i8x16 i8x16 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_i16x8: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i16x8 i16x8 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_i32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i32x4 i32x4 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_i64x2: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i64x2 i64x2 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_f32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_f32x4 f32x4 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low4_i8x16: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i8x16 i8x16 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_i16x8: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i16x8 i16x8 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_i32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i32x4 i32x4 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_i64x2: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i64x2 i64x2 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_f32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_f32x4 f32x4 qreg_low4 "vmov"); diff --git a/src/test/assembly/asm/riscv-modifiers.rs b/src/test/assembly/asm/riscv-modifiers.rs new file mode 100644 index 0000000000000..8c816e3220b74 --- /dev/null +++ b/src/test/assembly/asm/riscv-modifiers.rs @@ -0,0 +1,59 @@ +// no-system-llvm +// assembly-output: emit-asm +// compile-flags: -O +// compile-flags: --target riscv64gc-unknown-linux-gnu +// compile-flags: -C target-feature=+f + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +impl Copy for f32 {} + +macro_rules! check { + ($func:ident $modifier:literal $reg:ident $mov:literal) => { + // -O and extern "C" guarantee that the selected register is always r0/s0/d0/q0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> f32 { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {0:", $modifier, "}, {0:", $modifier, "}"), out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: #APP +// CHECK: mv a0, a0 +// CHECK: #NO_APP +check!(reg "" reg "mv"); + +// CHECK-LABEL: freg: +// CHECK: #APP +// CHECK: fmv.s fa0, fa0 +// CHECK: #NO_APP +check!(freg "" freg "fmv.s"); diff --git a/src/test/assembly/asm/riscv-types.rs b/src/test/assembly/asm/riscv-types.rs new file mode 100644 index 0000000000000..449213471cc6f --- /dev/null +++ b/src/test/assembly/asm/riscv-types.rs @@ -0,0 +1,135 @@ +// no-system-llvm +// revisions: riscv64 riscv32 +// assembly-output: emit-asm +//[riscv64] compile-flags: --target riscv64imac-unknown-none-elf +//[riscv32] compile-flags: --target riscv32imac-unknown-none-elf +// compile-flags: -C target-feature=+d + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: #APP +// CHECK: call extern_func +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("call {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: #APP +// CHECK: lb t0, extern_static +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("lb t0, {}", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {}, {}"), out($class) y, in($class) x); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i8 i8 reg "mv"); + +// CHECK-LABEL: reg_i16: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i16 i16 reg "mv"); + +// CHECK-LABEL: reg_i32: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i32 i32 reg "mv"); + +// CHECK-LABEL: reg_f32: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_f32 f32 reg "mv"); + +// riscv64-LABEL: reg_i64: +// riscv64: #APP +// riscv64: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// riscv64: #NO_APP +#[cfg(riscv64)] +check!(reg_i64 i64 reg "mv"); + +// riscv64-LABEL: reg_f64: +// riscv64: #APP +// riscv64: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// riscv64: #NO_APP +#[cfg(riscv64)] +check!(reg_f64 f64 reg "mv"); + +// CHECK-LABEL: reg_ptr: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_ptr ptr reg "mv"); + +// CHECK-LABEL: freg_f32: +// CHECK: #APP +// CHECK: fmv.s f{{[a-z0-9]+}}, f{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(freg_f32 f32 freg "fmv.s"); + +// CHECK-LABEL: freg_f64: +// CHECK: #APP +// CHECK: fmv.d f{{[a-z0-9]+}}, f{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(freg_f64 f64 freg "fmv.d"); diff --git a/src/test/assembly/asm/x86-modifiers.rs b/src/test/assembly/asm/x86-modifiers.rs new file mode 100644 index 0000000000000..e538167cd462a --- /dev/null +++ b/src/test/assembly/asm/x86-modifiers.rs @@ -0,0 +1,204 @@ +// no-system-llvm +// revisions: x86_64 i686 +// assembly-output: emit-asm +// compile-flags: -O +//[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//[i686] compile-flags: --target i686-unknown-linux-gnu +// compile-flags: -C llvm-args=--x86-asm-syntax=intel +// compile-flags: -C target-feature=+avx512bw + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +impl Copy for i32 {} + +macro_rules! check { + ($func:ident $modifier:literal $reg:ident $mov:literal) => { + // -O and extern "C" guarantee that the selected register is always ax/xmm0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> i32 { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {0:", $modifier, "}, {0:", $modifier, "}"), out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: #APP +// x86_64: mov rax, rax +// i686: mov eax, eax +// CHECK: #NO_APP +check!(reg "" reg "mov"); + +// x86_64-LABEL: reg_l: +// x86_64: #APP +// x86_64: mov al, al +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_l "l" reg "mov"); + +// CHECK-LABEL: reg_x: +// CHECK: #APP +// CHECK: mov ax, ax +// CHECK: #NO_APP +check!(reg_x "x" reg "mov"); + +// CHECK-LABEL: reg_e: +// CHECK: #APP +// CHECK: mov eax, eax +// CHECK: #NO_APP +check!(reg_e "e" reg "mov"); + +// x86_64-LABEL: reg_r: +// x86_64: #APP +// x86_64: mov rax, rax +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_r "r" reg "mov"); + +// CHECK-LABEL: reg_abcd: +// CHECK: #APP +// x86_64: mov rax, rax +// i686: mov eax, eax +// CHECK: #NO_APP +check!(reg_abcd "" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_l: +// CHECK: #APP +// CHECK: mov al, al +// CHECK: #NO_APP +check!(reg_abcd_l "l" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_h: +// CHECK: #APP +// CHECK: mov ah, ah +// CHECK: #NO_APP +check!(reg_abcd_h "h" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_x: +// CHECK: #APP +// CHECK: mov ax, ax +// CHECK: #NO_APP +check!(reg_abcd_x "x" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_e: +// CHECK: #APP +// CHECK: mov eax, eax +// CHECK: #NO_APP +check!(reg_abcd_e "e" reg_abcd "mov"); + +// x86_64-LABEL: reg_abcd_r: +// x86_64: #APP +// x86_64: mov rax, rax +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_abcd_r "r" reg_abcd "mov"); + +// CHECK-LABEL: xmm_reg +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(xmm_reg "" xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_x +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(xmm_reg_x "x" xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_y +// CHECK: #APP +// CHECK: vmovaps ymm0, ymm0 +// CHECK: #NO_APP +check!(xmm_reg_y "y" xmm_reg "vmovaps"); + +// CHECK-LABEL: xmm_reg_z +// CHECK: #APP +// CHECK: vmovaps zmm0, zmm0 +// CHECK: #NO_APP +check!(xmm_reg_z "z" xmm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg +// CHECK: #APP +// CHECK: movaps ymm0, ymm0 +// CHECK: #NO_APP +check!(ymm_reg "" ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_x +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(ymm_reg_x "x" ymm_reg "movaps"); + +// CHECK-LABEL: ymm_reg_y +// CHECK: #APP +// CHECK: vmovaps ymm0, ymm0 +// CHECK: #NO_APP +check!(ymm_reg_y "y" ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_z +// CHECK: #APP +// CHECK: vmovaps zmm0, zmm0 +// CHECK: #NO_APP +check!(ymm_reg_z "z" ymm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg +// CHECK: #APP +// CHECK: movaps zmm0, zmm0 +// CHECK: #NO_APP +check!(zmm_reg "" zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_x +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(zmm_reg_x "x" zmm_reg "movaps"); + +// CHECK-LABEL: zmm_reg_y +// CHECK: #APP +// CHECK: vmovaps ymm0, ymm0 +// CHECK: #NO_APP +check!(zmm_reg_y "y" zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_z +// CHECK: #APP +// CHECK: vmovaps zmm0, zmm0 +// CHECK: #NO_APP +check!(zmm_reg_z "z" zmm_reg "vmovaps"); + +// Note: we don't have any way of ensuring that k1 is actually the register +// chosen by the register allocator, so this check may fail if a different +// register is chosen. + +// CHECK-LABEL: kreg: +// CHECK: #APP +// CHECK: kmovb k1, k1 +// CHECK: #NO_APP +check!(kreg "" kreg "kmovb"); diff --git a/src/test/assembly/asm/x86-types.rs b/src/test/assembly/asm/x86-types.rs new file mode 100644 index 0000000000000..de2e67c421f2e --- /dev/null +++ b/src/test/assembly/asm/x86-types.rs @@ -0,0 +1,694 @@ +// no-system-llvm +// revisions: x86_64 i686 +// assembly-output: emit-asm +//[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//[i686] compile-flags: --target i686-unknown-linux-gnu +// compile-flags: -C llvm-args=--x86-asm-syntax=intel +// compile-flags: -C target-feature=+avx512bw + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +#[repr(simd)] +pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x2(i64, i64); +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x2(f64, f64); + +#[repr(simd)] +pub struct i8x32( + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, +); +#[repr(simd)] +pub struct i16x16(i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x8(i32, i32, i32, i32, i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x4(i64, i64, i64, i64); +#[repr(simd)] +pub struct f32x8(f32, f32, f32, f32, f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x4(f64, f64, f64, f64); + +#[repr(simd)] +pub struct i8x64( + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, +); +#[repr(simd)] +pub struct i16x32( + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, +); +#[repr(simd)] +pub struct i32x16(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x8(i64, i64, i64, i64, i64, i64, i64, i64); +#[repr(simd)] +pub struct f32x16(f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x8(f64, f64, f64, f64, f64, f64, f64, f64); + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} +impl Copy for i8x16 {} +impl Copy for i16x8 {} +impl Copy for i32x4 {} +impl Copy for i64x2 {} +impl Copy for f32x4 {} +impl Copy for f64x2 {} +impl Copy for i8x32 {} +impl Copy for i16x16 {} +impl Copy for i32x8 {} +impl Copy for i64x4 {} +impl Copy for f32x8 {} +impl Copy for f64x4 {} +impl Copy for i8x64 {} +impl Copy for i16x32 {} +impl Copy for i32x16 {} +impl Copy for i64x8 {} +impl Copy for f32x16 {} +impl Copy for f64x8 {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: #APP +// CHECK: call extern_func +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("call {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: #APP +// CHECK: mov al, byte ptr [extern_static] +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("mov al, byte ptr [{}]", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {}, {}"), out($class) y, in($class) x); + y + } + }; +} + +// CHECK-LABEL: reg_i16: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i16 i16 reg "mov"); + +// CHECK-LABEL: reg_i32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i32 i32 reg "mov"); + +// CHECK-LABEL: reg_f32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_f32 f32 reg "mov"); + +// x86_64-LABEL: reg_i64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_i64 i64 reg "mov"); + +// x86_64-LABEL: reg_f64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_f64 f64 reg "mov"); + +// CHECK-LABEL: reg_ptr: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_ptr ptr reg "mov"); + +// CHECK-LABEL: reg_abcd_i16: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_i16 i16 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_i32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_i32 i32 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_f32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_f32 f32 reg_abcd "mov"); + +// x86_64-LABEL: reg_abcd_i64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_abcd_i64 i64 reg_abcd "mov"); + +// x86_64-LABEL: reg_abcd_f64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_abcd_f64 f64 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_ptr: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_ptr ptr reg_abcd "mov"); + +// CHECK-LABEL: reg_byte: +// CHECK: #APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_byte i8 reg_byte "mov"); + +// CHECK-LABEL: xmm_reg_i32: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i32 i32 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f32: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f32 f32 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i64: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i64 i64 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f64: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f64 f64 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_ptr: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_ptr ptr xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i8x16: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i8x16 i8x16 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i16x8: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i16x8 i16x8 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i32x4: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i32x4 i32x4 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i64x2: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i64x2 i64x2 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f32x4: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f32x4 f32x4 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f64x2: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f64x2 f64x2 xmm_reg "movaps"); + +// CHECK-LABEL: ymm_reg_i32: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i32 i32 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f32: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f32 f32 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i64: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i64 i64 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f64: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f64 f64 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_ptr: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_ptr ptr ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i8x16: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i8x16 i8x16 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i16x8: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i16x8 i16x8 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i32x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i32x4 i32x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i64x2: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i64x2 i64x2 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f32x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f32x4 f32x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f64x2: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f64x2 f64x2 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i8x32: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i8x32 i8x32 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i16x16: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i16x16 i16x16 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i32x8: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i32x8 i32x8 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i64x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i64x4 i64x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f32x8: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f32x8 f32x8 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f64x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f64x4 f64x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32 i32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32 f32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64 i64 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64 f64 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_ptr: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_ptr ptr zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i8x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i8x16 i8x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i16x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i16x8 i16x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32x4 i32x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64x2: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64x2 i64x2 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32x4 f32x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64x2: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64x2 f64x2 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i8x32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i8x32 i8x32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i16x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i16x16 i16x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32x8 i32x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64x4 i64x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32x8 f32x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64x4 f64x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i8x64: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i8x64 i8x64 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i16x32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i16x32 i16x32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32x16 i32x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64x8 i64x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32x16 f32x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64x8 f64x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: kreg_i8: +// CHECK: #APP +// CHECK: kmovb k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i8 i8 kreg "kmovb"); + +// CHECK-LABEL: kreg_i16: +// CHECK: #APP +// CHECK: kmovw k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i16 i16 kreg "kmovw"); + +// CHECK-LABEL: kreg_i32: +// CHECK: #APP +// CHECK: kmovd k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i32 i32 kreg "kmovd"); + +// CHECK-LABEL: kreg_i64: +// CHECK: #APP +// CHECK: kmovq k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i64 i64 kreg "kmovq"); + +// CHECK-LABEL: kreg_ptr: +// CHECK: #APP +// CHECK: kmovq k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_ptr ptr kreg "kmovq"); diff --git a/src/test/codegen/asm-options.rs b/src/test/codegen/asm-options.rs new file mode 100644 index 0000000000000..21e7eb4379634 --- /dev/null +++ b/src/test/codegen/asm-options.rs @@ -0,0 +1,96 @@ +// compile-flags: -O +// only-x86_64 + +#![crate_type = "rlib"] +#![feature(asm)] + +// CHECK-LABEL: @pure +// CHECK-NOT: asm +// CHECK: ret void +#[no_mangle] +pub unsafe fn pure(x: i32) { + let y: i32; + asm!("", out("ax") y, in("bx") x, options(pure, nomem)); +} + +// CHECK-LABEL: @noreturn +// CHECK: call void asm +// CHECK-NEXT: unreachable +#[no_mangle] +pub unsafe fn noreturn() { + asm!("", options(noreturn)); +} + +pub static mut VAR: i32 = 0; +pub static mut DUMMY_OUTPUT: i32 = 0; + +// CHECK-LABEL: @readonly +// CHECK: call i32 asm +// CHECK: ret i32 1 +#[no_mangle] +pub unsafe fn readonly() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(pure, readonly)); + VAR +} + +// CHECK-LABEL: @not_readonly +// CHECK: call i32 asm +// CHECK: ret i32 % +#[no_mangle] +pub unsafe fn not_readonly() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options()); + VAR +} + +// CHECK-LABEL: @nomem +// CHECK-NOT: store +// CHECK: call i32 asm +// CHECK: store +// CHECK: ret i32 2 +#[no_mangle] +pub unsafe fn nomem() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(pure, nomem)); + VAR = 2; + VAR +} + +// CHECK-LABEL: @nomem_nopure +// CHECK-NOT: store +// CHECK: call i32 asm +// CHECK: store +// CHECK: ret i32 2 +#[no_mangle] +pub unsafe fn nomem_nopure() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(nomem)); + VAR = 2; + VAR +} + +// CHECK-LABEL: @not_nomem +// CHECK: store +// CHECK: call i32 asm +// CHECK: store +// CHECK: ret i32 2 +#[no_mangle] +pub unsafe fn not_nomem() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(pure, readonly)); + VAR = 2; + VAR +} + +// CHECK-LABEL: @dont_remove_nonpure +// CHECK: call void asm +// CHECK: call void asm +// CHECK: call void asm +// CHECK: ret void +#[no_mangle] +pub unsafe fn dont_remove_nonpure() { + asm!("", options()); + asm!("", options(nomem)); + asm!("", options(readonly)); +} diff --git a/src/test/pretty/asm.pp b/src/test/pretty/asm.pp new file mode 100644 index 0000000000000..1723e1cc1cb09 --- /dev/null +++ b/src/test/pretty/asm.pp @@ -0,0 +1,25 @@ +#![feature(prelude_import)] +#![no_std] +#![feature(asm)] +#[prelude_import] +use ::std::prelude::v1::*; +#[macro_use] +extern crate std; + +// pretty-mode:expanded +// pp-exact:asm.pp + +pub fn main() { + let a: i32; + let mut b = 4i32; + unsafe { + asm!(""); + asm!(""); + asm!("", options(nomem, nostack)); + asm!("{0}", in(reg) 4); + asm!("{0}", out(reg) a); + asm!("{0}", inout(reg) b); + asm!("{0} {1}", out(reg) _, inlateout(reg) b => _); + asm!("", out("al") _, lateout("rbx") _); + } +} diff --git a/src/test/pretty/asm.rs b/src/test/pretty/asm.rs new file mode 100644 index 0000000000000..9812f1d97e5ff --- /dev/null +++ b/src/test/pretty/asm.rs @@ -0,0 +1,19 @@ +#![feature(asm)] + +// pretty-mode:expanded +// pp-exact:asm.pp + +pub fn main() { + let a: i32; + let mut b = 4i32; + unsafe { + asm!(""); + asm!("", options()); + asm!("", options(nostack, nomem)); + asm!("{}", in(reg) 4); + asm!("{0}", out(reg) a); + asm!("{name}", name = inout(reg) b); + asm!("{} {}", out(reg) _, inlateout(reg) b => _); + asm!("", out("al") _, lateout("rbx") _); + } +} diff --git a/src/test/pretty/asm-clobbers.rs b/src/test/pretty/llvm-asm-clobbers.rs similarity index 100% rename from src/test/pretty/asm-clobbers.rs rename to src/test/pretty/llvm-asm-clobbers.rs diff --git a/src/test/pretty/asm-options.rs b/src/test/pretty/llvm-asm-options.rs similarity index 100% rename from src/test/pretty/asm-options.rs rename to src/test/pretty/llvm-asm-options.rs diff --git a/src/test/ui/asm/bad-options.rs b/src/test/ui/asm/bad-options.rs new file mode 100644 index 0000000000000..755fc2ca238aa --- /dev/null +++ b/src/test/ui/asm/bad-options.rs @@ -0,0 +1,18 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + unsafe { + asm!("", options(nomem, readonly)); + //~^ ERROR the `nomem` and `readonly` options are mutually exclusive + asm!("", options(pure, nomem, noreturn)); + //~^ ERROR the `pure` and `noreturn` options are mutually exclusive + //~^^ ERROR asm with `pure` option must have at least one output + asm!("{}", in(reg) foo, options(pure, nomem)); + //~^ ERROR asm with `pure` option must have at least one output + asm!("{}", out(reg) foo, options(noreturn)); + //~^ ERROR asm outputs are not allowed with the `noreturn` option + } +} diff --git a/src/test/ui/asm/bad-options.stderr b/src/test/ui/asm/bad-options.stderr new file mode 100644 index 0000000000000..c5e8e2ccf44cc --- /dev/null +++ b/src/test/ui/asm/bad-options.stderr @@ -0,0 +1,32 @@ +error: the `nomem` and `readonly` options are mutually exclusive + --> $DIR/bad-options.rs:8:18 + | +LL | asm!("", options(nomem, readonly)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the `pure` and `noreturn` options are mutually exclusive + --> $DIR/bad-options.rs:10:18 + | +LL | asm!("", options(pure, nomem, noreturn)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: asm with `pure` option must have at least one output + --> $DIR/bad-options.rs:10:18 + | +LL | asm!("", options(pure, nomem, noreturn)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: asm with `pure` option must have at least one output + --> $DIR/bad-options.rs:13:33 + | +LL | asm!("{}", in(reg) foo, options(pure, nomem)); + | ^^^^^^^^^^^^^^^^^^^^ + +error: asm outputs are not allowed with the `noreturn` option + --> $DIR/bad-options.rs:15:20 + | +LL | asm!("{}", out(reg) foo, options(noreturn)); + | ^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/test/ui/asm/bad-reg.rs b/src/test/ui/asm/bad-reg.rs new file mode 100644 index 0000000000000..016ea9329c4d0 --- /dev/null +++ b/src/test/ui/asm/bad-reg.rs @@ -0,0 +1,55 @@ +// only-x86_64 +// compile-flags: -C target-feature=+avx2 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + let mut bar = 0; + unsafe { + // Bad register/register class + + asm!("{}", in(foo) foo); + //~^ ERROR invalid register class `foo`: unknown register class + asm!("", in("foo") foo); + //~^ ERROR invalid register `foo`: unknown register + asm!("{:z}", in(reg) foo); + //~^ ERROR invalid asm template modifier for this register class + asm!("{:r}", in(xmm_reg) foo); + //~^ ERROR invalid asm template modifier for this register class + asm!("{:a}", const 0); + //~^ ERROR asm template modifiers are not allowed for `const` arguments + asm!("{:a}", sym main); + //~^ ERROR asm template modifiers are not allowed for `sym` arguments + asm!("{}", in(zmm_reg) foo); + //~^ ERROR register class `zmm_reg` requires the `avx512f` target feature + asm!("", in("zmm0") foo); + //~^ ERROR register class `zmm_reg` requires the `avx512f` target feature + asm!("", in("ebp") foo); + //~^ ERROR invalid register `ebp`: the frame pointer cannot be used as an operand + asm!("", in("rsp") foo); + //~^ ERROR invalid register `rsp`: the stack pointer cannot be used as an operand + asm!("", in("ip") foo); + //~^ ERROR invalid register `ip`: the instruction pointer cannot be used as an operand + asm!("", in("st(2)") foo); + //~^ ERROR invalid register `st(2)`: x87 registers are not currently supported as operands + asm!("", in("mm0") foo); + //~^ ERROR invalid register `mm0`: MMX registers are not currently supported as operands + asm!("", in("k0") foo); + //~^ ERROR invalid register `k0`: the k0 AVX mask register cannot be used as an operand + + // Explicit register conflicts + // (except in/lateout which don't conflict) + + asm!("", in("eax") foo, in("al") bar); + //~^ ERROR register `al` conflicts with register `ax` + asm!("", in("rax") foo, out("rax") bar); + //~^ ERROR register `ax` conflicts with register `ax` + asm!("", in("al") foo, lateout("al") bar); + asm!("", in("xmm0") foo, in("ymm0") bar); + //~^ ERROR register `ymm0` conflicts with register `xmm0` + asm!("", in("xmm0") foo, out("ymm0") bar); + //~^ ERROR register `ymm0` conflicts with register `xmm0` + asm!("", in("xmm0") foo, lateout("ymm0") bar); + } +} diff --git a/src/test/ui/asm/bad-reg.stderr b/src/test/ui/asm/bad-reg.stderr new file mode 100644 index 0000000000000..c6b7d310dfa6c --- /dev/null +++ b/src/test/ui/asm/bad-reg.stderr @@ -0,0 +1,142 @@ +error: invalid register class `foo`: unknown register class + --> $DIR/bad-reg.rs:12:20 + | +LL | asm!("{}", in(foo) foo); + | ^^^^^^^^^^^ + +error: invalid register `foo`: unknown register + --> $DIR/bad-reg.rs:14:18 + | +LL | asm!("", in("foo") foo); + | ^^^^^^^^^^^^^ + +error: invalid asm template modifier for this register class + --> $DIR/bad-reg.rs:16:15 + | +LL | asm!("{:z}", in(reg) foo); + | ^^^^ ----------- argument + | | + | template modifier + | + = note: the `reg` register class supports the following template modifiers: `l`, `x`, `e`, `r` + +error: invalid asm template modifier for this register class + --> $DIR/bad-reg.rs:18:15 + | +LL | asm!("{:r}", in(xmm_reg) foo); + | ^^^^ --------------- argument + | | + | template modifier + | + = note: the `xmm_reg` register class supports the following template modifiers: `x`, `y`, `z` + +error: asm template modifiers are not allowed for `const` arguments + --> $DIR/bad-reg.rs:20:15 + | +LL | asm!("{:a}", const 0); + | ^^^^ ------- argument + | | + | template modifier + +error: asm template modifiers are not allowed for `sym` arguments + --> $DIR/bad-reg.rs:22:15 + | +LL | asm!("{:a}", sym main); + | ^^^^ -------- argument + | | + | template modifier + +error: register class `zmm_reg` requires the `avx512f` target feature + --> $DIR/bad-reg.rs:24:20 + | +LL | asm!("{}", in(zmm_reg) foo); + | ^^^^^^^^^^^^^^^ + +error: register class `zmm_reg` requires the `avx512f` target feature + --> $DIR/bad-reg.rs:26:18 + | +LL | asm!("", in("zmm0") foo); + | ^^^^^^^^^^^^^^ + +error: invalid register `ebp`: the frame pointer cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:28:18 + | +LL | asm!("", in("ebp") foo); + | ^^^^^^^^^^^^^ + +error: invalid register `rsp`: the stack pointer cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:30:18 + | +LL | asm!("", in("rsp") foo); + | ^^^^^^^^^^^^^ + +error: invalid register `ip`: the instruction pointer cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:32:18 + | +LL | asm!("", in("ip") foo); + | ^^^^^^^^^^^^ + +error: invalid register `st(2)`: x87 registers are not currently supported as operands for inline asm + --> $DIR/bad-reg.rs:34:18 + | +LL | asm!("", in("st(2)") foo); + | ^^^^^^^^^^^^^^^ + +error: invalid register `mm0`: MMX registers are not currently supported as operands for inline asm + --> $DIR/bad-reg.rs:36:18 + | +LL | asm!("", in("mm0") foo); + | ^^^^^^^^^^^^^ + +error: invalid register `k0`: the k0 AVX mask register cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:38:18 + | +LL | asm!("", in("k0") foo); + | ^^^^^^^^^^^^ + +error: register `al` conflicts with register `ax` + --> $DIR/bad-reg.rs:44:33 + | +LL | asm!("", in("eax") foo, in("al") bar); + | ------------- ^^^^^^^^^^^^ register `al` + | | + | register `ax` + +error: register `ax` conflicts with register `ax` + --> $DIR/bad-reg.rs:46:33 + | +LL | asm!("", in("rax") foo, out("rax") bar); + | ------------- ^^^^^^^^^^^^^^ register `ax` + | | + | register `ax` + | +help: use `lateout` instead of `out` to avoid conflict + --> $DIR/bad-reg.rs:46:18 + | +LL | asm!("", in("rax") foo, out("rax") bar); + | ^^^^^^^^^^^^^ + +error: register `ymm0` conflicts with register `xmm0` + --> $DIR/bad-reg.rs:49:34 + | +LL | asm!("", in("xmm0") foo, in("ymm0") bar); + | -------------- ^^^^^^^^^^^^^^ register `ymm0` + | | + | register `xmm0` + +error: register `ymm0` conflicts with register `xmm0` + --> $DIR/bad-reg.rs:51:34 + | +LL | asm!("", in("xmm0") foo, out("ymm0") bar); + | -------------- ^^^^^^^^^^^^^^^ register `ymm0` + | | + | register `xmm0` + | +help: use `lateout` instead of `out` to avoid conflict + --> $DIR/bad-reg.rs:51:18 + | +LL | asm!("", in("xmm0") foo, out("ymm0") bar); + | ^^^^^^^^^^^^^^ + +error: aborting due to 18 previous errors + diff --git a/src/test/ui/asm/bad-template.rs b/src/test/ui/asm/bad-template.rs new file mode 100644 index 0000000000000..0b333eca1ab91 --- /dev/null +++ b/src/test/ui/asm/bad-template.rs @@ -0,0 +1,26 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + unsafe { + asm!("{}"); + //~^ ERROR invalid reference to argument at index 0 + asm!("{1}", in(reg) foo); + //~^ ERROR invalid reference to argument at index 1 + //~^^ ERROR argument never used + asm!("{a}"); + //~^ ERROR there is no argument named `a` + asm!("{}", a = in(reg) foo); + //~^ ERROR invalid reference to argument at index 0 + //~^^ ERROR argument never used + asm!("{1}", a = in(reg) foo); + //~^ ERROR invalid reference to argument at index 1 + //~^^ ERROR named argument never used + asm!("{}", in("eax") foo); + //~^ ERROR invalid reference to argument at index 0 + asm!("{:foo}", in(reg) foo); + //~^ ERROR asm template modifier must be a single character + } +} diff --git a/src/test/ui/asm/bad-template.stderr b/src/test/ui/asm/bad-template.stderr new file mode 100644 index 0000000000000..2de76ef824192 --- /dev/null +++ b/src/test/ui/asm/bad-template.stderr @@ -0,0 +1,86 @@ +error: invalid reference to argument at index 0 + --> $DIR/bad-template.rs:8:15 + | +LL | asm!("{}"); + | ^^ from here + | + = note: no arguments were given + +error: invalid reference to argument at index 1 + --> $DIR/bad-template.rs:10:15 + | +LL | asm!("{1}", in(reg) foo); + | ^^^ from here + | + = note: there is 1 argument + +error: argument never used + --> $DIR/bad-template.rs:10:21 + | +LL | asm!("{1}", in(reg) foo); + | ^^^^^^^^^^^ argument never used + +error: there is no argument named `a` + --> $DIR/bad-template.rs:13:15 + | +LL | asm!("{a}"); + | ^^^ + +error: invalid reference to argument at index 0 + --> $DIR/bad-template.rs:15:15 + | +LL | asm!("{}", a = in(reg) foo); + | ^^ --------------- named argument + | | + | from here + | + = note: no positional arguments were given +note: named arguments cannot be referenced by position + --> $DIR/bad-template.rs:15:20 + | +LL | asm!("{}", a = in(reg) foo); + | ^^^^^^^^^^^^^^^ + +error: named argument never used + --> $DIR/bad-template.rs:15:20 + | +LL | asm!("{}", a = in(reg) foo); + | ^^^^^^^^^^^^^^^ named argument never used + +error: invalid reference to argument at index 1 + --> $DIR/bad-template.rs:18:15 + | +LL | asm!("{1}", a = in(reg) foo); + | ^^^ from here + | + = note: no positional arguments were given + +error: named argument never used + --> $DIR/bad-template.rs:18:21 + | +LL | asm!("{1}", a = in(reg) foo); + | ^^^^^^^^^^^^^^^ named argument never used + +error: invalid reference to argument at index 0 + --> $DIR/bad-template.rs:21:15 + | +LL | asm!("{}", in("eax") foo); + | ^^ ------------- explicit register argument + | | + | from here + | + = note: no positional arguments were given +note: explicit register arguments cannot be used in the asm template + --> $DIR/bad-template.rs:21:20 + | +LL | asm!("{}", in("eax") foo); + | ^^^^^^^^^^^^^ + +error: asm template modifier must be a single character + --> $DIR/bad-template.rs:23:17 + | +LL | asm!("{:foo}", in(reg) foo); + | ^^^ + +error: aborting due to 10 previous errors + diff --git a/src/test/ui/asm/const.rs b/src/test/ui/asm/const.rs new file mode 100644 index 0000000000000..e08da24f44a22 --- /dev/null +++ b/src/test/ui/asm/const.rs @@ -0,0 +1,56 @@ +// no-system-llvm +// only-x86_64 +// run-pass + +#![feature(asm)] + +use std::mem::size_of; + +trait Proj { + const C: usize; +} +impl Proj for i8 { + const C: usize = 8; +} +impl Proj for i16 { + const C: usize = 16; +} + +const fn constfn(x: usize) -> usize { + x +} + +fn generic() { + unsafe { + let a: usize; + asm!("mov {}, {}", out(reg) a, const size_of::()); + assert_eq!(a, size_of::()); + + let b: usize; + asm!("mov {}, {}", out(reg) b, const size_of::() + constfn(5)); + assert_eq!(b, size_of::() + 5); + + let c: usize; + asm!("mov {}, {}", out(reg) c, const T::C); + assert_eq!(c, T::C); + } +} + +fn main() { + unsafe { + let a: usize; + asm!("mov {}, {}", out(reg) a, const 5); + assert_eq!(a, 5); + + let b: usize; + asm!("mov {}, {}", out(reg) b, const constfn(5)); + assert_eq!(b, 5); + + let c: usize; + asm!("mov {}, {}", out(reg) c, const constfn(5) + constfn(5)); + assert_eq!(c, 10); + } + + generic::(); + generic::(); +} diff --git a/src/test/ui/asm/noreturn.rs b/src/test/ui/asm/noreturn.rs new file mode 100644 index 0000000000000..5e1ee93bfb073 --- /dev/null +++ b/src/test/ui/asm/noreturn.rs @@ -0,0 +1,17 @@ +// only-x86_64 +// check-pass + +#![feature(asm, never_type)] +#![crate_type = "rlib"] + +pub unsafe fn asm1() { + let _: () = asm!(""); +} + +pub unsafe fn asm2() { + let _: ! = asm!("", options(noreturn)); +} + +pub unsafe fn asm3() -> ! { + asm!("", options(noreturn)); +} diff --git a/src/test/ui/asm/parse-error.rs b/src/test/ui/asm/parse-error.rs new file mode 100644 index 0000000000000..2b1f018f3642e --- /dev/null +++ b/src/test/ui/asm/parse-error.rs @@ -0,0 +1,56 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + let mut bar = 0; + unsafe { + asm!(); + //~^ ERROR requires at least a template string argument + asm!(foo); + //~^ ERROR asm template must be a string literal + asm!("{}" foo); + //~^ ERROR expected token: `,` + asm!("{}", foo); + //~^ ERROR expected one of + asm!("{}", in foo); + //~^ ERROR expected `(`, found `foo` + asm!("{}", in(reg foo)); + //~^ ERROR expected `)`, found `foo` + asm!("{}", in(reg)); + //~^ ERROR expected expression, found end of macro arguments + asm!("{}", inout(=) foo => bar); + //~^ ERROR expected register class or explicit register + asm!("{}", inout(reg) foo =>); + //~^ ERROR expected expression, found end of macro arguments + asm!("{}", in(reg) foo => bar); + //~^ ERROR expected one of `!`, `,`, `.`, `::`, `?`, `{`, or an operator, found `=>` + asm!("{}", sym foo + bar); + //~^ ERROR argument to `sym` must be a path expression + asm!("", options(foo)); + //~^ ERROR expected one of + asm!("", options(nomem foo)); + //~^ ERROR expected one of + asm!("", options(nomem, foo)); + //~^ ERROR expected one of + asm!("", options(), options()); + //~^ ERROR asm options cannot be specified multiple times + asm!("", options(), options(), options()); + //~^ ERROR asm options cannot be specified multiple times + //~^^ ERROR asm options cannot be specified multiple times + asm!("{}", options(), const foo); + //~^ ERROR arguments are not allowed after options + asm!("{a}", a = const foo, a = const bar); + //~^ ERROR duplicate argument named `a` + //~^^ ERROR argument never used + asm!("", a = in("eax") foo); + //~^ ERROR explicit register arguments cannot have names + asm!("{a}", in("eax") foo, a = const bar); + //~^ ERROR named arguments cannot follow explicit register arguments + asm!("{a}", in("eax") foo, a = const bar); + //~^ 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 + } +} diff --git a/src/test/ui/asm/parse-error.stderr b/src/test/ui/asm/parse-error.stderr new file mode 100644 index 0000000000000..fa422f56bece5 --- /dev/null +++ b/src/test/ui/asm/parse-error.stderr @@ -0,0 +1,162 @@ +error: requires at least a template string argument + --> $DIR/parse-error.rs:9:9 + | +LL | asm!(); + | ^^^^^^^ + +error: asm template must be a string literal + --> $DIR/parse-error.rs:11:14 + | +LL | asm!(foo); + | ^^^ + +error: expected token: `,` + --> $DIR/parse-error.rs:13:19 + | +LL | asm!("{}" foo); + | ^^^ expected `,` + +error: expected one of `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `foo` + --> $DIR/parse-error.rs:15:20 + | +LL | asm!("{}", foo); + | ^^^ expected one of 8 possible tokens + +error: expected `(`, found `foo` + --> $DIR/parse-error.rs:17:23 + | +LL | asm!("{}", in foo); + | ^^^ expected `(` + +error: expected `)`, found `foo` + --> $DIR/parse-error.rs:19:27 + | +LL | asm!("{}", in(reg foo)); + | ^^^ expected `)` + +error: expected expression, found end of macro arguments + --> $DIR/parse-error.rs:21:27 + | +LL | asm!("{}", in(reg)); + | ^ expected expression + +error: expected register class or explicit register + --> $DIR/parse-error.rs:23:26 + | +LL | asm!("{}", inout(=) foo => bar); + | ^ + +error: expected expression, found end of macro arguments + --> $DIR/parse-error.rs:25:37 + | +LL | asm!("{}", inout(reg) foo =>); + | ^ expected expression + +error: expected one of `!`, `,`, `.`, `::`, `?`, `{`, or an operator, found `=>` + --> $DIR/parse-error.rs:27:32 + | +LL | asm!("{}", in(reg) foo => bar); + | ^^ expected one of 7 possible tokens + +error: argument to `sym` must be a path expression + --> $DIR/parse-error.rs:29:24 + | +LL | asm!("{}", sym foo + bar); + | ^^^^^^^^^ + +error: expected one of `)`, `att_syntax`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` + --> $DIR/parse-error.rs:31:26 + | +LL | asm!("", options(foo)); + | ^^^ expected one of 8 possible tokens + +error: expected one of `)` or `,`, found `foo` + --> $DIR/parse-error.rs:33:32 + | +LL | asm!("", options(nomem foo)); + | ^^^ expected one of `)` or `,` + +error: expected one of `)`, `att_syntax`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` + --> $DIR/parse-error.rs:35:33 + | +LL | asm!("", options(nomem, foo)); + | ^^^ expected one of 8 possible tokens + +error: asm options cannot be specified multiple times + --> $DIR/parse-error.rs:37:29 + | +LL | asm!("", options(), options()); + | --------- ^^^^^^^^^ duplicate options + | | + | previously here + +error: asm options cannot be specified multiple times + --> $DIR/parse-error.rs:39:29 + | +LL | asm!("", options(), options(), options()); + | --------- ^^^^^^^^^ duplicate options + | | + | previously here + +error: asm options cannot be specified multiple times + --> $DIR/parse-error.rs:39:40 + | +LL | asm!("", options(), options(), options()); + | --------- ^^^^^^^^^ duplicate options + | | + | previously here + +error: arguments are not allowed after options + --> $DIR/parse-error.rs:42:31 + | +LL | asm!("{}", options(), const foo); + | --------- ^^^^^^^^^ argument + | | + | previous options + +error: duplicate argument named `a` + --> $DIR/parse-error.rs:44:36 + | +LL | asm!("{a}", a = const foo, a = const bar); + | ------------- ^^^^^^^^^^^^^ duplicate argument + | | + | previously here + +error: argument never used + --> $DIR/parse-error.rs:44:36 + | +LL | asm!("{a}", a = const foo, a = const bar); + | ^^^^^^^^^^^^^ argument never used + +error: explicit register arguments cannot have names + --> $DIR/parse-error.rs:47:18 + | +LL | asm!("", a = in("eax") foo); + | ^^^^^^^^^^^^^^^^^ + +error: named arguments cannot follow explicit register arguments + --> $DIR/parse-error.rs:49:36 + | +LL | asm!("{a}", in("eax") foo, a = const bar); + | ------------- ^^^^^^^^^^^^^ named argument + | | + | explicit register argument + +error: named arguments cannot follow explicit register arguments + --> $DIR/parse-error.rs:51:36 + | +LL | asm!("{a}", in("eax") foo, a = const bar); + | ------------- ^^^^^^^^^^^^^ named argument + | | + | explicit register argument + +error: positional arguments cannot follow named arguments or explicit register arguments + --> $DIR/parse-error.rs:53:36 + | +LL | asm!("{1}", in("eax") foo, const bar); + | ------------- ^^^^^^^^^ positional argument + | | + | explicit register argument + +error: aborting due to 24 previous errors + diff --git a/src/test/ui/asm/rustfix-asm.fixed b/src/test/ui/asm/rustfix-asm.fixed new file mode 100644 index 0000000000000..c9271059810c7 --- /dev/null +++ b/src/test/ui/asm/rustfix-asm.fixed @@ -0,0 +1,16 @@ +// run-rustfix +// only-x86_64 + +#![feature(asm, llvm_asm)] + +fn main() { + unsafe { + let x = 1; + let y: i32; + llvm_asm!("" :: "r" (x)); + //~^ ERROR legacy asm! syntax is no longer supported + llvm_asm!("" : "=r" (y)); + //~^ ERROR legacy asm! syntax is no longer supported + let _ = y; + } +} diff --git a/src/test/ui/asm/rustfix-asm.rs b/src/test/ui/asm/rustfix-asm.rs new file mode 100644 index 0000000000000..a108595ca1b66 --- /dev/null +++ b/src/test/ui/asm/rustfix-asm.rs @@ -0,0 +1,16 @@ +// run-rustfix +// only-x86_64 + +#![feature(asm, llvm_asm)] + +fn main() { + unsafe { + let x = 1; + let y: i32; + asm!("" :: "r" (x)); + //~^ ERROR legacy asm! syntax is no longer supported + asm!("" : "=r" (y)); + //~^ ERROR legacy asm! syntax is no longer supported + let _ = y; + } +} diff --git a/src/test/ui/asm/rustfix-asm.stderr b/src/test/ui/asm/rustfix-asm.stderr new file mode 100644 index 0000000000000..28675b51d15fb --- /dev/null +++ b/src/test/ui/asm/rustfix-asm.stderr @@ -0,0 +1,18 @@ +error: legacy asm! syntax is no longer supported + --> $DIR/rustfix-asm.rs:10:9 + | +LL | asm!("" :: "r" (x)); + | ----^^^^^^^^^^^^^^^^ + | | + | help: replace with: `llvm_asm!` + +error: legacy asm! syntax is no longer supported + --> $DIR/rustfix-asm.rs:12:9 + | +LL | asm!("" : "=r" (y)); + | ----^^^^^^^^^^^^^^^^ + | | + | help: replace with: `llvm_asm!` + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/asm/type-check-1.rs b/src/test/ui/asm/type-check-1.rs new file mode 100644 index 0000000000000..7880382c3b74f --- /dev/null +++ b/src/test/ui/asm/type-check-1.rs @@ -0,0 +1,25 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + unsafe { + // Outputs must be place expressions + + asm!("{}", in(reg) 1 + 2); + asm!("{}", out(reg) 1 + 2); + //~^ ERROR invalid asm output + asm!("{}", inout(reg) 1 + 2); + //~^ ERROR invalid asm output + + // Operands must be sized + + let v: [u64; 3] = [0, 1, 2]; + asm!("{}", in(reg) v[..]); + //~^ ERROR the size for values of type `[u64]` cannot be known at compilation time + asm!("{}", out(reg) v[..]); + //~^ ERROR the size for values of type `[u64]` cannot be known at compilation time + asm!("{}", inout(reg) v[..]); + //~^ ERROR the size for values of type `[u64]` cannot be known at compilation time + } +} diff --git a/src/test/ui/asm/type-check-1.stderr b/src/test/ui/asm/type-check-1.stderr new file mode 100644 index 0000000000000..7c9c041f45784 --- /dev/null +++ b/src/test/ui/asm/type-check-1.stderr @@ -0,0 +1,45 @@ +error: invalid asm output + --> $DIR/type-check-1.rs:10:29 + | +LL | asm!("{}", out(reg) 1 + 2); + | ^^^^^ cannot assign to this expression + +error: invalid asm output + --> $DIR/type-check-1.rs:12:31 + | +LL | asm!("{}", inout(reg) 1 + 2); + | ^^^^^ cannot assign to this expression + +error[E0277]: the size for values of type `[u64]` cannot be known at compilation time + --> $DIR/type-check-1.rs:18:28 + | +LL | asm!("{}", in(reg) v[..]); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u64]` + = note: to learn more, visit + = note: all inline asm arguments must have a statically known size + +error[E0277]: the size for values of type `[u64]` cannot be known at compilation time + --> $DIR/type-check-1.rs:20:29 + | +LL | asm!("{}", out(reg) v[..]); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u64]` + = note: to learn more, visit + = note: all inline asm arguments must have a statically known size + +error[E0277]: the size for values of type `[u64]` cannot be known at compilation time + --> $DIR/type-check-1.rs:22:31 + | +LL | asm!("{}", inout(reg) v[..]); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u64]` + = note: to learn more, visit + = note: all inline asm arguments must have a statically known size + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/asm/type-check-2.rs b/src/test/ui/asm/type-check-2.rs new file mode 100644 index 0000000000000..1652e9e4c9f66 --- /dev/null +++ b/src/test/ui/asm/type-check-2.rs @@ -0,0 +1,104 @@ +// only-x86_64 + +#![feature(asm, repr_simd, never_type)] + +#[repr(simd)] +struct SimdNonCopy(f32, f32, f32, f32); + +fn main() { + unsafe { + // Inputs must be initialized + + let x: u64; + asm!("{}", in(reg) x); + //~^ ERROR use of possibly-uninitialized variable: `x` + let mut y: u64; + asm!("{}", inout(reg) y); + //~^ ERROR use of possibly-uninitialized variable: `y` + let _ = y; + + // Outputs require mutable places + + let v: Vec = vec![0, 1, 2]; + asm!("{}", in(reg) v[0]); + asm!("{}", out(reg) v[0]); + //~^ ERROR cannot borrow `v` as mutable, as it is not declared as mutable + asm!("{}", inout(reg) v[0]); + //~^ ERROR cannot borrow `v` as mutable, as it is not declared as mutable + + // Const operands must be integer or floats, and must be constants. + + let x = 0; + const C: i32 = 0; + const fn const_foo(x: i32) -> i32 { + x + } + const fn const_bar(x: T) -> T { + x + } + asm!("{}", const 0i32); + asm!("{}", const 0f32); + asm!("{}", const 0 as *mut u8); + //~^ ERROR asm `const` arguments must be integer or floating-point values + asm!("{}", const &0); + //~^ ERROR asm `const` arguments must be integer or floating-point values + asm!("{}", const x); + //~^ ERROR argument 1 is required to be a constant + asm!("{}", const const_foo(0)); + asm!("{}", const const_foo(x)); + //~^ ERROR argument 1 is required to be a constant + asm!("{}", const const_bar(0)); + asm!("{}", const const_bar(x)); + //~^ ERROR argument 1 is required to be a constant + + // Sym operands must point to a function or static + + static S: i32 = 0; + asm!("{}", sym S); + asm!("{}", sym main); + asm!("{}", sym C); + //~^ ERROR asm `sym` operand must point to a fn or static + asm!("{}", sym x); + //~^ ERROR asm `sym` operand must point to a fn or static + + // Register operands must be Copy + + asm!("{}", in(xmm_reg) SimdNonCopy(0.0, 0.0, 0.0, 0.0)); + //~^ ERROR arguments for inline assembly must be copyable + + // Register operands must be integers, floats, SIMD vectors, pointers or + // function pointers. + + asm!("{}", in(reg) 0i64); + asm!("{}", in(reg) 0f64); + asm!("{}", in(xmm_reg) std::arch::x86_64::_mm_setzero_ps()); + asm!("{}", in(reg) 0 as *const u8); + asm!("{}", in(reg) 0 as *mut u8); + asm!("{}", in(reg) main as fn()); + asm!("{}", in(reg) |x: i32| x); + //~^ ERROR cannot use value of type + asm!("{}", in(reg) vec![0]); + //~^ ERROR cannot use value of type `std::vec::Vec` for inline assembly + asm!("{}", in(reg) (1, 2, 3)); + //~^ ERROR cannot use value of type `(i32, i32, i32)` for inline assembly + asm!("{}", in(reg) [1, 2, 3]); + //~^ ERROR cannot use value of type `[i32; 3]` for inline assembly + + // Register inputs (but not outputs) allow references and function types + + let mut f = main; + let mut r = &mut 0; + asm!("{}", in(reg) f); + asm!("{}", inout(reg) f); + //~^ ERROR cannot use value of type `fn() {main}` for inline assembly + asm!("{}", in(reg) r); + asm!("{}", inout(reg) r); + //~^ ERROR cannot use value of type `&mut i32` for inline assembly + let _ = (f, r); + + // Type checks ignore never type + + let u: ! = unreachable!(); + asm!("{}", in(reg) u); + } +} diff --git a/src/test/ui/asm/type-check-2.stderr b/src/test/ui/asm/type-check-2.stderr new file mode 100644 index 0000000000000..dc7949534f1a9 --- /dev/null +++ b/src/test/ui/asm/type-check-2.stderr @@ -0,0 +1,133 @@ +error: asm `const` arguments must be integer or floating-point values + --> $DIR/type-check-2.rs:41:26 + | +LL | asm!("{}", const 0 as *mut u8); + | ^^^^^^^^^^^^ + +error: asm `const` arguments must be integer or floating-point values + --> $DIR/type-check-2.rs:43:26 + | +LL | asm!("{}", const &0); + | ^^ + +error: arguments for inline assembly must be copyable + --> $DIR/type-check-2.rs:66:32 + | +LL | asm!("{}", in(xmm_reg) SimdNonCopy(0.0, 0.0, 0.0, 0.0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `SimdNonCopy` does not implement the Copy trait + +error: cannot use value of type `[closure@$DIR/type-check-2.rs:78:28: 78:38]` for inline assembly + --> $DIR/type-check-2.rs:78:28 + | +LL | asm!("{}", in(reg) |x: i32| x); + | ^^^^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `std::vec::Vec` for inline assembly + --> $DIR/type-check-2.rs:80:28 + | +LL | asm!("{}", in(reg) vec![0]); + | ^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot use value of type `(i32, i32, i32)` for inline assembly + --> $DIR/type-check-2.rs:82:28 + | +LL | asm!("{}", in(reg) (1, 2, 3)); + | ^^^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `[i32; 3]` for inline assembly + --> $DIR/type-check-2.rs:84:28 + | +LL | asm!("{}", in(reg) [1, 2, 3]); + | ^^^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `fn() {main}` for inline assembly + --> $DIR/type-check-2.rs:92:31 + | +LL | asm!("{}", inout(reg) f); + | ^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `&mut i32` for inline assembly + --> $DIR/type-check-2.rs:95:31 + | +LL | asm!("{}", inout(reg) r); + | ^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: asm `sym` operand must point to a fn or static + --> $DIR/type-check-2.rs:59:24 + | +LL | asm!("{}", sym C); + | ^ + +error: asm `sym` operand must point to a fn or static + --> $DIR/type-check-2.rs:61:24 + | +LL | asm!("{}", sym x); + | ^ + +error: argument 1 is required to be a constant + --> $DIR/type-check-2.rs:45:9 + | +LL | asm!("{}", const x); + | ^^^^^^^^^^^^^^^^^^^^ + +error: argument 1 is required to be a constant + --> $DIR/type-check-2.rs:48:9 + | +LL | asm!("{}", const const_foo(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: argument 1 is required to be a constant + --> $DIR/type-check-2.rs:51:9 + | +LL | asm!("{}", const const_bar(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0381]: use of possibly-uninitialized variable: `x` + --> $DIR/type-check-2.rs:13:28 + | +LL | asm!("{}", in(reg) x); + | ^ use of possibly-uninitialized `x` + +error[E0381]: use of possibly-uninitialized variable: `y` + --> $DIR/type-check-2.rs:16:9 + | +LL | asm!("{}", inout(reg) y); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ use of possibly-uninitialized `y` + +error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable + --> $DIR/type-check-2.rs:24:29 + | +LL | let v: Vec = vec![0, 1, 2]; + | - help: consider changing this to be mutable: `mut v` +LL | asm!("{}", in(reg) v[0]); +LL | asm!("{}", out(reg) v[0]); + | ^ cannot borrow as mutable + +error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable + --> $DIR/type-check-2.rs:26:31 + | +LL | let v: Vec = vec![0, 1, 2]; + | - help: consider changing this to be mutable: `mut v` +... +LL | asm!("{}", inout(reg) v[0]); + | ^ cannot borrow as mutable + +error: aborting due to 18 previous errors + +Some errors have detailed explanations: E0381, E0596. +For more information about an error, try `rustc --explain E0381`. diff --git a/src/test/ui/asm/type-check-3.rs b/src/test/ui/asm/type-check-3.rs new file mode 100644 index 0000000000000..5de15fe49067a --- /dev/null +++ b/src/test/ui/asm/type-check-3.rs @@ -0,0 +1,71 @@ +// only-x86_64 +// compile-flags: -C target-feature=+avx512f + +#![feature(asm)] + +use std::arch::x86_64::{_mm256_setzero_ps, _mm_setzero_ps}; + +fn main() { + unsafe { + // Types must be in the whitelist for the register class + + asm!("{}", in(reg) 0i128); + //~^ ERROR type `i128` cannot be used with this register class + asm!("{}", in(reg) _mm_setzero_ps()); + //~^ ERROR type `std::arch::x86_64::__m128` cannot be used with this register class + asm!("{}", in(reg) _mm256_setzero_ps()); + //~^ ERROR type `std::arch::x86_64::__m256` cannot be used with this register class + asm!("{}", in(xmm_reg) 0u8); + //~^ ERROR type `u8` cannot be used with this register class + asm!("{:e}", in(reg) 0i32); + asm!("{}", in(xmm_reg) 0i32); + asm!("{:e}", in(reg) 0f32); + asm!("{}", in(xmm_reg) 0f32); + asm!("{}", in(xmm_reg) _mm_setzero_ps()); + asm!("{:x}", in(ymm_reg) _mm_setzero_ps()); + asm!("{}", in(kreg) 0u16); + asm!("{}", in(kreg) 0u64); + //~^ ERROR `avx512bw` target feature is not enabled + + // Template modifier suggestions for sub-registers + + asm!("{0} {0}", in(reg) 0i16); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{0} {0:x}", in(reg) 0i16); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{}", in(reg) 0i32); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{}", in(reg) 0i64); + asm!("{}", in(ymm_reg) 0i64); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{}", in(ymm_reg) _mm256_setzero_ps()); + asm!("{:l}", in(reg) 0i16); + asm!("{:l}", in(reg) 0i32); + asm!("{:l}", in(reg) 0i64); + asm!("{:x}", in(ymm_reg) 0i64); + asm!("{:x}", in(ymm_reg) _mm256_setzero_ps()); + + // Suggest different register class for type + + asm!("{}", in(reg) 0i8); + //~^ ERROR type `i8` cannot be used with this register class + asm!("{}", in(reg_byte) 0i8); + + // Split inout operands must have compatible types + + let mut val_i16: i16; + let mut val_f32: f32; + let mut val_u32: u32; + let mut val_u64: u64; + let mut val_ptr: *mut u8; + asm!("{:r}", inout(reg) 0u16 => val_i16); + asm!("{:r}", inout(reg) 0u32 => val_f32); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) 0u32 => val_ptr); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) main => val_u32); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) 0u64 => val_ptr); + asm!("{:r}", inout(reg) main => val_u64); + } +} diff --git a/src/test/ui/asm/type-check-3.stderr b/src/test/ui/asm/type-check-3.stderr new file mode 100644 index 0000000000000..01dbe78db887a --- /dev/null +++ b/src/test/ui/asm/type-check-3.stderr @@ -0,0 +1,118 @@ +error: type `i128` cannot be used with this register class + --> $DIR/type-check-3.rs:12:28 + | +LL | asm!("{}", in(reg) 0i128); + | ^^^^^ + | + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 + +error: type `std::arch::x86_64::__m128` cannot be used with this register class + --> $DIR/type-check-3.rs:14:28 + | +LL | asm!("{}", in(reg) _mm_setzero_ps()); + | ^^^^^^^^^^^^^^^^ + | + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 + +error: type `std::arch::x86_64::__m256` cannot be used with this register class + --> $DIR/type-check-3.rs:16:28 + | +LL | asm!("{}", in(reg) _mm256_setzero_ps()); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 + +error: type `u8` cannot be used with this register class + --> $DIR/type-check-3.rs:18:32 + | +LL | asm!("{}", in(xmm_reg) 0u8); + | ^^^ + | + = note: register class `xmm_reg` supports these types: i32, i64, f32, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 + +error: `avx512bw` target feature is not enabled + --> $DIR/type-check-3.rs:27:29 + | +LL | asm!("{}", in(kreg) 0u64); + | ^^^^ + | + = note: this is required to use type `u64` with register class `kreg` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:32:15 + | +LL | asm!("{0} {0}", in(reg) 0i16); + | ^^^ ^^^ ---- for this argument + | + = note: `#[warn(asm_sub_register)]` on by default + = help: use the `x` modifier to have the register formatted as `ax` + = help: or use the `r` modifier to keep the default formatting of `rax` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:34:15 + | +LL | asm!("{0} {0:x}", in(reg) 0i16); + | ^^^ ---- for this argument + | + = help: use the `x` modifier to have the register formatted as `ax` + = help: or use the `r` modifier to keep the default formatting of `rax` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:36:15 + | +LL | asm!("{}", in(reg) 0i32); + | ^^ ---- for this argument + | + = help: use the `e` modifier to have the register formatted as `eax` + = help: or use the `r` modifier to keep the default formatting of `rax` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:39:15 + | +LL | asm!("{}", in(ymm_reg) 0i64); + | ^^ ---- for this argument + | + = help: use the `x` modifier to have the register formatted as `xmm0` + = help: or use the `y` modifier to keep the default formatting of `ymm0` + +error: type `i8` cannot be used with this register class + --> $DIR/type-check-3.rs:50:28 + | +LL | asm!("{}", in(reg) 0i8); + | ^^^ + | + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 + = help: consider using the `reg_byte` register class instead + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:62:33 + | +LL | asm!("{:r}", inout(reg) 0u32 => val_f32); + | ^^^^ ^^^^^^^ type `f32` + | | + | type `u32` + | + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:64:33 + | +LL | asm!("{:r}", inout(reg) 0u32 => val_ptr); + | ^^^^ ^^^^^^^ type `*mut u8` + | | + | type `u32` + | + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:66:33 + | +LL | asm!("{:r}", inout(reg) main => val_u32); + | ^^^^ ^^^^^^^ type `u32` + | | + | type `fn()` + | + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size + +error: aborting due to 9 previous errors; 4 warnings emitted + diff --git a/src/test/ui/asm/type-check-4.rs b/src/test/ui/asm/type-check-4.rs new file mode 100644 index 0000000000000..2be627c11657b --- /dev/null +++ b/src/test/ui/asm/type-check-4.rs @@ -0,0 +1,23 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + unsafe { + // Can't output to borrowed values. + + let mut a = 0isize; + let p = &a; + asm!("{}", out(reg) a); + //~^ cannot assign to `a` because it is borrowed + println!("{}", p); + + // Can't read from mutable borrowed values. + + let mut a = 0isize; + let p = &mut a; + asm!("{}", in(reg) a); + //~^ cannot use `a` because it was mutably borrowed + println!("{}", p); + } +} diff --git a/src/test/ui/asm/type-check-4.stderr b/src/test/ui/asm/type-check-4.stderr new file mode 100644 index 0000000000000..8035bbefc1aa1 --- /dev/null +++ b/src/test/ui/asm/type-check-4.stderr @@ -0,0 +1,26 @@ +error[E0506]: cannot assign to `a` because it is borrowed + --> $DIR/type-check-4.rs:11:9 + | +LL | let p = &a; + | -- borrow of `a` occurs here +LL | asm!("{}", out(reg) a); + | ^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `a` occurs here +LL | +LL | println!("{}", p); + | - borrow later used here + +error[E0503]: cannot use `a` because it was mutably borrowed + --> $DIR/type-check-4.rs:19:28 + | +LL | let p = &mut a; + | ------ borrow of `a` occurs here +LL | asm!("{}", in(reg) a); + | ^ use of borrowed `a` +LL | +LL | println!("{}", p); + | - borrow later used here + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0503, E0506. +For more information about an error, try `rustc --explain E0503`. diff --git a/src/test/ui/feature-gates/feature-gate-asm.rs b/src/test/ui/feature-gates/feature-gate-asm.rs index 70f5845550d17..4eb72031d51b1 100644 --- a/src/test/ui/feature-gates/feature-gate-asm.rs +++ b/src/test/ui/feature-gates/feature-gate-asm.rs @@ -2,8 +2,9 @@ fn main() { unsafe { - asm!(""); //~ ERROR inline assembly is not stable enough - //~^ WARN use of deprecated item 'asm' - llvm_asm!(""); //~ ERROR inline assembly is not stable enough + asm!(""); + //~^ ERROR inline assembly is not stable enough + llvm_asm!(""); + //~^ ERROR LLVM-style inline assembly will never be stabilized } } diff --git a/src/test/ui/feature-gates/feature-gate-asm.stderr b/src/test/ui/feature-gates/feature-gate-asm.stderr index 9d4d7b53955b5..a71643e0d33a3 100644 --- a/src/test/ui/feature-gates/feature-gate-asm.stderr +++ b/src/test/ui/feature-gates/feature-gate-asm.stderr @@ -4,10 +4,10 @@ error[E0658]: use of unstable library feature 'asm': inline assembly is not stab LL | asm!(""); | ^^^ | - = note: see issue #70173 for more information + = note: see issue #72016 for more information = help: add `#![feature(asm)]` to the crate attributes to enable -error[E0658]: use of unstable library feature 'llvm_asm': inline assembly is not stable enough for use and is subject to change +error[E0658]: use of unstable library feature 'llvm_asm': LLVM-style inline assembly will never be stabilized, prefer using asm! instead --> $DIR/feature-gate-asm.rs:7:9 | LL | llvm_asm!(""); @@ -16,14 +16,6 @@ LL | llvm_asm!(""); = note: see issue #70173 for more information = help: add `#![feature(llvm_asm)]` to the crate attributes to enable -warning: use of deprecated item 'asm': the syntax of asm! will change soon, use llvm_asm! to avoid breakage - --> $DIR/feature-gate-asm.rs:5:9 - | -LL | asm!(""); - | ^^^ help: replace the use of the deprecated item: `llvm_asm` - | - = note: `#[warn(deprecated)]` on by default - -error: aborting due to 2 previous errors; 1 warning emitted +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/feature-gates/feature-gate-asm2.rs b/src/test/ui/feature-gates/feature-gate-asm2.rs index e3e86592a480a..8bd7226aca730 100644 --- a/src/test/ui/feature-gates/feature-gate-asm2.rs +++ b/src/test/ui/feature-gates/feature-gate-asm2.rs @@ -2,8 +2,9 @@ fn main() { unsafe { - println!("{:?}", asm!("")); //~ ERROR inline assembly is not stable - //~^ WARN use of deprecated item 'asm' - println!("{:?}", llvm_asm!("")); //~ ERROR inline assembly is not stable + println!("{:?}", asm!("")); + //~^ ERROR inline assembly is not stable enough + println!("{:?}", llvm_asm!("")); + //~^ ERROR LLVM-style inline assembly will never be stabilized } } diff --git a/src/test/ui/feature-gates/feature-gate-asm2.stderr b/src/test/ui/feature-gates/feature-gate-asm2.stderr index a3c8116d6b166..a8022cb72e0e3 100644 --- a/src/test/ui/feature-gates/feature-gate-asm2.stderr +++ b/src/test/ui/feature-gates/feature-gate-asm2.stderr @@ -4,10 +4,10 @@ error[E0658]: use of unstable library feature 'asm': inline assembly is not stab LL | println!("{:?}", asm!("")); | ^^^ | - = note: see issue #70173 for more information + = note: see issue #72016 for more information = help: add `#![feature(asm)]` to the crate attributes to enable -error[E0658]: use of unstable library feature 'llvm_asm': inline assembly is not stable enough for use and is subject to change +error[E0658]: use of unstable library feature 'llvm_asm': LLVM-style inline assembly will never be stabilized, prefer using asm! instead --> $DIR/feature-gate-asm2.rs:7:26 | LL | println!("{:?}", llvm_asm!("")); @@ -16,14 +16,6 @@ LL | println!("{:?}", llvm_asm!("")); = note: see issue #70173 for more information = help: add `#![feature(llvm_asm)]` to the crate attributes to enable -warning: use of deprecated item 'asm': the syntax of asm! will change soon, use llvm_asm! to avoid breakage - --> $DIR/feature-gate-asm2.rs:5:26 - | -LL | println!("{:?}", asm!("")); - | ^^^ help: replace the use of the deprecated item: `llvm_asm` - | - = note: `#[warn(deprecated)]` on by default - -error: aborting due to 2 previous errors; 1 warning emitted +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/asm-concat-src.rs b/src/test/ui/llvm-asm/llvm-asm-concat-src.rs similarity index 100% rename from src/test/ui/asm-concat-src.rs rename to src/test/ui/llvm-asm/llvm-asm-concat-src.rs diff --git a/src/test/ui/asm-in-moved.rs b/src/test/ui/llvm-asm/llvm-asm-in-moved.rs similarity index 100% rename from src/test/ui/asm-in-moved.rs rename to src/test/ui/llvm-asm/llvm-asm-in-moved.rs diff --git a/src/test/ui/asm-in-out-operand.rs b/src/test/ui/llvm-asm/llvm-asm-in-out-operand.rs similarity index 100% rename from src/test/ui/asm-in-out-operand.rs rename to src/test/ui/llvm-asm/llvm-asm-in-out-operand.rs diff --git a/src/test/ui/asm-indirect-memory.rs b/src/test/ui/llvm-asm/llvm-asm-indirect-memory.rs similarity index 100% rename from src/test/ui/asm-indirect-memory.rs rename to src/test/ui/llvm-asm/llvm-asm-indirect-memory.rs diff --git a/src/test/ui/asm-out-assign.rs b/src/test/ui/llvm-asm/llvm-asm-out-assign.rs similarity index 100% rename from src/test/ui/asm-out-assign.rs rename to src/test/ui/llvm-asm/llvm-asm-out-assign.rs diff --git a/src/test/ui/macros/macro-expanded-include/foo/mod.rs b/src/test/ui/macros/macro-expanded-include/foo/mod.rs index f0eb92b2be820..a8bfa0299f66f 100644 --- a/src/test/ui/macros/macro-expanded-include/foo/mod.rs +++ b/src/test/ui/macros/macro-expanded-include/foo/mod.rs @@ -5,5 +5,5 @@ macro_rules! m { } macro_rules! n { - () => { unsafe { llvm_asm!(include_str!("file.txt")); } } + () => { unsafe { asm!(include_str!("file.txt")); } } } diff --git a/src/test/ui/macros/macro-expanded-include/test.rs b/src/test/ui/macros/macro-expanded-include/test.rs index abf83a5c6ffd3..f1a71059a8901 100644 --- a/src/test/ui/macros/macro-expanded-include/test.rs +++ b/src/test/ui/macros/macro-expanded-include/test.rs @@ -1,6 +1,6 @@ // ignore-emscripten no llvm_asm! support // build-pass (FIXME(62277): could be check-pass?) -#![feature(llvm_asm)] +#![feature(asm)] #![allow(unused)] #[macro_use] diff --git a/src/test/ui/macros/macros-nonfatal-errors.rs b/src/test/ui/macros/macros-nonfatal-errors.rs index cc96a5bff522f..0a496c9dc3d33 100644 --- a/src/test/ui/macros/macros-nonfatal-errors.rs +++ b/src/test/ui/macros/macros-nonfatal-errors.rs @@ -3,13 +3,14 @@ // test that errors in a (selection) of macros don't kill compilation // immediately, so that we get more errors listed at a time. -#![feature(llvm_asm)] +#![feature(asm, llvm_asm)] #![feature(trace_macros, concat_idents)] #[derive(Default)] //~ ERROR enum OrDeriveThis {} fn main() { + asm!(invalid); //~ ERROR llvm_asm!(invalid); //~ ERROR concat_idents!("not", "idents"); //~ ERROR diff --git a/src/test/ui/macros/macros-nonfatal-errors.stderr b/src/test/ui/macros/macros-nonfatal-errors.stderr index f416c70123c66..6ef757a55b8fb 100644 --- a/src/test/ui/macros/macros-nonfatal-errors.stderr +++ b/src/test/ui/macros/macros-nonfatal-errors.stderr @@ -6,44 +6,50 @@ LL | #[derive(Default)] | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) +error: asm template must be a string literal + --> $DIR/macros-nonfatal-errors.rs:13:10 + | +LL | asm!(invalid); + | ^^^^^^^ + error: inline assembly must be a string literal - --> $DIR/macros-nonfatal-errors.rs:13:15 + --> $DIR/macros-nonfatal-errors.rs:14:15 | LL | llvm_asm!(invalid); | ^^^^^^^ error: concat_idents! requires ident args. - --> $DIR/macros-nonfatal-errors.rs:15:5 + --> $DIR/macros-nonfatal-errors.rs:16:5 | LL | concat_idents!("not", "idents"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:17:17 + --> $DIR/macros-nonfatal-errors.rs:18:17 | LL | option_env!(invalid); | ^^^^^^^ error: expected string literal - --> $DIR/macros-nonfatal-errors.rs:18:10 + --> $DIR/macros-nonfatal-errors.rs:19:10 | LL | env!(invalid); | ^^^^^^^ error: expected string literal - --> $DIR/macros-nonfatal-errors.rs:19:10 + --> $DIR/macros-nonfatal-errors.rs:20:10 | LL | env!(foo, abr, baz); | ^^^ error: environment variable `RUST_HOPEFULLY_THIS_DOESNT_EXIST` not defined - --> $DIR/macros-nonfatal-errors.rs:20:5 + --> $DIR/macros-nonfatal-errors.rs:21:5 | LL | env!("RUST_HOPEFULLY_THIS_DOESNT_EXIST"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: format argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:22:13 + --> $DIR/macros-nonfatal-errors.rs:23:13 | LL | format!(invalid); | ^^^^^^^ @@ -54,19 +60,19 @@ LL | format!("{}", invalid); | ^^^^^ error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:24:14 + --> $DIR/macros-nonfatal-errors.rs:25:14 | LL | include!(invalid); | ^^^^^^^ error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:26:18 + --> $DIR/macros-nonfatal-errors.rs:27:18 | LL | include_str!(invalid); | ^^^^^^^ error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2) - --> $DIR/macros-nonfatal-errors.rs:27:5 + --> $DIR/macros-nonfatal-errors.rs:28:5 | LL | include_str!("i'd be quite surprised if a file with this name existed"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -74,13 +80,13 @@ LL | include_str!("i'd be quite surprised if a file with this name existed") = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:28:20 + --> $DIR/macros-nonfatal-errors.rs:29:20 | LL | include_bytes!(invalid); | ^^^^^^^ error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2) - --> $DIR/macros-nonfatal-errors.rs:29:5 + --> $DIR/macros-nonfatal-errors.rs:30:5 | LL | include_bytes!("i'd be quite surprised if a file with this name existed"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,11 +94,11 @@ LL | include_bytes!("i'd be quite surprised if a file with this name existed = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error: trace_macros! accepts only `true` or `false` - --> $DIR/macros-nonfatal-errors.rs:31:5 + --> $DIR/macros-nonfatal-errors.rs:32:5 | LL | trace_macros!(invalid); | ^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors For more information about this error, try `rustc --explain E0665`. diff --git a/src/test/ui/target-feature/gate.rs b/src/test/ui/target-feature/gate.rs index 2d51cab675e00..f738c16673dca 100644 --- a/src/test/ui/target-feature/gate.rs +++ b/src/test/ui/target-feature/gate.rs @@ -25,6 +25,7 @@ // gate-test-movbe_target_feature // gate-test-rtm_target_feature // gate-test-f16c_target_feature +// gate-test-riscv_target_feature #[target_feature(enable = "avx512bw")] //~^ ERROR: currently unstable diff --git a/src/test/ui/target-feature/gate.stderr b/src/test/ui/target-feature/gate.stderr index 848538a4e92fd..2384a00aa47aa 100644 --- a/src/test/ui/target-feature/gate.stderr +++ b/src/test/ui/target-feature/gate.stderr @@ -1,5 +1,5 @@ error[E0658]: the target feature `avx512bw` is currently unstable - --> $DIR/gate.rs:29:18 + --> $DIR/gate.rs:30:18 | LL | #[target_feature(enable = "avx512bw")] | ^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/clippy_lints/src/loops.rs b/src/tools/clippy/clippy_lints/src/loops.rs index 84e8a010738cf..38a5829b3f745 100644 --- a/src/tools/clippy/clippy_lints/src/loops.rs +++ b/src/tools/clippy/clippy_lints/src/loops.rs @@ -16,8 +16,8 @@ use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::{ - def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, LoopSource, - MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, + def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, + LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, }; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -693,6 +693,20 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { NeverLoopResult::AlwaysBreak } }, + ExprKind::InlineAsm(ref asm) => asm + .operands + .iter() + .map(|o| match o { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr } + | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), + InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) + }, + }) + .fold(NeverLoopResult::Otherwise, combine_both), ExprKind::Struct(_, _, None) | ExprKind::Yield(_, _) | ExprKind::Closure(_, _, _, _, _) diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 74601008dca41..bbcf396eef7d6 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -469,6 +469,10 @@ impl<'tcx> Visitor<'tcx> for PrintVisitor { println!("Ret(None) = {};", current); } }, + ExprKind::InlineAsm(_) => { + println!("InlineAsm(_) = {};", current); + println!(" // unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); + }, ExprKind::LlvmInlineAsm(_) => { println!("LlvmInlineAsm(_) = {};", current); println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment"); diff --git a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs index bd7da57c665dc..92c27e79452ab 100644 --- a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs +++ b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs @@ -1,10 +1,11 @@ use crate::consts::{constant_context, constant_simple}; use crate::utils::differing_macro_contexts; +use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_hir::{ BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FnRetTy, GenericArg, - GenericArgs, Guard, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, Ty, - TyKind, TypeBinding, + GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath, + Stmt, StmtKind, Ty, TyKind, TypeBinding, }; use rustc_lint::LateContext; use rustc_middle::ich::StableHashingContextProvider; @@ -474,6 +475,56 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(a); self.hash_expr(i); }, + ExprKind::InlineAsm(ref asm) => { + for piece in asm.template { + match piece { + InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s), + InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier, + span: _, + } => { + operand_idx.hash(&mut self.s); + modifier.hash(&mut self.s); + }, + } + } + asm.options.hash(&mut self.s); + for op in asm.operands { + match op { + InlineAsmOperand::In { reg, expr } => { + reg.hash(&mut self.s); + self.hash_expr(expr); + }, + InlineAsmOperand::Out { reg, late, expr } => { + reg.hash(&mut self.s); + late.hash(&mut self.s); + if let Some(expr) = expr { + self.hash_expr(expr); + } + }, + InlineAsmOperand::InOut { reg, late, expr } => { + reg.hash(&mut self.s); + late.hash(&mut self.s); + self.hash_expr(expr); + }, + InlineAsmOperand::SplitInOut { + reg, + late, + in_expr, + out_expr, + } => { + reg.hash(&mut self.s); + late.hash(&mut self.s); + self.hash_expr(in_expr); + if let Some(out_expr) = out_expr { + self.hash_expr(out_expr); + } + }, + InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr), + } + } + }, ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {}, ExprKind::Lit(ref l) => { l.node.hash(&mut self.s); diff --git a/src/tools/clippy/clippy_lints/src/utils/inspector.rs b/src/tools/clippy/clippy_lints/src/utils/inspector.rs index 7e8c61ba24a22..748c11fac64ff 100644 --- a/src/tools/clippy/clippy_lints/src/utils/inspector.rs +++ b/src/tools/clippy/clippy_lints/src/utils/inspector.rs @@ -1,7 +1,7 @@ //! checks for attributes use crate::utils::get_attr; -use rustc_ast::ast::Attribute; +use rustc_ast::ast::{Attribute, InlineAsmTemplatePiece}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::Session; @@ -282,6 +282,31 @@ fn print_expr(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, indent: usize) { print_expr(cx, e, indent + 1); } }, + hir::ExprKind::InlineAsm(ref asm) => { + println!("{}InlineAsm", ind); + println!("{}template: {}", ind, InlineAsmTemplatePiece::to_string(asm.template)); + println!("{}options: {:?}", ind, asm.options); + println!("{}operands:", ind); + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + print_expr(cx, expr, indent + 1); + } + }, + hir::InlineAsmOperand::InOut { expr, .. } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + print_expr(cx, in_expr, indent + 1); + if let Some(out_expr) = out_expr { + print_expr(cx, out_expr, indent + 1); + } + }, + hir::InlineAsmOperand::Const { expr } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::Sym { expr } => print_expr(cx, expr, indent + 1), + } + } + }, hir::ExprKind::LlvmInlineAsm(ref asm) => { let inputs = &asm.inputs_exprs; let outputs = &asm.outputs_exprs; diff --git a/src/tools/clippy/clippy_lints/src/utils/sugg.rs b/src/tools/clippy/clippy_lints/src/utils/sugg.rs index a8fe637d3d978..4ebe2e2852fb4 100644 --- a/src/tools/clippy/clippy_lints/src/utils/sugg.rs +++ b/src/tools/clippy/clippy_lints/src/utils/sugg.rs @@ -108,6 +108,7 @@ impl<'a> Sugg<'a> { | hir::ExprKind::Call(..) | hir::ExprKind::Field(..) | hir::ExprKind::Index(..) + | hir::ExprKind::InlineAsm(..) | hir::ExprKind::LlvmInlineAsm(..) | hir::ExprKind::Lit(..) | hir::ExprKind::Loop(..) @@ -150,6 +151,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Field(..) | ast::ExprKind::ForLoop(..) | ast::ExprKind::Index(..) + | ast::ExprKind::InlineAsm(..) | ast::ExprKind::LlvmInlineAsm(..) | ast::ExprKind::Lit(..) | ast::ExprKind::Loop(..) diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index 26bf463bd2922..dfa6223f1b9dd 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -353,7 +353,8 @@ impl Write { is_write: bool, ) -> (Option, Option) { use fmt_macros::{ - AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, Parser, Piece, + AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser, + Piece, }; let tts = tts.clone(); @@ -376,7 +377,7 @@ impl Write { }; let tmp = fmtstr.symbol.as_str(); let mut args = vec![]; - let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false); + let mut fmt_parser = Parser::new(&tmp, None, None, false, ParseMode::Format); while let Some(piece) = fmt_parser.next() { if !fmt_parser.errors.is_empty() { return (None, expr);