Inline Assembly Overview
Inline assembly embeds target-architecture machine instructions directly inside a Rust function via the std::arch::asm! macro (or at module scope via std::arch::global_asm!). Because the compiler cannot verify what registers, flags, memory addresses, or calling-convention invariants raw CPU instructions modify, executing asm! always requires an unsafe block.
Supported Architectures
Stabilized in core::arch / std::arch, asm! supports major targets out of the box:
x86andx86_64(uses Intel syntax by default; AT&T syntax optional viaoptions(att_syntax))aarch64(ARM64) andarmriscv32andriscv64loongarch64ands390x
Operand Direction Specifiers in asm!
Operands bind Rust variables to hardware register classes (reg, reg_byte, xmm_reg) using explicit data-flow direction keywords:
| Operand Specifier | Syntax | Meaning |
|---|---|---|
in(reg) expr |
Read-only input | Copies expr into an allocated general-purpose register before the assembly runs |
out(reg) var |
Write-only output | Writes the register's final value into var after the assembly finishes (_ discards output) |
inout(reg) var |
Read-modify-write | Initializes register from var on entry and writes the modified register back to var on exit |
inout(reg) in_expr => out_var |
Split input/output | Initializes register with in_expr and writes final register value to out_var |
lateout(reg) var |
Late output | Output written after all inputs are consumed (allows compiler to reuse an input register) |
in("rax") expr / out("x0") var |
Explicit register | Pins operand to a specific architectural register (required for syscalls and instructions like cpuid) |
clobber_abi("C") |
ABI clobber | Declares that the assembly may overwrite caller-saved registers of the "C" ABI |
Portable Multi-Architecture asm! Example
Using conditional compilation (#[cfg(target_arch = "...")]), an asm! block can execute architecture-specific instructions while binding cleanly to Rust local variables:
use std::arch::asm;
fn multiply_add_asm(a: u64, b: u64, c: u64) -> u64 {
let mut result: u64 = a;
// SAFETY: The assembly instructions only mutate the register allocated for
// `result` and preserve the stack pointer and ABI invariants.
unsafe {
#[cfg(target_arch = "aarch64")]
asm!(
"madd {res}, {lhs}, {rhs}, {acc}",
res = out(reg) result,
lhs = in(reg) a,
rhs = in(reg) b,
acc = in(reg) c,
options(pure, nomem, nostack),
);
#[cfg(target_arch = "x86_64")]
asm!(
"imul {res}, {rhs}",
"add {res}, {acc}",
res = inout(reg) result,
rhs = in(reg) b,
acc = in(reg) c,
options(pure, nomem, nostack),
);
}
result
}
fn main() {
let ans = multiply_add_asm(6, 7, 8);
println!("6 * 7 + 8 = {}", ans);
}
6 * 7 + 8 = 50
Important options(...) Flags
Providing accurate options(...) flags enables the optimizer to schedule and eliminate dead asm! blocks safely:
| Option Flag | Contract Asserted by Programmer |
|---|---|
pure |
Assembly has no side effects and its output depends solely on its inputs (must be paired with nomem or readonly) |
nomem |
Assembly does not read or write any memory addresses |
readonly |
Assembly may read memory, but never writes to memory |
nostack |
Assembly does not push data onto or touch the stack frame (allows preserving the x86_64/AArch64 red zone) |
preserves_flags |
Assembly does not modify CPU condition flags (EFLAGS / NZCV) |
noreturn |
Assembly never falls through to the end (diverges like !) |