6

In javascript, division by zero with "integer" arguments acts like floating points should:

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

The asm.js spec says that division with integer arguments returns intish, which must be immediately coerced to signed or unsigned. If we do this in javascript, division by zero with "integer" arguments always returns zero after coersion:

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

However, in languages with actual integer types like Java and C, dividing an integer by zero is an error and execution halts somehow (e.g., throws exception, triggers a trap, etc).

This also seems to violate the type signatures specified by asm.js. The type of Infinity and NaN is double and of / is supposedly (from the spec):

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

However if any of these has a zero denominator, the result is double, so it seems like the type can only be:

(double?, double?) → double

What is expected to happen in asm.js code? Does it follow javascript and return 0 or does divide-by-zero produce a runtime error? If it follows javascript, why is it ok that the typing is wrong? If it produces a runtime error, why doesn't the spec mention it?

JF Bastien
  • 6,673
  • 2
  • 26
  • 34
Francis Avila
  • 31,233
  • 6
  • 58
  • 96

1 Answers1

5

asm.js is a subset of JavaScript, so it has to return what JavaScript does: Infinity|00.

You point out that Infinity is double, but that mixes up the asm.js type system with the C one (in JavaScript those are number): asm.js uses JavaScript type coercion to make intermediate results the "right" type when they aren't. The same thing happens when a small integer in JavaScript would overflow to a double: it gets coerced back into an integer using bitwise operations.

The key here is that it gives the compiler a hint that it doesn't need to calculate all the things JavaScript would usually have it calculate: it doesn't matter if a small integer overflows because it's coerced back into an integer, so the compiler can omit overflow checks and emit straight-line integer arithmetic. Note that it still has to behave correctly for every possible value! The type system basically hints the compiler towards doing a bunch of strength reductions.

Now back to integer division: on x86 this causes a floating-point exception (yes! Integer division causes SIGFPE!). The compiler knows the output is an integer so it can do an integer division, but it can't halt the program if the denominator was zero. There are two options here:

  • Branch around the division if the input is zero, and return zero directly.
  • Do the division with the provided input, but at the start of the program install a signal handler, catching SIGFPE. When it faults look up the code location, and if the compiler's metadata says that's a division location then modify the return value to be zero and continue executing.

The former is what V8 and OdinMonkey implement.

On ARM the integer division instruction is defined to always return zero, except ARMv7-R profile of ARM where it faults (the fault is undefined instruction, or can be changed to return zero if SCTRL.DZ == 0). ARM only added the UDIV and SDIV instructions recently with the ARMv7VE extension (virtualization extension), and made it optional in ARMv7-A processors (most phones and tablets use these). You can check for the instruction using /proc/cpuinfo, but note that some kernels are unaware of the instruction! A workaround is to check for the instruction when the process starts by executing the instruction and using sigsetjmp/siglongjmp to catch cases where it's not handled. That has a further caveat of also catching cases where the kernel is being "helpful" and emulating UDIV/IDIV on processors that don't support it! If the instruction isn't present then you have to use the C library's integer division instruction (libgcc or compiler_rt contain functions such as __udivmoddi4). Note that the behavior of this function on divide by zero may vary between implementations and has to be handled with a branch on zero denominator or checked at load time (same as outlined above for UDIV/SDIV).

I'll leave you off with a question: what happens in asm.js when executing the following C code: INT_MIN/-1?

JF Bastien
  • 6,673
  • 2
  • 26
  • 34
  • 1
    So the general principle here is that asm.js inherits all JS semantics plus extra numeric types and its up to compilers targeting asm.js to implement their language's semantics (e.g. of C) on top of those. `INT_MIN/-1` is undefined in C, but in asm.js after truncation it is INT_MIN again (signed overflow), and since the behavior is undefined in C that's ok? – Francis Avila Mar 24 '15 at 19:13
  • @FrancisAvila there are two compilers: LLVM generating asm.js code from C/C++, and SpiderMonkey/V8 generating assembly from JavaScript. SpiderMonkey has a shortcut for valid asm.js code (OdinMonkey), but that shortcut still has to be 100% valid JavaScript. V8 does something similar with TurboFan, but it doesn't validate the asm.js subset (just "use asm" is sufficient to trigger it). LLVM has to generate asm.js code that follows C rules, which is why `INT_MIN/-1` can do whatever it wants because it's undefined so JavaScript's result is as good as any. SpiderMonkey/V8 know nothing of C rules! – JF Bastien Mar 24 '15 at 23:08