14

现代 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 来通过内联汇编和鞋拔可移植性自己完成它,或者我可以使用部分和来模拟乘法部分(这会显着变慢),但我想避免这种情况以提高可读性。当然存在一些内置函数可以做到这一点?

4

2 回答 2

21

对于 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_tuint64_t32。优化将适用于更旧的 gcc。

于 2012-11-02T00:59:16.310 回答
9

对于那些想知道问题的另一半(除法)的人,gcc 没有为此提供内在函数,因为处理器除法指令不符合标准。

对于 64 位 x86 目标的 128 位红利和 32 位 x86 目标的 64 位红利都是如此。问题是,在标准规定结果应该被截断的情况下,DIV 会导致除法溢出异常。例如(unsigned long long) (((unsigned _int128) 1 << 64) / 1)应该评估为 0,但如果使用 DIV 评估会导致除法溢出异常。

(感谢@ross-ridge提供此信息)

于 2018-09-22T12:12:13.583 回答