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 指定的类型签名。 Infinity
和 NaN
的类型是 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|0
→ 0
.
你指出 Infinity
是 double
,但这混淆了 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 扩展(虚拟化扩展)中添加了 UDIV
和 SDIV
指令,并使其在 ARMv7-A 处理器中成为可选(大多数手机和平板电脑都使用这些)。您可以使用 /proc/cpuinfo
检查指令,但请注意,某些内核不知道该指令!解决方法是在进程启动时检查指令,方法是执行指令并使用 sigsetjmp
/siglongjmp
捕获未处理的情况。这还有一个进一步的警告,即要捕获内核 "helpful" 并在不支持它的处理器上模拟 UDIV
/IDIV
的情况!如果该指令不存在,则您必须使用 C 库的整数除法指令(libgcc
或 compiler_rt
包含 __udivmoddi4
等函数)。请注意,此函数在除以零时的行为可能因实现而异,必须使用零分母上的分支来处理或在加载时检查(与上面概述的 UDIV
/SDIV
相同)。
我会留给你一个问题:执行以下 C 代码时 asm.js 中会发生什么:INT_MIN/-1
?
在 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 指定的类型签名。 Infinity
和 NaN
的类型是 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|0
→ 0
.
你指出 Infinity
是 double
,但这混淆了 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 扩展(虚拟化扩展)中添加了 UDIV
和 SDIV
指令,并使其在 ARMv7-A 处理器中成为可选(大多数手机和平板电脑都使用这些)。您可以使用 /proc/cpuinfo
检查指令,但请注意,某些内核不知道该指令!解决方法是在进程启动时检查指令,方法是执行指令并使用 sigsetjmp
/siglongjmp
捕获未处理的情况。这还有一个进一步的警告,即要捕获内核 "helpful" 并在不支持它的处理器上模拟 UDIV
/IDIV
的情况!如果该指令不存在,则您必须使用 C 库的整数除法指令(libgcc
或 compiler_rt
包含 __udivmoddi4
等函数)。请注意,此函数在除以零时的行为可能因实现而异,必须使用零分母上的分支来处理或在加载时检查(与上面概述的 UDIV
/SDIV
相同)。
我会留给你一个问题:执行以下 C 代码时 asm.js 中会发生什么:INT_MIN/-1
?