MSVC 在 GCC/CLang 中的内在函数 __emulu 和 _umul128

问题描述

在 MSVC 中存在内在函数 __emulu()_umul128()。首先进行 u32*u32->u64 乘法和第二次 u64*u64->u128 乘法。

CLang/GCC 是否存在相同的内在函数

我发现最接近的是 Intel's Guide 中提到的 _mulx_u32()_mulx_u64()。但是它们产生需要 BMI2 支持mulx 指令。虽然 MSVC 的内在函数产生常规的 mul 指令。此外,_mulx_u32()-m64 模式下不可用,而 __emulu()_umul128() 在 MSVC 的 32 位和 64 位模式下都存在。

您可以在线尝试32-bit code64-bit code

32 位的原因可能是 return uint64_t(a) * uint64_t(b);(参见 online),希望编译器能够正确猜测并优化使用 u32*u32->u64 乘法而不是 u64*u64->u64。但是有没有办法确定这一点?不要依赖编译器的猜测,即两个参数都是 32 位的(即 uint64_t 的较高部分为零)?拥有一些内在函数,例如 __emulu(),可以让您对代码有把握。

在 GCC/CLang 中有 __int128(参见代码 online),但我们再次依赖编译器的猜测,我们实际上乘以 64 位数字(即 int128 的较高部分为零)。如果存在一些内在函数,有没有一种方法可以确定而无需编译器猜测?

顺便说一句,uint64_t(用于 32 位)和 __int128(用于 64 位)在 GCC/CLang 中生成正确的 mul 指令而不是 mulx。但是我们又不得不依赖编译器正确猜测 uint64_t__int128 的较高部分为零。

当然,我可以查看 GCC/Clang 已优化并正确猜测的汇编代码,但是查看一次汇编代码并不能保证在所有情况下都会发生相同的情况。而且我不知道在 C++ 中有什么方法可以静态断言编译器对汇编指令的猜测是正确的。

解决方法

你已经有了答案。使用 uint64_t__uint128_t。不需要内在函数。这适用于所有 64 位目标的现代 GCC 和 Clang。见Is there a 128 bit integer in gcc?

#include <stdint.h>
typedef __uint128_t uint128_t;

// 32*32=64 multiplication
f(uint32_t a,uint32_t b) {
   uint64_t ab = (uint64_t)a * b;
}

//64*64=128 multiplication
f(uint64_t a,uint64_t b) {
    uint128_t ab = (uint128_t)a * b;
}

请注意,强制转换必须在操作数上,或至少在一个操作数上。转换结果不起作用,因为它会与较短的类型进行乘法运算并扩展结果。

但是有没有办法确定这一点?不要依赖编译器的猜测

您得到与编译器内在函数完全相同的保证:结果的值是正确的。从来没有任何关于优化的保证。仅仅因为您使用了内部函数并不能保证编译器会发出“明显的”汇编指令。获得这种保证的唯一方法是使用内联汇编,对于像这样的简单操作,它可能会损害性能,因为它会限制编译器优化寄存器使用的方式。