Support for inline assembly is provided via the asm!
and global_asm!
macros. It can be used to embed handwritten assembly in the assembly output generated by the compiler.
Support for inline assembly is stable on the following architectures:
The compiler will emit an error if asm!
is used on an unsupported target.
# #[cfg(target_arch = "x86_64")] { use std::arch::asm; // Multiply x by 6 using shifts and adds let mut x: u64 = 4; unsafe { asm!( "mov {tmp}, {x}", "shl {tmp}, 1", "shl {x}, 2", "add {x}, {tmp}", x = inout(reg) x, tmp = out(reg) _, ); } assert_eq!(x, 4 * 6); # }
The following ABNF specifies the general syntax:
format_string := STRING_LITERAL / RAW_STRING_LITERAL dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout" reg_spec := <register class> / "\"" <explicit register> "\"" operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr clobber_abi := "clobber_abi(" <abi> *("," <abi>) [","] ")" option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw" options := "options(" option *("," option) [","] ")" operand := reg_operand / clobber_abi / options asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")" global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")"
Inline assembly can be used in one of two ways.
With the asm!
macro, the assembly code is emitted in a function scope and integrated into the compiler-generated assembly code of a function. This assembly code must obey strict rules to avoid undefined behavior. Note that in some cases the compiler may choose to emit the assembly code as a separate function and generate a call to it.
With the global_asm!
macro, the assembly code is emitted in a global scope, outside a function. This can be used to hand-write entire functions using assembly code, and generally provides much more freedom to use arbitrary registers and assembler directives.
The assembler template uses the same syntax as format strings (i.e. placeholders are specified by curly braces). The corresponding arguments are accessed in order, by index, or by name. However, implicit named arguments (introduced by RFC #2795) are not supported.
An asm!
invocation may have one or more template string arguments; an asm!
with multiple template string arguments is treated as if all the strings were concatenated with a \n
between them. The expected usage is for each template string argument to correspond to a line of assembly code. All template string arguments must appear before any other arguments.
As with format strings, positional arguments must appear before named arguments and explicit register operands.
Explicit register operands cannot be used by placeholders in the template string. All other named and positional operands must appear at least once in the template string, otherwise a compiler error is generated.
The exact assembly code syntax is target-specific and opaque to the compiler except for the way operands are substituted into the template string to form the code passed to the assembler.
Currently, all supported targets follow the assembly code syntax used by LLVM's internal assembler which usually corresponds to that of the GNU assembler (GAS). On x86, the .intel_syntax noprefix
mode of GAS is used by default. On ARM, the .syntax unified
mode is used. These targets impose an additional restriction on the assembly code: any assembler state (e.g. the current section which can be changed with .section
) must be restored to its original value at the end of the asm string. Assembly code that does not conform to the GAS syntax will result in assembler-specific behavior. Further constraints on the directives used by inline assembly are indicated by Directives Support.
Several types of operands are supported:
in(<reg>) <expr>
<reg>
can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string.<expr>
at the start of the asm code.lateout
is allocated to the same register).out(<reg>) <expr>
<reg>
can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string.<expr>
must be a (possibly uninitialized) place expression, to which the contents of the allocated register are written at the end of the asm code._
) may be specified instead of an expression, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber).lateout(<reg>) <expr>
out
except that the register allocator can reuse a register allocated to an in
.inout(<reg>) <expr>
<reg>
can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string.<expr>
at the start of the asm code.<expr>
must be a mutable initialized place expression, to which the contents of the allocated register are written at the end of the asm code.inout(<reg>) <in expr> => <out expr>
inout
except that the initial value of the register is taken from the value of <in expr>
.<out expr>
must be a (possibly uninitialized) place expression, to which the contents of the allocated register are written at the end of the asm code._
) may be specified instead of an expression for <out expr>
, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber).<in expr>
and <out expr>
may have different types.inlateout(<reg>) <expr>
/ inlateout(<reg>) <in expr> => <out expr>
inout
except that the register allocator can reuse a register allocated to an in
(this can happen if the compiler knows the in
has the same initial value as the inlateout
).sym <path>
<path>
must refer to a fn
or static
.<path>
is allowed to point to a #[thread_local]
static, in which case the asm code can combine the symbol with relocations (e.g. @plt
, @TPOFF
) to read from thread-local data.Operand expressions are evaluated from left to right, just like function call arguments. After the asm!
has executed, outputs are written to in left to right order. This is significant if two outputs point to the same place: that place will contain the value of the rightmost output.
Since global_asm!
exists outside a function, it can only use sym
operands.
Input and output operands can be specified either as an explicit register or as a register class from which the register allocator can select a register. Explicit registers are specified as string literals (e.g. "eax"
) while register classes are specified as identifiers (e.g. reg
).
Note that explicit registers treat register aliases (e.g. r14
vs lr
on ARM) and smaller views of a register (e.g. eax
vs rax
) as equivalent to the base register. It is a compile-time error to use the same explicit register for two input operands or two output operands. Additionally, it is also a compile-time error to use overlapping registers (e.g. ARM VFP) in input operands or in output operands.
Only the following types are allowed as operands for inline assembly:
#[repr(simd)]
and which implement Copy
). This includes architecture-specific vector types defined in std::arch
such as __m128
(x86) or int8x16_t
(ARM).Here is the list of currently supported register classes:
Architecture | Register class | Registers | LLVM constraint code |
---|---|---|---|
x86 | reg | ax , bx , cx , dx , si , di , bp , r[8-15] (x86-64 only) | r |
x86 | reg_abcd | ax , bx , cx , dx | Q |
x86-32 | reg_byte | al , bl , cl , dl , ah , bh , ch , dh | q |
x86-64 | reg_byte * | al , bl , cl , dl , sil , dil , bpl , r[8-15]b | q |
x86 | xmm_reg | xmm[0-7] (x86) xmm[0-15] (x86-64) | x |
x86 | ymm_reg | ymm[0-7] (x86) ymm[0-15] (x86-64) | x |
x86 | zmm_reg | zmm[0-7] (x86) zmm[0-31] (x86-64) | v |
x86 | kreg | k[1-7] | Yk |
x86 | kreg0 | k0 | Only clobbers |
x86 | x87_reg | st([0-7]) | Only clobbers |
x86 | mmx_reg | mm[0-7] | Only clobbers |
x86-64 | tmm_reg | tmm[0-7] | Only clobbers |
AArch64 | reg | x[0-30] | r |
AArch64 | vreg | v[0-31] | w |
AArch64 | vreg_low16 | v[0-15] | x |
AArch64 | preg | p[0-15] , ffr | Only clobbers |
ARM (ARM/Thumb2) | reg | r[0-12] , r14 | r |
ARM (Thumb1) | reg | r[0-7] | r |
ARM | sreg | s[0-31] | t |
ARM | sreg_low16 | s[0-15] | x |
ARM | dreg | d[0-31] | w |
ARM | dreg_low16 | d[0-15] | t |
ARM | dreg_low8 | d[0-8] | x |
ARM | qreg | q[0-15] | w |
ARM | qreg_low8 | q[0-7] | t |
ARM | qreg_low4 | q[0-3] | x |
RISC-V | reg | x1 , x[5-7] , x[9-15] , x[16-31] (non-RV32E) | r |
RISC-V | freg | f[0-31] | f |
RISC-V | vreg | v[0-31] | Only clobbers |
LoongArch | reg | $r1 , $r[4-20] , $r[23,30] | r |
LoongArch | freg | $f[0-31] | f |
Notes:
On x86 we treat
reg_byte
differently fromreg
because the compiler can allocateal
andah
separately whereasreg
reserves the whole register.On x86-64 the high byte registers (e.g.
ah
) are not available in thereg_byte
register class.Some register classes are marked as “Only clobbers” which means that registers in these classes cannot be used for inputs or outputs, only clobbers of the form
out(<explicit register>) _
orlateout(<explicit register>) _
.
Each register class has constraints on which value types they can be used with. This is necessary because the way a value is loaded into a register depends on its type. For example, on big-endian systems, loading a i32x4
and a i8x16
into a SIMD register may result in different register contents even if the byte-wise memory representation of both values is identical. The availability of supported types for a particular register class may depend on what target features are currently enabled.
Architecture | Register class | Target feature | Allowed types |
---|---|---|---|
x86-32 | reg | None | i16 , i32 , f32 |
x86-64 | reg | None | i16 , i32 , f32 , i64 , f64 |
x86 | reg_byte | None | i8 |
x86 | xmm_reg | sse | i32 , f32 , i64 , f64 , i8x16 , i16x8 , i32x4 , i64x2 , f32x4 , f64x2 |
x86 | ymm_reg | avx | i32 , f32 , i64 , f64 , i8x16 , i16x8 , i32x4 , i64x2 , f32x4 , f64x2 i8x32 , i16x16 , i32x8 , i64x4 , f32x8 , f64x4 |
x86 | zmm_reg | avx512f | i32 , f32 , i64 , f64 , i8x16 , i16x8 , i32x4 , i64x2 , f32x4 , f64x2 i8x32 , i16x16 , i32x8 , i64x4 , f32x8 , f64x4 i8x64 , i16x32 , i32x16 , i64x8 , f32x16 , f64x8 |
x86 | kreg | avx512f | i8 , i16 |
x86 | kreg | avx512bw | i32 , i64 |
x86 | mmx_reg | N/A | Only clobbers |
x86 | x87_reg | N/A | Only clobbers |
x86 | tmm_reg | N/A | Only clobbers |
AArch64 | reg | None | i8 , i16 , i32 , f32 , i64 , f64 |
AArch64 | vreg | neon | i8 , i16 , i32 , f32 , i64 , f64 , i8x8 , i16x4 , i32x2 , i64x1 , f32x2 , f64x1 , i8x16 , i16x8 , i32x4 , i64x2 , f32x4 , f64x2 |
AArch64 | preg | N/A | Only clobbers |
ARM | reg | None | i8 , i16 , i32 , f32 |
ARM | sreg | vfp2 | i32 , f32 |
ARM | dreg | vfp2 | i64 , f64 , i8x8 , i16x4 , i32x2 , i64x1 , f32x2 |
ARM | qreg | neon | i8x16 , i16x8 , i32x4 , i64x2 , f32x4 |
RISC-V32 | reg | None | i8 , i16 , i32 , f32 |
RISC-V64 | reg | None | i8 , i16 , i32 , f32 , i64 , f64 |
RISC-V | freg | f | f32 |
RISC-V | freg | d | f64 |
RISC-V | vreg | N/A | Only clobbers |
LoongArch64 | reg | None | i8 , i16 , i32 , i64 , f32 , f64 |
LoongArch64 | freg | None | f32 , f64 |
Note: For the purposes of the above table pointers, function pointers and
isize
/usize
are treated as the equivalent integer type (i16
/i32
/i64
depending on the target).
If a value is of a smaller size than the register it is allocated in then the upper bits of that register will have an undefined value for inputs and will be ignored for outputs. The only exception is the freg
register class on RISC-V where f32
values are NaN-boxed in a f64
as required by the RISC-V architecture.
When separate input and output expressions are specified for an inout
operand, both expressions must have the same type. The only exception is if both operands are pointers or integers, in which case they are only required to have the same size. This restriction exists because the register allocators in LLVM and GCC sometimes cannot handle tied operands with different types.
Some registers have multiple names. These are all treated by the compiler as identical to the base register name. Here is the list of all supported register aliases:
Architecture | Base register | Aliases |
---|---|---|
x86 | ax | eax , rax |
x86 | bx | ebx , rbx |
x86 | cx | ecx , rcx |
x86 | dx | edx , rdx |
x86 | si | esi , rsi |
x86 | di | edi , rdi |
x86 | bp | bpl , ebp , rbp |
x86 | sp | spl , esp , rsp |
x86 | ip | eip , rip |
x86 | st(0) | st |
x86 | r[8-15] | r[8-15]b , r[8-15]w , r[8-15]d |
x86 | xmm[0-31] | ymm[0-31] , zmm[0-31] |
AArch64 | x[0-30] | w[0-30] |
AArch64 | x29 | fp |
AArch64 | x30 | lr |
AArch64 | sp | wsp |
AArch64 | xzr | wzr |
AArch64 | v[0-31] | b[0-31] , h[0-31] , s[0-31] , d[0-31] , q[0-31] |
ARM | r[0-3] | a[1-4] |
ARM | r[4-9] | v[1-6] |
ARM | r9 | rfp |
ARM | r10 | sl |
ARM | r11 | fp |
ARM | r12 | ip |
ARM | r13 | sp |
ARM | r14 | lr |
ARM | r15 | pc |
RISC-V | x0 | zero |
RISC-V | x1 | ra |
RISC-V | x2 | sp |
RISC-V | x3 | gp |
RISC-V | x4 | tp |
RISC-V | x[5-7] | t[0-2] |
RISC-V | x8 | fp , s0 |
RISC-V | x9 | s1 |
RISC-V | x[10-17] | a[0-7] |
RISC-V | x[18-27] | s[2-11] |
RISC-V | x[28-31] | t[3-6] |
RISC-V | f[0-7] | ft[0-7] |
RISC-V | f[8-9] | fs[0-1] |
RISC-V | f[10-17] | fa[0-7] |
RISC-V | f[18-27] | fs[2-11] |
RISC-V | f[28-31] | ft[8-11] |
LoongArch | $r0 | $zero |
LoongArch | $r1 | $ra |
LoongArch | $r2 | $tp |
LoongArch | $r3 | $sp |
LoongArch | $r[4-11] | $a[0-7] |
LoongArch | $r[12-20] | $t[0-8] |
LoongArch | $r21 | |
LoongArch | $r22 | $fp , $s9 |
LoongArch | $r[23-31] | $s[0-8] |
LoongArch | $f[0-7] | $fa[0-7] |
LoongArch | $f[8-23] | $ft[0-15] |
LoongArch | $f[24-31] | $fs[0-7] |
Some registers cannot be used for input or output operands:
Architecture | Unsupported register | Reason |
---|---|---|
All | sp | The stack pointer must be restored to its original value at the end of an asm code block. |
All | bp (x86), x29 (AArch64), x8 (RISC-V), $fp (LoongArch) | The frame pointer cannot be used as an input or output. |
ARM | r7 or r11 | On ARM the frame pointer can be either r7 or r11 depending on the target. The frame pointer cannot be used as an input or output. |
All | si (x86-32), bx (x86-64), r6 (ARM), x19 (AArch64), x9 (RISC-V), $s8 (LoongArch) | This is used internally by LLVM as a “base pointer” for functions with complex stack frames. |
x86 | ip | This is the program counter, not a real register. |
AArch64 | xzr | This is a constant zero register which can't be modified. |
AArch64 | x18 | This is an OS-reserved register on some AArch64 targets. |
ARM | pc | This is the program counter, not a real register. |
ARM | r9 | This is an OS-reserved register on some ARM targets. |
RISC-V | x0 | This is a constant zero register which can't be modified. |
RISC-V | gp , tp | These registers are reserved and cannot be used as inputs or outputs. |
LoongArch | $r0 or $zero | This is a constant zero register which can't be modified. |
LoongArch | $r2 or $tp | This is reserved for TLS. |
LoongArch | $r21 | This is reserved by the ABI. |
The frame pointer and base pointer registers are reserved for internal use by LLVM. While asm!
statements cannot explicitly specify the use of reserved registers, in some cases LLVM will allocate one of these reserved registers for reg
operands. Assembly code making use of reserved registers should be careful since reg
operands may use the same registers.
The placeholders can be augmented by modifiers which are specified after the :
in the curly braces. These modifiers do not affect register allocation, but change the way operands are formatted when inserted into the template string. Only one modifier is allowed per template placeholder.
The supported modifiers are a subset of LLVM‘s (and GCC’s) asm template argument modifiers, but do not use the same letter codes.
Architecture | Register class | Modifier | Example output | LLVM modifier |
---|---|---|---|---|
x86-32 | reg | None | eax | k |
x86-64 | reg | None | rax | q |
x86-32 | reg_abcd | l | al | b |
x86-64 | reg | l | al | b |
x86 | reg_abcd | h | ah | h |
x86 | reg | x | ax | w |
x86 | reg | e | eax | k |
x86-64 | reg | r | rax | q |
x86 | reg_byte | None | al / ah | None |
x86 | xmm_reg | None | xmm0 | x |
x86 | ymm_reg | None | ymm0 | t |
x86 | zmm_reg | None | zmm0 | g |
x86 | *mm_reg | x | xmm0 | x |
x86 | *mm_reg | y | ymm0 | t |
x86 | *mm_reg | z | zmm0 | g |
x86 | kreg | None | k1 | None |
AArch64 | reg | None | x0 | x |
AArch64 | reg | w | w0 | w |
AArch64 | reg | x | x0 | x |
AArch64 | vreg | None | v0 | None |
AArch64 | vreg | v | v0 | None |
AArch64 | vreg | b | b0 | b |
AArch64 | vreg | h | h0 | h |
AArch64 | vreg | s | s0 | s |
AArch64 | vreg | d | d0 | d |
AArch64 | vreg | q | q0 | q |
ARM | reg | None | r0 | None |
ARM | sreg | None | s0 | None |
ARM | dreg | None | d0 | P |
ARM | qreg | None | q0 | q |
ARM | qreg | e / f | d0 / d1 | e / f |
RISC-V | reg | None | x1 | None |
RISC-V | freg | None | f0 | None |
LoongArch | reg | None | $r1 | None |
LoongArch | freg | None | $f0 | None |
Notes:
- on ARM
e
/f
: this prints the low or high doubleword register name of a NEON quad (128-bit) register.- on x86: our behavior for
reg
with no modifiers differs from what GCC does. GCC will infer the modifier based on the operand value type, while we default to the full register size.- on x86
xmm_reg
: thex
,t
andg
LLVM modifiers are not yet implemented in LLVM (they are supported by GCC only), but this should be a simple change.
As stated in the previous section, passing an input value smaller than the register width will result in the upper bits of the register containing undefined values. This is not a problem if the inline asm only accesses the lower bits of the register, which can be done by using a template modifier to use a subregister name in the asm code (e.g. ax
instead of rax
). Since this an easy pitfall, the compiler will suggest a template modifier to use where appropriate given the input type. If all references to an operand already have modifiers then the warning is suppressed for that operand.
The clobber_abi
keyword can be used to apply a default set of clobbers to an asm!
block. This will automatically insert the necessary clobber constraints as needed for calling a function with a particular calling convention: if the calling convention does not fully preserve the value of a register across a call then lateout("...") _
is implicitly added to the operands list (where the ...
is replaced by the register's name).
clobber_abi
may be specified any number of times. It will insert a clobber for all unique registers in the union of all specified calling conventions.
Generic register class outputs are disallowed by the compiler when clobber_abi
is used: all outputs must specify an explicit register. Explicit register outputs have precedence over the implicit clobbers inserted by clobber_abi
: a clobber will only be inserted for a register if that register is not used as an output. The following ABIs can be used with clobber_abi
:
Architecture | ABI name | Clobbered registers |
---|---|---|
x86-32 | "C" , "system" , "efiapi" , "cdecl" , "stdcall" , "fastcall" | ax , cx , dx , xmm[0-7] , mm[0-7] , k[0-7] , st([0-7]) |
x86-64 | "C" , "system" (on Windows), "efiapi" , "win64" | ax , cx , dx , r[8-11] , xmm[0-31] , mm[0-7] , k[0-7] , st([0-7]) , tmm[0-7] |
x86-64 | "C" , "system" (on non-Windows), "sysv64" | ax , cx , dx , si , di , r[8-11] , xmm[0-31] , mm[0-7] , k[0-7] , st([0-7]) , tmm[0-7] |
AArch64 | "C" , "system" , "efiapi" | x[0-17] , x18 *, x30 , v[0-31] , p[0-15] , ffr |
ARM | "C" , "system" , "efiapi" , "aapcs" | r[0-3] , r12 , r14 , s[0-15] , d[0-7] , d[16-31] |
RISC-V | "C" , "system" , "efiapi" | x1 , x[5-7] , x[10-17] , x[28-31] , f[0-7] , f[10-17] , f[28-31] , v[0-31] |
LoongArch | "C" , "system" , "efiapi" | $r1 , $r[4-20] , $f[0-23] |
Notes:
- On AArch64
x18
only included in the clobber list if it is not considered as a reserved register on the target.
The list of clobbered registers for each ABI is updated in rustc as architectures gain new registers: this ensures that asm!
clobbers will continue to be correct when LLVM starts using these new registers in its generated code.
Flags are used to further influence the behavior of the inline assembly block. Currently the following options are defined:
pure
: The asm!
block has no side effects, and its outputs depend only on its direct inputs (i.e. the values themselves, not what they point to) or values read from memory (unless the nomem
options is also set). This allows the compiler to execute the asm!
block fewer times than specified in the program (e.g. by hoisting it out of a loop) or even eliminate it entirely if the outputs are not used. The pure
option must be combined with either the nomem
or readonly
options, otherwise a compile-time error is emitted.nomem
: The asm!
blocks does not read or write to any memory. This allows the compiler to cache the values of modified global variables in registers across the asm!
block since it knows that they are not read or written to by the asm!
. The compiler also assumes that this asm!
block does not perform any kind of synchronization with other threads, e.g. via fences.readonly
: The asm!
block does not write to any memory. This allows the compiler to cache the values of unmodified global variables in registers across the asm!
block since it knows that they are not written to by the asm!
. The compiler also assumes that this asm!
block does not perform any kind of synchronization with other threads, e.g. via fences.preserves_flags
: The asm!
block does not modify the flags register (defined in the rules below). This allows the compiler to avoid recomputing the condition flags after the asm!
block.noreturn
: The asm!
block never returns, and its return type is defined as !
(never). Behavior is undefined if execution falls through past the end of the asm code. A noreturn
asm block behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked.nostack
: The asm!
block does not push data to the stack, or write to the stack red-zone (if supported by the target). If this option is not used then the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call.att_syntax
: This option is only valid on x86, and causes the assembler to use the .att_syntax prefix
mode of the GNU assembler. Register operands are substituted in with a leading %
.raw
: This causes the template string to be parsed as a raw assembly string, with no special handling for {
and }
. This is primarily useful when including raw assembly code from an external file using include_str!
.The compiler performs some additional checks on options:
nomem
and readonly
options are mutually exclusive: it is a compile-time error to specify both.pure
on an asm block with no outputs or only discarded outputs (_
).noreturn
on an asm block with outputs.global_asm!
only supports the att_syntax
and raw
options. The remaining options are not meaningful for global-scope inline assembly
To avoid undefined behavior, these rules must be followed when using function-scope inline assembly (asm!
):
undef
which can have a different value every time you read it (since such a concept does not exist in assembly code).lateout
may be allocated to the same register as an in
, in which case this rule does not apply. Code should not rely on this however since it depends on the results of register allocation.readonly
option is set, then only memory reads are allowed.nomem
option is set then no reads or writes to memory are allowed.asm!
as a black box and only take the interface specification into account, not the instructions themselves.nostack
option is set, asm code is allowed to use stack space below the stack pointer.noreturn
option is set then behavior is undefined if execution falls through to the end of the asm block.pure
option is set then behavior is undefined if the asm!
has side-effects other than its direct outputs. Behavior is also undefined if two executions of the asm!
code with the same inputs result in different outputs.nomem
option, “inputs” are just the direct inputs of the asm!
.readonly
option, “inputs” comprise the direct inputs of the asm!
and any memory that the asm!
block is allowed to read.preserves_flags
option is set:EFLAGS
(CF, PF, AF, ZF, SF, OF).MXCSR
(PE, UE, OE, ZE, DE, IE).CPSR
(N, Z, C, V)CPSR
(Q)CPSR
(GE).FPSCR
(N, Z, C, V)FPSCR
(QC)FPSCR
(IDC, IXC, UFC, OFC, DZC, IOC).NZCV
register).FPSR
register).fcsr
(fflags
).vtype
, vl
, vcsr
).$fcc[0-7]
.EFLAGS
) is clear on entry to an asm block and must be clear on exit.st([0-7])
registers have been marked as clobbered with out("st(0)") _, out("st(1)") _, ...
.asm
block. Assembly code must ensure that the x87 register stack is also empty when exiting the asm block.asm!
block.asm!
blocks that never return (even if not marked noreturn
) don't need to preserve these registers.asm!
block than you entered (e.g. for context switching), these registers must contain the value they had upon entering the asm!
block that you are exiting.asm!
block that has not been entered. Neither can you exit an asm!
block that has already been exited (without first entering it again).asm!
block to an address in another, even within the same function or block, without treating their contexts as potentially different and requiring context switching. You cannot assume that any particular value in those contexts (e.g. current stack pointer or temporary values below the stack pointer) will remain unchanged between the two asm!
blocks.asm!
blocks you entered and exited.asm!
blocks adjacent in source code, even without any other code between them, will end up in successive addresses in the binary without any other instructions between them.asm!
block will appear exactly once in the output binary. The compiler is allowed to instantiate multiple copies of the asm!
block, for example when the function containing it is inlined in multiple places.LOCK
) that would apply to instructions generated by the compiler.Note: As a general rule, the flags covered by
preserves_flags
are those which are not preserved when performing a function call.
In addition to all of the previous rules, the string argument to asm!
must ultimately become— after all other arguments are evaluated, formatting is performed, and operands are translated— assembly that is both syntactically correct and semantically valid for the target architecture. The formatting rules allow the compiler to generate assembly with correct syntax. Rules concerning operands permit valid translation of Rust operands into and out of asm!
. Adherence to these rules is necessary, but not sufficient, for the final expanded assembly to be both correct and valid. For instance:
As a result, these rules are non-exhaustive. The compiler is not required to check the correctness and validity of the initial string nor the final assembly that is generated. The assembler may check for correctness and validity but is not required to do so. When using asm!
, a typographical error may be sufficient to make a program unsound, and the rules for assembly may include thousands of pages of architectural reference manuals. Programmers should exercise appropriate care, as invoking this unsafe
capability comes with assuming the responsibility of not violating rules of both the compiler or the architecture.
Inline assembly supports a subset of the directives supported by both GNU AS and LLVM's internal assembler, given as follows. The result of using other directives is assembler-specific (and may cause an error, or may be accepted as-is).
If inline assembly includes any “stateful” directive that modifies how subsequent assembly is processed, the block must undo the effects of any such directives before the inline assembly ends.
The following directives are guaranteed to be supported by the assembler:
.2byte
.4byte
.8byte
.align
.alt_entry
.ascii
.asciz
.balign
.balignl
.balignw
.bss
.byte
.comm
.data
.def
.double
.endef
.equ
.equiv
.eqv
.fill
.float
.global
.globl
.inst
.lcomm
.long
.octa
.option
.p2align
.popsection
.private_extern
.pushsection
.quad
.scl
.section
.set
.short
.size
.skip
.sleb128
.space
.string
.text
.type
.uleb128
.word
The following directives are supported on ELF targets that support DWARF unwind info:
.cfi_adjust_cfa_offset
.cfi_def_cfa
.cfi_def_cfa_offset
.cfi_def_cfa_register
.cfi_endproc
.cfi_escape
.cfi_lsda
.cfi_offset
.cfi_personality
.cfi_register
.cfi_rel_offset
.cfi_remember_state
.cfi_restore
.cfi_restore_state
.cfi_return_column
.cfi_same_value
.cfi_sections
.cfi_signal_frame
.cfi_startproc
.cfi_undefined
.cfi_window_save
On targets with structured exception Handling, the following additional directives are guaranteed to be supported:
.seh_endproc
.seh_endprologue
.seh_proc
.seh_pushreg
.seh_savereg
.seh_setframe
.seh_stackalloc
On x86 targets, both 32-bit and 64-bit, the following additional directives are guaranteed to be supported:
.nops
.code16
.code32
.code64
Use of .code16
, .code32
, and .code64
directives are only supported if the state is reset to the default before exiting the assembly block. 32-bit x86 uses .code32
by default, and x86_64 uses .code64
by default.
On ARM, the following additional directives are guaranteed to be supported:
.even
.fnstart
.fnend
.save
.movsp
.code
.thumb
.thumb_func