// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --expose-wasm --experimental-wasm-return-call
// Reduce the stack size to test that we are indeed doing return calls (instead
// of standard calls which consume stack space).
// Flags: --stack-size=128

load("test/mjsunit/wasm/wasm-module-builder.js");

(function TestFactorialReturnCall() {
  print(arguments.callee.name);

  let builder = new WasmModuleBuilder();

  const sig_i_iii = builder.addType(kSig_i_iii);

  // construct the code for the function
  // f_aux(N,X) where N=<1 => X
  // f_aux(N,X) => f_aux(N-1,X*N)
  let fact_aux = builder.addFunction("fact_aux",kSig_i_ii);
  fact_aux.addBody([
    kExprLocalGet, 0, kExprI32Const, 1, kExprI32LeS,
    kExprIf, kWasmI32,
      kExprLocalGet, 1,
    kExprElse,
      kExprLocalGet, 0,
      kExprI32Const, 1,
      kExprI32Sub,
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kExprI32Mul,
      kExprReturnCall, fact_aux.index,
    kExprEnd
      ]);

  //main(N)=>fact_aux(N,1)
  let main = builder.addFunction("main", kSig_i_i)
        .addBody([
          kExprLocalGet, 0,
          kExprI32Const, 1,
          kExprReturnCall,0
    ]).exportFunc();

  let module = builder.instantiate();

  print(" --three--");
  assertEquals(6, module.exports.main(3));
  print(" --four--");
  assertEquals(24, module.exports.main(4));
})();

(function TestIndirectFactorialReturnCall() {
  print(arguments.callee.name);

  let builder = new WasmModuleBuilder();

  const sig_i_iii = builder.addType(kSig_i_iii);

  // construct the code for the function
  // fact(N) => f_ind(N,1,f).
  //
  // f_ind(N,X,_) where N=<1 => X
  // f_ind(N,X,F) => F(N-1,X*N,F).

  let f_ind = builder.addFunction("f_ind",kSig_i_iii).
      addBody([
    kExprLocalGet, 0, kExprI32Const, 1, kExprI32LeS,
    kExprIf, kWasmI32,
      kExprLocalGet, 1,
    kExprElse,
      kExprLocalGet, 0,
      kExprI32Const, 1,
      kExprI32Sub,
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kExprI32Mul,
      kExprLocalGet, 2,
      kExprLocalGet, 2,
      kExprReturnCallIndirect, sig_i_iii, kTableZero,
    kExprEnd
      ]);

  //main(N)=>fact_aux(N,1)
  let main = builder.addFunction("main", kSig_i_i)
        .addBody([
      kExprLocalGet, 0,
      kExprI32Const, 1,
      kExprI32Const, f_ind.index,
      kExprReturnCall, f_ind.index
    ]).exportFunc();

  builder.appendToTable([f_ind.index, main.index]);

  let module = builder.instantiate();

  print(" --three--");
  assertEquals(6, module.exports.main(3));
  print(" --four--");
  assertEquals(24, module.exports.main(4));
})();

(function TestImportReturnCall() {
  print(arguments.callee.name);

  let builder = new WasmModuleBuilder();

  const sig_i_iii = builder.addType(kSig_i_iii);

  let pick = builder.addImport("q", "pick", sig_i_iii);

  let main = builder.addFunction("main", kSig_i_iii)
        .addBody([
          kExprLocalGet, 1,
          kExprLocalGet, 2,
          kExprLocalGet, 0,
          kExprReturnCall, pick
        ])
        .exportFunc();

  let module = builder.instantiate({q: {
    pick: function(a, b, c) { return c ? a : b; }}});

  print(" --left--");
  assertEquals(-2, module.exports.main(1, -2, 3));
  print(" --right--");
  assertEquals(3, module.exports.main(0, -2, 3));
})();

(function TestImportIndirectReturnCall() {
  print(arguments.callee.name);

  let builder = new WasmModuleBuilder();

  const sig_i_iii = builder.addType(kSig_i_iii);

  let pick = builder.addImport("q", "pick", sig_i_iii);
  builder.addTable(kWasmAnyFunc, 4);
  // Arbitrary location in the table.
  const tableIndex = 3;

  builder.addElementSegment(0, tableIndex,false,[pick]);

  let main = builder.addFunction("main", kSig_i_iii)
        .addBody([
          kExprLocalGet, 1,
          kExprLocalGet, 2,
          kExprLocalGet, 0,
          kExprI32Const, tableIndex,
          kExprReturnCallIndirect, sig_i_iii, kTableZero
        ])
        .exportFunc();
  builder.appendToTable([pick, main.index]);

  let module = builder.instantiate({q: {
    pick: function(a, b, c) { return c ? a : b; }
  }});

  print(" --left--");
  assertEquals(-2, module.exports.main(1, -2, 3));
  print(" --right--");
  assertEquals(3, module.exports.main(0, -2, 3));
})();

(function TestMultiReturnCallWithLongSig() {
  print(arguments.callee.name);
  const callee_inputs = 10;
  // Tail call from a function with less, as many, or more parameters than the
  // callee.
  for (caller_inputs = 9; caller_inputs <= 11; ++caller_inputs) {
    let builder = new WasmModuleBuilder();

    // f just returns its arguments in reverse order.
    const f_params = new Array(callee_inputs).fill(kWasmI32);
    const f_returns = f_params;
    const f_sig = builder.addType(makeSig(f_params, f_returns));
    let f_body = [];
    for (i = 0; i < callee_inputs; ++i) {
      f_body.push(kExprLocalGet, callee_inputs - i - 1);
    }
    const f = builder.addFunction("f", f_sig).addBody(f_body);

    // Slice or pad the caller inputs to match the callee.
    const main_params = new Array(caller_inputs).fill(kWasmI32);
    const main_sig = builder.addType(makeSig(main_params, f_returns));
    let main_body = [];
    for (i = 0; i < callee_inputs; ++i) {
      main_body.push(kExprLocalGet, Math.min(caller_inputs - 1, i));
    }
    main_body.push(kExprReturnCall, f.index);
    builder.addFunction("main", main_sig).addBody(main_body).exportFunc();

    let module = builder.instantiate();

    inputs = [];
    for (i = 0; i < caller_inputs; ++i) {
      inputs.push(i);
    }
    let expect = inputs.slice(0, callee_inputs);
    while (expect.length < callee_inputs) {
      expect.push(inputs[inputs.length - 1]);
    }
    expect.reverse();
    assertEquals(expect, module.exports.main(...inputs));
  }
})();