//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// -force:fieldhoist -prejit
(function(){
    var obj6 = 1;
    LABEL0:
    LABEL1:
    for (var __loopvar0 = 0; obj6.a < (1) && __loopvar0 < 3; obj6.a++ + __loopvar0++) {
    }
})();

// - 'o1.a = 1' kills the hoisted o2.a as being live
// - As part of the load in '++o2.a' it reloads field into the hoisted stack sym. This load is int-specialized, making the
//   hosited stack sym only live as an int.
// - As part of the store in '++o2.a' it loads the value to store into the hoisted stack sym and then stores the hoisted stack
//   sym value into the field. As part of this load, since only the int version of the hoisted stack sym was live, liveness
//   needs to be updated to say that only the var version of the hoisted stack sym is now live.
(function() {
    var o1 = { a: 0 };
    var o2 = o1;
    for (var i = 0; i < 1; ++i)
        for (; o2.a < 1; ++o2.a)
            o1.a = 1;
})();

function test0() {
    function test0a(o) {
        o.p = "1";
    }

    // - 'o.p' is live around the outer loop, so it's hoisted
    // - At 'o.p &= 1' the hoisted stack sym is only live as an int32
    // - In the prepass of the inner loop, at 'o.q = test0a(o)', 'o.p' is killed. Ideally, here the hoisted int32 stack sym
    //   should also be killed, but it's not since it requires more tracking and computation.
    // - In the optimization pass of the inner loop, the use at 'o.p | 0' requires a reload since 'o.p' is not live. At this
    //   point, the field should not have been live on entry into the loop either (after merge with prepass), so it should also
    //   kill the hoisted int32 stack sym.
    // - This prevents compensation to be required on the back-edge of the inner loop in the optimization pass, which would in
    //   this case, cause an unnecessary (and permanent) bail-out even when aggressive int type spec is off (hence the assert).
    function test0b(o) {
        var sum = 0;
        for (var i = 0; i < 10; ++i) {
            sum += o.p &= 1;
            for (var j = 0; j < 10; ++j) {
                sum += o.p | 0;
                o.q = test0a(o);
            }
            sum += o.p | 0;
        }

        return sum;
    }

    return test0b({ p: 1, q: 1 });
}
WScript.Echo("test0: " + test0());

// - 'a' is a slot variable since it's used in a child function
// - In 'b = a = c ? 1 : 2', the sym for 'b' is only available as an int32 and 'b' is the sym store for that value
// - At 'a = a', the field (slot) load is hoisted. It is found that the field's value number in the landing pad is the same as
//   the value's sym store's value number. So, instead of loading the field in the landing pad, 'b' is copied instead.
// - Since 'b' is only available as an int32, it needs to be converted to var.
function test1() {
    var c = 1;
    function test1a() {
        var a;
        for (var i = 0; i < 1; ++i) {
            var b = a = c ? 1 : 2;
            for (var j = 0; j < 1; ++j)
                a = a;
        }
        function test1aa() { a; }
        return a;
    }
    return test1a();
}
WScript.Echo("test1: " + test1());

// - 'a' is considered a field since it's used by a child function
// - 'a' is hoisted outside the outer loop into a stack sym
// - 'a |= 0' makes the hoisted stack sym for 'a' available as var and lossless int32
// - 'test2a()' should kill the hoisted stack syms for 'a' since that function could have changed 'a'
// - In this case, by the time of 'c = a' in the inner loop's second prepass, there are hoistable fields. Regardless of whether
//   the loop has hoistable fields, the call should kill the hoisted stack syms for 'a' (the kill actually happens upon reload
//   at 'c = a').
function test2() {
    var a, b, c;
    for (var i = 0; i < 1; ++i) {
        a |= 0;
        b = 0;
        for (var j = 0; j < 1; ++j) {
            0 ? b : 0;
            null ? b = test2a() : a;
            c = a;
        }
    }
    function test2a() { a, b; }
    return c;
}
WScript.Echo("test2: " + test2());

// - 'b' gets hoisted outside the most outer loop
// - 'b = -158506649.9 >> 1' makes the hoisted stack sym for 'b' available as an int32
// - 'test3a()' kills 'b'. However, hoisted stack syms are not killed when fields are killed, rather, they need to be killed on
//   the next reload.
// - Because 'b' is not live coming into the most inner loop, 'b' is hoistable in the prepass
// - The use of 'b' in 'b >>= b' and the lack of kills in the most inner loop cause 'b' to be hoisted outside that loop
// - Since 'b' was already hoisted, the hoisted stack sym is reused
// - Note that the hoisted stack sym from the most outer loop is available an an int32 in the most inner loop's landing pad
// - Hoisting 'b' into the most inner loop's landing pad counts as a reload, so it needs to kill specialized versions of the
//   hoisted stack sym in the landing pad, loop header, and in the on-entry info to prevent forcing compensation
function test3() {
    var a = 0;
    var b = 0;
    for (var i = 0; b !== 1 && i < 1; ++i) {
        b = -158506649.9 >> 1;
        for (var j = 0; j < 8; ++j) {
            test3a();
            ++a;
            for (var k = 0; (b >>= b) && k < 1; ++k)
                a >>>= 1;
        }
    }
    return a;

    function test3a() {
        a;
        ++b;
    }
}
WScript.Echo("test3: " + test3());

// - 'a' is a slot variable since it's used by a child function, and is hoisted into a stack sym as a field
// - 'a &= 1' makes the hoisted stack sym available as an int
// - In 'a = 1', the constant is loaded into a stack sym ('s1 = 1') somewhere before, so the inner loop's last prepass sees
//   'a = s1'. Since 's1 = 1' would have already been int-specialized, 's1' would be available as an int and the hoisted stack
//   sym of 'a' will also be made to be available as an int.
// - In the optimization pass, due to constant propagation, 'a = 1' appears as 'a.var = 0x3'. Since the loop prepass made the
//   hoisted stack sym for 'a' available as an int, the optimization pass should do so as well (prepass must be equally or less
//   aggressive than the optimization pass).
// - By the end, the int version of the hoisted stack sym of 'a' should be live through the inner loop and compensation should
//   not be necessary
function test4() {
    var a = 0;
    for (var i = 0; i < 1; ++i) {
        a &= 1;
        for (var j = 0; j < 1; ++j)
            a = 1;
    }
    return a;

    function test4a() { a; }
}
WScript.Echo("test4: " + test4());

// Same as above but with a field instead of a slot variable
function test5() {
    var o = { a: 0 };
    for (var i = 0; i < 1; ++i) {
        o.a &= 1;
        for (var j = 0; j < 1; ++j)
            o.a = 1;
    }
    return o.a;
}
WScript.Echo("test5: " + test5());
