【发布时间】: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