@Retention(value=CLASS) @Target(value=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:
NodeField
for the enclosing node.new
keyword
as method name.ImportStatic
.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 ...
}
@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)
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
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)
private
.
@Specialization
void s(int operand, @Cached
("transformLocal(operand)") int cachedOperand) {
}
int transformLocal(int operand) {
return operand & 0x42;
}
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();
}
}
BranchProfile.create()
is used to instantiate the
BranchProfile
instance.
@Specialization
void s(int operand, @Cached
("create()") BranchProfile profile) {
}
Specialization.guards()
,
Specialization.replaces()
,
Specialization.limit()
,
ImportStatic
Modifier and Type | Optional Element and Description |
---|---|
boolean |
adopt
Specifies whether the cached parameter values of type
NodeInterface should be adopted
as its child by the current node. |
boolean |
allowUncached
Allows the
Cached.value() to be used for Cached.uncached() . |
int |
dimensions
Specifies the number of array dimensions to be marked as
compilation
final . |
boolean |
inline
Enables inlining of the cached parameter if supported.
|
String |
inlineMethod
Specifies an alternative method name for node object inlining.
|
boolean |
neverDefault
Returns
true if the cache initializer never returns the default value of its
parameter type at runtime. |
String[] |
parameters
Specifies the bindings used for the $parameters variable in cached or uncached initializers.
|
String |
uncached
Defines the initializer that is used for
uncached nodes or uncached
versions of exported library messages. |
String |
value
Defines the initializer expression of the cached parameter value.
|
boolean |
weak
If set to
true then weak references will be used to refer to this cached value
in the generated node. |
public abstract String uncached
uncached
nodes or uncached
versions of exported library messages.GenerateUncached
public abstract int dimensions
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.CompilerDirectives.CompilationFinal.dimensions()
public abstract boolean allowUncached
Cached.value()
to be used for Cached.uncached()
. This is useful if the
expression is the same for Cached.value()
and Cached.uncached()
. By setting
Cached.allowUncached()
to true
it is not necessary to repeat the
Cached.value()
expression in the Cached.uncached()
expression. This flag cannot be set in
combination with Cached.uncached()
.public abstract boolean inline
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:
GenerateInline
is set to true
for the declaring specializing node
type.
GenerateCached.alwaysInlineCached()
property is set to true
for
the declaring specializing node type.
GenerateInline
enabled
but GenerateCached
disabled.
InlineSupport
public abstract String inlineMethod
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.public abstract String[] parameters
public abstract boolean weak
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; } }
TruffleWeakReference
public abstract boolean adopt
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.
public abstract boolean neverDefault
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 Cached.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.
NeverDefault