load(libdir + 'simd.js');

setJitCompilerOption("baseline.warmup.trigger", 10);
setJitCompilerOption("ion.warmup.trigger", 30);

var max = 40, pivot = 35;

var i32x4 = SIMD.Int32x4;
var f32x4 = SIMD.Float32x4;
var i32x4Add = SIMD.Int32x4.add;

var FakeSIMDType = function (o) { this.x = o.x; this.y = o.y; this.z = o.z; this.w = o.w; };
if (this.hasOwnProperty("TypedObject")) {
  var TO = TypedObject;
  FakeSIMDType = new TO.StructType({ x: TO.int32, y: TO.int32, z: TO.int32, w: TO.int32 });
}

function simdunbox_bail_undef(i, lhs, rhs) {
  return i32x4Add(lhs, rhs);
}

function simdunbox_bail_object(i, lhs, rhs) {
  return i32x4Add(lhs, rhs);
}

function simdunbox_bail_typeobj(i, lhs, rhs) {
  return i32x4Add(lhs, rhs);
}

function simdunbox_bail_badsimd(i, lhs, rhs) {
  return i32x4Add(lhs, rhs);
}

var arr_undef = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ];
var fail_undef = 0;
var arr_object = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ];
var fail_object = 0;
var arr_typeobj = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ];
var fail_typeobj = 0;
var arr_badsimd = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ];
var fail_badsimd = 0;
for (var i = 0; i < max; i++) {
  try {
    arr_undef[i + 2] = simdunbox_bail_undef(i, arr_undef[i], arr_undef[i + 1]);
  } catch (x) {
    arr_undef[i + 2] = arr_undef[i - 1];
    fail_undef++;
  }

  try {
    arr_object[i + 2] = simdunbox_bail_object(i, arr_object[i], arr_object[i + 1]);
  } catch (x) {
    arr_object[i + 2] = arr_object[i - 1];
    fail_object++;
  }

  try {
    arr_typeobj[i + 2] = simdunbox_bail_typeobj(i, arr_typeobj[i], arr_typeobj[i + 1]);
  } catch (x) {
    arr_typeobj[i + 2] = arr_typeobj[i - 1];
    fail_typeobj++;
  }

  try {
    arr_badsimd[i + 2] = simdunbox_bail_badsimd(i, arr_badsimd[i], arr_badsimd[i + 1]);
  } catch (x) {
    arr_badsimd[i + 2] = arr_badsimd[i - 1];
    fail_badsimd++;
  }

  if (i + 2 == pivot) {
    arr_undef[pivot] = undefined;
    arr_object[pivot] = { x: 0, y: 1, z: 2, w: 3 };
    arr_typeobj[pivot] = new FakeSIMDType({ x: 0, y: 1, z: 2, w: 3 });
    arr_badsimd[pivot] = f32x4(0, 1, 2, 3);
  }
}

assertEq(fail_undef, 2);
assertEq(fail_object, 2);
assertEq(fail_typeobj, 2);
assertEq(fail_badsimd, 2);

// Assert that all SIMD values are correct.
function assertEqX4(real, expected, assertFunc) {
    if (typeof assertFunc === 'undefined')
        assertFunc = assertEq;

    assertFunc(real.x, expected[0]);
    assertFunc(real.y, expected[1]);
    assertFunc(real.z, expected[2]);
    assertFunc(real.w, expected[3]);
}

var fib = [0, 1];
for (i = 0; i < max + 5; i++)
  fib[i+2] = (fib[i] + fib[i+1]) | 0;

for (i = 0; i < max; i++) {
  if (i == pivot)
    continue;
  var ref = fib.slice(i < pivot ? i : i - 3);
  assertEqX4(arr_undef[i], ref);
  assertEqX4(arr_object[i], ref);
  assertEqX4(arr_typeobj[i], ref);
  assertEqX4(arr_badsimd[i], ref);
}

// Check that unbox operations aren't removed
(function() {

    function add(i, v, w) {
        if (i % 2 == 0) {
            SIMD.Int32x4.add(v, w);
        } else {
            SIMD.Float32x4.add(v, w);
        }
    }

    var i = 0;
    var caught = false;
    var f4 = SIMD.Float32x4(1,2,3,4);
    var i4 = SIMD.Int32x4(1,2,3,4);
    try {
        for (; i < 200; i++) {
            if (i % 2 == 0) {
                add(i, i4, i4);
            } else if (i == 199) {
                add(i, i4, f4);
            } else {
                add(i, f4, f4);
            }
        }
    } catch(e) {
        print(e);
        assertEq(e instanceof TypeError, true);
        assertEq(i, 199);
        caught = true;
    }

    assertEq(i < 199 || caught, true);

})();