Consider the following sample program (targeting Linux/x86-64):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
unsigned arg1 = strtoul(argv[1], NULL, 0);
unsigned arg2 = strtoul(argv[2], NULL, 0);
asm(
"mov %[arg1], %%ecx\n\t"
"add %[arg2], %[arg1]\n\t"
"add %[arg2], %%ecx\n\t"
"xchg %%ecx, %[arg2]"
: [arg1] "+&abdSD" (arg1), [arg2] "+&abdSD" (arg2)
:
: "cc", "ecx");
printf("%u %u\n", arg1, arg2);
}
(xchg is used just for easy grepping of compiled instructions in listing.)
With GCC, it works as expected - different registers are assigned to arg1 and arg2, for example:
11bf: e8 cc fe ff ff callq 1090 <strtoul@plt>
11c4: 89 da mov %ebx,%edx
11c6: 89 d1 mov %edx,%ecx
11c8: 01 c2 add %eax,%edx
11ca: 01 c1 add %eax,%ecx
11cc: 91 xchg %eax,%ecx
(so, arg1 in edx, arg2 in eax)
But, compiling with Clang (confirmed on 6.0 and 10.0) results in assigning the same register for arg1 and arg2:
401174: e8 d7 fe ff ff callq 401050 <strtoul@plt>
401179: 44 89 f0 mov %r14d,%eax ; <--
40117c: 89 c1 mov %eax,%ecx
40117e: 01 c0 add %eax,%eax ; <-- so, arg1 and arg2 both in eax
401180: 01 c1 add %eax,%ecx
401182: 91 xchg %eax,%ecx
The issue remains with multiple variations as e.g.: + instead of +& in constraint strings; numeric forms like %0 to address operands; replacing xchg with another rare instruction; and so on.
I have been expecting, from the basic principles, that compilerʼs logic to assign output locations will always assign different locations to different output operands, whatever constraints are defined for them; and the same works among the input operands set. (Modifiers like '+', '&' add more rules to placement logic but canʼt erode the main principles.)
Is there a some trivial aspect Iʼve overlooked?
UPD: reported to LLVM.