setJitCompilerOption("ion.warmup.trigger", 50);

var f32 = new Float32Array(10);

function test(setup, f) {
    if (f === undefined) {
        f = setup;
        setup = function(){};
    }
    setup();
    for(var n = 200; n; --n) {
        f();
    }
}

// Basic arithmetic
function setupBasicArith() {
    f32[0] = -Infinity;
    f32[1] = -1;
    f32[2] = -0;
    f32[3] = 0;
    f32[4] = 1.337;
    f32[5] = 42;
    f32[6] = Infinity;
    f32[7] = NaN;
}
function basicArith() {
    for (var i = 0; i < 7; ++i) {
        var opf = Math.fround(f32[i] + f32[i+1]);
        var opd = (1 / (1 / f32[i])) + f32[i+1];
        assertFloat32(opf, true);
        assertFloat32(opd, false);
        assertEq(opf, Math.fround(opd));

        opf = Math.fround(f32[i] - f32[i+1]);
        opd = (1 / (1 / f32[i])) - f32[i+1];
        assertFloat32(opf, true);
        assertFloat32(opd, false);
        assertEq(opf, Math.fround(opd));

        opf = Math.fround(f32[i] * f32[i+1]);
        opd = (1 / (1 / f32[i])) * f32[i+1];
        assertFloat32(opf, true);
        assertFloat32(opd, false);
        assertEq(opf, Math.fround(opd));

        opf = Math.fround(f32[i] / f32[i+1]);
        opd = (1 / (1 / f32[i])) / f32[i+1];
        assertFloat32(opf, true);
        assertFloat32(opd, false);
        assertEq(opf, Math.fround(opd));
    }
}
test(setupBasicArith, basicArith);

// MAbs
function setupAbs() {
    f32[0] = -0;
    f32[1] = 0;
    f32[2] = -3.14159;
    f32[3] = 3.14159;
    f32[4] = -Infinity;
    f32[5] = Infinity;
    f32[6] = NaN;
}
function abs() {
    for(var i = 0; i < 7; ++i) {
        assertEq( Math.fround(Math.abs(f32[i])), Math.abs(f32[i]) );
    }
}
test(setupAbs, abs);

// MSqrt
function setupSqrt() {
    f32[0] = 0;
    f32[1] = 1;
    f32[2] = 4;
    f32[3] = -1;
    f32[4] = Infinity;
    f32[5] = NaN;
    f32[6] = 13.37;
}
function sqrt() {
    for(var i = 0; i < 7; ++i) {
        var sqrtf = Math.fround(Math.sqrt(f32[i]));
        var sqrtd = 1 + Math.sqrt(f32[i]) - 1; // force no float32 by chaining arith ops
        assertEq( sqrtf, Math.fround(sqrtd) );
    }
}
test(setupSqrt, sqrt);

// MMinMax
function setupMinMax() {
    f32[0] = -0;
    f32[1] = 0;
    f32[2] = 1;
    f32[3] = 4;
    f32[4] = -1;
    f32[5] = Infinity;
    f32[6] = NaN;
    f32[7] = 13.37;
    f32[8] = -Infinity;
    f32[9] = Math.pow(2,31) - 1;
}
function minMax() {
    for(var i = 0; i < 9; ++i) {
        for(var j = 0; j < 9; j++) {
            var minf = Math.fround(Math.min(f32[i], f32[j]));
            var mind = 1 / (1 / Math.min(f32[i], f32[j])); // force no float32 by chaining arith ops
            assertFloat32(minf, true);
            assertFloat32(mind, false);
            assertEq( minf, Math.fround(mind) );

            var maxf = Math.fround(Math.max(f32[i], f32[j]));
            var maxd = 1 / (1 / Math.max(f32[i], f32[j])); // force no float32 by chaining arith ops
            assertFloat32(maxf, true);
            assertFloat32(maxd, false);
            assertEq( maxf, Math.fround(maxd) );
        }
    }
}
test(setupMinMax, minMax);

// MTruncateToInt32
// The only way to get a MTruncateToInt32 with a Float32 input is to use Math.imul
function setupTruncateToInt32() {
    f32[0] = -1;
    f32[1] = 4;
    f32[2] = 5.13;
}
function truncateToInt32() {
    assertEq( Math.imul(f32[0], f32[1]), Math.imul(-1, 4) );
    assertEq( Math.imul(f32[1], f32[2]), Math.imul(4, 5) );
}
test(setupTruncateToInt32, truncateToInt32);

// MCompare
function comp() {
    for(var i = 0; i < 9; ++i) {
        assertEq( f32[i] < f32[i+1], true );
    }
}
function setupComp() {
    f32[0] = -Infinity;
    f32[1] = -1;
    f32[2] = -0.01;
    f32[3] = 0;
    f32[4] = 0.01;
    f32[5] = 1;
    f32[6] = 10;
    f32[7] = 13.37;
    f32[8] = 42;
    f32[9] = Infinity;
}
test(setupComp, comp);

// MNot
function setupNot() {
    f32[0] = -0;
    f32[1] = 0;
    f32[2] = 1;
    f32[3] = NaN;
    f32[4] = Infinity;
    f32[5] = 42;
    f32[6] = -23;
}
function not() {
    assertEq( !f32[0], true );
    assertEq( !f32[1], true );
    assertEq( !f32[2], false );
    assertEq( !f32[3], true );
    assertEq( !f32[4], false );
    assertEq( !f32[5], false );
    assertEq( !f32[6], false );
}
test(setupNot, not);

// MToInt32
var str = "can haz cheezburger? okthxbye;";
function setupToInt32() {
    f32[0] = 0;
    f32[1] = 1;
    f32[2] = 2;
    f32[3] = 4;
    f32[4] = 5;
}
function testToInt32() {
    assertEq(str[f32[0]], 'c');
    assertEq(str[f32[1]], 'a');
    assertEq(str[f32[2]], 'n');
    assertEq(str[f32[3]], 'h');
    assertEq(str[f32[4]], 'a');
}
test(setupToInt32, testToInt32);

function setupBailoutToInt32() {
    f32[0] = .5;
}
function testBailoutToInt32() {
    assertEq(typeof str[f32[0]], 'undefined');
}
test(setupBailoutToInt32, testBailoutToInt32);

// MMath (no trigo - see also testFloat32-trigo.js
function assertNear(a, b) {
    var r = (a != a && b != b) || Math.abs(a-b) < 1e-1 || a === b;
    if (!r) {
        print('Precision error: ');
        print(new Error().stack);
        print('Got', a, ', expected near', b);
        assertEq(false, true);
    }
}

function setupOtherMath() {
    setupComp();
    f32[8] = 4.2;
}
function otherMath() {
    for (var i = 0; i < 9; ++i) {
        assertNear(Math.fround(Math.exp(f32[i])), Math.exp(f32[i]));
        assertNear(Math.fround(Math.log(f32[i])), Math.log(f32[i]));
    }
};
test(setupOtherMath, otherMath);

function setupFloor() {
    f32[0] = -5.5;
    f32[1] = -0.5;
    f32[2] = 0;
    f32[3] = 1.5;
}
function setupFloorDouble() {
    f32[4] = NaN;
    f32[5] = -0;
    f32[6] = Infinity;
    f32[7] = -Infinity;
    f32[8] = Math.pow(2,31); // too big to fit into a int
}
function testFloor() {
    for (var i = 0; i < 4; ++i) {
        var f = Math.floor(f32[i]);
        assertFloat32(f, false); // f is an int32

        var g = Math.floor(-0 + f32[i]);
        assertFloat32(g, false);

        assertEq(f, g);
    }
}
function testFloorDouble() {
    for (var i = 4; i < 9; ++i) {
        var f = Math.fround(Math.floor(f32[i]));
        assertFloat32(f, true);

        var g = Math.floor(-0 + f32[i]);
        assertFloat32(g, false);

        assertEq(f, g);
    }
}
test(setupFloor, testFloor);
test(setupFloorDouble, testFloorDouble);

function setupRound() {
    f32[0] = -5.5;
    f32[1] = -0.6;
    f32[2] = 1.5;
    f32[3] = 1;
}
function setupRoundDouble() {
    f32[4] = NaN;
    f32[5] = -0.49;          // rounded to -0
    f32[6] = Infinity;
    f32[7] = -Infinity;
    f32[8] = Math.pow(2,31); // too big to fit into a int
    f32[9] = -0;
}
function testRound() {
    for (var i = 0; i < 4; ++i) {
        var r32 = Math.round(f32[i]);
        assertFloat32(r32, false); // r32 is an int32

        var r64 = Math.round(-0 + f32[i]);
        assertFloat32(r64, false);

        assertEq(r32, r64);
    }
}
function testRoundDouble() {
    for (var i = 4; i < 10; ++i) {
        var r32 = Math.fround(Math.round(f32[i]));
        assertFloat32(r32, true);

        var r64 = Math.round(-0 + f32[i]);
        assertFloat32(r64, false);

        assertEq(r32, r64);
    }
}
test(setupRound, testRound);
test(setupRoundDouble, testRoundDouble);

function setupCeil() {
    f32[0] = -5.5;
    f32[1] = -1.5;
    f32[2] = 0;
    f32[3] = 1.5;
}
function setupCeilDouble() {
    f32[4] = NaN;
    f32[5] = -0;
    f32[6] = Infinity;
    f32[7] = -Infinity;
    f32[8] = Math.pow(2,31); // too big to fit into a int
}
function testCeil() {
    for(var i = 0; i < 2; ++i) {
        var f = Math.ceil(f32[i]);
        assertFloat32(f, false);

        var g = Math.ceil(-0 + f32[i]);
        assertFloat32(g, false);

        assertEq(f, g);
    }
}
function testCeilDouble() {
    for(var i = 4; i < 9; ++i) {
        var f = Math.fround(Math.ceil(f32[i]));
        assertFloat32(f, true);

        var g = Math.ceil(-0 + f32[i]);
        assertFloat32(g, false);

        assertEq(f, g);
    }
}
test(setupCeil, testCeil);
test(setupCeilDouble, testCeilDouble);