1 /*
   2  * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.core.test.inlining;
  26 
  27 import static org.graalvm.compiler.test.SubprocessUtil.getVMCommandLine;
  28 import static org.graalvm.compiler.test.SubprocessUtil.java;
  29 import static org.graalvm.compiler.test.SubprocessUtil.withoutDebuggerArguments;
  30 
  31 import jdk.vm.ci.meta.ResolvedJavaMethod;
  32 import org.graalvm.compiler.core.test.GraalCompilerTest;
  33 import org.graalvm.compiler.debug.DebugContext;
  34 import org.graalvm.compiler.debug.DebugDumpScope;
  35 import org.graalvm.compiler.graph.Node;
  36 import org.graalvm.compiler.nodes.DeoptimizeNode;
  37 import org.graalvm.compiler.nodes.InvokeNode;
  38 import org.graalvm.compiler.nodes.StructuredGraph;
  39 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
  40 import org.graalvm.compiler.nodes.StructuredGraph.Builder;
  41 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
  42 import org.graalvm.compiler.nodes.java.TypeSwitchNode;
  43 import org.graalvm.compiler.phases.OptimisticOptimizations;
  44 import org.graalvm.compiler.phases.PhaseSuite;
  45 import org.graalvm.compiler.phases.common.CanonicalizerPhase;
  46 import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
  47 import org.graalvm.compiler.phases.common.inlining.InliningPhase;
  48 import org.graalvm.compiler.phases.tiers.HighTierContext;
  49 import org.graalvm.compiler.test.SubprocessUtil;
  50 import org.junit.Assert;
  51 import org.junit.Test;
  52 
  53 import java.io.IOException;
  54 import java.util.List;
  55 
  56 public class PolymorphicInliningTest extends GraalCompilerTest {
  57 
  58     @Test
  59     public void testInSubprocess() throws InterruptedException, IOException {
  60         String recursionPropName = getClass().getName() + ".recursion";
  61         if (Boolean.getBoolean(recursionPropName)) {
  62             testPolymorphicInlining();
  63             testPolymorphicNotInlining();
  64             testMegamorphicInlining();
  65             testMegamorphicNotInlining();
  66         } else {
  67             List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
  68             NotInlinableSubClass.class.getCanonicalName();
  69             vmArgs.add("-XX:CompileCommand=dontinline,org/graalvm/compiler/core/test/inlining/PolymorphicInliningTest$NotInlinableSubClass.publicOverriddenMethod");
  70             vmArgs.add("-D" + recursionPropName + "=true");
  71             SubprocessUtil.Subprocess proc = java(vmArgs, "com.oracle.mxtool.junit.MxJUnitWrapper", getClass().getName());
  72             if (proc.exitCode != 0) {
  73                 Assert.fail(String.format("non-zero exit code %d for command:%n%s", proc.exitCode, proc));
  74             }
  75         }
  76     }
  77 
  78     public int polymorphicCallsite(SuperClass receiver) {
  79         return receiver.publicOverriddenMethod();
  80     }
  81 
  82     public void testPolymorphicInlining() {
  83         for (int i = 0; i < 10000; i++) {
  84             if (i % 2 == 0) {
  85                 polymorphicCallsite(Receivers.subClassA);
  86             } else {
  87                 polymorphicCallsite(Receivers.subClassB);
  88             }
  89         }
  90         StructuredGraph graph = getGraph("polymorphicCallsite", false);
  91         // This callsite should be inlined with a TypeCheckedInliningViolated deoptimization.
  92         assertTrue(getNodeCount(graph, InvokeNode.class) == 0);
  93         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 1);
  94         assertTrue(getNodeCount(graph, DeoptimizeNode.class) >= 1);
  95     }
  96 
  97     /**
  98      * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding
  99      * interference of the receiver type profile from different unit tests.
 100      */
 101     public int polymorphicCallsite1(SuperClass receiver) {
 102         return receiver.publicOverriddenMethod();
 103     }
 104 
 105     public void testPolymorphicNotInlining() {
 106         for (int i = 0; i < 10000; i++) {
 107             if (i % 2 == 0) {
 108                 polymorphicCallsite1(Receivers.subClassA);
 109             } else {
 110                 polymorphicCallsite1(Receivers.notInlinableSubClass);
 111             }
 112         }
 113         StructuredGraph graph = getGraph("polymorphicCallsite1", false);
 114         // This callsite should not be inlined due to one of the potential callee method is not
 115         // inlinable.
 116         assertTrue(getNodeCount(graph, InvokeNode.class) == 1);
 117         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 0);
 118     }
 119 
 120     /**
 121      * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding
 122      * interference of the receiver type profile from different unit tests.
 123      */
 124     public int polymorphicCallsite2(SuperClass receiver) {
 125         return receiver.publicOverriddenMethod();
 126     }
 127 
 128     public void testMegamorphicInlining() {
 129         // Construct a receiver type profile that exceeds the max type width (by default 8 in JVMCI,
 130         // specified by -XX:TypeProfileWidth).
 131         for (int i = 0; i < 2000; i++) {
 132             // Ensure the following receiver type is within the type profile.
 133             polymorphicCallsite2(Receivers.subClassA);
 134         }
 135         for (int i = 0; i < 10000; i++) {
 136             switch (i % 20) {
 137                 case 0:
 138                 case 1:
 139                 case 2:
 140                 case 3:
 141                 case 4:
 142                 case 5:
 143                 case 6:
 144                 case 7:
 145                     // Probability: 40%
 146                     // Ensure the probability is greater than
 147                     // GraalOptions.MegamorphicInliningMinMethodProbability (by default 0.33D);
 148                     polymorphicCallsite2(Receivers.subClassA);
 149                     break;
 150                 case 8:
 151                     polymorphicCallsite2(Receivers.subClassB);
 152                     break;
 153                 case 9:
 154                     polymorphicCallsite2(Receivers.subClassC);
 155                     break;
 156                 case 10:
 157                     polymorphicCallsite2(Receivers.subClassD);
 158                     break;
 159                 case 11:
 160                     polymorphicCallsite2(Receivers.subClassE);
 161                     break;
 162                 case 12:
 163                     polymorphicCallsite2(Receivers.subClassF);
 164                     break;
 165                 case 13:
 166                     polymorphicCallsite2(Receivers.subClassG);
 167                     break;
 168                 case 14:
 169                     polymorphicCallsite2(Receivers.subClassH);
 170                     break;
 171                 default:
 172                     // Probability: 25%
 173                     polymorphicCallsite2(Receivers.notInlinableSubClass);
 174                     break;
 175             }
 176         }
 177         StructuredGraph graph = getGraph("polymorphicCallsite2", false);
 178         // This callsite should be inlined with a fallback invocation.
 179         assertTrue(getNodeCount(graph, InvokeNode.class) == 1);
 180         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 1);
 181     }
 182 
 183     /**
 184      * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding
 185      * interference of the receiver type profile from different unit tests.
 186      */
 187     public int polymorphicCallsite3(SuperClass receiver) {
 188         return receiver.publicOverriddenMethod();
 189     }
 190 
 191     public void testMegamorphicNotInlining() {
 192         for (int i = 0; i < 10000; i++) {
 193             switch (i % 10) {
 194                 case 0:
 195                 case 1:
 196                     polymorphicCallsite3(Receivers.subClassA);
 197                     break;
 198                 case 2:
 199                     polymorphicCallsite3(Receivers.subClassB);
 200                     break;
 201                 case 3:
 202                     polymorphicCallsite3(Receivers.subClassC);
 203                     break;
 204                 case 4:
 205                     polymorphicCallsite3(Receivers.subClassD);
 206                     break;
 207                 case 5:
 208                     polymorphicCallsite3(Receivers.subClassE);
 209                     break;
 210                 case 6:
 211                     polymorphicCallsite3(Receivers.subClassF);
 212                     break;
 213                 case 7:
 214                     polymorphicCallsite3(Receivers.subClassG);
 215                     break;
 216                 case 8:
 217                     polymorphicCallsite3(Receivers.subClassH);
 218                     break;
 219                 default:
 220                     polymorphicCallsite3(Receivers.notInlinableSubClass);
 221                     break;
 222             }
 223         }
 224         StructuredGraph graph = getGraph("polymorphicCallsite3", false);
 225         // This callsite should not be inlined due to non of the potential callee method exceeds the
 226         // probability specified by GraalOptions.MegamorphicInliningMinMethodProbability.
 227         assertTrue(getNodeCount(graph, InvokeNode.class) == 1);
 228         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 0);
 229     }
 230 
 231     @SuppressWarnings("try")
 232     private StructuredGraph getGraph(final String snippet, final boolean eagerInfopointMode) {
 233         DebugContext debug = getDebugContext();
 234         try (DebugContext.Scope s = debug.scope("InliningTest", new DebugDumpScope(snippet, true))) {
 235             ResolvedJavaMethod method = getResolvedJavaMethod(snippet);
 236             Builder builder = builder(method, AllowAssumptions.YES, debug);
 237             StructuredGraph graph = eagerInfopointMode ? parse(builder, getDebugGraphBuilderSuite()) : parse(builder, getEagerGraphBuilderSuite());
 238             try (DebugContext.Scope s2 = debug.scope("Inlining", graph)) {
 239                 PhaseSuite<HighTierContext> graphBuilderSuite = eagerInfopointMode
 240                                 ? getCustomGraphBuilderSuite(GraphBuilderConfiguration.getDefault(getDefaultGraphBuilderPlugins()).withFullInfopoints(true))
 241                                 : getDefaultGraphBuilderSuite();
 242                 HighTierContext context = new HighTierContext(getProviders(), graphBuilderSuite, OptimisticOptimizations.ALL);
 243                 debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph");
 244                 new CanonicalizerPhase().apply(graph, context);
 245                 new InliningPhase(new CanonicalizerPhase()).apply(graph, context);
 246                 debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph");
 247                 new CanonicalizerPhase().apply(graph, context);
 248                 new DeadCodeEliminationPhase().apply(graph);
 249                 return graph;
 250             }
 251         } catch (Throwable e) {
 252             throw debug.handle(e);
 253         }
 254     }
 255 
 256     private static int getNodeCount(StructuredGraph graph, Class<? extends Node> nodeClass) {
 257         return graph.getNodes().filter(nodeClass).count();
 258     }
 259 
 260     private static final class Receivers {
 261         static final SubClassA subClassA = new SubClassA();
 262         static final SubClassB subClassB = new SubClassB();
 263         static final SubClassC subClassC = new SubClassC();
 264         static final SubClassD subClassD = new SubClassD();
 265         static final SubClassE subClassE = new SubClassE();
 266         static final SubClassF subClassF = new SubClassF();
 267         static final SubClassG subClassG = new SubClassG();
 268         static final SubClassH subClassH = new SubClassH();
 269 
 270         static final NotInlinableSubClass notInlinableSubClass = new NotInlinableSubClass();
 271     }
 272 
 273     private abstract static class SuperClass {
 274 
 275         public abstract int publicOverriddenMethod();
 276 
 277     }
 278 
 279     private static class SubClassA extends SuperClass {
 280 
 281         @Override
 282         public int publicOverriddenMethod() {
 283             return 'A';
 284         }
 285 
 286     }
 287 
 288     private static class SubClassB extends SuperClass {
 289 
 290         @Override
 291         public int publicOverriddenMethod() {
 292             return 'B';
 293         }
 294 
 295     }
 296 
 297     private static class SubClassC extends SuperClass {
 298 
 299         @Override
 300         public int publicOverriddenMethod() {
 301             return 'C';
 302         }
 303 
 304     }
 305 
 306     private static class SubClassD extends SuperClass {
 307 
 308         @Override
 309         public int publicOverriddenMethod() {
 310             return 'D';
 311         }
 312 
 313     }
 314 
 315     private static class SubClassE extends SuperClass {
 316 
 317         @Override
 318         public int publicOverriddenMethod() {
 319             return 'E';
 320         }
 321 
 322     }
 323 
 324     private static class SubClassF extends SuperClass {
 325 
 326         @Override
 327         public int publicOverriddenMethod() {
 328             return 'F';
 329         }
 330 
 331     }
 332 
 333     private static class SubClassG extends SuperClass {
 334 
 335         @Override
 336         public int publicOverriddenMethod() {
 337             return 'G';
 338         }
 339 
 340     }
 341 
 342     private static class SubClassH extends SuperClass {
 343 
 344         @Override
 345         public int publicOverriddenMethod() {
 346             return 'H';
 347         }
 348 
 349     }
 350 
 351     private static final class NotInlinableSubClass extends SuperClass {
 352 
 353         @Override
 354         public int publicOverriddenMethod() {
 355             return 'X';
 356         }
 357 
 358     }
 359 
 360 }