var btversion = "0.15";

// browser specialization SUCKS
function isGecko() {
    var ua = navigator.userAgent;
    return (ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1);
}

function isOpera() {
    var ua = navigator.userAgent;
    return (ua.indexOf('Opera') > -1);
}

// dummy thing. perform a trivial computation, so runtime cost is dominated by dispatch
function Dummy() {
    this.foo = "foo";
    this.bar = "bar";
}

Dummy.prototype.fib = function() {
    1;
}

// types which behave identically to Dummy but are distinct
function Dummy0() { Dummy.call(this); }
Dummy0.prototype = new Dummy();
Dummy0.prototype.fibb = function() { 0; return this.fib(); }
function Dummy1() { Dummy.call(this); }
Dummy1.prototype = new Dummy();
Dummy1.prototype.fibb = function() { 1; return this.fib(); }
function Dummy2() { Dummy.call(this); }
Dummy2.prototype = new Dummy();
Dummy2.prototype.fibb = function() { 2; return this.fib(); }
function Dummy3() { Dummy.call(this); }
Dummy3.prototype = new Dummy();
Dummy3.prototype.fibb = function() { 3; return this.fib(); }
function Dummy4() { Dummy.call(this); }
Dummy4.prototype = new Dummy();
Dummy4.prototype.fibb = function() { 4; return this.fib(); }
function Dummy5() { Dummy.call(this); }
Dummy5.prototype = new Dummy();
Dummy5.prototype.fibb = function() { 5; return this.fib(); }
function Dummy6() { Dummy.call(this); }
Dummy6.prototype = new Dummy();
Dummy6.prototype.fibb = function() { 6; return this.fib(); }
function Dummy7() { Dummy.call(this); }
Dummy7.prototype = new Dummy();
Dummy7.prototype.fibb = function() { 7; return this.fib(); }
function Dummy8() { Dummy.call(this); }
Dummy8.prototype = new Dummy();
Dummy8.prototype.fibb = function() { 8; return this.fib(); }
function Dummy9() { Dummy.call(this); }
Dummy9.prototype = new Dummy();
Dummy9.prototype.fibb = function() { 9; return this.fib(); }


// some simple mathematical types to use for tests
function Fibber() {
    this.a = 0;
    this.b = this.c = 1;
}

Fibber.prototype.fib = function() {
    this.c = this.a;
    this.a = this.b;
    this.b += this.c;
    return this.a;
}

// and a type for adding fields late
function EmptyFibber() {}

EmptyFibber.prototype.fib = function() {
    this.c = this.a;
    this.a = this.b;
    this.b += this.c;
    return this.a;
}

function newPseudoFibber() {
    var r = new EmptyFibber();
    r.a = 0;
    r.b = r.c = 1;
    return r;
}


// types which behave identically to Fibber but are distinct
function Fibber0() { Fibber.call(this); }
Fibber0.prototype = new Fibber();
Fibber0.prototype.fibb = function() { 0; return this.fib(); }
function Fibber1() { Fibber.call(this); }
Fibber1.prototype = new Fibber();
Fibber1.prototype.fibb = function() { 1; return this.fib(); }
function Fibber2() { Fibber.call(this); }
Fibber2.prototype = new Fibber();
Fibber2.prototype.fibb = function() { 2; return this.fib(); }
function Fibber3() { Fibber.call(this); }
Fibber3.prototype = new Fibber();
Fibber3.prototype.fibb = function() { 3; return this.fib(); }
function Fibber4() { Fibber.call(this); }
Fibber4.prototype = new Fibber();
Fibber4.prototype.fibb = function() { 4; return this.fib(); }
function Fibber5() { Fibber.call(this); }
Fibber5.prototype = new Fibber();
Fibber5.prototype.fibb = function() { 5; return this.fib(); }
function Fibber6() { Fibber.call(this); }
Fibber6.prototype = new Fibber();
Fibber6.prototype.fibb = function() { 6; return this.fib(); }
function Fibber7() { Fibber.call(this); }
Fibber7.prototype = new Fibber();
Fibber7.prototype.fibb = function() { 7; return this.fib(); }
function Fibber8() { Fibber.call(this); }
Fibber8.prototype = new Fibber();
Fibber8.prototype.fibb = function() { 8; return this.fib(); }
function Fibber9() { Fibber.call(this); }
Fibber9.prototype = new Fibber();
Fibber9.prototype.fibb = function() { 9; return this.fib(); }
// --
function Fibber10() { Fibber.call(this); }
Fibber10.prototype = new Fibber();
Fibber10.prototype.fibb = function() { 10; return this.fib(); }
function Fibber11() { Fibber.call(this); }
Fibber11.prototype = new Fibber();
Fibber11.prototype.fibb = function() { 11; return this.fib(); }
function Fibber12() { Fibber.call(this); }
Fibber12.prototype = new Fibber();
Fibber12.prototype.fibb = function() { 12; return this.fib(); }
function Fibber13() { Fibber.call(this); }
Fibber13.prototype = new Fibber();
Fibber13.prototype.fibb = function() { 13; return this.fib(); }
function Fibber14() { Fibber.call(this); }
Fibber14.prototype = new Fibber();
Fibber14.prototype.fibb = function() { 14; return this.fib(); }
function Fibber15() { Fibber.call(this); }
Fibber15.prototype = new Fibber();
Fibber15.prototype.fibb = function() { 15; return this.fib(); }
function Fibber16() { Fibber.call(this); }
Fibber16.prototype = new Fibber();
Fibber16.prototype.fibb = function() { 16; return this.fib(); }
function Fibber17() { Fibber.call(this); }
Fibber17.prototype = new Fibber();
Fibber17.prototype.fibb = function() { 17; return this.fib(); }
function Fibber18() { Fibber.call(this); }
Fibber18.prototype = new Fibber();
Fibber18.prototype.fibb = function() { 18; return this.fib(); }
function Fibber19() { Fibber.call(this); }
Fibber19.prototype = new Fibber();
Fibber19.prototype.fibb = function() { 19; return this.fib(); }

// we need array.contains
if (typeof(Array.prototype.contains) == "undefined")
Array.prototype.contains = function(x) {
    var i;
    for (i = 0; i < this.length; i++) {
        if (this[i] == x) return true;
    }
    return false;
}


// set up the tests
function setup() {
    var ctl = document.getElementById("controls");

    var tr = new TestRunner();

    tr.calcIterations = function() {
        var i, j;
        var f = new Fibber();
        var t = new Timer();

        t.start();
        for (i = 0;; i++) {
            for (j = 0; j < 1000; j++) {
                f.fib();
            }
            if (t.end() > 250) return i * 1000;
        }

        return 100000;
    }

    // TEST LIST
    tr.addTest(hiddenClassesTest,           ctl);
    tr.addTest(worstTracerTest,             ctl);
    tr.addTest(picTest,                     ctl);
    tr.addTest(shapeCachingHMTest,          ctl);
    tr.addTest(shapeCachingNHMTest,         ctl);
    tr.addTest(constructorSensitivityTest,  ctl);
    tr.addTest(jitTest,                     ctl);
    tr.addTest(icSizeTest,                  ctl);
    tr.addTest(factoryMethodTest,           ctl);
}

(function() {
    var curonload = window.onload;
    window.onload = function() {
        curonload();
        setup();
    }
})();

// identify the browser
function identBrowser() {
    document.getElementById("browserOut").innerHTML =
        "Your browser identifies itself as " + navigator.userAgent;
}


/******************************************************************************
 * General test generator
 *****************************************************************************/
function generalFibTest(preloop, inloop) {
    return eval("(function (iter, faultmod, fault) {\n" +
"    var f = new Fibber();\n" +
"    var t = new Timer();\n" +
"    var i, imod;\n" +
"\n" +
    preloop +
"\n" +
"    t.start();\n" +
"    for (i = 0, imod = 1; i < iter; i++, imod++) {\n" +
"        f.fib();\n" +
"\n" +
"        if (imod == faultmod) {\n" +
"            imod = 0;\n" +
inloop +
"        }\n" +
"\n" +
"    }\n" +
"    return t.end();\n" +
"    " + Math.random() + ";\n" +
"})");
}


/******************************************************************************
 * Hidden classes (type modification) test
 *****************************************************************************/
function hiddenClassesTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = 25;

    consistent(function() { return eval(hiddenClassesTestPrime())(iter, faultmod, false); }, function(t1) {
    consistent(function() { return eval(hiddenClassesTestPrime())(iter, faultmod, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("No type manipulation: " + t1 + "<br/>" +
             "Type manipulation: " + t2)
        });
    }); });
}
hiddenClassesTest.feature = "hidden classes";
hiddenClassesTest.itermult = 1;

function hiddenClassesTestPrime() {
    return generalFibTest(
        "var o = new Fibber(), j = true, slobj = (fault ? f : o); f.x = o.x = \"0\";",
        "slobj.x = j ? 0 : \"0\"; j = !j;"
        );
}


/******************************************************************************
 * TraceMonkey test
 *****************************************************************************/

function tracerTest(iter, faultmod, resf) {
    consistent(function() { return tracerTestPrime(iter, faultmod, false); }, function(t1) {
    consistent(function() { return tracerTestPrime(iter, faultmod, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("Predictable trace: " + t1 + "<br/>" +
             "Unpredictable trace: " + t2)
        });
    }); });
}
tracerTest.feature = "tracing";
tracerTest.itermult = 3;

function tracerTestPrime(iter, faultmod, tracerslow) {
    var f = new Fibber();
    var t = new Timer();
    var o = new Object();
    var i, imod, a, b, c;

    a = 0;
    b = c = 1;

    t.start();
    for (i = 0; i < iter; i++) {
        if (tracerslow ? (i%2 == 0) : (i%1 == 0)) {
            c = a;
            a = b;
            b += c;
        } else {
            c = b;
            b += a;
            a = b;
        }
    }
    return t.end();
}


/******************************************************************************
 * Worst-case tracer test (every fault is a new branch
 *****************************************************************************/
function worstTracerTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = Math.ceil(iter / 20);
    var faults = Math.round(iter / faultmod);
    if (faults > 100) {
        resf({matches: false, nftime: 1, ftime: 0, html: ""});
        return;
    }

    consistent(function() { return eval(worstTracerTestPrime(faults))(iter, false); }, function(t1) {
    consistent(function() { return eval(worstTracerTestPrime(faults))(iter, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("Predictable trace: " + t1 + "<br/>" +
             "Unpredictable trace: " + t2)
        });
    }); });
}
worstTracerTest.feature = "tracing";
worstTracerTest.itermult = 0.2;

function worstTracerTestPrime(faults) {
    return ("(function(iter, fault) {\n" +
"    var f = new Fibber();\n" +
"    var t = new Timer();\n" +
"    var o = new Object();\n" +
"    var i, wfi, nfi, a, b, c;\n" +
"\n" +
"    a = 0;\n" +
"    b = c = 1;\n" +
"    wfi = nfi = 0;\n" +
"\n" +
"    t.start();\n" +
"    for (i = 0; i < iter; i++) {\n" +
        worstTracerTestGen(0, faults) +
"        if (fault) { wfi++; } else { nfi++; }\n" +
"    }\n" +
"    return t.end();\n" +
"    " + Math.random() + "\n" +
"})");
}

// generate the branches for a tracer fault
function worstTracerTestGen(step, faults) {
    if (faults <= 1) {
        // inner case
        return "c = a; a = b; b += c;\n"

    } else {
        // switch
        return "if ((wfi & " + (1<<step) + ") != 0) {\n" +
            worstTracerTestGen(step + 1, Math.ceil(faults / 2)) +
            "} else {\n" +
            worstTracerTestGen(step + 1, Math.floor(faults / 2)) +
            "}\n";
    }
}

/******************************************************************************
 * Test to determine size of (polymorphic) inline cache
xo *****************************************************************************/
function icSizeTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = 2;
    if (faultmod > 10) faultmod = 10;
    consistent(function() { return eval(icSizeTestPrime())(iter, faultmod, false); }, function(t1) {
    consistent(function() { return eval(icSizeTestPrime())(iter, faultmod, true); }, function(t2) {
	    m  = (t2 < t1*1.1);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("Monomorphic callsite: " + t1 + "<br/>" +
             "2-morphic callsite: " + t2)
        });
    }); });
}
icSizeTest.feature = "polymorphic inline cache";
icSizeTest.itermult = isGecko() ? 3 : 0.5;
icSizeTest.faultmax = 10;

function icSizeTestPrime() {
    return ("(function (iter, faultmod, doslow) {\n" +
"    var foo = new Object();\n" +
"    var t = new Timer();\n" +
"    var bar = new Object();\n" +
"    var i, imod, j;\n" +
"    \n" +
"    // depending on faultmod, we'll switch between a certain number of fibber functions\n" +
"    var fs = [\n" +
"        new Dummy0(),\n" +
"        new Dummy1(),\n" +
"        new Dummy2(),\n" +
"        new Dummy3(),\n" +
"        new Dummy4(),\n" +
"        new Dummy5(),\n" +
"        new Dummy6(),\n" +
"        new Dummy7(),\n" +
"        new Dummy8(),\n" +
"        new Dummy9()\n" +
"    ];\n" +
"    foo = bar = fs[0];\n" +
"    j = 0;\n" +
"\n" +
"    // now run the tests\n" +
"    t.start();\n" +
"    for (i = 0, imod = 1; i < iter; i++, imod++) {\n" +
"        foo.fibb();\n" +
"        if (imod == faultmod) imod = 0;\n" +
"        /* now swap out our f, one of Dummy0..faultmod. call 10 times */\n" +
"        if (doslow) foo = fs[imod];\n" +
"        else bar = fs[imod];\n" +
"        // would this help to offset overhead of assignment?\n" +
"    }\n" +
"    return t.end();\n" +
"    " + Math.random() + ";\n" +
"})");
}


/******************************************************************************
 * Polymorphic inline caching tests
 *****************************************************************************/
function picTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = 1;

    consistent(function() { return eval(picTestPrime())(iter, faultmod, false); }, function(t1) {
    consistent(function() { return eval(picTestPrime())(iter, faultmod, true); }, function(t2) {
        m  = (t2 > t1 * 1.25);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("One function: " + t1 + "<br/>" +
             "Multiple functions: " + t2)
        });
    }); });
}
picTest.feature = "IC";
picTest.itermult = 0.5;

function picTestPrime() {
    return ("(function (iter, faultmod, picslow) {\n" +
"    var foo = new Object();\n" +
"    var t = new Timer();\n" +
"    var bar = new Object();\n" +
"    var i, imod, j;\n" +
"    \n" +
"\n" +
"    // to make PIC fail, we'll be changing our types 'round\n" +
"    var fs = [\n" +
"        new Fibber0(),\n" +
"        new Fibber1(),\n" +
"        new Fibber2(),\n" +
"        new Fibber3(),\n" +
"        new Fibber4(),\n" +
"        new Fibber5(),\n" +
"        new Fibber6(),\n" +
"        new Fibber7(),\n" +
"        new Fibber8(),\n" +
"        new Fibber9(),\n" +
"        new Fibber10(),\n" +
"        new Fibber11(),\n" +
"        new Fibber12(),\n" +
"        new Fibber13(),\n" +
"        new Fibber14(),\n" +
"        new Fibber15(),\n" +
"        new Fibber16(),\n" +
"        new Fibber17(),\n" +
"        new Fibber18(),\n" +
"        new Fibber19()\n" +
"    ];\n" +
"    foo = bar = fs[0];\n" +
"    j = 1;\n" +
"\n" +
"    // now run the tests\n" +
"    t.start();\n" +
"    for (i = 0, imod = 1; i < iter; i++, imod++) {\n" +
"        foo.fibb();\n" +
"\n" +
"        if (imod == faultmod) {\n" +
"            imod = 0;\n" +
"            /* now swap out our f */\n" +
"            if (picslow) foo = fs[j++];\n" +
"            else bar = fs[j++];\n" +
"            if (j == 19) j = 0;\n" +
"        }\n" +
"    }\n" +
"    return t.end();\n" +
"    " + Math.random() + ";\n" +
"})");
}


/******************************************************************************
 * Shape caching test (with hashmap access)
 *****************************************************************************/
function shapeCachingHMTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = 10;

    consistent(function() { return eval(shapeCachingHMTestPrime())(iter, faultmod, false); }, function(t1) {
    consistent(function() { return eval(shapeCachingHMTestPrime())(iter, faultmod, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("No hashmap shape manipulation: " + t1 + "<br/>" +
             "Hashmap shape manipulation: " + t2)
        });
    }); });
}
shapeCachingHMTest.feature = "hashmap shape caching";
shapeCachingHMTest.itermult = 0.2;

// this is INANELY expensive on Firefox
if (isGecko()) {
    shapeCachingHMTest.itermult *= 0.2;
}

function shapeCachingHMTestPrime() {
    return generalFibTest(
        "var o = new Fibber(), j = 0, slobj = (fault ? f : o); f[\"x\" + j] = o[\"x\" + j] = 0;",
        "delete slobj[\"x\" + (j++)]; slobj[\"x\" + j] = 0;"
        );
}


/******************************************************************************
 * Shape caching test (without hashmap access)
 *****************************************************************************/
function shapeCachingNHMTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = 10;

    consistent(function() { return eval(shapeCachingNHMTestPrime())(iter, faultmod, false); }, function(t1) {
    consistent(function() { return eval(shapeCachingNHMTestPrime())(iter, faultmod, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("No hashmap shape manipulation: " + t1 + "<br/>" +
             "Hashmap shape manipulation: " + t2)
        });
    }); });
}
shapeCachingNHMTest.feature = "non-hashmap shape caching";
shapeCachingNHMTest.itermult = 0.2;

function shapeCachingNHMTestPrime() {
    return generalFibTest(
        "var o = new Fibber(), j = true, slobj = (fault ? f : o); f.x0 = o.x0 = 0;",
        "if (j) { delete slobj.x0; slobj.x1 = 0; } else { delete slobj.x1; slobj.x0 = 0; }; j = !j;"
        );
}


/******************************************************************************
 * Constructor sensitivity test
 *****************************************************************************/
function constructorSensitivityTest(iter, faultmod, resf) {
    if (faultmod === false) faultmod = 10;
    if (faultmod < constructorSensitivityTest.faultmax) faultmod = constructorSensitivityTest.faultmax;

    consistent(function() { return eval(constructorSensitivityTestPrime())(iter, faultmod, false); }, function(t1) {
    consistent(function() { return eval(constructorSensitivityTestPrime())(iter, faultmod, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("No extended object: " + t1 + "<br/>" +
             "Extended object: " + t2)
        });
    }); });
}
constructorSensitivityTest.feature = "constructor sensitivity";
constructorSensitivityTest.itermult = 0.2;
constructorSensitivityTest.faultmax = 1024;
constructorSensitivityTest.sanitytest = false;

function constructorSensitivityTestPrime() {
    return generalFibTest(
        "var o = new Fibber(), slobj = (fault ? f : o); slobj.x = 0; for (i = 1; i < faultmod; i++) { slobj[\"x\" + i] = 0; };",
        ""
        );
}


/******************************************************************************
 * JIT test
 *****************************************************************************/
var jitTestFunctions = [];
function jitTest(iter, faultmod, resf) {
    var jitted, jitruns, unjitted, i;
    var jtime, ujtime;

    if (faultmod === false) faultmod = 100;

    // find the right maybe-jitted function
    for (i = jitTestFunctions.length - 1; i >= 0 && typeof(jitTestFunctions[i]) == "undefined"; i--);
    if (i >= 0) {
        // found one
        jitted = jitTestFunctions[i];
        jitruns = i;
        delete(jitTestFunctions[i]);
    } else {
        // didn't find one
        jitted = eval(generalFibTest("", ""));
        jitruns = 0;
    }

    // make an unjitted one too
    unjitted = eval(generalFibTest("", ""));

    // now JIT the jitted one properly
    var jitto = function() {
        if (jitruns < faultmod - 1) {
            jitted(iter, 0, false);
            jitruns++;
            setTimeout(jitto, 0);
        } else {
            // do the actual test
            setTimeout(dounjitted, 0);
        }
    }
    setTimeout(jitto, 0);

    // once that's done, do the actual test
    var dounjitted = function() {
        ujtime = unjitted(iter, 0, false);
        setTimeout(dojitted, 0);
    }

    // and the JITted test
    var dojitted = function() {
        jtime = jitted(iter, 0, false);
        jitruns++;
        jitTestFunctions[jitruns] = jitted;

        var m  = (ujtime > jtime * 1.4);
        resf({
            matches: m,
            nftime: jtime,
            ftime: ujtime,
            html: 
            ("No JIT: " + ujtime + "<br/>" +
             "JIT: " + jtime)
        });
    }
}
jitTest.feature = "JIT";
jitTest.itermult = 1;


/******************************************************************************
 * Factory method test
 *****************************************************************************/
function factoryMethodTest(iter, faultmod, resf) {
    consistent(function() { return eval(factoryMethodTestPrime())(iter, 0, false); }, function(t1) {
    consistent(function() { return eval(factoryMethodTestPrime())(iter, 0, true); }, function(t2) {
        m  = (t2 > t1 * 1.4);
        resf({
            matches: m,
            nftime: t1,
            ftime: t2,
            html: 
            ("No factory method: " + t1 + "<br/>" +
             "Factory method: " + t2)
        });
    }); });
}
factoryMethodTest.feature = "factory method sensitivity";
factoryMethodTest.itermult = 1;
factoryMethodTest.faultmax = 1; // faultmod is meaningless

function factoryMethodTestPrime() {
    return generalFibTest(
        "f = (fault ? newPseudoFibber() : new Fibber());",
        ""
        );
}

