Inline Assembly#
This page describes assembly language embedded in C source: asm
statements inside functions (basic and extended), local register
variables, assembler names on declarations, and file-scope asm blocks.
For each target it lists the constraint letters, the operand modifiers and
the complete set of instructions EmbCC's built-in assembler accepts in a
template. It is for people writing kernels, drivers, startup code and
other low-level C with EmbCC, and for people porting inline assembly
written for GCC or Clang. Target triples, register conventions and ABIs
are in Targets; the other GNU extensions are in
Extensions.
How EmbCC assembles a template#
EmbCC does not hand an asm template to an external assembler. It
assembles the template itself, while it generates code for the
function, with a built-in assembler for the selected target. Each
target's assembler accepts a fixed set of instructions, listed on this
page under the target. An instruction outside that set is refused with a
diagnostic that names it. The few places where an assembler accepts an
operand form and encodes something other than what GNU as would are
stated under the target.
| Target | Diagnostic for an instruction the assembler does not know |
|---|---|
| x86-64 | asm instruction "vzeroall" not supported |
| AArch64 | aarch64 inline asm: instruction 'ldxr' is not supported (in "...") |
| ARM Cortex-M | asm instruction "vldr" is not in the ARMv7-M vocabulary |
| RISC-V | asm instruction "amoswap.w" is not in the RISC-V vocabulary |
| MIPS32 | asm instruction "madd $t0, $t1" is not in the MIPS vocabulary |
| SPARC | asm instruction "popc" is SPARC V9's, and the LEON3 is a V8, or ... is not in the SPARC vocabulary |
| PowerPC | asm instruction "fadd" is a floating-point instruction: ..., or ... is not in the PowerPC vocabulary |
| AVR | 'frobnicate' is not an AVR instruction this assembler knows (assembling "...") |
The full diagnostic carries the file and the line of the asm statement:
embcc: probe.c:1: error: asm instruction "vzeroall" not supported
void f(void){ __asm__("vzeroall"); }
On AArch64, ARM Cortex-M, RISC-V and AVR, EmbCC first substitutes the
operands into the template text and then assembles the text with the
same assembler that handles .s and .S files for that target (see
Assembly input). On x86-64 the template is
AT&T syntax and is read by a separate encoder that resolves each operand
reference as it encodes the instruction; it is unrelated to the
NASM-syntax assembler used for .asm files (embas).
On MIPS32 the operands are substituted the same way, and the text is
assembled by the same MIPS32 assembler that reads .s and .S files.
A template is assembled only when its function is emitted. An asm
statement in a function that is never emitted, such as an unused
static inline function in a header, is not assembled, and an unknown
instruction in it is not reported.
There is no -masm= option; x86-64 templates are AT&T syntax. The GCC
option -fno-asm is not accepted (unknown argument '-fno-asm').
Basic asm#
asm ( "TEMPLATE" ) ;
The keyword is spelled asm, __asm__ or __asm. All three are
keywords in every --std= mode, so asm cannot be used as an identifier
even with --std=c11. Adjacent string literals in the template are
concatenated.
Inside a function, EmbCC treats basic asm exactly as an extended asm statement with no operands. In particular:
- The template rules of extended asm apply. On x86-64 a hard register is
written
%%raxeven in basic asm; a single%is read as an operand reference and refused withasm: expected a %N operand in "...". - Basic asm is a compiler barrier like any other
asmstatement (see What the compiler assumes).
Basic asm at file scope is described in File-scope asm.
Extended asm#
Syntax#
asm [volatile] ( TEMPLATE
[ : OUTPUTS
[ : INPUTS
[ : CLOBBERS ] ] ] ) ;
OUTPUTS and INPUTS are comma-separated lists of operands, each of the
form
[ [NAME] ] "CONSTRAINT" ( EXPRESSION )
and CLOBBERS is a comma-separated list of string literals. Any list may
be empty, and trailing sections may be omitted. The template, every
constraint and every clobber may be written as adjacent string literals,
which are concatenated.
Operands are numbered from 0 in the order they appear, outputs first and
then inputs. A + operand is one operand and has one number.
long add_named(long a, long b)
{
long r;
__asm__("movq %[x], %[r]\n\taddq %[y], %[r]"
: [r] "=&r"(r)
: [x] "r"(a), [y] "r"(b));
return r;
}
Qualifiers#
| Qualifier | Status |
|---|---|
volatile |
Accepted, also spelled __volatile__ and __volatile. It changes nothing: EmbCC treats every asm statement as volatile. |
inline |
Not accepted in C: expected '(' after asm before 'inline'. |
goto |
Not supported; refused with the diagnostic below. |
`asm goto` is not supported: its template branches to a label, which needs a patchable placeholder in each backend's inline assembler and CFG edges the optimizer honours. Use a normal asm that sets a value and branch on that
Output operands#
An output constraint must begin with = or +; otherwise EmbCC reports
an asm output constraint must start with '=' or '+' (got "r"). The
expression must be an lvalue; otherwise it reports
an asm output operand must be an lvalue.
| Modifier | Meaning |
|---|---|
= |
The operand is written by the template. Its register holds an unspecified value when the template starts. |
+ |
The operand is read and written. EmbCC loads the lvalue's current value into the register before the template starts. A + anywhere in the constraint has this effect. |
& |
Accepted and ignored. Every operand EmbCC allocates gets a register of its own, distinct from every other operand's, so an output never shares a register with an input unless both name the same fixed register. |
After the template, EmbCC stores the output register into the lvalue, writing as many bytes as the lvalue's type has.
An output must fit in one general-purpose register; on AVR it may occupy
up to four consecutive registers. A wider output is refused on ARM
Cortex-M, RISC-V, MIPS32 and AVR with the diagnostics listed under each target;
on x86-64 and AArch64 it stops the compiler with an internal error
(bad store size 16, aarch64 access size cannot encode 16).
An output constraint that contains m (=m, +m) is accepted, but
EmbCC does not load the lvalue's address into the operand's register and
stores nothing afterwards, so the template has no usable operand. Pass
the address as an input in a register and add a "memory" clobber
instead.
Input operands#
EmbCC evaluates each input expression and loads its value into the operand's register immediately before the template. On AVR an input occupies as many consecutive registers as its type has bytes (1, 2 or 4).
An input whose constraint contains the letter m anywhere ("m",
"rm") is passed by address: the register holds the address of the
expression, and the template dereferences it. The expression must be an
lvalue; anything else stops the compiler with an internal error
(address of a non-lvalue). This differs from GCC, where %1 of an m
operand prints a memory reference. In EmbCC the template writes the
dereference itself:
| Target | Template |
|---|---|
| x86-64 | movq (%1), %0 |
| AArch64, ARM Cortex-M | ldr %0, [%1] |
| RISC-V, MIPS32 | lw %0, 0(%1) |
| AVR | do not use m; pass a pointer with e, x or z and write %a1 (see AVR) |
On AArch64 a bare m is refused; rm is accepted and passes the
address.
The integer-constant constraints i and n behave differently per
target. On AArch64, MIPS32 and AVR the constant is substituted into the
template as a literal. On x86-64, ARM Cortex-M and RISC-V the constant is
computed into a register like any other input, so the template uses it
as a register operand:
/* ARM Cortex-M and RISC-V: %2 is a register holding 5 */
__asm__("add %0, %1, %2" : "=r"(r) : "r"(a), "i"(5));
Clobbers#
A clobber names a register the template changes that is neither an
operand nor otherwise visible to EmbCC. EmbCC never places an operand in
a clobbered general-purpose register. List every register an instruction
writes implicitly, for example rcx and r11 for x86-64 syscall, so
that no input is placed in it.
"memory" is accepted and has no additional effect, because EmbCC
already treats every asm statement as reading and writing memory.
"cc" is accepted and has no effect.
A clobber that does not name a general-purpose register of the target (a floating-point or vector register, or an unknown name) is accepted and ignored.
A callee-saved register in the clobber list is refused on AArch64, ARM Cortex-M, RISC-V, MIPS32 and AVR, and accepted without being saved on x86-64; see Callee-saved registers.
Referring to operands in the template#
| Form | Meaning |
|---|---|
%N |
Operand number N. |
%[NAME] |
The operand declared with [NAME]. An unknown name is refused (x86-64: asm: unknown operand %[zz] in "..."; other targets: asm template names an unknown operand 'zz'). |
%% |
On x86-64, the prefix of a hard register name (%%rax, %%cr3). On the other targets, a literal %. |
A number past the last operand is refused (x86-64:
asm operand %5 out of range in "..."; other targets:
asm template refers to operand %5, but there are only 2).
The modifiers each target accepts between % and the operand are:
| Modifier | Meaning | Targets |
|---|---|---|
%cN, %c[NAME] |
the value of a constant ("i", "n") operand alone, with no # or $: -5 |
every target; refused for a register operand (%c0 names a register operand) |
%wN, %w[NAME] |
the 32-bit name of the register (w9) |
AArch64 |
%xN, %x[NAME] |
the 64-bit name of the register (x9) |
AArch64 |
%AN .. %DN |
byte 0 to 3 of a multi-register operand | AVR |
%aN |
the operand as a pointer register: X, Y or Z |
AVR |
No other modifier is supported. On x86-64, %c is read only in a
directive (below); GCC's %b, %h, %w, %k and %q are refused
there with asm: expected a %N operand in "...". The generic %n,
%l, %P and %= are refused on every target; outside x86-64 the
diagnostic is asm template modifier '%n' is not supported for aarch64
(with ARMv7-M, RISC-V or AVR in place of aarch64).
On x86-64, a constant operand that the template names only as %c is
text: no register is allocated or loaded for it.
A register that the template names directly (%%rsi on x86-64, x0 on
AArch64, r0 on ARM, a0 on RISC-V, r24 on AVR) is never given to an
operand.
Directives in a template#
Beside its instructions, a template may hold data and alignment, written
as GNU as writes them:
| Directive | Emits |
|---|---|
.ascii "S"[, "S"...] |
the bytes of each string |
.asciz "S", .string "S" |
each string followed by a NUL byte |
.byte, .short, .word, .long, .quad and the other data directives |
each comma-separated constant expression at the directive's size |
.p2align N[, FILL[, MAX]] |
padding to a multiple of 2^N bytes |
.balign N[, FILL[, MAX]] |
padding to a multiple of N bytes (a power of two) |
.align N[, FILL[, MAX]] |
as .balign N on x86-64 in ELF and COFF objects, as .p2align N everywhere else, including AVR, where GNU as reads it that way |
Strings take the escapes \b, \f, \n, \r, \t, \", \\, an
octal \NNN and a hex \xHH. A ;, #, @ or // inside a string is
part of the string. A data value must be a constant that fits the
directive's size, signed or unsigned; a symbol is refused, because an
inline template cannot carry a relocation.
The data directive's size follows the target's assembler: .word is two
bytes on x86-64 and AVR and four elsewhere; .half and .dword exist on
RISC-V; .hword, .xword and .dword on AArch64.
Padding is decided where the template lands in its section, not relative
to the template: .p2align 2 after an 18-byte string pads to the next
multiple of four in .text. Without a fill value, the padding is the
target's nop, as llvm-mc pads code (x86-64's long nops, AArch64's nop
after zero bytes up to a word, Thumb's nop, RISC-V's c.nop and
nop, zero bytes on AVR). A function holding such a template starts on
that alignment, so the padding is also right when the function is in a
section of its own (--sections=functions). The largest alignment is 16
bytes, the alignment of .text.
Data that would leave the next instruction off an instruction boundary,
such as .byte 7 alone on Arm or RISC-V, is followed by zero bytes up to
one, as GNU as does on AArch64.
On ARM and AArch64, the data and the padding after it are marked with
$d mapping symbols, so a disassembler shows them as data.
This is how Linux's asm-offsets and similar layout probes write a
structure's size into an object file:
#define LAYOUT(name, size, align) \
__asm__ volatile("\n.ascii \"->PROBE " #name " %c0 %c1\"\n.p2align 2\n" \
: : "i"(size), "i"(align))
These directives are read on x86-64, AArch64, ARM (Cortex-M and ARM
state), RISC-V, AVR, MIPS32 and LoongArch64. On MIPS32 and LoongArch64
an alignment larger than four bytes is refused (the asm aligns to 8 bytes, which MIPS inline asm does not do yet). On Xtensa, SPARC, PowerPC,
TriCore, RX and ColdFire a template takes no directive yet.
Constraint strings#
EmbCC reads a constraint as a set of letters: it looks for the letters it knows, in a fixed order of precedence for the target, and ignores the others. A GCC constraint with several alternatives therefore takes the meaning of the first known letter by that order. The letters and their precedence are listed under each target.
A matching constraint, a digit such as "0", is supported on every
target: the input's value goes into the register of the output it
names, and the output's value comes out. EmbCC makes the pair one
in-out ("+") operand before the target reads a constraint -- with the
output's own lvalue directly, and through a temporary when the input is
another value ("=r"(sum) : "0"(a)) -- and renumbers the template: the
input's %N names the output, and the operands after it keep their
meaning. An input of a narrower type than its output is converted to the
output's type, where GCC leaves whatever the target's load gives.
Flag-output constraints ("=@ccz") are not supported on any target.
They are discussed under x86-64 and AArch64, the
two targets that define __GCC_ASM_FLAG_OUTPUTS__.
Whether an unsupported constraint is reported depends on the target. On
x86-64, ARM Cortex-M and RISC-V it is reported when the function is
checked, even if the function is never emitted. On AArch64, MIPS32 and
AVR it is reported only when the asm statement is generated, so a GCC-style
header with another machine's constraints in an unused static inline
function compiles.
What the compiler assumes#
EmbCC makes the same assumptions about every asm statement, with or
without volatile and whatever its clobbers say:
- It is never deleted, even when its outputs are unused, and a loop that contains one is not unrolled.
- It may read and write any memory. Values that the code read from memory before the statement are read again after it.
- A function that contains an
asmstatement may be inlined like any other, so analways_inlineintrinsic around one disappears into its caller. - On ARM Cortex-M and RISC-V, a value live across the statement is kept
out of the registers it may change: its operands' registers, the
registers in its clobber list, the registers its template names, the
one EmbCC stores outputs through, and every caller-saved register
(r0 to r3, r12 and lr; ra, t0 to t6 and a0 to a7) when the template
calls (
bl,blxorsvcon ARM;call,tail,jal,jalrorecallon RISC-V). Any other register may hold a value across it, so the template must list every register it changes and does not name, as GCC requires. On x86-64, AArch64 and AVR no value is kept in a register across it: every value live across anasmstatement stays in memory, so the template may change any caller-saved register without listing it, provided no operand is placed there (see Clobbers).
The operands are moved into their registers immediately before the
template and out of them immediately after it. On ARM Cortex-M and
RISC-V they are values like any other: an input is moved from wherever it is (a
register or memory), several at once as one parallel move, and an
"=r" output of an integer or pointer type is a value that its variable
receives afterwards, so a local written by an asm can live in a
register. Other outputs, and every operand on the other targets, go
through memory: inputs and + outputs are loaded immediately before the
template, and outputs are stored through their addresses immediately
after it.
Callee-saved registers#
EmbCC does not save callee-saved registers around an asm statement.
Each target handles a template that would change one as follows:
| Target | Callee-saved register named in the template or the clobbers | Operand placed in a callee-saved register |
|---|---|---|
| x86-64 | accepted; not saved | saved by the prologue at --opt=speed and --opt=size only |
| AArch64 | refused (x19 to x30) | cannot happen: neither the allocator nor a register variable uses one |
| ARM Cortex-M | refused (r4 to r11) | never chosen by the allocator; possible through the letters S and D (see ARM Cortex-M), and then saved by the prologue and given no other value in that function |
| RISC-V | refused (s0 to s11) | never chosen by the allocator; see Register variables |
| MIPS32 | refused (s0 to s7, and gp, sp, fp, ra, k0, k1) | cannot happen: neither the allocator nor a register variable uses one |
| AVR | refused (r2 to r17, and r28 and r29) | refused |
The details and the diagnostics are under each target.
Register variables#
register TYPE NAME __asm__("REGISTER") [= INITIALIZER];
A local variable declared with an asm register name is placed in that
register when, and only when, the variable itself appears as an operand
of an asm statement. Everywhere else it is an ordinary local variable.
An expression that merely contains the variable (x + 0) is not bound.
The register keyword is optional: long x __asm__("r10"); binds the
same way.
| Target | Names that bind | Other names |
|---|---|---|
| x86-64 | rax, rbx, rcx, rdx, rsi, rdi, r8 to r15 |
ignored without a diagnostic; the operand is placed as its constraint says |
| AArch64 | x0 to x11, x13 to x15, and the w names of the same registers |
refused |
| ARM Cortex-M | not supported (see below) | |
| RISC-V | not supported (see below) | |
| MIPS32 | v0, v1, a0 to a3, t0 to t9, with or without $, and their numbers $2 to $15, $24, $25 |
refused |
| AVR | r0, r1, r18 to r27, r30, r31, XL, XH, ZL, ZH, X, Z, __tmp_reg__, __zero_reg__ |
refused |
On x86-64 a fixed-register constraint letter (a, b, c, d, S,
D) takes precedence over the variable's register. Only the 64-bit
names bind: register int x __asm__("eax") is ignored.
On AArch64 any other name is refused with
register variable bound to 'x19' is not supported for aarch64 asm (use x0..x11 or x13..x15).
x12 is the code generator's address scratch, x16 to x18 are the
intra-procedure-call and platform registers, and x19 and above are
callee-saved.
On MIPS32 any other name is refused with
register variable bound to '$s0' is not supported for MIPS asm (use v0-v1, a0-a3 or t0-t9).
On AVR a name that is not a register is refused with
register variable bound to 'foo' is not an AVR register, and a
callee-saved register or half of the frame pointer with
an asm operand is pinned to 'r5', which is callee-saved, and EmbCC saves nothing around an asm.
On ARM Cortex-M and RISC-V register variables are not supported, and
EmbCC does not diagnose them. It looks the name up among the x86-64
names above, so every RISC-V ABI name and the ARM names r0 to r7 are
ignored and the operand is allocated as its constraint says. The names
r8 to r15 match x86-64 names and bind to register number 8 to 15
with no callee-saved check: on Cortex-M that is r8 to r15. To put a
value in a particular register on these targets, move it there inside
the template and name the register; a register the template names is
never given to an operand:
int semihost(int op, void *arg) /* ARM Cortex-M */
{
int r;
__asm__ volatile("mov r0, %1\n\tmov r1, %2\n\tbkpt #0xab\n\tmov %0, r0"
: "=r"(r) : "r"(op), "r"(arg) : "r0", "r1", "memory");
return r;
}
Register variables at file scope (global register variables) are not
supported: 'register' is not supported yet (see docs/manual/c-language.md).
Assembler names on declarations#
GCC's asm labels, which give a declaration a different symbol name,
are not supported in C:
| Declaration | Result |
|---|---|
int foo asm("bar"); at file scope |
expected ';' before 'asm' |
int f(void) asm("g"); |
expected '{' or ';' before 'asm' |
extern int y asm("zz"); in a block |
expected ';' before 'asm' |
static int y asm("zz"); in a block |
accepted; the name is ignored and the object keeps its usual symbol |
In C++ they are supported; see C++.
File-scope asm#
asm ( "TEMPLATE" ) ;
An asm block outside any function takes no qualifier and no operands.
Each line of the template is one statement, optionally preceded by a
label (name:).
How a block is assembled depends on the target:
- ARM Cortex-M, RISC-V, MIPS32, LoongArch64, Xtensa, TriCore, SPARC, PowerPC, RX, ColdFire, AVR, and AArch64 ELF. The block is read by the assembler
that reads a
.sfile, so it holds the target's own instructions; see On Cortex-M, RISC-V, MIPS32 and AVR. On AArch64 they are the instructions of its inline asm vocabulary, which has noldp,stpor arithmetic yet. - x86-64, and AArch64 into Mach-O. The block is read by a small fixed vocabulary
of directives, data and (on x86-64) four instructions, described in the
rest of this section.
#,;and/*start a comment that runs to the end of the line. Registers are written with a single%.
The assembled bytes of every block are placed in .text after the
unit's functions. On x86-64 and Apple arm64 each block starts on a
16-byte boundary; elsewhere on the alignment its own .align asks for,
and at least the instruction size.
Directives#
| Directive | Effect |
|---|---|
.global NAME, .globl NAME |
makes the label NAME, defined in the same block, a global symbol |
.weak NAME |
makes the label NAME a weak global symbol |
.type NAME, %function (or @function) |
makes NAME's symbol a function; on Cortex-M its value has bit 0 set, as a Thumb function's must |
.type NAME, %object (or @object) |
makes NAME's symbol an object |
.thumb_func |
Cortex-M only: the next label is a Thumb function, as with .type NAME, %function |
.byte V, ... |
1-byte values |
.long V, ... |
4-byte values; on a target with 4-byte addresses (Cortex-M, RV32), a symbol name instead of a number emits an absolute 32-bit relocation |
.quad V, ... |
8-byte values; on a target with 8-byte addresses, a symbol name instead of a number emits an absolute 64-bit relocation |
.align N, .balign N, .p2align N |
pads to the boundary with the target's no-op instruction; .align counts bytes on x86-64 and is a power of two elsewhere, as in GNU as |
.text, .section .text..., .pushsection .text..., .popsection, .previous, .local, .size |
accepted; no effect beyond what is above |
.file, .loc, .cfi_startproc, .cfi_endproc, .cfi_def_cfa, .cfi_def_cfa_offset, .cfi_def_cfa_register, .cfi_offset, .cfi_restore, .cfi_sections |
accepted, no effect |
A global label without .type is a function symbol, except on Cortex-M,
where it is untyped and its value has no Thumb bit, as with GNU as. Values
are decimal, hexadecimal (0x) or octal (leading 0). Any other
directive is refused:
file-scope asm directive not supported: ".word". EmbCC's assembler emits data with .byte/.long/.quad; an unknown directive would contribute no bytes and leave the label pointing at whatever came next
These are refused because they would not do what they say:
| Refused | Diagnostic |
|---|---|
Any section other than .text |
file-scope asm section ".data" is not supported: EmbCC places every byte of a block in .text, where this data would not be writable; define it in C |
| Alignment beyond 16 bytes | file-scope asm ".balign 32": the alignment must be a power of two of at most 16 bytes, which is what a block starts on |
.hidden |
file-scope asm .hidden is not supported: the symbol would be emitted with default visibility |
| A symbol in data of another width | asm data naming a symbol must be the size of an address, 8 bytes here (.quad): ".long main" |
.type other than function or object |
file-scope asm ".type f, %gnu_indirect_function": the type must be function or object |
A .global or .weak that names no label in its block is refused with
asm .global names "ghost", which has no label.
Labels that no .global names are local to the block and produce no
symbol.
Instructions#
On x86-64 four instructions are accepted:
| Instruction | Encoding |
|---|---|
and $IMM, %REG |
64-bit AND with a sign-extended 8-bit immediate (-128 to 127); REG is a 64-bit name |
call SYMBOL |
call rel32 with a relocation; SYMBOL may be any function |
jmp LABEL |
jmp rel32 to a label in the same block (1b, 1f, or a name) |
ret |
ret |
Anything else is refused:
file-scope asm instruction not supported: "push %rbx" (EmbCC assembles .global/labels/.byte/.long/.quad and and/call/jmp/ret)
A jmp to a name outside the block is refused with
file-scope asm jump target "nowhere" is not a local label. A function
that a block calls with call counts as used, so a static function
reached only from file-scope asm is still emitted.
__asm__(".global _start\n"
"_start:\n"
" and $-16, %rsp\n"
" call start_c\n"
"1: jmp 1b\n");
Per target#
| Target | What a file-scope block may contain |
|---|---|
x86-64 ELF (x86_64-elf, x86_64-emblink, x86_64-linux-gnu) |
everything above |
| ARM Cortex-M, RISC-V, MIPS32, LoongArch64, Xtensa, TriCore, SPARC, PowerPC, RX, ColdFire, AVR | the target's instructions and the GNU assembler's directives; see On Cortex-M, RISC-V, MIPS32 and AVR |
| AArch64 ELF | the instructions of the AArch64 inline-asm vocabulary, labels and the GNU assembler's directives |
x86_64-apple-darwin |
a block with a label or a symbol reference is refused (below) |
aarch64-apple-darwin |
directives and data only, and a block with a label or a symbol reference is refused (below) |
x86_64-windows-gnu |
a block with a label or a symbol reference is refused (below) |
| C++ (any target) | refused: file-scope asm in C++ is not supported yet |
On Apple arm64, an instruction in a file-scope block is refused, whatever its mnemonic, because the Mach-O writer takes no relocation type from an assembler:
file-scope asm instruction "ret": EmbCC assembles a block's instructions on x86-64 (a few) and on every ELF target, not yet into Mach-O objects. On this target write the block as .byte/.long data (see lib/libc/src/setjmp).
A block written as data (.byte, .long, .quad, labels and the
directives above) assembles there.
On Darwin and Windows targets the bytes of a block would be emitted without its symbols and relocations, so a block with any label or symbol reference is refused:
a file-scope asm block with labels or symbol references is not supported for a Darwin target yet: its bytes would be emitted but its symbols and relocations dropped
a file-scope asm block with labels or symbol references is not supported for a Windows target yet
On Cortex-M, RISC-V, MIPS32 and AVR#
On these targets a block is assembled by EmbCC's GNU-syntax assembler,
the one that assembles a .s or .S file (see
embas). A block may hold the
target's instructions, labels (named, .L local and numeric 1:/1b),
literal pools (ldr rd, =sym with .ltorg on ARM), the assembler's
expressions and macros, and references to C functions and objects:
/* sum_table(n): the first n words of a table, on a Cortex-M */
__asm__(".text\n"
".global sum_table\n"
".type sum_table, %function\n"
".thumb_func\n"
"sum_table:\n"
" ldr r1, .Ladr\n"
" movs r2, #0\n"
"1: cbz r0, 2f\n"
" ldr r3, [r1], #4\n"
" add r2, r2, r3\n"
" subs r0, #1\n"
" b 1b\n"
"2: mov r0, r2\n"
" bx lr\n"
" .p2align 2\n"
".Ladr: .word .Ldata\n"
".Ldata: .word 10, 20, 30\n");
The block's bytes go into the unit's .text, aligned as the block's own
alignment directives ask and at least to 4 bytes (2 on AVR); .text
itself is aligned as strictly, so a .balign 256 vector table (an
ESP32-C3's mtvec ignores the low eight bits) is on a 256-byte boundary
in the linked image, not only in the object. Its symbols:
- A label named by
.globalor.weakis a global symbol, typed by.typeor.thumb_func, with the size.sizegives. A Thumb function has bit 0 set. - A label that is not global, with the name of a function or object the
C code declares and does not define, is a local symbol, and the C
code's calls and references go to it. That is how a
staticfunction written in assembly is called from C. - Other labels produce no symbol.
A reference to a label the block itself defines and does not make global
reaches that label, whatever C or another block calls by the same name:
two naked functions may each have a loop:, and a block's own cfunc:
is not the C function cfunc.
A call or a data word naming a C function or object is a relocation
against it, and a function called only from a block is still emitted.
A data word naming a local label (.word .Ldata) is a relocation
against .text. The block's data (its literal pools and data words) is
marked with $d mapping symbols on ARM, so disassemblers show it as
data.
These are refused by name:
| Refused | Diagnostic |
|---|---|
Bytes in any section but .text |
assembly in a C file that switches to section .data is not supported yet: a block's bytes go in .text |
A relocation outside .text |
a relocation outside .text in an asm block |
.set naming something the block does not define |
'a' is set to 'b', which this block does not define |
Everything the assembler itself refuses (an instruction it does not encode, an out-of-range branch) is refused with the file and line of the block.
Naked functions#
void xPortPendSVHandler(void) __attribute__((naked));
A naked function has no prologue and no epilogue: its body is the asm in
it, which must return by itself. It is how a Cortex-M RTOS writes its
context switch, which runs on a task's stack and saves registers the
code generator's frame would otherwise own. EmbCC supports naked on
every target but x86-64 and Apple arm64, and does what GCC does: the function's
body is assembled where the function would be, starting with its label.
It is assembled as a file-scope block, so
it may hold labels, literal pools and calls into C.
The attribute may be on any declaration of the function; FreeRTOS's ports put it on the prototype only.
static void prvPortStartFirstTask(void) __attribute__((naked));
static void prvPortStartFirstTask(void)
{
__asm volatile(" ldr r0, =0xE000ED08 \n"
" ldr r0, [r0] \n"
" ldr r0, [r0] \n"
" msr msp, r0 \n"
" cpsie i \n"
" svc 0 \n"
" .ltorg \n");
}
The body may contain:
- asm statements. Basic asm is assembled as written. Extended asm
may have input operands that are constants (
"i"or"n"), written into the template as numbers:%0,%c0and%[name]all give80for"i"(80).%%is%. - calls with no arguments,
vTaskSwitchContext();, assembled as the target's call instruction (blon ARM,jalon MIPS32,callon RISC-V and AVR). AVR's FreeRTOS port calls the scheduler this way from its naked yield.
A naked function goes where a function would: into .text, into the
section its section attribute names (.ramfunc, AVR's .init3), or,
under --sections=functions, into a .text.NAME of its own, for
--gc-sections to keep only when something refers to it.
A naked function's parameters arrive in the registers the calling
convention puts them in, for the asm to read. There is no warning for an
unused parameter, and no error for a non-void function without a
return: the asm leaves the value in the return register.
These are refused, because a naked function has no frame for them:
| Refused | Diagnostic |
|---|---|
| any other statement | naked function 'f' holds a statement that is not an asm or a call with no arguments; with no prologue there is no frame for it to run in |
| an operand that is not a constant | operand 0 of the asm in naked function 'f' is not a constant ("i"); a naked function has no frame to load one from |
| an output operand | the asm in naked function 'f' has an output; a naked function has no frame to put it in |
| x86-64 and Apple arm64 | __attribute__((naked)) is not supported: on this target the body could only be assembled by the file-scope assembler's few instructions; it is supported on every target but x86-64 and AArch64 for Darwin or Windows |
tests/golden/freertos-cm3.sh builds the FreeRTOS kernel and its GCC
ARM_CM3 port, unmodified, and runs three tasks, a queue, a mutex and a
software timer on QEMU's lm3s6965evb.
x86-64#
This section applies to every x86-64 triple, including
x86_64-apple-darwin and x86_64-windows-gnu.
Constraints#
| Letter | Meaning |
|---|---|
a |
rax |
b |
rbx |
c |
rcx |
d |
rdx |
S |
rsi |
D |
rdi |
r, q, g, R |
a general register chosen by EmbCC |
i, n |
the constant, computed into a general register chosen by EmbCC |
m |
a general register chosen by EmbCC, holding the address of the operand (inputs; see Input operands) |
x |
an SSE register, xmm0 to xmm7 |
=, +, & |
see Output operands |
Precedence: a fixed letter (a, b, c, d, S, D) anywhere in the
string wins; then a register variable; then any of
r, q, g, m, R, i, n; then x. So "Nd" means rdx and
"rm" means a register holding an address.
q, g and R do not have their GCC meanings: each is any register of
the pool, including r8 to r11. g never means memory or an
immediate.
A chosen register comes from rax, rcx, rdx, rbx, rsi, rdi,
r8, r9, r10, r11, in that order, skipping registers fixed by
another operand, listed as clobbers, or named anywhere in the template
(%%rsi). When none is left EmbCC reports
asm: out of registers for the operands, and for x operands
asm: out of xmm registers for the operands.
Any other letter is refused:
asm constraint "=t" is not supported (EmbCC handles a/b/c/d/S/D, 'r'/'q'/'g'/'m', 'x', and a register-asm variable)
This includes A, I, J, K, L, M, N alone, e, Z, t,
u, f, y, Y and 0 to 9.
Flag outputs. __GCC_ASM_FLAG_OUTPUTS__ is defined, but flag-output
constraints are not implemented and are not refused: in "=@ccc",
"=@ccz" and every other =@cc spelling, EmbCC sees the fixed letter
c, and the output receives the contents of rcx. Compute the condition
in the template instead, for example with setc into an r output.
Operands#
An operand reference names the whole register; there are no size
modifiers. The width of an operation comes from the mnemonic (addl
operates on 32 bits, addq and add on 64), and setc writes the low
byte of its operand's register. Hard registers are written %%name.
Memory operands have the form DISP(BASE), where BASE is %N,
%[NAME] or a 64-bit %%register, and DISP is an optional signed
decimal or 0x number. There is no index register, no scale, and no
symbol. (%N) with a base of rsp or rbp is refused by invlpg and
movdqa with asm: memory base %rsp/%rbp unsupported.
Statements are separated by newlines or ;. A template has no comment
syntax: # is read as a mnemonic and refused. Mnemonics are lower case.
Instructions#
No operands (any operand text after the mnemonic is ignored):
cli, sti, hlt, nop, pause, cpuid, rdtsc, rdmsr,
wrmsr, syscall, mfence, lfence, sfence, wbinvd, fninit,
pushfq, popfq, iretq, lretq, stac, clac.
The registers these instructions read and write are fixed by the
hardware; bind them with the fixed-register constraints (a, b, c,
d, S, D) or register variables.
| Instruction | Operands |
|---|---|
int $N |
vector 0 to 255, written as a literal |
inb, inw, inl |
always in %dx, %al / %ax / %eax; the operand text is ignored |
outb, outw, outl |
always out %al / %ax / %eax, %dx; the operand text is ignored |
rdrand %N, rdseed %N |
64-bit register; numbered form only |
setc %N |
low byte of the register; numbered form only |
sqrtsd %N, %M, sqrtss %N, %M |
both operands x; numbered forms only |
push, pushq |
%N, %[NAME], a 64-bit %%register, or $IMM (32-bit, sign-extended) |
pop, popq |
%N, %[NAME] or a 64-bit %%register |
mov, movq, movabs |
see below |
add, or, and, sub, xor, cmp, with suffix q, l or none |
SRC, DST or $IMM, DST; registers only; any register name width is accepted, the suffix sets the operation size |
leaq Nf(%%rip), %%REG |
address of local label N: defined in the same template (Nb is accepted too); REG is a 64-bit name |
str %N, ltr %N |
task register |
lgdt %N, lidt %N |
the register holds the address of the descriptor-table pointer |
stmxcsr %N, ldmxcsr %N |
the register holds the address of the 4-byte MXCSR image |
invlpg (%N) |
the register holds the address |
movdqa (%N), %%xmm0, movdqa %%xmm0, (%N) |
xmm0 only |
N: |
defines local label N, used only by leaq |
mov, movq and movabs always operate on 64 bits, except the two
16-bit segment forms:
| Form | Encoding |
|---|---|
mov SRC, DST, both registers (%N, %[NAME], 64-bit %%register) |
64-bit register move |
mov $IMM, DST |
sign-extended 32-bit immediate when it fits, else a 64-bit immediate; movabs always uses the 64-bit form |
mov DISP(BASE), DST |
64-bit load |
mov SRC, DISP(BASE) |
64-bit store; $IMM to memory is refused with asm: mov $imm to memory unsupported |
mov %%crN, DST, mov SRC, %%crN |
control register |
mov %%ax, %%SEG |
segment register (es, cs, ss, ds, fs, gs) |
mov $IMM, %%ax |
16-bit immediate |
There is no movl, movb, movw, lea, xchg, lock, test,
jmp, jcc, call, ret, ud2 or int3, and no memory operand for
the arithmetic instructions.
The port I/O instructions ignore their operands: inb $0x60, %%al is
encoded as inb %dx, %al. Use the "a" and "Nd" (or "d")
constraints so that the value and the port are in al and dx:
static inline unsigned char inb(unsigned short port)
{
unsigned char v;
__asm__ volatile("inb %1, %0" : "=a"(v) : "Nd"(port));
return v;
}
static inline void outb(unsigned short port, unsigned char v)
{
__asm__ volatile("outb %0, %1" : : "a"(v), "Nd"(port));
}
A system call with six arguments:
static inline long syscall6(long n, long a1, long a2, long a3,
long a4, long a5, long a6)
{
long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
register long r9 __asm__("r9") = a6;
__asm__ volatile("syscall"
: "=a"(ret)
: "a"(n), "D"(a1), "S"(a2), "d"(a3),
"r"(r10), "r"(r8), "r"(r9)
: "rcx", "r11", "memory");
return ret;
}
Callee-saved registers on x86-64#
From --opt=basic the prologue saves rbx and r12 to r15 when an
operand is placed in one of them, whether by a constraint ("=b" for
cpuid), a register variable, or the allocator (the fourth r operand
gets rbx when rax, rcx and rdx are taken). At --opt=none nothing is
saved.
A callee-saved register that the template writes by name or that the
clobber list names is not saved at any level, and EmbCC gives no
diagnostic. On x86_64-windows-gnu, where rsi and rdi are also
callee-saved, an operand placed in either is not saved. In these cases
the template must preserve the register itself.
AArch64#
This section applies to every AArch64 triple, including
aarch64-apple-darwin.
Constraints#
| Letter | Meaning |
|---|---|
r, g |
a general register chosen by EmbCC |
i, n |
an integer constant expression, substituted into the template as a decimal literal |
=, +, & |
see Output operands |
Precedence: a register variable wins; then, if the
string contains i or n but neither r nor g, the operand is an
immediate; then r or g makes it a register. "ri" is therefore a
register. An m in a string that also contains r or g passes the
address (see Input operands).
An immediate whose expression is not a constant, and any constraint with
none of these letters (m, w, x, Q, I, K, 0, ...), is
refused when the statement is generated:
asm constraint "m" is not valid for aarch64
An output cannot be an immediate (an asm output cannot be an immediate).
A chosen register comes from x9, x10, x11, x13, x14, x15,
then x0 to x8, skipping registers bound to another operand, listed
as clobbers, or named in the template. When none is left EmbCC reports
no free register for an asm operand.
Flag outputs. __GCC_ASM_FLAG_OUTPUTS__ is defined, but flag-output
constraints are not implemented. Most spellings are refused (for example
asm constraint "=@cceq" is not valid for aarch64), but "=@ccge" and
"=@ccgt" contain g and are accepted as ordinary register outputs:
the output receives a register's contents, not the condition.
Modifiers#
%N prints a register operand as wN when its type is 4 bytes or
smaller and as xN otherwise. %wN and %xN (and %w[NAME],
%x[NAME]) force the 32-bit or 64-bit name. An immediate prints as its
value; a modifier on an immediate is refused with
a %w modifier on an immediate asm operand makes no sense.
Instructions that need an X register reject a 4-byte operand printed as
w: write %x0, or give the operand a 64-bit type.
Template syntax#
GNU AArch64 syntax. Statements are separated by ; or newlines; //
starts a comment. Mnemonics, system-register names and barrier options
are case-insensitive. Immediates may be written with or without #, in
decimal or 0x hexadecimal. A branch target is a byte displacement from
the start of the instruction, written .+N, .-N or as a bare number;
labels are refused with
labels in aarch64 inline asm are not supported yet (in "1: nop").
Instructions#
| Instruction | Operands |
|---|---|
nop, yield, wfe, wfi, sev, sevl |
none |
dsb OPT, dmb OPT |
sy, st, ld, ish, ishst, ishld, nsh, nshst, nshld, osh, oshst, oshld |
isb, isb sy |
none, or sy |
mrs Xt, SYSREG |
X register only |
msr SYSREG, Xt |
X register only |
msr daifset, #IMM, msr daifclr, #IMM |
immediate mask |
tlbi OP[, Xt] |
vmalle1, vmalle1is (no register); vae1, vae1is, aside1, aside1is, vaae1, vaae1is, vale1, vale1is, vaale1, vaale1is (with a register) |
dc OP, Xt |
ivac, isw, csw, cisw, zva, cvac, cvau, cvap, civac |
ic OP[, Xt] |
ialluis, iallu (no register); ivau (with a register) |
brk, hvc, smc, svc #IMM |
0 to 0xffff |
ldr, str Rt, [Xn{, #OFF}] |
Rt a W, X or Q register; base an X register or sp; OFF unsigned and a multiple of the access size (4, 8, or 16 for Q) |
add, sub Rd, Rn, Rm or Rd, Rn, #IMM |
|
and, orr, eor Rd, Rn, Rm |
registers only |
mov Rd, Rm or mov Rd, #IMM |
any 64-bit immediate |
cmp Rn, Rm or cmp Rn, #IMM |
|
ret, br Xn, blr Xn |
|
b, bl, b.COND OFFSET |
COND one of eq, ne, cs, hs, cc, lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le; offset a multiple of 4 |
cbz, cbnz Rt, OFFSET |
offset a multiple of 4 |
.inst V[, V...] |
32-bit words |
SYSREG is one of nzcv, daif, fpcr, fpsr, currentel, pan,
spsel, sp_el0, spsr_el1, elr_el1, esr_el1, far_el1,
par_el1, midr_el1, mpidr_el1, id_aa64pfr0_el1,
id_aa64pfr1_el1, id_aa64isar0_el1, id_aa64isar1_el1,
id_aa64mmfr0_el1, id_aa64mmfr1_el1, id_aa64mmfr2_el1,
sctlr_el1, cpacr_el1, ttbr0_el1, ttbr1_el1, tcr_el1,
mair_el1, vbar_el1, contextidr_el1, tpidr_el1, tpidr_el0,
tpidrro_el0, cntfrq_el0, cntpct_el0, cntvct_el0,
cntp_tval_el0, cntp_ctl_el0, cntp_cval_el0, cntv_tval_el0,
cntv_ctl_el0, cntv_cval_el0, cntkctl_el1, or the generic form
S<op0>_<op1>_C<n>_C<m>_<op2> (for example s3_0_c12_c12_5).
There are no exclusive or acquire/release loads and stores, no ldrb,
ldrh, ldp or stp, no pre- or post-indexed addressing, no
conditional select, no eret, and no floating-point or SIMD instruction
other than ldr/str of a Q register. Use .inst with the encoding for
anything else.
Other assembler diagnostics name the statement, for example
'mrs' needs an X register, got 'w0' (in "mrs w0") and
offset must be a multiple of 8 in the unsigned range (in "ldr x0").
Callee-saved registers on AArch64#
A template that names x19 to x30 (including x29 and x30) is refused:
aarch64 asm names callee-saved register 'x19', which EmbCC does not save around an asm
and the same registers in the clobber list with
aarch64 asm clobbers callee-saved register 'x20', which EmbCC does not save around an asm.
Example#
static inline unsigned long read_cntvct(void)
{
unsigned long v;
__asm__ volatile("isb; mrs %0, cntvct_el0" : "=r"(v));
return v;
}
static long psci_version(void)
{
register long x0 __asm__("x0") = 0x84000000; /* PSCI_VERSION */
register long x1 __asm__("x1") = 0;
register long x2 __asm__("x2") = 0;
register long x3 __asm__("x3") = 0;
__asm__ volatile("hvc #0" : "+r"(x0) : "r"(x1), "r"(x2), "r"(x3)
: "memory");
return x0;
}
int second(const int *p)
{
int w;
__asm__("ldr %w0, [%1, #4]" : "=r"(w) : "r"(p));
return w;
}
ARM Cortex-M#
This section applies to every Cortex-M triple: thumbv6m-none-eabi,
thumbv8m.base-none-eabi, thumbv7m-none-eabi, thumbv7em-none-eabi,
thumbv7em-none-eabihf, thumbv8m.main-none-eabi and
thumbv8m.main-none-eabihf. All of them use the same assembler; at the
Thumb-1 levels (ARMv6-M and ARMv8-M Baseline) it refuses what the core
does not have, and the ARMv8-M levels add the instructions in
ARMv8-M security and acquire/release instructions.
Constraints#
| Letter | Meaning |
|---|---|
r, q, g, R |
a general register chosen by EmbCC |
i, n, I, J, K, L, M |
a constant, written into the template as a number (ssat %0, %1, %2 with "I"(8) reads ssat r0, 8, r1); refused when the operand is not a constant |
m |
a general register chosen by EmbCC, holding the address of the operand (inputs) |
0, 1, ... (inputs) |
the output of that number: the pair is taken as one +r operand, as CMSIS's __SMLALD writes its accumulator, through a temporary when the input is another value (see Constraint strings) |
=, +, & |
see Output operands |
Constraints are read by the same rules as on x86-64. As a result the
x86-64 letters a, b, c, d, S and D are accepted and select
r0, r3, r1, r2, r6 and r7 respectively; r6 and r7 are callee-saved, so a
function that uses S or D saves them and keeps no other value there
(and D is refused in a function with a variable-length array, whose
frame r7 addresses). Do not use these letters. x is accepted and produces a
template that does not assemble. ARM letters such as l, h, Q, t
and w, alone, are refused:
asm constraint "=l" is not supported (EmbCC handles a/b/c/d/S/D, 'r'/'q'/'g'/'m', 'x', and a register-asm variable)
A combination with r, such as "Ir", is a register.
A chosen register comes from r0, r1, r2, r3 and r12, skipping registers
listed as clobbers or named in the template. When none is left EmbCC
reports no free register for an asm operand. One of these five
registers must also stay free for EmbCC to store the outputs; otherwise:
the ARMv7-M backend cannot lower an asm with no scratch register left around it yet (function f)
An output wider than 4 bytes is refused:
the ARMv7-M backend cannot lower an asm output wider than a register yet (function f)
Modifiers#
None. %N prints the register name (r0 to r12, sp, lr, pc).
Any modifier is refused with
asm template modifier '%w' is not supported for ARMv7-M.
Template syntax#
GNU ARM unified syntax. Statements are separated by ; or newlines; @
and // start a comment. Mnemonics are lower case and case-sensitive. A
.w or .n suffix is accepted and ignored: EmbCC chooses the 16-bit or
32-bit encoding itself. Registers are r0 to r15, sp, lr, pc,
ip (r12) and fp (r11). A branch target is a byte displacement from
the start of the instruction, written .+N, .-N or as a bare number;
every branch uses the 32-bit encoding. Labels are not accepted (1: is
reported as an unknown instruction).
An immediate (#...), including a memory operand's offset, may be a
constant expression, as in GNU as: C's integer literals (decimal, 0x,
0b, octal, with u/l suffixes) and operators (~ * / % + - << >> & ^ |) with parentheses. It runs to the next comma, so spaces are allowed:
FreeRTOS's Cortex-M4F port enables the FPU with
orr r1, r1, #( 0xf << 20 ). A symbol in an immediate is not evaluated
and is refused, as is a value the instruction cannot encode.
Instructions#
| Instruction | Operands |
|---|---|
nop, yield, wfe, wfi, sev |
none |
dsb, dmb, isb |
none, or sy |
cpsid, cpsie |
i, f or if |
mrs Rd, SPECREG, msr SPECREG, Rn |
apsr, iapsr, eapsr, xpsr, ipsr, epsr, iepsr, msp, psp, primask, basepri, basepri_max, faultmask, control (case-insensitive) |
bkpt #IMM |
0 to 255 |
bx Rm, blx Rm |
|
mov, movs Rd, Rm or Rd, #IMM |
any 32-bit immediate |
movw, movt Rd, #IMM |
0 to 0xffff |
mvn, mvns Rd, Rm |
|
clz, rbit, rev, rev16, revsh Rd, Rm |
rev16/revsh swap the bytes of each halfword / of the low one, sign-extended (CMSIS's __REV16, __REVSH) |
rrx, rrxs Rd, Rm |
rotate right one bit through the carry |
cmp, tst Rn, Rm or Rn, #IMM |
|
add, adds, sub, subs, and, ands, orr, orrs, eor, eors, bic, bics, adc, adcs, sbc, sbcs, rsb, rsbs |
Rd, Rn, Rm or Rd, Rn, #IMM (three operands) |
lsl, lsls, lsr, lsrs, asr, asrs, ror, rors |
Rd, Rn, Rm or Rd, Rn, #0..31 |
mul Rd, Rn, Rm, udiv, sdiv |
|
mla, mls Rd, Rn, Rm, Ra |
Ra + Rn*Rm, Ra - Rn*Rm |
smull, umull, smlal, umlal RdLo, RdHi, Rn, Rm |
the 64-bit product (accumulated); RdLo not RdHi. umaal is the DSP extension's (below) |
bfi Rd, Rn, #LSB, #WIDTH, bfc Rd, #LSB, #WIDTH |
insert Rn's low bits into / clear a field of Rd; LSB 0 to 31, WIDTH 1 to 32-LSB |
ubfx, sbfx Rd, Rn, #LSB, #WIDTH |
extract a field, zero- or sign-extended |
ldrd, strd Rt, Rt2, [Rn{, #OFF}], [Rn, #OFF]! or [Rn], #OFF |
OFF a multiple of 4, -1020 to 1020; Rt and Rt2 not sp or pc (and different for a load); with writeback Rn neither pc nor Rt nor Rt2; ldrd Rt, Rt2, [pc, #OFF] is a literal |
ldr, ldrb, ldrsb, ldrh, ldrsh, str, strb, strh |
Rt, [Rn] or Rt, [Rn, #OFF]; with writeback, Rt, [Rn, #OFF]! (pre-indexed) or Rt, [Rn], #OFF (post-indexed), OFF -255 to 255 and Rn neither pc nor Rt |
ldr Rt, [pc, #OFF] |
a literal, OFF from the word-aligned pc, -4095 to 4095 |
ldr Rt, =IMM |
any 32-bit constant, assembled as movw and movt |
ldrex Rt, [Rn{, #OFF}] |
OFF a multiple of 4, 0 to 1020 |
strex Rd, Rt, [Rn{, #OFF}] |
OFF a multiple of 4, 0 to 1020 |
ldrexb, ldrexh Rt, [Rn]; strexb, strexh Rd, Rt, [Rn] |
Rd neither Rt nor Rn |
clrex |
none |
msr apsr_nzcvq, Rn, msr apsr_nzcvqg, Rn |
the flags; apsr_nzcvqg also the DSP extension's GE bits (not on ARMv6-M or ARMv8-M Baseline) |
ssat Rd, #1..32, Rn, usat Rd, #0..31, Rn |
an optional lsl #0..31 or asr #1..31 (not on ARMv6-M or ARMv8-M Baseline) |
sxtb, sxth, uxtb, uxth Rd, Rm |
an optional ror #8, #16 or #24 (not on ARMv6-M or ARMv8-M Baseline, where only the 16-bit form with r0-r7 exists) |
| The DSP extension's | see The DSP extension |
ARMv8-M only: lda, ldab, ldah, ldaex, ldaexb, ldaexh, stl, stlb, stlh, stlex, stlexb, stlexh, tt, ttt, tta, ttat, sg, bxns, blxns, and on Mainline vlstm, vlldm |
see ARMv8-M security and acquire/release instructions |
b, bl, beq, bne, bcs, bhs, bcc, blo, bmi, bpl, bvs, bvc, bhi, bls, bge, blt, bgt, ble |
OFFSET (even) |
cbz, cbnz |
Rn, OFFSET: Rn r0 to r7, OFFSET 4 to 130, forward |
push, pop |
a register list, {r4-r7, lr} or without braces, r4, lr |
ldm, ldmia, ldmfd, ldmdb, ldmea, stm, stmia, stmea, stmdb, stmfd |
Rn{!}, {LIST}: two or more registers, never sp; no pc in a store; not pc and lr together in a load; not Rn with writeback |
svc #IMM |
0 to 255 |
it, itt, ite, ... (up to four instructions) |
a condition; each instruction in the block carries its condition suffix (moveq) |
vmov Sn, Rt, vmov Rt, Sn |
|
vldm, vldmia, vldmdb, vstm, vstmia, vstmdb |
Rn{!}, {Sm-Sn}: consecutive single-precision registers; db needs writeback |
vpush, vpop |
{Sm-Sn} |
Mnemonics, register names and conditions are case-insensitive, as in
GNU as (MRS %0, primask). The two-operand forms (adds r0, #1,
lsls r2, #2) are the three-operand ones with the destination repeated.
Loads and stores take [Rn, Rm] and [Rn, Rm, lsl #N] (N 0 to 3) too.
The barriers take sy or its number 0xF; on ARMv8-M, mrs/msr also
name msplim, psplim and the TrustZone _ns registers (see
ARMv8-M stack limits).
Points that differ from the GNU assembler (in inline asm; a .s/.S
file is assembled as GNU as does it, see embas):
ldm,stm, the branches andldr Rt, [pc, #OFF]always use the 32-bit encoding, andldr Rt, =IMMismovw/movtrather than a load from a literal pool.- Inside an IT block, a flag-setting instruction (
adds,movs, ...) is refused, because its 16-bit encoding sets no flags there, and so is a branch that is not the block's last instruction. A block left open at the end of an asm statement is refused, since it would make the compiler's next instructions conditional. - A barrier option other than
syis refused withonly the `sy` barrier option is supported; "ish" is not.
There is no floating-point instruction beyond vmov, vldm/vstm and
vpush/vpop (vmrs, vmsr, vldr, ...), and no doubleword exclusive
(ldrexd/strexd), which no Cortex-M has: they are refused by name
(ldrexd is not an M-profile instruction: no Cortex-M core has a doubleword exclusive).
On ARMv6-M (thumbv6m-none-eabi) and ARMv8-M Baseline
(thumbv8m.base-none-eabi), every 32-bit encoding a statement produces is
checked against what the core has, in inline asm and in a .s file
alike: an instruction outside the set -- ldr.w, an add whose
immediate needs the 32-bit form, it -- is refused by name rather than
assembled into a HardFault, and so are the Main Extension's special
registers (basepri, basepri_max, faultmask and their _ns forms):
"add.w r0, r1, #4096" is not an ARMv8-M Baseline instruction: it encodes as a 32-bit Thumb-2 form that core does not have
"basepri" is not a special register of ARMv8-M Baseline: it is the Main Extension's
So mla, the long multiplies, rrx, the bit fields, ldrd/strd, and
rev16/revsh of a high register are refused there, as llvm-mc refuses
them; rev16 and revsh of r0-r7 are the 16-bit forms every core
has. ARMv6-M has bl, mrs, msr and the barriers; ARMv8-M Baseline adds
b.w, movw, movt, sdiv, udiv, the exclusives (ldrex/strex
with an offset, the byte and halfword forms, clrex), the
acquire/release family, tt/ttt/tta/ttat and sg, and the 16-bit
cbz, cbnz, bxns and blxns (see
Targets).
The DSP extension#
ARMv7E-M (thumbv7em-none-eabi[hf], --cpu=cortex-m4 or cortex-m7) and
ARMv8-M Mainline with the extension (--cpu=cortex-m33,
--isa=armv8-m.main+dsp) have the DSP instructions, and so does EmbCC's
assembler for them, in inline asm and in .s files. They are what
CMSIS's cmsis_gcc.h (__SADD16 ... __SMLALD, selected by
__ARM_FEATURE_DSP) and EmbCC's <arm_acle.h> (__sadd16, __smlad,
__ssat, ...: ACLE's DSP and SIMD32 intrinsics, under clang's names) are
made of:
| Instructions | Operands |
|---|---|
sadd16, sasx, ssax, ssub16, sadd8, ssub8, and the same with q, sh, u, uq and uh in place of s (qadd16, shsub8, uasx, uqsub16, uhadd8, ...) |
Rd, Rn, Rm |
qadd, qsub, qdadd, qdsub |
Rd, Rm, Rn: qsub rd, rm, rn is rd = sat(rm - rn) |
sel |
Rd, Rn, Rm, each byte by the GE bits |
usad8; usada8 |
Rd, Rn, Rm; Rd, Rn, Rm, Ra |
smuad, smuadx, smusd, smusdx, smulbb, smulbt, smultb, smultt, smulwb, smulwt, smmul, smmulr |
Rd, Rn, Rm |
smlad, smladx, smlsd, smlsdx, smlabb, smlabt, smlatb, smlatt, smlawb, smlawt, smmla, smmlar, smmls, smmlsr |
Rd, Rn, Rm, Ra |
smlald, smlaldx, smlsld, smlsldx, smlalbb, smlalbt, smlaltb, smlaltt, umaal |
RdLo, RdHi, Rn, Rm, RdLo not RdHi (umaal: RdHi:RdLo = Rn*Rm + RdLo + RdHi) |
ssat16 Rd, #1..16, Rn, usat16 Rd, #0..15, Rn |
|
pkhbt Rd, Rn, Rm{, lsl #0..31}, pkhtb Rd, Rn, Rm{, asr #1..32} |
pkhtb with no shift is pkhbt Rd, Rm, Rn, as GNU as and llvm-mc encode it |
sxtb16, uxtb16 Rd, Rm; sxtab16, uxtab16, sxtab, sxtah, uxtab, uxtah Rd, Rn, Rm |
an optional ror #8, #16 or #24 |
No operand may be sp or pc (in ARM state, pc). The rotation and shift keywords are
case-insensitive and the # is optional, so CMSIS's
"sxtb16 %0, %1, ROR %2" with an "i" operand assembles.
On a part without the extension -- ARMv7-M, thumbv8m.main-none-eabi
alone (as with clang), ARMv6-M, ARMv8-M Baseline -- each is refused by
name, in inline asm and in a .s file alike, as llvm-mc refuses it:
"sadd16" is an instruction of the DSP extension, which ARMv7-M lacks: it is ARMv7E-M's (-mcpu=cortex-m4, cortex-m7, --target=thumbv7em-none-eabi) and ARMv8-M Mainline's with the extension (-mcpu=cortex-m33, -march=armv8-m.main+dsp)
Every ARMv7-A part has them, and so does the ARM-state assembler
(ARM state), where <arm_acle.h> declares them too.
tests/golden/thumb-dsp.sh checks every form against llvm-mc, byte for
byte and by disassembly, compares the refusals core by core, and runs
each instruction on a Cortex-M4 under QEMU against a C model of it;
tests/golden/arm-asm-more.sh does the same for the multiplies, bit
fields and pairs above, and for all of them in ARM state on a
Cortex-A15.
ARM state (ARMv7-A)#
On armv7a-none-eabi[hf] inline asm and .s files take the vocabulary
above, A32-encoded, the DSP extension's instructions included, with these
differences, each as llvm-mc has it for armv7a:
- Any instruction may carry a condition, with no IT block:
moveq r0, #1,sadd16ne r0, r1, r2,ldrdlt r0, r1, [r2]. spmay be an operand of the DSP instructions, the multiplies, the reversals,rrxand the bit fields;pcmay not.ldrd/strdtake an even register and the next one (r0, r1...r12, sp), and an offset of -255 to 255 in every addressing form.ssat/usattakeasr #32.ldrexd Rt, Rt2, [Rn]andstrexd Rd, Rt, Rt2, [Rn]exist (Rteven,Rt2the next;Rdnone of the others).mrs/msrnamecpsr(and its fields), not the M-profile special registers;mrc/mcrreach the system control coprocessor;svc,bkptandudftake A32's wider immediates;cbz,cbnz,tbbandtbhare refused.
A few UNPREDICTABLE forms llvm-mc assembles are refused, in either state:
pc as a multiply's accumulator or a reversal's register, an ldrd
writeback through pc, and a strexd status register that is one of
its other operands.
ARMv8-M security and acquire/release instructions#
On the ARMv8-M triples (thumbv8m.main-none-eabi[hf] and
thumbv8m.base-none-eabi), the security extension's instructions and the
load-acquire/store-release family assemble; on any other level each is
refused by name (tt is an instruction of ARMv8-M's security extension, and this is not an ARMv8-M target, lda is an ARMv8-M instruction (load-acquire and store-release), and this is not an ARMv8-M target).
| Instruction | Operands | Meaning |
|---|---|---|
tt, ttt, tta, ttat |
Rd, Rn (Rd not sp or pc) |
the test target: the MPU, SAU and IDAU attributes of the address in Rn; t as unprivileged code would see them, a as the other security state would (Secure state only) |
sg |
none | the secure gateway: the first instruction of an entry the Non-secure state may branch to |
bxns, blxns |
Rm |
bx/blx that may leave the Secure state, when Rm's bit 0 is clear |
vlstm, vlldm |
Rn (or Rn, {d0-d15}) |
Mainline only: the lazy save and restore of the floating-point context around a call to the Non-secure state |
lda, ldab, ldah |
Rt, [Rn] |
load-acquire, word, byte, halfword |
ldaex, ldaexb, ldaexh |
Rt, [Rn] |
load-acquire exclusive |
stl, stlb, stlh |
Rt, [Rn] |
store-release |
stlex, stlexb, stlexh |
Rd, Rt, [Rn] |
store-release exclusive; Rd 0 when it took |
None of these takes an offset, and sp and pc are refused as their
registers. tasm_vocabulary_v8m in src/arch/thumb/asm.c lists every
form across the registers, and tests/golden/thumbv8mbase-encoding.sh
checks each against llvm-mc (-mattr=+8msecext) byte for byte, at
Baseline and Mainline. A Secure program normally reaches tt through
<arm_cmse.h> (Targets):
static inline unsigned test_target(void *p)
{
unsigned v;
__asm__ volatile("tt %0, %1" : "=r"(v) : "r"(p));
return v; /* bits 7:0 the MPU region, 16 MPU-region-valid, ... */
}
sg, bxns and blxns are what the compiler and the linker emit for
cmse_nonsecure_entry and cmse_nonsecure_call; written by hand, a
bxns to the Non-secure state must first clear every register and flag
that holds a secret, as those do.
ARMv8-M stack limits#
On the ARMv8-M triples, mrs and msr name the stack-limit
registers, and the core enforces them. A push or a stack-pointer
adjustment that would take the stack below its limit is not made, and
takes a UsageFault with CFSR.STKOF (bit 20) set instead. An RTOS sets
PSPLIM to the bottom of each task's stack, so an overflow faults rather
than overwriting whatever lies below.
| Name | Register |
|---|---|
msplim |
the main stack's limit |
psplim |
the process stack's limit |
msplim_ns, psplim_ns |
the Non-secure limits, from Secure code |
static inline void set_psplim(unsigned limit)
{
__asm__ volatile("msr psplim, %0" : : "r"(limit));
}
static inline unsigned get_msplim(void)
{
unsigned v;
__asm__ volatile("mrs %0, msplim" : "=r"(v));
return v;
}
The encodings are clang's. tests/golden/thumbv8m-splim.sh runs a
thread past its PSPLIM on QEMU's Cortex-M33 and checks the UsageFault.
The ARMv7-M triples have no stack-limit registers, and refuse the names:
"msplim" is not an ARMv7-M special register.
Callee-saved registers on ARM Cortex-M#
A template that names r4 to r11 (or fp) is refused:
ARMv7-M asm names callee-saved register 'r4', which EmbCC does not save around an asm
and the same registers in the clobber list with
ARMv7-M asm clobbers callee-saved register 'r5', which EmbCC does not save around an asm.
Example#
static inline unsigned irq_save(void)
{
unsigned primask;
__asm__ volatile("mrs %0, primask\n\tcpsid i" : "=r"(primask) : : "memory");
return primask;
}
static inline void irq_restore(unsigned primask)
{
__asm__ volatile("msr primask, %0" : : "r"(primask) : "memory");
}
static inline int clz32(unsigned x)
{
int r;
__asm__("clz %0, %1" : "=r"(r) : "r"(x));
return r;
}
RISC-V#
This section applies to riscv32-unknown-elf and riscv64-unknown-elf.
Constraints#
| Letter | Meaning |
|---|---|
r, q, g, R |
a general register chosen by EmbCC |
i, n |
the constant, computed into a general register chosen by EmbCC |
m |
a general register chosen by EmbCC, holding the address of the operand (inputs) |
=, +, & |
see Output operands |
Constraints are read by the same rules as on x86-64. As a result the
x86-64 letters a, b, c, d, S and D are accepted and select
x0 (zero), x3 (gp), x1 (ra), x2 (sp), x6 (t1) and x7 (t2)
respectively. Do not use these letters. x is accepted and produces a
template that does not assemble. RISC-V letters such as f, I, J,
K and A, alone, are refused with
asm constraint "=f" is not supported (EmbCC handles a/b/c/d/S/D, 'r'/'q'/'g'/'m', 'x', and a register-asm variable).
Because i gives a register, an instruction with an immediate field
cannot take an i operand: addi %0, %1, %2 with "i"(5) is refused
with addi wants an immediate. Write add %0, %1, %2, or put the
constant in the template text.
A chosen register comes from t0 to t6, then a0 to a7, skipping
registers listed as clobbers or named in the template. When none is left
EmbCC reports no free register for an asm operand. An output wider
than a register (an 8-byte type on RV32) is refused:
the RV32 backend cannot lower an asm output wider than a register yet (function f)
Modifiers#
None. %N prints the ABI name of the register (t0, a0, ...). Any
modifier is refused with
asm template modifier '%z' is not supported for RISC-V.
Template syntax#
GNU RISC-V syntax. Statements are separated by ; or newlines; # and
// start a comment. Mnemonics are lower case and case-sensitive.
Registers are the ABI names (zero, ra, sp, gp, tp, t0-t6,
s0-s11, a0-a7), fp (s0), and x0 to x31; the float
registers are f0 to f31 or ft0-ft11, fs0-fs11, fa0-fa7.
Memory operands are
OFF(REG) or (REG) with a 12-bit signed offset. A branch or jump
target is a byte displacement from the start of the instruction, written
.+N, .-N or as a bare number. Labels are not accepted.
Instructions#
| Instruction | Operands |
|---|---|
nop, ret, ebreak, unimp, ecall, mret, sret, wfi, fence.i |
none |
fence |
none (meaning fence iorw, iorw), or two sets of i, o, r, w |
csrr Rd, CSR |
|
csrw, csrs, csrc CSR, Rs |
|
csrrw, csrrs, csrrc Rd, CSR, Rs |
|
csrwi, csrsi, csrci CSR, IMM |
IMM 0 to 31 |
csrrwi, csrrsi, csrrci Rd, CSR, IMM |
IMM 0 to 31 |
add, sub, and, or, xor, slt, sltu Rd, Rs1, Rs2 |
|
addi, andi, ori, xori, slti, sltiu Rd, Rs1, IMM |
12-bit signed |
sll, srl, sra Rd, Rs1, Rs2; slli, srli, srai Rd, Rs1, SHAMT |
SHAMT 0 to XLEN-1 |
mul, mulh, mulhsu, mulhu, div, divu, rem, remu |
three registers |
lb, lbu, lh, lhu, lw, sb, sh, sw |
Rt, OFF(Rs) |
beq, bne, blt, bge, bltu, bgeu Rs1, Rs2, OFFSET |
even, within 4 KiB |
beqz, bnez, bltz, bgez, blez, bgtz Rs, OFFSET |
even, within 4 KiB |
j OFFSET, jal [Rd,] OFFSET |
even, within 1 MiB |
jr Rs, jalr Rs, jalr Rd, Rs, jalr Rd, OFF(Rs) |
|
lui, auipc Rd, IMM |
20-bit |
li Rd, IMM |
any constant; expanded to the instruction sequence the code generator uses |
mv, not, neg, seqz, snez Rd, Rs |
With the F extension (--isa= naming f, or g), the floating-point
instructions, in their single (.s) form; with D also their double
(.d) form. A rounding mode, where one is shown, is rne, rtz, rdn,
rup, rmm or dyn, and is dyn when left out (rne for the
conversions that are always exact, fcvt.d.s and fcvt.d.w[u], as
llvm-mc writes them).
| Instruction | Operands |
|---|---|
flw, fsw, fld, fsd |
Ft, OFF(Rs) |
fadd, fsub, fmul, fdiv |
Fd, Fs1, Fs2 [, RM] |
fsqrt |
Fd, Fs [, RM] |
fmadd, fmsub, fnmsub, fnmadd |
Fd, Fs1, Fs2, Fs3 [, RM] |
fsgnj, fsgnjn, fsgnjx, fmin, fmax |
Fd, Fs1, Fs2 |
fmv, fneg, fabs |
Fd, Fs |
feq, flt, fle |
Rd, Fs1, Fs2 |
fclass |
Rd, Fs |
fcvt.w, .wu, .l, .lu .s/.d |
Rd, Fs [, RM] (l, lu RV64 only) |
fcvt.s/.d .w, .wu, .l, .lu |
Fd, Rs [, RM] |
fcvt.s.d, fcvt.d.s |
Fd, Fs [, RM] |
fmv.x.w, fmv.w.x (and fmv.x.s, fmv.s.x); fmv.x.d, fmv.d.x (RV64) |
Rd, Fs or Fd, Rs |
frcsr, frrm, frflags |
Rd |
fscsr, fsrm, fsflags |
[Rd,] Rs |
fsrmi, fsflagsi |
[Rd,] IMM, 0 to 31 |
An F instruction on a target without F, or a D one without D, is refused
with fsd needs the D extension, which --isa= does not name. The same
instructions are accepted in .s and .S files. There is no "f"
constraint yet: name the float register in the template, and pass
values through memory or with fmv.w.x/fmv.x.w.
RV64 only: addw, subw, addiw, sllw, srlw, sraw, slliw,
srliw, sraiw, mulw, divw, divuw, remw, remuw, ld, sd,
negw, sext.w, lwu. At RV32 most of them are refused, for example
with ld is an RV64 instruction and this is RV32. Three are not: negw
and sext.w are encoded with their RV64 opcodes, which an RV32 core does
not implement, and lwu is encoded as lw.
CSR is a name from the table below, or a number from 0 to 0xfff.
| Group | CSRs |
|---|---|
| Floating point | fflags, frm, fcsr |
| Machine information | mvendorid, marchid, mimpid, mhartid |
| Machine trap setup | mstatus, misa, medeleg, mideleg, mie, mtvec, mcounteren |
| Machine trap handling | mscratch, mepc, mcause, mtval, mip |
| Memory protection | pmpcfg0, pmpcfg1 (RV32 only), pmpaddr0, pmpaddr1 |
| Supervisor | sstatus, sie, stvec, scounteren, sscratch, sepc, scause, stval, sip, satp |
| Counters | cycle, time, instret, and cycleh, timeh, instreth (RV32 only) |
| Machine counters | mcycle, minstret, mcountinhibit, and mcycleh, minstreth (RV32 only) |
An RV32-only CSR at RV64 is refused with CSR "cycleh" exists only on RV32,
and an unknown name or a number out of range with
"mfoo" is not a CSR this assembler knows.
There are no atomic instructions (lr, sc, amo*), no compressed
instructions written explicitly (c.*), no la, call or tail, no rdcycle/rdtime/rdinstret (use
csrr), and no two-register bgt, ble, bgtu or bleu.
Callee-saved registers on RISC-V#
A template that names s0 to s11 (or fp) is refused:
RISC-V asm names callee-saved register 's1', which EmbCC does not save around an asm
and the same registers in the clobber list with
RISC-V asm clobbers callee-saved register 's2', which EmbCC does not save around an asm.
Example#
static inline unsigned long hart_id(void)
{
unsigned long id;
__asm__ volatile("csrr %0, mhartid" : "=r"(id));
return id;
}
static inline unsigned long irq_save(void)
{
unsigned long m;
__asm__ volatile("csrrci %0, mstatus, 8" : "=r"(m) : : "memory");
return m;
}
static inline void irq_restore(unsigned long m)
{
__asm__ volatile("csrw mstatus, %0" : : "r"(m) : "memory");
}
long sbi_call(long ext, long fid, long arg0)
{
long r;
__asm__ volatile("mv a7, %1\n\tmv a6, %2\n\tmv a0, %3\n\tecall\n\tmv %0, a0"
: "=r"(r) : "r"(ext), "r"(fid), "r"(arg0)
: "a0", "a1", "a6", "a7", "memory");
return r;
}
MIPS32#
This section applies to mipsel-none-elf.
Constraints#
| Letter | Meaning |
|---|---|
r, d, g |
a general register chosen by EmbCC |
m |
a general register chosen by EmbCC, holding the address of the operand; for an =m output the template stores through it |
i, n, I to P |
the constant, written into the template as a decimal number |
=, +, & |
see Output operands |
A constraint with r, d, g or m anywhere gets a register; one with
only the constant letters must be given an integer constant expression.
Anything else, a non-constant i included, is refused with
asm constraint "=a" is not valid for MIPS.
A chosen register comes from t0 to t9, then v0, v1 and a0 to
a3, skipping registers listed as clobbers or named in the template.
When none is left EmbCC reports no free register for an asm operand.
An input wider than a register passes its low word; an output wider than
a register is refused:
the MIPS32 backend cannot lower an asm output wider than a register yet (function f) [asm w=4 size=4]
Modifiers#
None. %N prints the register with its $ ($t0, $a1, ...) or the
constant. Any modifier is refused with
asm template modifier '%z' is not supported for MIPS.
Template syntax#
GNU MIPS syntax. A template starts in .set reorder mode, as GCC's and
clang's do: the assembler puts a nop in the delay slot of every branch
and jump, so the instruction written after a transfer runs after it, not
in its slot. A template that schedules its own delay slots says .set noreorder (and may .set push and .set pop around it); a template
written for GCC with an explicit slot but without .set noreorder gets
an extra nop, as it would from GNU as. The other .set options that
name what EmbCC emits anyway (at, noat, macro, nomacro,
mips32r2, ...) are accepted; .set mips16, micromips, mips32r6
and the 64-bit ISAs are refused. Statements are separated by ; or
newlines; # and // start a comment. Mnemonics are lower case. Registers are $0 to $31 and the
ABI names with $ ($zero, $at, $v0, $v1, $a0-$a3,
$t0-$t9, $s0-$s7, $k0, $k1, $gp, $sp, $fp or $s8,
$ra). Memory operands are OFF(REG) or (REG) with a 16-bit signed
offset; an offset or immediate may be a constant expression ((16 + 4 * 3)($sp)), and %hi(N) and %lo(N) of a number give the halves a
lui/addiu pair adds up to. A branch target is a byte displacement
from the delay slot, a multiple of 4 written as a number (b 8 skips the
slot and the instruction after it), or .+N / .-N from the branch
itself. Labels are not accepted in a template; they are in a
file-scope block and a .S file,
where jal sym, %hi(sym), %lo(sym) and la also take symbols.
Instructions#
| Instruction | Operands |
|---|---|
nop, ssnop, ehb, eret, wait, syscall |
none |
break [CODE] |
0 to 1023 |
sync [STYPE] |
0 to 31 |
di [Rt], ei [Rt] |
|
addu, subu, and, or, xor, nor, slt, sltu, movn, movz, mul Rd, Rs, Rt |
|
sllv, srlv, srav, rotrv Rd, Rt, Rs |
the value, then the amount |
addiu, slti, sltiu Rt, Rs, IMM |
16-bit signed |
andi, ori, xori Rt, Rs, IMM |
0 to 0xffff |
sll, srl, sra, rotr Rd, Rt, SA |
0 to 31 |
lui Rt, IMM |
0 to 65535, or %hi(N) |
lb, lbu, lh, lhu, lw, sb, sh, sw, ll, sc, lwl, lwr, swl, swr |
Rt, OFF(Rs) |
mult, multu Rs, Rt |
|
div, divu |
$zero, Rs, Rt only (below) |
mfhi, mflo, mthi, mtlo Rd |
|
clz, clo, seb, seh, wsbh Rd, Rs |
|
ext, ins Rt, Rs, POS, SIZE |
within the word |
mfc0, mtc0 Rt, $N[, SEL] |
SEL 0 to 7 |
teq Rs, Rt[, CODE] |
0 to 1023 |
beq, bne Rs, Rt, OFFSET; blez, bgtz, bltz, bgez, beqz, bnez Rs, OFFSET; b, bal OFFSET |
a multiple of 4 from -131072 to 131068 |
jr Rs, jalr Rs, jalr Rd, Rs |
Rd and Rs different |
j TARGET, jal TARGET |
a register (j $ra is jr, jal $t9 is jalr); .+N from the jump, encoded as b/bal within 128 KiB; or a number, the target's place in its 256 MiB region |
jal SYM, j SYM, lui Rt, %hi(SYM), addiu Rt, Rs, %lo(SYM), lw Rt, %lo(SYM)(Rs) (and the other loads and stores), la Rt, SYM |
a .S file or a file-scope block only: R_MIPS_26, R_MIPS_HI16, R_MIPS_LO16; SYM may have a +K or -K |
move Rd, Rs |
or Rd, Rs, $zero |
li Rd, IMM |
any 32-bit constant; the code generator's sequence (one or two instructions) |
not Rd, Rs, negu Rd, Rs |
nor Rd, Rs, $zero; subu Rd, $zero, Rs |
div and divu with two registers are assembler macros in GNU as,
which add a divide-by-zero trap, and are refused:
this div is an assembler macro (it adds a divide-by-zero trap); write div $zero, rs, rt and read the quotient with mflo.
neg is not accepted for the same reason (it is the trapping sub);
write negu. An immediate out of its field is refused with, for example,
addiu immediate 70000 does not fit its signed 16-bit field.
%lo is only an addiu's or a load's or store's offset, and %hi
only a lui's operand: %hi is rounded for the sign extension those
instructions give %lo, so ori $t0, $t0, %lo(sym) would compute a
wrong address for half of all symbols, and is refused (%lo(symbol) is an addiu's or a load's or store's offset: %hi is rounded for its sign extension). The operators of position-independent, small-data and TLS
code (%got, %call16, %gp_rel, ...) are refused, and so is a branch
to a symbol the block or file does not define.
There are no floating-point or coprocessor 1 instructions, no trapping
add, addi or sub, no DSP, MIPS16 or microMIPS instructions, no
cache, pref or rdhwr, and no madd or msub.
On the MIPS64 targets (mips64el-none-elf, mips64-none-elf) the same
vocabulary is joined by MIPS64r2's doubleword forms, each encoded as
llvm-mc encodes it (tests/golden/mips64-encoding.sh), and the registers
have their n64 names ($a4-$a7 where o32 has $t0-$t3); .set mips64r2 is accepted. On MIPS32 each is refused as daddu is a MIPS64 instruction.
| Instruction | Operands |
|---|---|
daddu, dsubu Rd, Rs, Rt; dsllv, dsrlv, dsrav, drotrv Rd, Rt, Rs |
|
daddiu Rt, Rs, IMM |
16-bit signed |
dsll, dsrl, dsra, drotr Rd, Rt, SA |
0 to 63 |
dsll32, dsrl32, dsra32, drotr32 Rd, Rt, SA |
0 to 31 (a shift by 32 + SA) |
ld, sd, lwu, lld, scd, ldl, ldr, sdl, sdr |
Rt, OFF(Rs) |
dmult, dmultu Rs, Rt; ddiv, ddivu |
$zero, Rs, Rt only, as div |
dext, dins Rt, Rs, POS, SIZE |
within the doubleword (dextm/dextu, dinsm/dinsu as POS and SIZE need) |
dsbh, dshd, dclz, dclo Rd, Rs |
|
dmfc0, dmtc0 Rt, $N[, SEL] |
SEL 0 to 7 |
dli Rd, IMM |
any 64-bit constant; the code generator's sequence |
Callee-saved registers on MIPS32#
A template that names s0 to s7, gp, fp, ra, k0 or k1 is refused, and so is the same register in the clobber list:
MIPS asm names register '$s1', which EmbCC does not save around an asm
MIPS asm clobbers register 's2', which EmbCC does not save around an asm
$sp may be named, for a template that reads it. $at may be named and
changed: no value is kept in it across an asm.
Example#
static inline unsigned read_status(void) /* CP0 Status */
{
unsigned s;
__asm__ volatile("mfc0 %0, $12" : "=r"(s));
return s;
}
static inline unsigned irq_save(void)
{
unsigned s;
__asm__ volatile("di %0; ehb" : "=r"(s) : : "memory");
return s & 1;
}
static inline unsigned bswap32(unsigned x)
{
unsigned r;
__asm__("wsbh %0, %1; rotr %0, %0, 16" : "=r"(r) : "r"(x));
return r;
}
LoongArch64#
This section applies to loongarch64-unknown-elf.
Constraints#
| Letter | Meaning |
|---|---|
r, g |
a general register chosen by EmbCC |
m |
a general register chosen by EmbCC, holding the address of the operand; it is written into the template as $REG, 0, the base and offset a load or store takes |
i, n, I, J, K |
the constant, written into the template as a decimal number |
=, +, & |
see Output operands |
Anything else, a non-constant i included, is refused with
asm constraint "=a" is not valid for LoongArch. A chosen register comes
from t0 to t8, then a0 to a7, skipping registers listed as
clobbers or named in the template. A register variable must be one of
those (register long x __asm__("a0")); another is refused with
register variable bound to 's3' is not supported for LoongArch asm (use a0-a7 or t0-t8).
Modifiers#
None. %N prints the register with its $ ($t0, $a1, ...) or the
constant; any modifier is refused with asm template modifier '%z' is not supported for LoongArch.
Template syntax#
GNU LoongArch syntax, as llvm-mc reads it. Registers are $r0 to $r31
and the psABI names with $ ($zero, $ra, $tp, $sp, $a0-$a7,
$t0-$t8, $fp or $s9, $s0-$s8). Every operand is written out:
a load or store is ld.w $a0, $a1, 0, and an offset or immediate may be
a constant expression. A branch target is a byte offset from the branch,
a multiple of 4, as a number or .+N / .-N. Labels and symbols are
accepted in a file-scope block and
a .S file, where b/bl sym, call36, tail36, la.pcrel,
la.local, la, la.global and the %pc_hi20/%pc_lo12,
%got_pc_hi20/%got_pc_lo12, %abs_* and %call36 operators take
them. Statements are separated by ; or newlines; # and // start a
comment.
Instructions#
| Instruction | Operands |
|---|---|
nop, ret, ertn |
none |
move Rd, Rj; jr Rj; li.w, li.d Rd, IMM |
li builds any constant, as llvm-mc's does |
add.w/d, sub.w/d, slt, sltu, and, or, xor, nor, andn, orn, sll/srl/sra/rotr.w/d, maskeqz, masknez, mul.w/d, mulh.w/wu/d/du, mulw.d.w/wu, div/mod.w/wu/d/du |
Rd, Rj, Rk |
addi.w, addi.d, slti, sltui |
Rd, Rj, IMM, 12-bit signed |
andi, ori, xori |
Rd, Rj, IMM, 0 to 4095 |
lu52i.d Rd, Rj, IMM; lu12i.w, lu32i.d, pcaddi, pcalau12i, pcaddu12i, pcaddu18i Rd, IMM |
12- and 20-bit signed |
slli, srli, srai, rotri .w / .d |
Rd, Rj, SA, 0-31 / 0-63 |
ext.w.b, ext.w.h, clo/clz/cto/ctz.w/d, revb.2h/4h/2w/d, bitrev.4b/8b/w/d, cpucfg, rdtimel.w, rdtimeh.w, rdtime.d, iocsrrd.b/h/w/d, iocsrwr.b/h/w/d |
Rd, Rj |
bstrpick.w/d, bstrins.w/d |
Rd, Rj, MSB, LSB |
alsl.w, alsl.d |
Rd, Rj, Rk, SA, 1 to 4 |
ld.b/bu/h/hu/w/wu/d, st.b/h/w/d |
Rd, Rj, OFF, 12-bit signed |
ldx.*, stx.* |
Rd, Rj, Rk |
ldptr.w/d, stptr.w/d, ll.w/d, sc.w/d |
Rd, Rj, OFF, a multiple of 4 in -32768..32764 |
amswap, amadd, amand, amor, amxor, ammax, ammin (.w, .d, .wu, .du, with and without _db) |
Rd, Rk, Rj; Rd may not be Rk or Rj |
beq, bne, blt, bge, bltu, bgeu, bgt, ble, bgtu, bleu |
Rj, Rd, TARGET, +-128 KiB |
beqz, bnez |
Rj, TARGET, +-4 MiB; bltz, bgez, bgtz, blez Rj, TARGET +-128 KiB |
b, bl |
TARGET, +-128 MiB |
jirl Rd, Rj, OFF |
|
dbar, ibar, break, syscall, idle |
0 to 32767 |
csrrd, csrwr Rd, CSR; csrxchg Rd, Rj, CSR |
CSR 0 to 16383; csrxchg's Rj not $r0 or $r1 |
Floating-point and vector instructions are refused, as is anything else
outside the list: asm instruction "fadd.d $fa0, $fa0, $fa0" is not in the LoongArch vocabulary.
Callee-saved registers on LoongArch64#
EmbCC saves nothing around an asm, so a template or clobber list naming
$fp or $s0-$s8 is refused (LoongArch asm names callee-saved register '$s0', which EmbCC does not save around an asm), and one naming
$tp or $r21, which the psABI reserves, likewise. A template that
calls (bl, jirl, syscall, ...) clobbers ra, a0-a7 and
t0-t8, whatever its clobber list says.
Example#
static inline void irq_disable(void)
{
long ie = 0, mask = 4; /* CRMD.IE */
__asm__ volatile("csrxchg %0, %1, 0x0" : "+r"(ie) : "r"(mask) : "memory");
}
Xtensa#
This section applies to xtensa-none-elf (the ESP32's LX6 and the
ESP32-S3's LX7, windowed ABI).
Constraints#
| Letter | Meaning |
|---|---|
r, a, g |
an address register chosen by EmbCC (a is GCC's Xtensa letter for them) |
m |
an address register chosen by EmbCC, holding the address of the operand; it is written into the template as aN, 0, the base and offset a load or store takes |
i, n, I-P |
the constant, written into the template as a decimal number |
=, +, & |
see Output operands |
Anything else, a non-constant i included, is refused with
asm constraint "b" is not valid for Xtensa. An operand is one 32-bit
register: a long long one is refused (Xtensa asm operand 0 is 8 bytes). A chosen register comes from a10-a13, then a2-a6 and
a8-a9, skipping registers listed as clobbers, named in the template or
changed by a call in it; never a0 or a1 (the return address and the
stack pointer), a7 (the frame base of a function that calls alloca) or
a14/a15 (the code generator's scratch). A register variable must be
one of those (register int x __asm__("a10")); another is refused with
register variable bound to 'a7' is not supported for Xtensa asm (use a2-a6 or a8-a13).
Modifiers#
None. %N prints the register (a10) or the constant; any modifier is
refused with asm template modifier '%x' is not supported for Xtensa.
Template syntax#
GNU Xtensa syntax, as Espressif's GNU as reads it. Registers are a0 to
a15, and sp is a1. Every operand is written out: a load or store is
l32i a2, a3, 8, and an offset or immediate may be a constant expression.
A branch, loop, j, callN or l32r target is .+N or .-N bytes from
the instruction, or a numeric label of the template (1: referred to as
1b or 1f); a named label is refused, since one template may be emitted
more than once. A special register is named as GNU as names it (rsr a2, ps, rsr.ps a2, wsr a3, intenable) or by number (rsr a2, 230).
Statements are separated by ; or newlines; # and // start a
comment.
A template cannot name a symbol: its bytes carry no relocation, so
call8 f is refused ("f" is a symbol, and inline asm cannot reach one)
-- call through a register with callx8, or write the code in a .S
file or a file-scope block, where
symbols, movi aN, sym and .literal work. For the same reason call
and l32r to a .+N target are refused in a template: their encoding
depends on the instruction's own address, rounded to a word, which a
template does not know. movi takes a 12-bit signed constant; a larger
one is an "r" operand.
Instructions#
| Instruction | Operands |
|---|---|
nop, ill, isync, rsync, esync, dsync, memw, extw, rfe, rfde, rfwo, rfwu, syscall, simcall, ret, retw |
none |
add, sub, and, or, xor, addx2/4/8, subx2/4/8, mull, mul16u, mul16s, quos, quou, rems, remu, min, max, minu, maxu, moveqz, movnez, movltz, movgez, src |
ar, as, at |
mov ar, as; neg, abs ar, at; nsa, nsau at, as; sll ar, as; srl, sra ar, at; movsp at, as |
|
ssl, ssr, ssa8l, jx, callx0, callx4, callx8, callx12 |
as |
movi at, IMM |
-2048 to 2047 |
addi at, as, IMM; addmi at, as, IMM |
-128 to 127; a multiple of 256 in -32768..32512 |
slli ar, as, SA; srli, srai ar, at, SA; ssai SA; extui ar, at, SHIFT, BITS |
1-31; 0-15 and 0-31; 0-31; 0-31 and 1-16 |
sext, clamps ar, as, B |
7 to 22 |
l8ui, l16ui, l16si, l32i, s8i, s16i, s32i, l32ai, s32ri, s32c1i |
at, as, OFF, scaled by the size: 0-255, even 0-510, a multiple of 4 in 0-1020 |
l32e, s32e |
at, as, OFF, a multiple of 4 in -64..-4 |
l32r at, TARGET |
4 to 262144 bytes back (in a file or block only) |
beq, bne, blt, bge, bltu, bgeu, bany, bnone, ball, bnall, bbc, bbs |
as, at, TARGET, -124..131 from the branch |
beqz, bnez, bltz, bgez |
as, TARGET, -2044..2051 |
beqi, bnei, blti, bgei / bltui, bgeui |
as, K, TARGET, K one of -1, 1-8, 10, 12, 16, 32, 64, 128, 256 / 2-8, 10, 12, 16, 32, 64, 128, 256, 32768, 65536 |
bbci, bbsi (and bbci.l, bbsi.l) |
as, BIT, TARGET |
loop, loopnez, loopgtz |
as, END, 4..259 bytes past the loop instruction |
j TARGET; call0, call4, call8, call12 TARGET |
+-128 KiB; +-512 KiB to a word-aligned target (in a file or block only) |
entry as, FRAME |
a multiple of 8 in 0..32760 |
rotw N; rsil at, LEVEL; waiti LEVEL; rfi LEVEL; break S, T |
-8..7; 0-15; 0-15; 1-15; 0-15 each |
rsr, wsr, xsr at, SR (or rsr.SR at) |
the ESP32's special registers by name -- ps, epc1-epc7, eps2-eps7, excsave1-excsave7, depc, exccause, excvaddr, intenable, interrupt, intset, intclear, ccount, ccompare0-2, vecbase, sar, lbeg, lend, lcount, scompare1, atomctl, windowbase, windowstart, prid, cpenable, br, acclo, acchi, m0-m3, memctl, ddr, ibreakenable, ibreaka0/1, dbreaka0/1, dbreakc0/1, icount, icountlevel, debugcause, configid0/1, misc0-3, mmid -- with GNU's read/write/exchange rules (intset is write-only, prid read-only), or by number 0-255 |
rur, wur at, threadptr (or rur.threadptr at) |
A leading _ (GNU's "do not transform") is accepted on any of them. The
density option's 16-bit forms are refused with ret.n is a 16-bit instruction of the density option, which this assembler does not emit: write ret, its 24-bit form; anything else outside the list with
asm instruction "ssa8b a2" is not in the Xtensa vocabulary.
The register window#
Under the windowed ABI a0 holds the return address and a1 the stack
pointer. A template may read or write them where it names them, but they
are never an operand's register and a clobber list naming either is
refused (Xtensa asm clobbers 'a0', which holds this function's return address under the windowed ABI; EmbCC does not save it around an asm).
a2-a15 belong to this function's window and nothing needs saving: a
value live across an asm keeps out of every register the asm changes --
its operands', its clobbers' and the template's.
A call in a template is a windowed call. call8/callx8 hands its
callee the window from a8, which the callee may change, so a8-a15
are clobbered -- as call4 clobbers a4-a15 and call12 a12-a15 --
whether or not the clobber list says so, and no operand is put there.
call0/callx0 would write a0 and are refused (callx0 in Xtensa asm writes a0, which holds this function's return address under the windowed ABI).
Example#
static inline unsigned irq_save(void)
{
unsigned ps;
__asm__ volatile("rsil %0, 3" : "=r"(ps) :: "memory");
return ps;
}
static inline void irq_restore(unsigned ps)
{
__asm__ volatile("wsr %0, ps\n rsync" :: "r"(ps) : "memory");
}
static inline unsigned cycles(void)
{
unsigned c;
__asm__ volatile("rsr %0, ccount" : "=r"(c));
return c;
}
SPARC#
This section applies to sparc-none-elf (SPARC V8, the LEON3).
Constraints#
| Letter | Meaning |
|---|---|
r, g |
an integer register chosen by EmbCC |
m |
a register chosen by EmbCC, holding the address of the operand; it is written into the template as [%o0], as GCC prints a SPARC memory operand |
i, n, I-P |
the constant, written into the template as a decimal number (I is GCC's signed 13-bit) |
0-9 |
an input in the register of the output it names |
=, +, & |
see Output operands |
Anything else, a non-constant i included, is refused with
asm constraint "a" is not valid for SPARC. An operand is one 32-bit
register: a long long one is refused (SPARC asm operand 0 is 8 bytes).
A chosen register comes from %o0-%o5, then %l0-%l5 and
%i0-%i5, skipping registers listed as clobbers, named in the template
or changed by a call in it; never a global (%g1-%g4, %l6, %l7 and
%o7 are the code generator's scratch, %g5-%g7 the system's), %sp,
%fp or %i7. A register variable must be one of those (register int x __asm__("o3")); another is refused with register variable bound to 'g1' is not supported for SPARC asm (use %o0-%o5, %l0-%l5 or %i0-%i5).
Modifiers#
%cN prints a constant operand bare, and %rN prints %g0 for the
constant 0 (as GCC's r). Any other modifier is refused with asm template modifier '%H' is not supported for SPARC.
Template syntax#
GNU SPARC syntax, as GNU as and llvm-mc read it: op rs1, reg_or_imm, rd, registers %g0-%g7, %o0-%o7, %l0-%l7, %i0-%i7,
%r0-%r31, %sp and %fp (in an extended asm's text a literal
register is %%o0); an address [%o0 + %o1], [%fp - 8], [%o0 + %lo(0x1234)]; a constant a C expression, or %hi()/%lo() of one. A
branch or call target is .+N or .-N bytes from the instruction, or a
numeric label of the template (1:, 1b, 1f). Delay slots are the
template's own: write the instruction that fills one after the branch,
call or retl; nothing is moved into or out of a template. Statements are
separated by ; or newlines; ! and // start a comment, and # does
at a line's start. A template cannot name a symbol ("foo" names a symbol, and inline asm cannot reach one): call through a register (call %o2),
or write the code in a .S file or a file-scope block, where call sym,
%hi(sym), %lo(sym) and set sym, rd are relocated.
Instructions#
| Instruction | Operands |
|---|---|
add, addcc, addx, addxcc, sub, subcc, subx, subxcc, and, andcc, andn, andncc, or, orcc, orn, orncc, xor, xorcc, xnor, xnorcc, umul, umulcc, smul, smulcc, udiv, udivcc, sdiv, sdivcc, taddcc, tsubcc, taddcctv, tsubcctv, mulscc, save, restore |
rs1, reg_or_imm, rd, the immediate -4096..4095; save/restore also with none |
sll, srl, sra |
rs1, reg_or_imm, rd, the count 0-31 |
ld, ldub, lduh, ldsb, ldsh, ldd, ldstub, swap |
[address], rd (ldd: an even rd) |
st, stb, sth, std |
rd, [address] |
the same with a (lda, stba, swapa...) |
[rs1 + rs2] ASI (0-255) |
casa |
[rs1] ASI, rs2, rd |
sethi |
a 22-bit constant or %hi(...), rd |
ba, bn, bne, be, bg, ble, bge, bl, bgu, bleu, bcc, bcs, bpos, bneg, bvc, bvs, b, bnz, bz, bgeu, blu, each also ,a |
TARGET, -8388608..8388604 bytes |
call |
TARGET (+-2 GiB), or an address in registers (jmpl ..., %o7) |
jmpl |
address, rd (no brackets) |
jmp, rett, flush, iflush |
address |
ret, retl, nop, stbar |
none |
ta, tn, tne, te, tg, tle, tge, tl, tgu, tleu, tcc, tcs, tpos, tneg, tvc, tvs, t, tnz, tz, tgeu, tlu |
N (0-127), %rs1, %rs1 + N, %rs1 + %rs2 |
rd |
%y, %psr, %wim, %tbr or %asr1-%asr31, rd |
wr |
rs1, reg_or_imm, %y (or another state register), or reg_or_imm, %y |
unimp |
a 22-bit constant |
mov |
reg_or_imm, rd; %y (or another state register), rd; reg_or_imm, %y |
cmp, btst, bset, bclr, btog |
rs1, reg_or_imm / reg_or_imm, rd |
tst; not, neg |
rs; rs, rd or rd |
inc, inccc, dec, deccc |
rd or imm, rd |
clr, clrb, clrh |
rd (clr) or [address] |
set |
value, rd: mov when the value fits 13 bits, sethi alone when its low ten bits are 0, else sethi and or -- GNU's choice |
Floating-point and coprocessor instructions are refused with asm instruction "faddd" is a floating-point instruction: EmbCC compiles soft float, and this assembler has no floating-point vocabulary; SPARC V9's
(ldx, membar, ,pt...) with asm instruction "ldx" is SPARC V9's, and the LEON3 is a V8; anything else outside the list with asm instruction "foo" is not in the SPARC vocabulary.
The register window#
%sp, %fp and %i7 (the stack and frame pointers and the return
address) may be read or written where a template names them, but they
are never an operand's register and a clobber list naming one is refused
(SPARC asm clobbers '%sp', which holds this function's stack pointer; EmbCC does not save it around an asm). Everything else in the window --
the locals and the ins -- is this function's own, so nothing needs
saving: a value live across an asm keeps out of every register the asm
changes, which is its operands', its clobbers' and every register its
text names, clobber list or not. A call (or a jmpl linking through
%o7) in a template also changes the outs and %g1-%g4, as any call
does, and no operand is put there.
Example#
static inline unsigned irq_disable(void)
{
unsigned psr;
__asm__ volatile("rd %%psr, %0\n"
" or %0, 0xf00, %%g1\n" /* PIL 15 */
" wr %%g1, %%psr\n nop\n nop\n nop"
: "=r"(psr) :: "g1", "memory");
return psr;
}
static inline unsigned long long umul64(unsigned a, unsigned b)
{
unsigned lo, hi;
__asm__("umul %2, %3, %0\n rd %%y, %1" : "=r"(lo), "=r"(hi)
: "r"(a), "r"(b));
return (unsigned long long)hi << 32 | lo;
}
PowerPC#
This section applies to powerpc-none-eabi (32-bit PowerPC, the e500
and e200 and the classic cores' shared vocabulary).
Constraints#
| Letter | Meaning |
|---|---|
r, b, g |
a general register chosen by EmbCC (never r0, so b is r) |
m |
a register chosen by EmbCC, holding the address of the operand; it is written into the template as 0(rN), the D-form GCC prints |
i, n, I-P |
the constant, written into the template as a decimal number |
0-9 |
an input in the register of the output it names |
=, +, & |
see Output operands |
Anything else is refused with asm constraint "a" is not valid for PowerPC; a long long operand with PowerPC asm operand 0 is 8 bytes. A
chosen register comes from r3-r8, then r14-r30, skipping registers listed
as clobbers, named in the template or changed by a call in it; never r0,
r1 (the stack pointer), r2 or r13 (the EABI's small-data anchors), r9-r12
(the code generator's scratch) or r31 (the frame base under alloca). A
register variable must be one of those (register int x __asm__("r5")); another is refused with register variable bound to 'r11' is not supported for PowerPC asm (use r3-r8 or r14-r30).
Modifiers#
%UN and %XN print nothing (an "m" operand here is always the plain
D-form, so lwz%U1%X1 %0, %1 is lwz), and %cN prints a constant
bare. Any other modifier is refused with asm template modifier '%L' is not supported for PowerPC.
Template syntax#
GNU PowerPC syntax, as GNU as and llvm-mc read it. A register is a bare
number in a register's place (addi 3, 4, 1, GCC's own spelling), rN
or %rN, or sp; a CR field crN or a number; a CR bit a number or an
expression of crN, lt, gt, eq, so and un (4*cr7+eq); a
constant a C expression, with @ha, @h or @l for its halves; a
memory operand d(rA). A branch target is .+N or .-N bytes from the
instruction, or a numeric label of the template (1:, 1b, 1f).
Statements are separated by ; or newlines; # and // start a
comment. A template cannot name a symbol ("foo" names a symbol, and inline asm cannot reach one): call through CTR (mtctr, bctrl), or
write the code in a .S file or a file-scope block, where bl sym,
b sym, a conditional branch to a symbol and sym@ha/@h/@l are
relocated.
Instructions#
| Instruction | Operands |
|---|---|
add, addc, adde, subf, subfc, subfe, mullw, divw, divwu (each also o, ., o.); mulhw, mulhwu (also .); sub, subc (also .) |
rt, ra, rb |
addze, addme, subfze, subfme, neg (also o, ., o.) |
rt, ra |
and, andc, or, orc, xor, nand, nor, eqv, slw, srw, sraw (also .) |
ra, rs, rb |
cntlzw, extsb, extsh, mr, not (also .); srawi (also .) |
ra, rs; ra, rs, SH |
addi, addis, addic, addic., mulli, subfic; li, lis, la, subi, subis, subic, subic. |
rt, ra, SIMM (addis/lis 16 bits either way); rt, SIMM; rt, d(ra) |
ori, oris, xori, xoris, andi., andis. |
ra, rs, UIMM |
rlwinm, rlwimi, rlwnm (also .) |
ra, rs, SH (rb), MB, ME |
rotlwi, rotrwi, rotlw, slwi, srwi, clrlwi, clrrwi, extlwi, extrwi, inslwi, insrwi, clrlslwi (also .) |
as the ISA's table of extended mnemonics |
cmpw, cmplw, cmpwi, cmplwi |
[crN,] ra, rb/SIMM/UIMM |
cmp, cmpl, cmpi, cmpli |
crN, 0, ra, rb/IMM |
lwz, lbz, lhz, lha, stw, stb, sth and their u forms, lmw, stmw |
rt, d(ra) |
lwzx, lbzx, lhzx, lhax, stwx, stbx, sthx and their ux forms, lwbrx, lhbrx, stwbrx, sthbrx, lwarx, stwcx. |
rt, ra, rb |
dcbf, dcbst, dcbt, dcbtst, dcbz, dcbi, icbi, tlbsx, tlbivax |
ra, rb |
sync, msync, lwsync, eieio, isync, tlbwe, tlbre, tlbsync, rfi, rfci, rfmci, sc, trap, nop; mbar |
none; [MO] |
tw, twi; tweq, twlt, twgt, twne, twle, twge, twnl, twng, twllt, twlgt, twlle, twlge, twlnl, twlng, twu and their i forms |
TO, ra, rb/SIMM; ra, rb/SIMM |
crand, cror, crxor, crnand, crnor, creqv, crandc, crorc; crset, crclr, crmove, crnot; mcrf |
three CR bits; one or two; two CR fields |
mfcr, mtcr; mtcrf |
rt; FXM, rs |
isel; isellt, iselgt, iseleq |
rt, ra, rb, BC; rt, ra, rb |
mfmsr, mtmsr, wrtee; wrteei |
rt; 0 or 1 |
mfspr, mtspr |
rt, SPR / SPR, rs: a number 0-1023 or a name -- xer, lr, ctr, dec, srr0, srr1, csrr0, csrr1, mcsrr0, mcsrr1, dear, esr, ivpr, ivor0-ivor15, ivor32-ivor35, pid, pid1, pid2, decar, tcr, tsr, tbl, tbu, tbwl, tbwu, sprg0-sprg7, usprg0, pvr, pir, svr, hid0, hid1, l1csr0, l1csr1, mmucsr0, mmucfg, bucsr, mas0-mas4, mas6, mas7, tlb0cfg, tlb1cfg, dbsr, dbcr0-dbcr2, iac1, iac2, dac1, dac2, mcsr, mcar, spefscr, ear, dsisr, dar, sdr1 |
mfNAME, mtNAME for each name above; mfsprg, mtsprg; mftb, mftbu |
rt / rs; rt, N / N, rs (N 0-7); rt |
b, bl; ba, bla |
TARGET, +-32 MiB; an absolute address |
bc, bcl, bca, bcla |
BO, BI, TARGET (-32768..32764) |
bclr, bclrl, bcctr, bcctrl; blr, blrl, bctr, bctrl |
BO, BI [, BH]; none |
b<cond> for lt, le, eq, ge, gt, nl, ne, ng, so, ns, un, nu, each also l, a, la, lr, lrl, ctr, ctrl |
[crN,] TARGET, or [crN] |
bt, bf (also l, a, la, lr, lrl, ctr, ctrl); bdnz, bdz (also l, a, la, lr, lrl); bdnzt, bdnzf, bdzt, bdzf (likewise) |
BI, TARGET; TARGET; BI, TARGET |
Writing LR (mtlr, mtspr lr) or a branch that links makes the function
save its own LR around the template; its operands and every value live
across it then keep out of r0 and r3-r12, which a call changes. Floating
point is refused with asm instruction "fadd" is a floating-point instruction: EmbCC compiles soft float, and this assembler has no floating-point vocabulary, as are AltiVec, SPE, 64-bit and string
instructions by name; a branch-prediction suffix with beq+: a branch-prediction hint (+/-) is not supported: write the branch without it; anything else outside the list with asm instruction "foo" is not in the PowerPC vocabulary.
Callee-saved registers on PowerPC#
r14-r31 survive a call. When an asm changes one -- an operand in it, a
clobber naming it, or the template's text naming it (a bare li 14, 0
counts: EmbCC assembles the template once with its operands in
placeholders to learn which registers it names) -- the function's prologue
saves it and its epilogue restores it, at every optimization level. r1,
r2 and r13 may not be clobbered (PowerPC asm clobbers 'r1', which holds the stack pointer; EmbCC does not save it around an asm), and r31 may
not be changed in a function that calls alloca, where it is the frame
base.
Example#
static inline unsigned irq_save(void)
{
unsigned msr;
__asm__ volatile("mfmsr %0\n wrteei 0" : "=r"(msr) :: "memory");
return msr;
}
static inline void irq_restore(unsigned msr)
{
__asm__ volatile("wrtee %0" :: "r"(msr) : "memory");
}
static inline int cas(int *p, int old, int new_)
{
int prev;
__asm__ volatile("1: lwarx %0, 0, %1\n"
" cmpw %0, %2\n"
" bne 2f\n"
" stwcx. %3, 0, %1\n"
" bne 1b\n"
"2:"
: "=&r"(prev) : "r"(p), "r"(old), "r"(new_)
: "cc", "memory");
return prev;
}
Renesas RX#
This section applies to rx-none-elf (RXv1: the RX600/RX610, RX100 and
RX200 cores).
Constraints#
| Letter | Meaning |
|---|---|
r, g |
a general register chosen by EmbCC |
m |
a general register chosen by EmbCC, holding the address of the operand; it is written into the template as [rN], the memory operand an RX instruction takes |
i, n, I-P, and GCC's Int08, Sint08, Sint16, Sint24, Uint04 |
the constant, written into the template as #N, as GCC's RX port prints it |
=, +, & |
see Output operands |
Anything else, a non-constant i included, is refused with
asm constraint "b" is not valid for RX. An operand is one 32-bit
register: a long long one is refused (RX asm operand 0 is 8 bytes).
A chosen register comes from r1-r4, then r6-r12, skipping
registers listed as clobbers or named in the template; never r0 (the
stack pointer), r5, r14 or r15 (the code generator's scratch) or
r13 (the frame base of a function that calls alloca). A register
variable must be one of those (register int x __asm__("r1")); another is
refused with register variable bound to 'r5' is not supported for RX asm (use r1-r4 or r6-r12).
Modifiers#
None. %N prints the register (r3), the constant (#5) or the memory
operand ([r3]); any modifier is refused with asm template modifier '%x' is not supported for RX. In a naked function
%cN prints a constant without its #.
Template syntax#
GNU RX syntax, as rx-elf-as reads it. Registers are r0 to r15 (GNU has
no sp), immediates #expr, memory [rN], dsp[rN], [ri, rb],
[rN+] and [-rN], with .b, .w, .l, .ub or .uw after a memex
source (add 4[r1].w, r2); a displacement is in bytes, a multiple of the
access size. A branch target is .+N or .-N bytes from the branch, or
a numeric label of the template (1: referred to as 1b or 1f); a
named label is refused, since one template may be emitted more than once.
A branch written without a size takes the shortest form that reaches, as
GNU as relaxes it: bra .s/.b/.w/.a, bsr .w/.a,
beq/bne .s/.b/.w and then the inverse condition over a
bra.a, any other condition .b and then its inverse over a bra.w or
bra.a (bgt far is ble.b .+5; bra.w far). Statements are separated by
newlines or !; ; starts a comment (GNU's RX comment character), as
does # at the start of a line.
A template cannot name a symbol: its bytes carry no relocation, so
bsr _f and mov.l #_x, r1 are refused ("_f" is a symbol, and inline asm cannot reach one) -- pass the address as an "r" operand and jsr
through it, or write the code in a .S file or a
file-scope block, where symbols
relocate. There, as with GCC, a C name carries the RX underscore:
bsr _c_function, .global _asm_function.
Instructions#
| Instruction | Operands |
|---|---|
nop, rts, rte, rtfi, wait, brk, satr |
none |
mov.b, mov.w, mov.l (mov is mov.l) |
rs, rd (.b/.w sign-extend); #imm, rd (.l); dsp[rs], rd; rs, dsp[rd]; #imm, dsp[rd]; [ri, rb], rd; rs, [ri, rb]; [rs+], rd; [-rs], rd; rs, [rd+]; rs, [-rd] |
movu.b, movu.w |
rs, rd; dsp[rs], rd; [ri, rb], rd; [rs+], rd; [-rs], rd |
add, sub, and, or, mul |
rs, rd; #imm, rd; src, rd (memex); rs, rs2, rd; add #imm, rs, rd |
cmp, xor, tst, max, min, div, divu, emul, emulu |
rs, rd; #imm, rd; src, rd (memex) |
adc, sbb |
rs, rd; #imm, rd; dsp[rs].l, rd |
neg, not, abs |
rd; rs, rd |
xchg |
rs, rd; src, rd (memex) |
stz, stnz |
#imm, rd |
shll, shlr, shar |
#0-31, rd; #0-31, rs, rd; rs, rd |
rotl, rotr |
#0-31, rd; rs, rd |
rolc, rorc, sat |
rd |
revl, revw |
rs, rd |
push.b, push.w, push.l (push is push.l); pop |
rs or dsp[rs]; rd |
pushm, popm |
rN-rM, 1 <= N <= M |
pushc, popc; mvfc cr, rd; mvtc rs, cr; mvtc #imm, cr |
psw, pc (not written), usp, fpsw, bpsw, bpc, isp, fintv, intb |
setpsw, clrpsw |
c, z, s, o, i, u |
mvtipl #0-15; int #0-255 |
|
bra (.s, .b, .w, .a), bsr (.w, .a) |
TARGET; bra.l/bsr.l rs (and bra/bsr rs) |
bCND (.s and .w for eq/ne, .b for all) |
TARGET; CND one of eq/z, ne/nz, geu/c, ltu/nc, gtu, leu, pz, n, ge, lt, gt, le, o, no |
jmp, jsr |
rs |
rtsd |
#0-1020 (a multiple of 4); #bytes, rN-rM |
bset, bclr, btst, bnot |
#0-31, rd; #0-7, dsp[rd].b; rs, rd; rs, dsp[rd].b |
bmCND |
#0-31, rd; #0-7, dsp[rd].b |
scCND.l |
rd |
suntil, swhile, sstr, rmpa (.b, .w, .l); scmpu, smovu, smovb, smovf |
none |
mvfachi, mvfaclo, mvfacmi; mvtachi, mvtaclo |
rd; rs |
racw #1-2; mulhi, mullo, machi, maclo |
; rs, rs2 |
An immediate is any 32-bit value, signed or unsigned (sub and sbb,
which GNU as negates and inverts, take a signed one). A memex source
defaults to .l; its displacement is unsigned, scaled by the size and at
most 65535 units -- 32767 for movu and for mov #imm, dsp[rd], whose
field QEMU reads signed. Each form is encoded as GNU as encodes it, the
shortest field that holds the value: tests/golden/rx-asm.sh assembles
every one of them with rx-elf-as and must get the same bytes. The FPU's
instructions (fadd, itof, ...) and RXv2's are refused with fadd is an FPU or RXv2 instruction, which is not in EmbCC's RX vocabulary; a
memory-to-memory mov and the memory forms of scCND are refused by
name; anything else outside the list with asm instruction "frob" is not in the RX vocabulary.
Callee-saved registers#
r6-r13 are callee-saved. A template may change any register except
r0 when it says so -- in its clobber list, or by naming the register in
the template, which counts as a clobber here even when the list does not
say it (beyond what GCC promises): a value live across the asm keeps out
of every register the asm changes, and a callee-saved one among them is
saved by the function's prologue (pushm r6-rN), as GCC saves it. A
clobber list naming r0 is refused (RX asm clobbers 'r0', the stack pointer; EmbCC does not save it around an asm). A call from a template
(jsr %1) must list the caller-saved registers it changes, r1-r5,
r14 and r15, as with GCC.
Example#
static inline unsigned irq_save(void)
{
unsigned psw;
__asm__ volatile("mvfc psw, %0\n\tclrpsw i" : "=r"(psw) :: "memory");
return psw;
}
static inline void irq_restore(unsigned psw)
{
__asm__ volatile("mvtc %0, psw" :: "r"(psw) : "memory");
}
static inline void set_usp(void *sp)
{
__asm__ volatile("mvtc %0, usp" :: "r"(sp));
}
ColdFire#
This section applies to m68k-none-elf (ColdFire ISA_A+, the MCF5208).
Constraints#
| Letter | Meaning |
|---|---|
d, r, g |
a data register chosen by EmbCC |
a |
an address register chosen by EmbCC |
m |
an address register chosen by EmbCC, holding the address of the operand; it is written into the template as (%aN) |
i, n, I-P |
the constant, written into the template as #N, as GCC's m68k port prints it |
=, +, & |
see Output operands |
Anything else, a non-constant i included, is refused with
asm constraint "b" is not valid for ColdFire. An operand is one 32-bit
register: a long long one is refused (ColdFire asm operand 0 is 8 bytes), and so is an output narrower than a long in an address register,
which ColdFire moves only whole. A chosen data register comes from d0,
d1, then d2-d7, an address register from a0, then a2-a5,
skipping registers listed as clobbers or named in the template; never
a1 (the lowering's own), a6 (the frame pointer) or a7 (the stack
pointer). A register variable must be one of those
(register int x __asm__("d2")); another is refused with register variable bound to 'a1' is not supported for ColdFire asm (use d0-d7, a0 or a2-a5).
Modifiers#
None. %N prints the register (%d2), the constant (#5) or the memory
operand ((%a2)); %% is a %, so a register the template names is
written %%d0 in extended asm (basic asm keeps its text: %d0). In a
naked function %cN prints a constant without its
#.
Template syntax#
GNU as's Motorola syntax, as m68k-elf GCC writes it: registers %d0-%d7,
%a0-%a7, %fp (%a6) and %sp (%a7) -- the % optional and either
case, as GNU as takes them (move.w SR,D7) -- immediates #expr, and the
effective addresses (An), (An)+, -(An), (d,An) or d(An),
(d,An,Xi.l*s), (d,%pc), (xxx).w, (xxx).l or a bare address, and
MIT's An@, An@+, An@-, An@(d) and An@(d,Xi:l:s). Sizes are
suffixes; where an instruction has a choice and none is written it is
.w, as GNU as defaults (move, mulu), and .l where ColdFire has
only that (add). As GNU as does, move.l #n,Dn with n in -128..127 is
moveq and add/sub #1..8 is addq/subq. A branch target is .+N
or .-N bytes from the branch, or a numeric label of the template (1:
referred to as 1b or 1f); a branch without a size takes .s or .w,
whichever reaches. Statements are separated by newlines or ;; |
starts a comment, as does # at the start of a line.
A template cannot name a symbol: its bytes carry no relocation, so jsr f
is refused ("f" is a symbol, and inline asm cannot reach one) -- pass
the address as an "a" operand and jsr (%0), or write the code in a
.S file or a file-scope block,
where symbols relocate.
Instructions#
| Instruction | Operands |
|---|---|
nop, rts, rte, tpf, halt, illegal |
none |
move.b, move.w, move.l, movea.w, movea.l |
<ea>,<ea> in the pairs ColdFire encodes: no source with an extension word into an indexed or absolute destination, no indexed, absolute or immediate source into anything but a register, (An), (An)+ or -(An) |
move.w %sr,Dn; move.w Dn,%sr, move.w #imm,%sr; the same with %ccr; move.l %usp,An, move.l An,%usp |
|
moveq |
#-128..127,Dn |
lea, pea, jmp, jsr |
a control address: (An), (d,An), (d,An,Xi), absolute, %pc-relative |
add, sub, and, or, cmp (.l) |
<ea>,Dn; Dn,<mem> (not cmp); #imm,Dn; <ea>,An (add, sub, cmp: adda/suba/cmpa) |
eor.l |
Dn,Dn; Dn,<mem>; #imm,Dn |
adda, suba, cmpa; addi, subi, andi, ori, eori, cmpi (.l) |
<ea>,An; #imm,Dn |
addq, subq (.l) |
#1..8,<ea> |
addx, subx (.l) |
Dy,Dx |
neg, negx, not (.l); swap; ext.w, ext.l, extb.l |
Dn |
clr, tst (.b, .w, .l) |
a data register or memory |
asl, asr, lsl, lsr (.l) |
#1..8,Dn; Dx,Dn |
muls, mulu (.w, .l) |
<ea>,Dn (.l: Dn, (An), (An)+, -(An), (d16,An)) |
divs.l, divu.l; rems.l, remu.l |
<ea>,Dq; <ea>,Dr:Dq (the same modes as mul.l) |
scc (st, sf, shi ... sle, shs, slo) |
Dn |
bra, bsr, bcc (.s, .w) |
TARGET; cc one of hi, ls, cc/hs, cs/lo, ne, eq, vc, vs, pl, mi, ge, lt, gt, le |
link (.w); unlk |
An,#-32768..32767; An |
movem.l |
a register list (%d2-%d7/%a2-%a5) and (An) or (d16,An), either way |
movec |
Rn,Rc, Rc one of %cacr, %asid, %acr0-%acr3, %mmubar, %vbr, %rombar, %rambar, %mbar |
trap #0..15; stop #imm16 |
|
btst, bchg, bclr, bset |
#0..31,Dn or Dx,Dn; #0..7,<mem> ((An), (An)+, -(An), (d16,An)) or Dx,<mem> |
What ColdFire lacks is refused by name: rol.l: ColdFire has no rotate,
dbra: ColdFire has no dbcc, exg, cas, chk, BCD, the FPU's
instructions, the MAC's; ISA_B's mvs, mvz, mov3q and sats and a
32-bit branch, on which the MCF5208 traps; a byte or word add
(add.w: ColdFire's arithmetic and logic are .l only); a predecrement
movem; anything else with asm instruction "frob" is not in the ColdFire vocabulary.
Callee-saved registers#
d2-d7 and a2-a6 are callee-saved. A template may change any
register except a6 and a7 when it says so -- in its clobber list, or by
naming the register in the template, which counts as a clobber here even
when the list does not say it: a value live across the asm keeps out of
every register the asm changes, and a callee-saved one among them is
saved by the function's prologue (movem.l), as GCC saves it. A clobber
list naming %sp or %fp is refused (ColdFire asm clobbers 'sp', the stack pointer; EmbCC does not save it around an asm). A call from a
template (jsr (%0)) must list the caller-saved registers it changes,
d0, d1, a0 and a1, as with GCC.
Example#
static inline unsigned short irq_save(void)
{
unsigned short sr;
__asm__ volatile("move.w %%sr,%0\n\tmove.w #0x2700,%%sr"
: "=d"(sr) :: "memory");
return sr;
}
static inline void irq_restore(unsigned short sr)
{
__asm__ volatile("move.w %0,%%sr" :: "d"(sr) : "memory");
}
static inline void set_vbr(void *table)
{
__asm__ volatile("movec %0,%%vbr" :: "a"(table));
}
AVR#
This section applies to the avr target (ATmega328P).
Constraints#
| Letter | Meaning |
|---|---|
r, g, q |
registers chosen by EmbCC from the pool below |
m |
registers chosen by EmbCC, holding the address of the operand; see below |
d |
registers chosen from r16 to r31 (the registers ldi, subi, andi, cpi reach) |
a |
registers chosen from r16 to r23 |
w |
the pair r24, r26 or r30 (the pairs adiw and sbiw reach, except Y) |
e |
the pointer pair X (r26) or Z (r30) |
b |
the pointer pair Z (r30) |
x |
X (r26:r27) |
z |
Z (r30:r31) |
i, n, I, J, K, L, M, N, O, P, R |
an integer constant expression, substituted into the template as a decimal literal |
=, +, & |
see Output operands |
Precedence: a register variable wins; then x,
y or z; then the class letters (r, g, d, a, w, e, b,
q, m); then the constant letters. When a string holds several class
letters, a register is accepted if any one of them accepts it, so a
string containing r, g, q or m accepts every register of the
pool. The constant letters only require a constant; its range is checked
by the instruction that uses it (adiw: 100 is outside 0..63).
y is refused, because Y is the frame pointer:
an asm operand is pinned to 'r28', which is half of Y, the frame pointer this function reaches its own locals through
q does not mean the stack pointer: it is treated like r.
Do not use m on AVR. The operand receives as many registers as the
expression's type has bytes, but the address loaded into them is two
bytes, so for a 1-byte object the address overwrites the next register,
which may belong to another operand. The operand is also not
necessarily placed in a pointer pair. To read or write through a
pointer, pass the pointer with e, x or z and write %a.
A constant letter with an expression that is not constant, and any other
letter, is refused when the statement is generated:
asm constraint "S" is not valid for AVR.
An operand occupies as many consecutive registers as its type has bytes,
starting on an even register when it is wider than one byte. Registers
are chosen from r18 to r27, r30 and r31, skipping registers bound to
another operand, listed as clobbers, or named in the template. An
operand wider than 4 bytes is refused with
an asm operand of 8 bytes needs 8 consecutive registers, which is more than this backend keeps free across an asm,
and an exhausted class with
no register satisfying "d" is free for an asm operand of 2 bytes.
Modifiers#
| Form | Register operand | Immediate operand |
|---|---|---|
%N |
the lowest register (r24) |
the value |
%AN |
byte 0: the lowest register | the whole value |
%BN |
byte 1: the next register up | bits 8 to 15 |
%CN |
byte 2 | bits 16 to 23 |
%DN |
byte 3 | bits 24 to 31 |
%aN |
the pair as a pointer: X, Y or Z |
The named forms (%A[NAME], %a[NAME], ...) work the same way. %a on
an operand that is not in X, Y or Z is refused:
%a names operand %1 as a pointer, but it is in r19 -- only X, Y and Z address memory, which is what the "e" and "b" constraints are for
Any other modifier is refused with
asm template modifier '%E' is not supported for AVR (%A..%D name the bytes of a wider operand).
For the low byte of an immediate use lo8(), since %A prints the whole
value: ldi %A0, lo8(%1) and ldi %B0, hi8(%1).
Template syntax#
GNU AVR syntax. Statements are separated by newlines only: on AVR
; starts a comment, as do # at the start of a statement and //.
Mnemonics are case-insensitive.
Registers are r0 to r31, XL, XH, YL, YH, ZL, ZH, the
pointer pairs X, Y, Z (with +, - and Y+q/Z+q forms), and
__tmp_reg__ (r0) and __zero_reg__ (r1). __SREG__, __SP_H__ and
__SP_L__ are the I/O addresses 0x3f, 0x3e and 0x3d, as avr-gcc
defines them at the top of every assembly file it writes, in inline asm,
in the body of a naked function, in a file-scope __asm__ block and in a
.S file. avr-libc's register names (PORTB, UDR0) are C macros: in
C, pass them as "I"(_SFR_IO_ADDR(PORTB)) operands
(<avr/io.h>).
Operands may be constant expressions with + - * / % & | ^ ~ ! << >>,
parentheses, character constants, and lo8(), hi8(), hlo8(),
hh8(), pm_lo8(), pm_hi8(), pm_hh8() and gs() applied to
constants. A symbol name is not accepted in an inline template
('buf)' is not a number, a register or an expression this assembler knows).
The target of a relative branch (br..., rjmp, rcall) must be
written relative to ., which counts from the end of the branch, as GNU
as and llvm-mc read it for AVR: rjmp . and rjmp .+0 go on to the next
instruction (avr-gcc's two-cycle nop), rcall . pushes the address of the
next one (the two bytes of stack avr-gcc's prologues take), and dec r24
then brne .-4 loops back to the dec. A bare number is refused with
rjmp needs a target written relative to `.` (a label, or `.+4`); ....
jmp and call take an absolute, even byte address written as a
number; a .-relative target is refused for them. Labels are not
accepted.
Instructions#
The whole AVR5 instruction set:
| Group | Instructions |
|---|---|
| Two registers | add, adc, sub, sbc, and, or, eor, mov, cp, cpc, cpse, mul, movw |
| One register written twice | clr, tst, lsl, rol |
| Signed and fractional multiply | muls, mulsu, fmul, fmuls, fmulsu |
| Register and immediate | subi, sbci, andi, ori, cpi, ldi, ser |
| One register | com, neg, swap, inc, asr, lsr, ror, dec, push, pop |
| Word | adiw, sbiw |
| Memory | ld, st, ldd, std, lds, sts, lpm, elpm |
| I/O | in, out, cbi, sbi, sbic, sbis |
| Register bits | bld, bst, sbrc, sbrs |
| Branches | brcs, brlo, brcc, brsh, breq, brne, brmi, brpl, brlt, brge, brvs, brvc, brhs, brhc, brts, brtc, brie, brid, brbs, brbc |
| Jumps and calls | rjmp, rcall, jmp, call, ijmp, icall, eijmp, eicall |
| Status register | sec, clc, sez, clz, sen, cln, sev, clv, ses, cls, seh, clh, set, clt, sei, cli, bset, bclr |
| No operands | ret, reti, nop, sleep, wdr, break, spm |
elpm, eijmp, eicall and spm are encoded although the ATmega328P
does not implement them all. The XMEGA instructions xch, las, lac,
lat and des are refused with
xch is an AVR instruction this assembler does not implement yet (it is XMEGA-only or a cryptographic accelerator); ....
Reserved registers on AVR#
A template, a clobber or an operand that names r2 to r17 (callee-saved)
or r28, r29 or Y (the frame pointer) is refused:
AVR asm names register 'r28', which is half of Y, the frame pointer this function reaches its own locals through
AVR asm clobbers register 'r5', which is callee-saved, and EmbCC saves nothing around an asm
r0 and r1 may be used. r1 is the zero register: a template that changes
it, for example with mul, must clear it again (clr r1) before it
ends. EmbCC does not check this.
Example#
static inline unsigned char irq_save(void)
{
unsigned char sreg;
__asm__ volatile("in %0, 0x3f\n\tcli" : "=r"(sreg) : : "memory");
return sreg;
}
static inline void irq_restore(unsigned char sreg)
{
__asm__ volatile("out 0x3f, %0" : : "r"(sreg) : "memory");
}
unsigned int mul8(unsigned char a, unsigned char b)
{
unsigned int r;
__asm__("mul %1, %2\n\tmovw %0, r0\n\tclr r1" : "=r"(r) : "r"(a), "r"(b));
return r;
}
unsigned int swap16(unsigned int v)
{
unsigned int r;
__asm__("mov %A0, %B1\n\tmov %B0, %A1" : "=r"(r) : "r"(v));
return r;
}
unsigned char next_byte(const unsigned char **pp)
{
const unsigned char *p = *pp;
unsigned char c;
__asm__("ld %0, %a1+" : "=r"(c), "+e"(p));
*pp = p;
return c;
}
Limitations#
In summary, compared with GCC:
- Only the instructions listed for each target, and the directives under Directives in a template, can appear in a template, and a function template cannot refer to a symbol on any target.
asm goto,asm inlineand flag-output constraints are not supported. Flag outputs are not always refused (see x86-64 and AArch64).moutputs do not work (except on MIPS32, Xtensa, SPARC and PowerPC, where the operand's register holds the address), andminputs are a register holding the address rather than a memory reference.- On x86-64 and RISC-V,
iandngive a register, not an immediate. - Labels inside a function template are supported only for the x86-64
leaq Nf(%%rip)form and as numeric labels (1:,1b) on Xtensa, TriCore, SPARC, PowerPC, RX and ColdFire; elsewhere branches use numeric displacements. - Register variables are not supported on ARM Cortex-M and RISC-V, are limited to x0-x11 and x13-x15 on AArch64, and are not supported at file scope on any target.
- Assembler names on C declarations are not supported.
- File-scope asm accepts data and a few directives on every target, and
four instructions on x86-64 only. Every block is in
.text, so other sections are refused. - On x86-64, a callee-saved register that a template changes is saved
only when it holds an operand and the optimization level is
--opt=speedor--opt=size. - Every
asmstatement is volatile, a memory barrier, and prevents inlining of its function.