/*
 * Copyright Amazon.com Inc. 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 8173585
 * @summary Test intrinsification of StringLatin1.indexOf(char). Note that
 * differing code paths are taken contingent upon the length of the input String.
 * Hence we must test against differing string lengths in order to validate
 * correct functionality. We also ensure the strings are long enough to trigger
 * the looping conditions of the individual code paths.
 *
 * Run with varing levels of AVX and SSE support, also without the intrinsic at all
 *
 * @requires vm.compiler2.enabled
 * @library /compiler/patches /test/lib /
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *                   -Xbatch compiler.intrinsics.string.TestStringLatin1IndexOfChar
 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *                   -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_indexOfL_char compiler.intrinsics.string.TestStringLatin1IndexOfChar
 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *                   -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseAVX=0 compiler.intrinsics.string.TestStringLatin1IndexOfChar
 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *                   -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseAVX=1 compiler.intrinsics.string.TestStringLatin1IndexOfChar
 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *                   -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseAVX=2 compiler.intrinsics.string.TestStringLatin1IndexOfChar
 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *                   -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseAVX=3 compiler.intrinsics.string.TestStringLatin1IndexOfChar
 */

package compiler.intrinsics.string;

import jdk.test.lib.Asserts;
import jdk.test.whitebox.WhiteBox;
import java.lang.reflect.Method;
import compiler.whitebox.CompilerWhiteBoxTest;

public class TestStringLatin1IndexOfChar{
    private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
    private final static int MAX_LENGTH = 2048;//future proof for AVX-512 instructions

    public static void main(String[] args) throws Exception {
        Method methodFindOneItem    = TestStringLatin1IndexOfChar.class.getDeclaredMethod("findOneItem");
        Method methodWithOffsetTest = TestStringLatin1IndexOfChar.class.getDeclaredMethod("withOffsetTest");
        Method methodTestEmpty      = TestStringLatin1IndexOfChar.class.getDeclaredMethod("testEmpty");
        Asserts.assertNotNull(methodFindOneItem);
        Asserts.assertNotNull(methodWithOffsetTest);
        Asserts.assertNotNull(methodTestEmpty);

        // Warmup - profiling must inline the methods
        for (int i = 0; i < 10; ++i) {
            findOneItem();
            withOffsetTest();
            testEmpty();
        }

        // Compile
        WHITE_BOX.enqueueMethodForCompilation(methodFindOneItem,    CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);
        WHITE_BOX.enqueueMethodForCompilation(methodWithOffsetTest, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);
        WHITE_BOX.enqueueMethodForCompilation(methodTestEmpty,      CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);

        // Run compiled method
        for (int i = 0; i < 10; ++i) {
            findOneItem();
            withOffsetTest();
            testEmpty();
        }
    }

    private static void testEmpty(){
        Asserts.assertEQ("".indexOf('a'), -1);
    }

    private final static char SEARCH_CHAR = 'z';
    private final static char INVERLEAVING_CHAR = 'a';
    private final static char MISSING_CHAR = 'd';

    private static void findOneItem(){
        //test strings of varying length ensuring that for all lengths one instance of the
        //search char can be found. We check what happens when the search character is in
        //each position of the search string (including first and last positions)
        for(int strLength : new int[]{1, 15, 31, 32, 79}){
            for(int searchPos = 0; searchPos < strLength; searchPos++){
                String totest = makeOneItemStringLatin1(strLength, searchPos);

                int intri = totest.indexOf(SEARCH_CHAR);
                int nonintri = indexOfCharNonIntrinsic(totest, SEARCH_CHAR, 0);
                Asserts.assertEQ(intri, nonintri);
            }
        }
    }

    private static String makeOneItemStringLatin1(int length, int searchPos){
        StringBuilder sb = new StringBuilder(length);

        for(int n =0; n < length; n++){
            sb.append(searchPos==n?SEARCH_CHAR:INVERLEAVING_CHAR);
        }

        return sb.toString();
    }

    private static void withOffsetTest(){
        //progressivly move through string checking indexes and starting offset correctly processed
        //string is of form azaza, aazaazaa, aaazaaazaaa, etc
        //we find n s.t. maxlength = (n*3) + 2
        int maxaInstances = (MAX_LENGTH-2)/3;

        for(int aInstances = 5; aInstances < MAX_LENGTH; aInstances++){
            String totest = makeWithOffsetStringLatin1(aInstances);

            int startoffset;
            {
                int intri = totest.indexOf(SEARCH_CHAR);
                int nonintri = indexOfCharNonIntrinsic(totest, SEARCH_CHAR, 0);

                Asserts.assertEQ(intri, nonintri);
                startoffset = intri+1;
            }

            {
                int intri = totest.indexOf(SEARCH_CHAR, startoffset);
                int nonintri = indexOfCharNonIntrinsic(totest, SEARCH_CHAR, startoffset);

                Asserts.assertEQ(intri, nonintri);
                startoffset = intri+1;
            }

            Asserts.assertEQ(totest.indexOf(SEARCH_CHAR, startoffset), -1);//only two SEARCH_CHAR per string
            Asserts.assertEQ(totest.indexOf(MISSING_CHAR), -1);
        }
    }

    private static String makeWithOffsetStringLatin1(int aInstances){
        StringBuilder sb = new StringBuilder((aInstances*3) + 2);
        for(int n =0; n < aInstances; n++){
            sb.append(INVERLEAVING_CHAR);
        }

        sb.append(SEARCH_CHAR);

        for(int n =0; n < aInstances; n++){
            sb.append(INVERLEAVING_CHAR);
        }

        sb.append(SEARCH_CHAR);

        for(int n =0; n < aInstances; n++){
            sb.append(INVERLEAVING_CHAR);
        }
        return sb.toString();
    }

    private static int indexOfCharNonIntrinsic(String value, int ch, int fromIndex) {
        //non intrinsic version of indexOfChar
        byte c = (byte)ch;
        for (int i = fromIndex; i < value.length(); i++) {
            if (value.charAt(i) == c) {
               return i;
            }
        }
        return -1;
    }
}
