【问题标题】:How to have GCC combine "move r10, r3; store r10" into a "store r3"?如何让 GCC 将“移动 r10,r3;存储 r10”组合成“存储 r3”?
【发布时间】:2018-11-27 00:24:28
【问题描述】:

我正在使用 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)

【问题讨论】:

  • 你必须从你的内联汇编模板中去掉mr,并告诉gcc你的输出在r3中。如有必要,在没有特定寄存器约束的平台上使用register int foo asm("r3");。 Gcc 将从不删除作为 asm 模板一部分的文本;除了替换 %thing 之外,它甚至不解析它。或者更好的是,不要硬编码注册号。使用支持DARN 指令的汇编程序。
  • 另外,xor 不是在 PowerPC 上将寄存器归零的有效方法。 PPC asm 等效于 C++11 std::memory_order_consume 的依赖规则要求它携带对输入寄存器的依赖。 xor-zeroing 仅适用于 x86,而不适用于固定指令宽度的 ISA。
  • 谢谢彼得。 “或者更好......使用支持 DARN 指令的汇编程序” - 我明白你在说什么,但这几乎是不可能的。编译场上的 GCC135 是 Power9 和 2 个月大。它有 CentOS 7 和 GCC 4.8。编译器和汇编器比我们需要的支持落后了十年。 Clang 7.0 还有lacks support 我们需要。
  • 关于 XOR 效率不高,您有什么建议?一个简单的li 3, 0?
  • 是的,没错。这就是 gcc 为 int foo(){return 0;} godbolt.org/z/-gHI4C 所做的。 ARM 的相同交易:mov-immediate。

标签: gcc inline-assembly powerpc


【解决方案1】:

GCC 永远不会删除作为 asm 模板一部分的文本;除了替换 %operand 之外,它甚至不解析它。它实际上只是在将 asm 发送到汇编器之前的文本替换。

您必须从您的内联 asm 模板中省略 mr,并告诉 gcc 您的输出在 r3 中(或使用内存目标输出操作数,但不要这样做)。如果您的 inline-asm 模板曾经以 mov 指令开头或结尾,那么您通常做错了。

在没有特定寄存器限制的平台上使用register uint64_t foo asm("r3"); 强制"=r"(foo) 选择r3

(尽管 ISO C++17 删除了 register 关键字,但此 GNU 扩展仍然适用于 -std=c++17。如果您想避免使用 asm 关键字,也可以使用 register uint64_t foo __asm__("r3");。您可能仍然需要将register 视为使用此扩展的源代码中的保留字;这很好。ISO C++ 将其从基础语言中删除不会强制实现将其用作扩展的一部分。)


或者更好的是,不要硬编码注册号。使用支持 DARN 指令的汇编程序。 (但显然它太新了,即使是最新的 clang 也缺少它,你只希望这个内联 asm 作为 gcc 的后备,因为 gcc 太旧而无法支持the __builtin_darn() intrinsic


使用这些约束也可以让您删除寄存器设置,并在内联 asm 语句之前使用 foo=0 / bar=-1,并使用 "+r"(foo)

但请注意darn 的输出寄存器是只写的。无需先将r3 归零。我找到了一份 IBM 的 POWER ISA 指令集手册,它足够新,可以在此处包含 darnhttps://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 也一样)

提供一个随机数 (darn) - 编程笔记

当得到错误值时,软件是 预计会重复该操作。如果一个非错误 多次尝试后仍未获得值, 一种软件随机数生成方法 应该使用。推荐数量 尝试可能是特定于实现的。在里面 没有其他指导,应尝试十次 足够了。


xor-zeroing 在大多数固定指令宽度的 ISA 上效率不高,因为 mov-immediate 一样短,因此不需要检测和特殊情况下的异或。 (因此 CPU 设计不会在其上使用晶体管)。此外,PPC asm 的依赖规则相当于 C++11 std::memory_order_consume 要求 它携带对输入寄存器的依赖,因此即使设计人员想要它也不会破坏依赖到。 xor-zeroing 仅适用于 x86 和其他一些可变宽度 ISA。

使用 li r3, 0,就像 gcc 对 int foo(){return 0;} https://godbolt.org/z/-gHI4C 所做的那样。

【讨论】:

  • 你是否也推荐li 4, -1而不是addi 4,3,-1
  • @prl - 是的,我已经做出了改变。我陷入了最初的问题,即如何将常量0xffffffff ffffffff 加载到寄存器中。 0-1 似乎是一个不错的选择,直到我知道我可以加载立即数 -1。
  • 现在在 LLVM 错误跟踪器中打开:问题错误 39800,Clang 7.0 missing Power9 __builtin_darn and friends
  • @prl:是的,当然。但实际上我建议让编译器通过在 asm 中使用 [minus_one] "ri"(-1LL) 输入约束和 cmp 0,0,3,%[minus_one] 来完成它。您只需为您手动编码的指令中涉及的一个寄存器强制分配寄存器。
  • 我想我会尝试删除 __volatile__ 并给 GCC 一个移动的机会。 GCC 删除了大部分代码,因此缓冲区不是随机的。 GCC 内联汇编器绝对烂。如果我发布了一个糟糕透顶的工具,我会很尴尬。我们不仅不能表达我们想要做什么,我们还必须使用 volatile 来防止 GCC 破坏。
猜你喜欢
  • 2016-10-03
  • 2023-01-16
  • 2013-04-06
  • 1970-01-01
  • 2021-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多