Skip to content

Commit 61d3e7b

Browse files
authored
Merge pull request #1687 from ehuss/x86-tests
Fix tests running on non-x86 platforms.
2 parents d4d33fb + 81834e3 commit 61d3e7b

File tree

1 file changed

+34
-0
lines changed

1 file changed

+34
-0
lines changed

src/unsafe/asm.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ Inline assembly is currently supported on the following architectures:
1818
Let us start with the simplest possible example:
1919

2020
```rust
21+
# #[cfg(target_arch = "x86_64")] {
2122
use std::arch::asm;
2223

2324
unsafe {
2425
asm!("nop");
2526
}
27+
# }
2628
```
2729

2830
This will insert a NOP (no operation) instruction into the assembly generated by the compiler.
@@ -36,13 +38,15 @@ Now inserting an instruction that does nothing is rather boring. Let us do somet
3638
actually acts on data:
3739

3840
```rust
41+
# #[cfg(target_arch = "x86_64")] {
3942
use std::arch::asm;
4043

4144
let x: u64;
4245
unsafe {
4346
asm!("mov {}, 5", out(reg) x);
4447
}
4548
assert_eq!(x, 5);
49+
# }
4650
```
4751

4852
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
6165
Let us see another example that also uses an input:
6266

6367
```rust
68+
# #[cfg(target_arch = "x86_64")] {
6469
use std::arch::asm;
6570

6671
let i: u64 = 3;
@@ -74,6 +79,7 @@ unsafe {
7479
);
7580
}
7681
assert_eq!(o, 8);
82+
# }
7783
```
7884

7985
This will add `5` to the input in variable `i` and write the result to variable `o`.
@@ -97,13 +103,15 @@ readability, and allows reordering instructions without changing the argument or
97103
We can further refine the above example to avoid the `mov` instruction:
98104

99105
```rust
106+
# #[cfg(target_arch = "x86_64")] {
100107
use std::arch::asm;
101108

102109
let mut x: u64 = 3;
103110
unsafe {
104111
asm!("add {0}, 5", inout(reg) x);
105112
}
106113
assert_eq!(x, 8);
114+
# }
107115
```
108116

109117
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
112120
It is also possible to specify different variables for the input and output parts of an `inout` operand:
113121

114122
```rust
123+
# #[cfg(target_arch = "x86_64")] {
115124
use std::arch::asm;
116125

117126
let x: u64 = 3;
@@ -120,6 +129,7 @@ unsafe {
120129
asm!("add {0}, 5", inout(reg) x => y);
121130
}
122131
assert_eq!(y, 8);
132+
# }
123133
```
124134

125135
## Late output operands
@@ -135,6 +145,7 @@ There is also a `inlateout` variant of this specifier.
135145
Here is an example where `inlateout` *cannot* be used in `release` mode or other optimized cases:
136146

137147
```rust
148+
# #[cfg(target_arch = "x86_64")] {
138149
use std::arch::asm;
139150

140151
let mut a: u64 = 4;
@@ -150,6 +161,7 @@ unsafe {
150161
);
151162
}
152163
assert_eq!(a, 12);
164+
# }
153165
```
154166
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.
155167

@@ -158,6 +170,7 @@ That is because in optimized cases, the compiler is free to allocate the same re
158170
However the following example can use `inlateout` since the output is only modified after all input registers have been read:
159171

160172
```rust
173+
# #[cfg(target_arch = "x86_64")] {
161174
use std::arch::asm;
162175

163176
let mut a: u64 = 4;
@@ -166,6 +179,7 @@ unsafe {
166179
asm!("add {0}, {1}", inlateout(reg) a, in(reg) b);
167180
}
168181
assert_eq!(a, 8);
182+
# }
169183
```
170184

171185
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
177191
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.
178192

179193
```rust,no_run
194+
# #[cfg(target_arch = "x86_64")] {
180195
use std::arch::asm;
181196
182197
let cmd = 0xd1;
183198
unsafe {
184199
asm!("out 0x64, eax", in("eax") cmd);
185200
}
201+
# }
186202
```
187203

188204
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`
192208
Consider this example which uses the x86 `mul` instruction:
193209

194210
```rust
211+
# #[cfg(target_arch = "x86_64")] {
195212
use std::arch::asm;
196213

197214
fn mul(a: u64, b: u64) -> u128 {
@@ -211,6 +228,7 @@ fn mul(a: u64, b: u64) -> u128 {
211228

212229
((hi as u128) << 64) + lo as u128
213230
}
231+
# }
214232
```
215233

216234
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
229247
```rust
230248
use std::arch::asm;
231249

250+
# #[cfg(target_arch = "x86_64")]
232251
fn main() {
233252
// three entries of four bytes each
234253
let mut name_buf = [0_u8; 12];
@@ -262,6 +281,9 @@ fn main() {
262281
let name = core::str::from_utf8(&name_buf).unwrap();
263282
println!("CPU Manufacturer ID: {}", name);
264283
}
284+
285+
# #[cfg(not(target_arch = "x86_64"))]
286+
# fn main() {}
265287
```
266288

267289
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
276298
This can also be used with a general register class to obtain a scratch register for use inside the asm code:
277299

278300
```rust
301+
# #[cfg(target_arch = "x86_64")] {
279302
use std::arch::asm;
280303

281304
// Multiply x by 6 using shifts and adds
@@ -291,6 +314,7 @@ unsafe {
291314
);
292315
}
293316
assert_eq!(x, 4 * 6);
317+
# }
294318
```
295319

296320
## Symbol operands and ABI clobbers
@@ -300,6 +324,7 @@ By default, `asm!` assumes that any register not specified as an output will hav
300324
[`clobber_abi`]: ../../reference/inline-assembly.html#abi-clobbers
301325

302326
```rust
327+
# #[cfg(target_arch = "x86_64")] {
303328
use std::arch::asm;
304329

305330
extern "C" fn foo(arg: i32) -> i32 {
@@ -325,6 +350,7 @@ fn call_foo(arg: i32) -> i32 {
325350
result
326351
}
327352
}
353+
# }
328354
```
329355

330356
## Register template modifiers
@@ -336,6 +362,7 @@ By default the compiler will always choose the name that refers to the full regi
336362
This default can be overridden by using modifiers on the template string operands, just like you would with format strings:
337363

338364
```rust
365+
# #[cfg(target_arch = "x86_64")] {
339366
use std::arch::asm;
340367

341368
let mut x: u16 = 0xab;
@@ -345,6 +372,7 @@ unsafe {
345372
}
346373

347374
assert_eq!(x, 0xabab);
375+
# }
348376
```
349377

350378
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,13 +389,15 @@ You have to manually use the memory address syntax specified by the target archi
361389
For example, on x86/x86_64 using Intel assembly syntax, you should wrap inputs/outputs in `[]` to indicate they are memory operands:
362390

363391
```rust
392+
# #[cfg(target_arch = "x86_64")] {
364393
use std::arch::asm;
365394

366395
fn load_fpu_control_word(control: u16) {
367396
unsafe {
368397
asm!("fldcw [{}]", in(reg) &control, options(nostack));
369398
}
370399
}
400+
# }
371401
```
372402

373403
## Labels
@@ -383,6 +413,7 @@ As a consequence, you should only use GNU assembler **numeric** [local labels] i
383413
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`.)
384414

385415
```rust
416+
# #[cfg(target_arch = "x86_64")] {
386417
use std::arch::asm;
387418

388419
let mut a = 0;
@@ -400,6 +431,7 @@ unsafe {
400431
);
401432
}
402433
assert_eq!(a, 5);
434+
# }
403435
```
404436

405437
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
419451
Let's take our previous example of an `add` instruction:
420452

421453
```rust
454+
# #[cfg(target_arch = "x86_64")] {
422455
use std::arch::asm;
423456

424457
let mut a: u64 = 4;
@@ -431,6 +464,7 @@ unsafe {
431464
);
432465
}
433466
assert_eq!(a, 8);
467+
# }
434468
```
435469

436470
Options can be provided as an optional final argument to the `asm!` macro. We specified three options here:

0 commit comments

Comments
 (0)