扩展除法/乘法的 gcc 内在函数
Posted
技术标签:
【中文标题】扩展除法/乘法的 gcc 内在函数【英文标题】:gcc intrinsic for extended division/multiplication 【发布时间】:2012-11-02 00:35:42 【问题描述】:现代 CPU 可以在两个原生大小的字之间执行扩展乘法,并将低位和高位结果存储在单独的寄存器中。同样,在执行除法时,它们将商和余数存储在两个不同的寄存器中,而不是丢弃不需要的部分。
是否存在某种可移植的 gcc 内在函数,它会采用以下签名:
void extmul(size_t a, size_t b, size_t *lo, size_t *hi);
或者类似的东西,对于除法:
void extdiv(size_t a, size_t b, size_t *q, size_t *r);
我知道我可以通过在代码中添加#ifdef 来通过内联汇编和鞋拔可移植性自行完成,或者我可以使用部分和来模拟乘法部分(这会慢得多),但我想避免这种情况为了可读性。肯定有一些内置函数可以做到这一点?
【问题讨论】:
但是当使用-O3
进行优化时,GCC 可能会发出正确的特定于处理器的扩展乘法或除法(甚至可能同时使用模数和余数,如果您的程序同时使用两者),所以我不会打扰在你的代码中。
@BasileStarynkevitch 但是您需要 128 位整数支持 - GCC 没有 x64 支持。您无法在 C 中仅使用 64 位整数“欺骗”GCC 以发出 64x64 -> 128 位乘法或 128 位/64 位 -> 64 位除法。
如果您需要 128 位,gcc 可能有一些扩展类型,例如 __int128
或者可以在没有 cpu 特定代码的情况下为您提供所需效果的东西。对于 64 位(在 32 位机器上),只需使用 long long
即可。
FWIW,MSVC 具有您正在寻找的扩展乘法的内在特性 (64x64 ->128)。在 GCC x64 中,我只使用内联汇编宏。
@Mystical 您提供的链接中的“整数模式”与本机整数宽度不对应,而是“机器模式”。大多数 64 位目标实际上都支持 QImode,即 128 位。特别是在 x86_64 128 位整数上工作得很好。
【参考方案1】:
对于 4.6 版以来的 gcc,您可以使用 __int128
。这适用于大多数 64 位硬件。比如
要获得 64x64 位乘法的 128 位结果,只需使用
void extmul(size_t a, size_t b, size_t *lo, size_t *hi)
__int128 result = (__int128)a * (__int128)b;
*lo = (size_t)result;
*hi = result >> 64;
在 x86_64 上,gcc 足够聪明,可以将其编译为
0: 48 89 f8 mov %rdi,%rax
3: 49 89 d0 mov %rdx,%r8
6: 48 f7 e6 mul %rsi
9: 49 89 00 mov %rax,(%r8)
c: 48 89 11 mov %rdx,(%rcx)
f: c3 retq
不需要原生 128 位支持或类似支持,在内联后只剩下 mul
指令。
编辑:在 32 位架构上,这以类似的方式工作,您需要将 __int128_t
替换为 uint64_t
并将移位宽度替换为 32。优化将适用于更旧的 gcc。
【讨论】:
那么,如果不使用一堆#ifdef,真的没有办法让任何字长(不仅仅是64位)都能正常工作吗?如果没有,我会接受这个答案.. 如果您需要 64 位结果的两个 32 位部分,这将以类似的方式在 32 位拱门上工作。您需要将移位宽度设为 32 位 - 我确信在 stdlib 中定义了一个适当的宏 @Thomas 可能缺少的可能是dsize_t
之类的东西,这将是一个双宽度 size_t
类型 - 就像 64/32 位拱门上的 __int128
或 uint64_t
。跨度>
@BasileStarynkevitch 刚刚测试过,gcc-4.5 不知道 128bit ints,gcc-4.6 可以。
为了完整性:英特尔 ICC 版本 13.0 也可以识别 __int128
(和/或 __int128_t
),而 ICC 12.1 及更低版本不知道。【参考方案2】:
对于那些想知道问题的另一半(除法)的人,gcc 没有为此提供内在函数,因为处理器除法指令不符合标准。
对于 64 位 x86 目标的 128 位除数和 32 位 x86 目标的 64 位除数都是如此。问题是,在标准规定结果应该被截断的情况下,DIV 会导致除法溢出异常。例如(unsigned long long) (((unsigned _int128) 1 << 64) / 1)
应该计算为 0,但如果使用 DIV 计算会导致除法溢出异常。
(感谢@ross-ridge 提供此信息)
【讨论】:
这是不将 64/32 优化为 idiv 的原因,而不是不提供内在函数 的原因。 GCC 有许多__builtin
函数调用带有一些参数的未定义行为。以上是关于扩展除法/乘法的 gcc 内在函数的主要内容,如果未能解决你的问题,请参考以下文章