如何让 GCC 将 "move r10, r3; store r10" 组合成 "store r3"?
How to have GCC combine "move r10, r3; store r10" into a "store r3"?
我正在使用 Power9 并使用称为 DARN 的硬件随机数生成器指令。我有以下内联程序集:
uint64_t val;
__asm__ __volatile__ (
"xor 3,3,3 \n" // r3 = 0
"addi 4,3,-1 \n" // r4 = -1, failure
"1: \n"
".byte 0xe6, 0x05, 0x61, 0x7c \n" // r3 = darn 3, 1
"cmpd 3,4 \n" // r3 == -1?
"beq 1b \n" // retry on failure
"mr %0,3 \n" // val = r3
: "=g" (val) : : "r3", "r4", "cc"
);
我不得不添加 mr %0,3
和 "=g" (val)
,因为我无法让 GCC 使用 "=r3" (val)
生成预期的代码。另见 Error: matching constraint not valid in output operand.
反汇编显示:
(gdb) b darn.cpp : 36
(gdb) r v
...
Breakpoint 1, DARN::GenerateBlock (this=<optimized out>,
output=0x7fffffffd990 "\b", size=0x100) at darn.cpp:77
77 DARN64(output+i*8);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.ppc64le libgcc-4.8.5-28.el7_5.1.ppc64le libstdc++-4.8.5-28.el7_5.1.ppc64le
(gdb) disass
Dump of assembler code for function DARN::GenerateBlock(unsigned char*, unsigned long):
...
0x00000000102442b0 <+48>: addi r10,r8,-8
0x00000000102442b4 <+52>: rldicl r10,r10,61,3
0x00000000102442b8 <+56>: addi r10,r10,1
0x00000000102442bc <+60>: mtctr r10
=> 0x00000000102442c0 <+64>: xor r3,r3,r3
0x00000000102442c4 <+68>: addi r4,r3,-1
0x00000000102442c8 <+72>: darn r3,1
0x00000000102442cc <+76>: cmpd r3,r4
0x00000000102442d0 <+80>: beq 0x102442c8 <DARN::GenerateBlock(unsigned char*, unsigned long)+72>
0x00000000102442d4 <+84>: mr r10,r3
0x00000000102442d8 <+88>: stdu r10,8(r9)
注意 GCC 忠实地再现了:
0x00000000102442d4 <+84>: mr r10,r3
0x00000000102442d8 <+88>: stdu r10,8(r9)
如何让 GCC 将两条指令折叠成:
0x00000000102442d8 <+84>: stdu r3,8(r9)
GCC 永远不会删除作为 asm 模板一部分的文本;除了替换 %operand
之外,它甚至不解析它。它实际上只是在将 asm 发送到汇编器之前的文本替换。
你必须从你的内联 asm 模板中省略 mr
,并告诉 gcc 你的输出在 r3
中(或使用内存目标输出操作数,但不要这样做那)。如果您的内联汇编模板以 mov
指令开始或结束,那么您通常做错了。
使用 register uint64_t foo asm("r3");
强制 "=r"(foo)
在没有特定寄存器限制的平台上选择 r3
。
(尽管 ISO C++17 删除了 register
关键字,这个 GNU 扩展仍然适用于 -std=c++17
。如果你想避免 asm
关键字。您可能仍然需要将 register
视为使用此扩展的源代码中的保留字;没关系。ISO C++ 将其从基本语言中删除不会强制实现 not 将其用作扩展的一部分。)
或者更好的是,不要对寄存器编号进行硬编码。使用支持 DARN 指令的汇编器。 (但显然它太新了,即使是最新的 clang 也缺少它,你只希望这个内联 asm 作为 gcc 的后备,因为 gcc 太旧而无法支持 the __builtin_darn()
intrinsic)
使用这些约束也可以让您删除寄存器设置,在内联 asm 语句之前使用 foo=0
/ bar=-1
,并使用 "+r"(foo)
.
但是注意darn
的输出寄存器是只写的。无需先将 r3
归零。我找到了一份 IBM 的 POWER ISA 指令集手册,它足够新,可以在此处包含 darn
:https://wiki.raptorcs.com/w/images/c/cb/PowerISA_public.v3.0B.pdf#page=96
实际上,您根本不需要在 asm 内部循环,您可以将其留给 C,仅 包装一条 asm 指令,就像 inline-asm 是为 .
设计的
uint64_t random_asm() {
register uint64_t val asm("r3");
do {
//__asm__ __volatile__ ("darn 3, 1");
__asm__ __volatile__ (".byte 0x7c, 0x61, 0x05, 0xe6 # gcc asm operand = %0\n" : "=r" (val));
} while(val == -1ULL);
return val;
}
干净地编译 (on the Godbolt compiler explorer) 到
random_asm():
.L6: # compiler-generated label, no risk of name clashes
.byte 0x7c, 0x61, 0x05, 0xe6 # gcc asm operand = 3
cmpdi 7,3,-1 # compare-immediate
beq 7,.L6
blr
与您的循环一样紧凑,设置更少。 (你确定你甚至需要在 asm 指令之前将 r3
置零吗?)
这个函数可以内联到任何你想要的地方,允许 gcc 发出一条存储指令,直接读取 r3
。
在实践中,您需要按照手册中的建议使用重试计数器:如果硬件 RNG 损坏,可能会永远失败,因此您应该回退到 PRNG。 (与 x86 相同 rdrand
)
Deliver A Random Number (darn
) - Programming Note
When the error value is obtained, software is
expected to repeat the operation. If a non-error
value has not been obtained after several attempts,
a software random number generation method
should be used. The recommended number of
attempts may be implementation specific. In the
absence of other guidance, ten attempts should be
adequate.
xor
-归零在大多数固定指令宽度的 ISA 上效率不高,因为 mov-immediate 一样短,所以不需要检测和特殊情况异或。 (因此 CPU 设计不会在其上花费晶体管)。此外,等同于 C++11 std::memory_order_consume
的 PPC asm 的依赖规则要求 它对输入寄存器具有依赖性,因此它不能打破依赖性如果设计师想要的话。异或归零只是 x86 上的事情,也许还有一些其他可变宽度的 ISA。
像 gcc 对 int foo(){return 0;}
https://godbolt.org/z/-gHI4C.
那样使用 li r3, 0
我正在使用 Power9 并使用称为 DARN 的硬件随机数生成器指令。我有以下内联程序集:
uint64_t val;
__asm__ __volatile__ (
"xor 3,3,3 \n" // r3 = 0
"addi 4,3,-1 \n" // r4 = -1, failure
"1: \n"
".byte 0xe6, 0x05, 0x61, 0x7c \n" // r3 = darn 3, 1
"cmpd 3,4 \n" // r3 == -1?
"beq 1b \n" // retry on failure
"mr %0,3 \n" // val = r3
: "=g" (val) : : "r3", "r4", "cc"
);
我不得不添加 mr %0,3
和 "=g" (val)
,因为我无法让 GCC 使用 "=r3" (val)
生成预期的代码。另见 Error: matching constraint not valid in output operand.
反汇编显示:
(gdb) b darn.cpp : 36
(gdb) r v
...
Breakpoint 1, DARN::GenerateBlock (this=<optimized out>,
output=0x7fffffffd990 "\b", size=0x100) at darn.cpp:77
77 DARN64(output+i*8);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.ppc64le libgcc-4.8.5-28.el7_5.1.ppc64le libstdc++-4.8.5-28.el7_5.1.ppc64le
(gdb) disass
Dump of assembler code for function DARN::GenerateBlock(unsigned char*, unsigned long):
...
0x00000000102442b0 <+48>: addi r10,r8,-8
0x00000000102442b4 <+52>: rldicl r10,r10,61,3
0x00000000102442b8 <+56>: addi r10,r10,1
0x00000000102442bc <+60>: mtctr r10
=> 0x00000000102442c0 <+64>: xor r3,r3,r3
0x00000000102442c4 <+68>: addi r4,r3,-1
0x00000000102442c8 <+72>: darn r3,1
0x00000000102442cc <+76>: cmpd r3,r4
0x00000000102442d0 <+80>: beq 0x102442c8 <DARN::GenerateBlock(unsigned char*, unsigned long)+72>
0x00000000102442d4 <+84>: mr r10,r3
0x00000000102442d8 <+88>: stdu r10,8(r9)
注意 GCC 忠实地再现了:
0x00000000102442d4 <+84>: mr r10,r3
0x00000000102442d8 <+88>: stdu r10,8(r9)
如何让 GCC 将两条指令折叠成:
0x00000000102442d8 <+84>: stdu r3,8(r9)
GCC 永远不会删除作为 asm 模板一部分的文本;除了替换 %operand
之外,它甚至不解析它。它实际上只是在将 asm 发送到汇编器之前的文本替换。
你必须从你的内联 asm 模板中省略 mr
,并告诉 gcc 你的输出在 r3
中(或使用内存目标输出操作数,但不要这样做那)。如果您的内联汇编模板以 mov
指令开始或结束,那么您通常做错了。
使用 register uint64_t foo asm("r3");
强制 "=r"(foo)
在没有特定寄存器限制的平台上选择 r3
。
(尽管 ISO C++17 删除了 register
关键字,这个 GNU 扩展仍然适用于 -std=c++17
。如果你想避免 asm
关键字。您可能仍然需要将 register
视为使用此扩展的源代码中的保留字;没关系。ISO C++ 将其从基本语言中删除不会强制实现 not 将其用作扩展的一部分。)
或者更好的是,不要对寄存器编号进行硬编码。使用支持 DARN 指令的汇编器。 (但显然它太新了,即使是最新的 clang 也缺少它,你只希望这个内联 asm 作为 gcc 的后备,因为 gcc 太旧而无法支持 the __builtin_darn()
intrinsic)
使用这些约束也可以让您删除寄存器设置,在内联 asm 语句之前使用 foo=0
/ bar=-1
,并使用 "+r"(foo)
.
但是注意darn
的输出寄存器是只写的。无需先将 r3
归零。我找到了一份 IBM 的 POWER ISA 指令集手册,它足够新,可以在此处包含 darn
:https://wiki.raptorcs.com/w/images/c/cb/PowerISA_public.v3.0B.pdf#page=96
实际上,您根本不需要在 asm 内部循环,您可以将其留给 C,仅 包装一条 asm 指令,就像 inline-asm 是为 .
设计的uint64_t random_asm() {
register uint64_t val asm("r3");
do {
//__asm__ __volatile__ ("darn 3, 1");
__asm__ __volatile__ (".byte 0x7c, 0x61, 0x05, 0xe6 # gcc asm operand = %0\n" : "=r" (val));
} while(val == -1ULL);
return val;
}
干净地编译 (on the Godbolt compiler explorer) 到
random_asm():
.L6: # compiler-generated label, no risk of name clashes
.byte 0x7c, 0x61, 0x05, 0xe6 # gcc asm operand = 3
cmpdi 7,3,-1 # compare-immediate
beq 7,.L6
blr
与您的循环一样紧凑,设置更少。 (你确定你甚至需要在 asm 指令之前将 r3
置零吗?)
这个函数可以内联到任何你想要的地方,允许 gcc 发出一条存储指令,直接读取 r3
。
在实践中,您需要按照手册中的建议使用重试计数器:如果硬件 RNG 损坏,可能会永远失败,因此您应该回退到 PRNG。 (与 x86 相同 rdrand
)
Deliver A Random Number (
darn
) - Programming NoteWhen the error value is obtained, software is expected to repeat the operation. If a non-error value has not been obtained after several attempts, a software random number generation method should be used. The recommended number of attempts may be implementation specific. In the absence of other guidance, ten attempts should be adequate.
xor
-归零在大多数固定指令宽度的 ISA 上效率不高,因为 mov-immediate 一样短,所以不需要检测和特殊情况异或。 (因此 CPU 设计不会在其上花费晶体管)。此外,等同于 C++11 std::memory_order_consume
的 PPC asm 的依赖规则要求 它对输入寄存器具有依赖性,因此它不能打破依赖性如果设计师想要的话。异或归零只是 x86 上的事情,也许还有一些其他可变宽度的 ISA。
像 gcc 对 int foo(){return 0;}
https://godbolt.org/z/-gHI4C.
li r3, 0