| /* |
| * 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 {@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<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'; |
| } |
| |
| } |
| |
| } |