Sometimes assembly instructions require operands passed via memory addresses/memory
locations. You have to manually use the memory address syntax specified by the target architecture.
For example, on x86/x86_64 using Intel assembly syntax, you should wrap inputs/outputs in [] to
indicate they are memory operands:
use std::arch::asm;
fn load_fpu_control_word(control: u16) {
unsafe {
asm!("fldcw [{}]", in(reg) &control, options(nostack));
}
}
Labels
Any reuse of a named label, local or otherwise, can result in an assembler or linker error or may
cause other strange behavior. Reuse of a named label can happen in a variety of ways including:
explicitly: using a label more than once in one asm! block, or multiple times across blocks.
implicitly via inlining: the compiler is allowed to instantiate multiple copies of an asm! block,
for example when the function containing it is inlined in multiple places.
implicitly via LTO: LTO can cause code from other crates to be placed in the same codegen unit,
and so could bring in arbitrary labels.
As a consequence, you should only use GNU assembler numeric local labels inside inline assembly
code. Defining symbols in assembly code may lead to assembler and/or linker errors due to
duplicate symbol definitions.
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, below, for more on options .)
use std::arch::asm;
let mut a = 0;
unsafe {
asm!(
"mov {0}, 10",
"2:",
"sub {0}, 1",
"cmp {0}, 3",
"jle 2f",
"jmp 2b",
"2:",
"add {0}, 2",
out(reg) a
);
}
assert_eq!(a, 5);
This will decrement the {0} register value from 10 to 3, then add 2 and store it in a .
This example shows a few things:
First, that the same number can be used as a label multiple times in the same inline block.
Second, that when a numeric label is used as a reference (as an instruction operand, for
example), the suffixes “b” (“backward”) or ”f” (“forward”) should be added to the numeric label.
It will then refer to the nearest label defined by this number in this direction.
Options
By default, an inline assembly block is treated the same way as an external FFI function call with a
custom calling convention: it may read/write memory, have observable side effects, etc. However, in
many cases it is desirable to give the compiler more information about what the assembly code is
actually doing so that it can optimize better.
Let's take our previous example of an add instruction:
use std::arch::asm;
let mut a: u64 = 4;
let b: u64 = 4;
unsafe {
asm!(
"add {0}, {1}",
inlateout(reg) a, in(reg) b,
options(pure, nomem, nostack),
);
}
assert_eq!(a, 8);
Options can be provided as an optional final argument to the asm! macro. We specified three
options here:
pure means that the asm code has no observable side effects and that its output depends
only on its inputs. This allows the compiler optimizer to call the inline asm fewer times or even
eliminate it entirely.
nomem means that the asm code does not read or write to memory. By default the compiler
will assume that inline assembly can read or write any memory address that is accessible to it
(e.g. through a pointer passed as an operand, or a global).
nostack means that the asm code does not push any data onto the stack. This allows the
compiler to use optimizations such as the stack red zone on x86-64 to avoid stack pointer
adjustments.
These allow the compiler to better optimize code using asm! , for example by eliminating pure asm!
blocks whose outputs are not needed.
See the reference for the full list of available options and their effects.