/*
 * Copyright (c) 2012, 2016, 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.
 */


package org.graalvm.compiler.core.test.inlining;

import static org.graalvm.compiler.test.SubprocessUtil.getVMCommandLine;
import static org.graalvm.compiler.test.SubprocessUtil.java;
import static org.graalvm.compiler.test.SubprocessUtil.withoutDebuggerArguments;

import jdk.vm.ci.meta.ResolvedJavaMethod;
import org.graalvm.compiler.core.test.GraalCompilerTest;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.DebugDumpScope;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.nodes.DeoptimizeNode;
import org.graalvm.compiler.nodes.InvokeNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
import org.graalvm.compiler.nodes.StructuredGraph.Builder;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import org.graalvm.compiler.nodes.java.TypeSwitchNode;
import org.graalvm.compiler.phases.OptimisticOptimizations;
import org.graalvm.compiler.phases.PhaseSuite;
import org.graalvm.compiler.phases.common.CanonicalizerPhase;
import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
import org.graalvm.compiler.phases.common.inlining.InliningPhase;
import org.graalvm.compiler.phases.tiers.HighTierContext;
import org.graalvm.compiler.test.SubprocessUtil;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

public class PolymorphicInliningTest extends GraalCompilerTest {

    @Test
    public void testInSubprocess() throws InterruptedException, IOException {
        String recursionPropName = getClass().getName() + ".recursion";
        if (Boolean.getBoolean(recursionPropName)) {
            testPolymorphicInlining();
            testPolymorphicNotInlining();
            testMegamorphicInlining();
            testMegamorphicNotInlining();
        } else {
            List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
            NotInlinableSubClass.class.getCanonicalName();
            vmArgs.add("-XX:CompileCommand=dontinline,org/graalvm/compiler/core/test/inlining/PolymorphicInliningTest$NotInlinableSubClass.publicOverriddenMethod");
            vmArgs.add("-D" + recursionPropName + "=true");
            SubprocessUtil.Subprocess proc = java(vmArgs, "com.oracle.mxtool.junit.MxJUnitWrapper", getClass().getName());
            if (proc.exitCode != 0) {
                Assert.fail(String.format("non-zero exit code %d for command:%n%s", proc.exitCode, proc));
            }
        }
    }

    public int polymorphicCallsite(SuperClass receiver) {
        return receiver.publicOverriddenMethod();
    }

    public void testPolymorphicInlining() {
        for (int i = 0; i < 10000; i++) {
            if (i % 2 == 0) {
                polymorphicCallsite(Receivers.subClassA);
            } else {
                polymorphicCallsite(Receivers.subClassB);
            }
        }
        StructuredGraph graph = getGraph("polymorphicCallsite", false);
        // This callsite should be inlined with a TypeCheckedInliningViolated deoptimization.
        assertTrue(getNodeCount(graph, InvokeNode.class) == 0);
        assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 1);
        assertTrue(getNodeCount(graph, DeoptimizeNode.class) >= 1);
    }

    
This snippet is identical to polymorphicCallsite(SuperClass), and is for avoiding interference of the receiver type profile from different unit tests.
/** * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding * interference of the receiver type profile from different unit tests. */
public int polymorphicCallsite1(SuperClass receiver) { return receiver.publicOverriddenMethod(); } public void testPolymorphicNotInlining() { for (int i = 0; i < 10000; i++) { if (i % 2 == 0) { polymorphicCallsite1(Receivers.subClassA); } else { polymorphicCallsite1(Receivers.notInlinableSubClass); } } StructuredGraph graph = getGraph("polymorphicCallsite1", false); // This callsite should not be inlined due to one of the potential callee method is not // inlinable. assertTrue(getNodeCount(graph, InvokeNode.class) == 1); assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 0); }
This snippet is identical to polymorphicCallsite(SuperClass), and is for avoiding interference of the receiver type profile from different unit tests.
/** * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding * interference of the receiver type profile from different unit tests. */
public int polymorphicCallsite2(SuperClass receiver) { return receiver.publicOverriddenMethod(); } public void testMegamorphicInlining() { // Construct a receiver type profile that exceeds the max type width (by default 8 in JVMCI, // specified by -XX:TypeProfileWidth). for (int i = 0; i < 2000; i++) { // Ensure the following receiver type is within the type profile. polymorphicCallsite2(Receivers.subClassA); } for (int i = 0; i < 10000; i++) { switch (i % 20) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // Probability: 40% // Ensure the probability is greater than // GraalOptions.MegamorphicInliningMinMethodProbability (by default 0.33D); polymorphicCallsite2(Receivers.subClassA); break; case 8: polymorphicCallsite2(Receivers.subClassB); break; case 9: polymorphicCallsite2(Receivers.subClassC); break; case 10: polymorphicCallsite2(Receivers.subClassD); break; case 11: polymorphicCallsite2(Receivers.subClassE); break; case 12: polymorphicCallsite2(Receivers.subClassF); break; case 13: polymorphicCallsite2(Receivers.subClassG); break; case 14: polymorphicCallsite2(Receivers.subClassH); break; default: // Probability: 25% polymorphicCallsite2(Receivers.notInlinableSubClass); break; } } StructuredGraph graph = getGraph("polymorphicCallsite2", false); // This callsite should be inlined with a fallback invocation. assertTrue(getNodeCount(graph, InvokeNode.class) == 1); assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 1); }
This snippet is identical to polymorphicCallsite(SuperClass), and is for avoiding interference of the receiver type profile from different unit tests.
/** * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding * interference of the receiver type profile from different unit tests. */
public int polymorphicCallsite3(SuperClass receiver) { return receiver.publicOverriddenMethod(); } public void testMegamorphicNotInlining() { for (int i = 0; i < 10000; i++) { switch (i % 10) { case 0: case 1: polymorphicCallsite3(Receivers.subClassA); break; case 2: polymorphicCallsite3(Receivers.subClassB); break; case 3: polymorphicCallsite3(Receivers.subClassC); break; case 4: polymorphicCallsite3(Receivers.subClassD); break; case 5: polymorphicCallsite3(Receivers.subClassE); break; case 6: polymorphicCallsite3(Receivers.subClassF); break; case 7: polymorphicCallsite3(Receivers.subClassG); break; case 8: polymorphicCallsite3(Receivers.subClassH); break; default: polymorphicCallsite3(Receivers.notInlinableSubClass); break; } } StructuredGraph graph = getGraph("polymorphicCallsite3", false); // This callsite should not be inlined due to non of the potential callee method exceeds the // probability specified by GraalOptions.MegamorphicInliningMinMethodProbability. assertTrue(getNodeCount(graph, InvokeNode.class) == 1); assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 0); } @SuppressWarnings("try") private StructuredGraph getGraph(final String snippet, final boolean eagerInfopointMode) { DebugContext debug = getDebugContext(); try (DebugContext.Scope s = debug.scope("InliningTest", new DebugDumpScope(snippet, true))) { ResolvedJavaMethod method = getResolvedJavaMethod(snippet); Builder builder = builder(method, AllowAssumptions.YES, debug); StructuredGraph graph = eagerInfopointMode ? parse(builder, getDebugGraphBuilderSuite()) : parse(builder, getEagerGraphBuilderSuite()); try (DebugContext.Scope s2 = debug.scope("Inlining", graph)) { PhaseSuite<HighTierContext> graphBuilderSuite = eagerInfopointMode ? getCustomGraphBuilderSuite(GraphBuilderConfiguration.getDefault(getDefaultGraphBuilderPlugins()).withFullInfopoints(true)) : getDefaultGraphBuilderSuite(); HighTierContext context = new HighTierContext(getProviders(), graphBuilderSuite, OptimisticOptimizations.ALL); debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph"); new CanonicalizerPhase().apply(graph, context); new InliningPhase(new CanonicalizerPhase()).apply(graph, context); debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph"); new CanonicalizerPhase().apply(graph, context); new DeadCodeEliminationPhase().apply(graph); return graph; } } catch (Throwable e) { throw debug.handle(e); } } private static int getNodeCount(StructuredGraph graph, Class<? extends Node> nodeClass) { return graph.getNodes().filter(nodeClass).count(); } private static final class Receivers { static final SubClassA subClassA = new SubClassA(); static final SubClassB subClassB = new SubClassB(); static final SubClassC subClassC = new SubClassC(); static final SubClassD subClassD = new SubClassD(); static final SubClassE subClassE = new SubClassE(); static final SubClassF subClassF = new SubClassF(); static final SubClassG subClassG = new SubClassG(); static final SubClassH subClassH = new SubClassH(); static final NotInlinableSubClass notInlinableSubClass = new NotInlinableSubClass(); } private abstract static class SuperClass { public abstract int publicOverriddenMethod(); } private static class SubClassA extends SuperClass { @Override public int publicOverriddenMethod() { return 'A'; } } private static class SubClassB extends SuperClass { @Override public int publicOverriddenMethod() { return 'B'; } } private static class SubClassC extends SuperClass { @Override public int publicOverriddenMethod() { return 'C'; } } private static class SubClassD extends SuperClass { @Override public int publicOverriddenMethod() { return 'D'; } } private static class SubClassE extends SuperClass { @Override public int publicOverriddenMethod() { return 'E'; } } private static class SubClassF extends SuperClass { @Override public int publicOverriddenMethod() { return 'F'; } } private static class SubClassG extends SuperClass { @Override public int publicOverriddenMethod() { return 'G'; } } private static class SubClassH extends SuperClass { @Override public int publicOverriddenMethod() { return 'H'; } } private static final class NotInlinableSubClass extends SuperClass { @Override public int publicOverriddenMethod() { return 'X'; } } }