【问题标题】:Conflicting register allocation in GCC x86_64 asm. Lifetime ignoredGCC x86_64 asm 中的寄存器分配冲突。终身忽略
【发布时间】:2019-12-18 10:53:15
【问题描述】:

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

void *ptr;
uint64_t foo(uint64_t arg1) {
  register uint64_t tmp;
  asm ("lea TARGET(%%rip), %[tmp];"
       "shl  $4, %[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    $0x4,%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 寄存器。

系统信息:

  • x86_64 CPU
  • gcc 版本 7.4.0
  • Ubuntu 18.04.3

我的问题:

  • 为什么 GCC 会创建这样的程序集输出?
  • 我对程序集输入和输出的约束是否正确?如果不是,为什么?
  • 这是 GCC 错误吗?

【问题讨论】:

  • manual says: "在所有不能与输入重叠的输出操作数上使用 '&' 约束修饰符(参见修饰符)。否则,GCC 可能会将输出操作数分配到相同的注册为不相关的输入操作数,假设汇编代码在产生输出之前消耗其输入。如果汇编代码实际上包含多条指令,则此假设可能是错误的。"
  • 这似乎完全相关。这意味着 GCC 没有使用编译器通常使用的那种生命周期分析。使用内联汇编来做这件事有真正的挑战吗?
  • @TSG 编译器不会检查您的内联程序集,因为它不能也不能。您必须进行此分析并将其作为汇编代码的约束呈现。这就是为什么我通常建议尽可能避免内联汇编。
  • 啊哈!有道理,尽管我们在很多工作中都依赖它,包括寄存器分配。
  • “不要使用内联汇编。很难做到正确,容易出错,并且让其他人难以维护。- David Wohlferd 2019 年 12 月 18 日”。去我的墙上。 @DavidWohlferd

标签: c gcc assembly inline-assembly


【解决方案1】:

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

manual 说:“在所有不能与输入重叠的输出操作数上使用 '&' 约束修饰符(请参阅修饰符)。否则,GCC 可能会将输出操作数分配到与不相关的输入操作数相同的寄存器中,假设汇编代码在产生输出之前消耗其输入。如果汇编代码实际上包含多条指令,则此假设可能是错误的。"

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

修复:

-       : [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    $0x4,%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   

【讨论】:

  • 这个内联汇编代码中的约束还有一个问题,这个答案没有提到。 arg1 仅作为约束中的输入列出,但由汇编代码修改。
  • @prl,你可能是对的。然而,它在其余代码中完全不相关,所以我就让它撒谎,不要让这个 QA 进一步复杂化
猜你喜欢
  • 2018-06-16
  • 1970-01-01
  • 2012-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
  • 2023-03-11
  • 1970-01-01
相关资源
最近更新 更多