mul
(或只有一个操作数的imul
)指令。例如:uint64_t x,y;
unsigned __int128 z = (unsigned __int128)x*y;
产生mul
。 我用它创建了一个128x128到256的函数(如果您感兴趣,请参见本问题末尾的代码)。
现在我想进行256位加法,但我没有找到编译器使用ADC
的方法,除非使用汇编语言。 我可以使用汇编程序,但我希望使用内联函数提高效率。 编译器已经为128x128到256函数生成了高效的代码(原因请参见本问题开头),因此我不明白为什么我还应该在汇编中重新编写这个函数(或任何其他编译器已经有效实现的函数)。
这是我想出来的内联汇编函数:
#define ADD256(X1, X2, X3, X4, Y1, Y2, Y3, Y4) \
__asm__ __volatile__ ( \
"addq %[v1], %[u1] \n" \
"adcq %[v2], %[u2] \n" \
"adcq %[v3], %[u3] \n" \
"adcq %[v4], %[u4] \n" \
: [u1] "+&r" (X1), [u2] "+&r" (X2), [u3] "+&r" (X3), [u4] "+&r" (X4) \
: [v1] "r" (Y1), [v2] "r" (Y2), [v3] "r" (Y3), [v4] "r" (Y4))
(可能不是每个输出都需要 early clobber modifier,但如果没有至少最后两个,我会得到错误的结果)。 (编辑注:只有在读取所有输入之后才会编写最后一个输出,并且可以安全地不声明为early-clobber。)
这里是一个用C语言实现同样功能的函数
void add256(int256 *x, int256 *y) {
uint64_t t1, t2;
t1 = x->x1; x->x1 += y->x1;
t2 = x->x2; x->x2 += y->x2 + ((x->x1) < t1);
t1 = x->x3; x->x3 += y->x3 + ((x->x2) < t2);
x->x4 += y->x4 + ((x->x3) < t1);
}
为什么需要汇编?为什么编译器不能将
add256
函数编译成使用进位标志的形式?有没有办法强制编译器这样做(例如,我可以更改add256
以实现此目的)?如果编译器不支持内联汇编怎么办(需要全部用汇编语言编写函数吗)?为什么没有针对此功能的内在函数?这是128x128到256函数:void muldwu128(int256 *w, uint128 u, uint128 v) {
uint128 t;
uint64_t u0, u1, v0, v1, k, w1, w2, w3;
u0 = u >> 64L;
u1 = u;
v0 = v >> 64L;
v1 = v;
t = (uint128)u1*v1;
w3 = t;
k = t >> 64L;
t = (uint128)u0*v1 + k;
w2 = t;
w1 = t >> 64L;
t = (uint128)u1*v0 + w2;
k = t >> 64L;
w->hi = (uint128)u0*v0 + w1 + k;
w->lo = (t << 64L) + w3;
}
一些类型定义:
typedef __int128 int128;
typedef unsigned __int128 uint128;
typedef union {
struct {
uint64_t x1;
uint64_t x2;
int64_t x3;
int64_t x4;
};
struct {
uint128 lo;
int128 hi;
};
} int256;
更新:
我的问题在很大程度上是以下问题的重复:
英特尔有一篇很好的文章(新指令支持大整数算术),介绍了大整数算术和三个新指令MULX、ADCX、ADOX。他们写道:
“mulx”,“adc x”和“adox”的内在定义也将集成到编译器中。这是第一个使用内在函数实现“带进位加法”类型指令的示例。内在函数的支持将使用户能够使用高级编程语言(例如C/C++)实现大整数算术。unsigned __int64 umul128(unsigned __int64 a, unsigned __int64 b, unsigned __int64 * hi);
unsigned char _addcarry_u64(unsigned char c_in, unsigned __int64 a, unsigned __int64 b, unsigned __int64 *out);
unsigned char _addcarryx_u64(unsigned char c_in, unsigned __int64 a, unsigned __int64 b, unsigned __int64 *out);
顺便提一下,MSVC已经有了_umul128
内置函数。因此,即使MSVC没有__int128
,也可以使用_umul128
内置函数生成mul
,从而进行128位乘法。
MULX
在BMI2(Haswell)中出现。自Broadwell以来,ADCX
和ADOX
指令作为ADX扩展可用。遗憾的是,自1979年8086以来就有了ADC
,但没有内置函数。这将解决内联汇编问题。
_addcarry_u64
,但可能并非所有编译器都实现了它。然而,gcc 通常会低效地编译它和/或 _addcarryx
,经常将 CF 溢出到带有 setc
的整数中,而不是更好地排序指令。)
GCC 的 __int128
代码生成将使用 mulx
,如果启用了 BMI2(例如使用 -mbmi2
或 -march=haswell
)。
编辑:
我尝试了 Lưu Vĩnh Phúc 建议的 Clang 的带进位加法内置函数。
void add256(int256 *x, int256 *y) {
unsigned long long carryin=0, carryout;
x->x1 = __builtin_addcll(x->x1, y->x1, carryin, &carryout); carryin = carryout;
x->x2 = __builtin_addcll(x->x2, y->x2, carryin, &carryout); carryin = carryout;
x->x3 = __builtin_addcll(x->x3, y->x3, carryin, &carryout); carryin = carryout;
x->x4 = __builtin_addcll(x->x4, y->x4, carryin, &carryout);
}
但是这并没有产生
ADC
,而且比我预想的更加复杂。
adc
在128位部分之间传播进位。 - Jesteradc
指令:https://gmplib.org/manual/Assembly-Carry-Propagation.html - Iwillnotexist Idonotexist