]> git.proxmox.com Git - rustc.git/blobdiff - src/doc/rust-by-example/src/unsafe/asm.md
New upstream version 1.63.0+dfsg1
[rustc.git] / src / doc / rust-by-example / src / unsafe / asm.md
index 10d4a9b27882288ebfb433d9afeaeb34d3161e2e..fa95fcad4010e4b0b3ffdf1da44f5360b7ec1df5 100644 (file)
@@ -132,7 +132,7 @@ To achieve this Rust provides a `lateout` specifier. This can be used on any out
 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:
+Here is an example where `inlateout` *cannot* be used in `release` mode or other optimized cases:
 
 ```rust
 use std::arch::asm;
@@ -151,8 +151,9 @@ unsafe {
 }
 assert_eq!(a, 12);
 ```
+The above could work well in unoptimized cases (`Debug` mode), but if you want optimized performance (`release` mode or other optimized cases), it could not work.
 
-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.
+That is because in optimized cases, 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:
 
@@ -232,25 +233,24 @@ fn main() {
     // three entries of four bytes each
     let mut name_buf = [0_u8; 12];
     // String is stored as ascii in ebx, edx, ecx in order
-    // Because ebx is reserved, we get a scratch register and move from
-    // ebx into it in the asm.  The asm needs to preserve the value of
-    // that register though, so it is pushed and popped around the main asm
+    // Because ebx is reserved, the asm needs to preserve the value of it.
+    // So we push and pop it around the main asm.
     // (in 64 bit mode for 64 bit processors, 32 bit processors would use ebx)
 
     unsafe {
         asm!(
             "push rbx",
             "cpuid",
-            "mov [{0}], ebx",
-            "mov [{0} + 4], edx",
-            "mov [{0} + 8], ecx",
+            "mov [rdi], ebx",
+            "mov [rdi + 4], edx",
+            "mov [rdi + 8], ecx",
             "pop rbx",
             // We use a pointer to an array for storing the values to simplify
             // the Rust code at the cost of a couple more asm instructions
             // This is more explicit with how the asm works however, as opposed
             // to explicit register outputs such as `out("ecx") val`
             // The *pointer itself* is only an input even though it's written behind
-            in(reg) name_buf.as_mut_ptr(),
+            in("rdi") name_buf.as_mut_ptr(),
             // select cpuid 0, also specify eax as clobbered
             inout("eax") 0 => _,
             // cpuid clobbers these registers too
@@ -269,9 +269,11 @@ This instruction writes to `eax` with the maximum supported `cpuid` argument and
 
 Even though `eax` is never read we still need to tell the compiler that the register has been modified so that the compiler can save any values that were in these registers before the asm. This is done by declaring it as an output but with `_` instead of a variable name, which indicates that the output value is to be discarded.
 
-This code also works around the limitation that `ebx` is a reserved register by LLVM. That means that LLVM assumes that it has full control over the register and it must be restored to its original state before exiting the asm block, so it cannot be used as an output. To work around this we save the register via `push`, read from `ebx` inside the asm block into a temporary register allocated with `out(reg)` and then restoring `ebx` to its original state via `pop`. The `push` and `pop` use the full 64-bit `rbx` version of the register to ensure that the entire register is saved. On 32 bit targets the code would instead use `ebx` in the `push`/`pop`.
+This code also works around the limitation that `ebx` is a reserved register by LLVM. That means that LLVM assumes that it has full control over the register and it must be restored to its original state before exiting the asm block, so it cannot be used as an input or output **except** if the compiler uses it to fulfill a general register class (e.g. `in(reg)`). This makes `reg` operands dangerous when using reserved registers as we could unknowingly corrupt out input or output because they share the same register.
 
-This can also be used with a general register class (e.g. `reg`) to obtain a scratch register for use inside the asm code:
+To work around this we use `rdi` to store the pointer to the output array, save `ebx` via `push`, read from `ebx` inside the asm block into the array and then restoring `ebx` to its original state via `pop`. The `push` and `pop` use the full 64-bit `rbx` version of the register to ensure that the entire register is saved. On 32 bit targets the code would instead use `ebx` in the `push`/`pop`.
+
+This can also be used with a general register class to obtain a scratch register for use inside the asm code:
 
 ```rust
 use std::arch::asm;