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