diff --git a/src/unsafe/asm.md b/src/unsafe/asm.md index 7ad6e0c5e7..c23f87e458 100644 --- a/src/unsafe/asm.md +++ b/src/unsafe/asm.md @@ -18,11 +18,13 @@ Inline assembly is currently supported on the following architectures: Let us start with the simplest possible example: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; unsafe { asm!("nop"); } +# } ``` This will insert a NOP (no operation) instruction into the assembly generated by the compiler. @@ -36,6 +38,7 @@ Now inserting an instruction that does nothing is rather boring. Let us do somet actually acts on data: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let x: u64; @@ -43,6 +46,7 @@ unsafe { asm!("mov {}, 5", out(reg) x); } assert_eq!(x, 5); +# } ``` This will write the value `5` into the `u64` variable `x`. @@ -61,6 +65,7 @@ the template and will read the variable from there after the inline assembly fin Let us see another example that also uses an input: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let i: u64 = 3; @@ -74,6 +79,7 @@ unsafe { ); } assert_eq!(o, 8); +# } ``` This will add `5` to the input in variable `i` and write the result to variable `o`. @@ -97,6 +103,7 @@ readability, and allows reordering instructions without changing the argument or We can further refine the above example to avoid the `mov` instruction: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut x: u64 = 3; @@ -104,6 +111,7 @@ unsafe { asm!("add {0}, 5", inout(reg) x); } assert_eq!(x, 8); +# } ``` We can see that `inout` is used to specify an argument that is both input and output. @@ -112,6 +120,7 @@ This is different from specifying an input and output separately in that it is g It is also possible to specify different variables for the input and output parts of an `inout` operand: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let x: u64 = 3; @@ -120,6 +129,7 @@ unsafe { asm!("add {0}, 5", inout(reg) x => y); } assert_eq!(y, 8); +# } ``` ## Late output operands @@ -135,6 +145,7 @@ There is also a `inlateout` variant of this specifier. Here is an example where `inlateout` *cannot* be used in `release` mode or other optimized cases: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; @@ -150,6 +161,7 @@ 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. @@ -158,6 +170,7 @@ That is because in optimized cases, the compiler is free to allocate the same re However the following example can use `inlateout` since the output is only modified after all input registers have been read: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; @@ -166,6 +179,7 @@ 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. @@ -177,12 +191,14 @@ Therefore, Rust inline assembly provides some more specific constraint specifier While `reg` is generally available on any architecture, explicit registers 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,no_run +# #[cfg(target_arch = "x86_64")] { use std::arch::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. @@ -192,6 +208,7 @@ In this example we call the `out` instruction to output the content of the `cmd` Consider this example which uses the x86 `mul` instruction: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; fn mul(a: u64, b: u64) -> u128 { @@ -211,6 +228,7 @@ fn mul(a: u64, b: u64) -> u128 { ((hi as u128) << 64) + lo as u128 } +# } ``` This uses the `mul` instruction to multiply two 64-bit inputs with a 128-bit result. @@ -229,6 +247,7 @@ We need to tell the compiler about this since it may need to save and restore th ```rust use std::arch::asm; +# #[cfg(target_arch = "x86_64")] fn main() { // three entries of four bytes each let mut name_buf = [0_u8; 12]; @@ -262,6 +281,9 @@ fn main() { let name = core::str::from_utf8(&name_buf).unwrap(); println!("CPU Manufacturer ID: {}", name); } + +# #[cfg(not(target_arch = "x86_64"))] +# fn main() {} ``` In the example above we use the `cpuid` instruction to read the CPU manufacturer ID. @@ -276,6 +298,7 @@ To work around this we use `rdi` to store the pointer to the output array, save This can also be used with a general register class to obtain a scratch register for use inside the asm code: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; // Multiply x by 6 using shifts and adds @@ -291,6 +314,7 @@ unsafe { ); } assert_eq!(x, 4 * 6); +# } ``` ## Symbol operands and ABI clobbers @@ -300,6 +324,7 @@ By default, `asm!` assumes that any register not specified as an output will hav [`clobber_abi`]: ../../reference/inline-assembly.html#abi-clobbers ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; extern "C" fn foo(arg: i32) -> i32 { @@ -325,6 +350,7 @@ fn call_foo(arg: i32) -> i32 { result } } +# } ``` ## Register template modifiers @@ -336,6 +362,7 @@ By default the compiler will always choose the name that refers to the full regi This default can be overridden by using modifiers on the template string operands, just like you would with format strings: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut x: u16 = 0xab; @@ -345,6 +372,7 @@ unsafe { } assert_eq!(x, 0xabab); +# } ``` In this example, we use the `reg_abcd` register class to restrict the register allocator to the 4 legacy x86 registers (`ax`, `bx`, `cx`, `dx`) of which the first two bytes can be addressed independently. @@ -361,6 +389,7 @@ You have to manually use the memory address syntax specified by the target archi For example, on x86/x86_64 using Intel assembly syntax, you should wrap inputs/outputs in `[]` to indicate they are memory operands: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; fn load_fpu_control_word(control: u16) { @@ -368,6 +397,7 @@ fn load_fpu_control_word(control: u16) { asm!("fldcw [{}]", in(reg) &control, options(nostack)); } } +# } ``` ## Labels @@ -383,6 +413,7 @@ As a consequence, you should only use GNU assembler **numeric** [local labels] i Moreover, on x86 when using the default Intel syntax, due to [an LLVM bug], you shouldn't use labels exclusively made of `0` and `1` digits, e.g. `0`, `11` or `101010`, as they may end up being interpreted as binary values. Using `options(att_syntax)` will avoid any ambiguity, but that affects the syntax of the _entire_ `asm!` block. (See [Options](#options), below, for more on `options`.) ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a = 0; @@ -400,6 +431,7 @@ unsafe { ); } assert_eq!(a, 5); +# } ``` This will decrement the `{0}` register value from 10 to 3, then add 2 and store it in `a`. @@ -419,6 +451,7 @@ By default, an inline assembly block is treated the same way as an external FFI Let's take our previous example of an `add` instruction: ```rust +# #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; @@ -431,6 +464,7 @@ unsafe { ); } assert_eq!(a, 8); +# } ``` Options can be provided as an optional final argument to the `asm!` macro. We specified three options here: