1 /* 2 * Copyright (c) 2011, 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 package org.graalvm.compiler.replacements; 24 25 import static org.graalvm.compiler.core.common.GraalOptions.UseSnippetGraphCache; 26 import static org.graalvm.compiler.debug.DebugContext.DEFAULT_LOG_STREAM; 27 import static org.graalvm.compiler.java.BytecodeParserOptions.InlineDuringParsing; 28 import static org.graalvm.compiler.java.BytecodeParserOptions.InlineIntrinsicsDuringParsing; 29 import static org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo.createIntrinsicInlineInfo; 30 import static org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext.CompilationContext.INLINE_AFTER_PARSING; 31 import static org.graalvm.compiler.phases.common.DeadCodeEliminationPhase.Optionality.Required; 32 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.concurrent.ConcurrentHashMap; 36 import java.util.concurrent.ConcurrentMap; 37 import java.util.concurrent.atomic.AtomicInteger; 38 39 import org.graalvm.compiler.api.replacements.Fold; 40 import org.graalvm.compiler.api.replacements.MethodSubstitution; 41 import org.graalvm.compiler.api.replacements.Snippet; 42 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; 43 import org.graalvm.compiler.api.replacements.SnippetTemplateCache; 44 import org.graalvm.compiler.bytecode.Bytecode; 45 import org.graalvm.compiler.bytecode.BytecodeProvider; 46 import org.graalvm.compiler.bytecode.ResolvedJavaMethodBytecode; 47 import org.graalvm.compiler.core.common.GraalOptions; 48 import org.graalvm.compiler.core.common.spi.ConstantFieldProvider; 49 import org.graalvm.compiler.debug.DebugCloseable; 50 import org.graalvm.compiler.debug.DebugHandlersFactory; 51 import org.graalvm.compiler.debug.DebugContext; 52 import org.graalvm.compiler.debug.DebugContext.Description; 53 import org.graalvm.compiler.debug.GraalError; 54 import org.graalvm.compiler.debug.TimerKey; 55 import org.graalvm.compiler.graph.Node; 56 import org.graalvm.compiler.graph.Node.NodeIntrinsic; 57 import org.graalvm.compiler.java.GraphBuilderPhase; 58 import org.graalvm.compiler.java.GraphBuilderPhase.Instance; 59 import org.graalvm.compiler.nodes.CallTargetNode; 60 import org.graalvm.compiler.nodes.Invoke; 61 import org.graalvm.compiler.nodes.StateSplit; 62 import org.graalvm.compiler.nodes.StructuredGraph; 63 import org.graalvm.compiler.nodes.ValueNode; 64 import org.graalvm.compiler.nodes.graphbuilderconf.GeneratedInvocationPlugin; 65 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; 66 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins; 67 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; 68 import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin; 69 import org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext; 70 import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin; 71 import org.graalvm.compiler.nodes.graphbuilderconf.MethodSubstitutionPlugin; 72 import org.graalvm.compiler.nodes.java.MethodCallTargetNode; 73 import org.graalvm.compiler.nodes.spi.Replacements; 74 import org.graalvm.compiler.nodes.spi.StampProvider; 75 import org.graalvm.compiler.options.OptionValues; 76 import org.graalvm.compiler.phases.OptimisticOptimizations; 77 import org.graalvm.compiler.phases.common.CanonicalizerPhase; 78 import org.graalvm.compiler.phases.common.ConvertDeoptimizeToGuardPhase; 79 import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase; 80 import org.graalvm.compiler.phases.tiers.PhaseContext; 81 import org.graalvm.compiler.phases.util.Providers; 82 import org.graalvm.compiler.word.Word; 83 import org.graalvm.compiler.word.WordOperationPlugin; 84 import org.graalvm.util.EconomicMap; 85 import org.graalvm.util.Equivalence; 86 87 import jdk.vm.ci.code.TargetDescription; 88 import jdk.vm.ci.meta.ConstantReflectionProvider; 89 import jdk.vm.ci.meta.MetaAccessProvider; 90 import jdk.vm.ci.meta.ResolvedJavaMethod; 91 import jdk.vm.ci.meta.ResolvedJavaType; 92 93 public class ReplacementsImpl implements Replacements, InlineInvokePlugin { 94 95 protected final OptionValues options; 96 public final Providers providers; 97 public final SnippetReflectionProvider snippetReflection; 98 public final TargetDescription target; 99 private GraphBuilderConfiguration.Plugins graphBuilderPlugins; 100 private final DebugHandlersFactory debugHandlersFactory; 101 102 @Override 103 public OptionValues getOptions() { 104 return options; 105 } 106 107 /** 108 * The preprocessed replacement graphs. 109 */ 110 protected final ConcurrentMap<ResolvedJavaMethod, StructuredGraph> graphs; 111 112 /** 113 * The default {@link BytecodeProvider} to use for accessing the bytecode of a replacement if 114 * the replacement doesn't provide another {@link BytecodeProvider}. 115 */ 116 protected final BytecodeProvider defaultBytecodeProvider; 117 118 public void setGraphBuilderPlugins(GraphBuilderConfiguration.Plugins plugins) { 119 assert this.graphBuilderPlugins == null; 120 this.graphBuilderPlugins = plugins; 121 } 122 123 @Override 124 public GraphBuilderConfiguration.Plugins getGraphBuilderPlugins() { 125 return graphBuilderPlugins; 126 } 127 128 protected boolean hasGeneratedInvocationPluginAnnotation(ResolvedJavaMethod method) { 129 return method.getAnnotation(Node.NodeIntrinsic.class) != null || method.getAnnotation(Fold.class) != null; 130 } 131 132 protected boolean hasGenericInvocationPluginAnnotation(ResolvedJavaMethod method) { 133 return method.getAnnotation(Word.Operation.class) != null; 134 } 135 136 private static final int MAX_GRAPH_INLINING_DEPTH = 100; // more than enough 137 138 /** 139 * Determines whether a given method should be inlined based on whether it has a substitution or 140 * whether the inlining context is already within a substitution. 141 * 142 * @return an object specifying how {@code method} is to be inlined or null if it should not be 143 * inlined based on substitution related criteria 144 */ 145 @Override 146 public InlineInfo shouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) { 147 Bytecode subst = getSubstitutionBytecode(method); 148 if (subst != null) { 149 if (b.parsingIntrinsic() || InlineDuringParsing.getValue(b.getOptions()) || InlineIntrinsicsDuringParsing.getValue(b.getOptions())) { 150 // Forced inlining of intrinsics 151 return createIntrinsicInlineInfo(subst.getMethod(), subst.getOrigin()); 152 } 153 return null; 154 } 155 if (b.parsingIntrinsic()) { 156 assert b.getDepth() < MAX_GRAPH_INLINING_DEPTH : "inlining limit exceeded"; 157 158 // Force inlining when parsing replacements 159 return createIntrinsicInlineInfo(method, defaultBytecodeProvider); 160 } else { 161 assert method.getAnnotation(NodeIntrinsic.class) == null : String.format("@%s method %s must only be called from within a replacement%n%s", NodeIntrinsic.class.getSimpleName(), 162 method.format("%h.%n"), b); 163 } 164 return null; 165 } 166 167 @Override 168 public void notifyNotInlined(GraphBuilderContext b, ResolvedJavaMethod method, Invoke invoke) { 169 if (b.parsingIntrinsic()) { 170 IntrinsicContext intrinsic = b.getIntrinsic(); 171 if (!intrinsic.isCallToOriginal(method)) { 172 if (hasGeneratedInvocationPluginAnnotation(method)) { 173 throw new GraalError("%s should have been handled by a %s", method.format("%H.%n(%p)"), GeneratedInvocationPlugin.class.getSimpleName()); 174 } 175 if (hasGenericInvocationPluginAnnotation(method)) { 176 throw new GraalError("%s should have been handled by %s", method.format("%H.%n(%p)"), WordOperationPlugin.class.getSimpleName()); 177 } 178 179 throw new GraalError("All non-recursive calls in the intrinsic %s must be inlined or intrinsified: found call to %s", 180 intrinsic.getIntrinsicMethod().format("%H.%n(%p)"), method.format("%h.%n(%p)")); 181 } 182 } 183 } 184 185 // This map is key'ed by a class name instead of a Class object so that 186 // it is stable across VM executions (in support of replay compilation). 187 private final EconomicMap<String, SnippetTemplateCache> snippetTemplateCache; 188 189 public ReplacementsImpl(OptionValues options, DebugHandlersFactory debugHandlersFactory, Providers providers, SnippetReflectionProvider snippetReflection, BytecodeProvider bytecodeProvider, 190 TargetDescription target) { 191 this.options = options; 192 this.providers = providers.copyWith(this); 193 this.snippetReflection = snippetReflection; 194 this.target = target; 195 this.graphs = new ConcurrentHashMap<>(); 196 this.snippetTemplateCache = EconomicMap.create(Equivalence.DEFAULT); 197 this.defaultBytecodeProvider = bytecodeProvider; 198 this.debugHandlersFactory = debugHandlersFactory; 199 200 } 201 202 private static final TimerKey SnippetPreparationTime = DebugContext.timer("SnippetPreparationTime"); 203 204 @Override 205 public StructuredGraph getSnippet(ResolvedJavaMethod method, Object[] args) { 206 return getSnippet(method, null, args); 207 } 208 209 private static final AtomicInteger nextDebugContextId = new AtomicInteger(); 210 211 protected DebugContext openDebugContext(String idPrefix, ResolvedJavaMethod method) { 212 DebugContext outer = DebugContext.forCurrentThread(); 213 Description description = new Description(method, idPrefix + nextDebugContextId.incrementAndGet()); 214 List<DebugHandlersFactory> factories = debugHandlersFactory == null ? Collections.emptyList() : Collections.singletonList(debugHandlersFactory); 215 return DebugContext.create(options, description, outer.getGlobalMetrics(), DEFAULT_LOG_STREAM, factories); 216 } 217 218 @Override 219 @SuppressWarnings("try") 220 public StructuredGraph getSnippet(ResolvedJavaMethod method, ResolvedJavaMethod recursiveEntry, Object[] args) { 221 assert method.getAnnotation(Snippet.class) != null : "Snippet must be annotated with @" + Snippet.class.getSimpleName(); 222 assert method.hasBytecodes() : "Snippet must not be abstract or native"; 223 224 StructuredGraph graph = UseSnippetGraphCache.getValue(options) ? graphs.get(method) : null; 225 if (graph == null) { 226 try (DebugContext debug = openDebugContext("Snippet_", method); 227 DebugCloseable a = SnippetPreparationTime.start(debug)) { 228 StructuredGraph newGraph = makeGraph(debug, defaultBytecodeProvider, method, args, recursiveEntry); 229 DebugContext.counter("SnippetNodeCount[%#s]", method).add(newGraph.getDebug(), newGraph.getNodeCount()); 230 if (!UseSnippetGraphCache.getValue(options) || args != null) { 231 return newGraph; 232 } 233 newGraph.freeze(); 234 graphs.putIfAbsent(method, newGraph); 235 graph = graphs.get(method); 236 } 237 } 238 return graph; 239 } 240 241 @Override 242 public void registerSnippet(ResolvedJavaMethod method) { 243 // No initialization needed as snippet graphs are created on demand in getSnippet 244 } 245 246 @Override 247 public boolean hasSubstitution(ResolvedJavaMethod method, int invokeBci) { 248 InvocationPlugin plugin = graphBuilderPlugins.getInvocationPlugins().lookupInvocation(method); 249 return plugin != null && (!plugin.inlineOnly() || invokeBci >= 0); 250 } 251 252 @Override 253 public BytecodeProvider getDefaultReplacementBytecodeProvider() { 254 return defaultBytecodeProvider; 255 } 256 257 @Override 258 public Bytecode getSubstitutionBytecode(ResolvedJavaMethod method) { 259 InvocationPlugin plugin = graphBuilderPlugins.getInvocationPlugins().lookupInvocation(method); 260 if (plugin instanceof MethodSubstitutionPlugin) { 261 MethodSubstitutionPlugin msPlugin = (MethodSubstitutionPlugin) plugin; 262 ResolvedJavaMethod substitute = msPlugin.getSubstitute(providers.getMetaAccess()); 263 return msPlugin.getBytecodeProvider().getBytecode(substitute); 264 } 265 return null; 266 } 267 268 @Override 269 public StructuredGraph getSubstitution(ResolvedJavaMethod method, int invokeBci) { 270 StructuredGraph result; 271 InvocationPlugin plugin = graphBuilderPlugins.getInvocationPlugins().lookupInvocation(method); 272 if (plugin != null && (!plugin.inlineOnly() || invokeBci >= 0)) { 273 MetaAccessProvider metaAccess = providers.getMetaAccess(); 274 if (plugin instanceof MethodSubstitutionPlugin) { 275 MethodSubstitutionPlugin msPlugin = (MethodSubstitutionPlugin) plugin; 276 ResolvedJavaMethod substitute = msPlugin.getSubstitute(metaAccess); 277 StructuredGraph graph = graphs.get(substitute); 278 if (graph == null) { 279 try (DebugContext debug = openDebugContext("Substitution_", method)) { 280 graph = makeGraph(debug, msPlugin.getBytecodeProvider(), substitute, null, method); 281 graph.freeze(); 282 graphs.putIfAbsent(substitute, graph); 283 graph = graphs.get(substitute); 284 } 285 } 286 assert graph.isFrozen(); 287 result = graph; 288 } else { 289 Bytecode code = new ResolvedJavaMethodBytecode(method); 290 ConstantReflectionProvider constantReflection = providers.getConstantReflection(); 291 ConstantFieldProvider constantFieldProvider = providers.getConstantFieldProvider(); 292 StampProvider stampProvider = providers.getStampProvider(); 293 try (DebugContext debug = openDebugContext("Substitution_", method)) { 294 result = new IntrinsicGraphBuilder(options, debug, metaAccess, constantReflection, constantFieldProvider, stampProvider, code, invokeBci).buildGraph(plugin); 295 } 296 } 297 } else { 298 result = null; 299 } 300 return result; 301 } 302 303 /** 304 * Creates a preprocessed graph for a snippet or method substitution. 305 * 306 * @param bytecodeProvider how to access the bytecode of {@code method} 307 * @param method the snippet or method substitution for which a graph will be created 308 * @param args 309 * @param original the original method if {@code method} is a {@linkplain MethodSubstitution 310 * substitution} otherwise null 311 */ 312 public StructuredGraph makeGraph(DebugContext debug, BytecodeProvider bytecodeProvider, ResolvedJavaMethod method, Object[] args, ResolvedJavaMethod original) { 313 return createGraphMaker(method, original).makeGraph(debug, bytecodeProvider, args); 314 } 315 316 /** 317 * Can be overridden to return an object that specializes various parts of graph preprocessing. 318 */ 319 protected GraphMaker createGraphMaker(ResolvedJavaMethod substitute, ResolvedJavaMethod original) { 320 return new GraphMaker(this, substitute, original); 321 } 322 323 /** 324 * Creates and preprocesses a graph for a replacement. 325 */ 326 public static class GraphMaker { 327 328 /** The replacements object that the graphs are created for. */ 329 protected final ReplacementsImpl replacements; 330 331 /** 332 * The method for which a graph is being created. 333 */ 334 protected final ResolvedJavaMethod method; 335 336 /** 337 * The original method which {@link #method} is substituting. Calls to {@link #method} or 338 * {@link #substitutedMethod} will be replaced with a forced inline of 339 * {@link #substitutedMethod}. 340 */ 341 protected final ResolvedJavaMethod substitutedMethod; 342 343 protected GraphMaker(ReplacementsImpl replacements, ResolvedJavaMethod substitute, ResolvedJavaMethod substitutedMethod) { 344 this.replacements = replacements; 345 this.method = substitute; 346 this.substitutedMethod = substitutedMethod; 347 } 348 349 @SuppressWarnings("try") 350 public StructuredGraph makeGraph(DebugContext debug, BytecodeProvider bytecodeProvider, Object[] args) { 351 try (DebugContext.Scope s = debug.scope("BuildSnippetGraph", method)) { 352 assert method.hasBytecodes() : method; 353 StructuredGraph graph = buildInitialGraph(debug, bytecodeProvider, method, args); 354 355 finalizeGraph(graph); 356 357 debug.dump(DebugContext.INFO_LEVEL, graph, "%s: Final", method.getName()); 358 359 return graph; 360 } catch (Throwable e) { 361 throw debug.handle(e); 362 } 363 } 364 365 /** 366 * Does final processing of a snippet graph. 367 */ 368 protected void finalizeGraph(StructuredGraph graph) { 369 if (!GraalOptions.SnippetCounters.getValue(replacements.options) || graph.getNodes().filter(SnippetCounterNode.class).isEmpty()) { 370 int sideEffectCount = 0; 371 assert (sideEffectCount = graph.getNodes().filter(e -> hasSideEffect(e)).count()) >= 0; 372 new ConvertDeoptimizeToGuardPhase().apply(graph, null); 373 assert sideEffectCount == graph.getNodes().filter(e -> hasSideEffect(e)).count() : "deleted side effecting node"; 374 375 new DeadCodeEliminationPhase(Required).apply(graph); 376 } else { 377 // ConvertDeoptimizeToGuardPhase will eliminate snippet counters on paths 378 // that terminate in a deopt so we disable it if the graph contains 379 // snippet counters. The trade off is that we miss out on guard 380 // coalescing opportunities. 381 } 382 } 383 384 /** 385 * Filter nodes which have side effects and shouldn't be deleted from snippets when 386 * converting deoptimizations to guards. Currently this only allows exception constructors 387 * to be eliminated to cover the case when Java assertions are in the inlined code. 388 * 389 * @param node 390 * @return true for nodes that have side effects and are unsafe to delete 391 */ 392 private boolean hasSideEffect(Node node) { 393 if (node instanceof StateSplit) { 394 if (((StateSplit) node).hasSideEffect()) { 395 if (node instanceof Invoke) { 396 CallTargetNode callTarget = ((Invoke) node).callTarget(); 397 if (callTarget instanceof MethodCallTargetNode) { 398 ResolvedJavaMethod targetMethod = ((MethodCallTargetNode) callTarget).targetMethod(); 399 if (targetMethod.isConstructor()) { 400 ResolvedJavaType throwableType = replacements.providers.getMetaAccess().lookupJavaType(Throwable.class); 401 return !throwableType.isAssignableFrom(targetMethod.getDeclaringClass()); 402 } 403 } 404 } 405 // Not an exception constructor call 406 return true; 407 } 408 } 409 // Not a StateSplit 410 return false; 411 } 412 413 /** 414 * Builds the initial graph for a replacement. 415 */ 416 @SuppressWarnings("try") 417 protected StructuredGraph buildInitialGraph(DebugContext debug, BytecodeProvider bytecodeProvider, final ResolvedJavaMethod methodToParse, Object[] args) { 418 // Replacements cannot have optimistic assumptions since they have 419 // to be valid for the entire run of the VM. 420 final StructuredGraph graph = new StructuredGraph.Builder(replacements.options, debug).method(methodToParse).build(); 421 422 // Replacements are not user code so they do not participate in unsafe access 423 // tracking 424 graph.disableUnsafeAccessTracking(); 425 426 try (DebugContext.Scope s = debug.scope("buildInitialGraph", graph)) { 427 MetaAccessProvider metaAccess = replacements.providers.getMetaAccess(); 428 429 Plugins plugins = new Plugins(replacements.graphBuilderPlugins); 430 GraphBuilderConfiguration config = GraphBuilderConfiguration.getSnippetDefault(plugins); 431 if (args != null) { 432 plugins.prependParameterPlugin(new ConstantBindingParameterPlugin(args, metaAccess, replacements.snippetReflection)); 433 } 434 435 IntrinsicContext initialIntrinsicContext = null; 436 if (method.getAnnotation(Snippet.class) == null) { 437 // Post-parse inlined intrinsic 438 initialIntrinsicContext = new IntrinsicContext(substitutedMethod, method, bytecodeProvider, INLINE_AFTER_PARSING); 439 } else { 440 // Snippet 441 ResolvedJavaMethod original = substitutedMethod != null ? substitutedMethod : method; 442 initialIntrinsicContext = new IntrinsicContext(original, method, bytecodeProvider, INLINE_AFTER_PARSING); 443 } 444 445 createGraphBuilder(metaAccess, replacements.providers.getStampProvider(), replacements.providers.getConstantReflection(), replacements.providers.getConstantFieldProvider(), config, 446 OptimisticOptimizations.NONE, initialIntrinsicContext).apply(graph); 447 448 new CanonicalizerPhase().apply(graph, new PhaseContext(replacements.providers)); 449 } catch (Throwable e) { 450 throw debug.handle(e); 451 } 452 return graph; 453 } 454 455 protected Instance createGraphBuilder(MetaAccessProvider metaAccess, StampProvider stampProvider, ConstantReflectionProvider constantReflection, ConstantFieldProvider constantFieldProvider, 456 GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) { 457 return new GraphBuilderPhase.Instance(metaAccess, stampProvider, constantReflection, constantFieldProvider, graphBuilderConfig, optimisticOpts, 458 initialIntrinsicContext); 459 } 460 } 461 462 @Override 463 public void registerSnippetTemplateCache(SnippetTemplateCache templates) { 464 assert snippetTemplateCache.get(templates.getClass().getName()) == null; 465 snippetTemplateCache.put(templates.getClass().getName(), templates); 466 } 467 468 @Override 469 public <T extends SnippetTemplateCache> T getSnippetTemplateCache(Class<T> templatesClass) { 470 SnippetTemplateCache ret = snippetTemplateCache.get(templatesClass.getName()); 471 return templatesClass.cast(ret); 472 } 473 }