gcc内在的扩展除法/乘法

Tho*_*mas 12 c gcc

现代CPU可以在两个原生大小的字之间执行扩展乘法,并将低和高结果存储在单独的寄存器中.类似地,当执行除法时,它们将商和余数存储在两个不同的寄存器中,而不是丢弃不需要的部分.

是否存在某种可移植的gcc内在函数,它将采用以下签名:

void extmul(size_t a, size_t b, size_t *lo, size_t *hi);
Run Code Online (Sandbox Code Playgroud)

或类似的东西,以及分裂:

void extdiv(size_t a, size_t b, size_t *q, size_t *r);
Run Code Online (Sandbox Code Playgroud)

我知道我可以通过在代码中抛出#ifdef来使用内联汇编和shoehorn可移植性来实现它,或者我可以使用部分和来模拟乘法部分(这将显着更慢)但我想避免这样做以便于阅读.当然有一些内置函数可以做到这一点?

Gun*_*iez 18

对于4.6版以后的gcc,您可以使用__int128.这适用于大多数64位硬件.例如

要获得64位64位乘法的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;
}
Run Code Online (Sandbox Code Playgroud)

在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   
Run Code Online (Sandbox Code Playgroud)

不需要本机128位支持或类似,并且在内联之后仅mul保留指令.

编辑:在32位拱形上,它以类似的方式工作,您需要替换__int128_tuint64_t和移位宽度为32.优化将适用于更老的gcc.

  • @Thomas 可能缺少的是像 `dsize_t` 这样的东西,它是双倍宽度的 `size_t` 类型——比如 64/32 位拱上的 `__int128` 或 `uint64_t`。 (2认同)

kle*_*tog 8

对于那些想知道问题的另一半(除法)的人来说,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”函数,它们通过一些参数调用未定义的行为。 (2认同)