// META: global=window,worker,jsshell,shadowrealm
// META: script=/wasm/jsapi/wasm-module-builder.js

function assert_throws_wasm(fn, message) {
  try {
    fn();
    assert_not_reached(`expected to throw with ${message}`);
  } catch (e) {
    assert_true(e instanceof WebAssembly.Exception, `Error should be a WebAssembly.Exception with ${message}`);
  }
}

promise_test(async () => {
  const kSig_v_r = makeSig([kWasmExternRef], []);
  const builder = new WasmModuleBuilder();
  const tagIndex = builder.addTag(kSig_v_r);
  builder.addFunction("throw_param", kSig_v_r)
    .addBody([
      kExprLocalGet, 0,
      kExprThrow, tagIndex,
    ])
    .exportFunc();
  const buffer = builder.toBuffer();
  const {instance} = await WebAssembly.instantiate(buffer, {});
  const values = [
    undefined,
    null,
    true,
    false,
    "test",
    Symbol(),
    0,
    1,
    4.2,
    NaN,
    Infinity,
    {},
    () => {},
  ];
  for (const v of values) {
    assert_throws_wasm(() => instance.exports.throw_param(v), String(v));
  }
}, "Wasm function throws argument");

promise_test(async () => {
  const builder = new WasmModuleBuilder();
  const tagIndex = builder.addTag(kSig_v_a);
  builder.addFunction("throw_null", kSig_v_v)
    .addBody([
      kExprRefNull, kAnyFuncCode,
      kExprThrow, tagIndex,
    ])
    .exportFunc();
  const buffer = builder.toBuffer();
  const {instance} = await WebAssembly.instantiate(buffer, {});
  assert_throws_wasm(() => instance.exports.throw_null());
}, "Wasm function throws null");

promise_test(async () => {
  const builder = new WasmModuleBuilder();
  const tagIndex = builder.addTag(kSig_v_i);
  builder.addFunction("throw_int", kSig_v_v)
    .addBody([
      ...wasmI32Const(7),
      kExprThrow, tagIndex,
    ])
    .exportFunc();
  const buffer = builder.toBuffer();
  const {instance} = await WebAssembly.instantiate(buffer, {});
  assert_throws_wasm(() => instance.exports.throw_int());
}, "Wasm function throws integer");

promise_test(async () => {
  const builder = new WasmModuleBuilder();
  const fnIndex = builder.addImport("module", "fn", kSig_v_v);
  const tagIndex= builder.addTag(kSig_v_r);
  builder.addFunction("catch_exception", kSig_r_v)
    .addBody([
      kExprTry, kWasmStmt,
        kExprCallFunction, fnIndex,
      kExprCatch, tagIndex,
        kExprReturn,
      kExprEnd,
      kExprRefNull, kExternRefCode,
    ])
    .exportFunc();

  const buffer = builder.toBuffer();

  const error = new Error();
  const fn = () => { throw error };
  const {instance} = await WebAssembly.instantiate(buffer, {
    module: { fn }
  });
  assert_throws_exactly(error, () => instance.exports.catch_exception());
}, "Imported JS function throws");

promise_test(async () => {
  const builder = new WasmModuleBuilder();
  const fnIndex = builder.addImport("module", "fn", kSig_v_v);
  builder.addFunction("catch_and_rethrow", kSig_r_v)
    .addBody([
      kExprTry, kWasmStmt,
        kExprCallFunction, fnIndex,
      kExprCatchAll,
        kExprRethrow, 0x00,
      kExprEnd,
      kExprRefNull, kExternRefCode,
    ])
    .exportFunc();

  const buffer = builder.toBuffer();

  const error = new Error();
  const fn = () => { throw error };
  const {instance} = await WebAssembly.instantiate(buffer, {
    module: { fn }
  });
  assert_throws_exactly(error, () => instance.exports.catch_and_rethrow());
}, "Imported JS function throws, Wasm catches and rethrows");
