// Tests for Wasm exception proposal instructions. // Test try blocks with no handlers. assertEq( wasmEvalText( `(module (func (export "f") (result i32) try (result i32) (i32.const 0) end))` ).exports.f(), 0 ); assertEq( wasmEvalText( `(module (func (export "f") (result i32) try (result i32) (i32.const 0) (br 0) (i32.const 1) end))` ).exports.f(), 0 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try (result i32) (throw $exn) (i32.const 1) end drop (i32.const 2) catch $exn (i32.const 0) end))` ).exports.f(), 0 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try (result i32) try try (throw $exn) end end (i32.const 1) end drop (i32.const 2) catch $exn (i32.const 0) end))` ).exports.f(), 0 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try (result i32) try try (throw $exn) end catch_all rethrow 0 end (i32.const 1) end drop (i32.const 2) catch $exn (i32.const 0) end))` ).exports.f(), 0 ); // Test trivial try-catch with empty bodies. assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try nop catch $exn end (i32.const 0)))` ).exports.f(), 0 ); assertEq( wasmEvalText( `(module (func (export "f") (result i32) try nop catch_all end (i32.const 0)))` ).exports.f(), 0 ); // Test try block with no throws assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) (i32.const 0) catch $exn (i32.const 1) end))` ).exports.f(), 0 ); // Ensure catch block is really not run when no throw occurs. assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) (local i32) try (local.set 0 (i32.const 42)) catch $exn (local.set 0 (i32.const 99)) end (local.get 0)))` ).exports.f(), 42 ); // Simple uses of throw. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) (i32.const 42) (throw $exn) catch $exn drop (i32.const 1) end))` ).exports.f(), 1 ); assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func $foo (param i32) (result i32) (local.get 0) (throw $exn)) (func (export "f") (result i32) try (result i32) (i32.const 42) (call $foo) catch $exn drop (i32.const 1) end))` ).exports.f(), 1 ); // Simple uses of throw of some Wasm vectortype values (Simd128). if (wasmSimdEnabled()) { assertEq( wasmEvalText( `(module (type (func (param v128 v128 v128 v128))) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) (v128.const i32x4 42 41 40 39) (v128.const f32x4 4.2 4.1 0.40 3.9) (v128.const i64x2 42 41) (v128.const f64x2 4.2 4.1) (throw $exn) catch $exn drop drop drop (i64x2.all_true) end))` ).exports.f(), 1 ); assertEq( wasmEvalText( `(module (type (func (param v128 v128 v128 v128))) (tag $exn (type 0)) (func $foo (param v128 v128 v128 v128) (result i32) (throw $exn (local.get 0) (local.get 1) (local.get 2) (local.get 3))) (func (export "f") (result i32) try (result i32) (v128.const i32x4 42 41 40 39) (v128.const f32x4 4.2 4.1 0.40 3.9) (v128.const i64x2 42 41) (v128.const f64x2 4.2 4.1) (call $foo) catch $exn drop drop drop (i64x2.all_true) end))` ).exports.f(), 1 ); { let imports = wasmEvalText( `(module (tag $exn (export "exn") (param v128)) (func (export "throws") (param v128) (result v128) (throw $exn (local.get 0)) (v128.const i32x4 9 10 11 12)))`).exports; let mod = `(module (import "m" "exn" (tag $exn (param v128))) (import "m" "throws" (func $throws (param v128) (result v128))) (func (export "f") (result i32) (local v128) (v128.const i32x4 1 2 3 4) (local.tee 0) try (param v128) (result v128) (call $throws) catch $exn catch_all (v128.const i32x4 5 6 7 8) end (local.get 0) (i32x4.eq) (i32x4.all_true)))`; assertEq(wasmEvalText(mod, { m : imports }).exports.f(), 1); } } // Further nested call frames should be ok. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func $foo (param i32) (result i32) (local.get 0) (call $bar)) (func $bar (param i32) (result i32) (local.get 0) (call $quux)) (func $quux (param i32) (result i32) (local.get 0) (throw $exn)) (func (export "f") (result i32) try (result i32) (i32.const 42) (call $foo) catch $exn drop (i32.const 1) end))` ).exports.f(), 1 ); // Basic throwing from loop. assertEq( wasmEvalText( `(module (tag $exn) ;; For the purpose of this test, the params below should be increasing. (func (export "f") (param $depth_to_throw_exn i32) (param $maximum_loop_iterations i32) (result i32) (local $loop_counter i32) ;; The loop is counting down. (local.get $maximum_loop_iterations) (local.set $loop_counter) (block $catch (loop $loop (if (i32.eqz (local.get $loop_counter)) (then (return (i32.const 440))) (else try (if (i32.eq (local.get $depth_to_throw_exn) (local.get $loop_counter)) (then (throw $exn)) (else (local.set $loop_counter (i32.sub (local.get $loop_counter) (i32.const 1))))) catch $exn (br $catch) catch_all end)) (br $loop)) (return (i32.const 10001))) (i32.const 10000)))` ).exports.f(2, 4), 10000 ); // Ensure conditional throw works. let conditional = wasmEvalText( `(module (type (func (param))) (tag $exn (type 0)) (func (export "f") (param i32) (result i32) try (result i32) (local.get 0) if (result i32) (throw $exn) else (i32.const 42) end catch $exn (i32.const 99) end))` ).exports.f; assertEq(conditional(0), 42); assertEq(conditional(1), 99); // Ensure multiple & nested try-catch blocks work. assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn (type 0)) (func $foo (throw $exn)) (func (export "f") (result i32) (local i32) try nop catch $exn (local.set 0 (i32.const 99)) end try (call $foo) catch $exn (local.set 0 (i32.const 42)) end (local.get 0)))` ).exports.f(), 42 ); assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn (type 0)) (func (export "f") (result i32) (local i32) try try try (throw $exn) catch $exn (local.set 0 (i32.const 42)) end catch $exn (local.set 0 (i32.const 97)) end catch $exn (local.set 0 (i32.const 98)) end (local.get 0)))` ).exports.f(), 42 ); assertEq( wasmEvalText( `(module (tag $exn) (func (export "f") (result i32) try throw $exn catch $exn try (throw $exn) catch $exn end end (i32.const 27)))` ).exports.f(), 27 ); { let nested_throw_in_block_and_in_catch = wasmEvalText( `(module (tag $exn) (func $throw (throw $exn)) (func (export "f") (param $arg i32) (result i32) (block (result i32) try (result i32) (call $throw) (unreachable) catch $exn (if (result i32) (local.get $arg) (then try (result i32) (call $throw) (unreachable) catch $exn (i32.const 27) end) (else (i32.const 11)) ) end ) ))` ).exports.f; assertEq(nested_throw_in_block_and_in_catch(1), 27); assertEq(nested_throw_in_block_and_in_catch(0), 11); } assertEq( wasmEvalText( `(module (tag $thrownExn) (tag $notThrownExn) (func (export "f") (result i32) try (result i32) try (result i32) (throw $thrownExn) catch $notThrownExn (i32.const 19) catch $thrownExn (i32.const 20) catch_all (i32.const 21) end end))` ).exports.f(), 20 ); // Test that uncaught exceptions get propagated. assertEq( wasmEvalText( `(module (tag $thrownExn) (tag $notThrownExn) (func (export "f") (result i32) (local i32) try try try (throw $thrownExn) catch $notThrownExn (local.set 0 (i32.or (local.get 0) (i32.const 1))) end catch $notThrownExn (local.set 0 (i32.or (local.get 0) (i32.const 2))) end catch $thrownExn (local.set 0 (i32.or (local.get 0) (i32.const 4))) end (local.get 0)))` ).exports.f(), 4 ); // Test tag dispatch for catches. assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (tag $exn3 (type 0)) (func (export "f") (result i32) try (result i32) throw $exn1 catch $exn1 i32.const 1 catch $exn2 i32.const 2 catch $exn3 i32.const 3 end))` ).exports.f(), 1 ); assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (tag $exn3 (type 0)) (func (export "f") (result i32) try (result i32) throw $exn2 catch $exn1 i32.const 1 catch $exn2 i32.const 2 catch $exn3 i32.const 3 end))` ).exports.f(), 2 ); assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (tag $exn3 (type 0)) (func (export "f") (result i32) try (result i32) throw $exn3 catch $exn1 i32.const 1 catch $exn2 i32.const 2 catch $exn3 i32.const 3 end))` ).exports.f(), 3 ); assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (tag $exn3 (type 0)) (tag $exn4 (type 0)) (func (export "f") (result i32) try (result i32) try (result i32) throw $exn4 catch $exn1 i32.const 1 catch $exn2 i32.const 2 catch $exn3 i32.const 3 end catch $exn4 i32.const 4 end))` ).exports.f(), 4 ); // Test usage of br before a throw. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) try $l (result i32) (i32.const 2) (br $l) (throw $exn) catch $exn drop (i32.const 1) end))` ).exports.f(), 2 ); assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn (type 0)) (func (export "f") (result i32) try $l (result i32) (throw $exn) catch $exn (i32.const 2) (br $l) rethrow 0 end))` ).exports.f(), 2 ); assertEq( wasmEvalText( `(module (type (func (param))) (tag $exn (type 0)) (func (export "f") (result i32) try $l (result i32) (throw $exn) catch_all (i32.const 2) (br $l) rethrow 0 end))` ).exports.f(), 2 ); // Test br branching out of a catch block. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) block $l (result i32) block (result i32) try (result i32) (i32.const 42) (throw $exn) catch $exn br $l (i32.const 99) end end end))` ).exports.f(), 42 ); // Test dead catch block. assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) i32.const 0 return try nop catch $exn end))` ).exports.f(), 0 ); assertEq( wasmEvalText( `(module (func (export "f") (result i32) i32.const 0 return try nop catch_all end))` ).exports.f(), 0 ); // Test catch with exception values pushed to stack. assertEq( wasmEvalText( `(module (type (func (param i32))) (type (func (param i32))) (type (func (param i64))) (tag $exn (type 0)) (tag $foo (type 1)) (tag $bar (type 2)) (func (export "f") (result i32) try $l (result i32) (i32.const 42) (throw $exn) catch $exn catch_all (i32.const 99) end))` ).exports.f(), 42 ); // Throw an exception carrying more than one value. assertEq( wasmEvalText( `(module (type (func (param i32 i64 f32 f64))) (tag $exn (type 0)) (func (export "f") (result i32) try $l (result i32 i64 f32 f64) (i32.const 42) (i64.const 84) (f32.const 42.2) (f64.const 84.4) (throw $exn) catch $exn catch_all (i32.const 99) (i64.const 999) (f32.const 99.9) (f64.const 999.9) end drop drop drop))` ).exports.f(), 42 ); // This should also work inside nested frames. assertEq( wasmEvalText( `(module (type (func (param i32 i64 f32 f64))) (tag $exn (type 0)) (func $foo (param i32 i64 f32 f64) (result i32 i64 f32 f64) (local.get 0) (local.get 1) (local.get 2) (local.get 3) (throw $exn)) (func (export "f") (result i32) try $l (result i32 i64 f32 f64) (i32.const 42) (i64.const 84) (f32.const 42.2) (f64.const 84.4) (call $foo) catch $exn catch_all (i32.const 99) (i64.const 999) (f32.const 99.9) (f64.const 999.9) end drop drop drop))` ).exports.f(), 42 ); // Multiple tagged catch in succession. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (func (export "f") (result i32) try (result i32) (i32.const 42) (throw $exn2) catch $exn1 catch $exn2 catch_all (i32.const 99) end))` ).exports.f(), 42 ); assertEq( wasmEvalText( `(module (tag $exn0) (tag $exn1) (tag $exn2) (tag $exn3) (tag $exn4) (tag $exn5) (func (export "f") (result i32) try (result i32) (throw $exn4) catch $exn5 (i32.const 5) catch $exn2 (i32.const 2) catch $exn4 (i32.const 4) ;; Caught here. catch $exn4 (i32.const 44) end ))` ).exports.f(), 4 ); // Try catch with block parameters. assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) (i32.const 42) try (param i32) (result i32) nop catch $exn (i32.const 99) end))` ).exports.f(), 42 ); assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) (i32.const 42) try $l (param i32) (result i32) (throw $exn) catch $exn catch_all (i32.const 99) end))` ).exports.f(), 42 ); // Test the catch_all case. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (func (export "f") (result i32) try $l (result i32) (i32.const 42) (throw $exn2) catch $exn1 catch_all (i32.const 99) end))` ).exports.f(), 99 ); assertEq( wasmEvalText( `(module (tag $exn (param i32)) (func (export "f") (result i32) try (result i32) try (result i32) (i32.const 42) (throw $exn) catch_all (i32.const 99) end catch $exn end))` ).exports.f(), 99 ); // Test foreign exception catch. assertEq( wasmEvalText( `(module (type (func)) (import "m" "foreign" (func $foreign)) (tag $exn (type 0)) (func (export "f") (result i32) (local i32) try $l (call $foreign) catch $exn catch_all (local.set 0 (i32.const 42)) end (local.get 0)))`, { m: { foreign() { throw 5; }, }, } ).exports.f(), 42 ); // Exception handlers should not catch traps. assertErrorMessage( () => wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) (local i32) try $l unreachable catch $exn (local.set 0 (i32.const 98)) catch_all (local.set 0 (i32.const 99)) end (local.get 0)))` ).exports.f(), WebAssembly.RuntimeError, "unreachable executed" ); // Ensure that a RuntimeError created by the user is not filtered out // as a trap emitted by the runtime (i.e., the filtering predicate is not // observable from JS). assertEq( wasmEvalText( `(module (import "m" "foreign" (func $foreign)) (func (export "f") (result i32) try (result i32) (call $foreign) (i32.const 99) catch_all (i32.const 42) end))`, { m: { foreign() { throw new WebAssembly.RuntimeError(); }, }, } ).exports.f(), 42 ); // Test uncatchable JS exceptions (OOM & stack overflow). { let f = wasmEvalText( `(module (import "m" "foreign" (func $foreign)) (func (export "f") (result) try (call $foreign) catch_all end))`, { m: { foreign() { throwOutOfMemory(); }, }, } ).exports.f; var thrownVal; try { f(); } catch (exn) { thrownVal = exn; } assertEq(thrownVal, "out of memory"); } assertErrorMessage( () => wasmEvalText( `(module (import "m" "foreign" (func $foreign)) (func (export "f") try (call $foreign) catch_all end))`, { m: { foreign: function foreign() { foreign(); }, }, } ).exports.f(), Error, "too much recursion" ); // Test all implemented instructions in a single module. { let divFunctypeInline = `(param $numerator i32) (param $denominator i32) (result i32)`; let safediv = wasmEvalText( `(module (tag $divexn (param i32 i32)) (tag $notThrownExn (param i32)) (func $throwingdiv ${divFunctypeInline} (local.get $numerator) (local.get $denominator) (if (param i32 i32) (result i32) (i32.eqz (local.get $denominator)) (then try (param i32 i32) (throw $divexn) delegate 0 (i32.const 9)) (else i32.div_u))) (func $safediv (export "safediv") ${divFunctypeInline} (local.get $numerator) (local.get $denominator) try (param i32 i32) (result i32) (call $throwingdiv) catch $notThrownExn catch $divexn i32.add catch_all (i32.const 44) end ))` ).exports.safediv; assertEq(safediv(6, 3), 2); assertEq(safediv(6, 0), 6); } // Test simple rethrow. assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try throw $exn catch $exn rethrow 0 end i32.const 1 catch $exn i32.const 27 end))` ).exports.f(), 27 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try throw $exn catch_all rethrow 0 end i32.const 1 catch $exn i32.const 27 end))` ).exports.f(), 27 ); // Test rethrows in nested blocks. assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try throw $exn catch $exn block rethrow 1 end end i32.const 1 catch $exn i32.const 27 end))` ).exports.f(), 27 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try throw $exn catch_all block rethrow 1 end end i32.const 1 catch $exn i32.const 27 end))` ).exports.f(), 27 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (func (export "f") (result i32) try (result i32) try throw $exn1 catch $exn1 try throw $exn2 catch $exn2 rethrow 1 end end i32.const 0 catch $exn1 i32.const 1 catch $exn2 i32.const 2 end))` ).exports.f(), 1 ); assertEq( wasmEvalText( `(module (type (func)) (tag $exn1 (type 0)) (tag $exn2 (type 0)) (func (export "f") (result i32) try (result i32) try throw $exn1 catch $exn1 try throw $exn2 catch_all rethrow 1 end end i32.const 0 catch $exn1 i32.const 1 catch $exn2 i32.const 2 end))` ).exports.f(), 1 ); // Test that rethrow makes the rest of the block dead code. assertEq( wasmEvalText( `(module (tag (param i32)) (func (export "f") (result i32) try (result i32) (i32.const 1) catch 0 (rethrow 0) (i32.const 2) end ))` ).exports.f(), 1 ); assertEq( wasmEvalText( `(module (tag (param i32)) (func (export "f") (result i32) try (result i32) try (i32.const 13) (throw 0) catch 0 (rethrow 0) end (unreachable) catch 0 end ))` ).exports.f(), 13 ); assertEq( wasmEvalText( `(module (tag) (func (export "f") (result i32) try (result i32) try (throw 0) catch 0 (i32.const 4) (rethrow 0) end (unreachable) catch 0 (i32.const 13) end ))` ).exports.f(), 13 ); assertEq( wasmEvalText( `(module (tag (param i32)) (func (export "f") (result i32) try (result i32) try (i32.const 13) (throw 0) catch 0 (i32.const 111) (rethrow 0) end (i32.const 222) catch 0 end ))` ).exports.f(), 13 ); // Test try-delegate blocks. // Dead delegate to caller assertEq( wasmEvalText( `(module (tag $exn (param)) (func (export "f") (result i32) i32.const 1 br 0 try throw $exn delegate 0))` ).exports.f(), 1 ); // Nested try-delegate. assertEq( wasmEvalText( `(module (tag $exn (param)) (func (export "f") (result i32) try (result i32) try try throw $exn delegate 0 end i32.const 0 catch $exn i32.const 1 end))` ).exports.f(), 1 ); // Non-throwing and breaking try-delegate. assertEq( wasmEvalText( `(module (tag $exn (param)) (func (export "f") (result i32) try (result i32) i32.const 1 br 0 delegate 0))` ).exports.f(), 1 ); assertEq( wasmEvalText( `(module (tag $exn (param)) (func (export "f") (result i32) try (result i32) i32.const 1 return delegate 0))` ).exports.f(), 1 ); // More nested try-delegate. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try i32.const 42 throw $exn delegate 0 i32.const 0 catch $exn i32.const 1 i32.add end))` ).exports.f(), 43 ); assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try (result i32) try i32.const 42 throw $exn delegate 1 i32.const 0 catch $exn i32.const 1 i32.add end catch $exn i32.const 2 i32.add end))` ).exports.f(), 44 ); assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) try (result i32) try (result i32) try i32.const 42 throw $exn delegate 1 i32.const 0 catch $exn i32.const 1 i32.add end delegate 0 catch $exn i32.const 2 i32.add end))` ).exports.f(), 44 ); assertEq( wasmEvalText( `(module (tag $exn (param)) (func $g (param i32) (result i32) (i32.const 42)) (func (export "f") (result i32) try (result i32) try $t block (result i32) (i32.const 4) (call $g) try throw $exn delegate $t end drop end i32.const 0 catch_all i32.const 1 end))` ).exports.f(), 1 ); // Test delegation to function body and blocks. // Non-throwing. assertEq( wasmEvalText( `(module (tag $exn (param)) (func (export "f") (result i32) try (result i32) i32.const 1 delegate 0))` ).exports.f(), 1 ); // Block target. assertEq( wasmEvalText( `(module (tag $exn (param i32)) (func (export "f") (result i32) try (result i32) block try i32.const 1 throw $exn delegate 0 end i32.const 0 catch $exn end))` ).exports.f(), 1 ); // Catch target. assertEq( wasmEvalText( `(module (tag $exn (param)) (func (export "f") (result i32) try (result i32) try throw $exn catch $exn try throw $exn delegate 0 end i32.const 0 catch_all i32.const 1 end))` ).exports.f(), 1 ); // Target function body. assertEq( wasmEvalText( `(module (type (func (param i32))) (tag $exn (type 0)) (func (export "f") (result i32) try (result i32) call $g catch $exn end) (func $g (result i32) try (result i32) try i32.const 42 throw $exn delegate 1 i32.const 0 catch $exn i32.const 1 i32.add end))` ).exports.f(), 42 ); // Try-delegate from inside a loop. assertEq( wasmEvalText( `(module (tag $exn) ;; For the purpose of this test, the params below should be increasing. (func (export "f") (param $depth_to_throw_exn i32) (param $maximum_loop_iterations i32) (result i32) (local $loop_countdown i32) ;; Counts how many times the loop was started. (local $loop_verifier i32) ;; The loop is counting down. (local.get $maximum_loop_iterations) (local.set $loop_countdown) try $catch_exn (result i32) try (loop $loop ;; Counts how many times the loop was started. (local.set $loop_verifier (i32.add (i32.const 1) (local.get $loop_verifier))) (if (i32.eqz (local.get $loop_countdown)) (then (return (i32.const 440))) (else try $rethrow_label (if (i32.eq (local.get $depth_to_throw_exn) (local.get $loop_countdown)) (then (throw $exn)) (else (local.set $loop_countdown (i32.sub (local.get $loop_countdown) (i32.const 1))))) catch $exn try (rethrow $rethrow_label) delegate $catch_exn end )) (br $loop)) catch_all unreachable end (i32.const 2000) catch_all (i32.const 10000) end (i32.add (local.get $loop_verifier))))` ).exports.f(3, 5), 10003 );