org.pkl.thirdparty.truffle.api.dsl.Cached Maven / Gradle / Ivy
Show all versions of pkl-tools Show documentation
/*
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.pkl.thirdparty.truffle.api.dsl;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.pkl.thirdparty.truffle.api.CompilerAsserts;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.CompilationFinal;
import org.pkl.thirdparty.truffle.api.dsl.InlineSupport.InlineTarget;
import org.pkl.thirdparty.truffle.api.nodes.Node;
import org.pkl.thirdparty.truffle.api.nodes.NodeInterface;
// Workaround for Eclipse formatter behaving different when running on JDK 9.
// @formatter:off
/**
*
* A parameter annotated with {@link Cached} in a {@link 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
* {@link 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
* {@link 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 {@link Node} then the {@link Node} instance is allowed
* to use the {@link Node#replace(Node)} method to replace itself. Bound elements without receivers
* are resolved using the following order:
*
* - Dynamic and cached parameters of the enclosing specialization.
* - Fields defined using {@link NodeField} for the enclosing node.
* - Public constructors of the type of the annotated parameter using the
new
keyword
* as method name.
* - Public and static methods or fields of the type of the annotated parameter.
* - 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 {@link ImportStatic}.
*
*
* The following examples explain the intended use of the {@link Cached} annotation. All of the
* examples have to be enclosed in the following node declaration:
*
*
*
* {@link NodeChild @NodeChild}("operand")
* abstract TestNode extends Node {
* abstract void execute(Object operandValue);
* // ... example here ...
* }
*
*
*
* - 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, {@code @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)
*
*
*
*
* - 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
* {@link 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 {@link UnsupportedSpecializationException} is thrown. The default
* specialization instantiation limit is 3
.
*
*
* @Specialization(guards = "operand == cachedOperand")
* void doCached(int operand, {@code @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
*
*
*
*
* - 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, {@code @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)
*
*
*
*
* - 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, {@code @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, {@code @Cached}("new()") OtherNode someNode) { * someNode.execute(operand); * } * * static class OtherNode extends Node { * * public String execute(Object value) { * throw new UnsupportedOperationException(); * } * } * ** *
* @Specialization * void s(int operand, {@code @Cached}("create()") BranchProfile profile) { * } ** *
true
it is not necessary to repeat the
* {@link #value()} expression in the {@link #uncached()} expression. This flag cannot be set in
* combination with {@link #uncached()}.
*
* @since 19.0
*/
boolean allowUncached() default false;
/**
* 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 {@link InlineTarget}. For specializing DSL nodes the {@link 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: *
-
*
- {@link GenerateInline} is set to
true
for the declaring specializing node * type. * - The {@link GenerateCached#alwaysInlineCached()} property is set to
true
for * the declaring specializing node type. * - If a target parameter type is a specializing node and has {@link GenerateInline} enabled * but {@link GenerateCached} disabled. *
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 {@link Fallback}. Weak cached
* parameters that are used as part of {@link GenerateUncached 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; * } * } ** * @see org.pkl.thirdparty.truffle.api.utilities.TruffleWeakReference * @since 20.2 */ boolean weak() default false; /** * Specifies whether the cached parameter values of type {@link NodeInterface} should be adopted * as its child by the current node. The default value is
true
, therefore all
* cached values of type {@link 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 {@link NodeInterface} array and adopt is set to
* false
, then the compilation final {@link Cached#dimensions() dimensions}
* attribute needs to be specified explicitly.
*
* @since 20.2
*/
boolean adopt() default true;
/**
* 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 {@link #neverDefault()}, the {@link 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 {@link IllegalStateException} at runtime.
*
*
* @see NeverDefault
* @since 23.0
*/
boolean neverDefault() default false;
/**
* Allows sharing between multiple Cached parameters between multiple specializations or
* exported library messages. If no sharing is desired then the {@link Cached cached} parameter
* can be annotated with {@link Exclusive exclusive}. The DSL will indicate sharing
* opportunities to the user by showing a warning.
*
* @see Exclusive
* @since 19.0
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PARAMETER})
public @interface Shared {
/**
* Specifies the sharing group of the shared cached element. If not specified then the
* parameter name will be used as a group.
*
* @since 19.0
*/
String value() default "";
}
/**
* Disallows any sharing with other cached parameters. The DSL will indicate sharing
* opportunities to the user by showing a warning.
*
* @since 19.0
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
public @interface Exclusive {
}
}