Annotation Interface GenerateBytecode
Add operation.
@GenerateBytecode(languageClass = MyLanguage.class)
public abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode {
@Operation
public static final class Add {
@Specialization
public static int doInts(int lhs, int rhs) {
return lhs + rhs;
}
@Specialization
@TruffleBoundary
public static String doStrings(String lhs, String rhs) {
return lhs + rhs;
}
}
}
The Bytecode DSL generates a node suffixed with Gen (e.g., MyBytecodeRootNodeGen)
that contains (among other things) a full bytecode encoding, an optimizing interpreter, and a
Builder class to generate and validate bytecode automatically.
A node can opt in to additional features, like an uncached
interpreter, boxing elimination, quickened instructions, and more. The fields of this annotation control which features are
included in the generated interpreter.
For information about using the Bytecode DSL, please consult the tutorial.
- Since:
- 24.2
-
Required Element Summary
Required ElementsModifier and TypeRequired ElementDescriptionClass<? extends TruffleLanguage<?>> TheTruffleLanguageclass associated with this node. -
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleanWhether to use unsafe array accesses.Class<?>[]Primitive types the interpreter should attempt to avoid boxing up.Sets the default value thatlocalsreturn when they are read without ever being written.Sets the default number of times an uncached interpreter must return, branch backwards, or yield before transitioning to cached.booleanEnables block scoping, which limits a local's lifetime to the lifetime of the enclosing Block/Root operation.booleanWhether theBytecodeDebugListenermethods should be notified by generated code.booleanEnables materialized local accesses.booleanWhether to generate quickened bytecodes for user-provided operations.booleanEnables automatic root body tagging ifinstrumentationis enabled.booleanEnables automatic root tagging ifinstrumentationis enabled.booleanWhether the generated interpreter should support serialization and deserialization.booleanWhether to generate introspection data for specializations.booleanWhether the generated interpreter should support Truffle tag instrumentation.booleanWhether to generate an uncached interpreter.booleanWhether the generated interpreter should support coroutines via ayieldoperation.booleanWhether the generated interpreter should store the bytecode index (bci) in the frame.Class<?> Allows to customize theNodeLibraryimplementation that is used for tag instrumentation.
-
Element Details
-
languageClass
Class<? extends TruffleLanguage<?>> languageClassTheTruffleLanguageclass associated with this node.- Since:
- 24.2
-
enableUncachedInterpreter
boolean enableUncachedInterpreterWhether to generate an uncached interpreter.The uncached interpreter improves start-up performance by executing
uncachednodes instead of allocating and executing cached (specializing) nodes. The node will transition to a specializing interpreter after enough invocations/back-edges (as determined bydefaultUncachedThreshold()).To generate an uncached interpreter, all operations need to support uncached execution. If an operation cannot easily support uncached execution, it can instead
force a transition to cachedbefore the operation is executed (this may limit the utility of the uncached interpreter).- Since:
- 24.2
- Default:
false
-
defaultUncachedThreshold
String defaultUncachedThresholdSets the default number of times an uncached interpreter must return, branch backwards, or yield before transitioning to cached.The default uncached threshold expression supports a subset of Java (see the
Cacheddocumentation). It should evaluate to an int. It should be a positive value,0, orInteger.MIN_VALUE. A threshold of0will cause each bytecode node to immediately transition to cached on first invocation. A threshold ofInteger.MIN_VALUEforces a bytecode node to stay uncached (i.e., it will not transition to cached).The default local value expression can be a constant literal (e.g.,
"42"), in which case the value will be validated at build time. However, the expression can also refer to static members of the bytecode root node (and validation is deferred to run time). The following example declares a default threshold of 32 that can be overridden with a system property:@GenerateBytecode(..., defaultUncachedThreshold = "DEFAULT_UNCACHED_THRESHOLD") abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { static final int DEFAULT_UNCACHED_THRESHOLD = Integer.parseInt(System.getProperty("defaultUncachedThreshold", "32")); // ... }Other expressions like static method calls are also possible. Note that instance members of the root node cannot be bound with the default uncached threshold expression for efficiency reasons.To override this default threshold for a given bytecode node, an explicit threshold can be set using
BytecodeNode.setUncachedThreshold(int).This field has no effect unless the uncached interpreter is
enabled.- Since:
- 24.2
- Default:
"16"
-
enableSerialization
boolean enableSerializationWhether the generated interpreter should support serialization and deserialization.When serialization is enabled, the Bytecode DSL generates code to convert bytecode nodes to and from a serialized byte array representation. The code effectively serializes the node's execution data (bytecode, constants, etc.) and all of its non-transient fields.
The serialization logic is defined in static
serializeanddeserializemethods of the generated root class. The generatedBytecodeRootNodesclass also overridesBytecodeRootNodes.serialize(java.io.DataOutput, com.oracle.truffle.api.bytecode.serialization.BytecodeSerializer).This feature can be used to avoid the overhead of parsing source code on start up. Note that serialization still incurs some overhead, as it does not trivially copy bytecode directly: in order to validate the bytecode (balanced stack pointers, valid branches, etc.), serialization encodes builder method calls and deserialization replays those calls.
Note that the generated
deserializemethod takes aSupplierrather than aDataInputdirectly. The supplier should produce a freshDataInputeach time because the input may be processed multiple times (due toreparsing).- Since:
- 24.2
- See Also:
- Default:
false
-
enableTagInstrumentation
boolean enableTagInstrumentationWhether the generated interpreter should support Truffle tag instrumentation. When instrumentation is enabled, the generated builder will definestartTag(...)andendTag(...)methods that can be used to annotate the bytecode withtags. Truffle tag instrumentation also allows you to specify implicit tagging usingOperation.tags(). If tag instrumentation is enabled all tagged operations will automatically handle and insertprobesfrom the Truffle instrumentation framework.Only tags that are
providedby the specifiedTruffle languagecan be used.- Since:
- 24.2
- See Also:
- Default:
false
-
enableRootTagging
boolean enableRootTaggingEnables automatic root tagging ifinstrumentationis enabled. Automatic root tagging automatically tags each root withStandardTags.RootTagif the languageprovidesit.Root tagging requires the probe to be notified before the
prologis executed. Implementing this behavior manually is not trivial and not recommended. It is recommended to use automatic root tagging. For inlining performed by the parser it may be useful to emit customroottag using the builder methods for inlined methods. This ensures that tools can still work correctly for inlined calls.- Since:
- 24.2
- See Also:
- Default:
true
-
enableRootBodyTagging
boolean enableRootBodyTaggingEnables automatic root body tagging ifinstrumentationis enabled. Automatic root body tagging automatically tags each root withStandardTags.RootBodyTagif the languageprovidesit.- Since:
- 24.2
- See Also:
- Default:
true
-
tagTreeNodeLibrary
Class<?> tagTreeNodeLibraryAllows to customize theNodeLibraryimplementation that is used for tag instrumentation. This option only makes sense ifenableTagInstrumentation()is set totrue.Common use-cases when implementing a custom tag tree node library is required:
- Allowing instruments to access the current receiver or function object.
- Implementing custom scopes for local variables instead of the default scope.
- Hiding certain local local variables or arguments from instruments.
Minimal example of a tag node library:
@ExportLibrary(value = NodeLibrary.class, receiverType = TagTreeNode.class) final class MyTagTreeNodeExports { @ExportMessage static boolean hasScope(TagTreeNode node, Frame frame) { return true; } @ExportMessage @SuppressWarnings("unused") static Object getScope(TagTreeNode node, Frame frame, boolean nodeEnter) throws UnsupportedMessageException { return new MyScope(node, frame); } }See theNodeLibraryjavadoc for more details.- Since:
- 24.2
- See Also:
- Default:
com.oracle.truffle.api.bytecode.TagTreeNodeExports.class
-
allowUnsafe
boolean allowUnsafeWhether to use unsafe array accesses.Unsafe accesses are faster, but they do not perform array bounds checks. This means it is possible (though unlikely) for unsafe accesses to cause undefined behaviour. Undefined behavior may only happen due to a bug in the Bytecode DSL implementation and not language implementation code.
- Since:
- 24.2
- Default:
true
-
enableYield
boolean enableYieldWhether the generated interpreter should support coroutines via ayieldoperation.The yield operation returns a
ContinuationResultfrom the current point in execution. TheContinuationResultsaves the current state of the interpreter so that it can be resumed at a later time. The yield and resume actions pass values, enabling communication between the caller and callee.Technical note: in theoretical terms, a
ContinuationResultimplements an asymmetric stack-less coroutine.- Since:
- 24.2
- See Also:
- Default:
false
-
enableMaterializedLocalAccesses
boolean enableMaterializedLocalAccessesEnables materialized local accesses. Materialized local accesses allow a root node to access the locals of any outer root nodes (root nodes created by enclosingRootoperations) in addition to its own locals. These accesses take thematerialized framecontaining the local as an operand. Materialized local accesses can be used to implement closures and nested functions with lexical scoping.When materialized local accesses are enabled, the interpreter defines two additional operations,
LoadLocalMaterializedandStoreLocalMaterialized, which implement the local accesses. Implementations can also useMaterializedLocalAccessors to access locals from user-defined operations.Materialized local accesses can only be used where the local is
in scope. The bytecode generator guarantees that each materialized access's local is in scope at the static location of the access, but since root nodes can be called at any time, it is still possible to execute the root node (and thus perform the access) when the local is out of scope, leading to unexpected behaviour (e.g., reading an incorrect local value). When the bytecode index isstored in the frame, the interpreter will dynamically validate each materialized access, throwing a runtime exception when the local is not in scope. Thus, to diagnose issues with invalid materialized accesses, it is recommended to enable storing the bytecode index in the frame.- Since:
- 24.2
- Default:
false
-
enableBlockScoping
boolean enableBlockScopingEnables block scoping, which limits a local's lifetime to the lifetime of the enclosing Block/Root operation. Block scoping is enabled by default. If this flag is set tofalse, locals use root scoping, which keeps locals alive for the lifetime of the root node (i.e., the entire invocation).The value of this flag significantly changes the behaviour of local variables, so the value of this flag should be decided relatively early in the development of a language.
When block scoping is enabled, all local variables are scoped to the closest enclosing Block/Root operation. When a local variable's enclosing Block ends, it falls out of scope and its value is automatically
cleared(or reset to adefault value, if provided). Locals scoped to the Root operation are not cleared on exit. Block scoping allows the interpreter to reuse a frame index for multiple locals that have disjoint lifetimes, which can reduce the frame size.With block scoping, a different set of locals can be live at different bytecode indices. The interpreter retains extra metadata to track the lifetimes of each local. The local accessor methods of
BytecodeNode(e.g.,BytecodeNode.getLocalValues(int, Frame)) take the current bytecode index as a parameter so that they can correctly compute the locals in scope. These liveness computations can require extra computation, so accessing locals using bytecode instructions orLocalAccessors(which validate liveness at parse time) is encouraged when possible. The bytecode index should be apartial evaluation constantfor performance reasons. The lifetime of local variables can also be accessed through introspection usingLocalVariable.getStartIndex()andLocalVariable.getEndIndex().When root scoping is enabled, all local variables are assigned a unique index in the frame regardless of the current source location. They are never cleared, and frame indexes are not reused. Consequently, the bytecode index parameter on the local accessor methods of
BytecodeNodehas no effect. Root scoping does not retain additional liveness metadata (which may be a useful footprint optimization); this also meansLocalVariable.getStartIndex()andLocalVariable.getEndIndex()methods do not return lifetime data.Root scoping is primarily intended for cases where the implemented language does not use block scoping. It can also be useful if the default block scoping is not flexible enough and custom scoping rules are needed.
- Since:
- 24.2
- Default:
true
-
enableQuickening
boolean enableQuickeningWhether to generate quickened bytecodes for user-provided operations.Quickened versions of instructions support a subset of the
specializationsdefined by an operation. They can improve interpreted performance by reducing footprint and requiring fewer guards.Quickened versions of operations can be specified using
ForceQuickening. When an instruction re-specializes itself, the interpreter attempts to automatically replace it with a quickened instruction.- Since:
- 24.2
- Default:
true
-
storeBytecodeIndexInFrame
boolean storeBytecodeIndexInFrameWhether the generated interpreter should store the bytecode index (bci) in the frame.By default, methods that compute location-dependent information (like
BytecodeNode.getBytecodeLocation(com.oracle.truffle.api.frame.Frame, Node)) must followNode parentpointers and scan the bytecode to compute the current bci, which is not suitable for the fast path. When this feature is enabled, an implementation can useBytecodeNode.getBytecodeIndex(com.oracle.truffle.api.frame.Frame)to obtain the bci efficiently on the fast path and use it for location-dependent computations (e.g.,BytecodeNode.getBytecodeLocation(int)).Note that operations always have fast-path access to the bci using a bind parameter (e.g.,
@Bind("$bytecodeIndex") int bci); this feature should only be enabled for fast-path bci access outside of the current operation (e.g., for closures or frame introspection). Storing the bci in the frame increases frame size and requires additional frame writes, so it can negatively affect performance.- Since:
- 24.2
- Default:
false
-
boxingEliminationTypes
Class<?>[] boxingEliminationTypesPrimitive types the interpreter should attempt to avoid boxing up. Each type should be primitive class literal (e.g.,int.class).If boxing elimination types are provided, the cached interpreter will generate instruction variants that load/store primitive values when possible. It will automatically use these instructions in a best-effort manner (falling back on boxed representations when necessary).
- Since:
- 24.2
- Default:
{}
-
enableSpecializationIntrospection
boolean enableSpecializationIntrospectionWhether to generate introspection data for specializations. The data is accessible usingInstruction.Argument.getSpecializationInfo().- Since:
- 24.2
- Default:
false
-
defaultLocalValue
String defaultLocalValueSets the default value thatlocalsreturn when they are read without ever being written. Unless a default local value is specified, loading from alocalthat was never stored into throws aFrameSlotTypeException.It is recommended for the default local value expression to refer to a static and final constant in the bytecode root node. For example:
@GenerateBytecode(..., defaultLocalValue = "DEFAULT_VALUE") abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { static final DefaultValue DEFAULT_VALUE = DefaultValue.INSTANCE; // ... }The expression supports a subset of Java (see theCacheddocumentation), including other expressions likenullor a static method call. Note that instance members of the root node cannot be bound with the default local value expression for efficiency reasons.- Since:
- 24.2
- Default:
""
-
enableBytecodeDebugListener
boolean enableBytecodeDebugListenerWhether theBytecodeDebugListenermethods should be notified by generated code. By default the debug bytecode listener is enabled if the root node implementsBytecodeDebugListener. If this attribute is set tofalsethen the debug bytecode listener won't be notified. This attribute may be useful to keep a default debug listener implementation permanently in the source code but only enable it temporarily during debug sessions.- Since:
- 24.2
- Default:
true
-