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.

Note: Only a single string can be passed to the simple form of inline assembly.

In an extended assembler instruction using asm, the operands of the instruction are specified using C expressions. The extended syntax is:

asm("template" [ : [ [ "[" symbolic-name "]" ] "constraint"(output-operand) [, ... ] ] 
               [ : [ [ "[" symbolic-name "]" ] "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. Placeholders can be in the form %n or %[symbolic-name] ; if using the former form operands are numbered in the order they appear in the constraint lists (after the first colon). The constraint strings specify operand constraints, such as that an operand must be either in a register or that it must be an immediate, or some other type, of value. If there are any clobber specifications, these normally represent a register name however there are two special values:

  • "cc" represents the condition code of the machine
  • "memory" asserts that there is a read or write to an unnamed entity which forces a barrier preventing code from moving around the assembly statement during optimization

Constraint letters and modifiers supported by the compiler are listed in the following two tables, respectively.

Table 17-1. Constraint Letters Supported by the Compiler - dsPIC33C/E/F and dsPIC30F Devices Only
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.
za 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, %n represents the first register for the operand (n). To access the second, third, or fourth register, use a modifier letter.

%[symbolic-name] An operand that matches the specified symbolic-name. If any other constraint modifiers are required, they should come after the % and before the opening square brace. By default, this will represent the first register or memory location of the operand.
C An even-odd register pair.
D An even-numbered register.
T A near or far data operand.
U A near data operand.
Table 17-2. Constraint Letters Supported by the Compiler - dsPIC33A Devices Only
Letter Constraint
d General purpose data registers W1-W14.
g Any register, memory or immediate integer operand is allowed, except for registers that are not general registers.
i An immediate operand (one with a constant value) is allowed. This includes symbolic constants whose values will be known only at assemble time.
l A floating point register F0 - F31.
r Any register operand in the set W0-W15.
w Accumulator register A - B.
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, %n represents the first register for the operand (n). To access the second, third, or fourth register, use a modifier letter.

%[symbolic-name] An operand that matches the specified symbolic-name. If any other constraint modifiers are required, they should come after the % and before the opening square brace. By default, this will represent the first register or memory location of the operand.
C An even-odd register pair.
D An even numbered register.
T A near or far data operand.
U A near data operand.
Table 17-3. Constraint Modifiers Supported by the Compiler
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) */
    );