Skip to content

Commit a1ce776

Browse files
committed
[GR-67146] Support user-defined yield operations in the Bytecode DSL.
PullRequest: graal/22050
2 parents b991b1d + bfb23ef commit a1ce776

File tree

36 files changed

+2019
-276
lines changed

36 files changed

+2019
-276
lines changed

truffle/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ 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-69188: Added `@HostCompilerDirectives.InliningRoot` to explicitly trigger host inlining for a method designed for partial evaluation. Note that on SubstrateVM, host inlining is automatically enabled for all runtime-compilable methods, while on HotSpot you must add either `@HostCompilerDirectives.InliningRoot` or `@HostCompilerDirectives.BytecodeInterpreterSwitch` to enable it. Host inlining is only enabled if Graal is enabled as a Java host compiler.
16+
* GR-67146: Specialization DSL: Added `@Bind` support for frames (including materialized frames).
17+
* 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.
1618

1719
## Version 25.0
1820
* 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
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright (c) 2024, 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.test;
42+
43+
import static org.junit.Assert.assertEquals;
44+
45+
import org.junit.Test;
46+
47+
import com.oracle.truffle.api.bytecode.BytecodeConfig;
48+
import com.oracle.truffle.api.bytecode.BytecodeParser;
49+
import com.oracle.truffle.api.bytecode.BytecodeRootNode;
50+
import com.oracle.truffle.api.bytecode.BytecodeRootNodes;
51+
import com.oracle.truffle.api.bytecode.BytecodeTier;
52+
import com.oracle.truffle.api.bytecode.ContinuationResult;
53+
import com.oracle.truffle.api.bytecode.ContinuationRootNode;
54+
import com.oracle.truffle.api.bytecode.GenerateBytecode;
55+
import com.oracle.truffle.api.bytecode.Operation;
56+
import com.oracle.truffle.api.dsl.Specialization;
57+
import com.oracle.truffle.api.frame.FrameDescriptor;
58+
import com.oracle.truffle.api.nodes.RootNode;
59+
60+
public class RootNodeOverridesTest {
61+
62+
private static final BytecodeDSLTestLanguage LANGUAGE = null;
63+
64+
private static RootNodeWithOverrides parseRootNodeWithOverrides(BytecodeParser<RootNodeWithOverridesGen.Builder> builder) {
65+
BytecodeRootNodes<RootNodeWithOverrides> nodes = RootNodeWithOverridesGen.create(LANGUAGE, BytecodeConfig.DEFAULT, builder);
66+
return nodes.getNode(0);
67+
}
68+
69+
private static RootNodeWithParentOverrides parseRootNodeWithParentOverrides(BytecodeParser<RootNodeWithParentOverridesGen.Builder> builder) {
70+
BytecodeRootNodes<RootNodeWithParentOverrides> nodes = RootNodeWithParentOverridesGen.create(LANGUAGE, BytecodeConfig.DEFAULT, builder);
71+
return nodes.getNode(0);
72+
}
73+
74+
@Test
75+
public void testPrepareForCompilation() {
76+
RootNodeWithOverrides root = parseRootNodeWithOverrides(b -> {
77+
b.beginRoot();
78+
b.beginReturn();
79+
b.emitLoadConstant(42L);
80+
b.endReturn();
81+
b.endRoot();
82+
});
83+
84+
// As long as the interpreter is uncached, it should not be ready.
85+
assertEquals(BytecodeTier.UNCACHED, root.getBytecodeNode().getTier());
86+
root.readyForCompilation = false;
87+
assertEquals(false, root.prepareForCompilation(true, 2, true));
88+
root.readyForCompilation = true;
89+
assertEquals(false, root.prepareForCompilation(true, 2, true));
90+
91+
// Transition to cached.
92+
root.getBytecodeNode().setUncachedThreshold(0);
93+
root.getCallTarget().call();
94+
assertEquals(BytecodeTier.CACHED, root.getBytecodeNode().getTier());
95+
96+
// When the interpreter is cached, the parent impl should be delegated to.
97+
root.readyForCompilation = false;
98+
assertEquals(false, root.prepareForCompilation(true, 2, true));
99+
root.readyForCompilation = true;
100+
assertEquals(true, root.prepareForCompilation(true, 2, true));
101+
}
102+
103+
@Test
104+
public void testPrepareForCompilationContinuation() {
105+
RootNodeWithOverrides root = parseRootNodeWithOverrides(b -> {
106+
b.beginRoot();
107+
b.beginYield();
108+
b.emitLoadConstant(42L);
109+
b.endYield();
110+
b.beginReturn();
111+
b.emitLoadConstant(42L);
112+
b.endReturn();
113+
b.endRoot();
114+
});
115+
116+
// As long as the interpreter is uncached, it should not be ready.
117+
ContinuationResult cont = (ContinuationResult) root.getCallTarget().call();
118+
assertEquals(BytecodeTier.UNCACHED, root.getBytecodeNode().getTier());
119+
root.readyForCompilation = false;
120+
assertEquals(false, invokePrepareForCompilation(cont.getContinuationRootNode()));
121+
root.readyForCompilation = true;
122+
assertEquals(false, invokePrepareForCompilation(cont.getContinuationRootNode()));
123+
124+
// Transition to cached.
125+
root.getBytecodeNode().setUncachedThreshold(0);
126+
cont = (ContinuationResult) root.getCallTarget().call();
127+
assertEquals(BytecodeTier.CACHED, root.getBytecodeNode().getTier());
128+
129+
// The continuation root does not transition to cached until its next execution.
130+
// Until then, it should not be ready for compilation.
131+
assertEquals(BytecodeTier.UNCACHED, cont.getBytecodeLocation().getBytecodeNode().getTier());
132+
root.readyForCompilation = false;
133+
assertEquals(false, invokePrepareForCompilation(cont.getContinuationRootNode()));
134+
root.readyForCompilation = true;
135+
assertEquals(false, invokePrepareForCompilation(cont.getContinuationRootNode()));
136+
137+
// After the continuation root transitions to cached, delegate to the root node.
138+
cont.continueWith(123L);
139+
assertEquals(BytecodeTier.CACHED, cont.getBytecodeLocation().getBytecodeNode().getTier());
140+
root.readyForCompilation = false;
141+
assertEquals(false, invokePrepareForCompilation(cont.getContinuationRootNode()));
142+
root.readyForCompilation = true;
143+
assertEquals(true, invokePrepareForCompilation(cont.getContinuationRootNode()));
144+
}
145+
146+
@Test
147+
public void testPrepareForCompilationContinuationParentOverrides() {
148+
RootNodeWithParentOverrides root = parseRootNodeWithParentOverrides(b -> {
149+
b.beginRoot();
150+
b.beginYield();
151+
b.emitLoadConstant(42L);
152+
b.endYield();
153+
b.beginReturn();
154+
b.emitLoadConstant(42L);
155+
b.endReturn();
156+
b.endRoot();
157+
});
158+
159+
ContinuationResult cont = (ContinuationResult) root.getCallTarget().call();
160+
root.readyForCompilation = false;
161+
assertEquals(false, invokePrepareForCompilation(cont.getContinuationRootNode()));
162+
root.readyForCompilation = true;
163+
assertEquals(true, invokePrepareForCompilation(cont.getContinuationRootNode()));
164+
}
165+
166+
private static boolean invokePrepareForCompilation(ContinuationRootNode cont) {
167+
try {
168+
return (boolean) cont.getClass().getMethod("prepareForCompilation", boolean.class, int.class, boolean.class).invoke(cont, true, 2, true);
169+
} catch (ReflectiveOperationException ex) {
170+
throw new AssertionError("Exception invoking prepareForCompilation", ex);
171+
}
172+
}
173+
174+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, enableYield = true, enableUncachedInterpreter = true)
175+
abstract static class RootNodeWithOverrides extends RootNode implements BytecodeRootNode {
176+
public boolean readyForCompilation = false;
177+
178+
protected RootNodeWithOverrides(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
179+
super(language, frameDescriptor);
180+
}
181+
182+
@Operation
183+
public static final class Add {
184+
@Specialization
185+
static int add(int x, int y) {
186+
return x + y;
187+
}
188+
}
189+
190+
// The generated code should delegate to this method.
191+
@Override
192+
public boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
193+
return readyForCompilation;
194+
}
195+
196+
}
197+
198+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, enableYield = true)
199+
abstract static class RootNodeWithParentOverrides extends ParentClassWithOverride implements BytecodeRootNode {
200+
201+
protected RootNodeWithParentOverrides(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
202+
super(language, frameDescriptor);
203+
}
204+
205+
@Operation
206+
public static final class Add {
207+
@Specialization
208+
static int add(int x, int y) {
209+
return x + y;
210+
}
211+
}
212+
213+
// This root node has no uncached interpreter, so there should be no override. The
214+
// continuation root node should still delegate to the parent implementation.
215+
}
216+
217+
abstract static class ParentClassWithOverride extends RootNode {
218+
public boolean readyForCompilation = false;
219+
220+
protected ParentClassWithOverride(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
221+
super(language, frameDescriptor);
222+
}
223+
224+
@Override
225+
public boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
226+
return readyForCompilation;
227+
}
228+
}
229+
230+
}

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,9 @@ protected BadOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor)
367367
super(language, frameDescriptor);
368368
}
369369

370-
private static final String ERROR_MESSAGE = "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. " +
371-
"You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it.";
370+
private static final String ERROR_MESSAGE_DELEGATED = "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final.";
371+
private static final String ERROR_MESSAGE = ERROR_MESSAGE_DELEGATED +
372+
" You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it.";
372373

373374
@ExpectError(ERROR_MESSAGE)
374375
@Override
@@ -411,7 +412,7 @@ public final boolean isCaptureFramesForTrace(boolean compiledFrame) {
411412
public final void prepareForCall() {
412413
}
413414

414-
@ExpectError(ERROR_MESSAGE)
415+
@ExpectError(ERROR_MESSAGE_DELEGATED)
415416
@Override
416417
public final boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
417418
return false;

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
/**

0 commit comments

Comments
 (0)