如何让GCC将“移动r10,r3;存储r10”合并到“存储r3”?

时间:2018-11-27 00:24:29

标签: gcc inline-assembly powerpc

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

1 个答案:

答案 0 :(得分:2)

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一起使用。如果要避免使用{{1},也可以使用register uint64_t foo __asm__("r3"); }关键字。您可能仍需要将asm视为使用此扩展名的源中的保留字;没关系,ISO C ++从基本语言中删除它不会强制实现 not 使用它作为扩展的一部分。)


或者更好的是,不要对注册号进行硬编码。使用支持DARN指令的汇编器。 (但显然,它是如此之新,以至于最新的clang也没有它,并且您只希望将此内联asm作为gcc的后备版本,以至于无法支持the __builtin_darn() intrinsic


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

但是请注意,"+r"(foo)的输出寄存器是只写的。无需先将darn设为零。我找到了IBM的POWER ISA指令集手册的副本,该手册足够新,可以在此处包含r3https://wiki.raptorcs.com/w/images/c/cb/PowerISA_public.v3.0B.pdf#page=96

实际上,您根本不需要在asm中循环,您可以将其留给C并且包装一条asm指令,就像设计inline-asm一样。

darn

干净地(on the Godbolt compiler explorer)编译为

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;
}

只需更少的设置就可以与循环一样紧密。 (您确定您甚至需要在asm指令之前将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 设为零吗?)

此函数可以内联到您想要的任何位置,从而允许gcc发出直接读取r3的存储指令。


在实践中,您将按照手册中的建议使用重试计数器:如果硬件RNG损坏,则可能永远导致失败,因此您应该使用PRNG。 (与x86的r3相同)

  

提供随机数(rdrand)-编程说明

     

获得错误值时,软件为   希望重复该操作。如果没有错误   经过几次尝试仍未获得价值,   软件随机数生成方法   应该使用。推荐数量   尝试可能是特定于实现的。在里面   如果没有其他指导,应尝试十次   足够。


darn-归零在大多数固定指令宽度的ISA上效率不高,因为移动立即数很短,因此无需检测和特殊化xor 。 (因此,CPU设计不会在其上花费晶体管)。此外,C ++ 11 xor require 的PPC asm依赖项规则对输入寄存器具有依赖项,因此即使设计人员也不能破坏依赖项想要它。异或归零只是x86上的一件事,也许还有其他一些可变宽度的ISA。

std::memory_order_consume https://godbolt.org/z/-gHI4C一样使用li r3, 0