/*
 * 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 8275908
 * @summary Quick test for the new WhiteBox methods of JDK-8275908
 *
 * @requires vm.compiler2.enabled & vm.compMode != "Xcomp"
 * @requires vm.opt.DeoptimizeALot != true
 *
 * @library /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 -XX:-UseOnStackReplacement -XX:-TieredCompilation
 *                   -XX:+UnlockExperimentalVMOptions -XX:PerMethodTrapLimit=100 -XX:PerBytecodeTrapLimit=4
 *                   -XX:TypeProfileLevel=0
 *                   -XX:+IgnoreUnrecognizedVMOptions -XX:-AlwaysIncrementalInline
 *                   -XX:CompileCommand=compileonly,compiler.uncommontrap.Decompile::uncommonTrap
 *                   -XX:CompileCommand=inline,compiler.uncommontrap.Decompile*::foo
 *                   compiler.uncommontrap.Decompile
 */

package compiler.uncommontrap;

import java.lang.reflect.Method;

import jdk.test.lib.Asserts;
import jdk.test.whitebox.WhiteBox;

public class Decompile {

    private static final WhiteBox WB = WhiteBox.getWhiteBox();
    // The number of deoptimizations after which a method will be made not-entrant
    private static final int PerBytecodeTrapLimit = WB.getIntxVMFlag("PerBytecodeTrapLimit").intValue();
    // The number of interpreter invocations after which a decompiled method will be re-compiled.
    private static final int Tier0InvokeNotifyFreq = (int)Math.pow(2, WB.getIntxVMFlag("Tier0InvokeNotifyFreqLog"));
    // VM builds without JVMCI like x86_32 call the bimorphic inlining trap just 'bimorphic'
    // while all the other builds with JVMCI call it 'bimorphic_or_optimized_type_check'.
    // Only builds with JVMCI have the "EnableJVMCI" flag.
    private static final boolean isJVMCISupported = (WB.getBooleanVMFlag("EnableJVMCI") != null);
    private static final String bimorphicTrapName = isJVMCISupported ? "bimorphic_or_optimized_type_check" : "bimorphic";

    static class Base {
        void foo() {}
    }
    static class X extends Base {
        void foo() {}
    }
    static class Y extends Base {
        void foo() {}
    }

    static void uncommonTrap(Base t) {
        t.foo();
    }

    private static void printCounters(Method uncommonTrap_m, int invocations) {
        System.out.println("-----------------------------------------------------------------");
        System.out.println("invocations=" + invocations + " " +
                           "method compiled=" + WB.isMethodCompiled(uncommonTrap_m) + " " +
                           "decompileCount=" + WB.getMethodDecompileCount(uncommonTrap_m) + "\n" +
                           "trapCount=" + WB.getMethodTrapCount(uncommonTrap_m) + " " +
                           "trapCount(class_check)=" + WB.getMethodTrapCount(uncommonTrap_m, "class_check") + " " +
                           "trapCount(" + bimorphicTrapName + ")=" +
                           WB.getMethodTrapCount(uncommonTrap_m, bimorphicTrapName) + "\n" +
                           "globalDeoptCount=" + WB.getDeoptCount() + " " +
                           "globalDeoptCount(class_check)=" + WB.getDeoptCount("class_check", null) + " " +
                           "globalDeoptCount(" + bimorphicTrapName + ")=" +
                           WB.getDeoptCount(bimorphicTrapName, null));
        System.out.println("-----------------------------------------------------------------");
    }

    private static void check(Method uncommonTrap_m, int invocations, boolean isCompiled, int decompileCount,
                              int trapCount, int trapCountClassCheck, int trapCountBimorphic,
                              int deoptCount, int deoptCountClassCheck, int deoptCountBimorphic) {

        printCounters(uncommonTrap_m, invocations);

        Asserts.assertEQ(isCompiled, WB.isMethodCompiled(uncommonTrap_m),
                         "Wrong compilation status.");
        Asserts.assertEQ(decompileCount, WB.getMethodDecompileCount(uncommonTrap_m),
                         "Wrong number of decompilations.");
        Asserts.assertEQ(trapCount, WB.getMethodTrapCount(uncommonTrap_m),
                         "Wrong number of traps.");
        Asserts.assertEQ(trapCountClassCheck, WB.getMethodTrapCount(uncommonTrap_m, "class_check"),
                         "Wrong number of traps.");
        Asserts.assertEQ(trapCountBimorphic, WB.getMethodTrapCount(uncommonTrap_m, bimorphicTrapName),
                         "Wrong number of traps.");
        Asserts.assertEQ(deoptCount, WB.getDeoptCount(),
                         "Wrong number of deoptimizations.");
        Asserts.assertEQ(deoptCountClassCheck, WB.getDeoptCount("class_check", null),
                         "Wrong number of class_check deoptimizations.");
        Asserts.assertEQ(deoptCountBimorphic, WB.getDeoptCount(bimorphicTrapName, null),
                         "Wrong number of " + bimorphicTrapName + "deoptimizations.");
    }
    public static void main(String[] args) throws Exception {

        // Get a handle of the test method for usage with the WhiteBox API.
        Method uncommonTrap_m = Decompile.class
            .getDeclaredMethod("uncommonTrap", new Class[] { Base.class });

        int invocations = 0;
        Base b = new Base();
        // This is a little tricky :) We have to define 'x' already here otherwise
        // the class 'X' won't be loaded and 'uncommonTrap()' will be compiled without
        // a class check but a CHA dependency that class 'B' has no subtypes.
        X x = new X();
        Y y = new Y();

        // Warmup and compile with an object of type 'Base' as receiver, but don't invoke compiled code.
        while(!WB.isMethodCompiled(uncommonTrap_m)) {
            invocations++;
            uncommonTrap(b);
        }
        check(uncommonTrap_m, invocations, true /* is_compiled */, 0 /* decompileCount */,
              0 /* trapCount  */, 0 /* trapCountClassCheck  */, 0 /* trapCountBimorphic  */,
              0 /* deoptCount */, 0 /* deoptCountClassCheck */, 0 /* deoptCountBimorphic */);

        // Invoke compiled code 'PerBytecodeTrapLimit' times with an receiver object of type 'X'.
        // This should deoptimize 'PerBytecodeTrapLimit' times and finally decompile the method.
        for (int i = 0; i < PerBytecodeTrapLimit; i++) {
            invocations++;
            uncommonTrap(x);
        }
        check(uncommonTrap_m, invocations, false /* is_compiled */, 1 /* decompileCount */,
              PerBytecodeTrapLimit /* trapCount  */, PerBytecodeTrapLimit /* trapCountClassCheck  */, 0 /* trapCountBimorphic  */,
              PerBytecodeTrapLimit /* deoptCount */, PerBytecodeTrapLimit /* deoptCountClassCheck */, 0 /* deoptCountBimorphic */);

        // Invoke the method 'Tier0InvokeNotifyFreq' more times with an receiver object of type 'X'.
        // This should re-compile the method again with bimorphic inlining for receiver types 'Base' and 'X'.
        for (int i = 0; i < Tier0InvokeNotifyFreq; i++) {
            invocations++;
            uncommonTrap(x);
        }
        check(uncommonTrap_m, invocations, true /* is_compiled */, 1 /* decompileCount */,
              PerBytecodeTrapLimit /* trapCount  */, PerBytecodeTrapLimit /* trapCountClassCheck  */, 0 /* trapCountBimorphic  */,
              PerBytecodeTrapLimit /* deoptCount */, PerBytecodeTrapLimit /* deoptCountClassCheck */, 0 /* deoptCountBimorphic */);

        // Invoke compiled code 'PerBytecodeTrapLimit' times with an receiver object of type 'Y'.
        // This should deoptimize 'PerBytecodeTrapLimit' times and finally decompile the method.
        for (int i = 0; i < PerBytecodeTrapLimit; i++) {
            invocations++;
            uncommonTrap(y);
        }
        check(uncommonTrap_m, invocations, false /* is_compiled */, 2 /* decompileCount */,
              2*PerBytecodeTrapLimit /* trapCount  */, PerBytecodeTrapLimit /* trapCountClassCheck  */, PerBytecodeTrapLimit /* trapCountBimorphic  */,
              2*PerBytecodeTrapLimit /* deoptCount */, PerBytecodeTrapLimit /* deoptCountClassCheck */, PerBytecodeTrapLimit /* deoptCountBimorphic */);
    }
}