// -----------------------------------------------------------------------------
// The tests in this file assert that any side effects that happened in try code
// before an exception was thrown, will be known to the landing pad. It checks
// local throws and throws from direct calls (of local and imported functions).
// Side effects checked are changes to locals, and to globals.
// -----------------------------------------------------------------------------

load(libdir + "eqArrayHelper.js");


function testSideEffectsOnLocals() {
  // Locals set before and after throwing instructions, either locally, or from
  // a direct wasm function call, for locals of all Wasm numtype and a local of
  // reftype (externref), and the thrown exception carrying a value from each
  // Wasm numtype and one of Wasm vectype (Simd128). Testing to see if the state
  // of the locals at the moment $exn is thrown, is known to the landing pad
  // when $exn is caught.

  let localThrow = "(throw $exn)";

  // The following is taken from calls.js
  // Some variables to be used in all tests.
  let typesJS = ["i32", "i64", "f32", "f64"];
  let types = typesJS.join(" ");
  let exnTypeDef = `(type $exnType (func (param ${types})))`;
  let correctLocalValues =
      `;; Correct local values
             (i32.const 2)
             (i64.const 3)
             (f32.const 4)
             (f64.const 13.37)`;
  let correctLocalValuesJS = [2, 3n, 4, 13.37];

  let wrongValues =
      `;; Wrong values.
             (i32.const 5)
             (i64.const 6)
             (f32.const 0.1)
             (f64.const 0.6437)`;
  let wrongValuesJS = [5, 6n, 0.1, 0.6437];

  // These variables are specific to the tests in this file.
  let throwValues =
      `;; Values to throw and catch.
             (i32.const 7)
             (i64.const 8)
             (f32.const 9)
             (f64.const 27.11)`;
  let thrownValuesJS = [7, 8n, 9, 27.11];

  let correctResultsJS = function(externref) {
    return [].concat(thrownValuesJS,
                     correctLocalValuesJS,
                     [externref, 1]);
  }

  // Testing also locals of Wasm vectype.
  // The following depend on whether simd is enabled or not. We write it like
  // this so we can run this test also when SIMD is not enabled.
  let wrongV128 = "";
  let correctV128 = "";
  let checkV128Value = "";

  if (wasmSimdEnabled()) {
    wrongV128 = `(v128.const i32x4 11 22 33 44)`;
    correctV128 = `(v128.const i32x4 55 66 77 88)`;
    checkV128Value =
      `       ${correctV128}
              (i32x4.eq)
              (i32x4.all_true)`;
    v128Type = " v128";
  } else {
    wrongV128 = "(i32.const 0)";
    correctV128 = "(i32.const 1)";
    v128Type = " i32";
  }

  let localTypes = types + " externref";
  let resultTypes = types + " " + localTypes;

  // The last i32 in the results is the v128 check.
  let testFuncTypeInline =
      `(param $argCorrectRef externref)
       (param $argWrongRef externref)
       (result ${resultTypes} i32)
       (local $localI32 i32)
       (local $localI64 i64)
       (local $localF32 f32)
       (local $localF64 f64)
       (local $localExternref externref)
       (local $localV128 ${v128Type})`;

  let localsSet =
      `;; Set locals.
             (local.set $localV128)
             (local.set $localExternref)
             (local.set $localF64)
             (local.set $localF32)
             (local.set $localI64)
             (local.set $localI32)`;
  let localsGet =
      `;; Get locals.
             (local.get $localI32)
             (local.get $localI64)
             (local.get $localF32)
             (local.get $localF64)
             (local.get $localExternref)
             (local.get $localV128)`;

  // The test module parts. ----------------------------------------------------

  let importsModule =
      `(module
         (type $exnType (func (param ${types})))
         (tag $exn (export "exn") (type $exnType))
         (func (export "throwif") (param $ifPredicate i32)
           (if (local.get $ifPredicate)
             (then
               ${throwValues}
               ${localThrow}))))`;

  let moduleHeader = `
       (module
         ${exnTypeDef}
         (import "m" "exn" (tag $exn (type $exnType)))
         (tag $emptyExn)
         (import "m" "throwif" (func $throwif (param $ifPredicate i32)))
         (func $wontThrow
           (throw $emptyExn))
         (func $localCallThrow
             ${throwValues}
             ${localThrow})
         (func (export "testFunc") ${testFuncTypeInline}
           try (result ${resultTypes} ${v128Type})
             ;; Locals not set.
             (i32.const 0) ;; Predicate for $throwif.
             (call $throwif)  ;; So this doesn't throw.
             ;; Set correct locals before throw to be caught.
             ${correctLocalValues}
             (local.get $argCorrectRef)
             ${correctV128}
             ${localsSet}
             ;; Next up should be $exn being thrown locally or via a call.`;

  let moduleRest = ` ;; The above throw to $exn should be caught here --------.
             ;; Set wrong locals after throw to be caught.              ;;    |
             ${wrongValues}                                             ;;    |
             (local.get $argWrongRef) ;; The wrong externref param.     ;;    |
             ${wrongV128}                                               ;;    |
             ${localsSet}                                               ;;    |
             (call $wontThrow)                                          ;;    |
             ${wrongValues}                                             ;;    |
             ${localsGet} ;; End of try code.                           ;;    |
           catch $emptyExn                                              ;;    |
             ${wrongValues}                                             ;;    |
             ${localsGet}                                               ;;    |
           catch $exn  ;; <---------------------------------------------------'
             ${localsGet}
           catch_all
             ${wrongValues}
             ${localsGet}
           end
           ;; Check if the local has the correct v128 value.
           ${checkV128Value}))`;

  let localThrowValues = `
             ${throwValues}
             (throw $exn)`;
  let directLocalCall = `
             (call $localCallThrow)`;
  let directImportCall = `
             (i32.const 1)
             (call $throwif)`;

  // Run test for side effects on locals before throwing an exception locally,
  // or from a direct call.

  let callInstructions = [localThrowValues, directLocalCall, directImportCall];

  for (let callThrow of callInstructions) {
    console.log("callThrow = " + callThrow); // Uncomment for debugging.
    moduleText = moduleHeader + callThrow + moduleRest;
    console.log("moduleText = " + moduleText); // Uncomment for debugging.
    assertEqArray(
      wasmEvalText(moduleText,
                   { m : wasmEvalText(importsModule).exports }
                  ).exports.testFunc("foo", "wrongFoo"),
      correctResultsJS("foo"));
  }
}

// Setting globals in try code, and testing to see if the changes are known to
// the landing pad.
function testGlobals() {
  let test = function (type, initialValue, resultValue, wrongValue, coercion) {
    let exports = wasmEvalText(
      `(module
         (tag (export "exn"))
         (func (export "throws")
           (throw 0)))`
    ).exports;

    assertEq(
      wasmEvalText(
        `(module
           (import "m" "exn" (tag $exn))
           (tag $notThrownExn)
           (import "m" "throws" (func $throws))
           (global (mut ${type}) (${type}.const ${initialValue}))
           (func (export "testFunc") (result ${type})
             try (result ${type})
               (global.set 0 (${type}.const ${resultValue}))
               (call $throws)
               (global.set 0 (${type}.const ${wrongValue}))
               (global.get 0)
             catch $notThrownExn
               (${type}.const ${wrongValue})
             catch $exn
               (global.get 0)
             end))`,
        { m: exports }
      ).exports.testFunc(), coercion(resultValue));
  };

  test("i32", 2, 7, 27, x => x);
  test("i64", 2n, 7n, 27n, x => x);
  test("f32", 0.3, 0.1, 0.6, Math.fround);
  test("f64", 13.37, 0.6437244242412325666666, 4, x => x);
};

// Run all tests.

testSideEffectsOnLocals();
testGlobals();
