17.2 Using Inline Assembly Language
Within a C function, the asm
statement may be used to
insert a line of assembly language code into the assembly language that the compiler
generates. Inline assembly has two forms: simple and extended.
In the simple form, the assembler instruction is written using the syntax:
asm ("instruction");
where instruction
is a valid assembly-language
construct. If you are writing inline assembly in ANSI C programs, write
__asm__
instead of asm
.
In an extended assembler instruction using asm
, the
operands of the instruction are specified using C expressions. The extended syntax is:
asm("template" [ : [ "constraint"(output-operand) [ , ... ] ]
[ : [ "constraint"(input-operand) [ , ... ] ]
[ "clobber" [ , ... ] ]
]
]);
You must specify an assembler instruction
template
, plus an operand constraint
string
for each operand. The template
specifies the instruction mnemonic
and optionally placeholders for the operands. The constraint
strings specify operand constraints, for example, that an operand must either be in a
register (the usual case) or that it must be an immediate value.
Constraint letters and modifiers supported by the compiler are listed in the following two tables, respectively.
Letter | Constraint |
---|---|
a |
Claims WREG |
b |
Divide support register W1 |
c |
Multiply support register W2 |
d |
General purpose data registers W1-W14 |
e |
Non-divide support registers W2-W14 |
g |
Any register, memory or immediate integer operand is allowed, except for registers that are not general registers. |
i |
An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time. |
r |
A register operand is allowed provided that it is in a general register. |
v |
AWB register W13 |
w |
Accumulator register A-B |
x |
x prefetch registers W8-W9 |
y |
y prefetch registers W10-W11 |
z |
MAC prefetch registers W4-W7 |
zs |
In Mixed-Sign Multiplication mode, allows for a signed input operand. |
zu |
In Mixed-Sign Multiplication mode, allows for an unsigned input operand. |
0, 1, … , 9 |
An operand that matches the specified operand
number is allowed. If a digit is used together with letters within the same
alternative, the digit should come last. By default,
% |
C |
An even-odd register pair |
D |
An even-numbered register |
T |
A near or far data operand. |
U |
A near data operand. |
Modifier | Constraint |
---|---|
= |
Means that this operand is write-only for this instruction: the previous value is discarded and replaced by output data. |
+ |
Means that this operand is both read and written by the instruction. |
& |
Means that this operand is an
earlyclobber operand, which is modified before
the instruction is finished using the input operands. Therefore, this operand
may not lie in a register that is used as an input operand or as part of any
memory address. |
d |
Second register for operand number n,
i.e., %dn . |
q |
Fourth register for operand number n,
i.e., %qn . |
t |
Third register for operand number n,
i.e., %tn . |
Passing C Variables
This example demonstrates how to use the swap
instruction (which the compiler does not generally use):
asm ("swap %0" : "+r"(var));
Here var
is the C expression for the operand, which is
both an input and an output operand. The operand is constrained to be of type
r
, which denotes a register operand. The +
in
+r
indicates that the operand is both an input and output
operand.
Each operand is described by an operand-constraint string that is followed by the C expression in parentheses. A colon separates the assembler template from the first output operand and another separates the last output operand from the first input, if any. Commas separate output operands and separate inputs.
If there are no output operands, but there are input operands; then
there must be two consecutive colons surrounding the place where the output operands
would go. The compiler requires that the output operand expressions must be L-values.
The input operands need not be L-values. The compiler cannot check whether the operands
have data types that are reasonable for the instruction being executed. It does not
parse the assembler instruction template and does not know what it means, or whether it
is valid assembler input. The extended asm
feature is most often used
for machine instructions that the compiler itself does not know exist. If the output
expression cannot be directly addressed (for example, it is a bit-field), the constraint
must allow a register. In that case, the compiler will use the register as the output of
the asm
, and then store that register into the output. If output
operands are write-only, the compiler will assume that the values in these operands
before the instruction are dead and need not be generated.
Clobbering Registers
Some instructions clobber specific hard registers. To describe this, write a third colon after the input operands, followed by the names of the clobbered hard registers (given as strings separated by commas). Here is an example:
asm volatile ("mul.b %0"
: /* no outputs */
: "U" (nvar)
: "w2");
In this case, the operand nvar
is a character variable
declared in near data space, as specified by the "U
" constraint. If the
assembler instruction can alter the flags (condition code) register, add
"cc
" to the list of clobbered registers. If the assembler
instruction modifies memory in an unpredictable fashion, add "memory
"
to the list of clobbered registers. This will cause the compiler to not keep memory
values cached in registers across the assembler instruction.
Using Multiple Assembler Instructions
You can put multiple assembler instructions together in a single
asm
template, separated with newlines (written as
\n
). The input operands and the output operands’ addresses are
ensured not to use any of the clobbered registers, so you can read and write the
clobbered registers as many times as you like. Here is an example of multiple
instructions in a template; it assumes that the subroutine _foo
accepts
arguments in registers W0 and W1:
asm ("mov %0,w0\nmov %1,W1\ncall _foo"
: /* no outputs */
: "g" (a), "g" (b)
: "W0", "W1");
In this example, the constraint strings "g
" indicate a
general operand.
Using '&' to Prevent Input Register Clobbering
Unless an output operand has the &
constraint
modifier, the compiler may allocate it in the same register as an unrelated input
operand, on the assumption that the inputs are consumed before the outputs are produced.
This assumption may be false if the assembler code actually consists of more than one
instruction. In such a case, use &
for each output operand that may
not overlap an input operand. For example, consider the following function:
int exprbad(int a, int b)
{
int c;
__asm__("add %1,%2,%0\n sl %0,%1,%0" : "=r"(c) : "r"(a), "r"(b));
return(c);
}
The intention is to compute the value (a + b) << a. However, as
written, the value computed may or may not be this value. The correct coding informs the
compiler that the operand c
is modified before the asm
instruction is finished using the input operands, as follows:
int exprgood(int a, int b)
{
int c;
__asm__("add %1,%2,%0\n sl %0,%1,%0" : "=&r"(c) : "r"(a), "r"(b));
return(c);
}
Matching Operands
When the assembler instruction has a read-write operand, or an operand
in which only some of the bits are to be changed, you must logically split its function
into two separate operands: one input operand and one write-only output operand. The
connection between them is expressed by constraints that say they need to be in the same
location when the instruction executes. You can use the same C expression for both
operands or different expressions. For example, here is the add
instruction with bar
as its read-only source operand and
foo
as its read-write destination:
asm ("add %2,%1,%0"
: "=r" (foo)
: "0" (foo), "r" (bar));
The constraint "0" for operand 1 says that it must occupy the same
location as operand 0. A digit in constraint is allowed only in an input operand and
must refer to an output operand. Only a digit in the constraint can ensure that one
operand will be in the same place as another. The mere fact that foo
is
the value of both operands is not enough to ensure that they will be in the same place
in the generated assembler code. The following would not work:
asm ("add %2,%1,%0"
: "=r" (foo)
: "r" (foo), "r" (bar));
Various optimizations or reloading could cause operands 0 and 1 to be
in different registers. For example, the compiler might find a copy of the value of
foo
in one register and use it for operand 1, but generate the
output operand 0 in a different register (copying it afterward to foo
’s
own address).
Naming Operands
It is also possible to specify input and output operands using symbolic names that can be referenced within the assembler code template. These names are specified inside square brackets preceding the constraint string, and can be referenced inside the assembler code template using %[name] instead of a percentage sign followed by the operand number. Using named operands, the above example could be coded as follows:
asm ("add %[foo],%[bar],%[foo]"
: [foo] "=r" (foo)
: "0" (foo), [bar] "r" (bar));
Volatile ASM Statements
You can prevent an asm
instruction from being deleted,
moved significantly, or combined, by writing the keyword volatile
after
the asm
. For example:
#define disi(n) \
asm volatile ("disi #%0" \
: /* no outputs */ \
: "i" (n))
In this case, the constraint letter "i
" denotes an
immediate operand, as required by the disi
instruction.
Handling Values Larger Than INT
Constraint letters and modifiers may be used to identify various
entities with which it is acceptable to replace a particular operand, such as
%0
in:
asm("mov %1, %0" : "r"(foo) : "r"(bar));
This example indicates that the value stored in foo
should be moved into bar
. The example code performs this task unless
foo
or bar
are larger than an
int
.
By default, %0
represents the first register for the
operand (0). To access the second, third, or fourth register, use a modifier letter
specified in the table above.
Using Mixed-Sign Multiplication Mode
The zu
constraint allows for unsigned input operand and
zs
for a signed input operand.
int x;
unsigned int y;
int result;
asm("MPY %[sgn]*%[unsgn], A \n\t"
"SAC.R A, #1, %[res] \n\t"
: [res] "=r" (result)
: [sgn] "zs" (x), /* zs for signed MAC (W5,W7) */
[unsgn] "zu" (y) /* zu for unsigned MAC (W4,W6) */
);