Annotation Interface Specialization


@Retention(RUNTIME) @Target(METHOD) public @interface Specialization

Defines a method of a node subclass to represent one specialization of an operation. Multiple specializations can be defined in a node representing an operation. A specialization defines which kind of input is expected using the method signature and the annotation attributes. The specialized semantics of the operation are defined using the body of the annotated Java method. A specialization method must be declared in a class that is derived from Node that references a TypeSystem. At least one specialization must be defined per operation. If no specialization is valid for the given set of input values then an UnsupportedSpecializationException is thrown instead of invoking any specialization method.

A specialization must have at least as many parameters as there are NodeChild annotations declared for the enclosing operation node. These parameters are declared in the same order as the NodeChild annotations (linear execution order). We call such parameters dynamic input parameters. Every specialization that is declared within an operation must have an equal number of dynamic input parameters.

The supported kind of input values for a specialization are declared using guards. A specialization may provide declarative specifications for four kinds of guards:

  • Type guards optimistically assume the type of an input value. A value that matches the type is cast to its expected type automatically. Type guards are modeled using the parameter type of the specialization method. Types used for type guards must be defined in the TypeSystem. If the type of the parameter is Object then no type guard is used for the dynamic input parameter.
  • Expression guards optimistically assume the return value of a user-defined expression to be true. Expression guards are modeled using Java expressions that return a boolean value. If the guard expression returns false, the specialization is no longer applicable and the operation is re-specialized. Guard expressions are declared using the guards() attribute.
  • Event guards trigger re-specialization in case an exception is thrown in the specialization body. The rewriteOn() attribute can be used to declare a list of such exceptions. Guards of this kind are useful to avoid calculating a value twice when it is used in the guard and its specialization.
  • Assumption guards optimistically assume that the state of an Assumption remains true. Assumptions can be assigned to specializations using the assumptions() attribute.

The enclosing Node of a specialization method must have at least one public and non-final execute method. An execute method is a method that starts with 'execute'. If all execute methods declare the first parameter type as Frame, VirtualFrame or MaterializedFrame then the same frame type can be used as optional first parameter of the specialization. This parameter does not count to the number of dynamic parameters.

A specialization method may declare multiple parameters annotated with Cached. Cached parameters are initialized and stored once per specialization instantiation. For consistency between specialization declarations cached parameters must be declared last in a specialization method.

If the operation is re-specialized or if it is executed for the first time then all declared specializations of the operation are tried in declaration order until the guards of the first specialization accepts the current input values. The new specialization is then added to the chain of current specialization instances which might consist of one (monomorph) or multiple instances (polymorph). If an assumption of an instantiated specialization is violated then re-specialization is triggered again.

With guards in combination with cached parameters it is possible that multiple instances of the same specialization are created. The limit() attribute can be used to limit the number of instantiations per specialization.

Since:
0.8 or earlier
See Also:
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    Declares assumption guards that optimistically assume that the state of an Assumption remains valid.
    Declares boolean expressions that define whether or not input values are applicable to this specialization instance.
    References a specialization of a super class by its method name where this specialization is inserted before.
    Declares the expression that limits the number of specialization instantiations.
    Declares other specializations of the same operation to be replaced by this specialization.
    Class<? extends Throwable>[]
    Declares an event guards that trigger re-specialization in case an exception is thrown in the specialization body.
    int
    Instructs the specialization to unroll a specialization with multiple instances.
  • Element Details

    • insertBefore

      String insertBefore
      References a specialization of a super class by its method name where this specialization is inserted before. The declaration order of a specialization is not usable for nodes where specializations are partly declared in the super class and partly declared in a derived class. By default all specializations declared in the derived class are appended to those in the super class. This attribute can be used to override the default behavior.
      Since:
      0.8 or earlier
      Default:
      ""
    • rewriteOn

      Class<? extends Throwable>[] rewriteOn

      Declares an event guards that trigger re-specialization in case an exception is thrown in the specialization body. This attribute can be used to declare a list of such exceptions. Guards of this kind are useful to avoid calculating a value twice when it is used in the guard and its specialization.

      If an event guard exception is triggered then all instantiations of this specialization are removed. If one of theses exceptions is thrown once then no further instantiations of this specialization are going to be created for this node. In case of explicitly declared UnexpectedResultExceptions, the result from the exception will be used. For all other exception types, the next available specialization will be executed, so that the original specialization must ensure that no non-repeatable side-effect is caused until the rewrite is triggered.

      Example usage:
       @Specialization(rewriteOn = ArithmeticException.class)
       int doAddNoOverflow(int a, int b) {
           return Math.addExact(a, b);
       }
       @Specialization
       long doAddWithOverflow(int a, int b) {
           return a + b;
       }
       ...
       Example executions:
         execute(Integer.MAX_VALUE - 1, 1) => doAddNoOverflow(Integer.MAX_VALUE - 1, 1)
         execute(Integer.MAX_VALUE, 1)     => doAddNoOverflow(Integer.MAX_VALUE, 1)
                                           => throws ArithmeticException
                                           => doAddWithOverflow(Integer.MAX_VALUE, 1)
         execute(Integer.MAX_VALUE - 1, 1) => doAddWithOverflow(Integer.MAX_VALUE - 1, 1)
       

      Since:
      0.8 or earlier
      See Also:
      Default:
      {}
    • replaces

      String[] replaces

      Declares other specializations of the same operation to be replaced by this specialization. Other specializations are referenced using their unique method name. If this specialization is instantiated then all replaced specialization instances are removed and never instantiated again for this node instance. Therefore this specialization should handle strictly more inputs than which were handled by the replaced specialization, otherwise the removal of the replaced specialization will lead to unspecialized types of input values. The replaces declaration is transitive for multiple involved specializations.

      Example usage:
       @Specialization(guards = "b == 2")
       void doDivPowerTwo(int a, int b) {
           return a >> 1;
       }
       @Specialization(replaces ="doDivPowerTwo", guards = "b > 0")
       void doDivPositive(int a, int b) {
           return a / b;
       }
       ...
       Example executions with replaces="doDivPowerTwo":
         execute(4, 2) => doDivPowerTwo(4, 2)
         execute(9, 3) => doDivPositive(9, 3) // doDivPowerTwo instances get removed
         execute(4, 2) => doDivPositive(4, 2)
       Same executions without replaces="doDivPowerTwo"
         execute(4, 2) => doDivPowerTwo(4, 2)
         execute(9, 3) => doDivPositive(9, 3)
         execute(4, 2) => doDivPowerTwo(4, 2)
       

      Since:
      0.22
      See Also:
      Default:
      {}
    • guards

      String[] guards

      Declares boolean expressions that define whether or not input values are applicable to this specialization instance. Guard expressions must always return the same result for each combination of the enclosing node instance and the bound input values.

      If a guard expression does not bind any dynamic input parameters then the DSL, by default, assumes that the result will not change for this node after specialization instantiation. In other words the DSL assumes idempotence for this guard on the fast-path, by default. The Idempotent and NonIdempotent annotations may be used to configure this explicitly. The DSL will also emit warnings in case the use of such annotations is recommended. If assertions are enabled (-ea), then the DSL will assert that the idempotence property does hold at runtime.

      Guard expressions are 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 guard expressions must be boolean. 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. Non-private, static or virtual methods or fields of enclosing node.
      4. Non-private, static or virtual methods or fields of super types of the enclosing node.
      5. Public and static methods or fields imported using ImportStatic.

      Example usage:

       static boolean acceptOperand(int operand) {
           assert operand <= 42;
           return (operand & 1) == 1;
       }
      
       @Specialization(guards = {"operand <= 42", "acceptOperand(operand)"})
       void doSpecialization(int operand) {...}
       

      Since:
      0.8 or earlier
      See Also:
      Default:
      {}
    • assumptions

      String[] assumptions

      Declares assumption guards that optimistically assume that the state of an Assumption remains valid. Assumption expressions are cached once per specialization instantiation. If one of the returned assumptions gets invalidated then the specialization instance is removed. If the assumption expression returns an array of assumptions then all assumptions of the array are checked. This is limited to one-dimensional arrays.

      Assumption expressions are 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 expression must be Assumption or an array of Assumption instances. Assumption expressions are not allowed to bind to dynamic parameter values of the specialization. Bound elements without receivers are resolved using the following order:

      1. Cached parameters of the enclosing specialization.
      2. Fields defined using NodeField for the enclosing node.
      3. Non-private, static or virtual methods or fields of enclosing node.
      4. Non-private, static or virtual methods or fields of super types of the enclosing node.
      5. Public and static methods or fields imported using ImportStatic.

      Example usage:

       abstract static class DynamicObject() { abstract Shape getShape(); ... }
       abstract static class Shape() { abstract Assumption getUnmodifiedAssuption(); ... }
      
       @Specialization(guards = "operand.getShape() == cachedShape", assumptions = "cachedShape.getUnmodifiedAssumption()")
       void doAssumeUnmodifiedShape(DynamicObject operand, @Cached("operand.getShape()") Shape cachedShape) {...}
       

      Since:
      0.8 or earlier
      See Also:
      Default:
      {}
    • limit

      String limit

      Declares the expression that limits the number of specialization instantiations. The default limit for specialization instantiations is defined as "3". If the limit is exceeded no more instantiations of the enclosing specialization method are created. Please note that the existing specialization instantiations are not removed from the specialization chain. You can use replaces() to remove unnecessary specializations instances.

      The limit expression 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 limit expression must be int. Limit expressions are not allowed to bind to dynamic parameter values of the specialization. Bound elements without receivers are resolved using the following order:

      1. Cached parameters of the enclosing specialization.
      2. Fields defined using NodeField for the enclosing node.
      3. Non-private, static or virtual methods or fields of enclosing node.
      4. Non-private, static or virtual methods or fields of super types of the enclosing node.
      5. Public and static methods or fields imported using ImportStatic.

      Example usage:

       static int getCacheLimit() {
           return Integer.parseInt(System.getProperty("language.cacheLimit"));
       }
      
       @Specialization(guards = "operand == cachedOperand", limit = "getCacheLimit()")
       void doCached(Object operand, @Cached("operand") Object cachedOperand) {...}
       

      Since:
      0.8 or earlier
      See Also:
      Default:
      ""
    • unroll

      int unroll
      Instructs the specialization to unroll a specialization with multiple instances. Unrolling causes fields of the inline cache to be directly stored in the node instead of a chained inline cache. At most 8 instances of a specialization can be unrolled to avoid code explosion in the interpreter.

      A common use-case for this feature is to unroll the first instance of an inline cache. It is often the case that specializations with multiple instances are instantiated only once. By unrolling the first instance we can optimize for this common situation which may lead to footprint and interpreter performance improvements.

      This feature is prone to cause inefficiencies if used too aggressively. Extra care should be taken, e.g. the generated code should be inspected and profiled to verify that the new code is better than the previous version.

      Consider the following example:

       class MyNode extends Node {
      
           static int limit = 2;
      
           abstract int execute(int value);
      
           @Specialization(guards = "value == cachedValue", limit = "limit", unroll = 1)
           int doDefault(int value,
                           @Cached("value") int cachedValue) {
               return value;
           }
      
       }
       
      In this example we unroll the first instance of an inline cache on int values. This is equivalent to manually specifying the following specializations:
       class MyUnrollNode extends Node {
      
           static int limit = 2;
      
           abstract int execute(int value);
      
           @Specialization(guards = "value == cachedValue", limit = "1")
           int doUnrolled0(int value,
                           @Cached("value") int cachedValue) {
               return value;
           }
      
           @Specialization(guards = "value == cachedValue", limit = "limit - 1")
           int doDefault(int value,
                           @Cached("value") int cachedValue) {
               return value;
           }
       }
      
       
      Since:
      23.0
      Default:
      0