WebAssembly Exception Handling
W3C TPAC Nov 2017
Heejin Ahn
Confidential + Proprietary
Confidential + Proprietary
LLVM CFG Example
try { foo(); } catch (...) { } |
Confidential + Proprietary
Exception Handling in WebAssembly
Confidential + Proprietary
Asm.js-style Exception Handling: Invoking Functions
int main() { … __THREW__ = 0; __invoke_void_i32(foo, 3); __THREW__.val = __THREW__; __THREW__ = 0; if (__THREW__.val == 1) ... } void foo(int n) { ... __cxa_throw(...); } void setThrew(int threw, int value) { if (__THREW__ == 0) { __THREW__ = threw; __threwValue = value; } } ... |
Transformed WASM code
(in C-like pseudocode)
... function invoke_vi(index,a1) { try { Module["dynCall_vi"](index,a1); } catch(e) { if (typeof e !== 'number' && e !== 'longjmp') throw e; asm["setThrew"](1, 0); } } function ___cxa_throw(ptr, type, destructor) { ... throw ptr; } ... |
.js glue code
call
call
call
throw
call
Setting __THREW__ to 1 means
an exception occurred
return
return
Exception Handling in WebAssembly
Current Exception Handling Spec
try instruction* catch i instruction* catch j instruction* ... catch n instruction* catch_all instruction* end |
Confidential + Proprietary
Current Exception Handling Spec
Confidential + Proprietary
Itanium ABI-style Stack Unwinding: Second Phase
throw e;
catch (int n)
run user-specified catch code
__cxa_throw()
__gxx_personality_v0()
__gxx_personality_v0()
Set IP to landing pad
_Unwind_RaiseException()
Unwind stack frames
Ask
Unwind stack frames
Ask
return
User code
C++ ABI library
Unwind library
Should we stop at this frame?
No
Should we stop at this frame?
Yes
Confidential + Proprietary
WebAssembly Stack Unwinding
Confidential + Proprietary
WebAssembly Stack Unwinding
throw e;
catch <C++ tag>
if type is not what we want (e.g. “int”)
catch <C++ tag>
if type is what we want (e.g. “float”)
run user-specified catch code
__cxa_throw()
throw <C++ tag>, e
throw <C++ tag>, e
Unwind stack frames
Unwind stack frames
User code
C++ ABI library
WebAssembly VM
Confidential + Proprietary
Personality Function: Itanium ABI version
Wasm can’t modify machine registers
Confidential + Proprietary
Direct Personality Function Call
// Gets a thrown exception object�void *exn = catch(0); // to be lowered to WebAssembly catch instruction��// Set input parameters�__wasm_lpad_context.lpad_index = index;�__wasm_lpad_context.personality = __gxx_personality_v0;�__wasm_lpad_context.lsda = &GCC_except_table0; // LSDA symbol of this function��// Call personality wrapper function�_Unwind_CallPersonality(exn);��// Retrieve output parameter�int selector = __wasm_lpad_context.selector; |
Compiler-inserted code at every landing pad (in C style)
Confidential + Proprietary
Compiler Backend Transformation
block try loop … end catch … try … catch … end end rethrow ... end … |
How?
try {� ...�} catch (int e) {� ...�} catch (...) {� ...�} |
Confidential + Proprietary
Difficulties with Current Spec
Confidential + Proprietary
Code Size Estimates
Confidential + Proprietary
Code Size Estimates
| # of instrs |
w/o exceptions | 766895 |
| 797046 |
| 824634 |
| 828114 |
| # of instrs |
w/o exceptions | 259974 |
| 289367 |
| 316955 |
| 320435 |
All 3367 functions
538 functions that uses exceptions
Confidential + Proprietary
Links
Confidential + Proprietary
Backup Slides
Confidential + Proprietary
LLVM CFG Example
try { foo(); } catch (int n) { } |
Confidential + Proprietary
Asm.js-style Exception Handling: Invoking Functions
__THREW__ = 0; __invoke_void_i32(foo, 3); __THREW__.val = __THREW__; __THREW__ = 0; if (__THREW__.val == 1) goto %lpad else goto %try.cont |
(i32.store offset=xyz … ) (call_import $invoke_vi (get_local $1) (i32.const 3) ) (set_local … (i32.load … ... ) ) |
invoke void @foo(i32 3) to label %try.cont unwind label %lpad |
Original LLVM IR code
Transformed LLVM IR pseudocode
.wast code
LowerEmscriptenEHSjLj pass
Invoke wrapper
LLVM backend + s2wasm
1 if exception occurred
Difficulties with Current Spec
Confidential + Proprietary
Problem 1: Code Pattern Matching
try {� ...�} catch (int e) {� action 1�} catch (...) {� action 2�} |
try� ...�catch <c++>� if (int e)� action 1� else� action 2�catch_all� action 2�end |
How do we know which code corresponds to “action 2”?
Confidential + Proprietary
Problem 1: Code Pattern Matching
MyClass obj;�try {� ...�} catch (int e) {� action 1;�} |
try ... catch <c++>� if (int e)� action 1� else� delete obj rethrow 0�catch_all� delete obj rethrow 0�end |
How do we know which code corresponds to cleanup (destructor calls)?
Confidential + Proprietary
Pattern Matching Code Doesn’t Work
lpad: // two blocks are now merged sel = landingpad... some action Compiler transformation / optimization passes can do anything, so we shouldn’t rely on the original code structure |
lpad: sel = landingpad ... typeA = eh.typeid.for(int type) if (selA == typeA) goto catch.A else goto catch.fallthrough catch.A: some action catch.fallthrough: some action |
IR code (in C-style pseudocode)
Optimized IR code
Confidential + Proprietary
Suggestion: Use only catch_all
try {� ...�} catch (int e) {� action 1�} catch (...) {� action 2�} |
// When throwing set_local 0, e throw e // When catching try� ...�catch_all get_local 0� if (int e)� action 1� else� action 2�end |
Confidential + Proprietary
Suggestion: Use only catch_all
Confidential + Proprietary
Problem 2: rethrow’s Execution Scope
MyClass obj;�try {� foo();�} catch (int n) {� ...�}�...�try {� foo();�} catch (int n) {� ...�} |
block $label0� try� call $foo� catch i� br $label$0� end� ...� try� ...� catch i� br $label$0� end�end��delete obj�rethrow |
Confidential + Proprietary
Suggestion: Make rethrow executable outside catch
try� ...�catch_all� set_local 0, capture_exception�end��get_local 0�use handle |
Confidential + Proprietary
Placing try / catch / end Instructions
block try loop … end catch … try … catch … end end … end … |
How?
Confidential + Proprietary
Review: CFG Sort
A
B
C
D
A
B
C
D
Confidential + Proprietary
Review: CFG Sort
Confidential + Proprietary
Review: CFG Stackify
A
B
C
D
A
B
C
D
block
end
Confidential + Proprietary
Review: CFG Stackify
A
B
C
D
A
B
C
D
block
end
Confidential + Proprietary
Review: CFG Stackify
Can we do the same for try / catch / end?
Confidential + Proprietary
Review: CFG Stackify
A
B
C
D
block block A B end C end D |
Confidential + Proprietary
CFG Stackify: try / catch / end
A
foo()
bar()
LPad
Cont
Normal edge
Unwind edge
block try block A foo() end bar() catch LPad end end Cont |
Confidential + Proprietary
CFG Stackify: try / catch / end
A
foo()
bar()
LPad
Cont
Normal edge
Unwind edge
block try block A foo() end bar() catch LPad end end Cont |
Confidential + Proprietary
CFG Stackify: try / catch / end
A
foo()
bar()
LPad
Cont
Normal edge
Unwind edge
block try block A foo() end bar() catch LPad end end Cont |
Confidential + Proprietary
Problem 3: Unwind Scope Mismatches & Workaround
try try … foo() … catch … end catch some code end |
Unwind dest. is outer landingpad!
try try … try foo() catch br 2 end … catch … end catch br 0 end some code |
X
Possible
Workaround
Confidential + Proprietary
Suggestion: Syntactic Sugar try_br
try try … try foo() catch br 2 end … catch … end catch br 0 end some code |
try try … try_br 2 foo() end … catch … end catch some code end |
Confidential + Proprietary
Suggestion: rethrow with a depth (label)
try try … try foo() catch br 2 end … catch … end catch br 0 end some code |
try try … try foo() catch rethrow 2 end … catch … end catch some code end |
Confidential + Proprietary
Rethrow <depth>
try� ...�catch 1� ...� block� ...� try� ...� catch 2� ...� try� ...� catch 3� ...� rethrow N� end� end� end� ...�end |
Confidential + Proprietary
Windows Exception Handling
MyClass obj; try { foo(); } catch (int n) { } catch (...) { } |
New instructions
Confidential + Proprietary
LSDA Format
... +---------------------+-----------+------------------------------------------------+�| Beginning of Call Site Table landing pad index is a index into this |�| table. |�| +-------------+---------------------------------+------------------------------+ |�| | landingPad | (ULEB128) | landingpad index | |�| | actionEntry | (ULEB128) | Action Table Index 1-based | |�| | | | actionEntry == 0 -> cleanup | |�| +-------------+---------------------------------+------------------------------+ |�| ... |�+----------------------------------------------------------------------------------+ ... |
Confidential + Proprietary