// Copyright 2016 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: --validate-asm --allow-natives-syntax

// Note that this test file contains tests that explicitly check modules are
// valid asm.js and then break them with invalid instantiation arguments. If
// this script is run more than once (e.g. --stress-opt) then modules remain
// broken in the second run and assertions would fail. We prevent re-runs.
// Flags: --nostress-opt

function assertValidAsm(func) {
  assertTrue(%IsAsmWasmCode(func));
}

(function TestConst() {
  function Module(s) {
    "use asm";
    var fround = s.Math.fround;
    // Global constants. These are treated just like numeric literals.
    const fConst = fround(-3.0);
    const dConst = -3.0;
    const iConst = -3;

    // consts can be used to initialize other consts.
    const fPrime = fConst;

    // The following methods verify that return statements with global constants
    // do not need type annotations.
    function f() {
      return fPrime;
    }
    function d() {
      return dConst;
    }
    function i() {
      return iConst;
    }

    // The following methods verify that locals initialized with global
    // constants do not need type annotations.
    function fVar() {
      var v = fPrime;
      return fround(v);
    }
    function iVar() {
      var v = iConst;
      return v|0;
    }
    function dVar() {
      var v = dConst;
      return +v;
    }

    return {
      f: f, d: d, i: i,
      fVar: fVar, dVar: dVar, iVar: iVar,
    };
  }

  function DisallowAssignToConstGlobal() {
    const constant = 0;
    function invalid(i) {
      i = i|0;
      constant = i;
      return constant;
    }
    return invalid;
  }

  var m = Module(this);
  assertValidAsm(Module);

  assertEquals(-3, m.i());
  assertEquals(-3.0, m.d());
  assertEquals(Math.fround(-3.0), m.f());

  assertEquals(-3, m.iVar());
  assertEquals(-3.0, m.dVar());
  assertEquals(Math.fround(-3.0), m.fVar());

  var m = DisallowAssignToConstGlobal();
  assertFalse(%IsAsmWasmCode(DisallowAssignToConstGlobal));
})();

(function TestModuleArgs() {
  function Module1(stdlib) {
    "use asm";
    function foo() { }
    return { foo: foo };
  }
  function Module2(stdlib, ffi) {
    "use asm";
    function foo() { }
    return { foo: foo };
  }
  function Module3(stdlib, ffi, heap) {
    "use asm";
    function foo() { }
    return { foo: foo };
  }
  var modules = [Module1, Module2, Module3];
  var heap = new ArrayBuffer(1024 * 1024);
  for (var i = 0; i < modules.length; ++i) {
    print('Module' + (i + 1));
    var module = modules[i];
    var m = module();
    assertValidAsm(module);
    var m = module({});
    assertValidAsm(module);
    var m = module({}, {});
    assertValidAsm(module);
    var m = module({}, {}, heap);
    assertValidAsm(module);
    var m = module({}, {}, heap, {});
    assertValidAsm(module);
  }
})();

(function TestBadModule() {
  function Module(stdlib, ffi, heap) {
    "use asm";
    function foo() { var y = 3; var x = 1 + y; return 123; }
    return { foo: foo };
  }
  var m = Module({});
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(123, m.foo());
})();

(function TestBadArgTypes() {
  function Module(a, b, c) {
    "use asm";
    var NaN = a.NaN;
    return {};
  }
  var m = Module(1, 2, 3);
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals({}, m);
})();

(function TestBadArgTypesMismatch() {
  function Module(a, b, c) {
    "use asm";
    var NaN = a.NaN;
    return {};
  }
  var m = Module(1, 2);
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals({}, m);
})();

(function TestModuleNoStdlib() {
  function Module() {
    "use asm";
    function foo() { return 123; }
    return { foo: foo };
  }
  var m = Module({});
  assertValidAsm(Module);
  assertEquals(123, m.foo());
})();

(function TestModuleWith5() {
  function Module(a, b, c, d, e) {
    "use asm";
    function foo() { return 123; }
    return { foo: foo };
  }
  var heap = new ArrayBuffer(1024 * 1024);
  var m = Module({}, {}, heap);
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(123, m.foo());
})();

(function TestModuleNoStdlibCall() {
  function Module(stdlib, ffi, heap) {
    "use asm";
    function foo() { return 123; }
    return { foo: foo };
  }
  var m = Module();
  assertValidAsm(Module);
  assertEquals(123, m.foo());
})();

(function TestModuleNew() {
  function Module(stdlib, ffi, heap) {
    "use asm";
    function foo() { return 123; }
    return { foo: foo };
  }
  var m = new Module({}, {});
  assertValidAsm(Module);
  assertEquals(123, m.foo());
})();

(function TestMultipleFailures() {
  function Module(stdlib) {
    "use asm";
    var NaN = stdlib.NaN;
    function foo() { return 123; }
    return { foo: foo };
  }
  var m1 = Module(1, 2, 3);
  assertFalse(%IsAsmWasmCode(Module));
  var m2 = Module(1, 2, 3);
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(123, m1.foo());
  assertEquals(123, m2.foo());
})();

(function TestFailureThenSuccess() {
  function MkModule() {
    function Module(stdlib, ffi, heap) {
      "use asm";
      var NaN = stdlib.NaN;
      function foo() { return 123; }
      return { foo: foo };
    }
    return Module;
  }
  var Module1 = MkModule();
  var Module2 = MkModule();
  var heap = new ArrayBuffer(1024 * 1024);
  var m1 = Module1(1, 2, 3);
  assertFalse(%IsAsmWasmCode(Module1));
  var m2 = Module2({}, {}, heap);
  assertFalse(%IsAsmWasmCode(Module2));
  assertEquals(123, m1.foo());
  assertEquals(123, m2.foo());
})();

(function TestSuccessThenFailure() {
  function MkModule() {
    function Module(stdlib, ffi, heap) {
      "use asm";
      var NaN = stdlib.NaN;
      function foo() { return 123; }
      return { foo: foo };
    }
    return Module;
  }
  var Module1 = MkModule();
  var Module2 = MkModule();
  var heap = new ArrayBuffer(1024 * 1024);
  var m1 = Module1({NaN: NaN}, {}, heap);
  assertValidAsm(Module1);
  var m2 = Module2(1, 2, 3);
  assertFalse(%IsAsmWasmCode(Module2));
  assertEquals(123, m1.foo());
  assertEquals(123, m2.foo());
})();

(function TestSuccessThenFailureThenRetry() {
  function MkModule() {
    function Module(stdlib, ffi, heap) {
      "use asm";
      var NaN = stdlib.NaN;
      function foo() { return 123; }
      return { foo: foo };
    }
    return Module;
  }
  var Module1 = MkModule();
  var Module2 = MkModule();
  var heap = new ArrayBuffer(1024 * 1024);
  var m1a = Module1({NaN: NaN}, {}, heap);
  assertValidAsm(Module1);
  var m2 = Module2(1, 2, 3);
  assertFalse(%IsAsmWasmCode(Module2));
  var m1b = Module1({NaN: NaN}, {}, heap);
  assertFalse(%IsAsmWasmCode(Module1));
  assertEquals(123, m1a.foo());
  assertEquals(123, m1b.foo());
  assertEquals(123, m2.foo());
})();

(function TestBoundFunction() {
  function Module(stdlib, ffi, heap) {
    "use asm";
    function foo() { return 123; }
    return { foo: foo };
  }
  var heap = new ArrayBuffer(1024 * 1024);
  var ModuleBound = Module.bind(this, {}, {}, heap);
  var m = ModuleBound();
  assertValidAsm(Module);
  assertEquals(123, m.foo());
})();

(function TestBadConstUnsignedReturn() {
  function Module() {
    "use asm";
    const i = 0xffffffff;
    function foo() { return i; }
    return { foo: foo };
  }
  var m = Module();
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(0xffffffff, m.foo());
})();

(function TestBadBooleanParamAnnotation() {
  function Module() {
    "use asm";
    function foo(x) {
      x = x | true;
      return x;
    }
    return { foo: foo };
  }
  var m = Module();
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(3, m.foo(3));
})();

(function TestBadExportTwice() {
  function Module() {
    "use asm";
    function bar() { return 1; }
    function baz() { return 2; }
    return {foo: bar, foo: baz};
  }
  var m = Module();
  assertTrue(%IsAsmWasmCode(Module));
  assertEquals(2, m.foo());
})();

(function TestBadImport() {
  function Module(stdlib) {
    "use asm";
    var set = 0;
    var foo = stdlib[set];
    return {};
  }
  var m = Module(this);
  assertFalse(%IsAsmWasmCode(Module));
})();

(function TestBadFroundTrue() {
  function Module(stdlib) {
    "use asm";
    var fround = stdlib.Math.fround;
    function foo() {
      var x = fround(true);
      return +x;
    }
    return { foo: foo };
  }
  var m = Module(this);
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(1, m.foo());
})();

(function TestBadCase() {
  function Module() {
    "use asm";
    function foo(x) {
      x = x | 0;
      switch (x|0) {
        case true:
          return 42;
        default:
          return 43;
      }
      return 0;
    }
    return { foo: foo };
  }
  var m = Module();
  assertFalse(%IsAsmWasmCode(Module));
  assertEquals(43, m.foo(3));
})();

(function TestVarHidesExport() {
  function Module() {
    "use asm";
    var foo;
    function foo() {}
    return foo;
  }
  Module();
  assertFalse(%IsAsmWasmCode(Module));
})();

(function TestUndefinedGlobalCall() {
  function Module() {
    "use asm";
    function foo() {
      return bar() | 0;
    }
    return foo;
  }
  Module();
  assertFalse(%IsAsmWasmCode(Module));
})();

(function TestConditionalReturn() {
  function Module() {
    'use asm';
    function foo(a, b) {
      a = +a;
      b = +b;
      // Allowed, despite not matching the spec, as emscripten emits this in
      // practice.
      return a == b ? +a : +b;
    }
    return foo;
  }
  var m = Module();
  assertEquals(4, m(4, 4));
  assertEquals(5, m(4, 5));
  assertEquals(4, m(5, 4));
  assertValidAsm(Module);
})();

(function TestMismatchedConditionalReturn() {
  function Module() {
    'use asm';
    function foo(a, b) {
      a = +a;
      return a == 0.0 ? 0 : +a;
    }
    return foo;
  }
  Module();
  assertFalse(%IsAsmWasmCode(Module));
})();

(function TestBadIntConditionalReturn() {
  function Module() {
    'use asm';
    function foo(a, b) {
      a = a | 0;
      b = b | 0;
      // Disallowed because signature must be signed, but these will be int.
      return 1 ? a : b;
    }
    return foo;
  }
  Module();
  assertFalse(%IsAsmWasmCode(Module));
})();

(function TestBadSignedConditionalReturn() {
  function Module() {
    'use asm';
    function foo(a, b) {
      a = a | 0;
      b = b | 0;
      // Disallowed because conditional yields int, even when both sides
      // are signed.
      return 1 ? a | 0 : b | 0;
    }
    return foo;
  }
  Module();
  assertFalse(%IsAsmWasmCode(Module));
})();

(function TestAsmIsRegular() {
  function Module() {
    'use asm';
    var g = 123;
    function foo() {
      return g | 0;
    }
    return {x: foo};
  }
  var o = Module();
  assertValidAsm(Module);
  assertFalse(o instanceof WebAssembly.Instance);
  assertTrue(o instanceof Object);
  assertTrue(o.__proto__ === Object.prototype);
  var p = Object.getOwnPropertyDescriptor(o, "x")
  assertTrue(p.writable);
  assertTrue(p.enumerable);
  assertTrue(p.configurable);
  assertTrue(typeof o.x === 'function');
  o.x = 5;
  assertTrue(typeof o.x === 'number');
  assertTrue(o.__single_function__ === undefined);
  assertTrue(o.__foreign_init__ === undefined);
})();

(function TestAsmExportOrderPreserved() {
  function Module() {
    "use asm";
    function f() {}
    function g() {}
    return { a:f, b:g, x:f, c:g, d:f };
  }
  var m = Module();
  assertValidAsm(Module);
  var props = Object.getOwnPropertyNames(m);
  assertEquals(["a","b","x","c","d"], props);
})();