Annotation Interface GenerateBytecode


@Retention(SOURCE) @Target(TYPE) public @interface GenerateBytecode
Generates a bytecode interpreter using the Bytecode DSL. The Bytecode DSL automatically produces an optimizing bytecode interpreter from a set of Node-like "operations". The following is an example of a Bytecode DSL interpreter with a single 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
  • Element Details

    • languageClass

      Class<? extends TruffleLanguage<?>> languageClass
      The TruffleLanguage class associated with this node.
      Since:
      24.2
    • enableUncachedInterpreter

      boolean enableUncachedInterpreter
      Whether to generate an uncached interpreter.

      The uncached interpreter improves start-up performance by executing uncached nodes instead of allocating and executing cached (specializing) nodes. The node will transition to a specializing interpreter after enough invocations/back-edges (as determined by defaultUncachedThreshold()).

      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 cached before the operation is executed (this may limit the utility of the uncached interpreter).

      Since:
      24.2
      Default:
      false
    • defaultUncachedThreshold

      String defaultUncachedThreshold
      Sets the default number of times an uncached interpreter must be invoked/resumed or branch backwards before transitioning to cached.

      The default uncached threshold expression supports a subset of Java (see the Cached documentation). It should evaluate to an int. It should be a positive value, 0, or Integer.MIN_VALUE. A threshold of 0 will cause each bytecode node to immediately transition to cached on first invocation. A threshold of Integer.MIN_VALUE forces 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 enableSerialization
      Whether 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 serialize and deserialize methods of the generated root class. The generated BytecodeRootNodes class also overrides BytecodeRootNodes.serialize(DataOutput, 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 deserialize method takes a Supplier rather than a DataInput directly. The supplier should produce a fresh DataInput each time because the input may be processed multiple times (due to reparsing).

      Since:
      24.2
      See Also:
      Default:
      false
    • enableTagInstrumentation

      boolean enableTagInstrumentation
      Whether the generated interpreter should support Truffle tag instrumentation. When instrumentation is enabled, the generated builder will define beginTag(...) and endTag(...) methods that can be used to annotate the bytecode with tags. Truffle tag instrumentation also allows you to specify implicit tagging using Operation.tags(). If tag instrumentation is enabled all tagged operations will automatically handle and insert probes from the Truffle instrumentation framework.

      Only tags that are provided by the specified Truffle language can be used.

      Since:
      24.2
      See Also:
      Default:
      false
    • enableRootTagging

      boolean enableRootTagging
      Enables automatic root tagging if instrumentation is enabled. Automatic root tagging automatically tags each root with StandardTags.RootTag if the language provides it.

      Root tagging requires the probe to be notified before the prolog is executed. Implementing this behaviour 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 custom root tag 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 enableRootBodyTagging
      Enables automatic root body tagging if instrumentation is enabled. Automatic root body tagging automatically tags each root with StandardTags.RootBodyTag if the language provides it.
      Since:
      24.2
      See Also:
      Default:
      true
    • tagTreeNodeLibrary

      Class<?> tagTreeNodeLibrary
      Allows to customize the NodeLibrary implementation that is used for tag instrumentation. This option only makes sense if enableTagInstrumentation() is set to true.

      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 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 the NodeLibrary javadoc for more details.
      Since:
      24.2
      See Also:
      Default:
      com.oracle.truffle.api.bytecode.TagTreeNodeExports.class
    • allowUnsafe

      boolean allowUnsafe
      Whether 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 behaviour may only happen due to a bug in the Bytecode DSL implementation and not language implementation code.

      Since:
      24.2
      Default:
      true
    • enableYield

      boolean enableYield
      Whether the generated interpreter should support coroutines via a built-in yield operation.

      The built-in yield operation returns a ContinuationResult from the current point in execution. The ContinuationResult saves 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.

      If more control over the yield process is required, consider defining a custom Yield instead. You can enable built-in and custom yields separately (i.e., enableYield() does not need to be true if you only use a custom yield).

      Technical note: in theoretical terms, a ContinuationResult implements an asymmetric stack-less coroutine.

      Since:
      24.2
      See Also:
      Default:
      false
    • enableMaterializedLocalAccesses

      boolean enableMaterializedLocalAccesses
      Enables materialized local accesses. Materialized local accesses allow a root node to access the locals of any outer root nodes (root nodes created by enclosing Root operations) in addition to its own locals. These accesses take the materialized frame containing 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, LoadLocalMaterialized and StoreLocalMaterialized, which implement the local accesses. Implementations can also use MaterializedLocalAccessors 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 is stored 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 enableBlockScoping
      Enables 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 to false, 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 a default 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 or LocalAccessors (which validate liveness at parse time) is encouraged when possible. The bytecode index should be a partial evaluation constant for performance reasons. The lifetime of local variables can also be accessed through introspection using LocalVariable.getStartIndex() and LocalVariable.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 BytecodeNode has no effect. Root scoping does not retain additional liveness metadata (which may be a useful footprint optimization); this also means LocalVariable.getStartIndex() and LocalVariable.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 enableQuickening
      Whether to generate quickened bytecodes for user-provided operations.

      Quickened versions of instructions support a subset of the specializations defined 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 storeBytecodeIndexInFrame
      Whether 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 follow Node parent pointers 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 use BytecodeNode.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
    • captureFramesForTrace

      boolean captureFramesForTrace
      Whether stack trace elements of the annotated root node should capture frames. This flag should be used instead of RootNode.isCaptureFramesForTrace(boolean), which the Bytecode DSL prevents you from overriding.

      When this flag is non-null, you can use BytecodeFrame.get(TruffleStackTraceElement) to access frame data from a stack trace element. The frame only supports read-only access.

      Bytecode DSL interpreters must not access frames directly using TruffleStackTraceElement.getFrame().

      Since:
      25.1
      Default:
      false
    • boxingEliminationTypes

      Class<?>[] boxingEliminationTypes
      Primitive 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:
      {}
    • inlinePrimitiveConstants

      boolean inlinePrimitiveConstants
      Whether constant operands of primitive type should be encoded directly in the bytecode. Inlining can reduce the number of memory reads required to access constants.

      Currently, inlining is only supported for ConstantOperands.

      Since:
      25.1
      Default:
      true
    • enableSpecializationIntrospection

      boolean enableSpecializationIntrospection
      Whether to generate introspection data for specializations. The data is accessible using Instruction.Argument.getSpecializationInfo().
      Since:
      24.2
      Default:
      false
    • defaultLocalValue

      String defaultLocalValue
      Specifies the default value produced when attempting to load a cleared local (i.e., a local that has not been written to).

      This attribute is mutually exclusive with illegalLocalException(): the interpreter can either produce a default value or throw a user-provided exception when loading a cleared local. If neither is explicitly specified, the interpreter defaults to throwing a FrameSlotTypeException.

      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 the Cached documentation), including other expressions like null or 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:
      ""
    • illegalLocalException

      Class<? extends RuntimeException> illegalLocalException
      Specifies the exception to throw when attempting to load a cleared local (i.e., a local that has not been written to).

      This attribute is mutually exclusive with defaultLocalValue(): the interpreter can either produce a default value or throw a user-provided exception when loading a cleared local. If neither is explicitly specified, the interpreter defaults to throwing a FrameSlotTypeException.

      When an illegal local exception is specified, the interpreter checks if a local is cleared before each load; if the local is cleared, the interpreter creates and throws an instance of the exception using the factory method. The interpreter will attempt to quicken away the clear check when possible.

      Below is an example:

      @GenerateBytecode(..., illegalLocalException = MyIllegalLocalException.class)
      abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode {
          // ...
      }
      
      class MyIllegaLocalException extends AbstractTruffleException {
          public static MyIllegalLocalException create(Node location, BytecodeNode bytecode, BytecodeLocation location, LocalVariable variable) {
              // ...
          }
      }
      
      The provided exception class must declare a static factory method for instantiating exceptions. By default, this method is named create, but this can be overridden using illegalLocalExceptionFactory().

      The factory method should return an instance of the exception class. It may take zero or more parameters of the following types (in any order):

      • Node: the current location (equivalent to @Bind("$node"))
      • BytecodeNode: the current bytecode node
      • BytecodeLocation: the current bytecode location
      • LocalVariable: an introspection object modeling the local's metadata (name, info, etc.)

      If the provided exception class is a Truffle exception (i.e., it extends AbstractTruffleException), the exception can be thrown from runtime compiled code without deoptimization; otherwise, it is considered an internal error and throwing the exception will always trigger deoptimization.

      Note: due to current limitations of the Bytecode DSL implementation, illegal local exceptions are not yet fully supported with local accessors:

      Since:
      25.1
      Default:
      com.oracle.truffle.api.frame.FrameSlotTypeException.class
    • illegalLocalExceptionFactory

      String illegalLocalExceptionFactory
      Specifies the name of the static factory method used to instantiate illegal local exceptions. This attribute is only applicable if an illegalLocalException() class is specified.
      Since:
      25.1
      See Also:
      Default:
      "create"
    • enableBytecodeDebugListener

      boolean enableBytecodeDebugListener
      Whether the BytecodeDebugListener methods should be notified by generated code. By default the debug bytecode listener is enabled if the root node implements BytecodeDebugListener. If this attribute is set to false then 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
    • variadicStackLimit

      String variadicStackLimit
      Sets the maximum number of stack slots that will be used for parameters annotated with Variadic. The value must represent a power of 2 greater than 1 and must not exceed Short.MAX_VALUE. The default value is "32". As with defaultLocalValue(), it is possible to specify this limit via an expression. If a constant value is provided, the limit is validated at compile time; otherwise, it is validated at class initialization.

      The expression supports a subset of Java (see Cached), and may include simple constants (for example, 0) or static method calls. It must evaluate to an int. Note that only static members of the root node can be bound with the expression.

      Since:
      25.0
      Default:
      "32"
    • additionalAssertions

      boolean additionalAssertions
      Enables additional assertions, that would be otherwise too costly outside testing. The additional assertions can also be enabled dynamically at build time by passing -Atruffle.dsl.AdditionalAssertions=true to the Java compiler.
      Since:
      25.0
      Default:
      false
    • enableThreadedSwitch

      boolean enableThreadedSwitch
      Enables the use of the threaded switch for the generated bytecode switch.

      The threaded-switch mechanism reduces dispatch overhead for large or frequently executed bytecode switches, improving interpreter performance. Disabling it forces a traditional (non-threaded) switch implementation, which can be useful for benchmarking or debugging but is generally slower.

      This option is enabled by default. It is not recommended to disable the threaded switch in production, as doing so may significantly reduce performance without improving stability. On JVMs that do not support HostCompilerDirectives.markThreadedSwitch(int) this flag has no effect.

      Since:
      25.1
      See Also:
      Default:
      true
    • enableInstructionTracing

      boolean enableInstructionTracing
      Enables instruction tracing support for the generated bytecode interpreter.

      If true, the interpreter is generated with tracing hooks so that InstructionTracers can be attached at run time (via BytecodeRootNodes.addInstructionTracer or BytecodeDescriptor.addInstructionTracer). Attaching a tracer causes all executed instructions to invoke the tracer before execution.

      If false, no tracing hooks are generated and any attempt to attach a tracer will throw UnsupportedOperationException.

      Since:
      25.1
      Default:
      true
    • enableInstructionRewriting

      boolean enableInstructionRewriting
      Enables instruction rewriting support for the generated bytecode interpreter.

      Instruction rewriting is used to implement peephole optimizations (e.g., remove redundant loads) and other bytecode optimizations.

      Since:
      25.1
      Default:
      true
    • enableTailCallHandlers

      boolean enableTailCallHandlers
      Enables tail call handlers for the generated bytecode interpreter.

      For more details, see the One Compilation per Bytecode Handler documentation.

      Since:
      25.1
      See Also:
      Default:
      false