// 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
);
