1 of 44

WebAssembly Exception Handling

  • A Toolchain’s Perspective

W3C TPAC Nov 2017

Heejin Ahn

Confidential + Proprietary

Confidential + Proprietary

2 of 44

LLVM CFG Example

try {

foo();

} catch (...) {

}

Confidential + Proprietary

3 of 44

Exception Handling in WebAssembly

  • Current: Asm.js-style exception handling
    • “Simulates” exceptions using JS try-catch
    • Uses library functions in asm.js glue code
    • Uses inter-language calls between Wasm and JS for every possibly-throwing call
    • Very slow even if no exception occurs

Confidential + Proprietary

4 of 44

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

5 of 44

Exception Handling in WebAssembly

  • WIP: Zero-cost Fast exception handling
    • Initial Wasm spec was proposed
    • Implementation is going on in LLVM
    • May need some spec changes - discussions are going on

6 of 44

Current Exception Handling Spec

  • i, j, … are tag indices (immediate values); NOT C++ types like int, std::exception&, ...
  • Tags can be used to denote language kinds
    • catch 0 can mean “catch C++ exceptions”
  • catch_all catches all exceptions

try

instruction*

catch i

instruction*

catch j

instruction*

...

catch n

instruction*

catch_all

instruction*

end

Confidential + Proprietary

7 of 44

Current Exception Handling Spec

  • try
    • Marks the start of code region, in which if an exception occurs the program control goes to a corresponding catch block
  • catch i
    • Catches an value with a tag i
    • Puts the caught value(s) on top of Wasm stack
  • catch_all
    • Catches all values with all tags
    • DOES NOT put the caught value on top of Wasm stack (= value is opaque), but the value can be thrown again using rethrow)
  • throw i
    • Throws a value or values on top of Wasm stack, tagging with a tag i
  • rethrow depth
    • Rethrows the caught exception at depth (currently only 0 is used)
    • Only can be executed within a catch block

Confidential + Proprietary

8 of 44

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

9 of 44

WebAssembly Stack Unwinding

  • Unwinding is performed by the VM
  • No two-phase unwinding: Only cleanup phase
  • From user code’s point of view, program control flow is automagically transferred to a matching “catch <tag>” instruction
    • If C++ (or other language) exceptions use the same tag, the unwinder stops at every frame
  • Unlike the unwind library, the VM can’t call the personality function

Confidential + Proprietary

10 of 44

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

11 of 44

Personality Function: Itanium ABI version

  • Decides whether to stop at this frame
    • Reads LSDA information
    • Figures out current callsite and which C++ types are in catch clauses
    • See if the currently caught type matches
  • If we should stop at this frame
    • Sets IP to the start of a matching landing pad block
    • Sets a register to give the address of the thrown exception
    • Sets a register to give the selector value corresponding to the type of exception thrown

Wasm can’t modify machine registers

Confidential + Proprietary

12 of 44

Direct Personality Function Call

// Gets a thrown exception objectvoid *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 parameterint selector = __wasm_lpad_context.selector;

Compiler-inserted code at every landing pad (in C style)

Confidential + Proprietary

13 of 44

Compiler Backend Transformation

block

try

loop

end

catch

try

catch

end

end

rethrow

...

end

How?

try {� ...�} catch (int e) {� ...�} catch (...) {� ...�}

Confidential + Proprietary

14 of 44

Difficulties with Current Spec

  • Problems
    • Code pattern matching is hard
    • rethrow instruction is not easy to use
    • Call - landing pad relationship mismatches try-catch block
  • Reasons
    • CFG has lost much information from original source code
    • Compiler optimization passes can change code structure
    • Current spec does not handle some of generated code patterns

Confidential + Proprietary

15 of 44

Code Size Estimates

  • Application: BananaBread client
    • DOES NOT use C++ try/catch, but uses exceptions due to cleanup code (=destructor calls)
    • Tested 3367 functions: among them, 538 use exceptions
    • Counted # of instructions in assembly (.s): rough estimates
      • End-to-end build is not ready yet
  • Planning to test with other applications that uses try/catch extensively

Confidential + Proprietary

16 of 44

Code Size Estimates

# of instrs

w/o exceptions

766895

  • handling code

797046

  • direct personality calls

824634

  • TRY/END markers

828114

# of instrs

w/o exceptions

259974

  • handling code

289367

  • direct personality calls

316955

  • TRY/END markers

320435

  • 107% compared to w/o exceptions
  • 103% compared to exception baseline
  • 123% compared to w/o exceptions
  • 110% compared to exception baseline

All 3367 functions

538 functions that uses exceptions

Confidential + Proprietary

17 of 44

Links

Confidential + Proprietary

18 of 44

Backup Slides

Confidential + Proprietary

19 of 44

LLVM CFG Example

try {

foo();

} catch (int n) {

}

Confidential + Proprietary

20 of 44

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

21 of 44

Difficulties with Current Spec

  • Problem 1: code pattern matching
  • Problem 2: rethrow’s execution scope
  • Problem 3: unwind scope mismatches

Confidential + Proprietary

22 of 44

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

23 of 44

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

24 of 44

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

25 of 44

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

26 of 44

Suggestion: Use only catch_all

  • Should catch_all leave a tag on top of stack?
  • Should catch_all leave a caught obj on top of stack?
  • New instructions to test the current tag?
    • e.g. match <tag>
  • Possible downside on performance?

Confidential + Proprietary

27 of 44

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

  • Shared code is factored out
  • rethrow is now outside catch block!

Confidential + Proprietary

28 of 44

Suggestion: Make rethrow executable outside catch

  • Make exception object capturable and usable outside a catch block
    • Do we need ref counting?
  • Adding an exception stack?
    • catch_all pushes an exception to an exception stack and rethrow pops it
  • Implications on the VM side?
  • Other alternatives?

try� ...�catch_all� set_local 0, capture_exception�end��get_local 0�use handle

Confidential + Proprietary

29 of 44

Placing try / catch / end Instructions

block

try

loop

end

catch

try

catch

end

end

end

How?

Confidential + Proprietary

30 of 44

Review: CFG Sort

A

B

C

D

A

B

C

D

Confidential + Proprietary

31 of 44

Review: CFG Sort

  • Topological sort
  • Blocks within a Loop should be grouped together
    • Once we place a loop header, we shouldn’t place a block not dominated by the header before sorting all the blocks in the loop
  • Catch blocks should be grouped together (New!)
    • Once we place a landingpad, we shouldn’t place a block not dominated by the landingpad before sorting all the blocks in the catch block�

Confidential + Proprietary

32 of 44

Review: CFG Stackify

A

B

C

D

A

B

C

D

block

end

Confidential + Proprietary

33 of 44

Review: CFG Stackify

A

B

C

D

A

B

C

D

block

end

Confidential + Proprietary

34 of 44

Review: CFG Stackify

  1. For each BB that has non-fallthrough branches to it
  2. Compute the nearest common dominator of all forward non-fallthrough�predecessors
    1. If the dominator is in another context, walk out to the nearest non-nested scope
  3. Place a block marker on the common dominator and end marker in BB.

Can we do the same for try / catch / end?

  1. For each landingpad that has unwind edges to it
  2. ...

Confidential + Proprietary

35 of 44

Review: CFG Stackify

A

B

C

D

block

block

A

B

end

C

end

D

Confidential + Proprietary

36 of 44

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

37 of 44

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

38 of 44

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

39 of 44

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

40 of 44

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

41 of 44

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

42 of 44

Rethrow <depth>

try� ...�catch 1� ...� block� ...� try� ...� catch 2� ...� try� ...� catch 3� ...� rethrow N� end� end� end� ...�end

Confidential + Proprietary

43 of 44

Windows Exception Handling

MyClass obj;

try {

foo();

} catch (int n) {

} catch (...) {

}

New instructions

  • catchswitch
  • catchpad / catchret
  • cleanuppad / cleanupret

Confidential + Proprietary

44 of 44

LSDA Format

  • Mostly the same with LSDA format of SjLj EH scheme
  • Difference: Call site index -> Landing pad index in call site table

...

+---------------------+-----------+------------------------------------------------+�| 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