Closed Bug 1065089 Opened 11 years ago Closed 9 years ago

OdinMonkey: add exception handling support to asm.js

Categories

(Core :: JavaScript Engine: JIT, defect, P3)

defect

Tracking

()

RESOLVED WONTFIX

People

(Reporter: luke, Assigned: luke)

Details

Emscripten uses try/catch to implement C++ exception handling. However, asm.js doesn't include try/catch so this forces Emscripten to kick these functions out of asm.js which imposes a significant performance penalty. Usually, games turn off exceptions entirely, but there are some cases where exceptions are used. In particular, Unity's il2cpp converts .NET exception handling to C++ exception handling and they've observed a slowdown when .NET exception handling is turned on. We have requests from other apps as well.
The current syntax is that you could have try/catch anywhere and put any asm.js in the try/catch blocks. The catch parameter wouldn't be a normal local variable, though; the only operations allowed on it would be (1) immediate coercion to an int at the start of the catch block, and (2) rethrowing. One question is how to implement the exceptional edges in the CFG. A pretty simple implementation would be to (1) use a second return register to return whether an exception is being thrown and (2) branch on this exception-return register after every call. In this way, catch blocks would just be normal branches in the CFG. To test overhead of this approach, I wrote an experimental patch that just emits the extra instructions. On a call-heavy microbenchmark (3 levels of calls in a loop), there was a 10% slowdown and on asmjs-apps-lua_binarytrees a 3.4% slowdown. Now, the experimental patch is a little pessimal in that it adds branches after every call and we'd only really need branches on functions with catch blocks. However, RAII-style C++ encourages tons of local destructors, so I don't think it's unrealistic. Naively, I tried to turn MAsmJSCall into an MControlInstruction. This would allow us to remove the dynamic branch and have the non-exceptional return fall through and the exceptional return use a trampoline to jump directly to the catch block. However, this breaks some widely-assumed Ion invariants (control instructions don't have defs and control instructions are effect-free) that sound hard and error-prone to remove. So my current best idea is to reuse the snapshot mechanism to capture the state on entry and exit of the catch block. We could then compile the catch block as a separate MIRGraph and use MIR nodes to fix the initial/final state of each slot. To enter the block, the unwind trampoline would just set sp (using the existing stack-walking metadata) and jump to the catch-block entry. When the catch block completes, it would simply jump back to the main function's code. Advantages include: - practically zero-overhead for non-exceptional paths since mainline codegen is not affected (at least, until we want to add callee-saved registers to the internal ABI) - reduces chance of rare/bad interactions with optimizations Feedback welcome on this.
(In reply to Luke Wagner [:luke] from comment #1) > To enter the block, the unwind trampoline would just > set sp (using the existing stack-walking metadata) and jump to the > catch-block entry. When the catch block completes, it would simply jump > back to the main function's code. Oops, this part isn't right; there needs to be one snapshot per callsite in the try block and obviously we wouldn't want to duplicate the catch block for each one. Rather, I think we'll need to copy the register/stack state into some homogenous layout which is passed to the catch block.
Marking p3, assuming this is something we may have in WebAssembly.
Priority: -- → P3
WONTFIX since I think we're done extending asm.js at this point.
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → WONTFIX
You need to log in before you can comment on or make changes to this bug.