1
votes

What is the correct use of multiple input and output operands in extended GCC asm under register constraint? Consider this minimal version of my problem. The following brief extended asm code in GCC, AT&T syntax:

    int input0 = 10;
    int input1 = 15;
    int output0 = 0;
    int output1 = 1;

    asm volatile("mov %[input0], %[output0]\t\n"
                 "mov %[input1], %[output1]\t\n"
                 : [output0] "=r" (output0), [output1] "=r" (output1)
                 : [input0] "r" (input0), [input1] "r" (input1)
                 :);

    printf("output0: %d\n", output0);
    printf("output1: %d\n", output1);

The syntax appears correct based on https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html However, I must have overlooked something or be committing some trivial mistake that I for some reason can't see.

The output with GCC 5.3.0 p1.0 (no compiler arguments) is:

output0: 10
output1: 10

Expected output is:

output0: 10
output1: 15

Looking at it in GDB shows:

0x0000000000400581 <+43>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000400584 <+46>: mov edx,DWORD PTR [rbp-0xc]
0x0000000000400587 <+49>: mov edx,eax
0x0000000000400589 <+51>: mov eax,edx
0x000000000040058b <+53>: mov DWORD PTR [rbp-0x8],edx
0x000000000040058e <+56>: mov DWORD PTR [rbp-0x4],eax

From what I can see it loads eax with input0 and edx with input1. It then overwrites edx with eax and eax with edx, making these equal. It then writes these back into output0 and output1.

If I use a memory constraint (=m) instead of a register constraint (=r) for the output, it gives the expected output and the assembly looks more reasonable.

1
You'll probably want to look at early clobbers & modifier . In particular I have a feeling you need =&r for your output0 operand since that register is modified before the last instruction of your assembler template. GCC will think it can reuse that register as an input as well. & will prevent the early clobber assigned register from being used as an input registerMichael Petch
You may also want to consider using g constraint on your input operands instead of r. Since the outputs are defined as registers only, and the mov instructions in your template can take at least one memory or immediate value operand you give the compiler a chance to perform other optimizations by using g. g constraint is documented as Any register, memory or immediate integer operand is allowed, except for registers that are not general registersMichael Petch
In particular if you use g as input operand constraints, the compiler should be able to realize that some of the inputs are in fact constants (immediate) values, which should allow for some code reduction. You can see these optimizations much better if you compile with GCC using an optimization level of -O3Michael Petch
@MichaelPetch Well if you want to fully enumerate the allowed operands and give the compiler the most flexibility you would use "=r,r,rm,rm", "=r,rm,r,rm" : "g,g,ri,ri", "g,ri,g,ri".Ross Ridge

1 Answers

3
votes

The problem is that GCC assumes that all all output operands are only written at the end of the instruction, after all input operands have been consumed. This means it can use the same operand (eg. a register) as an input operand and an output operand which is what is happening here. The solution is to mark [output0] with an early clobber constraint so that GCC knows that its written to before the end of the asm statement.

For example:

 asm volatile("mov %[input0], %[output0]\t\n"
              "mov %[input1], %[output1]\t\n"
              : [output0] "=&r" (output0), [output1] "=r" (output1)
              : [input0] "r" (input0), [input1] "r" (input1)
              :);

You don't need to mark [output1] as early clobber because it's only written to at the end of the instruction so it doesn't matter if it uses the same register as [input0] or [input1].