16

我了解何时使用 cobbler 列表(例如,列出在程序集中修改的寄存器,以便它不会被选择用作输入寄存器等),但我无法绕过 earlyclobber 约束&. 如果您列出您的输出,那是否已经意味着输入不能使用所选寄存器(除了匹配的数字约束)?

例如:

asm(
    "movl $1, %0;"
    "addl $3, %0;"
    "addl $4, %1;"   // separate bug: modifies input-only operand
    "addl %1, %0;"
    : "=g"(num_out)
    : "g"(num_in)
    :
);

&甚至需要输出变量吗?编译器应该知道为输出选择的寄存器,因此知道不将它用于输入。

4

2 回答 2

23

默认情况下,编译器假定在写入任何输出寄存器之前将使用所有输入,因此允许对两者使用相同的寄存器。这会在可能的情况下产生更好的代码,但如果假设错误,事情将会灾难性地失败。“early clobber”标记是一种告诉编译器此输出将在所有输入被消耗之前写入的方法,因此它不能与任何输入共享寄存器。

GNU C 内联汇编语法旨在尽可能高效地包装单个指令。您可以在 asm 模板中放置多个指令,但默认值(假设在写入任何输出之前读取所有输入)是围绕包装单个指令设计的。

它与 GCC 在其机器描述文件中使用的约束语法相同,这些文件教编译器在 ISA 中可用的指令。

于 2013-04-04T19:23:33.900 回答
10

最小的教育例子

在这里,我提供了一个最小的教育示例,试图使https://stackoverflow.com/a/15819941/895245提到的内容更清晰。

这个具体的代码在实践中当然没有用,单lea 1(%q[in]), %out条指令可以更高效地实现,只是一个简单的教育例子。

主程序

#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);
}

编译并运行:

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

这个程序是正确的并且断言通过了,因为强制编译器为和&选择不同的寄存器。inout

这是因为&告诉编译器in可能在out写入后使用,这里实际上就是这种情况。

因此,唯一不会错误修改in的方法是将inout放在不同的寄存器中。

拆解:

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

包含:

   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

这表明 GCC 选择了raxforoutrdxfor in

但是,如果我们删除它&,则行为是未指定的。

在我的测试系统中,断言实际上失败了,因为编译器试图最小化寄存器的使用,并编译为:

   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

因此rax同时用于inout

这样做的结果out是增加了两次,并且等于3而不是2最后。

在 Ubuntu 18.10 amd64、GCC 8.2.0 中测试。

更实际的例子

于 2019-02-24T15:53:53.053 回答