Skip to content

Commit e88f411

Browse files
chorman0773ehuss
authored andcommitted
Add explanatory comments to outstanding tests
1 parent 5f86d06 commit e88f411

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
@@ -111,7 +111,7 @@ The corresponding arguments are accessed in order, by index, or by name.
111111
unsafe { core::arch::asm!("mov {0}, {1}", out(reg) y, in(reg) 5);}
112112
/// ... and this
113113
unsafe { core::arch::asm!("mov {out}, {in}", out = out(reg) z, in = in(reg) 5);}
114-
/// all have the same behaviour
114+
/// all have the same behavior
115115
assert_eq!(x,y);
116116
assert_eq!(y,z);
117117
# }
@@ -138,6 +138,7 @@ The expected usage is for each template string argument to correspond to a line
138138
# #[cfg(target_arch = "x86_64")] {
139139
let x: i64;
140140
let y: i64;
141+
// We can separate multiple strings as if they were written together
141142
unsafe { core::arch::asm!("mov eax, 5", "mov ecx, eax", out("rax") x, out("rcx") y); }
142143
assert_eq!(x, y);
143144
# }
@@ -230,6 +231,7 @@ r[asm.operand-type.supported-operands.in]
230231

231232
```rust
232233
# #[cfg(target_arch = "x86_64")] {
234+
// ``in` can be used to pass values into inline assembly...
233235
unsafe { core::arch::asm!("/* {} */", in(reg) 5); }
234236
# }
235237
```
@@ -245,6 +247,7 @@ r[asm.operand-type.supported-operands.out]
245247
```rust
246248
# #[cfg(target_arch = "x86_64")] {
247249
let x: i64;
250+
/// and `out` can be used to pass values back to rust.
248251
unsafe { core::arch::asm!("/* {} */", out(reg) x); }
249252
# }
250253
```
@@ -257,6 +260,8 @@ r[asm.operand-type.supported-operands.lateout]
257260
```rust
258261
# #[cfg(target_arch = "x86_64")] {
259262
let x: i64;
263+
// `lateout` is the same as `out`
264+
// but the compiler knows we don't care about the value of any inputs by the time we overwrite it.
260265
unsafe { core::arch::asm!("mov {}, 5", lateout(reg) x); }
261266
assert_eq!(x, 5)
262267
# }
@@ -272,6 +277,7 @@ r[asm.operand-type.supported-operands.inout]
272277
```rust
273278
# #[cfg(target_arch = "x86_64")] {
274279
let mut x: i64 = 4;
280+
// `inout` can be used to modify values in-register
275281
unsafe { core::arch::asm!("inc {}", inout(reg) x); }
276282
assert_eq!(x,5);
277283
# }
@@ -287,6 +293,7 @@ r[asm.operand-type.supported-operands.inout-arrow]
287293
```rust
288294
# #[cfg(target_arch = "x86_64")] {
289295
let x: i64;
296+
/// `inout` can also move values to different places
290297
unsafe { core::arch::asm!("inc {}", inout(reg) 4u64=>x); }
291298
assert_eq!(x,5);
292299
# }
@@ -300,6 +307,7 @@ r[asm.operand-type.supported-operands.inlateout]
300307
```rust
301308
# #[cfg(target_arch = "x86_64")] {
302309
let mut x: i64 = 4;
310+
// `inlateout` is `inout` using `lateout`
303311
unsafe { core::arch::asm!("inc {}", inlateout(reg) x); }
304312
assert_eq!(x,5);
305313
# }
@@ -317,6 +325,7 @@ r[asm.operand-type.supported-operands.sym]
317325
extern "C" fn foo(){
318326
println!("Hello from inline assembly")
319327
}
328+
// `sym` can be used to refer to a function (even if it doesn't have an external name we can directly write)
320329
unsafe { core::arch::asm!("call {}", sym foo, clobber_abi("C")); }
321330
# }
322331
```
@@ -568,6 +577,9 @@ The availability of supported types for a particular register class may depend o
568577
let x = 5i32;
569578
let y = -1i8;
570579
let z = unsafe{core::arch::x86_64::_mm_set_epi64x(1,0)};
580+
581+
// reg is valid for `i32`, `reg_byte` is valid for `i8`, and xmm_reg is valid for `__m128i`
582+
// We can't use `tmm0` as an input or output, but we can clobber it.
571583
unsafe { core::arch::asm!("/* {} {} {} */", in(reg) x, in(reg_byte) y, in(xmm_reg) z, out("tmm0") _); }
572584
# }
573585
```
@@ -585,12 +597,12 @@ r[asm.register-operands.smaller-value]
585597
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.
586598
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.
587599

588-
<!--no_run, this test has a non-deterministic runtime behaviour-->
600+
<!--no_run, this test has a non-deterministic runtime behavior-->
589601
```rust,no_run
590602
# #[cfg(target_arch = "x86_64")] {
591603
let mut x: i64;
592604
// Moving a 32-bit value into a 64-bit value, oops.
593-
#[allow(asm_sub_register)] // rustc warns about this behaviour
605+
#[allow(asm_sub_register)] // rustc warns about this behavior
594606
unsafe { core::arch::asm!("mov {}, {}", lateout(reg) x, in(reg) 4i32); }
595607
// top 32-bits are indeterminate
596608
assert_eq!(x, 4); // This assertion is not guaranteed to succeed
@@ -753,7 +765,7 @@ Only one modifier is allowed per template placeholder.
753765

754766
```rust,compile_fail
755767
# #[cfg(target_arch = "x86_64")] {
756-
// bp is reserved
768+
// We can't specify both `r` and `e` at the same time.
757769
unsafe { core::arch::asm!("/* {:er}", in(reg) 5i32); }
758770
# }
759771
# #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
@@ -810,6 +822,17 @@ The supported modifiers are a subset of LLVM's (and GCC's) [asm template argumen
810822
> GCC will infer the modifier based on the operand value type, while we default to the full register size.
811823
> - 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.
812824
825+
```rust
826+
# #[cfg(target_arch = "x86_64")] {
827+
let mut x = 0x10u16;
828+
829+
// u16::swap_bytes using `xchg`
830+
// low half of `{x}` is referred to by `{x:l}`, and the high half by `{x:h}`
831+
unsafe { core::arch::asm!("xchg {x:l}, {x:h}", x = inout(reg_abcd) x); }
832+
assert_eq!(x, 0x1000u16);
833+
# }
834+
```
835+
813836
r[asm.template-modifiers.smaller-value]
814837
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.
815838
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`).
@@ -829,6 +852,7 @@ This will automatically insert the necessary clobber constraints as needed for c
829852
extern "C" fn foo() -> i32{ 0 }
830853
# #[cfg(target_arch = "x86_64")] {
831854
let z: i32;
855+
// To call a function, we have to inform the compiler that we're clobbering callee saved registers
832856
unsafe { core::arch::asm!("call {}", sym foo, out("rax") z, clobber_abi("C")); }
833857
assert_eq!(z, 0);
834858
# }
@@ -842,6 +866,7 @@ extern "sysv64" fn foo() -> i32{ 0 }
842866
extern "win64" fn bar(x: i32) -> i32{ x + 1}
843867
# #[cfg(target_arch = "x86_64")] {
844868
let z: i32;
869+
// We can even call multiple functions with different conventions and different saved registers
845870
unsafe { core::arch::asm!("call {}", "mov ecx, eax", "call {}", sym foo, sym bar, out("rax") z, clobber_abi("C")); }
846871
assert_eq!(z, 1);
847872
# }
@@ -854,6 +879,7 @@ Generic register class outputs are disallowed by the compiler when `clobber_abi`
854879
extern "C" fn foo(x: i32) -> i32{ 0 }
855880
# #[cfg(target_arch = "x86_64")] {
856881
let z: i32;
882+
// explicit registers must be used to not accidentally overlap.
857883
unsafe { core::arch::asm!("mov eax, {:e}", "call {}", out(reg) z, sym foo, clobber_abi("C")); }
858884
assert_eq!(z, 0);
859885
# }
@@ -900,6 +926,7 @@ r[asm.options.supported-options.pure]
900926
# #[cfg(target_arch = "x86_64")] {
901927
let x: i32 = 0;
902928
let z: i32;
929+
// pure can be used to optimize by assuming the assembly has no side effects
903930
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure, nomem)); }
904931
assert_eq!(z, 1);
905932
# }
@@ -908,6 +935,7 @@ r[asm.options.supported-options.pure]
908935
```rust,compile_fail
909936
# #[cfg(target_arch = "x86_64")] {
910937
let z: i32;
938+
// Either nomem or readonly must be satisfied, to indicate whether or not memory is allowed to be read
911939
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure)); }
912940
assert_eq!(z, 0);
913941
# }
@@ -919,16 +947,31 @@ r[asm.options.supported-options.nomem]
919947
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!`.
920948
The compiler also assumes that this `asm!` block does not perform any kind of synchronization with other threads, e.g. via fences.
921949

922-
<!-- no_run: This test has unpredictable or undefined behaviour at runtime -->
950+
<!-- no_run: This test has unpredictable or undefined behavior at runtime -->
923951
```rust,no_run
924952
# #[cfg(target_arch = "x86_64")] {
925953
let mut x = 0i32;
926954
let z: i32;
927-
// The following line has undefined behaviour
955+
// Accessing memory from a nomem asm block is disallowed
928956
unsafe { core::arch::asm!("mov {val:e}, dword ptr [{ptr}]", ptr = in(reg) &mut x, val = lateout(reg) z, options(nomem))}
957+
958+
// Writing to memory is also undefined behaviour
959+
unsafe { core::arch::asm!("mov dword ptr [{ptr}], {val:e}", ptr = in(reg) &mut x, val = in(reg) z, options(nomem))}
929960
# }
930961
```
931962

963+
```rust
964+
# #[cfg(target_arch = "x86_64")] {
965+
let x: i32 = 0;
966+
let z: i32;
967+
// If we allocate our own memory, such as via `push`, however.
968+
// we can still use it
969+
unsafe { core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}", x = inout(reg) x => z, options(nomem)); }
970+
assert_eq!(z, 1);
971+
# }
972+
```
973+
974+
932975
r[asm.options.supported-options.readonly]
933976
- `readonly`: The `asm!` block does not write to any memory accessible outside of the `asm!` block.
934977
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!`.
@@ -938,11 +981,32 @@ r[asm.options.supported-options.readonly]
938981
```rust,no_run
939982
# #[cfg(target_arch = "x86_64")] {
940983
let mut x = 0;
941-
// The following line has undefined behaviour
984+
// We cannot modify memory in readonly
942985
unsafe { core::arch::asm!("mov dword ptr[{}], 1", in(reg) &mut x, options(readonly))}
943986
# }
944987
```
945988

989+
```rust
990+
# #[cfg(target_arch = "x86_64")] {
991+
let x: i64 = 0;
992+
let z: i64;
993+
// We can still read from it, though
994+
unsafe { core::arch::asm!("mov {x}, qword ptr [{x}]", x = inout(reg) &x => z, options(readonly)); }
995+
assert_eq!(z, 0);
996+
# }
997+
```
998+
999+
1000+
```rust
1001+
# #[cfg(target_arch = "x86_64")] {
1002+
let x: i64 = 0;
1003+
let z: i64;
1004+
// Same exception applies as with nomem.
1005+
unsafe { core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}", x = inout(reg) x => z, options(readonly)); }
1006+
assert_eq!(z, 1);
1007+
# }
1008+
```
1009+
9461010
r[asm.options.supported-options.preserves_flags]
9471011
- `preserves_flags`: The `asm!` block does not modify the flags register (defined in the rules below).
9481012
This allows the compiler to avoid recomputing the condition flags after the `asm!` block.
@@ -962,7 +1026,7 @@ fn main() -> !{
9621026
}
9631027
```
9641028

965-
<!-- no_run: Test has undefined behaviour at runtime -->
1029+
<!-- no_run: Test has undefined behavior at runtime -->
9661030
```rust,no_run
9671031
# #[cfg(target_arch = "x86_64")] {
9681032
// You are responsible for not falling past the end of a noreturn asm block
@@ -974,7 +1038,7 @@ r[asm.options.supported-options.nostack]
9741038
- `nostack`: The `asm!` block does not push data to the stack, or write to the stack red-zone (if supported by the target).
9751039
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.
9761040

977-
<!-- no_run: Test has undefined behaviour at runtime -->
1041+
<!-- no_run: Test has undefined behavior at runtime -->
9781042
```rust,no_run
9791043
# #[cfg(target_arch = "x86_64")] {
9801044
// `push` and `pop` are UB when used with nostack

0 commit comments

Comments
 (0)