4

So I was messing around with inline assembly and compiled this using GCC 9. The result was that the two variables a and b were swapped without actually issuing any direct commands.

#include<cstdio>
int main(){
    int a(1),b(2),c(3);
    asm ("": "=r"(c):"r"(a));
    asm ("": "=r"(a):"r"(b));
    asm ("": "=r"(b):"r"(c));
    printf("%d %d %d", a,b,c);
}

Can somebody explain what is going on here?

Peter Cordes
  • 286,368
  • 41
  • 520
  • 731
A.Hristov
  • 461
  • 1
  • 4
  • 12
  • Disassemble your code and you shall find out. – goodvibration Nov 03 '19 at 10:42
  • 2
    @goodvibration: That's not a useful answer in this case; it doesn't tell you *why* gcc made those choices. You need to look at the compiler's asm output to find out which registers the asm statements picked, not disassembly. – Peter Cordes Nov 03 '19 at 10:45

1 Answers1

6

Basically random chance of variable allocation. (Or actually GCC's internal machinery's first choices).

You used output-only "=r" asm operands but then your asm template doesn't actually write that register so you get whatever value was sitting in the register GCC picked.

This is a lot like C undefined behaviour for using an uninitialized varible.

To see what happened, put asm comments in the asm template that expand %0 and %1 inside the template inside an asm comment. This will have no impact on how GCC does register allocation: it doesn't care if the template uses the registers it picks implicitly or explicitly; it's up to you to write a useful template and match that to the operand constraints.

With your code on the Godbolt compiler explorer, with gcc9.2 -O3 -fverbose-asm:

.intel_syntax noprefix
.LC0:
        .string "%d %d %d"
main:
        sub     rsp, 8    #,
        mov     edi, OFFSET FLAT:.LC0     #,
        xor     eax, eax  #
        mov     ecx, 1    # tmp87,
        mov     esi, 2    # tmp89,
        nop  #ecx ecx   # c, tmp87
        nop  #esi esi   # a, tmp89
        nop  #edx ecx   # b, c
        call    printf  #
        xor     eax, eax  #
        add     rsp, 8    #,
        ret     

(Instead of a bare asm comment, I put the comments on NOP instructions like
asm ("nop #%0 %1": "=r"(c):"r"(a)); so compiler-explorer filtering wouldn't remove them. asm templates are pure text substitution before feeding it to the assembler so again this is still exactly equivalent to how GCC would have compiled your original source with the same options.)

In the first 2 cases, gcc decided to pick the same register for output as for input so those happened to work like assignments.

In the 3rd case, c was already in a register, and putting a 3 anywhere was optimized away because "=r"(c) overwrite that value before it was ever read.

Perhaps you were compiling with optimization disabled? You could do that too, and follow what happened. (Probably GCC would pick eax for both input and output each time). I normally don't bother looking at anti-optimized -O0 asm because its full of store/reload noise.

Peter Cordes
  • 286,368
  • 41
  • 520
  • 731
  • 1
    @A.Hristov: Thanks for asking a clear well-formatted question with an interesting answer that was also a good way to illustrate a useful technique for understanding / debugging GNU C inline asm statements. :) – Peter Cordes Nov 03 '19 at 11:21