Detailed Description

This file lowers exception-related instructions and setjmp/longjmp function calls in order to use Emscripten's JavaScript try and catch mechanism.

To handle exceptions and setjmp/longjmps, this scheme relies on JavaScript's try and catch syntax and relevant exception-related libraries implemented in JavaScript glue code that will be produced by Emscripten. This is similar to the current Emscripten asm.js exception handling in fastcomp. For fastcomp's EH / SjLj scheme, see these files in fastcomp LLVM branch: (Location: https://github.com/kripken/emscripten-fastcomp) lib/Target/JSBackend/NaCl/LowerEmExceptionsPass.cpp lib/Target/JSBackend/NaCl/LowerEmSetjmp.cpp lib/Target/JSBackend/JSBackend.cpp lib/Target/JSBackend/CallHandlers.h

Exception handling This pass lowers invokes and landingpads into library functions in JS glue code. Invokes are lowered into function wrappers called invoke wrappers that exist in JS side, which wraps the original function call with JS try-catch. If an exception occurred, cxa_throw() function in JS side sets some variables (see below) so we can check whether an exception occurred from wasm code and handle it appropriately.

Setjmp-longjmp handling This pass lowers setjmp to a reasonably-performant approach for emscripten. The idea is that each block with a setjmp is broken up into two parts: the part containing setjmp and the part right after the setjmp. The latter part is either reached from the setjmp, or later from a longjmp. To handle the longjmp, all calls that might longjmp are also called using invoke wrappers and thus JS / try-catch. JS longjmp() function also sets some variables so we can check / whether a longjmp occurred from wasm code. Each block with a function call that might longjmp is also split up after the longjmp call. After the longjmp call, we check whether a longjmp occurred, and if it did, which setjmp it corresponds to, and jump to the right post-setjmp block. We assume setjmp-longjmp handling always run after EH handling, which means we don't expect any exception-related instructions when SjLj runs. FIXME Currently this scheme does not support indirect call of setjmp, because of the limitation of the scheme itself. fastcomp does not support it either.

In detail, this pass does following things:

1) Create three global variables: THREW, threwValue, and __tempRet0. __tempRet0 will be set within __cxa_find_matching_catch() function in JS library, and __THREW and threwValue will be set in invoke wrappers in JS glue code. For what invoke wrappers are, refer to 3). These variables are used for both exceptions and setjmp/longjmps. __THREW indicates whether an exception or a longjmp occurred or not. 0 means nothing occurred, 1 means an exception occurred, and other numbers mean a longjmp occurred. In the case of longjmp, __threwValue variable indicates the corresponding setjmp buffer the longjmp corresponds to. In exception handling, __tempRet0 indicates the type of an exception caught, and in setjmp/longjmp, it means the second argument to longjmp function.

Exception handling

2) Create setThrew and setTempRet0 functions. The global variables created in 1) will exist in wasm address space, but their values should be set in JS code, so we provide these functions as interfaces to JS glue code. These functions are equivalent to the following JS functions, which actually exist in asm.js version of JS library.

3) Lower invoke (arg1, arg2) to label invoke.cont unwind label lpad into THREW = 0; call (func, arg1, arg2) __THREW__.val = THREW; THREW = 0; if (__THREW__.val == 1) goto lpad else goto invoke.cont SIG is a mangled string generated based on the LLVM IR-level function signature. After LLVM IR types are lowered to the target wasm types, the names for these wrappers will change based on wasm types as well, as in invoke_vi (function takes an int and returns void). The bodies of these wrappers will be generated in JS glue code, and inside those wrappers we use JS try-catch to generate actual exception effects. It also calls the original callee function. An example wrapper in JS code would look like this: function invoke_vi(index,a1) { try { Module["dynCall_vi"](index,a1); // This calls original callee } catch(e) { if (typeof e !== 'number' && e !== 'longjmp') throw e; asm["setThrew"](1, 0); // setThrew is called here } } If an exception is thrown, THREW will be set to true in a wrapper, so we can jump to the right BB based on this value.

8) Lower setjmp(buf) into setjmpTable = saveSetjmp(buf, label, setjmpTable, setjmpTableSize); setjmpTableSize = __tempRet0; For each dynamic setjmp call, setjmpTable stores its ID (a number which is incrementally assigned from 0) and its label (a unique number that represents each callsite of setjmp). When we need more entries in setjmpTable, it is reallocated in saveSetjmp() in JS code and it will return the new table address, and assign the new table size in __tempRet0. saveSetjmp also stores the setjmp's ID into the buffer buf. A BB with setjmp is split into two after setjmp call in order to make the post-setjmp BB the possible destination of longjmp BB.

10) Lower every call that might longjmp into THREW = 0; call (func, arg1, arg2) __THREW__.val = THREW; THREW = 0; if (__THREW__.val != 0 & threwValue != 0) { label = testSetjmp(mem[__THREW.val], setjmpTable, setjmpTableSize); if (label == 0) emscripten_longjmp(__THREW__.val, threwValue); __tempRet0 = __threwValue; } else { label = -1; } longjmp_result = __tempRet0; switch label { label 1: goto post-setjmp BB 1 label 2: goto post-setjmp BB 2 ... default: goto splitted next BB } testSetjmp examines setjmpTable to see if there is a matching setjmp call. After calling an invoke wrapper, if a longjmp occurred, __THREW will be the address of matching jmp_buf buffer and threwValue be the second argument to longjmp. mem[__THREW.val] is a setjmp ID that is stored in saveSetjmp. testSetjmp returns a setjmp label, a unique ID to each setjmp callsite. Label 0 means this longjmp buffer does not correspond to one of the setjmp callsites in this function, so in this case we just chain the longjmp to the caller. (Here we call emscripten_longjmp, which is different from emscripten_longjmp_jmpbuf. emscripten_longjmp_jmpbuf takes jmp_buf as its first argument, while emscripten_longjmp takes an int. Both of them will eventually be lowered to emscripten_longjmp in s2wasm, but here we need two signatures - we can't translate an int value to a jmp_buf.) Label -1 means no longjmp occurred. Otherwise we jump to the right post-setjmp BB based on the label.