我正在尝试为GCC编写内联x86-64程序集以有效地使用MULQ指令 . MULQ将64位寄存器RAX与另一个64位值相乘 . 另一个值可以是任何64位寄存器(甚至是RAX)或内存中的值 . MULQ将产品的高64位放入RDX,将低64位放入RAX .
现在,很容易表达一个正确的mulq作为内联汇编:
#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有一个语法,称为"multiple alternative constraints" . 注意逗号(但是整个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
“mul%rax”应为“mul%rdx” .
如何重写这个内联asm,以便在每种情况下生成正确的输出?
3 回答
这类似于各种GNU包中包含的
longlong.h
中的内容;"r,m"
而不是"rm"
真的是为了clang的利益 . 对于clang,多重约束语法似乎仍然很重要,如here所述 . 这是一种耻辱,但我仍然发现clang在约束匹配(尤其是在x86 [-86]上)比gcc更糟糕 . 对于gcc:这将是足够的,并且有利于将
(y)
保留在登记簿中,除非登记压力太高;但在许多情况下,铿锵似乎总是泄漏 . 我的测试显示它将在多重约束语法中选择第一个选项"r"
."%3"
作为指令中的被乘数允许寄存器(偏好)或存储器位置,作为第三个操作数的别名,相对于零,即(y)
."0"
别名为'zero-th'操作数:(*low)
,明确为"a"
,即%rax
为64位 ."%0"
中的前导%
字符是可交换运算符:即,(x)可以与(y)通信,如果这有助于寄存器分配 . 显然,mulq
是可交换的:x * y == y * x
.我们实际上在这里受到很大限制 .
mulq
将64位操作数%3
乘以%rax
中的值以生成128位乘积:%rdx:%rax
."0" (x)
意味着必须将(x)
加载到%rax
中,并且必须将(y)
加载到64位寄存器或内存地址中 . 但是%0
表示(x)
,以下输入(y)
可能会通勤 .我也会参考我发现的best practical inline assembly tutorial . 虽然gcc引用是'authoritative',但它们是一个糟糕的教程 .
感谢Chris在原始约束排序中拾取错误 .
使用这样的技巧:
注意输入操作数
a
的"1"
参数规范 . 这意味着"put 'a' into the same place where argument #1 is" .Brett Hale的answer在某些情况下产生次优代码(至少在GCC 5.4.0上) .
鉴于:
然后
mulq(&high, &low, foo(), 42)
编译为:......这是最佳的 .
但现在颠倒操作数的顺序:
...看看编译代码会发生什么:
哎呀!发生了什么?编译器坚持在
rax
中放入42,因此它必须将foo()
的返回值移出rax
. 显然,%
(可交换)操作数约束是有缺陷的 .有没有办法优化这个?事实证明,虽然它有点混乱 .
现在
mulq(&high, &low, foo(), 42)
编译为:并且
mulq(&high, &low, 42, foo())
编译为:此代码使用汇编程序技巧来解决GCC不允许我们发出不同汇编代码的限制,具体取决于它所选择的约束 . 在每种情况下,汇编器将只发出两个可能的
mulq
指令中的一个,具体取决于编译器是否选择将x
或y
放在rax
中 .可悲的是,如果我们这样做,这个技巧是不理想的将
foo()
的返回值乘以内存位置的值:现在
mulq(&high, &low, bar, foo())
编译为:...这是最佳的,但
mulq(&high, &low, foo(), bar)
编译为:......不必要地将
bar
复制到rbx
.遗憾的是,我无法在所有情况下找到使GCC输出最佳代码的方法 . 为了调查,强制乘法器为内存操作数,只会导致GCC将
bar(%rip)
加载到寄存器中,然后将该寄存器存储到临时堆栈位置,然后传递给mulq
.