GCC x86_64 asm 中的冲突寄存器分配。生命周期被忽略

Conflicting register allocation in GCC x86_64 asm. Lifetime ignored

我有一个包含小程序块的函数。由 GCC 编译的这个函数的汇编是不正确的,因为它将同一个寄存器分配给两个不同的变量。这是函数来源:

void *ptr;
uint64_t foo(uint64_t arg1) {
  register uint64_t tmp;
  asm ("lea TARGET(%%rip), %[tmp];"
       "shl  , %[arg1];"
       "sub  %[arg1], %[tmp];"
       "movq %[tmp], %[ptr];"
      : [ptr] "+m" (ptr), [tmp] "=r" (tmp)
      : [arg1] "r" (arg1)
      :);

  asm ("TARGET:;");
}

我对全局 ptr 使用约束“+m”,因为我写入它,它应该在内存中。 tmp 的约束是“=r”,因为它只被写入。输入 arg1 的约束只是 "r"。这可能很重要。

汇编块的 SSA 伪代码是:

tmp_0 = something
arg1_1 = arg1_0 << 4
tmp_1 = tmp_0 - arg_1
*ptr_0 = tmp_1

优化时此函数的编译程序集 O0

00000000000005fa <foo>:
 5fa:   55                      push   %rbp
 5fb:   48 89 e5                mov    %rsp,%rbp
 5fe:   48 89 7d f8             mov    %rdi,-0x8(%rbp)       # Storing arg1 to stack (optimization level O0)
 602:   48 8b 45 f8             mov    -0x8(%rbp),%rax       # arg1_0 is assigned register rax
 606:   48 8d 05 0e 00 00 00    lea    0xe(%rip),%rax        # 61b <TARGET>, tmp_0 is ALSO assigned rax
 60d:   48 c1 e0 04             shl    [=13=]x4,%rax             # arg1_0 is used, its lifetime ends
 611:   48 29 c0                sub    %rax,%rax             # subtracting two vars both assigned the same register
 614:   48 89 05 fd 09 20 00    mov    %rax,0x2009fd(%rip)   # 201018 <ptr>, store to global

000000000000061b <TARGET>:
 61b:   90                      nop
 61c:   5d                      pop    %rbp
 61d:   c3                      retq 

检查程序集,我们看到在地址 0x602 中,寄存器 rax 被分配给 SSA 寄存器 arg1_0,其生命周期为指令 0x60d。同时,地址 0x606 处的指令还将寄存器 rax 分配给 SSA 寄存器 tmp_0。似乎一个物理寄存器在其生命周期内被分配给了另一个 SSA 寄存器。

系统信息:

我的问题:

首先,感谢@Jester 完美的评论回答了这个问题。引用:

The manual says: "Use the ‘&’ constraint modifier (see Modifiers) on all output operands that must not overlap an input. Otherwise, GCC may allocate the output operand in the same register as an unrelated input operand, on the assumption that the assembler code consumes its inputs before producing outputs. This assumption may be false if the assembler code actually consists of more than one instruction. "

简而言之,我的内联汇编不同意 gcc 的假设,即在写入输出之前先消耗输入。事实上,gcc 提供了适当的约束修饰符 (&) 来表达这一点。

修复:

-       : [ptr] "+m" (ptr), [tmp] "=r" (tmp)
+       : [ptr] "+m" (ptr), [tmp] "=&r" (tmp)

固定汇编输出:

00000000000005fa <foo>:
 5fa:   55                      push   %rbp
 5fb:   48 89 e5                mov    %rsp,%rbp
 5fe:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
 602:   48 8b 45 f8             mov    -0x8(%rbp),%rax
 606:   48 8d 15 0e 00 00 00    lea    0xe(%rip),%rdx        # 61b <TARGET>
 60d:   48 c1 e0 04             shl    [=11=]x4,%rax
 611:   48 29 c2                sub    %rax,%rdx
 614:   48 89 15 fd 09 20 00    mov    %rdx,0x2009fd(%rip)        # 201018 <ptr>

000000000000061b <TARGET>:
 61b:   90                      nop
 61c:   5d                      pop    %rbp
 61d:   c3                      retq