Annotation 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 isObject
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 aboolean
value. If the guard expression returnsfalse
, the specialization is no longer applicable and the operation is re-specialized. Guard expressions are declared using theguards()
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
remainstrue
. Assumptions can be assigned to specializations using theassumptions()
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
Modifier and TypeOptional ElementDescriptionString[]
Declares assumption guards that optimistically assume that the state of anAssumption
remains valid.String[]
Declaresboolean
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.String[]
Declares other specializations of the same operation to be replaced by this specialization.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 insertBeforeReferences 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
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
Example usage:UnexpectedResultException
s, 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.@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[] replacesDeclares 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[] guardsDeclares
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
andNonIdempotent
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:- Dynamic and cached parameters of the enclosing specialization.
- Fields defined using
NodeField
for the enclosing node. - Non-private, static or virtual methods or fields of enclosing node.
- Non-private, static or virtual methods or fields of super types of the enclosing node.
- 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[] assumptionsDeclares 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 ofAssumption
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:- Cached parameters of the enclosing specialization.
- Fields defined using
NodeField
for the enclosing node. - Non-private, static or virtual methods or fields of enclosing node.
- Non-private, static or virtual methods or fields of super types of the enclosing node.
- 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 limitDeclares 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 usereplaces()
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:- Cached parameters of the enclosing specialization.
- Fields defined using
NodeField
for the enclosing node. - Non-private, static or virtual methods or fields of enclosing node.
- Non-private, static or virtual methods or fields of super types of the enclosing node.
- 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 unrollInstructs 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 onint
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
-