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 寄存器。
系统信息:
- x86_64 CPU
- gcc 版本 7.4.0
- Ubuntu 18.04.3
我的问题:
- 为什么 GCC 会创建这样的程序集输出?
- 我对程序集输入和输出的约束是否正确?如果不是,为什么?
- 这是 GCC 错误吗?
首先,感谢@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
我有一个包含小程序块的函数。由 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 寄存器。
系统信息:
- x86_64 CPU
- gcc 版本 7.4.0
- Ubuntu 18.04.3
我的问题:
- 为什么 GCC 会创建这样的程序集输出?
- 我对程序集输入和输出的约束是否正确?如果不是,为什么?
- 这是 GCC 错误吗?
首先,感谢@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