/*
 * Copyright (c) 2023, 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
 * @bug 8298935
 * @summary Test forced vectorization, and check IR for vector instructions
 * @requires vm.compiler2.enabled
 * @library /test/lib /
 * @run driver compiler.vectorization.TestOptionVectorizeIR
 */

package compiler.vectorization;
import compiler.lib.ir_framework.*;

public class TestOptionVectorizeIR {
    static int RANGE = 1024*2;
    static int ITER  = 100;
    int[] gold1 = new int[RANGE];
    int[] gold2 = new int[RANGE];
    int[] gold3 = new int[RANGE];
    int[] gold4 = new int[RANGE];
    int[] gold5 = new int[RANGE];
    int[] gold6 = new int[RANGE];

    long[] gold10 = new long[RANGE];
    long[] gold11 = new long[RANGE];
    long[] gold12 = new long[RANGE];
    long[] gold13 = new long[RANGE];

    short[] gold20 = new short[RANGE];
    short[] gold21 = new short[RANGE];
    short[] gold22 = new short[RANGE];
    short[] gold23 = new short[RANGE];

    byte[] gold30 = new byte[RANGE];
    byte[] gold31 = new byte[RANGE];
    byte[] gold32 = new byte[RANGE];
    byte[] gold33 = new byte[RANGE];

    char[] gold40 = new char[RANGE];
    char[] gold41 = new char[RANGE];
    char[] gold42 = new char[RANGE];
    char[] gold43 = new char[RANGE];

    float[] gold50 = new float[RANGE];
    float[] gold51 = new float[RANGE];
    float[] gold52 = new float[RANGE];
    float[] gold53 = new float[RANGE];

    double[] gold60 = new double[RANGE];
    double[] gold61 = new double[RANGE];
    double[] gold62 = new double[RANGE];
    double[] gold63 = new double[RANGE];

    public static void main(String args[]) {
        TestFramework.runWithFlags("-XX:CompileCommand=option,compiler.vectorization.TestOptionVectorizeIR::test*,Vectorize");
    }

    TestOptionVectorizeIR() {
        // compute the gold standard in interpreter mode
        // test1
        test1(gold1);
        // test2
        test1(gold2);
        test2(gold2);
        // test3
        test1(gold3);
        test3(gold3, 2, 3);
        // test4
        test1(gold4);
        test4(gold4);
        // test5
        test1(gold5);
        test5(gold5);
        // test6
        test1(gold6);
        test6(gold6);

        // long
        init(gold10);
        test10(gold10);
        init(gold11);
        test11(gold11);
        init(gold12);
        test12(gold12);
        init(gold13);
        test13(gold13);

        // short
        init(gold20);
        test20(gold20);
        init(gold21);
        test21(gold21);
        init(gold22);
        test22(gold22);
        init(gold23);
        test23(gold23);

        // byte
        init(gold30);
        test30(gold30);
        init(gold31);
        test31(gold31);
        init(gold32);
        test32(gold32);
        init(gold33);
        test33(gold33);

        // char
        init(gold40);
        test40(gold40);
        init(gold41);
        test41(gold41);
        init(gold42);
        test42(gold42);
        init(gold43);
        test43(gold43);

        // float
        init(gold50);
        test50(gold50);
        init(gold51);
        test51(gold51);
        init(gold52);
        test52(gold52);
        init(gold53);
        test53(gold53);

        // double
        init(gold60);
        test60(gold60);
        init(gold61);
        test61(gold61);
        init(gold62);
        test62(gold62);
        init(gold63);
        test63(gold63);
    }

    @Run(test = "test1")
    @Warmup(100)
    public void runTest1() {
        int[] data = new int[RANGE];
        test1(data);
        verify("test1", data, gold1);
    }

    @Run(test = "test2")
    @Warmup(100)
    public void runTest2() {
        int[] data = new int[RANGE];
        test1(data);
        test2(data);
        verify("test2", data, gold2);
    }

    @Run(test = "test3")
    @Warmup(100)
    public void runTest3() {
        int[] data = new int[RANGE];
        test1(data);
        test3(data, 2, 3);
        verify("test3", data, gold3);
    }

    @Run(test = "test4")
    @Warmup(100)
    public void runTest4() {
        int[] data = new int[RANGE];
        test1(data);
        test4(data);
        verify("test4", data, gold4);
    }

    @Run(test = "test5")
    @Warmup(100)
    public void runTest5() {
        int[] data = new int[RANGE];
        test1(data);
        test5(data);
        verify("test5", data, gold5);
    }

    @Run(test = "test6")
    @Warmup(100)
    public void runTest6() {
        int[] data = new int[RANGE];
        test1(data);
        test6(data);
        verify("test6", data, gold6);
    }

    @Test
    static void test1(int[] data) {
       for (int j = 0; j < RANGE; j++) {
           // Vectorizes even if it is not forced
           data[j] = j;
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_I, "> 0", IRNode.ADD_VI, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test2(int[] data) {
       for (int j = 0; j < RANGE - 1; j++) {
           // Only vectorizes if forced, because of offset by 1
           data[j] = data[j] + data[j + 1];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_I, "> 0", IRNode.REPLICATE_I, "> 0", IRNode.ADD_VI, "> 0", IRNode.MUL_VI, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test3(int[] data, int A, int B) {
       for (int j = 0; j < RANGE - 1; j++) {
           // Only vectorizes if forced, because of offset by 1
           data[j] = A * data[j] + B * data[j + 1];
       }
    }

    @Test
    static void test4(int[] data) {
       for (int j = 0; j < RANGE - 1; j++) {
           // write forward -> cyclic dependency -> cannot vectorize
           // independent(s1, s2) for adjacent loads should detect this
           data[j + 1] = data[j];
       }
    }

    @Test
    static void test5(int[] data) {
       for (int j = 0; j < RANGE - 3; j++) {
           // write forward -> cyclic dependency -> cannot vectorize
           // independent(s1, s2) for adjacent loads cannot detect this
           // Checks with memory_alignment are disabled via compile option
           data[j + 2] = data[j];
       }
    }

    @Test
    static void test6(int[] data) {
       for (int j = 0; j < RANGE - 3; j++) {
           // write forward -> cyclic dependency -> cannot vectorize
           // independent(s1, s2) for adjacent loads cannot detect this
           // Checks with memory_alignment are disabled via compile option
           data[j + 3] = data[j];
       }
    }

    // ------------------------- Long -----------------------------

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_L, "> 0", IRNode.ADD_VL, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test10(long[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 2];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_L, "> 0", IRNode.ADD_VL, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test11(long[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 1];
       }
    }

    @Test
    static void test12(long[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 1];
       }
    }

    @Test
    static void test13(long[] data) {
       // 128-bit vectors -> can vectorize because only 2 elements
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 2];
       }
    }

    @Run(test = "test10")
    @Warmup(100)
    public void runTest10() {
        long[] data = new long[RANGE];
        init(data);
        test10(data);
        verify("test10", data, gold10);
    }

    @Run(test = "test11")
    @Warmup(100)
    public void runTest11() {
        long[] data = new long[RANGE];
        init(data);
        test11(data);
        verify("test11", data, gold11);
    }

    @Run(test = "test12")
    @Warmup(100)
    public void runTest12() {
        long[] data = new long[RANGE];
        init(data);
        test12(data);
        verify("test12", data, gold12);
    }

    @Run(test = "test13")
    @Warmup(100)
    public void runTest13() {
        long[] data = new long[RANGE];
        init(data);
        test13(data);
        verify("test13", data, gold13);
    }


    // ------------------------- Short -----------------------------

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_S, "> 0", IRNode.ADD_VS, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test20(short[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 2];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_S, "> 0", IRNode.ADD_VS, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test21(short[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 1];
       }
    }

    @Test
    static void test22(short[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 1];
       }
    }

    @Test
    static void test23(short[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 2];
       }
    }

    @Run(test = "test20")
    @Warmup(100)
    public void runTest20() {
        short[] data = new short[RANGE];
        init(data);
        test20(data);
        verify("test20", data, gold20);
    }

    @Run(test = "test21")
    @Warmup(100)
    public void runTest21() {
        short[] data = new short[RANGE];
        init(data);
        test21(data);
        verify("test21", data, gold21);
    }

    @Run(test = "test22")
    @Warmup(100)
    public void runTest22() {
        short[] data = new short[RANGE];
        init(data);
        test22(data);
        verify("test22", data, gold22);
    }

    @Run(test = "test23")
    @Warmup(100)
    public void runTest23() {
        short[] data = new short[RANGE];
        init(data);
        test23(data);
        verify("test23", data, gold23);
    }


    // ------------------------- Byte -----------------------------

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_B, "> 0", IRNode.ADD_VB, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test30(byte[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 2];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_B, "> 0", IRNode.ADD_VB, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test31(byte[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 1];
       }
    }

    @Test
    static void test32(byte[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 1];
       }
    }

    @Test
    static void test33(byte[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 2];
       }
    }

    @Run(test = "test30")
    @Warmup(100)
    public void runTest30() {
        byte[] data = new byte[RANGE];
        init(data);
        test30(data);
        verify("test30", data, gold30);
    }

    @Run(test = "test31")
    @Warmup(100)
    public void runTest31() {
        byte[] data = new byte[RANGE];
        init(data);
        test31(data);
        verify("test31", data, gold31);
    }

    @Run(test = "test32")
    @Warmup(100)
    public void runTest32() {
        byte[] data = new byte[RANGE];
        init(data);
        test32(data);
        verify("test32", data, gold32);
    }

    @Run(test = "test33")
    @Warmup(100)
    public void runTest33() {
        byte[] data = new byte[RANGE];
        init(data);
        test33(data);
        verify("test33", data, gold33);
    }


    // ------------------------- Char -----------------------------

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_C, "> 0", IRNode.ADD_VS, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test40(char[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 2];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_C, "> 0", IRNode.ADD_VS, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test41(char[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 1];
       }
    }

    @Test
    static void test42(char[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 1];
       }
    }

    @Test
    static void test43(char[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 2];
       }
    }

    @Run(test = "test40")
    @Warmup(100)
    public void runTest40() {
        char[] data = new char[RANGE];
        init(data);
        test40(data);
        verify("test40", data, gold40);
    }

    @Run(test = "test41")
    @Warmup(100)
    public void runTest41() {
        char[] data = new char[RANGE];
        init(data);
        test41(data);
        verify("test41", data, gold41);
    }

    @Run(test = "test42")
    @Warmup(100)
    public void runTest42() {
        char[] data = new char[RANGE];
        init(data);
        test42(data);
        verify("test42", data, gold42);
    }

    @Run(test = "test43")
    @Warmup(100)
    public void runTest43() {
        char[] data = new char[RANGE];
        init(data);
        test43(data);
        verify("test43", data, gold43);
    }

    // ------------------------- Float -----------------------------

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_F, "> 0", IRNode.ADD_VF, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test50(float[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 2];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_F, "> 0", IRNode.ADD_VF, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test51(float[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 1];
       }
    }

    @Test
    static void test52(float[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 1];
       }
    }

    @Test
    static void test53(float[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 2];
       }
    }

    @Run(test = "test50")
    @Warmup(100)
    public void runTest50() {
        float[] data = new float[RANGE];
        init(data);
        test50(data);
        verify("test50", data, gold50);
    }

    @Run(test = "test51")
    @Warmup(100)
    public void runTest51() {
        float[] data = new float[RANGE];
        init(data);
        test51(data);
        verify("test51", data, gold51);
    }

    @Run(test = "test52")
    @Warmup(100)
    public void runTest52() {
        float[] data = new float[RANGE];
        init(data);
        test52(data);
        verify("test52", data, gold52);
    }

    @Run(test = "test53")
    @Warmup(100)
    public void runTest53() {
        float[] data = new float[RANGE];
        init(data);
        test53(data);
        verify("test53", data, gold53);
    }

    // ------------------------- Double -----------------------------

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_D, "> 0", IRNode.ADD_VD, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test60(double[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 2];
       }
    }

    @Test
    @IR(counts = {IRNode.LOAD_VECTOR_D, "> 0", IRNode.ADD_VD, "> 0", IRNode.STORE_VECTOR, "> 0"},
        applyIf = {"AlignVector", "false"},
        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
    static void test61(double[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j + 1];
       }
    }

    @Test
    static void test62(double[] data) {
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 1];
       }
    }

    @Test
    static void test63(double[] data) {
       // 128-bit vectors -> can vectorize because only 2 elements
       for (int j = 2; j < RANGE - 2; j++) {
           data[j] += data[j - 2];
       }
    }

    @Run(test = "test60")
    @Warmup(100)
    public void runTest60() {
        double[] data = new double[RANGE];
        init(data);
        test60(data);
        verify("test60", data, gold60);
    }

    @Run(test = "test61")
    @Warmup(100)
    public void runTest61() {
        double[] data = new double[RANGE];
        init(data);
        test61(data);
        verify("test61", data, gold61);
    }

    @Run(test = "test62")
    @Warmup(100)
    public void runTest62() {
        double[] data = new double[RANGE];
        init(data);
        test62(data);
        verify("test62", data, gold62);
    }

    @Run(test = "test63")
    @Warmup(100)
    public void runTest63() {
        double[] data = new double[RANGE];
        init(data);
        test63(data);
        verify("test63", data, gold63);
    }

    static void init(long[] data) {
       for (int j = 0; j < RANGE; j++) {
           data[j] = j;
       }
    }

    static void init(short[] data) {
       for (int j = 0; j < RANGE; j++) {
           data[j] = (short)j;
       }
    }

    static void init(byte[] data) {
       for (int j = 0; j < RANGE; j++) {
           data[j] = (byte)j;
       }
    }

    static void init(char[] data) {
       for (int j = 0; j < RANGE; j++) {
           data[j] = (char)j;
       }
    }


    static void init(float[] data) {
       for (int j = 0; j < RANGE; j++) {
           data[j] = j;
       }
    }


    static void init(double[] data) {
       for (int j = 0; j < RANGE; j++) {
           data[j] = j;
       }
    }

    static void verify(String name, int[] data, int[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }

    static void verify(String name, long[] data, long[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }

    static void verify(String name, short[] data, short[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }

    static void verify(String name, byte[] data, byte[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }

    static void verify(String name, char[] data, char[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }

    static void verify(String name, float[] data, float[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }

    static void verify(String name, double[] data, double[] gold) {
        for (int i = 0; i < RANGE; i++) {
            if (data[i] != gold[i]) {
                throw new RuntimeException(" Invalid " + name + " result: data[" + i + "]: " + data[i] + " != " + gold[i]);
            }
        }
    }
}