我正在尝试用 RISC-V 汇编语言实现一个递归阶乘函数,如果出现溢出,该函数会引发错误。但是,我正在努力检测它。有解决办法吗?
`
看看 GCC 如何实现
__builtin_smul_overflow
或 umul
(https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html)
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;
}
clang 16 -O2 for rv32gc on Godbolt
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
跨阶乘迭代将高半部分放在一起以检查最后是否溢出。
(如果您希望调用者经常传递溢出的输入,那么提前输出可以节省时间,并且使最坏情况下的性能只有大约 13 次乘法,因为
13!
溢出 32 位整数。与最坏的 -大约 2 ^ 32-1 次迭代的案例时间和早出。但否则只是一个 or
可以比每次分支更便宜。当然,如果你关心性能,你就不会进行递归实现;这增加了很多开销与循环。)
正如 Jester 所建议的,
mulhu
获得完整乘法的高半部分将使您检查高半部分是否非零。这与检查完整结果是否与输入操作数的宽度相同。
(
mulh
是一个带符号的乘法,所以在一般情况下,您需要检查它是否是低半部分的符号扩展,如果将结果截断到输入的宽度,则检查是否有符号溢出。对于具有非负输入的阶乘,您可以检查它是否为零,或者最好只使用无符号来增加您可以支持的值范围。)
在具有高效乘法器的 CPU 上,尤其是将
mulhu
/mul
融合到单个操作中以扩大乘法 ALU 的 CPU 上,额外的 mulhu
比您可能做的任何其他事情都要便宜,例如计算前导零输入。特别是没有扩展 B,因为基线 RISC-V 遗漏了许多其他主流 ISA 中常见的指令,例如位扫描。