Skip to content

Commit e7579c7

Browse files
committed
Add explanatory comments to outstanding tests
1 parent 1414747 commit e7579c7

File tree

1 file changed

+73
-9
lines changed

1 file changed

+73
-9
lines changed

src/inline-assembly.md

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ The corresponding arguments are accessed in order, by index, or by name.
113113
unsafe { core::arch::asm!("mov {0}, {1}", out(reg) y, in(reg) 5);}
114114
/// ... and this
115115
unsafe { core::arch::asm!("mov {out}, {in}", out = out(reg) z, in = in(reg) 5);}
116-
/// all have the same behaviour
116+
/// all have the same behavior
117117
assert_eq!(x,y);
118118
assert_eq!(y,z);
119119
# }
@@ -140,6 +140,7 @@ The expected usage is for each template string argument to correspond to a line
140140
# #[cfg(target_arch = "x86_64")] {
141141
let x: i64;
142142
let y: i64;
143+
// We can separate multiple strings as if they were written together
143144
unsafe { core::arch::asm!("mov eax, 5", "mov ecx, eax", out("rax") x, out("rcx") y); }
144145
assert_eq!(x, y);
145146
# }
@@ -233,6 +234,7 @@ r[asm.operand-type.supported-operands.in]
233234

234235
```rust
235236
# #[cfg(target_arch = "x86_64")] {
237+
// ``in` can be used to pass values into inline assembly...
236238
unsafe { core::arch::asm!("/* {} */", in(reg) 5); }
237239
# }
238240
```
@@ -248,6 +250,7 @@ r[asm.operand-type.supported-operands.out]
248250
```rust
249251
# #[cfg(target_arch = "x86_64")] {
250252
let x: i64;
253+
/// and `out` can be used to pass values back to rust.
251254
unsafe { core::arch::asm!("/* {} */", out(reg) x); }
252255
# }
253256
```
@@ -260,6 +263,8 @@ r[asm.operand-type.supported-operands.lateout]
260263
```rust
261264
# #[cfg(target_arch = "x86_64")] {
262265
let x: i64;
266+
// `lateout` is the same as `out`
267+
// but the compiler knows we don't care about the value of any inputs by the time we overwrite it.
263268
unsafe { core::arch::asm!("mov {}, 5", lateout(reg) x); }
264269
assert_eq!(x, 5)
265270
# }
@@ -275,6 +280,7 @@ r[asm.operand-type.supported-operands.inout]
275280
```rust
276281
# #[cfg(target_arch = "x86_64")] {
277282
let mut x: i64 = 4;
283+
// `inout` can be used to modify values in-register
278284
unsafe { core::arch::asm!("inc {}", inout(reg) x); }
279285
assert_eq!(x,5);
280286
# }
@@ -290,6 +296,7 @@ r[asm.operand-type.supported-operands.inout-arrow]
290296
```rust
291297
# #[cfg(target_arch = "x86_64")] {
292298
let x: i64;
299+
/// `inout` can also move values to different places
293300
unsafe { core::arch::asm!("inc {}", inout(reg) 4u64=>x); }
294301
assert_eq!(x,5);
295302
# }
@@ -303,6 +310,7 @@ r[asm.operand-type.supported-operands.inlateout]
303310
```rust
304311
# #[cfg(target_arch = "x86_64")] {
305312
let mut x: i64 = 4;
313+
// `inlateout` is `inout` using `lateout`
306314
unsafe { core::arch::asm!("inc {}", inlateout(reg) x); }
307315
assert_eq!(x,5);
308316
# }
@@ -320,6 +328,7 @@ r[asm.operand-type.supported-operands.sym]
320328
extern "C" fn foo(){
321329
println!("Hello from inline assembly")
322330
}
331+
// `sym` can be used to refer to a function (even if it doesn't have an external name we can directly write)
323332
unsafe { core::arch::asm!("call {}", sym foo, clobber_abi("C")); }
324333
# }
325334
```
@@ -557,6 +566,9 @@ The availability of supported types for a particular register class may depend o
557566
let x = 5i32;
558567
let y = -1i8;
559568
let z = unsafe{core::arch::x86_64::_mm_set_epi64x(1,0)};
569+
570+
// reg is valid for `i32`, `reg_byte` is valid for `i8`, and xmm_reg is valid for `__m128i`
571+
// We can't use `tmm0` as an input or output, but we can clobber it.
560572
unsafe { core::arch::asm!("/* {} {} {} */", in(reg) x, in(reg_byte) y, in(xmm_reg) z, out("tmm0") _); }
561573
# }
562574
```
@@ -574,12 +586,12 @@ r[asm.register-operands.smaller-value]
574586
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.
575587
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.
576588

577-
<!--no_run, this test has a non-deterministic runtime behaviour-->
589+
<!--no_run, this test has a non-deterministic runtime behavior-->
578590
```rust,no_run
579591
# #[cfg(target_arch = "x86_64")] {
580592
let mut x: i64;
581593
// Moving a 32-bit value into a 64-bit value, oops.
582-
#[allow(asm_sub_register)] // rustc warns about this behaviour
594+
#[allow(asm_sub_register)] // rustc warns about this behavior
583595
unsafe { core::arch::asm!("mov {}, {}", lateout(reg) x, in(reg) 4i32); }
584596
// top 32-bits are indeterminate
585597
assert_eq!(x, 4); // This assertion is not guaranteed to succeed
@@ -733,7 +745,7 @@ Only one modifier is allowed per template placeholder.
733745

734746
```rust,compile_fail
735747
# #[cfg(target_arch = "x86_64")] {
736-
// bp is reserved
748+
// We can't specify both `r` and `e` at the same time.
737749
unsafe { core::arch::asm!("/* {:er}", in(reg) 5i32); }
738750
# }
739751
# #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
@@ -787,6 +799,17 @@ The supported modifiers are a subset of LLVM's (and GCC's) [asm template argumen
787799
> GCC will infer the modifier based on the operand value type, while we default to the full register size.
788800
> - 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.
789801
802+
```rust
803+
# #[cfg(target_arch = "x86_64")] {
804+
let mut x = 0x10u16;
805+
806+
// u16::swap_bytes using `xchg`
807+
// low half of `{x}` is referred to by `{x:l}`, and the high half by `{x:h}`
808+
unsafe { core::arch::asm!("xchg {x:l}, {x:h}", x = inout(reg_abcd) x); }
809+
assert_eq!(x, 0x1000u16);
810+
# }
811+
```
812+
790813
r[asm.template-modifiers.smaller-value]
791814
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.
792815
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`).
@@ -807,6 +830,7 @@ This will automatically insert the necessary clobber constraints as needed for c
807830
extern "C" fn foo() -> i32{ 0 }
808831
# #[cfg(target_arch = "x86_64")] {
809832
let z: i32;
833+
// To call a function, we have to inform the compiler that we're clobbering callee saved registers
810834
unsafe { core::arch::asm!("call {}", sym foo, out("rax") z, clobber_abi("C")); }
811835
assert_eq!(z, 0);
812836
# }
@@ -820,6 +844,7 @@ extern "sysv64" fn foo() -> i32{ 0 }
820844
extern "win64" fn bar(x: i32) -> i32{ x + 1}
821845
# #[cfg(target_arch = "x86_64")] {
822846
let z: i32;
847+
// We can even call multiple functions with different conventions and different saved registers
823848
unsafe { core::arch::asm!("call {}", "mov ecx, eax", "call {}", sym foo, sym bar, out("rax") z, clobber_abi("C")); }
824849
assert_eq!(z, 1);
825850
# }
@@ -832,6 +857,7 @@ Generic register class outputs are disallowed by the compiler when `clobber_abi`
832857
extern "C" fn foo(x: i32) -> i32{ 0 }
833858
# #[cfg(target_arch = "x86_64")] {
834859
let z: i32;
860+
// explicit registers must be used to not accidentally overlap.
835861
unsafe { core::arch::asm!("mov eax, {:e}", "call {}", out(reg) z, sym foo, clobber_abi("C")); }
836862
assert_eq!(z, 0);
837863
# }
@@ -876,6 +902,7 @@ r[asm.options.supported-options.pure]
876902
# #[cfg(target_arch = "x86_64")] {
877903
let x: i32 = 0;
878904
let z: i32;
905+
// pure can be used to optimize by assuming the assembly has no side effects
879906
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure, nomem)); }
880907
assert_eq!(z, 1);
881908
# }
@@ -884,6 +911,7 @@ r[asm.options.supported-options.pure]
884911
```rust,compile_fail
885912
# #[cfg(target_arch = "x86_64")] {
886913
let z: i32;
914+
// Either nomem or readonly must be satisfied, to indicate whether or not memory is allowed to be read
887915
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure)); }
888916
assert_eq!(z, 0);
889917
# }
@@ -895,16 +923,31 @@ r[asm.options.supported-options.nomem]
895923
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!`.
896924
The compiler also assumes that this `asm!` block does not perform any kind of synchronization with other threads, e.g. via fences.
897925

898-
<!-- no_run: This test has unpredictable or undefined behaviour at runtime -->
926+
<!-- no_run: This test has unpredictable or undefined behavior at runtime -->
899927
```rust,no_run
900928
# #[cfg(target_arch = "x86_64")] {
901929
let mut x = 0i32;
902930
let z: i32;
903-
// The following line has undefined behaviour
931+
// Accessing memory from a nomem asm block is disallowed
904932
unsafe { core::arch::asm!("mov {val:e}, dword ptr [{ptr}]", ptr = in(reg) &mut x, val = lateout(reg) z, options(nomem))}
933+
934+
// Writing to memory is also undefined behaviour
935+
unsafe { core::arch::asm!("mov dword ptr [{ptr}], {val:e}", ptr = in(reg) &mut x, val = in(reg) z, options(nomem))}
905936
# }
906937
```
907938

939+
```rust
940+
# #[cfg(target_arch = "x86_64")] {
941+
let x: i32 = 0;
942+
let z: i32;
943+
// If we allocate our own memory, such as via `push`, however.
944+
// we can still use it
945+
unsafe { core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}", x = inout(reg) x => z, options(nomem)); }
946+
assert_eq!(z, 1);
947+
# }
948+
```
949+
950+
908951
r[asm.options.supported-options.readonly]
909952
- `readonly`: The `asm!` block does not write to any memory accessible outside of the `asm!` block.
910953
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!`.
@@ -914,11 +957,32 @@ r[asm.options.supported-options.readonly]
914957
```rust,no_run
915958
# #[cfg(target_arch = "x86_64")] {
916959
let mut x = 0;
917-
// The following line has undefined behaviour
960+
// We cannot modify memory in readonly
918961
unsafe { core::arch::asm!("mov dword ptr[{}], 1", in(reg) &mut x, options(readonly))}
919962
# }
920963
```
921964

965+
```rust
966+
# #[cfg(target_arch = "x86_64")] {
967+
let x: i64 = 0;
968+
let z: i64;
969+
// We can still read from it, though
970+
unsafe { core::arch::asm!("mov {x}, qword ptr [{x}]", x = inout(reg) &x => z, options(readonly)); }
971+
assert_eq!(z, 0);
972+
# }
973+
```
974+
975+
976+
```rust
977+
# #[cfg(target_arch = "x86_64")] {
978+
let x: i64 = 0;
979+
let z: i64;
980+
// Same exception applies as with nomem.
981+
unsafe { core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}", x = inout(reg) x => z, options(readonly)); }
982+
assert_eq!(z, 1);
983+
# }
984+
```
985+
922986
r[asm.options.supported-options.preserves_flags]
923987
- `preserves_flags`: The `asm!` block does not modify the flags register (defined in the rules below).
924988
This allows the compiler to avoid recomputing the condition flags after the `asm!` block.
@@ -938,7 +1002,7 @@ fn main() -> !{
9381002
}
9391003
```
9401004

941-
<!-- no_run: Test has undefined behaviour at runtime -->
1005+
<!-- no_run: Test has undefined behavior at runtime -->
9421006
```rust,no_run
9431007
# #[cfg(target_arch = "x86_64")] {
9441008
// You are responsible for not falling past the end of a noreturn asm block
@@ -950,7 +1014,7 @@ r[asm.options.supported-options.nostack]
9501014
- `nostack`: The `asm!` block does not push data to the stack, or write to the stack red-zone (if supported by the target).
9511015
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.
9521016

953-
<!-- no_run: Test has undefined behaviour at runtime -->
1017+
<!-- no_run: Test has undefined behavior at runtime -->
9541018
```rust,no_run
9551019
# #[cfg(target_arch = "x86_64")] {
9561020
// `push` and `pop` are UB when used with nostack

0 commit comments

Comments
 (0)