Assembler Instructions with C Expression Operands

Previous Inline Assembler Next

In an assembler instruction using asm, you can specify the operands of the instruction using C expressions. This means you need not guess which registers or memory locations will contain the data you want to use.

You must specify an assembler instruction template much like what appears in a machine description, plus an operand constraint string for each operand.

For the beginning, you will be given here a very illustrative example on how to use the 68881's fsinx instruction (note that this example is not applicable to TI calculators, because the MC68000 processor which is built into the TI-89 and TI-92+ does not have this instruction, but the example is illustrative anyway):

asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
Here angle is the C expression for the input operand while result is that of the output operand. Each has "f" as its operand constraint, saying that a floating point register is required. The = in =f indicates that the operand is an output; all output operands' constraints must use =. The constraints use the same language used in the machine description (see the section Operand Constraints). In this example, the compiler will convert this instruction into a sequence of assembly instructions which will ensure that the evaluated expression angle is really stored in one of the 68881's floating point registers, and which will store the result of the fsinx (which is in some other floating point register) into the expression result (note that result need not be a variable; it can be any lvalue expression, for example a dereferenced pointer).

Each operand is described by an operand-constraint string 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 the operands within each group. The total number of operands is currently limited to 30; this limitation may be lifted in some future version of GCC.

If there are no output operands but there are input operands, you must place two consecutive colons surrounding the place where the output operands would go.

As of GCC version 3.1, it is also possible to specify input and output operands using symbolic names which can be referenced within the assembler code. These names are specified inside square brackets preceding the constraint string, and can be referenced inside the assembler code using %[name] instead of a percentage sign followed by the operand number. Using named operands the above example could look like:
asm ("fsinx %[angle],%[output]"
     : [output] "=f" (result)
     : [angle] "f" (angle));
Note that the symbolic operand names have no relation whatsoever to other C identifiers. You may use any name you like, even those of existing C symbols, but must ensure that no two operands within the same assembler construct use the same symbolic name.

Output operand expressions must be lvalues; the compiler can check this. The input operands need not be lvalues. 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 even whether it is valid assembler input. The extended asm feature is most often used for machine instructions the compiler itself does not know exist. If the output expression cannot be directly addressed (for example, it is a bit-field), your constraint must allow a register. In that case, GCC will use the register as the output of the asm, and then store that register into the output.

Here is an another example, which is applicable to TIGCC. Suppose that we want to rotate the value of the expression input one bit to the left, and to store the result into the expression output (which is an lvalue). We can write the following code:
asm ("move.l %1,%%d0; rol #1,%%d0; move.l %%d0,%0" : "=g" (output) : "g" (input));
Note that when extended asm constructions are used, the percent sign before the register names must be doubled. It is important to say that in the above example input and output may be any valid expressions; in the simple case when both of them are just global variables, a simple asm construction like
asm ("move.l input,%d0; rol #1,%d0; move.l %d0,output");
would be quite enough. Extended asm constructions allow encapsulating them in macros which look like functions, like in the following example, which defines a macro 'rotate' which acts like a void function:
#define rotate(input, output) \
({ asm ("move.l %1,%%d0; rol #1,%%d0; move.l %%d0,%0" \
: "=g" (output) : "g" (input)); })
The ordinary output operands must be write-only; GCC will assume that the values in these operands before the instruction are dead and need not be generated. That's why the following version of 'rotate' macro which accepts just one argument (and which rotates it one bit to the left) is quite unreliable:
#define rotate(inout) ({ asm ("rol #1,%0" : "=d" (inout)); })
To solve such difficulties, extended asm supports input-output or read-write operands. Use the constraint character + to indicate such an operand and list it with the output operands.

When the constraints for the read-write operand (or the operand in which only some of the bits are to be changed) allows a register, you may, as an alternative, 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 which 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 we write the (fictitious) combine instruction with bar as its read-only source operand and foo as its read-write destination:
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
The constraint "0" for operand 1 says that it must occupy the same location as operand 0. A number in constraint is allowed only in an input operand and it must refer to an output operand.

Only a number in the constraint can guarantee 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 guarantee that they will be in the same place in the generated assembler code. The following would not work reliably:
asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
Various optimizations or reloading could cause operands 0 and 1 to be in different registers; GCC knows no reason not to do so. 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). Of course, since the register for operand 1 is not even mentioned in the assembler code, the result will not work, but GCC can't tell that.

As of GCC version 3.1, one may write [name] instead of the operand number for a matching constraint. For example:
asm ("cmoveq %1,%2,%[result]"
     : [result] "=r"(result)
     : "r" (test), "r"(new), "[result]"(old));
All the examples given above have a serious drawback: they clobber the register 'd0'. If the compiler keeps something important in it (which is very likely), this may cause lots of trouble. Of course, you can save it on the stack at the beginning and restore it at the end, but there is a much better solution which will save clobbered registers only when necessary. To describe clobbered registers, write a third colon after the input operands, followed by the names of the clobbered registers (given as strings), separated by commas:
#define rotate(input, output) \
({ asm ("move.l %1,%%d0; rol #1,%%d0; move.l %%d0,%0" \
: "=g" (output) : "g" (input) : "d0"); })
You may not write a clobber description in a way that overlaps with an input or output operand. For example, you may not have an operand describing a register class with one member if you mention that register in the clobber list. Variables declared to live in specific registers (see Explicit Reg Vars), and used as asm input or output operands must have no part mentioned in the clobber description. There is no way for you to specify that an input operand is modified without also specifying it as an output operand. Note that if all the output operands you specify are for this purpose (and hence unused), you will then also need to specify volatile for the asm construct, as described below, to prevent GCC from deleting the asm statement as unused.

Whenever you refer to a particular hardware register from the assembler code, you will probably have to list the register after the third colon to tell the compiler the register's value is modified. Alternatively, you can force the compiler to use any data register instead of 'd0', using the following trick:
#define rotate(input, output) \
({ register long __temp; \
asm ("move.l %1,%2; rol #1,%2; move.l %2,%0" \
: "=g" (output) : "g" (input), "d" (__temp)); })
Here, the "d" constraint ensures that '__temp' will be stored in the data register (note that rol is applicable only to data registers). In fact, there is no need for a temporary register if we forced the output to be in a data register, which can be implemented as in the following example:
#define rotate(input, output) \
({ asm ("move.l %1,%0; rol #1,%0" : "=d" (output) : "g" (input)); })
If your assembler instruction can alter the condition code register, add cc to the list of clobbered registers. GCC on some machines represents the condition codes as a specific hardware register; cc serves to name this register. On other machines, the condition code is handled differently, and specifying cc has no effect. But it is valid no matter what the machine.

If your assembler instruction modifies memory in an unpredictable fashion, add memory to the list of clobbered registers. This will cause GCC to not keep memory values cached in registers across the assembler instruction. You will also want to add the volatile keyword if the memory affected is not listed in the inputs or outputs of the asm, as the memory clobber does not count as a side-effect of the asm.

As already mentioned above, you can put multiple assembler instructions together in a single asm template, separated either with newlines or with semicolons (the GNU assembler allows semicolons as a line-breaking character). The input operands are guaranteed not to use any of the clobbered registers, and neither will the output operands' addresses, 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 the subroutine _foo accepts arguments in registers a0 and a1:
asm ("move.l %0,%%a0; move.l %1,%%a1; jsr _foo"
: /* no outputs */
: "g" (from), "g" (to)
: "a0", "a1");
Unless an output operand has the '&' constraint modifier, GCC may allocate it in the same register as an unrelated input operand, on the assumption 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. See the section Constraint Modifier Characters.

If you want to test the condition code produced by an assembler instruction, you must include a branch and a label in the asm construct, as follows:
asm ("clr.l %0; test.l %1; beq 0f; moveq #1,%0; 0:"
: "g" (result) : "g" (input));
This assumes your assembler supports local labels, which is true for the GNU Assembler (it uses the suffix 'f' for forward and 'b' for backward local labels; see the section about Local Symbol Names).

Speaking of labels, jumps from one asm to another are not supported. The compiler's optimizers do not know about these jumps, and therefore they cannot take account of them when deciding how to optimize.

As already mentioned, usually the most convenient way to use these asm instructions is to encapsulate them in macros that look like functions. Here is an example of how to define a function-looking macro with a non-void return type:
#define rotate(x) \
({ unsigned long __result, __arg=(x);   \
asm ("move.l %1,%0; rol #1,%0": "=d" (__result): "g" (__arg));  \
__result; })
This macro acts nearly exactly like a function which takes one unsigned long argument, and which returns an unsigned long result. Here the variable __arg is used to make sure that the instruction operates on a proper unsigned long value, and to accept only those arguments x which can convert automatically to an unsigned long.

Another way to make sure the instruction operates on the correct data type is to use a cast in the asm. This is different from using a variable __arg in that it converts more different types. For example, if the desired type were int, casting the argument to int would accept a pointer with no complaint, while assigning the argument to an int variable named __arg would warn about using a pointer unless the caller explicitly casts it.

If an asm has output operands, GCC assumes for optimization purposes the instruction has no side effects except to change the output operands. This does not mean instructions with a side effect cannot be used, but you must be careful, because the compiler may eliminate them if the output operands aren't used, or move them out of loops, or replace two with one if they constitute a common subexpression. Also, if your instruction does have a side effect on a variable that otherwise appears not to change, the old value of the variable may be reused later if it happens to be found in a register.

If you are not happy with this behavior, you can prevent an asm instruction from being deleted, moved significantly, or combined, by writing the keyword volatile after the asm. For example:
#define get_and_set_priority(new)              \
({ int __old;                                  \
   asm volatile ("get_and_set_priority %0, %1" \
                 : "=g" (__old) : "g" (new));  \
   __old; })
If you write an asm instruction with no outputs, GCC will know the instruction has side-effects and will not delete the instruction or move it outside of loops.

The volatile keyword indicates that the instruction has important side-effects. GCC will not delete a volatile asm if it is reachable. (The instruction can still be deleted if GCC can prove that control-flow will never reach the location of the instruction.) In addition, GCC will not reschedule instructions across a volatile asm instruction. For example:
#define inline_rowread(x) \
  ({*(short*)0x600018=(short)(x); \
  asm volatile("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop":::"memory"); \
  ~(*(unsigned char*)0x60001b);})
Here, we can read the keyboard port at 0x60001b only 12 nops after setting the keyboard mask at 0x600018. Therefore, we need to make sure that the nops get scheduled between the write to 0x600018 and the read to 0x60001b. The volatile keyword makes sure that the instructions get ordered in the correct way.

Note that even a volatile asm instruction can be moved in ways that appear insignificant to the compiler, such as across jump instructions. You can't expect a sequence of volatile asm instructions to remain perfectly consecutive. If you want consecutive output, use a single asm. Also, GCC will perform some optimizations across a volatile asm instruction; GCC does not "forget everything" when it encounters a volatile asm instruction the way some other compilers do.

An asm instruction without any operands or clobbers (an "old style" asm) will be treated identically to a volatile asm instruction.

It is a natural idea to look for a way to give access to the condition code left by the assembler instruction. However, when we attempted to implement this, we found no way to make it work reliably. The problem is that output operands might need reloading, which would result in additional following "store" instructions. On most machines, these instructions would alter the condition code before there was time to test it. This problem doesn't arise for ordinary "test" and "compare" instructions because they don't have any output operands.

For reasons similar to those described above, it is not possible to give an assembler instruction access to the condition code left by previous instructions.

You can write write __asm__ instead of asm. See section Alternate Keywords.