asm.js 如何处理被零除?

How does asm.js handle divide-by-zero?

在 javascript 中,用 "integer" 参数除以零就像浮点数一样:

 1/0;    // Infinity
-1/0;    // -Infinity
 0/0;    // NaN

asm.js 规范说明整数参数除法 returns intish,必须立即将其强制转换为有符号或无符号。如果我们在 javascript 中这样做,用 "integer" 参数除以零总是 return 在强制转换后为零:

(1/0)|0;    // == 0, signed case.
(1/0) >> 0; // == 0, unsigned case.

但是,在 Java 和 C 等具有实际整数类型的语言中,将整数除以零是错误的,并且执行会以某种方式停止(例如,抛出异常、触发陷阱等)。

这似乎也违反了 asm.js 指定的类型签名。 InfinityNaN 的类型是 double/ 的类型应该是(来自规范):

(signed, signed) → intish ∧ (unsigned, unsigned) → intish ∧ (double?, double?) → double ∧ (float?, float?) → floatish

但是,如果其中任何一个的分母为零,则结果为 double,因此类型似乎只能是:

(double?, double?) → double

asm.js 代码中预期会发生什么?它遵循 javascript 和 return 0 还是除以零会产生运行时错误?如果跟在javascript后面,为什么打错了还可以呢?如果它产生运行时错误,为什么规范没有提到它?

asm.js 是 JavaScript 的一个子集,所以它必须 return JavaScript 的作用:Infinity|00.

你指出 Infinitydouble,但这混淆了 asm.js 类型系统和 C 类型系统(在 JavaScript 中是 number): asm.js 使用 JavaScript 类型强制使中间结果成为 "right" 类型,而它们不是。当 JavaScript 中的一个小整数溢出到 double 时会发生同样的事情:它会使用按位运算被强制转换回整数。

这里的关键是它给编译器一个提示,它不需要计算所有的东西 JavaScript 通常会让它计算:一个小整数溢出并不重要,因为它是强制返回一个整数,因此编译器可以省略溢出检查并发出直线整数运算。请注意,对于每个可能的值,它仍然必须正确运行!类型系统基本上提示编译器进行一系列强度缩减。

现在回到整数除法:在 x86 上这会导致浮点异常(是的!整数除法会导致 SIGFPE!)。编译器 知道 输出是一个整数,因此它可以进行整数除法,但如果分母为零,则它无法停止程序。这里有两个选项:

  • 如果输入为零,则围绕除法分支,return直接为零。
  • 使用提供的输入进行除法,但在程序开始时安装一个信号处理程序,捕获 SIGFPE。当它查找代码位置出错时,如果编译器的元数据表明这是一个除法位置,则将 return 值修改为零并继续执行。

前者是 V8 和 OdinMonkey 实现的。

在 ARM 上,整数除法指令总是被定义为 return 零,除了 ARMv7-R 配置文件出现错误(错误是未定义的指令,或者可以更改为 return 零如果 SCTRL.DZ == 0)。 ARM 最近只在 ARMv7VE 扩展(虚拟化扩展)中添加了 UDIVSDIV 指令,并使其在 ARMv7-A 处理器中成为可选(大多数手机和平板电脑都使用这些)。您可以使用 /proc/cpuinfo 检查指令,但请注意,某些内核不知道该指令!解决方法是在进程启动时检查指令,方法是执行指令并使用 sigsetjmp/siglongjmp 捕获未处理的情况。这还有一个进一步的警告,即要捕获内核 "helpful" 并在不支持它的处理器上模拟 UDIV/IDIV 的情况!如果该指令不存在,则您必须使用 C 库的整数除法指令(libgcccompiler_rt 包含 __udivmoddi4 等函数)。请注意,此函数在除以零时的行为可能因实现而异,必须使用零分母上的分支来处理或在加载时检查(与上面概述的 UDIV/SDIV 相同)。

我会留给你一个问题:执行以下 C 代码时 asm.js 中会发生什么:INT_MIN/-1?