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.Assume;
  52 import org.junit.Before;
  53 import org.junit.Test;
  54 
  55 import java.io.IOException;
  56 import java.util.List;
  57 
  58 public class PolymorphicInliningTest extends GraalCompilerTest {
  59 
  60     @Before
  61     public void checkJavaAgent() {
  62         Assume.assumeFalse("Java Agent found -> skipping", SubprocessUtil.isJavaAgentAttached());
  63     }
  64 
  65     @Test
  66     public void testInSubprocess() throws InterruptedException, IOException {
  67         String recursionPropName = getClass().getName() + ".recursion";
  68         if (Boolean.getBoolean(recursionPropName)) {
  69             testPolymorphicInlining();
  70             testPolymorphicNotInlining();
  71             testMegamorphicInlining();
  72             testMegamorphicNotInlining();
  73         } else {
  74             List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
  75             NotInlinableSubClass.class.getCanonicalName();
  76             vmArgs.add("-XX:CompileCommand=dontinline,org/graalvm/compiler/core/test/inlining/PolymorphicInliningTest$NotInlinableSubClass.publicOverriddenMethod");
  77             vmArgs.add("-D" + recursionPropName + "=true");
  78             SubprocessUtil.Subprocess proc = java(vmArgs, "com.oracle.mxtool.junit.MxJUnitWrapper", getClass().getName());
  79             if (proc.exitCode != 0) {
  80                 Assert.fail(String.format("non-zero exit code %d for command:%n%s", proc.exitCode, proc));
  81             }
  82         }
  83     }
  84 
  85     public int polymorphicCallsite(SuperClass receiver) {
  86         return receiver.publicOverriddenMethod();
  87     }
  88 
  89     public void testPolymorphicInlining() {
  90         for (int i = 0; i < 10000; i++) {
  91             if (i % 2 == 0) {
  92                 polymorphicCallsite(Receivers.subClassA);
  93             } else {
  94                 polymorphicCallsite(Receivers.subClassB);
  95             }
  96         }
  97         StructuredGraph graph = getGraph("polymorphicCallsite", false);
  98         // This callsite should be inlined with a TypeCheckedInliningViolated deoptimization.
  99         assertTrue(getNodeCount(graph, InvokeNode.class) == 0);
 100         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 1);
 101         assertTrue(getNodeCount(graph, DeoptimizeNode.class) >= 1);
 102     }
 103 
 104     /**
 105      * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding
 106      * interference of the receiver type profile from different unit tests.
 107      */
 108     public int polymorphicCallsite1(SuperClass receiver) {
 109         return receiver.publicOverriddenMethod();
 110     }
 111 
 112     public void testPolymorphicNotInlining() {
 113         for (int i = 0; i < 10000; i++) {
 114             if (i % 2 == 0) {
 115                 polymorphicCallsite1(Receivers.subClassA);
 116             } else {
 117                 polymorphicCallsite1(Receivers.notInlinableSubClass);
 118             }
 119         }
 120         StructuredGraph graph = getGraph("polymorphicCallsite1", false);
 121         // This callsite should not be inlined due to one of the potential callee method is not
 122         // inlinable.
 123         assertTrue(getNodeCount(graph, InvokeNode.class) == 1);
 124         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 0);
 125     }
 126 
 127     /**
 128      * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding
 129      * interference of the receiver type profile from different unit tests.
 130      */
 131     public int polymorphicCallsite2(SuperClass receiver) {
 132         return receiver.publicOverriddenMethod();
 133     }
 134 
 135     public void testMegamorphicInlining() {
 136         // Construct a receiver type profile that exceeds the max type width (by default 8 in JVMCI,
 137         // specified by -XX:TypeProfileWidth).
 138         for (int i = 0; i < 2000; i++) {
 139             // Ensure the following receiver type is within the type profile.
 140             polymorphicCallsite2(Receivers.subClassA);
 141         }
 142         for (int i = 0; i < 10000; i++) {
 143             switch (i % 20) {
 144                 case 0:
 145                 case 1:
 146                 case 2:
 147                 case 3:
 148                 case 4:
 149                 case 5:
 150                 case 6:
 151                 case 7:
 152                     // Probability: 40%
 153                     // Ensure the probability is greater than
 154                     // GraalOptions.MegamorphicInliningMinMethodProbability (by default 0.33D);
 155                     polymorphicCallsite2(Receivers.subClassA);
 156                     break;
 157                 case 8:
 158                     polymorphicCallsite2(Receivers.subClassB);
 159                     break;
 160                 case 9:
 161                     polymorphicCallsite2(Receivers.subClassC);
 162                     break;
 163                 case 10:
 164                     polymorphicCallsite2(Receivers.subClassD);
 165                     break;
 166                 case 11:
 167                     polymorphicCallsite2(Receivers.subClassE);
 168                     break;
 169                 case 12:
 170                     polymorphicCallsite2(Receivers.subClassF);
 171                     break;
 172                 case 13:
 173                     polymorphicCallsite2(Receivers.subClassG);
 174                     break;
 175                 case 14:
 176                     polymorphicCallsite2(Receivers.subClassH);
 177                     break;
 178                 default:
 179                     // Probability: 25%
 180                     polymorphicCallsite2(Receivers.notInlinableSubClass);
 181                     break;
 182             }
 183         }
 184         StructuredGraph graph = getGraph("polymorphicCallsite2", false);
 185         // This callsite should be inlined with a fallback invocation.
 186         assertTrue(getNodeCount(graph, InvokeNode.class) == 1);
 187         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 1);
 188     }
 189 
 190     /**
 191      * This snippet is identical to {@link #polymorphicCallsite(SuperClass)}, and is for avoiding
 192      * interference of the receiver type profile from different unit tests.
 193      */
 194     public int polymorphicCallsite3(SuperClass receiver) {
 195         return receiver.publicOverriddenMethod();
 196     }
 197 
 198     public void testMegamorphicNotInlining() {
 199         for (int i = 0; i < 10000; i++) {
 200             switch (i % 10) {
 201                 case 0:
 202                 case 1:
 203                     polymorphicCallsite3(Receivers.subClassA);
 204                     break;
 205                 case 2:
 206                     polymorphicCallsite3(Receivers.subClassB);
 207                     break;
 208                 case 3:
 209                     polymorphicCallsite3(Receivers.subClassC);
 210                     break;
 211                 case 4:
 212                     polymorphicCallsite3(Receivers.subClassD);
 213                     break;
 214                 case 5:
 215                     polymorphicCallsite3(Receivers.subClassE);
 216                     break;
 217                 case 6:
 218                     polymorphicCallsite3(Receivers.subClassF);
 219                     break;
 220                 case 7:
 221                     polymorphicCallsite3(Receivers.subClassG);
 222                     break;
 223                 case 8:
 224                     polymorphicCallsite3(Receivers.subClassH);
 225                     break;
 226                 default:
 227                     polymorphicCallsite3(Receivers.notInlinableSubClass);
 228                     break;
 229             }
 230         }
 231         StructuredGraph graph = getGraph("polymorphicCallsite3", false);
 232         // This callsite should not be inlined due to non of the potential callee method exceeds the
 233         // probability specified by GraalOptions.MegamorphicInliningMinMethodProbability.
 234         assertTrue(getNodeCount(graph, InvokeNode.class) == 1);
 235         assertTrue(getNodeCount(graph, TypeSwitchNode.class) == 0);
 236     }
 237 
 238     @SuppressWarnings("try")
 239     private StructuredGraph getGraph(final String snippet, final boolean eagerInfopointMode) {
 240         DebugContext debug = getDebugContext();
 241         try (DebugContext.Scope s = debug.scope("InliningTest", new DebugDumpScope(snippet, true))) {
 242             ResolvedJavaMethod method = getResolvedJavaMethod(snippet);
 243             Builder builder = builder(method, AllowAssumptions.YES, debug);
 244             StructuredGraph graph = eagerInfopointMode ? parse(builder, getDebugGraphBuilderSuite()) : parse(builder, getEagerGraphBuilderSuite());
 245             try (DebugContext.Scope s2 = debug.scope("Inlining", graph)) {
 246                 PhaseSuite<HighTierContext> graphBuilderSuite = eagerInfopointMode
 247                                 ? getCustomGraphBuilderSuite(GraphBuilderConfiguration.getDefault(getDefaultGraphBuilderPlugins()).withFullInfopoints(true))
 248                                 : getDefaultGraphBuilderSuite();
 249                 HighTierContext context = new HighTierContext(getProviders(), graphBuilderSuite, OptimisticOptimizations.ALL);
 250                 debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph");
 251                 new CanonicalizerPhase().apply(graph, context);
 252                 new InliningPhase(new CanonicalizerPhase()).apply(graph, context);
 253                 debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph");
 254                 new CanonicalizerPhase().apply(graph, context);
 255                 new DeadCodeEliminationPhase().apply(graph);
 256                 return graph;
 257             }
 258         } catch (Throwable e) {
 259             throw debug.handle(e);
 260         }
 261     }
 262 
 263     private static int getNodeCount(StructuredGraph graph, Class<? extends Node> nodeClass) {
 264         return graph.getNodes().filter(nodeClass).count();
 265     }
 266 
 267     private static final class Receivers {
 268         static final SubClassA subClassA = new SubClassA();
 269         static final SubClassB subClassB = new SubClassB();
 270         static final SubClassC subClassC = new SubClassC();
 271         static final SubClassD subClassD = new SubClassD();
 272         static final SubClassE subClassE = new SubClassE();
 273         static final SubClassF subClassF = new SubClassF();
 274         static final SubClassG subClassG = new SubClassG();
 275         static final SubClassH subClassH = new SubClassH();
 276 
 277         static final NotInlinableSubClass notInlinableSubClass = new NotInlinableSubClass();
 278     }
 279 
 280     private abstract static class SuperClass {
 281 
 282         public abstract int publicOverriddenMethod();
 283 
 284     }
 285 
 286     private static class SubClassA extends SuperClass {
 287 
 288         @Override
 289         public int publicOverriddenMethod() {
 290             return 'A';
 291         }
 292 
 293     }
 294 
 295     private static class SubClassB extends SuperClass {
 296 
 297         @Override
 298         public int publicOverriddenMethod() {
 299             return 'B';
 300         }
 301 
 302     }
 303 
 304     private static class SubClassC extends SuperClass {
 305 
 306         @Override
 307         public int publicOverriddenMethod() {
 308             return 'C';
 309         }
 310 
 311     }
 312 
 313     private static class SubClassD extends SuperClass {
 314 
 315         @Override
 316         public int publicOverriddenMethod() {
 317             return 'D';
 318         }
 319 
 320     }
 321 
 322     private static class SubClassE extends SuperClass {
 323 
 324         @Override
 325         public int publicOverriddenMethod() {
 326             return 'E';
 327         }
 328 
 329     }
 330 
 331     private static class SubClassF extends SuperClass {
 332 
 333         @Override
 334         public int publicOverriddenMethod() {
 335             return 'F';
 336         }
 337 
 338     }
 339 
 340     private static class SubClassG extends SuperClass {
 341 
 342         @Override
 343         public int publicOverriddenMethod() {
 344             return 'G';
 345         }
 346 
 347     }
 348 
 349     private static class SubClassH extends SuperClass {
 350 
 351         @Override
 352         public int publicOverriddenMethod() {
 353             return 'H';
 354         }
 355 
 356     }
 357 
 358     private static final class NotInlinableSubClass extends SuperClass {
 359 
 360         @Override
 361         public int publicOverriddenMethod() {
 362             return 'X';
 363         }
 364 
 365     }
 366 
 367 }