unsigned checked_umul(unsigned a, unsigned b, bool *overflow_happened)
{
unsigned res;
bool ovf = __builtin_umul_overflow(a,b, &res);
if (ovf){
*overflow_happened = 1; // set or leave unmodified
// check once at the end.
}
return res;
}
为Godbolt上的rv 32 gc发出16 -O2叮当声
checked_umul(unsigned int, unsigned int, bool*):
mulhu a3, a0, a1 # high half of a * b
mul a0, a0, a1 # result = a * b
beqz a3, .LBB1_2
li a1, 1
sb a1, 0(a2) # just an example of something to branch on
.LBB1_2:
ret
当然你会在asm中内联这个,而不是实际调用一个函数;在C语言中将其作为函数只是为了让我们可以单独查看asm,就像How to remove "noise" from GCC/clang assembly output?中那样 你可以在非零的高半部分分支,做任何你喜欢的事情,或者你可以在阶乘迭代中一起OR高半部分,只是检查最后的溢出。 (If你期望调用者经常传递溢出的输入,在它上面的early-out可以保存时间,并且使最坏情况下的性能只有大约13次乘法,因为13!溢出32位整数。与early-out的最坏情况下大约2^32-1次迭代的时间相比。但是否则,仅仅一个or可以比每次分支更便宜。当然,如果你关心性能,你就不会做递归实现了与循环相比,这增加了大量开销。) 正如Jester所建议的,mulhu获取完整乘法的高半部分将允许您检查高半部分是否为非零。这与检查完整结果是否与输入操作数的宽度相同。 (mulh是一个有符号乘法,所以在一般情况下,你会想检查它是下半部分的符号扩展,如果将结果截断到输入的宽度,检查是否有符号溢出。对于非负输入的阶乘,你可能会检查它是否为零,或者更好地使用unsigned来增加你可以支持的值范围。) 在具有高效乘法器的CPU上,特别是将mulhu/mul融合到扩展乘法ALU的单个操作中的CPU,额外的mulhu比您可能做的任何事情都便宜,例如计算输入的前导零。特别是没有扩展B,因为基线RISC-V省略了许多在其他主流ISA中常见的指令,例如位扫描。
1条答案
按热度按时间dpiehjr41#
看看GCC如何实现
__builtin_smul_overflow
或umul
(https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html)为Godbolt上的rv 32 gc发出16 -O2叮当声
当然你会在asm中内联这个,而不是实际调用一个函数;在C语言中将其作为函数只是为了让我们可以单独查看asm,就像How to remove "noise" from GCC/clang assembly output?中那样
你可以在非零的高半部分分支,做任何你喜欢的事情,或者你可以在阶乘迭代中一起
OR
高半部分,只是检查最后的溢出。(If你期望调用者经常传递溢出的输入,在它上面的early-out可以保存时间,并且使最坏情况下的性能只有大约13次乘法,因为
13!
溢出32位整数。与early-out的最坏情况下大约2^32-1次迭代的时间相比。但是否则,仅仅一个or
可以比每次分支更便宜。当然,如果你关心性能,你就不会做递归实现了与循环相比,这增加了大量开销。)正如Jester所建议的,
mulhu
获取完整乘法的高半部分将允许您检查高半部分是否为非零。这与检查完整结果是否与输入操作数的宽度相同。(
mulh
是一个有符号乘法,所以在一般情况下,你会想检查它是下半部分的符号扩展,如果将结果截断到输入的宽度,检查是否有符号溢出。对于非负输入的阶乘,你可能会检查它是否为零,或者更好地使用unsigned来增加你可以支持的值范围。)在具有高效乘法器的CPU上,特别是将
mulhu
/mul
融合到扩展乘法ALU的单个操作中的CPU,额外的mulhu
比您可能做的任何事情都便宜,例如计算输入的前导零。特别是没有扩展B,因为基线RISC-V省略了许多在其他主流ISA中常见的指令,例如位扫描。