/* * 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.Assume; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.List; public class PolymorphicInliningTest extends GraalCompilerTest { @Before public void checkJavaAgent() { Assume.assumeFalse("Java Agent found -> skipping", SubprocessUtil.isJavaAgentAttached()); } @Test public void testInSubprocess() throws InterruptedException, IOException { String recursionPropName = getClass().getName() + ".recursion"; if (Boolean.getBoolean(recursionPropName)) { testPolymorphicInlining(); testPolymorphicNotInlining(); testMegamorphicInlining(); testMegamorphicNotInlining(); } else { List 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 {@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 {@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 {@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 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 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'; } } }