From 914f7968ce5a0a7309ef067079783e0f4e269487 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 4 Dec 2024 19:41:22 +0000 Subject: [PATCH 1/7] Add reference for asm-goto The asm-goto-with-outputs is still unstable, so in the reference it's still mentioned as forbidden by a check rule. --- src/inline-assembly.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index 39a129393..bd4219f38 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -52,7 +52,7 @@ format_string := STRING_LITERAL / RAW_STRING_LITERAL dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout" reg_spec := / "\"" "\"" operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" -reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr / sym / const +reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr / sym / const / label clobber_abi := "clobber_abi(" *("," ) [","] ")" option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw" options := "options(" option *("," option) [","] ")" @@ -354,6 +354,23 @@ assert_eq!(y, [3, 2, 0, 1]); # } ``` +r[asm.operand-type.supported-operands.label] +* `label ` + - The address of the block is substituted into the asm template string. The assembly block may jump to the substituted addresses. + - After execution of the block, the `asm!` expression returns. + - The type of the block must be unit or `!` (never). + - The block starts new safety context; despite the outer `unsafe` needed for `asm!`, you need an extra `unsafe` to perform unsafe operations inside the block. + +```rust +# #[cfg(target_arch = "x86_64")] +unsafe { + core::arch::asm!("jmp {}", label { + println!("Hello from inline assembly label"); + }); +} +``` + + r[asm.operand-type.left-to-right] 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. @@ -1080,6 +1097,8 @@ r[asm.options.supported-options.noreturn] - `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. +- When labels are present, `noreturn` means the execution of the `asm!` block never falls through; the asm block may only exit by jumping to one of the specified blocks. + The entire `asm!` block will have unit type in this case, unless all label blocks diverge, in which case the return type is `!`. ```rust,no_run @@ -1164,7 +1183,10 @@ unsafe { core::arch::asm!("", options(pure)); } ``` r[asm.options.checks.noreturn] -- It is a compile-time error to specify `noreturn` on an asm block with outputs. +- It is a compile-time error to specify `noreturn` on an asm block with outputs and without labels. + +r[asm.options.checks.label-with-outputs] +- It is a compile-time error to specify label on an asm block with outputs. ```rust,compile_fail # #[cfg(target_arch = "x86_64")] { From 83f880647bbf14b4d1d034b90d0873c7828dcd3f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 29 Dec 2024 02:05:01 +0000 Subject: [PATCH 2/7] Make some editorial adjustments --- src/inline-assembly.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index bd4219f38..9d0755477 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -356,10 +356,10 @@ assert_eq!(y, [3, 2, 0, 1]); r[asm.operand-type.supported-operands.label] * `label ` - - The address of the block is substituted into the asm template string. The assembly block may jump to the substituted addresses. + - The address of the block is substituted into the asm template string. The assembly block may jump to the substituted address. - After execution of the block, the `asm!` expression returns. - The type of the block must be unit or `!` (never). - - The block starts new safety context; despite the outer `unsafe` needed for `asm!`, you need an extra `unsafe` to perform unsafe operations inside the block. + - The block starts a new safety context; despite the outer `unsafe` block needed for `asm!`, unsafe operations within the `label` block must be wrapped in an inner `unsafe` block. ```rust # #[cfg(target_arch = "x86_64")] @@ -1097,8 +1097,8 @@ r[asm.options.supported-options.noreturn] - `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. -- When labels are present, `noreturn` means the execution of the `asm!` block never falls through; the asm block may only exit by jumping to one of the specified blocks. - The entire `asm!` block will have unit type in this case, unless all label blocks diverge, in which case the return type is `!`. +- When any `label` blocks are present, `noreturn` means the execution of the `asm!` block never falls through; the asm block may only exit by jumping to one of the specified blocks. + The entire `asm!` block will have unit type in this case, unless all `label` blocks diverge, in which case the return type is `!`. ```rust,no_run @@ -1186,7 +1186,7 @@ r[asm.options.checks.noreturn] - It is a compile-time error to specify `noreturn` on an asm block with outputs and without labels. r[asm.options.checks.label-with-outputs] -- It is a compile-time error to specify label on an asm block with outputs. +- It is a compile-time error to have any `label` blocks in an asm block with outputs. ```rust,compile_fail # #[cfg(target_arch = "x86_64")] { From 4314463f0095b69187e8811ac413268d3577d47b Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 10 Feb 2025 13:03:16 +0000 Subject: [PATCH 3/7] Differentiate "assembly code" and "asm block". The latter now only means the entire block, while the former means the assembly code specified within the block (thus excluding `label` blocks). --- src/inline-assembly.md | 66 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index 9d0755477..e301f28d4 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -356,7 +356,7 @@ assert_eq!(y, [3, 2, 0, 1]); r[asm.operand-type.supported-operands.label] * `label ` - - The address of the block is substituted into the asm template string. The assembly block may jump to the substituted address. + - The address of the block is substituted into the asm template string. The assembly code may jump to the substituted address. - After execution of the block, the `asm!` expression returns. - The type of the block must be unit or `!` (never). - The block starts a new safety context; despite the outer `unsafe` block needed for `asm!`, unsafe operations within the `label` block must be wrapped in an inner `unsafe` block. @@ -749,7 +749,7 @@ Some registers cannot be used for input or output operands: | Architecture | Unsupported register | Reason | | ------------ | -------------------- | ------ | -| All | `sp`, `r15` (s390x) | The stack pointer must be restored to its original value at the end of an asm code block. | +| All | `sp`, `r15` (s390x) | The stack pointer must be restored to its original value at the end of an assembly code. | | All | `bp` (x86), `x29` (AArch64 and Arm64EC), `x8` (RISC-V), `$fp` (LoongArch), `r11` (s390x) | The frame pointer cannot be used as an input or output. | | ARM | `r7` or `r11` | On ARM the frame pointer can be either `r7` or `r11` depending on the target. The frame pointer cannot be used as an input or output. | | All | `si` (x86-32), `bx` (x86-64), `r6` (ARM), `x19` (AArch64 and Arm64EC), `x9` (RISC-V), `$s8` (LoongArch) | This is used internally by LLVM as a "base pointer" for functions with complex stack frames. | @@ -873,7 +873,7 @@ r[asm.abi-clobbers] ## ABI clobbers r[asm.abi-clobbers.intro] -The `clobber_abi` keyword can be used to apply a default set of clobbers to an `asm!` block. +The `clobber_abi` keyword can be used to apply a default set of clobbers to the assembly code. This will automatically insert the necessary clobber constraints as needed for calling a function with a particular calling convention: if the calling convention does not fully preserve the value of a register across a call then `lateout("...") _` is implicitly added to the operands list (where the `...` is replaced by the register's name). ```rust @@ -966,12 +966,12 @@ r[asm.options] ## Options r[asm.options.supported-options] -Flags are used to further influence the behavior of the inline assembly block. +Flags are used to further influence the behavior of the inline assembly code. Currently the following options are defined: r[asm.options.supported-options.pure] -- `pure`: The `asm!` block has no side effects, must eventually return, 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. +- `pure`: The assembly code has no side effects, must eventually return, 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 assembly code 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. The `pure` option must be combined with either the `nomem` or `readonly` options, otherwise a compile-time error is emitted. ```rust @@ -998,9 +998,9 @@ assert_eq!(z, 0); ``` r[asm.options.supported-options.nomem] -- `nomem`: The `asm!` block does not read from or write to any memory accessible outside of the `asm!` block. - 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!`. - The compiler also assumes that this `asm!` block does not perform any kind of synchronization with other threads, e.g. via fences. +- `nomem`: The assembly code does not read from or write to any memory accessible outside of the assembly code. + This allows the compiler to cache the values of modified global variables in registers across the assembly code since it knows that they are not read or written to by the `asm!`. + The compiler also assumes that the assembly code does not perform any kind of synchronization with other threads, e.g. via fences. ```rust,no_run @@ -1044,9 +1044,9 @@ assert_eq!(z, 1); ``` r[asm.options.supported-options.readonly] -- `readonly`: The `asm!` block does not write to any memory accessible outside of the `asm!` block. - 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!`. - The compiler also assumes that this `asm!` block does not perform any kind of synchronization with other threads, e.g. via fences. +- `readonly`: The assembly code does not write to any memory accessible outside of the assembly code. + This allows the compiler to cache the values of unmodified global variables in registers across the assembly code since it knows that they are not written to by the `asm!`. + The compiler also assumes that this assembly code does not perform any kind of synchronization with other threads, e.g. via fences. ```rust,no_run @@ -1090,14 +1090,14 @@ assert_eq!(z, 1); ``` r[asm.options.supported-options.preserves_flags] -- `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. +- `preserves_flags`: The assembly code does not modify the flags register (defined in the rules below). + This allows the compiler to avoid recomputing the condition flags after the assembly code. r[asm.options.supported-options.noreturn] -- `noreturn`: The `asm!` block never returns, and its return type is defined as `!` (never). +- `noreturn`: The assembly code 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. -- When any `label` blocks are present, `noreturn` means the execution of the `asm!` block never falls through; the asm block may only exit by jumping to one of the specified blocks. +- When any `label` blocks are present, `noreturn` means the execution of the assembly code never falls through; the assembly code may only exit by jumping to one of the specified blocks. The entire `asm!` block will have unit type in this case, unless all `label` blocks diverge, in which case the return type is `!`. @@ -1120,7 +1120,7 @@ unsafe { core::arch::asm!("", options(noreturn)); } ``` r[asm.options.supported-options.nostack] -- `nostack`: The `asm!` block does not push data to the stack, or write to the stack red-zone (if supported by the target). +- `nostack`: The assembly code 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. @@ -1217,19 +1217,19 @@ r[asm.rules.intro] To avoid undefined behavior, these rules must be followed when using function-scope inline assembly (`asm!`): r[asm.rules.reg-not-input] -- Any registers not specified as inputs will contain an undefined value on entry to the asm block. +- Any registers not specified as inputs will contain an undefined value on entry to the assembly code. - 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). r[asm.rules.reg-not-output] -- 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. +- Any registers not specified as outputs must have the same value upon exiting the assembly code 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. r[asm.rules.unwind] -- Behavior is undefined if execution unwinds out of an asm block. +- Behavior is undefined if execution unwinds out of the assembly code. - This also applies if the assembly code calls a function which then unwinds. r[asm.rules.mem-same-as-ffi] @@ -1237,7 +1237,7 @@ r[asm.rules.mem-same-as-ffi] - 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. + - These rules do not apply to memory which is private to the asm code, such as stack space allocated within the assembly code. r[asm.rules.black-box] - The compiler cannot assume that the instructions in the asm are the ones that will actually end up executed. @@ -1247,22 +1247,22 @@ r[asm.rules.black-box] r[asm.rules.stack-below-sp] - 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. + - On entry to the assembly code 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. + - The stack pointer must be restored to its original value before leaving the assembly code. r[asm.rules.noreturn] -- If the `noreturn` option is set then behavior is undefined if execution falls through to the end of the asm block. +- If the `noreturn` option is set then behavior is undefined if execution falls through to the end of the assembly code. r[asm.rules.pure] - 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. + - When used with the `readonly` option, "inputs" comprise the direct inputs of the `asm!` and any memory that the assembly code is allowed to read. r[asm.rules.preserved-registers] -- These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set: +- These flags registers must be restored upon exiting the assembly code if the `preserves_flags` option is set: - x86 - Status flags in `EFLAGS` (CF, PF, AF, ZF, SF, OF). - Floating-point status word (all). @@ -1286,12 +1286,12 @@ r[asm.rules.preserved-registers] - The condition code register `cc`. r[asm.rules.x86-df] -- 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. +- On x86, the direction flag (DF in `EFLAGS`) is clear on entry to the assembly code and must be clear on exit. + - Behavior is undefined if the direction flag is set on exiting the assembly code. r[asm.rules.x86-x87] - On x86, the x87 floating-point register stack must remain unchanged unless all of the `st([0-7])` registers have been marked as clobbered with `out("st(0)") _, out("st(1)") _, ...`. - - If all x87 registers are clobbered then the x87 register stack is guaranteed to be empty upon entering an `asm` block. Assembly code must ensure that the x87 register stack is also empty when exiting the asm block. + - If all x87 registers are clobbered then the x87 register stack is guaranteed to be empty upon entering the assembly code. Assembly code must ensure that the x87 register stack is also empty when exiting the asssembly code. ```rust # #[cfg(target_arch = "x86_64")] @@ -1330,8 +1330,8 @@ r[asm.rules.arm64ec] - On arm64ec, [call checkers with appropriate thunks](https://learn.microsoft.com/en-us/windows/arm/arm64ec-abi#authoring-arm64ec-in-assembly) are mandatory when calling functions. r[asm.rules.only-on-exit] -- 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. +- The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting the assembly code. + - This means that assembly code 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 (without first entering it again). @@ -1387,7 +1387,7 @@ Inline assembly supports a subset of the directives supported by both GNU AS and The result of using other directives is assembler-specific (and may cause an error, or may be accepted as-is). r[asm.directives.stateful] -If inline assembly includes any "stateful" directive that modifies how subsequent assembly is processed, the block must undo the effects of any such directives before the inline assembly ends. +If inline assembly includes any "stateful" directive that modifies how subsequent assembly is processed, the assembly code must undo the effects of any such directives before the inline assembly ends. r[asm.directives.supported-directives] The following directives are guaranteed to be supported by the assembler: @@ -1514,7 +1514,7 @@ On x86 targets, both 32-bit and 64-bit, the following additional directives are - `.code32` - `.code64` -Use of `.code16`, `.code32`, and `.code64` directives are only supported if the state is reset to the default before exiting the assembly block. +Use of `.code16`, `.code32`, and `.code64` directives are only supported if the state is reset to the default before exiting the assembly code. 32-bit x86 uses `.code32` by default, and x86_64 uses `.code64` by default. r[asm.target-specific-directives.arm-32-bit] From c8aa67d9498f042295182cda3919fac729d3b81e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 19 Feb 2025 13:29:50 +0000 Subject: [PATCH 4/7] Apply editorial suggestions from code review Co-authored-by: Alice Ryhl --- src/inline-assembly.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index e301f28d4..48c1fc0b4 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -359,7 +359,7 @@ r[asm.operand-type.supported-operands.label] - The address of the block is substituted into the asm template string. The assembly code may jump to the substituted address. - After execution of the block, the `asm!` expression returns. - The type of the block must be unit or `!` (never). - - The block starts a new safety context; despite the outer `unsafe` block needed for `asm!`, unsafe operations within the `label` block must be wrapped in an inner `unsafe` block. + - The block starts a new safety context: unsafe operations within the `label` block must be wrapped in an inner `unsafe` block, even though the entire `asm!` statement is already wrapped in `unsafe`. ```rust # #[cfg(target_arch = "x86_64")] @@ -1331,7 +1331,7 @@ r[asm.rules.arm64ec] r[asm.rules.only-on-exit] - The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting the assembly code. - - This means that assembly code that never return (even if not marked `noreturn`) don't need to preserve these registers. + - This means that assembly code that never returns (even if not marked `noreturn`) doesn'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 (without first entering it again). From 230d8ff389ce6e2b56fc2b477669af3ac89857f0 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Mar 2025 09:12:19 -0700 Subject: [PATCH 5/7] More distinctions between asm blocks and assembly code Update more cases where the phrasing could potentially have been interpreted as affecting a `label` block. --- src/inline-assembly.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index 48c1fc0b4..ed0b85250 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -1007,7 +1007,7 @@ r[asm.options.supported-options.nomem] # #[cfg(target_arch = "x86_64")] { let mut x = 0i32; let z: i32; -// Accessing memory from a nomem asm block is disallowed +// Accessing memory from assembly in a nomem asm block is disallowed unsafe { core::arch::asm!("mov {val:e}, dword ptr [{ptr}]", ptr = in(reg) &mut x, @@ -1016,7 +1016,7 @@ unsafe { ) } -// Writing to memory is also undefined behaviour +// Writing to memory from assembly in a nomem asm block is also undefined behaviour unsafe { core::arch::asm!("mov dword ptr [{ptr}], {val:e}", ptr = in(reg) &mut x, @@ -1243,7 +1243,7 @@ r[asm.rules.black-box] - 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. - - However there is no guarantee that each `asm!` directly corresponds to a single instance of instructions in the object file: the compiler is free to duplicate or deduplicate `asm!` blocks. + - However there is no guarantee that each `asm!` directly corresponds to a single instance of instructions in the object file: the compiler is free to duplicate or deduplicate the assembly code in `asm!` blocks. r[asm.rules.stack-below-sp] - Unless the `nostack` option is set, asm code is allowed to use stack space below the stack pointer. @@ -1332,9 +1332,9 @@ r[asm.rules.arm64ec] r[asm.rules.only-on-exit] - The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting the assembly code. - This means that assembly code that never returns (even if not marked `noreturn`) doesn'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 (without first entering it again). + - When returning to the assembly code of 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 the assembly code of an `asm!` block that has not been entered. + Neither can you exit the assembly code of an `asm!` block whose assembly code has already been exited (without first entering it again). - You are responsible for switching any target-specific state (e.g. thread-local storage, stack bounds). - You cannot jump from an address in one `asm!` block to an address in another, even within the same function or block, without treating their contexts as potentially different and requiring context switching. You cannot assume that any particular value in those contexts (e.g. current stack pointer or temporary values below the stack pointer) will remain unchanged between the two `asm!` blocks. - The set of memory locations that you may access is the intersection of those allowed by the `asm!` blocks you entered and exited. @@ -1361,7 +1361,7 @@ In addition to all of the previous rules, the string argument to `asm!` must ult after all other arguments are evaluated, formatting is performed, and operands are translated--- assembly that is both syntactically correct and semantically valid for the target architecture. The formatting rules allow the compiler to generate assembly with correct syntax. -Rules concerning operands permit valid translation of Rust operands into and out of `asm!`. +Rules concerning operands permit valid translation of Rust operands into and out of the assembly code. Adherence to these rules is necessary, but not sufficient, for the final expanded assembly to be both correct and valid. For instance: From 18c78079ae6902b018585c71ada2d8b1b2b5aece Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Mar 2025 09:14:38 -0700 Subject: [PATCH 6/7] Clarify an interaction between `noreturn` and `label` --- src/inline-assembly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index ed0b85250..dd2e4d916 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -1096,7 +1096,7 @@ r[asm.options.supported-options.preserves_flags] r[asm.options.supported-options.noreturn] - `noreturn`: The assembly code 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. + A `noreturn` asm block with no `label` blocks behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked. - When any `label` blocks are present, `noreturn` means the execution of the assembly code never falls through; the assembly code may only exit by jumping to one of the specified blocks. The entire `asm!` block will have unit type in this case, unless all `label` blocks diverge, in which case the return type is `!`. From 9a6e970db5ac638e7ae1d502e9c70fdcda4b9669 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 17 Mar 2025 08:40:17 +0000 Subject: [PATCH 7/7] Revise `asm_goto` changes Let's make some clarifications of substance and editorial tweaks to the changes for `asm_goto`. We add an example, we change various wording to be more clear -- in particular, we significantly revise the section on `noreturn` to unify the statement of the rules -- and we replace remaining uses of "asm code" with "assembly code" to match our verbiage elsewhere. The main clarification of substance is that for requirements that must be upheld when falling through the assembly, such as he requirement to restore the stack pointer, we must also note that the requirement must be satisfied before jumping to any `label` blocks. --- src/inline-assembly.md | 73 +++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/src/inline-assembly.md b/src/inline-assembly.md index dd2e4d916..48e9baebf 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -225,8 +225,8 @@ r[asm.operand-type.supported-operands.in] * `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). + - The allocated register will contain the value of `` at the start of the assembly code. + - The allocated register must contain the same value at the end of the assembly code (except if a `lateout` is allocated to the same register). ```rust # #[cfg(target_arch = "x86_64")] { @@ -239,9 +239,9 @@ r[asm.operand-type.supported-operands.out] * `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 are written 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). + - The allocated register will contain an undefined value at the start of the assembly code. + - `` must be a (possibly uninitialized) place expression, to which the contents of the allocated register are written at the end of the assembly 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 assembly code (effectively acting as a clobber). ```rust # #[cfg(target_arch = "x86_64")] { @@ -271,8 +271,8 @@ r[asm.operand-type.supported-operands.inout] * `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 are written at the end of the asm code. + - The allocated register will contain the value of `` at the start of the assembly code. + - `` must be a mutable initialized place expression, to which the contents of the allocated register are written at the end of the assembly code. ```rust # #[cfg(target_arch = "x86_64")] { @@ -286,8 +286,8 @@ assert_eq!(x, 5); r[asm.operand-type.supported-operands.inout-arrow] * `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 are written 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). + - `` must be a (possibly uninitialized) place expression, to which the contents of the allocated register are written at the end of the assembly 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 assembly code (effectively acting as a clobber). - `` and `` may have different types. ```rust @@ -318,7 +318,7 @@ r[asm.operand-type.supported-operands.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. + - `` is allowed to point to a `#[thread_local]` static, in which case the assembly code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data. ```rust # #[cfg(target_arch = "x86_64")] { @@ -359,7 +359,7 @@ r[asm.operand-type.supported-operands.label] - The address of the block is substituted into the asm template string. The assembly code may jump to the substituted address. - After execution of the block, the `asm!` expression returns. - The type of the block must be unit or `!` (never). - - The block starts a new safety context: unsafe operations within the `label` block must be wrapped in an inner `unsafe` block, even though the entire `asm!` statement is already wrapped in `unsafe`. + - The block starts a new safety context; unsafe operations within the `label` block must be wrapped in an inner `unsafe` block, even though the entire `asm!` expression is already wrapped in `unsafe`. ```rust # #[cfg(target_arch = "x86_64")] @@ -370,7 +370,6 @@ unsafe { } ``` - r[asm.operand-type.left-to-right] 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. @@ -749,7 +748,7 @@ Some registers cannot be used for input or output operands: | Architecture | Unsupported register | Reason | | ------------ | -------------------- | ------ | -| All | `sp`, `r15` (s390x) | The stack pointer must be restored to its original value at the end of an assembly code. | +| All | `sp`, `r15` (s390x) | The stack pointer must be restored to its original value at the end of the assembly code or before jumping to a `label` block. | | All | `bp` (x86), `x29` (AArch64 and Arm64EC), `x8` (RISC-V), `$fp` (LoongArch), `r11` (s390x) | The frame pointer cannot be used as an input or output. | | ARM | `r7` or `r11` | On ARM the frame pointer can be either `r7` or `r11` depending on the target. The frame pointer cannot be used as an input or output. | | All | `si` (x86-32), `bx` (x86-64), `r6` (ARM), `x19` (AArch64 and Arm64EC), `x9` (RISC-V), `$s8` (LoongArch) | This is used internally by LLVM as a "base pointer" for functions with complex stack frames. | @@ -863,7 +862,7 @@ assert_eq!(x, 0x1000u16); r[asm.template-modifiers.smaller-value] 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`). +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 assembly 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. @@ -999,7 +998,7 @@ assert_eq!(z, 0); r[asm.options.supported-options.nomem] - `nomem`: The assembly code does not read from or write to any memory accessible outside of the assembly code. - This allows the compiler to cache the values of modified global variables in registers across the assembly code since it knows that they are not read or written to by the `asm!`. + This allows the compiler to cache the values of modified global variables in registers across execution of the assembly code since it knows that they are not read from or written to by it. The compiler also assumes that the assembly code does not perform any kind of synchronization with other threads, e.g. via fences. @@ -1007,7 +1006,8 @@ r[asm.options.supported-options.nomem] # #[cfg(target_arch = "x86_64")] { let mut x = 0i32; let z: i32; -// Accessing memory from assembly in a nomem asm block is disallowed +// Accessing outside memory from assembly when `nomem` is +// specified is disallowed unsafe { core::arch::asm!("mov {val:e}, dword ptr [{ptr}]", ptr = in(reg) &mut x, @@ -1016,7 +1016,8 @@ unsafe { ) } -// Writing to memory from assembly in a nomem asm block is also undefined behaviour +// Writing to outside memory from assembly when `nomem` is +// specified is also undefined behaviour unsafe { core::arch::asm!("mov dword ptr [{ptr}], {val:e}", ptr = in(reg) &mut x, @@ -1045,14 +1046,14 @@ assert_eq!(z, 1); r[asm.options.supported-options.readonly] - `readonly`: The assembly code does not write to any memory accessible outside of the assembly code. - This allows the compiler to cache the values of unmodified global variables in registers across the assembly code since it knows that they are not written to by the `asm!`. + This allows the compiler to cache the values of unmodified global variables in registers across execution of the assembly code since it knows that they are not written to by it. The compiler also assumes that this assembly code does not perform any kind of synchronization with other threads, e.g. via fences. ```rust,no_run # #[cfg(target_arch = "x86_64")] { let mut x = 0; -// We cannot modify memory in readonly +// We cannot modify outside memory when `readonly` is specified unsafe { core::arch::asm!("mov dword ptr[{}], 1", in(reg) &mut x, options(readonly)) } @@ -1091,14 +1092,10 @@ assert_eq!(z, 1); r[asm.options.supported-options.preserves_flags] - `preserves_flags`: The assembly code does not modify the flags register (defined in the rules below). - This allows the compiler to avoid recomputing the condition flags after the assembly code. + This allows the compiler to avoid recomputing the condition flags after execution of the assembly code. r[asm.options.supported-options.noreturn] -- `noreturn`: The assembly code 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 with no `label` blocks behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked. -- When any `label` blocks are present, `noreturn` means the execution of the assembly code never falls through; the assembly code may only exit by jumping to one of the specified blocks. - The entire `asm!` block will have unit type in this case, unless all `label` blocks diverge, in which case the return type is `!`. +- `noreturn`: The assembly code does not fall through; behavior is undefined if it does. It may still jump to `label` blocks. If any `label` blocks return unit, the `asm!` block will return unit. Otherwise it will return `!` (never). As with a call to a function that does not return, local variables in scope are not dropped before execution of the assembly code. ```rust,no_run @@ -1119,6 +1116,16 @@ unsafe { core::arch::asm!("", options(noreturn)); } # } ``` +```rust +# #[cfg(target_arch = "x86_64")] +let _: () = unsafe { + // You may still jump to a `label` block + core::arch::asm!("jmp {}", label { + println!(); + }, options(noreturn)); +}; +``` + r[asm.options.supported-options.nostack] - `nostack`: The assembly code 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. @@ -1237,29 +1244,29 @@ r[asm.rules.mem-same-as-ffi] - 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 assembly code. + - These rules do not apply to memory which is private to the assembly code, such as stack space allocated within it. r[asm.rules.black-box] -- 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. +- The compiler cannot assume that the instructions in the assembly code are the ones that will actually end up executed. + - This effectively means that the compiler must treat the assembly code 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. - - However there is no guarantee that each `asm!` directly corresponds to a single instance of instructions in the object file: the compiler is free to duplicate or deduplicate the assembly code in `asm!` blocks. + - However there is no guarantee that each block of assembly code in the source directly corresponds to a single instance of instructions in the object file; the compiler is free to duplicate or deduplicate the assembly code in `asm!` blocks. r[asm.rules.stack-below-sp] -- Unless the `nostack` option is set, asm code is allowed to use stack space below the stack pointer. +- Unless the `nostack` option is set, assembly code is allowed to use stack space below the stack pointer. - On entry to the assembly code 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 assembly code. r[asm.rules.noreturn] -- If the `noreturn` option is set then behavior is undefined if execution falls through to the end of the assembly code. +- If the `noreturn` option is set then behavior is undefined if execution falls through the end of the assembly code. r[asm.rules.pure] - 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 assembly code is allowed to read. + - When used with the `readonly` option, "inputs" comprise the direct inputs of the assembly code and any memory that it is allowed to read. r[asm.rules.preserved-registers] - These flags registers must be restored upon exiting the assembly code if the `preserves_flags` option is set: @@ -1331,7 +1338,7 @@ r[asm.rules.arm64ec] r[asm.rules.only-on-exit] - The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting the assembly code. - - This means that assembly code that never returns (even if not marked `noreturn`) doesn't need to preserve these registers. + - This means that assembly code that does not fall through and does not jump to any `label` blocks, even if not marked `noreturn`, doesn't need to preserve these registers. - When returning to the assembly code of 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 the assembly code of an `asm!` block that has not been entered. Neither can you exit the assembly code of an `asm!` block whose assembly code has already been exited (without first entering it again).