/*
 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test id=Xbatch
 * @bug 8332920
 * @summary Tests partial peeling at unsigned tests with limit being negative in exit tests "i >u limit".
 * @run main/othervm -Xbatch -XX:-TieredCompilation
 *                   -XX:CompileOnly=*TestPartialPeel*::original*,*TestPartialPeel*::test*
 *                   compiler.loopopts.TestPartialPeelAtUnsignedTestsNegativeLimit
 */

/*
 * @test id=Xcomp-run-inline
 * @bug 8332920
 * @summary Tests partial peeling at unsigned tests with limit being negative in exit tests "i >u limit".
 * @run main/othervm -Xcomp -XX:-TieredCompilation
 *                   -XX:CompileOnly=*TestPartialPeel*::original*,*TestPartialPeel*::run*,*TestPartialPeel*::test*
 *                   -XX:CompileCommand=inline,*TestPartialPeelAtUnsignedTestsNegativeLimit::test*
 *                   -XX:CompileCommand=dontinline,*TestPartialPeelAtUnsignedTestsNegativeLimit::check
 *                   compiler.loopopts.TestPartialPeelAtUnsignedTestsNegativeLimit
 */

/*
 * @test id=Xcomp-compile-test
 * @bug 8332920
 * @summary Tests partial peeling at unsigned tests with limit being negative in exit tests "i >u limit".
 * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:CompileOnly=*TestPartialPeel*::original*,*TestPartialPeel*::test*
 *                   compiler.loopopts.TestPartialPeelAtUnsignedTestsNegativeLimit
 */

/*
 * @test id=vanilla
 * @bug 8332920
 * @requires vm.flavor == "server" & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4)
 * @summary Tests partial peeling at unsigned tests with limit being negative in exit tests "i >u limit".
 *          Only run this test with C2 since it is time-consuming and only tests a C2 issue.
 * @run main compiler.loopopts.TestPartialPeelAtUnsignedTestsNegativeLimit
 */

package compiler.loopopts;

import java.util.Random;

import static java.lang.Integer.*;

public class TestPartialPeelAtUnsignedTestsNegativeLimit {
    static int iFld = 10000;
    static int iterations = 0;
    static int iFld2;
    static boolean flag;
    final static Random RANDOM = new Random();

    public static void main(String[] args) {
        compareUnsigned(3, 3); // Load Integer class for -Xcomp
        for (int i = 0; i < 2; i++) {
            if (!originalTest()) {
                throw new RuntimeException("originalTest() failed");
            }
        }

        for (int i = 0; i < 2000; i++) {
            // For profiling
            iFld = -1;
            originalTestVariation1();

            // Actual run
            iFld = MAX_VALUE - 100_000;
            if (!originalTestVariation1()) {
                throw new RuntimeException("originalTestVariation1() failed");
            }
        }

        for (int i = 0; i < 2000; ++i) {
            // For profiling
            iFld = MAX_VALUE;
            originalTestVariation2();

            // Actual run
            iFld = MIN_VALUE + 100000;
            if (!originalTestVariation2()) {
                throw new RuntimeException("originalTestVariation2() failed");
            }
        }

        runWhileLTIncr();
        runWhileLTDecr();
    }

    // Originally reported simplified regression test with 2 variations (see below).
    public static boolean originalTest() {
        for (int i = MAX_VALUE - 50_000; compareUnsigned(i, -1) < 0; i++) {
            if (compareUnsigned(MIN_VALUE, i) < 0) {
                return true;
            }
        }
        return false;
    }

    public static boolean originalTestVariation1() {
        int a = 0;
        for (int i = iFld; compareUnsigned(i, -1) < 0; ++i) { // i <u -1

            if (i >= Integer.MIN_VALUE + 1 && i <= 100) { // Transformed to unsigned test.
                return true;
            }
            a *= 23;
        }
        return false;
    }

    public static boolean originalTestVariation2() {
        int a = 0;
        for (int i = iFld; compareUnsigned(i, -1000) < 0; i--) { // i <u -1
            if (compareUnsigned(MAX_VALUE - 20, i) > 0) {
                return true;
            }
            a = i;
        }
        System.out.println(a);
        return false;
    }


    public static void testWhileLTIncr(int init, int limit) {
        int i = init;
        while (true) {
            // <Peeled Section>

            // Found as loop head in ciTypeFlow, but both paths inside loop -> head not cloned.
            // As a result, this head has the safepoint as backedge instead of the loop exit test
            // and we cannot create a counted loop (yet). We first need to partial peel.
            if (flag) {
            }

            iFld2++;

            // Loop exit test i >=u limit (i.e. "while (i <u limit)") to partial peel with.
            // insert_cmpi_loop_exit() changes this exit condition into a signed and an unsigned test:
            //   i >= limit && i >=u limit
            // where the signed condition can be used as proper loop exit condition for a counted loop
            // (we cannot use an unsigned counted loop exit condition).
            //
            // After Partial Peeling, we have:
            //   if (i >= limit) goto Exit
            // Loop:
            //   if (i >=u limit) goto Exit
            //   ...
            //   i++;
            //   if (i >= limit) goto Exit
            //   goto Loop
            // Exit:
            //   ...
            //
            // If init = MAX_VALUE and limit = MIN_VALUE:
            //   i >= limit
            //   MAX_VALUE >= MIN_VALUE
            // which is true where
            //   i >=u limit
            //   MAX_VALUE >=u MIN_VALUE
            //   MAX_VALUE >=u (uint)(MAX_INT + 1)
            // is false and we wrongly never enter the loop even though we should have.
            // This results in a wrong execution.
            if (compareUnsigned(i, limit) >= 0) {
                return;
            }
            // <-- Partial Peeling CUT -->
            // Safepoint
            // <Unpeeled Section>
            iterations++;
            i++;
        }
    }

    // Same as testWhileLTIncr() but with decrement instead.
    public static void testWhileLTDecr(int init, int limit) {
        int i = init;
        while (true) {
            if (flag) {
            }

            // Loop exit test.
            if (compareUnsigned(i, limit) >= 0) { // While (i <u limit)
                return;
            }

            iterations++;
            i--;
        }
    }

    public static void runWhileLTIncr() {
        // Currently works:
        testWhileLTIncr(MAX_VALUE, -1);
        check(MIN_VALUE); // MAX_VALUE + 1 iterations
        testWhileLTIncr(-1, 1);
        check(0);
        testWhileLTIncr(0, 0);
        check(0);
        checkIncrWithRandom(0, 0); // Sanity check this method.
        flag = !flag; // Change profiling
        testWhileLTIncr(MAX_VALUE - 2000, MAX_VALUE);
        check(2000);
        testWhileLTIncr(MAX_VALUE - 1990, MAX_VALUE);
        check(1990);
        testWhileLTIncr(MAX_VALUE - 1, MAX_VALUE);
        check(1);
        testWhileLTIncr(MIN_VALUE, MIN_VALUE + 2000);
        check(2000);
        testWhileLTIncr(MIN_VALUE, MIN_VALUE + 1990);
        check(1990);
        testWhileLTIncr(MIN_VALUE, MIN_VALUE + 1);
        check(1);

        flag = !flag;
        // Overflow currently does not work with negative limit and is fixed with patch:
        testWhileLTIncr(MAX_VALUE, MIN_VALUE);
        check(1);
        testWhileLTIncr(MAX_VALUE - 2000, MIN_VALUE);
        check(2001);
        testWhileLTIncr(MAX_VALUE, MIN_VALUE + 2000);
        check(2001);
        testWhileLTIncr(MAX_VALUE - 2000, MIN_VALUE + 2000);
        check(4001);

        // Random values
        int init = RANDOM.nextInt(0, MAX_VALUE);
        int limit = RANDOM.nextInt(MIN_VALUE, 0);
        testWhileLTIncr(init, limit);
        checkIncrWithRandom(init, limit);
    }

    public static void runWhileLTDecr() {
        // Currently works:
        testWhileLTDecr(1, -1);
        check(2);
        testWhileLTDecr(-1, 1);
        check(0);
        testWhileLTDecr(0, 0);
        check(0);
        checkDecrWithRandom(0, 0); // Sanity check this method.
        flag = !flag;
        testWhileLTDecr(MAX_VALUE, MIN_VALUE);
        check(MIN_VALUE); // MAX_VALUE + 1 iterations
        testWhileLTDecr(MAX_VALUE, -1);
        check(MIN_VALUE); // MAX_VALUE + 1 iterations
        testWhileLTDecr(MAX_VALUE, MIN_VALUE);
        check(MIN_VALUE); // MAX_VALUE + 1 iterations
        testWhileLTDecr(MIN_VALUE, 0);
        check(0);
        testWhileLTDecr(MIN_VALUE, 1);
        check(0);
        flag = !flag;

        // Underflow currently does not work with negative limit and is fixed with patch:
        testWhileLTDecr(MIN_VALUE, -1);
        check(MIN_VALUE + 1); // MAX_VALUE + 2 iterations
        testWhileLTDecr(MIN_VALUE, -2000);
        check(MIN_VALUE + 1); // MAX_VALUE + 2 iterations
        testWhileLTDecr(MIN_VALUE, MIN_VALUE + 1);
        check(MIN_VALUE + 1); // MAX_VALUE + 2 iterations
        testWhileLTDecr(MIN_VALUE + 2000, -1);
        check(MIN_VALUE + 2001); // MAX_VALUE + 2002 iterations
        testWhileLTDecr(MIN_VALUE + 2000, -2000);
        check(MIN_VALUE + 2001); // MAX_VALUE + 2002 iterations
        testWhileLTDecr(MIN_VALUE + 2000, MIN_VALUE + 2001);
        check(MIN_VALUE + 2001); // MAX_VALUE + 2002 iterations

        // Random values
        int r1 = RANDOM.nextInt(MIN_VALUE, 0);
        int r2 = RANDOM.nextInt(MIN_VALUE, 0);
        int init = Math.min(r1, r2);
        int limit = Math.max(r1, r2);
        testWhileLTDecr(init, limit);
        checkDecrWithRandom(init, limit);
    }

    static void check(int expectedIterations) {
        if (expectedIterations != iterations) {
            throw new RuntimeException("Expected " + expectedIterations + " iterations but only got " + iterations);
        }
        iterations = 0; // Reset
    }

    static void checkIncrWithRandom(long init, long limit) {
        long expectedIterations = ((long)(MAX_VALUE) - init) + (limit - (long)MIN_VALUE) + 1;
        if ((int)expectedIterations != iterations) {
            String error = "Expected %d iterations but only got %d, init: %d, limit: %d"
                            .formatted(expectedIterations, iterations, init, limit);
            throw new RuntimeException(error);
        }
        iterations = 0; // Reset
    }

    static void checkDecrWithRandom(long init, long limit) {
        long expectedIterations = init + MIN_VALUE + MAX_VALUE + 2;
        if (init == limit) {
            expectedIterations = 0;
        }
        if ((int)expectedIterations != iterations) {
            String error = "Expected %d iterations but only got %d, init: %d, limit: %d"
                    .formatted(expectedIterations, iterations, init, limit);
            throw new RuntimeException(error);
        }
        iterations = 0; // Reset
    }
}