var ins = wasmEvalText(`(module
    (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
        (if (result i64) (i64.eqz (local.get 0))
        (then (local.get 1))
        (else
            (return_call $fac-acc
                (i64.sub (local.get 0) (i64.const 1))
                (i64.mul (local.get 0) (local.get 1))
            )
        )
        )
    )
    (func (export "main") (param i64) (result i64)
        (call $fac-acc (local.get 0) (i64.const 1))
    )
)`);

// Check return call via wasm function
assertEq(ins.exports.main(5n), 120n);

// Check return call directly via interpreter stub
const fac = ins.exports["fac-acc"];
assertEq(fac(4n, 1n), 24n);

// Check return call directly via jit stub
check_stub1: {
    let options = getJitCompilerOptions();
    if (!options["baseline.enable"]) break check_stub1;
    const check = function() {
        fac(4n, 1n);
    };
    for (let i = options["baseline.warmup.trigger"] + 1; i--;)
        check();
}

// Return call of an import
var ins0 = wasmEvalText(`(module
    (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
      (if (result i64) (i64.eqz (local.get 0))
        (then (local.get 1))
        (else
          (return_call $fac-acc
            (i64.sub (local.get 0) (i64.const 1))
            (i64.mul (local.get 0) (local.get 1))
          )
        )
      )
    )
)`);

var ins = wasmEvalText(`(module
    (import "" "fac-acc" (func $fac-acc (param i64 i64) (result i64)))
    (func (export "fac") (param i64) (result i64)
      local.get 0
      i64.const 1
      return_call $fac-acc
    )
    (func (export "main") (result i64)
      i64.const 4
      call 1
    )
)`, {"": {"fac-acc": ins0.exports["fac-acc"],}});

assertEq(ins.exports.main(), 24n);
assertEq(ins.exports.fac(3n, 1n), 6n);
check_stub2: {
    let options = getJitCompilerOptions();
    if (!options["baseline.enable"]) break check_stub2;
    const check = function() {
        ins.exports.fac(3n, 1n)
    };
    for (let i = options["baseline.warmup.trigger"] + 1; i--;)
        check();
}

// Check with parameters area growth
var ins0 = wasmEvalText(`(module
  (func $fac-acc (export "fac-acc") (param i64 i64 i64 i64 i64 i64 i64 i64) (result i64)
    (if (result i64) (i64.eqz (local.get 0))
      (then (local.get 1))
      (else
        (return_call $fac-acc
          (i64.sub (local.get 0) (i64.const 1))
          (i64.mul (local.get 0) (local.get 1))
          i64.const 1 i64.const 2 i64.const 3 i64.const 4 i64.const 5 i64.const 6
        )
      )
    )
  )
)`);

var ins = wasmEvalText(`(module
  (import "" "fac-acc" (func $fac-acc (param i64 i64 i64 i64 i64 i64 i64 i64) (result i64)))
  (func (export "fac") (param i64) (result i64)
    local.get 0
    i64.const 1
    i64.const 1 i64.const 2 i64.const 3 i64.const 4 i64.const 5 i64.const 6
    return_call $fac-acc
  )
  (func (export "main") (result i64)
    i64.const 5
    call 1
  )
)`, {"": {"fac-acc": ins0.exports["fac-acc"],}});

assertEq(ins.exports.main(), 120n);
assertEq(ins.exports.fac(3n, 1n), 6n);
check_stub3: {
    let options = getJitCompilerOptions();
    if (!options["baseline.enable"]) break check_stub3;
    const check = function() {
        ins.exports.fac(4n, 1n)
    };
    for (let i = options["baseline.warmup.trigger"] + 1; i--;)
        check();
}

// Test multi-value returns.
var ins = wasmEvalText(`(module
  (memory (export "memory") 1 1)
  (func $rec (export "rec") (param i32 i32 i32 i32 i32 i32 i32) (result i32 i32 f32 f32)
    (local f32 i32)
    (if (result i32 i32 f32 f32) (i32.ge_u (local.get 0) (local.get 1))
      (then
        (local.get 5)
        (local.get 6)
        (local.tee 7 (f32.div (f32.convert_i32_u (local.get 3)) (f32.convert_i32_u (local.get 2))))
        (f32.sqrt
          (f32.sub
            (f32.div (f32.convert_i32_u (local.get 4)) (f32.convert_i32_u (local.get 2)))
            (f32.mul (local.get 7) (local.get 7))
          )
        )
      )
      (else
        (return_call $rec
          (i32.add (local.get 0) (i32.const 1))
          (local.get 1)
          (i32.add (local.get 2) (i32.const 1))
          (i32.add (local.get 3) (local.tee 8 (i32.load8_u (local.get 0))))
          (i32.add (local.get 4) (i32.mul (local.get 8) (local.get 8)))
          (if (result i32) (i32.gt_s (local.get 5) (local.get 8))
            (then (local.get 8)) (else (local.get 5))
          )
          (if (result i32) (i32.lt_s (local.get 6) (local.get 8))
            (then (local.get 8)) (else (local.get 6))
          )
        )
      )
    )
  )
  (func $main (export "main") (result i32 i32 f32 f32)
    (call $rec
      (i32.const 0)
      (i32.const 6)
      (i32.const 0)
      (i32.const 0)
      (i32.const 0)
      (i32.const 1000)
      (i32.const -1000)
    )
  )
  (data (i32.const 0) "\\02\\13\\22\\04\\08\\30")
)`);

const main = ins.exports["main"];
assertEq(""+ main(), "2,48,19.16666603088379,16.836633682250977");
assertEq("" + ins.exports.rec(1, 5, 0, 0, 0, 1000, -1000), "4,34,16.25,11.627016067504883");
check_stub3: {
    let options = getJitCompilerOptions();
    if (!options["baseline.enable"]) break check_stub3;
    const check = function() {
        ins.exports.rec(1, 5, 0, 0, 0, 1000, -1000);
    };
    for (let i = options["baseline.warmup.trigger"] + 1; i--;)
        check();
}

// Handling trap.
var ins = wasmEvalText(`(module
  (func $fac-acc (export "fac") (param i64 i64) (result i64)
    (if (result i64) (i64.eqz (local.get 0))
    (then (unreachable))
    (else
        (return_call $fac-acc
            (i64.sub (local.get 0) (i64.const 1))
            (i64.mul (local.get 0) (local.get 1))
        )
    )
    )
  )
  (func (export "main") (param i64) (result i64)
    (call $fac-acc (local.get 0) (i64.const 1))
  )
)`);

assertErrorMessage(() => ins.exports.main(4n), WebAssembly.RuntimeError, /unreachable executed/);
assertErrorMessage(() => ins.exports.fac(3n, 1n), WebAssembly.RuntimeError, /unreachable executed/);

// Performance and stack growth: calculating sum of numbers 1..40000000
var ins = wasmEvalText(`(module
  (func $sum (param i32 i64) (result i64)
    local.get 0
    i32.eqz
    if
      local.get 1
      return
    else
      local.get 0
      i32.const 1
      i32.sub
      local.get 1
      local.get 0
      i64.extend_i32_s
      i64.add
      return_call $sum
    end
    unreachable
  )

  (func (export "main") (param i32) (result i64)
    local.get 0
    i64.const 0
    call $sum
  )
)`);

if (getBuildConfiguration("simulator")) {
  assertEq(ins.exports.main(400000), 80000200000n);
} else {
  assertEq(ins.exports.main(40000000), 800000020000000n);
}

// GC/externref shall not cling to the trampoline frame.
// The `return_call` caller will create a trampoline because the callee is
// an import. The caller will create a GC object and will hold in its frame
// and a WeakMap.
// Test if the created object is in the WeakMap even after gc().
var wm = new WeakMap();
var ins = wasmEvalText(`(module
  (import "" "test" (func $test))
  (func $sum (param i32 i64) (result i64)
    local.get 0
    i32.eqz
    if
      call $test
      local.get 1
      return
    else
      local.get 0
      i32.const 1
      i32.sub
      local.get 1
      local.get 0
      i64.extend_i32_s
      i64.add
      return_call $sum
    end
    unreachable
  )
  (export "sum" (func $sum))
)`, {"": {
  test() {
    gc();
    assertEq(nondeterministicGetWeakMapKeys(wm).length, 0);
  }
}});

var ins2 = wasmEvalText(`(module
  (import "" "add_ref" (func $add_ref (result externref)))
  (import "" "use_ref" (func $use_ref (param externref)))
  (import "" "sum" (func $sum (param i32 i64) (result i64)))
  (global $g1 (mut i32) (i32.const 0))
  (func (export "main_gc") (param i32) (result i64)
    (local $ref externref)
    call $add_ref
    local.set $ref

    local.get $ref
    call $use_ref

    block
      global.get $g1
      br_if 0
      local.get 0
      i64.const 0
      return_call $sum
    end

    local.get $ref
    call $use_ref
    i64.const -1
  )
)`, {"": {
  sum: ins.exports.sum,
  add_ref() {
    const obj = {}; wm.set(obj, 'foo'); return obj;
  },
  use_ref(obj) {
    assertEq(nondeterministicGetWeakMapKeys(wm).length, 1);
  },
}});

assertEq(ins2.exports.main_gc(400000), 80000200000n);
assertEq(nondeterministicGetWeakMapKeys(wm).length, 0);
