Skip to content

Commit 03373de

Browse files
committed
Implement YieldOperation
1 parent 9ece18c commit 03373de

File tree

23 files changed

+1447
-228
lines changed

23 files changed

+1447
-228
lines changed

truffle/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ This changelog summarizes major changes between Truffle versions relevant to lan
1010
* GR-67821: `TruffleLanguage.Env#createSystemThread` is now allowed to be be called from a system thread now without an explicitly entered context.
1111
* GR-67702: Specialization DSL: For nodes annotated with `@GenerateInline`, inlining warnings emitted for `@Cached` expressions are now suppressed if the inlined node is explicitly annotated with `@GenerateInline(false)`. This avoids unnecessary warnings if inlining for a node was explicitly disabled.
1212
* GR-66310: Added support for passing arrays of primitive types to native code through the Truffle NFI Panama backend.
13-
* GR-61292 Specialization DSL: Single specialization nodes no longer specialize on first execution unless they use assumptions, cached state, or multiple instances. This was done to improve the interpreter performance and memory footprint of such nodes. As a result, these nodes no longer invalidate on first execution, which means they can no longer be used as an implicit branch profile. Language implementations are encouraged to check whether they are relying on this behavior and insert explicit branch profiles instead (see `BranchProfile` or `InlinedBranchProfile`).
13+
* GR-61292: Specialization DSL: Single specialization nodes no longer specialize on first execution unless they use assumptions, cached state, or multiple instances. This was done to improve the interpreter performance and memory footprint of such nodes. As a result, these nodes no longer invalidate on first execution, which means they can no longer be used as an implicit branch profile. Language implementations are encouraged to check whether they are relying on this behavior and insert explicit branch profiles instead (see `BranchProfile` or `InlinedBranchProfile`).
1414
* GR-64005: Bytecode DSL: `@Operation` annotated classes can now inherit specializations and methods from super classes which are also declared in the same bytecode root node class. Language implementations no longer need to use operation proxies to use specialization inheritance.
1515
* GR-67146: Specialization DSL: Added `@Bind` support for frames (including materialized frames).
16+
* GR-67146: Bytecode DSL: Added support for user-defined yield operations using `@Yield`. These operations behave like the built-in yield but allow you to customize the yield result or perform custom logic on yield.
1617

1718
## Version 25.0
1819
* GR-31495 Added ability to specify language and instrument specific options using `Source.Builder.option(String, String)`. Languages may describe available source options by implementing `TruffleLanguage.getSourceOptionDescriptors()` and `TruffleInstrument.getSourceOptionDescriptors()` respectively.

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/CustomYieldTest.java

Lines changed: 849 additions & 0 deletions
Large diffs are not rendered by default.

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstructionBytecodeSizeTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,17 @@
5757
import com.oracle.truffle.api.bytecode.BytecodeRootNode;
5858
import com.oracle.truffle.api.bytecode.BytecodeRootNodes;
5959
import com.oracle.truffle.api.bytecode.BytecodeTier;
60+
import com.oracle.truffle.api.bytecode.ContinuationRootNode;
6061
import com.oracle.truffle.api.bytecode.GenerateBytecode;
6162
import com.oracle.truffle.api.bytecode.Operation;
63+
import com.oracle.truffle.api.bytecode.Yield;
64+
import com.oracle.truffle.api.dsl.Bind;
6265
import com.oracle.truffle.api.dsl.ImplicitCast;
6366
import com.oracle.truffle.api.dsl.Specialization;
6467
import com.oracle.truffle.api.dsl.TypeSystem;
6568
import com.oracle.truffle.api.dsl.TypeSystemReference;
6669
import com.oracle.truffle.api.frame.FrameDescriptor;
70+
import com.oracle.truffle.api.frame.MaterializedFrame;
6771
import com.oracle.truffle.api.impl.asm.ClassReader;
6872
import com.oracle.truffle.api.impl.asm.ClassVisitor;
6973
import com.oracle.truffle.api.impl.asm.MethodVisitor;
@@ -456,6 +460,15 @@ static double doDefault(double a0, double a1) {
456460
return 1.0d;
457461
}
458462
}
463+
464+
// Though it is a custom operation, the yield should not be outlined.
465+
@Yield
466+
static final class MyYield {
467+
@Specialization
468+
static Object doDefault(Object result, @Bind ContinuationRootNode root, @Bind MaterializedFrame frame) {
469+
return result;
470+
}
471+
}
459472
}
460473

461474
@TypeSystem

truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ meth public com.oracle.truffle.api.frame.MaterializedFrame getFrame()
242242
meth public java.lang.Object continueWith(java.lang.Object)
243243
meth public java.lang.Object getResult()
244244
meth public java.lang.String toString()
245+
meth public static com.oracle.truffle.api.bytecode.ContinuationResult create(com.oracle.truffle.api.bytecode.ContinuationRootNode,com.oracle.truffle.api.frame.MaterializedFrame,java.lang.Object)
245246
supr java.lang.Object
246247
hfds frame,result,rootNode
247248

@@ -640,6 +641,14 @@ CLSS public abstract interface !annotation com.oracle.truffle.api.bytecode.Varia
640641
intf java.lang.annotation.Annotation
641642
meth public abstract !hasdefault int startOffset()
642643

644+
CLSS public abstract interface !annotation com.oracle.truffle.api.bytecode.Yield
645+
anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=SOURCE)
646+
anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
647+
intf java.lang.annotation.Annotation
648+
meth public abstract !hasdefault boolean forceCached()
649+
meth public abstract !hasdefault java.lang.Class<? extends com.oracle.truffle.api.instrumentation.Tag>[] tags()
650+
meth public abstract !hasdefault java.lang.String javadoc()
651+
643652
CLSS public abstract interface com.oracle.truffle.api.bytecode.debug.BytecodeDebugListener
644653
meth public void afterInstructionExecute(com.oracle.truffle.api.bytecode.Instruction,java.lang.Throwable)
645654
meth public void afterRootExecute(com.oracle.truffle.api.bytecode.Instruction,java.lang.Object,java.lang.Throwable)

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/ConstantOperand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555

5656
/**
5757
* Defines a constant operand for an operation. Constant operands are supported for
58-
* {@link Operation}, {@link Instrumentation}, and {@link Prolog} operations.
58+
* {@link Operation}, {@link Instrumentation}, {@link Yield}, and {@link Prolog} operations.
5959
* <p>
6060
* Constant operands have a few benefits:
6161
* <ul>

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/ContinuationResult.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ public ContinuationResult(ContinuationRootNode rootNode, MaterializedFrame frame
105105
this.result = result;
106106
}
107107

108+
/**
109+
* Public API for creating a continuation result.
110+
* <p>
111+
* This method is mainly useful when using {@link Yield custom yield operations} since the
112+
* {@link GenerateBytecode#enableYield() built-in yield} creates continuation results
113+
* automatically.
114+
*
115+
* @since 26.0
116+
*/
117+
public static ContinuationResult create(ContinuationRootNode rootNode, MaterializedFrame frame, Object result) {
118+
return new ContinuationResult(rootNode, frame, result);
119+
}
120+
108121
/**
109122
* Resumes the continuation.
110123
* <p>

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/ContinuationRootNode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
package com.oracle.truffle.api.bytecode;
4242

4343
import com.oracle.truffle.api.TruffleLanguage;
44+
import com.oracle.truffle.api.dsl.Bind.DefaultExpression;
4445
import com.oracle.truffle.api.frame.Frame;
4546
import com.oracle.truffle.api.frame.FrameDescriptor;
4647
import com.oracle.truffle.api.nodes.RootNode;
@@ -57,6 +58,7 @@
5758
*
5859
* @since 24.2
5960
*/
61+
@DefaultExpression("$continuationRootNode")
6062
public abstract class ContinuationRootNode extends RootNode {
6163

6264
/**

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,17 @@
290290
boolean allowUnsafe() default true;
291291

292292
/**
293-
* Whether the generated interpreter should support coroutines via a {@code yield} operation.
293+
* Whether the generated interpreter should support coroutines via a built-in {@code yield}
294+
* operation.
294295
* <p>
295-
* The yield operation returns a {@link ContinuationResult} from the current point in execution.
296-
* The {@link ContinuationResult} saves the current state of the interpreter so that it can be
297-
* resumed at a later time. The yield and resume actions pass values, enabling communication
298-
* between the caller and callee.
296+
* The built-in yield operation returns a {@link ContinuationResult} from the current point in
297+
* execution. The {@link ContinuationResult} saves the current state of the interpreter so that
298+
* it can be resumed at a later time. The yield and resume actions pass values, enabling
299+
* communication between the caller and callee.
300+
* <p>
301+
* If more control over the yield process is required, consider defining a custom {@link Yield}
302+
* instead. You can enable built-in and custom yields separately (i.e., {@link #enableYield}
303+
* does not need to be {@code true} if you only use a custom yield).
299304
* <p>
300305
* Technical note: in theoretical terms, a {@link ContinuationResult} implements an asymmetric
301306
* stack-less coroutine.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.api.bytecode;
42+
43+
import java.lang.annotation.ElementType;
44+
import java.lang.annotation.Retention;
45+
import java.lang.annotation.RetentionPolicy;
46+
import java.lang.annotation.Target;
47+
48+
import com.oracle.truffle.api.frame.MaterializedFrame;
49+
import com.oracle.truffle.api.instrumentation.Tag;
50+
import com.oracle.truffle.api.interop.InteropLibrary;
51+
import com.oracle.truffle.api.dsl.Bind;
52+
53+
/**
54+
* Declares a custom yield operation. A custom yield operation has the same control flow behaviour
55+
* as the {@link GenerateBytecode#enableYield built-in yield}, but allows to customize the result
56+
* produced. For example, a custom yield can create a guest generator object instead of a
57+
* {@link ContinuationResult}, or it can perform custom logic before
58+
* {@link ContinuationResult#create(ContinuationRootNode, MaterializedFrame, Object) creating} the
59+
* continuation result.
60+
* <p>
61+
* Below is an example of a custom yield that returns a guest language generator object:
62+
*
63+
* <pre>
64+
* &#64;Yield
65+
* public static final class CustomYield {
66+
* &#64;Specialization
67+
* public static Object doYield(Object value, &#64;Bind ContinuationRootNode root, &#64;Bind MaterializedFrame frame) {
68+
* return new MyGeneratorObject(root, frame, value);
69+
* }
70+
* }
71+
* </pre>
72+
*
73+
* A custom yield operation has many of the same restrictions and capabilities of a regular
74+
* {@link Operation}, with a few differences:
75+
* <ul>
76+
* <li>It must take zero or one dynamic operand.</li>
77+
* <li>It yields a value to the caller, so it must have a return value. The result should be a
78+
* {@link InteropLibrary#isValidValue valid interop type}. Typically, the result should encapsulate
79+
* the continuation state so that the callee can resume execution at a later time.</li>
80+
* <li>It can {@link Bind} a {@link ContinuationRootNode} operand, which can be used to resume
81+
* execution at a later time. (Custom yields will typically also {@link Bind} the
82+
* {@link MaterializedFrame} to capture the interpreter state, but this is possible in regular
83+
* operations.)</li>
84+
* </ul>
85+
*
86+
* Note that {@link GenerateBytecode#enableYield} does not need to be {@code true} to use custom
87+
* yields; it is only necessary if you need the built-in yield operation. The built-in yield
88+
* operation is semantically equivalent to the following custom yield operation:
89+
*
90+
* <pre>
91+
* &#64;Yield
92+
* public static final class BuiltinYield {
93+
* &#64;Specialization
94+
* public static Object doYield(Object result, &#64;Bind ContinuationRootNode root, &#64;Bind MaterializedFrame frame) {
95+
* return ContinuationResult.create(root, frame, result);
96+
* }
97+
* }
98+
* </pre>
99+
*
100+
* @since 26.0
101+
* @see GenerateBytecode#enableYield()
102+
*/
103+
@Retention(RetentionPolicy.SOURCE)
104+
@Target({ElementType.TYPE})
105+
public @interface Yield {
106+
107+
/**
108+
* Whether executing this operation should force the uncached interpreter (if enabled) to
109+
* transition to cached.
110+
*
111+
* @since 26.0
112+
* @see Operation#forceCached()
113+
*/
114+
boolean forceCached() default false;
115+
116+
/**
117+
* The instrumentation tags that should be implicitly associated with this operation.
118+
*
119+
* @since 26.0
120+
* @see GenerateBytecode#enableTagInstrumentation()
121+
*/
122+
Class<? extends Tag>[] tags() default {};
123+
124+
/**
125+
* Optional documentation for the instrumentation. This documentation is included in the javadoc
126+
* for the generated interpreter.
127+
*
128+
* @since 26.0
129+
*/
130+
String javadoc() default "";
131+
}

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/BytecodeDebugListener.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
public interface BytecodeDebugListener {
6060

6161
/**
62-
* Invoked before an instruction is executed. This has a very significant performance cost. Only
62+
* Invoked before a root is executed. This has a very significant performance cost. Only
6363
* override this method temporarily for debugging. This method may be called on partial
6464
* evaluated code paths.
6565
*
@@ -69,9 +69,13 @@ default void beforeRootExecute(Instruction enterInstruction) {
6969
}
7070

7171
/**
72-
* Invoked before an instruction is executed. This has a very significant performance cost. Only
73-
* override this method temporarily for debugging. This method may be called on partial
74-
* evaluated code paths.
72+
* Invoked before a root is left. This has a very significant performance cost. Only override
73+
* this method temporarily for debugging. This method may be called on partial evaluated code
74+
* paths.
75+
*
76+
* @param returnValue the value returned, or null if an exception was thrown. For yields,
77+
* returnValue is the continuation result or custom yield return value.
78+
* @param t the exception thrown, or null if the root is exited normally.
7579
*
7680
* @since 24.2
7781
*/

0 commit comments

Comments
 (0)