/*
 * Copyright (c) 2021, Red Hat, Inc. 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
 * @bug 8259609 8276116
 * @summary C2: optimize long range checks in long counted loops
 * @requires vm.compiler2.enabled
 * @requires vm.compMode != "Xcomp"
 * @library /test/lib /
 * @modules java.base/jdk.internal.util
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 *
 * @run main/othervm -ea -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-BackgroundCompilation -XX:-UseOnStackReplacement
 *                   -XX:+UnlockExperimentalVMOptions -XX:PerMethodSpecTrapLimit=5000 -XX:PerMethodTrapLimit=100
 *                   TestLongRangeCheck
 *
 */

import jdk.internal.util.Preconditions;
import jdk.test.whitebox.WhiteBox;
import java.lang.reflect.Method;
import compiler.whitebox.CompilerWhiteBoxTest;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.lang.reflect.InvocationTargetException;

public class TestLongRangeCheck {
    private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();

     private static void assertIsCompiled(Method m) {
         if (!WHITE_BOX.isMethodCompiled(m) || WHITE_BOX.getMethodCompilationLevel(m) != CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION) {
             throw new RuntimeException("should still be compiled");
         }
    }

     private static void assertIsNotCompiled(Method m) {
         if (WHITE_BOX.isMethodCompiled(m) && WHITE_BOX.getMethodCompilationLevel(m) == CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION) {
             throw new RuntimeException("should have been deoptimized");
         }
    }

    private static void compile(Method m) {
        WHITE_BOX.enqueueMethodForCompilation(m, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);
        assertIsCompiled(m);
    }

    public static ClassLoader newClassLoader() {
        try {
            return new URLClassLoader(new URL[] {
                    Paths.get(System.getProperty("test.classes",".")).toUri().toURL(),
            }, null);
        } catch (MalformedURLException e){
            throw new RuntimeException("Unexpected URL conversion failure", e);
        }
    }

    private static void test(String method, long start, long stop, long length, long offset) throws Exception {
        Method m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class);
        m.invoke(null, start, stop, length, offset); // run once so all classes are loaded
        compile(m);

        m.invoke(null, start, stop, length, offset);
        assertIsCompiled(m);
        try {
            m.invoke(null, start-1, stop, length, offset);
            throw new RuntimeException("should have thrown");
        } catch(InvocationTargetException e) {
            if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                throw new RuntimeException("unexpected exception");
            }
        }
        assertIsNotCompiled(m);

        m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class);
        m.invoke(null, start, stop, length, offset); // run once so all classes are loaded
        compile(m);
        assertIsCompiled(m);

        m.invoke(null, start, stop, length, offset);
        assertIsCompiled(m);
        try {
            m.invoke(null, stop, stop + 100, length, offset);
            throw new RuntimeException("should have thrown");
        } catch(InvocationTargetException e) {
            if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                throw new RuntimeException("unexpected exception");
            }
        }
        assertIsNotCompiled(m);

        m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class);
        m.invoke(null, start, stop, length, offset); // run once so all classes are loaded
        compile(m);

        m.invoke(null, start, stop, length, offset);
        assertIsCompiled(m);
        try {
            m.invoke(null, start, stop+1, length, offset);
            throw new RuntimeException("should have thrown");
        } catch(InvocationTargetException e) {
            if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                throw new RuntimeException("unexpected exception");
            }
        }
        assertIsNotCompiled(m);
    }

    private static void testOverflow(String method, long start, long stop, long length, long offset0, long offset1) throws Exception {
        Method m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class);
        m.invoke(null, start, stop, length, offset0);
        compile(m);

        m.invoke(null, start, stop, length, offset0);
        assertIsCompiled(m);
        try {
            m.invoke(null, start, stop, length, offset1);
            throw new RuntimeException("should have thrown");
        } catch(InvocationTargetException e) {
            if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                throw new RuntimeException("unexpected exception");
            }
        }
        assertIsNotCompiled(m);
    }

    private static void testConditional(String method, long start, long stop, long length, long offset0, long offset1, long start1, long stop1) throws Exception {
        Method m;

        if (start1 != start) {
            m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class, long.class, long.class);
            m.invoke(null, start, stop, length, offset0, start, stop);
            compile(m);

            m.invoke(null, start, stop, length, offset0, start, stop);
            assertIsCompiled(m);
            try {
                m.invoke(null, start, stop, length, offset1, start1-1, stop1);
                throw new RuntimeException("should have thrown");
            } catch(InvocationTargetException e) {
                if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                    throw new RuntimeException("unexpected exception");
                }
            }
            assertIsNotCompiled(m);
        }

        if (stop1 != stop) {
            m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class, long.class, long.class);
            m.invoke(null, start, stop, length, offset0, start, stop);
            compile(m);

            m.invoke(null, start, stop, length, offset0, start, stop);
            assertIsCompiled(m);
            try {
                m.invoke(null, start, stop, length, offset1, start1, stop1+1);
                throw new RuntimeException("should have thrown");
            } catch(InvocationTargetException e) {
                if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                    throw new RuntimeException("unexpected exception");
                }
            }
            assertIsNotCompiled(m);
        }

        m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class, long.class, long.class);
        m.invoke(null, start, stop, length, offset0, start, stop);
        compile(m);

        m.invoke(null, start, stop, length, offset0, start, stop);
        assertIsCompiled(m);

        m.invoke(null, start, stop, length, offset1, start1, stop1);
        assertIsCompiled(m);
    }

    public static void main(String[] args) throws Exception {

        test("testStridePosScalePos", 0, 100, 100, 0);

        test("testStrideNegScaleNeg", 0, 100, 100, 100);

        test("testStrideNegScalePos", 0, 100, 100, 0);

        test("testStridePosScaleNeg", 0, 100, 100, 99);

        test("testStridePosScalePosNotOne", 0, 100, 1090, 0);

        test("testStrideNegScaleNegNotOne", 0, 100, 1090, 1100);

        test("testStrideNegScalePosNotOne", 0, 100, 1090, 0);

        test("testStridePosScaleNegNotOne", 0, 100, 1090, 1089);

        long v = ((long)Integer.MAX_VALUE / 10000) * 250000;

        test("testStridePosNotOneScalePos", -v, v, v * 2, v);

        test("testStrideNegNotOneScaleNeg", -v, v, v * 2, v);

        test("testStrideNegNotOneScalePos", -v, v, v * 2, v);

        test("testStridePosNotOneScaleNeg", -v, v, v * 2, v-1);

        // offset causes overflow
        testOverflow("testStridePosScalePos", 0, 100, 100, 0, Long.MAX_VALUE - 50);
        testOverflow("testStrideNegScaleNeg", 0, 100, 100, 100, Long.MIN_VALUE + 50);
        testOverflow("testStrideNegScalePos", 0, 100, 100, 0, Long.MAX_VALUE - 50);
        testOverflow("testStridePosScaleNeg", 0, 100, 100, 99, Long.MIN_VALUE + 50);

        // no spurious deopt if the range check doesn't fail because not executed
        testConditional("testStridePosScalePosConditional", 0, 100, 100, 0, -50, 50, 100);
        testConditional("testStridePosScalePosConditional", 0, 100, Long.MAX_VALUE, 0, Long.MAX_VALUE - 50, 0, 50);
        testConditional("testStrideNegScaleNegConditional", 0, 100, 100, 100, 50, 0, 51);
        testConditional("testStrideNegScaleNegConditional", 0, 100, Long.MAX_VALUE, 100, Long.MIN_VALUE + 50, 52, 100);
        testConditional("testStrideNegScalePosConditional", 0, 100, 100, 0, -50, 50, 100);
        testConditional("testStrideNegScalePosConditional", 0, 100, Long.MAX_VALUE, 100, Long.MAX_VALUE - 50, 0, 50);
        testConditional("testStridePosScaleNegConditional", 0, 100, 100, 99, 50, 0, 51);
        testConditional("testStridePosScaleNegConditional", 0, 100, Long.MAX_VALUE, 99, Long.MIN_VALUE + 50, 52, 100);

        test("testStridePosScalePosInIntLoop", 0, 100, 100, 0);

        test("testStrideNegScaleNegInIntLoop", 0, 100, 100, 100);

        test("testStrideNegScalePosInIntLoop", 0, 100, 100, 0);

        test("testStridePosScaleNegInIntLoop", 0, 100, 100, 99);

        test("testStridePosScalePosNotOneInIntLoop", 0, 100, 1090, 0);

        test("testStrideNegScaleNegNotOneInIntLoop", 0, 100, 1090, 1100);

        test("testStrideNegScalePosNotOneInIntLoop", 0, 100, 1090, 0);

        test("testStridePosScaleNegNotOneInIntLoop", 0, 100, 1090, 1089);

        v = ((long)Integer.MAX_VALUE / 10000) * 9999;

        test("testStridePosNotOneScalePosInIntLoop", -v, v, v * 4, 2 * v);

        test("testStrideNegNotOneScaleNegInIntLoop", -v, v, v * 4, 2 * v);

        test("testStrideNegNotOneScalePosInIntLoop", -v, v, v * 4, 2 * v);

        test("testStridePosNotOneScaleNegInIntLoop", -v, v, v * 4, 2 * v - 1);

        // offset causes overflow
        testOverflow("testStridePosScalePosInIntLoop", 0, 100, 100, 0, Long.MAX_VALUE - 50);
        testOverflow("testStrideNegScaleNegInIntLoop", 0, 100, 100, 100, Long.MIN_VALUE + 50);
        testOverflow("testStrideNegScalePosInIntLoop", 0, 100, 100, 0, Long.MAX_VALUE - 50);
        testOverflow("testStridePosScaleNegInIntLoop", 0, 100, 100, 99, Long.MIN_VALUE + 50);
        // no spurious deopt if the range check doesn't fail because not executed
        testConditional("testStridePosScalePosConditionalInIntLoop", 0, 100, 100, 0, -50, 50, 100);
        testConditional("testStridePosScalePosConditionalInIntLoop", 0, 100, Long.MAX_VALUE, 0, Long.MAX_VALUE - 50, 0, 50);
        testConditional("testStrideNegScaleNegConditionalInIntLoop", 0, 100, 100, 100, 50, 0, 51);
        testConditional("testStrideNegScaleNegConditionalInIntLoop", 0, 100, Long.MAX_VALUE, 100, Long.MIN_VALUE + 50, 52, 100);
        testConditional("testStrideNegScalePosConditionalInIntLoop", 0, 100, 100, 0, -50, 50, 100);
        testConditional("testStrideNegScalePosConditionalInIntLoop", 0, 100, Long.MAX_VALUE, 100, Long.MAX_VALUE - 50, 0, 50);
        testConditional("testStridePosScaleNegConditionalInIntLoop", 0, 100, 100, 99, 50, 0, 51);
        testConditional("testStridePosScaleNegConditionalInIntLoop", 0, 100, Long.MAX_VALUE, 99, Long.MIN_VALUE + 50, 52, 100);

        test("testStridePosScalePosNotOneInIntLoop2", 0, 100, 1090, 0);

        test("testStrideNegScaleNegNotOneInIntLoop2", 0, 100, 1090, 1100);

        test("testStrideNegScalePosNotOneInIntLoop2", 0, 100, 1090, 0);

        test("testStridePosScaleNegNotOneInIntLoop2", 0, 100, 1090, 1089);

        {
            Method m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod("testStridePosScalePosInIntLoopOverflow", long.class, long.class, long.class, long.class);
            long stride = 1 << 14;
            long scale = 1 << 15;
            long offset = stride * scale * 4;
            long length = offset + stride * scale * 3 + 1;
            long stop = stride * 5;

            m.invoke(null, 0, stop, length, offset);
            compile(m);

            m.invoke(null, 0, stop, length, offset);
            // deoptimizes even though no range check fails
        }
        {
            Method m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod("testStridePosScalePosInIntLoopOverflow", long.class, long.class, long.class, long.class);
            long stride = 1 << 14;
            long scale = 1 << 15;
            long offset = stride * scale * 4;
            long length = offset + stride * scale * 3 + 1;
            long stop = stride * 5;

            m.invoke(null, 0, stop, length, offset);
            compile(m);

            offset = 0;
            stop = stride * 5;

            try {
                m.invoke(null, 0, stop, length, offset);
                throw new RuntimeException("should have thrown");
            } catch(InvocationTargetException e) {
                if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
                    throw new RuntimeException("unexpected exception");
                }
            }
            assertIsNotCompiled(m);
        }
    }

    public static void testStridePosScalePos(long start, long stop, long length, long offset) {
        final long scale = 1;
        final long stride = 1;
        for (long i = start; i < stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScaleNeg(long start, long stop, long length, long offset) {
        final long scale = -1;
        final long stride = 1;
        for (long i = stop; i > start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScalePos(long start, long stop, long length, long offset) {
        final long scale = 1;
        final long stride = 1;
        for (long i = stop-1; i >= start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScaleNeg(long start, long stop, long length, long offset) {
        final long scale = -1;
        final long stride = 1;
        for (long i = start; i < stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScalePosNotOne(long start, long stop, long length, long offset) {
        final long scale = 11;
        final long stride = 1;
        for (long i = start; i < stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScaleNegNotOne(long start, long stop, long length, long offset) {
        final long scale = -11;
        final long stride = 1;
        for (long i = stop; i > start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScalePosNotOne(long start, long stop, long length, long offset) {
        final long scale = 11;
        final long stride = 1;
        for (long i = stop-1; i >= start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScaleNegNotOne(long start, long stop, long length, long offset) {
        final long scale = -11;
        final long stride = 1;
        for (long i = start; i < stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosNotOneScalePos(long start, long stop, long length, long offset) {
        final long scale = 1;
        final long stride = Integer.MAX_VALUE / 10000;
        for (long i = start; i < stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegNotOneScaleNeg(long start, long stop, long length, long offset) {
        final long scale = -1;
        final long stride = Integer.MAX_VALUE / 10000;
        for (long i = stop; i > start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegNotOneScalePos(long start, long stop, long length, long offset) {
        final long scale = 1;
        final long stride = Integer.MAX_VALUE / 10000;
        for (long i = stop-1; i >= start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosNotOneScaleNeg(long start, long stop, long length, long offset) {
        final long scale = -1;
        final long stride = Integer.MAX_VALUE / 10000;
        for (long i = start; i < stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScalePosConditional(long start, long stop, long length, long offset, long start2, long stop2) {
        Preconditions.checkIndex(0, length, null);
        final long scale = 1;
        final long stride = 1;
        for (long i = start; i < stop; i += stride) {
            if (i >= start2 && i < stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStrideNegScaleNegConditional(long start, long stop, long length, long offset, long start2, long stop2) {
        final long scale = -1;
        final long stride = 1;
        for (long i = stop; i > start; i -= stride) {
            if (i >= start2 && i < stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStrideNegScalePosConditional(long start, long stop, long length, long offset, long start2, long stop2) {
        final long scale = 1;
        final long stride = 1;
        for (long i = stop-1; i >= start; i -= stride) {
            if (i >= start2 && i < stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStridePosScaleNegConditional(long start, long stop, long length, long offset, long start2, long stop2) {
        final long scale = -1;
        final long stride = 1;
        for (long i = start; i < stop; i += stride) {
            if (i >= start2 && i < stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    private static void checkInputs(long... inputs) {
        for (int i = 0; i < inputs.length; i++) {
            if ((long)((int)inputs[i]) != inputs[i]) {
                throw new RuntimeException("bad arguments");
            }
        }
    }

    public static void testStridePosScalePosInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = 1;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScaleNegInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = -1;
        final int stride = 1;
        for (int i = (int)stop; i > (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScalePosInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = 1;
        final int stride = 1;
        for (int i = (int)(stop-1); i >= (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScaleNegInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = -1;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScalePosNotOneInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = 11;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScaleNegNotOneInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = -11;
        final int stride = 1;
        for (int i = (int)stop; i > (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScalePosNotOneInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = 11;
        final int stride = 1;
        for (int i = (int)(stop-1); i >= (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScaleNegNotOneInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = -11;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosNotOneScalePosInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = 2;
        final int stride = Integer.MAX_VALUE / 10000;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegNotOneScaleNegInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = -2;
        final int stride = Integer.MAX_VALUE / 10000;
        for (int i = (int)stop; i > (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegNotOneScalePosInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = 2;
        final int stride = Integer.MAX_VALUE / 10000;
        for (int i = (int)(stop-1); i >= (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosNotOneScaleNegInIntLoop(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final long scale = -2;
        final int stride = Integer.MAX_VALUE / 10000;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScalePosConditionalInIntLoop(long start, long stop, long length, long offset, long start2, long stop2) {
        checkInputs(start, stop, start2, stop2);
        final long scale = 1;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            if (i >= (int)start2 && i < (int)stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStrideNegScaleNegConditionalInIntLoop(long start, long stop, long length, long offset, long start2, long stop2) {
        checkInputs(start, stop, start2, stop2);
        final long scale = -1;
        final int stride = 1;
        for (int i = (int)stop; i > (int)start; i -= stride) {
            if (i >= (int)start2 && i < (int)stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStrideNegScalePosConditionalInIntLoop(long start, long stop, long length, long offset, long start2, long stop2) {
        checkInputs(start, stop, start2, stop2);
        final long scale = 1;
        final int stride = 1;
        for (int i = (int)(stop-1); i >= (int)start; i -= stride) {
            if (i >= (int)start2 && i < (int)stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStridePosScaleNegConditionalInIntLoop(long start, long stop, long length, long offset, long start2, long stop2) {
        checkInputs(start, stop, start2, stop2);
        final long scale = -1;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            if (i >= (int)start2 && i < (int)stop2) {
                Preconditions.checkIndex(scale * i + offset, length, null);
            }
        }
    }

    public static void testStridePosScalePosNotOneInIntLoop2(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final int scale = 11;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScaleNegNotOneInIntLoop2(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final int scale = -11;
        final int stride = 1;
        for (int i = (int)stop; i > (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStrideNegScalePosNotOneInIntLoop2(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final int scale = 11;
        final int stride = 1;
        for (int i = (int)(stop-1); i >= (int)start; i -= stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScaleNegNotOneInIntLoop2(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final int scale = -11;
        final int stride = 1;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }

    public static void testStridePosScalePosInIntLoopOverflow(long start, long stop, long length, long offset) {
        checkInputs(start, stop);
        final int scale = 1 << 15;
        final int stride = 1 << 14;
        for (int i = (int)start; i < (int)stop; i += stride) {
            Preconditions.checkIndex(scale * i + offset, length, null);
        }
    }
}