当前位置: 首页 > 知识库问答 >
问题:

在内联程序集的多个可选操作数约束之间进行选择时,GCC能否发出不同的指令助记符?

闻人和歌
2023-03-14
#include <stdint.h>
static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y)
{
    asm ("mulq %[y]" 
          : "=d" (*high), "=a" (*low)
          : "a" (x), [y] "rm" (y)    
        );
}

这段代码是正确的,但不是最优的。MULQ是可交换的,因此如果y恰好已经在RAX中,那么将y保留在其所在的位置并进行乘法是正确的。但是GCC不知道这一点,所以它会发出额外的指令将操作数移到它们预定义的位置。我想告诉GCC,它可以将任何一个输入放在任何一个位置,只要一个输入最终在RAX中,并且MULQ引用了另一个位置。GCC对此有一个语法,称为“多重替代约束”。请注意逗号(但是整个asm()是断开的;请参见下面):

asm ("mulq %[y]" 
      : "=d,d" (*high), "=a,a" (*low)
      : "a,rm" (x), [y] "rm,a" (y)    
    );

不幸的是,这是错误的。如果GCC选择第二个替代约束,它将发出“mulq%rax”。为了明确起见,请考虑以下函数:

uint64_t f()
{
    uint64_t high, low;
    uint64_t rax;
    asm("or %0,%0": "=a" (rax));
    mulq(&high, &low, 7, rax);
    return high;
}

使用gcc-o3-c-fkeep-inline-functions mulq.c编译后,GCC发出以下程序集:

0000000000000010 <f>:
  10:   or     %rax,%rax
  13:   mov    $0x7,%edx
  18:   mul    %rax
  1b:   mov    %rdx,%rax
  1e:   retq

如何重写这个内联asm以便在每种情况下都生成正确的输出?

共有1个答案

韩志专
2023-03-14

这个2012年的问题在2019年仍然很相关。尽管gcc已经发生了变化,一些生成的代码在2012年并不是最优的,但现在已经是了,相反的情况也是如此。

受Whitlock分析的启发,我在9种不同的情况下测试了MULQ,其中XY都是常量(56)或内存中的值(barZAR)或RAX(F1()F2()):

uint64_t h1() { uint64_t h, l; mulq(&h, &l,    5,    6); return h + l; }
uint64_t h2() { uint64_t h, l; mulq(&h, &l,    5,  bar); return h + l; }
uint64_t h3() { uint64_t h, l; mulq(&h, &l,    5, f1()); return h + l; }
uint64_t h4() { uint64_t h, l; mulq(&h, &l,  bar,    5); return h + l; }
uint64_t h5() { uint64_t h, l; mulq(&h, &l,  bar,  zar); return h + l; }
uint64_t h6() { uint64_t h, l; mulq(&h, &l,  bar, f1()); return h + l; }
uint64_t h7() { uint64_t h, l; mulq(&h, &l, f1(),    5); return h + l; }
uint64_t h8() { uint64_t h, l; mulq(&h, &l, f1(),  bar); return h + l; }
uint64_t h9() { uint64_t h, l; mulq(&h, &l, f1(), f2()); return h + l; }

我已经测试了5个实现:Staufk、Whitlock、Hale、Burdo和我自己的:

inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) {
    asm("mulq %[y]" : [a]"=a,a"(*low), "=d,d"(*high) : "%a,rm"(x), [y]"rm,a"(y) : "cc");
}

所有的实现仍然不能在所有情况下产生最优的代码。其他的代码不能为H3、H4H6生成最佳代码,而Whitlock和我的代码只能为H3生成最佳代码:

h3():
 callq 4004d0 <f1()>
 mov %rax,%r8
 mov $0x5,%eax
 mul %r8
 add %rdx,%rax
 retq 

在其他条件相同的情况下,可以看出我的比惠特洛克的简单。通过额外的间接级和使用GCC的内置函数(在clang中也可用,但我没有测试过),可以通过调用这个函数而不是mulq,获得最佳的h3:

inline void mulq_fixed(uint64_t* high, uint64_t* low, uint64_t x, uint64_t y) {
    if (__builtin_constant_p(x))
        mulq(high, low, y, x);
    else
        mulq(high, low, x, y);
}

产量:

h3():
 callq 4004d0 <f1()>
 mov $0x5,%edx
 mul %rdx
 add %rdx,%rax
 retq 

在模板中没有方法来确定选择了哪个替代方案。但是,您可以使用builtin_constant_p之类的builtin包装asm语句,以实现所需的结果。

您可以在编译器资源管理器中查看。

注意:Whitlock的实现还有另一个较小的、意想不到的缺点。您需要在编译器资源管理器中检查选项11010,否则输出是误导性的,函数h1、...、h9出现两次使用指令mulq。这是因为编译器资源管理器的解析器不能正确处理汇编指令.ifnc/.else/.endif,只需删除它们,并显示两个可能的路径(.if.else)。或者,您可以取消选中option.text。

 类似资料:
  • 我在函数中有以下代码: 现在我不知道为什么这不起作用。Gcc说:“错误:'asm'操作数有不可能的约束”我一直在学习Gcc内联汇编教程,我认为这是将参数从c代码带到内联汇编块的正确方法。 我还使用了为32位x86构建的gcc交叉编译器。

  • 我有一个包含一系列事件及其时间戳的数据库。 我在这里读到这是可以在SQLite中实现的,我想知道是否也可以在presto中实现。我查看了文档,但找不到一个类似的函数来执行SQLite中的操作。

  • 我必须在两个字符串变量中进行选择--第一个具有非值。如果它们都是-那么我想退出该方法。这可以在下面的一段代码中完成: 也可以用简短的形式完成: 我正在纠结如何用 注意:我只能使用Java 8语法(所以没有)

  • 尝试使用gcc:https://github.com/wolf9466/cpuminer-multi/blob/master/cryptonight_aesni.c编译此源文件时遇到此错误 “CRYPTONIGT_AESNI.c:162:4:错误:操作数约束不一致”

  • 问题内容: 我有两个mysql表-销售表: 和一个项目表: sales表包含每个 ItemID的 多个记录-每个 SaleWe​​ek一个 。我想通过加入两个表来选择出售的所有商品,如下所示: 但是,这将基于每个 SaleWe​​ek* 的多个条目返回多个 ItemId 值。我可以做一个单独的选择,只返回一个 ItemID 吗?-我不想查询最新的 SaleWe​​ek, 因为有些项目可能没有最新的

  • 这里是一个虚拟的*z++=*x++**y++指令。请注意,x、y和z指针寄存器必须指定为输入/输出,因为asm会修改它们。 在第一个示例中,在输入操作数中列出和有什么意义?同一份文件指出: 特别是,如果不将输入操作数指定为输出操作数,就无法指定输入操作数被修改。