// Copyright 2015 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

var stdlib = this;

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

function assertWasm(expected, func, ffi) {
  print("Testing " + func.name + "...");
  assertEquals(
      expected, func(stdlib, ffi, new ArrayBuffer(1024)).caller());
  assertValidAsm(func);
}

function EmptyTest(a, b, c) {
  "use asm";
  function caller() {
    empty();
    return 11;
  }
  function empty() {
  }
  return {caller: caller};
}

assertWasm(11, EmptyTest);

function VoidReturnTest(a, b, c) {
  "use asm";
  function caller() {
    empty();
    return 19;
  }
  function empty() {
    var x = 0;
    if (x) return;
  }
  return {caller: caller};
}

assertWasm(19, VoidReturnTest);

function IntTest(a, b, c) {
  "use asm";
  function sum(a, b) {
    a = a|0;
    b = b|0;
    var c = 0;
    var d = 3.0;
    var e = 0;
    e = ~~d;  // double conversion
    c = (b + 1)|0
    return (a + c + 1)|0;
  }

  function caller() {
    return sum(77,22) | 0;
  }

  return {caller: caller};
}

assertWasm(101,IntTest);


function Float64Test() {
  "use asm";
  function sum(a, b) {
    a = +a;
    b = +b;
    return +(a + b);
  }

  function caller() {
    var a = 0.0;
    var ret = 0|0;
    a = +sum(70.1,10.2);
    if (a == 80.3) {
      ret = 1|0;
    } else {
      ret = 0|0;
    }
    return ret|0;
  }

  return {caller: caller};
}

assertWasm(1, Float64Test);


function BadModule() {
  "use asm";
  function caller(a, b) {
    a = a|0;
    b = b+0;
    var c = 0;
    c = (b + 1)|0
    return (a + c + 1)|0;
  }

  function caller() {
    return call(1, 2)|0;
  }

  return {caller: caller};
}

assertFalse(%IsAsmWasmCode(BadModule));


function TestReturnInBlock() {
  "use asm";

  function caller() {
    if(1) {
      {
        {
          return 1;
        }
      }
    }
    return 0;
  }

  return {caller: caller};
}

assertWasm(1, TestReturnInBlock);


function TestAddSimple() {
  "use asm";

  function caller() {
    var x = 0;
    x = (x + 1)|0;
    return x|0;
  }

  return {caller: caller};
}

assertWasm(1, TestAddSimple);


function TestWhileSimple() {
  "use asm";

  function caller() {
    var x = 0;
    while((x|0) < 5) {
      x = (x + 1)|0;
    }
    return x|0;
  }

  return {caller: caller};
}

assertWasm(5, TestWhileSimple);


function TestWhileWithoutBraces() {
  "use asm";

  function caller() {
    var x = 0;
    while((x|0) <= 3)
      x = (x + 1)|0;
    return x|0;
  }

  return {caller: caller};
}

assertWasm(4, TestWhileWithoutBraces);


function TestReturnInWhile() {
  "use asm";

  function caller() {
    var x = 0;
    while((x|0) < 10) {
      x = (x + 6)|0;
      return x|0;
    }
    return x|0;
  }

  return {caller: caller};
}

assertWasm(6, TestReturnInWhile);


function TestReturnInWhileWithoutBraces() {
  "use asm";

  function caller() {
    var x = 0;
    while((x|0) < 5)
      return 7;
    return x|0;
  }

  return {caller: caller};
}

assertWasm(7, TestReturnInWhileWithoutBraces);


function TestBreakInIf() {
  "use asm";

  function caller() {
    label: {
      if(1) break label;
      return 11;
    }
    return 12;
  }

  return {caller: caller};
}

assertWasm(12, TestBreakInIf);

function TestBreakInIfInDoWhileFalse() {
  "use asm";

  function caller() {
    do {
      if(1) break;
      return 11;
    } while(0);
    return 12;
  }

  return {caller: caller};
}

assertWasm(12, TestBreakInIfInDoWhileFalse);

function TestBreakInElse() {
  "use asm";

  function caller() {
    do {
      if(0) ;
      else break;
      return 14;
    } while(0);
    return 15;
  }

  return {caller: caller};
}

assertWasm(15, TestBreakInElse);

function TestBreakInWhile() {
  "use asm";

  function caller() {
    while(1) {
      break;
    }
    return 8;
  }

  return {caller: caller};
}

assertWasm(8, TestBreakInWhile);


function TestBreakInIfInWhile() {
  "use asm";

  function caller() {
    while(1) {
      if (1) break;
      else break;
    }
    return 8;
  }

  return {caller: caller};
}

assertWasm(8, TestBreakInIfInWhile);

function TestBreakInNestedWhile() {
  "use asm";

  function caller() {
    var x = 1.0;
    var ret = 0;
    while(x < 1.5) {
      while(1)
        break;
      x = +(x + 0.25);
    }
    if (x == 1.5) {
      ret = 9;
    }
    return ret|0;
  }

  return {caller: caller};
}

assertWasm(9, TestBreakInNestedWhile);


function TestBreakInBlock() {
  "use asm";

  function caller() {
    var x = 0;
    abc: {
      x = 10;
      if ((x|0) == 10) {
        break abc;
      }
      x = 20;
    }
    return x|0;
  }

  return {caller: caller};
}

assertWasm(10, TestBreakInBlock);


function TestBreakInNamedWhile() {
  "use asm";

  function caller() {
    var x = 0;
    outer: while (1) {
      x = (x + 1)|0;
      while ((x|0) == 11) {
        break outer;
      }
    }
    return x|0;
  }

  return {caller: caller};
}

assertWasm(11, TestBreakInNamedWhile);


function TestContinue() {
  "use asm";

  function caller() {
    var x = 5;
    var ret = 0;
    while ((x|0) >= 0) {
      x = (x - 1)|0;
      if ((x|0) == 2) {
        continue;
      }
      ret = (ret - 1)|0;
    }
    return ret|0;
  }

  return {caller: caller};
}

assertWasm(-5, TestContinue);


function TestContinueInNamedWhile() {
  "use asm";

  function caller() {
    var x = 5;
    var y = 0;
    var ret = 0;
    outer: while ((x|0) > 0) {
      x = (x - 1)|0;
      y = 0;
      while ((y|0) < 5) {
        if ((x|0) == 3) {
          continue outer;
        }
        ret = (ret + 1)|0;
        y = (y + 1)|0;
      }
    }
    return ret|0;
  }

  return {caller: caller};
}

assertWasm(20, TestContinueInNamedWhile);


function TestNot() {
  "use asm";

  function caller() {
    var a = 0;
    a = !(2 > 3);
    return a | 0;
  }

  return {caller:caller};
}

assertWasm(1, TestNot);


function TestNotEquals() {
  "use asm";

  function caller() {
    var a = 3;
    if ((a|0) != 2) {
      return 21;
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(21, TestNotEquals);


function TestUnsignedComparison() {
  "use asm";

  function caller() {
    var a = 0xffffffff;
    if ((a>>>0) > (0>>>0)) {
      return 22;
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(22, TestUnsignedComparison);


function TestMixedAdd() {
  "use asm";

  function caller() {
    var a = 0x80000000;
    var b = 0x7fffffff;
    var c = 0;
    c = ((a>>>0) + b)|0;
    if ((c >>> 0) > (0>>>0)) {
      if ((c|0) < 0) {
        return 23;
      }
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(23, TestMixedAdd);


function TestInt32HeapAccess(stdlib, foreign, buffer) {
  "use asm";

  var m = new stdlib.Int32Array(buffer);
  function caller() {
    var i = 4;

    m[0] = (i + 1) | 0;
    m[i >> 2] = ((m[0]|0) + 1) | 0;
    m[2] = ((m[i >> 2]|0) + 1) | 0;
    return m[2] | 0;
  }

  return {caller: caller};
}

assertWasm(7, TestInt32HeapAccess);


function TestInt32HeapAccessExternal() {
  var memory = new ArrayBuffer(1024);
  var memory_int32 = new Int32Array(memory);
  var module_decl = eval('(' + TestInt32HeapAccess.toString() + ')');
  var module = module_decl(stdlib, null, memory);
  assertValidAsm(module_decl);
  assertEquals(7, module.caller());
  assertEquals(7, memory_int32[2]);
}

TestInt32HeapAccessExternal();


function TestHeapAccessIntTypes() {
  var types = [
    [Int8Array, 'Int8Array', '>> 0'],
    [Uint8Array, 'Uint8Array', '>> 0'],
    [Int16Array, 'Int16Array', '>> 1'],
    [Uint16Array, 'Uint16Array', '>> 1'],
    [Int32Array, 'Int32Array', '>> 2'],
    [Uint32Array, 'Uint32Array', '>> 2'],
  ];
  for (var i = 0; i < types.length; i++) {
    var code = TestInt32HeapAccess.toString();
    code = code.replace('Int32Array', types[i][1]);
    code = code.replace(/>> 2/g, types[i][2]);
    var memory = new ArrayBuffer(1024);
    var memory_view = new types[i][0](memory);
    var module_decl = eval('(' + code + ')');
    var module = module_decl(stdlib, null, memory);
    assertValidAsm(module_decl);
    assertEquals(7, module.caller());
    assertEquals(7, memory_view[2]);
    assertEquals(7, module_decl(stdlib).caller());
    assertValidAsm(module_decl);
  }
}

TestHeapAccessIntTypes();


function TestFloatHeapAccess(stdlib, foreign, buffer) {
  "use asm";

  var f32 = new stdlib.Float32Array(buffer);
  var f64 = new stdlib.Float64Array(buffer);
  var fround = stdlib.Math.fround;
  function caller() {
    var i = 8;
    var j = 8;
    var v = 6.0;

    f64[2] = v + 1.0;
    f64[i >> 3] = +f64[2] + 1.0;
    f64[j >> 3] = +f64[j >> 3] + 1.0;
    i = +f64[i >> 3] == 9.0;
    return i|0;
  }

  return {caller: caller};
}

assertWasm(1, TestFloatHeapAccess);


function TestFloatHeapAccessExternal() {
  var memory = new ArrayBuffer(1024);
  var memory_float64 = new Float64Array(memory);
  var module_decl = eval('(' + TestFloatHeapAccess.toString() + ')');
  var module = module_decl(stdlib, null, memory);
  assertValidAsm(module_decl);
  assertEquals(1, module.caller());
  assertEquals(9.0, memory_float64[1]);
}

TestFloatHeapAccessExternal();


function TestConvertI32() {
  "use asm";

  function caller() {
    var a = 1.5;
    if ((~~(a + a)) == 3) {
      return 24;
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(24, TestConvertI32);


function TestConvertF64FromInt() {
  "use asm";

  function caller() {
    var a = 1;
    if ((+((a + a)|0)) > 1.5) {
      return 25;
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(25, TestConvertF64FromInt);


function TestConvertF64FromUnsigned() {
  "use asm";

  function caller() {
    var a = 0xffffffff;
    if ((+(a>>>0)) > 0.0) {
      if((+(a|0)) < 0.0) {
        return 26;
      }
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(26, TestConvertF64FromUnsigned);


function TestModInt() {
  "use asm";

  function caller() {
    var a = -83;
    var b = 28;
    return ((a|0)%(b|0))|0;
  }

  return {caller:caller};
}

assertWasm(-27,TestModInt);


function TestModUnsignedInt() {
  "use asm";

  function caller() {
    var a = 0x80000000;  //2147483648
    var b = 10;
    return ((a>>>0)%(b>>>0))|0;
  }

  return {caller:caller};
}

assertWasm(8, TestModUnsignedInt);


function TestModDouble() {
  "use asm";

  function caller() {
    var a = 5.25;
    var b = 2.5;
    if (a%b == 0.25) {
      return 28;
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(28, TestModDouble);


function TestModDoubleNegative() {
  "use asm";

  function caller() {
    var a = -34359738368.25;
    var b = 2.5;
    if (a%b == -0.75) {
      return 28;
    }
    return 0;
  }

  return {caller:caller};
}

assertWasm(28, TestModDoubleNegative);


(function () {
function TestNamedFunctions() {
  "use asm";

  var a = 0.0;
  var b = 0.0;

  function add() {
    return +(a + b);
  }

  function init() {
    a = 43.25;
    b = 34.25;
  }

  return {init:init,
          add:add};
}

var module_decl = eval('(' + TestNamedFunctions.toString() + ')');
var module = module_decl(stdlib);
assertValidAsm(module_decl);
module.init();
assertEquals(77.5, module.add());
})();


(function () {
function TestGlobalsWithInit() {
  "use asm";

  var a = 43.25;
  var b = 34.25;

  function add() {
    return +(a + b);
  }

  return {add:add};
}

var module_decl = eval('(' + TestGlobalsWithInit.toString() + ')');
var module = module_decl(stdlib);
assertValidAsm(module_decl);
assertEquals(77.5, module.add());
})();

function TestForLoop() {
  "use asm"

  function caller() {
    var ret = 0;
    var i = 0;
    for (i = 2; (i|0) <= 10; i = (i+1)|0) {
      ret = (ret + i) | 0;
    }
    return ret|0;
  }

  return {caller:caller};
}

assertWasm(54, TestForLoop);


function TestForLoopWithoutInit() {
  "use asm"

  function caller() {
    var ret = 0;
    var i = 0;
    for (; (i|0) < 10; i = (i+1)|0) {
      ret = (ret + 10) | 0;
    }
    return ret|0;
  }

  return {caller:caller};
}

assertWasm(100,TestForLoopWithoutInit);


function TestForLoopWithoutCondition() {
  "use asm"

  function caller() {
    var ret = 0;
    var i = 0;
    for (i=1;; i = (i+1)|0) {
      ret = (ret + i) | 0;
      if ((i|0) == 11) {
        break;
      }
    }
    return ret|0;
  }

  return {caller:caller};
}

assertWasm(66, TestForLoopWithoutCondition);


function TestForLoopWithoutNext() {
  "use asm"

  function caller() {
    var i = 0;
    for (i=1; (i|0) < 41;) {
      i = (i + 1) | 0;
    }
    return i|0;
  }

  return {caller:caller};
}

assertWasm(41, TestForLoopWithoutNext);


function TestForLoopWithoutBody() {
  "use asm"

  function caller() {
    var i = 0;
    for (i=1; (i|0) < 45 ; i = (i+1)|0) {
    }
    return i|0;
  }

  return {caller:caller};
}

assertWasm(45, TestForLoopWithoutBody);


function TestDoWhile() {
  "use asm"

  function caller() {
    var i = 0;
    var ret = 21;
    do {
      ret = (ret + ret)|0;
      i = (i + 1)|0;
    } while ((i|0) < 2);
    return ret|0;
  }

  return {caller:caller};
}

assertWasm(84, TestDoWhile);


function TestConditional() {
  "use asm"

  function caller() {
    var x = 1;
    return (((x|0) > 0) ? 41 : 71)|0;
  }

  return {caller:caller};
}

assertWasm(41, TestConditional);


function TestInitFunctionWithNoGlobals() {
  "use asm";
  function caller() {
    return 51;
  }
  return {caller};
}

assertWasm(51, TestInitFunctionWithNoGlobals);


(function () {
function TestExportNameDifferentFromFunctionName() {
  "use asm";
  function caller() {
    return 55;
  }
  return {alt_caller:caller};
}

var module_decl = eval(
  '(' + TestExportNameDifferentFromFunctionName.toString() + ')');
var module = module_decl(stdlib);
assertValidAsm(module_decl);
assertEquals(55, module.alt_caller());
})();


function TestFunctionTableSingleFunction() {
  "use asm";

  function dummy() {
    return 71;
  }

  function caller() {
    // TODO(jpp): the parser optimizes function_table[0&0] to function table[0].
    var v = 0;
    return function_table[v&0]() | 0;
  }

  var function_table = [dummy]

  return {caller:caller};
}

assertWasm(71, TestFunctionTableSingleFunction);


function TestFunctionTableMultipleFunctions() {
  "use asm";

  function inc1(x) {
    x = x|0;
    return (x+1)|0;
  }

  function inc2(x) {
    x = x|0;
    return (x+2)|0;
  }

  function caller() {
    var i = 0, j = 1;
    if ((function_table[i&1](50)|0) == 51) {
      if ((function_table[j&1](60)|0) == 62) {
        return 73;
      }
    }
    return 0;
  }

  var function_table = [inc1, inc2]

  return {caller:caller};
}

assertWasm(73, TestFunctionTableMultipleFunctions);


(function () {
function TestFunctionTable(stdlib, foreign, buffer) {
  "use asm";

  function add(a, b) {
    a = a|0;
    b = b|0;
    return (a+b)|0;
  }

  function sub(a, b) {
    a = a|0;
    b = b|0;
    return (a-b)|0;
  }

  function inc(a) {
    a = a|0;
    return (a+1)|0;
  }

  function caller(table_id, fun_id, arg1, arg2) {
    table_id = table_id|0;
    fun_id = fun_id|0;
    arg1 = arg1|0;
    arg2 = arg2|0;
    if ((table_id|0) == 0) {
      return funBin[fun_id&3](arg1, arg2)|0;
    } else if ((table_id|0) == 1) {
      return fun[fun_id&0](arg1)|0;
    }
    return 0;
  }

  var funBin = [add, sub, sub, add];
  var fun = [inc];

  return {caller:caller};
}

print("TestFunctionTable...");
var module = TestFunctionTable(stdlib);
assertEquals(55, module.caller(0, 0, 33, 22));
assertEquals(11, module.caller(0, 1, 33, 22));
assertEquals(9, module.caller(0, 2, 54, 45));
assertEquals(99, module.caller(0, 3, 54, 45));
assertEquals(23, module.caller(0, 4, 12, 11));
assertEquals(31, module.caller(1, 0, 30, 11));
})();


function TestForeignFunctions() {
  function AsmModule(stdlib, foreign, buffer) {
    "use asm";

    var setVal = foreign.setVal;
    var getVal = foreign.getVal;

    function caller(initial_value, new_value) {
      initial_value = initial_value|0;
      new_value = new_value|0;
      if ((getVal()|0) == (initial_value|0)) {
        setVal(new_value|0);
        return getVal()|0;
      }
      return 0;
    }

    return {caller:caller};
  }

  function ffi(initial_val) {
    var val = initial_val;

    function getVal() {
      return val;
    }

    function setVal(new_val) {
      val = new_val;
    }

    return {getVal:getVal, setVal:setVal};
  }

  var foreign = new ffi(23);

  var module = AsmModule({Math: Math}, foreign, null);
  assertValidAsm(AsmModule);

  assertEquals(103, module.caller(23, 103));
}

print("TestForeignFunctions...");
TestForeignFunctions();


function TestForeignFunctionMultipleUse() {
  function AsmModule(stdlib, foreign, buffer) {
    "use asm";

    var getVal = foreign.getVal;

    function caller(int_val, double_val) {
      int_val = int_val|0;
      double_val = +double_val;
      if ((getVal()|0) == (int_val|0)) {
        if ((+getVal()) == (+double_val)) {
          return 89;
        }
      }
      return 0;
    }

    return {caller:caller};
  }

  function ffi() {
    function getVal() {
      return 83.25;
    }

    return {getVal:getVal};
  }

  var foreign = new ffi();

  var module_decl = eval('(' + AsmModule.toString() + ')');
  var module = module_decl(stdlib, foreign, null);
  assertValidAsm(module_decl);

  assertEquals(89, module.caller(83, 83.25));
}

print("TestForeignFunctionMultipleUse...");
TestForeignFunctionMultipleUse();


function TestForeignVariables() {
  function AsmModule(stdlib, foreign, buffer) {
    "use asm";

    var i1 = foreign.foo | 0;
    var f1 = +foreign.bar;
    var i2 = foreign.baz | 0;
    var f2 = +foreign.baz;

    function geti1() {
      return i1|0;
    }

    function getf1() {
      return +f1;
    }

    function geti2() {
      return i2|0;
    }

    function getf2() {
      return +f2;
    }

    return {geti1:geti1, getf1:getf1, geti2:geti2, getf2:getf2};
  }

  function TestCase(env, i1, f1, i2, f2) {
    print("Testing foreign variables...");
    var module_decl = eval('(' + AsmModule.toString() + ')');
    var module = module_decl(stdlib, env);
    assertValidAsm(module_decl);
    assertEquals(i1, module.geti1());
    assertEquals(f1, module.getf1());
    assertEquals(i2, module.geti2());
    assertEquals(f2, module.getf2());
  }

  // Check normal operation.
  TestCase({foo: 123, bar: 234.5, baz: 345.7}, 123, 234.5, 345, 345.7);
  // Check partial operation.
  TestCase({baz: 345.7}, 0, NaN, 345, 345.7);
  // Check that undefined values are converted to proper defaults.
  TestCase({qux: 999}, 0, NaN, 0, NaN);
  // Check that an undefined ffi is ok.
  TestCase(undefined, 0, NaN, 0, NaN);
  // Check that true values are converted properly.
  TestCase({foo: true, bar: true, baz: true}, 1, 1.0, 1, 1.0);
  // Check that false values are converted properly.
  TestCase({foo: false, bar: false, baz: false}, 0, 0, 0, 0);
  // Check that null values are converted properly.
  TestCase({foo: null, bar: null, baz: null}, 0, 0, 0, 0);
  // Check that string values are converted properly.
  TestCase({foo: 'hi', bar: 'there', baz: 'dude'}, 0, NaN, 0, NaN);
  TestCase({foo: '0xff', bar: '234', baz: '456.1'}, 255, 234, 456, 456.1, 456);
  // Check that Date values are converted properly.
  TestCase({foo: new Date(123), bar: new Date(456),
            baz: new Date(789)}, 123, 456, 789, 789);
  // Check that list values are converted properly.
  TestCase({foo: [], bar: [], baz: []}, 0, 0, 0, 0);
  // Check that object values are converted properly.
  TestCase({foo: {}, bar: {}, baz: {}}, 0, NaN, 0, NaN);
  // Check that getter object values are converted properly.
  var o = {
    get foo() {
      return 123.4;
    }
  };
  TestCase({foo: o.foo, bar: o.foo, baz: o.foo}, 123, 123.4, 123, 123.4);
  // Check that getter object values are converted properly.
  var o = {
    get baz() {
      return 123.4;
    }
  };
  TestCase(o, 0, NaN, 123, 123.4);
  // Check that objects with valueOf are converted properly.
  var o = {
    valueOf: function() { return 99; }
  };
  TestCase({foo: o, bar: o, baz: o}, 99, 99, 99, 99);
  // Check that function values are converted properly.
  TestCase({foo: TestCase, bar: TestCase, qux: TestCase}, 0, NaN, 0, NaN);
  // Check that a missing ffi object is safe.
  TestCase(undefined, 0, NaN, 0, NaN);
}

print("TestForeignVariables...");
TestForeignVariables();


(function() {
  function TestByteHeapAccessCompat(stdlib, foreign, buffer) {
    "use asm";

    var HEAP8 = new stdlib.Uint8Array(buffer);
    var HEAP32 = new stdlib.Int32Array(buffer);

    function store(i, v) {
      i = i | 0;
      v = v | 0;
      HEAP32[i >> 2] = v;
    }

    function storeb(i, v) {
      i = i | 0;
      v = v | 0;
      HEAP8[i | 0] = v;
    }

    function load(i) {
      i = i | 0;
      return HEAP8[i] | 0;
    }

    function iload(i) {
      i = i | 0;
      return HEAP8[HEAP32[i >> 2] | 0] | 0;
    }

    return {load: load, iload: iload, store: store, storeb: storeb};
  }

  var module_decl = eval('(' + TestByteHeapAccessCompat.toString() + ')');
  var m = module_decl(stdlib);
  assertValidAsm(module_decl);
  m.store(0, 20);
  m.store(4, 21);
  m.store(8, 22);
  m.storeb(20, 123);
  m.storeb(21, 42);
  m.storeb(22, 77);
  assertEquals(123, m.load(20));
  assertEquals(42, m.load(21));
  assertEquals(77, m.load(22));
  assertEquals(123, m.iload(0));
  assertEquals(42, m.iload(4));
  assertEquals(77, m.iload(8));
})();


function TestGlobalBlock(stdlib, foreign, buffer) {
  "use asm";

  var x = foreign.x | 0, y = foreign.y | 0;

  function test() {
    return (x + y) | 0;
  }

  return {caller: test};
}

assertWasm(15, TestGlobalBlock, { x: 4, y: 11 });

(function TestComma() {
  function CommaModule() {
    "use asm";

    function ifunc(a, b) {
      a = +a;
      b = b | 0;
      return (a, b) | 0;
    }

    function dfunc(a, b) {
      a = a | 0;
      b = +b;
      return +(a, b);
    }

    return {ifunc: ifunc, dfunc: dfunc};
  }

  var module_decl = eval('(' + CommaModule.toString() + ')');
  var m = module_decl(stdlib);
  assertValidAsm(module_decl);
  assertEquals(123, m.ifunc(456.7, 123));
  assertEquals(123.4, m.dfunc(456, 123.4));
})();


function TestFloatAsDouble(stdlib) {
  "use asm";
  var fround = stdlib.Math.fround;
  function func() {
    var x = fround(1.0);
    return +fround(x);
  }
  return {caller: func};
}
assertWasm(1, TestFloatAsDouble);


function TestOr() {
  "use asm";
  function func() {
    var x = 1;
    var y = 2;
    return (x | y) | 0;
  }
  return {caller: func};
}

assertWasm(3, TestOr);


function TestAnd() {
  "use asm";
  function func() {
    var x = 3;
    var y = 2;
    return (x & y) | 0;
  }
  return {caller: func};
}

assertWasm(2, TestAnd);


function TestXor() {
  "use asm";
  function func() {
    var x = 3;
    var y = 2;
    return (x ^ y) | 0;
  }
  return {caller: func};
}

assertWasm(1, TestXor);


function TestIntishAssignment(stdlib, foreign, heap) {
  "use asm";
  var HEAP32 = new stdlib.Int32Array(heap);
  function func() {
    var a = 1;
    var b = 2;
    HEAP32[0] = a + b;
    return HEAP32[0] | 0;
  }
  return {caller: func};
}

assertWasm(3, TestIntishAssignment);


function TestFloatishAssignment(stdlib, foreign, heap) {
  "use asm";
  var HEAPF32 = new stdlib.Float32Array(heap);
  var fround = stdlib.Math.fround;
  function func() {
    var a = fround(1.0);
    var b = fround(2.0);
    HEAPF32[0] = a + b;
    return +HEAPF32[0];
  }
  return {caller: func};
}

assertWasm(3, TestFloatishAssignment);


function TestDoubleToFloatAssignment(stdlib, foreign, heap) {
  "use asm";
  var HEAPF32 = new stdlib.Float32Array(heap);
  var fround = stdlib.Math.fround;
  function func() {
    var a = 1.23;
    HEAPF32[0] = a;
    return +HEAPF32[0];
  }
  return {caller: func};
}

assertWasm(Math.fround(1.23), TestDoubleToFloatAssignment);


function TestIntegerMultiplyBothWays(stdlib, foreign, heap) {
  "use asm";
  function func() {
    var a = 1;
    return (((a * 3)|0) + ((4 * a)|0)) | 0;
  }
  return {caller: func};
}

assertWasm(7, TestIntegerMultiplyBothWays);


(function TestBadAssignDoubleFromIntish() {
  function Module(stdlib, foreign, heap) {
    "use asm";
    function func() {
      var a = 1;
      var b = 3.0;
      b = a;
    }
    return {func: func};
  }
  print("TestBadAssignDoubleFromIntish...");
  Module(stdlib);
  assertFalse(%IsAsmWasmCode(Module));
})();


(function TestBadAssignIntFromDouble() {
  function Module(stdlib, foreign, heap) {
    "use asm";
    function func() {
      var a = 1;
      var b = 3.0;
      a = b;
    }
    return {func: func};
  }
  print("TestBadAssignIntFromDouble...");
  Module(stdlib);
  assertFalse(%IsAsmWasmCode(Module));
})();


(function TestBadMultiplyIntish() {
  function Module(stdlib, foreign, heap) {
    "use asm";
    function func() {
      var a = 1;
      return ((a + a) * 4) | 0;
    }
    return {func: func};
  }
  print("TestBadMultiplyIntish...");
  Module(stdlib);
  assertFalse(%IsAsmWasmCode(Module));
})();


(function TestBadCastFromInt() {
  function Module(stdlib, foreign, heap) {
    "use asm";
    function func() {
      var a = 1;
      return +a;
    }
    return {func: func};
  }
  print("TestBadCastFromInt...");
  Module(stdlib);
  assertFalse(%IsAsmWasmCode(Module));
})();


function TestAndNegative() {
  "use asm";
  function func() {
    var x = 1;
    var y = 2;
    var z = 0;
    z = x + y & -1;
    return z | 0;
  }
  return {caller: func};
}

assertWasm(3, TestAndNegative);


function TestNegativeDouble() {
  "use asm";
  function func() {
    var x = -(34359738368.25);
    var y = -2.5;
    return +(x + y);
  }
  return {caller: func};
}

assertWasm(-34359738370.75, TestNegativeDouble);


(function TestBadAndDouble() {
  function Module() {
    "use asm";
    function func() {
      var x = 1.0;
      var y = 2.0;
      return (x & y) | 0;
    }
    return {func: func};
  }

  Module(stdlib);
  assertFalse(%IsAsmWasmCode(Module));
})();


(function TestBadExportKey() {
  function Module() {
    "use asm";
    function func() {
    }
    return {123: func};
  }

  Module(stdlib);
  assertFalse(%IsAsmWasmCode(Module));
})();


(function TestUnicodeExportKey() {
  function Module() {
    "use asm";
    function func() {
      return 42;
    }
    return {"\u00d1\u00e6": func};
  }

  var m = Module(stdlib);
  assertEquals(42, m.Ñæ());
  assertValidAsm(Module);
})();


function TestAndIntAndHeapValue(stdlib, foreign, buffer) {
  "use asm";
  var HEAP32 = new stdlib.Int32Array(buffer);
  function func() {
    var x = 0;
    x = HEAP32[0] & -1;
    return x | 0;
  }
  return {caller: func};
}

assertWasm(0, TestAndIntAndHeapValue);


function TestOutOfBoundsConversion($a,$b,$c){'use asm';
  function aaa() {
    var f = 0.0;
    var a = 0;
    f = 5616315000.000001;
    a = ~~f >>>0;
    return a | 0;
  }
  return { caller : aaa };
}

assertWasm(1321347704, TestOutOfBoundsConversion);


(function TestUnsignedLiterals() {
  function asmModule() {
    "use asm";
    function u0xffffffff() {
      var f = 0xffffffff;
      return +(f >>> 0);
    }
    function u0x80000000() {
      var f = 0x80000000;
      return +(f >>> 0);
    }
    function u0x87654321() {
      var f = 0x87654321;
      return +(f >>> 0);
    }
    return {
      u0xffffffff: u0xffffffff,
      u0x80000000: u0x80000000,
      u0x87654321: u0x87654321,
    };
  }
  var decl = eval('(' + asmModule.toString() + ')');
  var wasm = decl(stdlib);
  assertValidAsm(decl);
  assertEquals(0xffffffff, wasm.u0xffffffff());
  assertEquals(0x80000000, wasm.u0x80000000());
  assertEquals(0x87654321, wasm.u0x87654321());
})();


function TestIfWithUnsigned() {
  "use asm";
  function main() {
    if (2147483658) { // 2^31 + 10
      return 231;
    }
    return 0;
  }
  return {caller:main};
}

assertWasm(231, TestIfWithUnsigned);


function TestLoopsWithUnsigned() {
  "use asm";
  function main() {
    var val = 1;
    var count = 0;
    for (val = 2147483648; 2147483648;) {
      val = 2147483649;
      break;
    }
    while (val>>>0) {
      val = (val + 1) | 0;
      count = (count + 1)|0;
      if ((count|0) == 9) {
        break;
      }
    }
    count = 0;
    do {
      val = (val + 2) | 0;
      count = (count + 1)|0;
      if ((count|0) == 5) {
        break;
      }
    } while (0xffffffff);
    if ((val>>>0) == 2147483668) {
      return 323;
    }
    return 0;
  }
  return {caller:main};
}

assertWasm(323, TestLoopsWithUnsigned);


function TestSingleFunctionModule() {
  "use asm";
  function add(a, b) {
    a = a | 0;
    b = b | 0;
    return (a + b) | 0;
  }
  return add;
}

assertEquals(7, TestSingleFunctionModule()(3, 4));


function TestNotZero() {
  "use asm";
  function caller() {
    if (!0) {
      return 44;
    } else {
      return 55;
    }
    return 0;
  }
  return {caller: caller};
}

assertWasm(44, TestNotZero);


function TestNotOne() {
  "use asm";
  function caller() {
    if (!1) {
      return 44;
    } else {
      return 55;
    }
    return 0;
  }
  return {caller: caller};
}

assertWasm(55, TestNotOne);


function TestDotfulFloat(stdlib) {
  "use asm";
  var fround = stdlib.Math.fround;
  var foo = fround(55.0);
  function caller() {
    return +foo;
  }
  return {caller: caller};
}

assertWasm(55, TestDotfulFloat);


function TestDotfulLocalFloat(stdlib) {
  "use asm";
  var fround = stdlib.Math.fround;
  function caller() {
    var foo = fround(55.0);
    return +foo;
  }
  return {caller: caller};
}

assertWasm(55, TestDotfulLocalFloat);


function TestDotlessFloat(stdlib) {
  "use asm";
  var fround = stdlib.Math.fround;
  var foo = fround(55);
  function caller() {
    return +foo;
  }
  return {caller: caller};
}

assertWasm(55, TestDotlessFloat);


function TestDotlessLocalFloat(stdlib) {
  "use asm";
  var fround = stdlib.Math.fround;
  function caller() {
    var foo = fround(55);
    return +foo;
  }
  return {caller: caller};
}

assertWasm(55, TestDotlessLocalFloat);


function TestFloatGlobals(stdlib) {
  "use asm";
  var fround = stdlib.Math.fround;
  var foo = fround(1.25);
  function caller() {
    foo = fround(foo + fround(1.0));
    foo = fround(foo + fround(1.0));
    return +foo;
  }
  return {caller: caller};
}

assertWasm(3.25, TestFloatGlobals);


(function TestExportTwice() {
  function asmModule() {
    "use asm";
    function foo() {
      return 42;
    }
    return {bar: foo, baz: foo};
  }
  var m = asmModule();
  assertEquals(42, m.bar());
  assertEquals(42, m.baz());
})();