When to use earlyclobber constraint in extended GCC inline assembly?

左心房为你撑大大i 提交于 2019-12-28 06:32:06

问题


I understand when to use a cobbler list (e.g. listing a register which is modified in the assembly so that it doesn't get chosen for use as an input register, etc), but I can't wrap my head around the the earlyclobber constraint &. If you list your outputs, wouldn't that already mean that inputs can't use the selected register (aside from matching digit constraints)?

For example:

asm(
    "movl $1, %0;"
    "addl $3, %0;"
    "addl $4, %1;"
    "addl %1, %0;"
    : "=g"(num_out)
    : "g"(num_in)
    :
);

Would & even be needed for the output variables? The compiler should know the register that was selected for the output, and thus know not to use it for the input.


回答1:


By default, the compiler assumes all inputs will be consumed before any output registers are written to, so that it's allowed to use the same registers for both. This leads to better code when possible, but if the assumption is wrong, things will fail catastrophically. The "early clobber" marker is a way to tell the compiler that this output will be written before all the input has been consumed, so it cannot share a register with any input.




回答2:


Minimal educational example

Here I provide a minimal educational example that attempts to make what https://stackoverflow.com/a/15819941/895245 mentioned clearer.

This code is of course not useful in practice, and could be achieved more efficiently a single lea 1(%q[in]), %out instruction.

main.c

#include <assert.h>
#include <inttypes.h>

int main(void) {
    uint64_t in = 1;
    uint64_t out;
    __asm__ (
        "mov %[in], %[out];" /* out = in */
        "inc %[out];"        /* out++ */
        "mov %[in], %[out];" /* out = in */
        "inc %[out];"        /* out++ */
        : [out] "=&r" (out)
        : [in] "r" (in)
        :
    );
    assert(out == 2);
}

Compile and run:

gcc -ggdb3 -std=c99 -O3 -Wall -Wextra -pedantic -o main.out main.c
./main.out

This program is correct and the assert passes, because & forces the compiler to choose different registers for in and out.

This is because & tells the compiler that in might be used after out was written to, which is actually the case here.

Therefore, the only way to not wrongly modify in is to put in and out in different registers.

The disassembly:

gdb -nh -batch -ex 'disassemble/rs main' main.out

contains:

   0x0000000000001055 <+5>:     48 89 d0        mov    %rdx,%rax
   0x0000000000001058 <+8>:     48 ff c0        inc    %rax
   0x000000000000105b <+11>:    48 89 d0        mov    %rdx,%rax
   0x000000000000105e <+14>:    48 ff c0        inc    %rax

which shows that GCC chose rax for out and rdx for in.

If we remove the & however, the behavior is unspecified.

In my test system, the assert actually fails, because the compiler tries to minimize register usage, and compiles to:

   0x0000000000001055 <+5>:     48 89 c0        mov    %rax,%rax
   0x0000000000001058 <+8>:     48 ff c0        inc    %rax
   0x000000000000105b <+11>:    48 89 c0        mov    %rax,%rax
   0x000000000000105e <+14>:    48 ff c0        inc    %rax

therefore using rax for both in and out.

The result of this is that out is incremented twice, and equals 3 instead of 2 in the end.

Tested in Ubuntu 18.10 amd64, GCC 8.2.0.

More practical examples

  • multiplication implicit output registers
  • non-hardcoded scratch registers: GCC: Prohibit use of some registers


来源:https://stackoverflow.com/questions/15819794/when-to-use-earlyclobber-constraint-in-extended-gcc-inline-assembly

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