// Used to ensure tests will trigger Wasm Jit stub code.
var threshold = 2 * getJitCompilerOptions()["ion.warmup.trigger"] + 10;
function testWithJit(f) {
  for (var i = 0; i < threshold; i++) {
    f();
  }
}

function testRet() {
  var f = wasmEvalText(`(module
    (func (export "f") (result i64) (i64.const 66))
  )`).exports.f;

  testWithJit(() => {
    assertEq(typeof f(), "bigint", "should return a bigint");
    assertEq(f(), 66n, "should return the correct value");
  });
}

function testId() {
  var exports = wasmEvalText(`(module
    (func (export "f") (param i64 i64) (result i64)
      (local.get 0)
    )
    (func (export "f2") (param i64 i64) (result i64)
      (local.get 1)
    )
  )`).exports;
  var f = exports.f;
  var f2 = exports.f2;

  testWithJit(() => {
    assertEq(f(0n, 1n), 0n);
    assertEq(f(-0n, 1n), -0n);
    assertEq(f(123n, 1n), 123n);
    assertEq(f(-123n, 1n), -123n);
    assertEq(f(2n ** 63n, 1n), -(2n ** 63n));
    assertEq(f(2n ** 64n + 123n, 1n), 123n);
    assertEq(f("5", 1n), 5n);
    assertEq(f(true, 1n), 1n);
    assertEq(f(false, 1n), 0n);
    assertEq(f({ toString() { return "5"; }, }, 1n), 5n);
    assertEq(f({ valueOf() { return 5n; }, }, 1n), 5n);

    assertEq(f2(1n, 0n), 0n);
    assertEq(f2(1n, -0n), -0n);
    assertEq(f2(1n, 123n), 123n);
    assertEq(f2(1n, -123n), -123n);
    assertEq(f2(1n, 2n ** 63n), -(2n ** 63n));
    assertEq(f2(1n, 2n ** 64n + 123n), 123n);
    assertEq(f2(1n, "5"), 5n);
    assertEq(f2(1n, true), 1n);
    assertEq(f2(1n, false), 0n);
    assertEq(f2(1n, { toString() { return "5"; }, }), 5n);
    assertEq(f2(1n, { valueOf() { return 5n; }, }), 5n);
  });
}

function testNonBigIntArgs() {
  var f = wasmEvalText(`(module
    (func (export "f") (param i64) (result i64)
      (local.get 0)
    )
  )`).exports.f;

  assertErrorMessage(() => f(5), TypeError, "can't convert 5 to BigInt");
  assertErrorMessage(() => f({ valueOf() { return 5; }, }),
                     TypeError,
                     "can't convert 5 to BigInt");
  assertErrorMessage(() => f(5.3), TypeError, "can't convert 5.3 to BigInt");
  assertErrorMessage(() => f(), TypeError, "can't convert undefined to BigInt");
  assertErrorMessage(
    () => f(undefined),
    TypeError,
    "can't convert undefined to BigInt"
  );
  assertErrorMessage(() => f(null), TypeError, "can't convert null to BigInt");
  assertErrorMessage(
    () => f(Symbol("foo")),
    TypeError,
    'can\'t convert Symbol("foo") to BigInt'
  );
  assertErrorMessage(() => f({}), SyntaxError, "invalid BigInt syntax");
  assertErrorMessage(() => f({ valueof() { return "foo"; }, }),
                     SyntaxError,
                     "invalid BigInt syntax");
  assertErrorMessage(() => f("x"), SyntaxError, "invalid BigInt syntax");
}

function testIdPlus() {
  var f = wasmEvalText(`(module
    (func (export "f") (param i64) (result i64)
      (i64.const 8)
      (get_local 0)
      (i64.add)
    )
  )`).exports.f;

  testWithJit(() => {
    assertEq(f(0n), 0n + 8n);
    assertEq(f(147n), 147n + 8n);
  });
}

// Test functions with many parameters to stress ABI cases. We want to test
// spilled arguments both under and over the Ion call inlining limit.
function testManyArgs() {
  var f1 = wasmEvalText(`(module
    (func (export "f")
      (param i64 i64 i64 i64 i64 i64 i64 i64)
      (result i64)
      (get_local 0)
      (get_local 1)
      (get_local 2)
      (get_local 3)
      (get_local 4)
      (get_local 5)
      (get_local 6)
      (get_local 7)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
    )
  )`).exports.f;

  var f2 = wasmEvalText(`(module
    (func (export "f")
      (param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
      (result i64)
      (get_local 0)
      (get_local 1)
      (get_local 2)
      (get_local 3)
      (get_local 4)
      (get_local 5)
      (get_local 6)
      (get_local 7)
      (get_local 8)
      (get_local 9)
      (get_local 10)
      (get_local 11)
      (get_local 12)
      (get_local 13)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
      (i64.add)
    )
  )`).exports.f;

  testWithJit(() => {
    assertEq(f1(1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n), 8n);
    assertEq(f2(1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n), 14n);
  });
}

// Test import and re-export.
function testImportExport() {
  var f1 = wasmEvalText(
    `(module
      (import "i64" "func" (func (param i64)))
      (export "f" (func 0))
    )`,
    {
      i64: {
        func(b) {
          assertEq(b, 42n);
        },
      },
    }
  ).exports.f;

  var f2 = wasmEvalText(
    `(module
      (import "i64" "func" (func (param i64) (result i64)))
      (export "f" (func 0))
    )`,
    {
      i64: {
        func(n) {
          return n + 1n;
        },
      },
    }
  ).exports.f;

  var f3 = wasmEvalText(
    `(module
      (import "" "i64" (func $i64 (param i64) (result i64)))
      (func (export "f") (param i64) (result i64)
        (get_local 0)
        (call $i64))
    )`,
    {
      "": {
        i64: n => {
          return n + 1n;
        },
      },
    }
  ).exports.f;

  var f4 = wasmEvalText(
    `(module
      (import "i64" "func" (func (result i64)))
      (export "f" (func 0))
    )`,
    { i64: { func() {} } }
  ).exports.f;

  testWithJit(() => {
    assertEq(f1(42n), undefined);
    assertEq(f2(42n), 43n);
    assertEq(f3(42n), 43n);
    assertErrorMessage(() => f4(42), TypeError, "can't convert undefined to BigInt");
  });
}

// Test that a mixture of I64 and other argument types works.
function testMixedArgs() {
  var f = wasmEvalText(`(module
    (func (export "f")
      (param i64 f32 f64 i32 i64)
      (result i64)
      (get_local 1)
      (i64.trunc_s/f32)
      (get_local 2)
      (i64.trunc_s/f64)
      (i64.add)
      (get_local 3)
      (i64.extend_i32_u)
      (i64.add)
      (get_local 0)
      (i64.add)
      (get_local 4)
      (i64.add)
    )
  )`).exports.f;

  testWithJit(() => {
    assertEq(f(1n, 1.3, 1.7, 1, 1n), 5n);
  });
}

function testGlobalImport() {
  var exports = wasmEvalText(
    `(module
      (import "g" "a" (global $a i64))
      (import "g" "b" (global $b i64))
      (import "g" "c" (global $c i64))

      (export "a" (global $a))
      (export "b" (global $b))
      (export "c" (global $c))
    )`,
    { g: { a: 1n, b: 2n ** 63n, c: -100n } }
  ).exports;

  testWithJit(() => {
    assertEq(exports.a.value, 1n);
    assertEq(exports.b.value, -(2n ** 63n));
    assertEq(exports.c.value, -100n);
  });
}

function testMutableGlobalImport() {
  var exports = wasmEvalText(
    `(module
      (import "g" "a" (global $a (mut i64)))
      (import "g" "b" (global $b (mut i64)))

      (export "a" (global $a))
      (export "b" (global $b))
    )`,
    {
      g: {
        a: new WebAssembly.Global({ value: "i64", mutable: true }, 1n),
        b: new WebAssembly.Global({ value: "i64", mutable: true }, "2"),
      },
    }
  ).exports;

  testWithJit(() => {
    assertEq(exports.a.value, 1n);
    assertEq(exports.b.value, 2n);
  });
}

function testMutableGlobalImportLiteral() {
  assertErrorMessage(
    () =>
      wasmEvalText(
        `(module
          (import "g" "a" (global $a (mut i64)))
        )`,
        { g: { a: 1n } }
      ),
    WebAssembly.LinkError,
    "imported global mutability mismatch"
  );
}

function testGlobalBadImportLiteral() {
  assertErrorMessage(
    () =>
      wasmEvalText(
        `(module
          (import "g" "a" (global $a i64))
          (export "a" (global $a))
        )`,
        { g: { a: 1 } }
      ),
    WebAssembly.LinkError,
    "import object field 'a' is not a BigInt"
  );

  assertErrorMessage(
    () =>
      wasmEvalText(
        `(module
          (import "g" "a" (global $a i64))
          (export "a" (global $a))
        )`,
        { g: { a: "foo" } }
      ),
    WebAssembly.LinkError,
    "import object field 'a' is not a BigInt"
  );
}

// This exercises error code paths that were added due to BigInt/I64
// conversion, though the test does not directly deal with I64 types.
function testGlobalBadImportNumber() {
  assertErrorMessage(
    () =>
      wasmEvalText(
        `(module
          (import "g" "a" (global $a i32))
          (export "a" (global $a))
        )`,
        { g: { a: 1n } }
      ),
    WebAssembly.LinkError,
    "import object field 'a' is not a Number"
  );

  assertErrorMessage(
    () =>
      wasmEvalText(
        `(module
          (import "g" "a" (global $a i32))
          (export "a" (global $a))
        )`,
        { g: { a: "foo" } }
      ),
    WebAssembly.LinkError,
    "import object field 'a' is not a Number"
  );
}

function testI64Global() {
  var global = new WebAssembly.Global({ value: "i64", mutable: true });

  assertEq(global.value, 0n); // initial value

  global.value = 123n;
  assertEq(global.value, 123n);

  global.value = 2n ** 63n;
  assertEq(global.value, -(2n ** 63n));

  global.value = "123";
  assertEq(global.value, 123n);
}

function testI64GlobalValueOf() {
  var argument = { value: "i64" };

  // as literal
  var global = new WebAssembly.Global(argument, {
    valueOf() {
      return 123n;
    },
  });
  assertEq(global.value, 123n);

  // as string
  var global2 = new WebAssembly.Global(argument, {
    valueOf() {
      return "123";
    },
  });
  assertEq(global.value, 123n);
}

function testGlobalI64ValueWrongType() {
  var argument = { value: "i64" };
  assertErrorMessage(
    () => new WebAssembly.Global(argument, 666),
    TypeError,
    "can't convert 666 to BigInt"
  );
  assertErrorMessage(
    () => new WebAssembly.Global(argument, "foo"),
    SyntaxError,
    "invalid BigInt syntax"
  );
  assertErrorMessage(
    () =>
      new WebAssembly.Global(argument, {
        valueOf() {
          return 5;
        },
      }),
    TypeError,
    "can't convert 5 to BigInt"
  );
}

function testGlobalI64SetWrongType() {
  var global = new WebAssembly.Global({ value: "i64", mutable: true });
  assertErrorMessage(() => (global.value = 1), TypeError, "can't convert 1 to BigInt");
  assertErrorMessage(
    () => (global.value = "foo"),
    SyntaxError,
    "invalid BigInt syntax"
  );
  assertErrorMessage(
    () =>
      (global.value = {
        valueOf() {
          return 5;
        },
      }),
    TypeError,
    "can't convert 5 to BigInt"
  );
}

testRet();
testId();
testIdPlus();
testNonBigIntArgs();
testManyArgs();
testImportExport();
testMixedArgs();
testGlobalImport();
testMutableGlobalImport();
testMutableGlobalImportLiteral();
testGlobalBadImportLiteral();
testGlobalBadImportNumber();
testI64Global();
testI64GlobalValueOf();
testGlobalI64ValueWrongType();
testGlobalI64SetWrongType();
