Annotation Interface Cached


@Retention(CLASS) @Target(PARAMETER) public @interface Cached

A parameter annotated with Cached in a Specialization refers to a cached value of a specialization instance. A cached parameter value is initialized once using the initializer expression at specialization instantiation. For each call of the specialization method the cached value is provided by using the annotated parameter from the method body. Cache initializers are potentially executed before guard expressions declared in Specialization.guards().

A typical specialization may define multiple dynamic and multiple cached parameters. Dynamic parameter values are typically provided by executing child nodes of the operation. Cached parameters are initialized and stored once per specialization instantiation. Cached parameters are always constant at compile time. You may verify this by invoking CompilerAsserts.compilationConstant(Object) on any cached parameter. For consistency between specialization declarations cached parameters must be declared last in a specialization method.

The initializer expression of a cached parameter is defined using a subset of Java. This subset includes field/parameter accesses, function calls, type exact infix comparisons (==, !=, <, <=, >, >=), logical negation (!), logical disjunction (||), null, true, false, and integer literals. The return type of the initializer expression must be assignable to the parameter type. If the annotated parameter type is derived from Node then the Node instance is allowed to use the Node.replace(Node) method to replace itself. Bound elements without receivers are resolved using the following order:

  1. Dynamic and cached parameters of the enclosing specialization.
  2. Fields defined using NodeField for the enclosing node.
  3. Public constructors of the type of the annotated parameter using the new keyword as method name.
  4. Public and static methods or fields of the type of the annotated parameter.
  5. Non-private, static or virtual methods or fields of enclosing node.
  6. Non-private, static or virtual methods or fields of super types of the enclosing node.
  7. Public and static methods or fields imported using ImportStatic.
The following examples explain the intended use of the Cached annotation. All of the examples have to be enclosed in the following node declaration:

 @NodeChild("operand")
 abstract TestNode extends Node {
   abstract void execute(Object operandValue);
   // ... example here ...
 }
 
  1. This example defines one dynamic and one cached parameter. The operand parameter is representing the dynamic value of the operand while the cachedOperand is initialized once at first execution of the specialization (specialization instantiation time).
      @Specialization
      void doCached(int operand, @Cached("operand") int cachedOperand) {
          CompilerAsserts.compilationConstant(cachedOperand);
          ...
      }
    
      Example executions:
      execute(1) => doCached(1, 1) // new instantiation, localOperand is bound to 1
      execute(0) => doCached(0, 1)
      execute(2) => doCached(2, 1)
    
     
  2. We extend the previous example by a guard for the cachedOperand value to be equal to the dynamic operand value. This specifies that the specialization is instantiated for each individual operand value that is provided. There are a lot of individual int values and for each individual int value a new specialization would get instantiated. The Specialization.limit() property defines a limit for the number of specializations that can get instantiated. If the specialization instantiation limit is reached then no further specializations are instantiated. Like for other specializations if there are no more specializations defined an UnsupportedSpecializationException is thrown. The default specialization instantiation limit is 3.
     @Specialization(guards = "operand == cachedOperand")
     void doCached(int operand, @Cached("operand") int cachedOperand) {
        CompilerAsserts.compilationConstant(cachedOperand);
        ...
     }
    
     Example executions:
     execute(0) => doCached(0, 0) // new instantiation, cachedOperand is bound to 0
     execute(1) => doCached(1, 1) // new instantiation, cachedOperand is bound to 1
     execute(1) => doCached(1, 1)
     execute(2) => doCached(2, 2) // new instantiation, cachedOperand is bound to 2
     execute(3) => throws UnsupportedSpecializationException // instantiation limit overflows
    
     
  3. To handle the limit overflow we extend our example by an additional specialization named doNormal. This specialization has the same type restrictions but does not have local state nor the operand identity guard. It is also declared after doCached therefore it is only instantiated if the limit of the doCached specialization has been reached. In other words doNormal is more generic than doCached . The doNormal specialization uses replaces="doCached" to specify that all instantiations of doCached get removed if doNormal is instantiated. Alternatively if the replaces relation is omitted then all doCached instances remain but no new instances are created.
     @Specialization(guards = "operand == cachedOperand")
     void doCached(int operand, @Cached("operand") int cachedOperand) {
        CompilerAsserts.compilationConstant(cachedOperand);
        ...
     }
    
     @Specialization(replaces = "doCached")
     void doNormal(int operand) {...}
    
     Example executions with replaces = "doCached":
     execute(0) => doCached(0, 0) // new instantiation, cachedOperand is bound to 0
     execute(1) => doCached(1, 1) // new instantiation, cachedOperand is bound to 1
     execute(1) => doCached(1, 1)
     execute(2) => doCached(2, 2) // new instantiation, cachedOperand is bound to 2
     execute(3) => doNormal(3)    // new instantiation of doNormal due to limit overflow; doCached gets removed.
     execute(1) => doNormal(1)
    
     Example executions without replaces = "doCached":
     execute(0) => doCached(0, 0) // new instantiation, cachedOperand is bound to 0
     execute(1) => doCached(1, 1) // new instantiation, cachedOperand is bound to 1
     execute(1) => doCached(1, 1)
     execute(2) => doCached(2, 2) // new instantiation, cachedOperand is bound to 2
     execute(3) => doNormal(3)    // new instantiation of doNormal due to limit overflow
     execute(1) => doCached(1, 1)
    
     
  4. This next example shows how methods from the enclosing node can be used to initialize cached parameters. Please note that the visibility of transformLocal must not be private.
     @Specialization
     void s(int operand, @Cached("transformLocal(operand)") int cachedOperand) {
     }
    
     int transformLocal(int operand) {
         return operand & 0x42;
     }
    
     
  5. The new keyword can be used to initialize a cached parameter using a constructor of the parameter type.
     @Specialization
     void s(Object operand, @Cached("new()") OtherNode someNode) {
         someNode.execute(operand);
     }
    
     static class OtherNode extends Node {
    
         public String execute(Object value) {
             throw new UnsupportedOperationException();
         }
     }
    
     
  6. Java types without public constructor but with a static factory methods can be initialized by just referencing its static factory method and its parameters. In this case BranchProfile.create() is used to instantiate the BranchProfile instance.
     @Specialization
     void s(int operand, @Cached("create()") BranchProfile profile) {
     }
     
Since:
0.8 or earlier
See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static @interface 
    Disallows any sharing with other cached parameters.
    static @interface 
    Allows sharing between multiple Cached parameters between multiple specializations or exported library messages.
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    boolean
    Specifies whether the cached parameter values of type NodeInterface should be adopted as its child by the current node.
    boolean
    Allows the value() to be used for uncached().
    int
    Specifies the number of array dimensions to be marked as compilation final.
    boolean
    Enables inlining of the cached parameter if supported.
    Specifies an alternative method name for node object inlining.
    boolean
    Returns true if the cache initializer never returns the default value of its parameter type at runtime.
    Specifies the bindings used for the $parameters variable in cached or uncached initializers.
    Defines the initializer that is used for uncached nodes or uncached versions of exported library messages.
    Defines the initializer expression of the cached parameter value.
    boolean
    If set to true then weak references will be used to refer to this cached value in the generated node.
  • Element Details

    • value

      String value
      Defines the initializer expression of the cached parameter value.
      Since:
      0.8 or earlier
      See Also:
      Default:
      "create($parameters)"
    • uncached

      String uncached
      Defines the initializer that is used for uncached nodes or uncached versions of exported library messages.
      Since:
      19.0
      See Also:
      Default:
      "getUncached($parameters)"
    • dimensions

      int dimensions
      Specifies the number of array dimensions to be marked as compilation final. This value must be specified for all array-typed cached values except node arrays and must be left unspecified in other cases where it has no meaning. The allowed range is from 0 to the number of declared array dimensions (inclusive). Specifically, a dimensions value of 0 marks only the reference to the (outermost) array as final but not its elements, a value of 1 marks the outermost array and all its elements as final but not the elements of any nested arrays. If not specified and the cached value type is an array type then this will cause a warning and in later releases an error.
      Since:
      0.26
      See Also:
      Default:
      -1
    • allowUncached

      boolean allowUncached
      Allows the value() to be used for uncached(). This is useful if the expression is the same for value() and uncached(). By setting allowUncached() to true it is not necessary to repeat the value() expression in the uncached() expression. This flag cannot be set in combination with uncached().
      Since:
      19.0
      Default:
      false
    • inline

      boolean inline
      Enables inlining of the cached parameter if supported. The type of a cached parameter is considered inlinable if it declares a static method called inline with a single parameter of type InlineSupport.InlineTarget. For specializing DSL nodes the GenerateInline is sufficient to be enabled for the cached target type, the static inline method will be resolved automatically from the generated code of the target type.

      Inlining is enabled by default if:

      Else, inlining is disabled by default. If a node is inlinable but is not inlined by default, the DSL will emit a warning to indicate this possibility.
      Since:
      23.0
      See Also:
      Default:
      false
    • inlineMethod

      String inlineMethod
      Specifies an alternative method name for node object inlining. Instead of looking up the inline method from the receiver type use an accessible enclosing method of the given name instead. The method must have a single parameter InlineSupport.InlineTarget and return a type compatible to the cached type. This can be useful if you want to route calls to the inline method through an abstraction that does not allow direct type access to the node classes. It is expected that this property is only needed rarely.
      Since:
      23.0
      Default:
      ""
    • parameters

      String[] parameters
      Specifies the bindings used for the $parameters variable in cached or uncached initializers.
      Since:
      19.0
      Default:
      {}
    • weak

      boolean weak
      If set to true then weak references will be used to refer to this cached value in the generated node. The default value is false. The weak cached parameter is guaranteed to not become null in guards or specialization method invocations. If a weak cached parameter gets collected by the GC, then any compiled code remain unaffected and the specialization instance will not be removed. Specializations with collected cached references continue to count to the specialization limit. This is necessary to provide an upper bound for the number of invalidations that may happen for this specialization.

      A weak cached parameter implicitly adds a weakRef.get() != null guard that is invoked before the cached value is referenced for the first time. This means that specializations which previously did not result in fall-through behavior may now fall-through. This is important if used in combination with Fallback. Weak cached parameters that are used as part of uncached nodes, execute the cached initializer for each execution and therefore implicitly do not use a weak reference.

      Example usage:

       @GenerateUncached
       abstract class WeakInlineCacheNode extends Node {
      
           abstract Object execute(Object arg0);
      
           @Specialization(guards = "cachedArg.equals(arg)", limit = "3")
           Object s0(String arg,
                           @Cached(value = "arg", weak = true) String cachedArg) {
               assertNotNull(cachedStorage);
               return arg;
           }
       }
       
      Since:
      20.2
      See Also:
      Default:
      false
    • adopt

      boolean adopt
      Specifies whether the cached parameter values of type NodeInterface should be adopted as its child by the current node. The default value is true, therefore all cached values of type NodeInterface and arrays of the same type are adopted. If the value is set to false, then no adoption is performed. It is useful to set adopt to false when nodes need to be referenced more than once in the AST.

      If the type of the field is an NodeInterface array and adopt is set to false, then the compilation final dimensions attribute needs to be specified explicitly.

      Since:
      20.2
      Default:
      true
    • neverDefault

      boolean neverDefault
      Returns true if the cache initializer never returns the default value of its parameter type at runtime. For reference types the default value is null, for primitive values 0. By default, default values in cache initializers are allowed. The DSL may use the default state of a cache for optimizations in the generated code layout. The DSL informs with a warning when the use of this property has an effect and is recommended to be set. Alternatively to specifying neverDefault(), the NeverDefault annotation may be used on the method or field bound by the cache initializer.

      If a cache initializer returns the illegal default value when this property is set to true then the node will throw an IllegalStateException at runtime.

      Since:
      23.0
      See Also:
      Default:
      false