Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tests running on non-x86 platforms. #1687

Merged
merged 1 commit into from
Mar 21, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/unsafe/asm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -36,13 +38,15 @@ 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;
unsafe {
asm!("mov {}, 5", out(reg) x);
}
assert_eq!(x, 5);
# }
```

This will write the value `5` into the `u64` variable `x`.
Expand All @@ -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;
Expand All @@ -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`.
Expand All @@ -97,13 +103,15 @@ 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;
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.
Expand All @@ -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;
Expand All @@ -120,6 +129,7 @@ unsafe {
asm!("add {0}, 5", inout(reg) x => y);
}
assert_eq!(y, 8);
# }
```

## Late output operands
Expand All @@ -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;
Expand All @@ -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.

Expand All @@ -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;
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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 {
Expand All @@ -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.
Expand All @@ -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];
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -291,6 +314,7 @@ unsafe {
);
}
assert_eq!(x, 4 * 6);
# }
```

## Symbol operands and ABI clobbers
Expand All @@ -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 {
Expand All @@ -325,6 +350,7 @@ fn call_foo(arg: i32) -> i32 {
result
}
}
# }
```

## Register template modifiers
Expand All @@ -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;
Expand All @@ -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.
Expand All @@ -361,13 +389,15 @@ 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) {
unsafe {
asm!("fldcw [{}]", in(reg) &control, options(nostack));
}
}
# }
```

## Labels
Expand All @@ -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;
Expand All @@ -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`.
Expand All @@ -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;
Expand All @@ -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:
Expand Down