How does “+&r” differ from “+r”?

风流意气都作罢 提交于 2021-01-27 07:02:58

问题


GCC's inline assembler recognizes the declarators =r and =&r. These make sense to me: the =r lets the assembler reuse an input register for output.

However, GCC's inline assembler also recognizes the declarators +r and +&r. These make less sense to me. After all, isn't the distinction between +r and +&r a distinction without a difference? Does the +r alone not suffice to tell the compiler to reserve a register for the sole use of a single variable?

For example, what is wrong with the following GCC code?

#include <stdio.h>
int main()
{
    int a = 0;
    printf("Initially, a == %d.\n", a);
    /* The architecture is amd64/x86-64. */
    asm(
        "inc %[a]\n"
        : [a] "+r" (a)
        : : "cc"
    );
    printf("However, after incrementation, a == %d.\n", a);
    return 0;
}

Notice incidentally that my inline assembly lacks an input declaration because, in my (perhaps mistaken) mind, the +r covers input, clobbering, output, everything. What have I misunderstood, please?

BACKGROUND

I have programmed 8- and 16-bit microcontrollers in assembly a bit, but have little or no experience at coding assembly in a hosted environment.


回答1:


GCC assumes by default that inline assembly statements consist of a simple single assembly instruction that consumes all of its input operands before writing to any of its output operands. When writing inline assembly statements that use multiple assembly instructions this assumption is often broken, so the early clobber constraint modifier & needs to be used to indicate which output operands are written to before all the input operands are consumed. This is necessary with both output operands that use the = modifier and read/write output operands that use +. For example consider the two following functions:

int
foo() {
    int a = 1;
    asm("add %1, %0" : "+r" (a) : "r" (1));
    return a;
}

int
bar() {
    int a = 1;
    asm("add %1, %0\n\t"
        "add %1, %0"
        : "+r" (a) : "r" (1));
    return a;
}

Both inline assembly statements use the same operands and the same constraints, but the only the inline assembly statement in foo is correct, the one in bar is broken. With optimizations enabled GCC generates the following code for the two functions:

_foo:
    movl    $1, %eax
/APP
    add %eax, %eax
/NO_APP
    ret

_bar:
    movl    $1, %eax
/APP
    add %eax, %eax
    add %eax, %eax
/NO_APP
    ret

GCC sees no reason not to use the same register, EAX, for both operands in both inline assembly statements. While this isn't a problem in foo, it causes bar to calculate the wrong result of 4 instead of the expected 3.

A correct version of bar would use the early clobber modifier:

int
baz() {
    int a = 1;
    asm("add %1, %0\n\t"
        "add %1, %0"
        : "+&r" (a) : "r" (1));
    return a;
}
_baz:
    movl    $1, %eax
    movl    %eax, %edx
/APP
    add %edx, %eax
    add %edx, %eax
/NO_APP
    ret

When compiling baz GCC knows to use a different register for both operands so it doesn't matter that the read/write output operand is modified before the input operand is read for the second time.



来源:https://stackoverflow.com/questions/45895564/how-does-r-differ-from-r

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!