Skip to content

Commit 28d3c7e

Browse files
committed
[GR-64005] [GR-69274] Support operation class inheritance and fix inheritance order.
PullRequest: graal/22023
2 parents 56106df + 1fc75dc commit 28d3c7e

File tree

8 files changed

+426
-62
lines changed

8 files changed

+426
-62
lines changed

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
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.
1313
* 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`).
14+
* 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.
1415

1516
## Version 25.0
1617
* 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.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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.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.GenerateBytecode;
52+
import com.oracle.truffle.api.bytecode.OperationProxy;
53+
import com.oracle.truffle.api.dsl.GenerateCached;
54+
import com.oracle.truffle.api.dsl.GenerateInline;
55+
import com.oracle.truffle.api.dsl.Specialization;
56+
import com.oracle.truffle.api.frame.FrameDescriptor;
57+
import com.oracle.truffle.api.nodes.Node;
58+
59+
public class GR64005Test {
60+
private static final BytecodeDSLTestLanguage LANGUAGE = null;
61+
62+
private static OperationProxyTestRootNode parse(BytecodeParser<OperationProxyTestRootNodeGen.Builder> builder) {
63+
BytecodeRootNodes<OperationProxyTestRootNode> nodes = OperationProxyTestRootNodeGen.create(LANGUAGE, BytecodeConfig.DEFAULT, builder);
64+
return nodes.getNode(0);
65+
}
66+
67+
@Test
68+
public void testSubclassSpecializationsOrder() {
69+
OperationProxyTestRootNode root = parse(b -> {
70+
b.beginRoot();
71+
b.beginReturn();
72+
b.beginNodeInheritanceTestSubclass();
73+
b.emitLoadConstant(4);
74+
b.endNodeInheritanceTestSubclass();
75+
b.endReturn();
76+
b.endRoot();
77+
});
78+
79+
Object result = root.getCallTarget().call();
80+
assertEquals(42, result);
81+
}
82+
83+
@GenerateCached(false)
84+
public abstract static class BaseNode extends Node {
85+
abstract int execute(Object arg);
86+
87+
@Specialization(guards = "arg < 10")
88+
public static int shouldBeFirst(@SuppressWarnings("unused") int arg) {
89+
return 42;
90+
}
91+
}
92+
93+
@GenerateInline(false)
94+
@OperationProxy.Proxyable
95+
public abstract static class NodeInheritanceTestSubclassNode extends BaseNode {
96+
@Specialization
97+
public static int shouldBeSecond(@SuppressWarnings("unused") int arg) {
98+
return -1;
99+
}
100+
}
101+
102+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class)
103+
@OperationProxy(NodeInheritanceTestSubclassNode.class)
104+
abstract static class OperationProxyTestRootNode extends DebugBytecodeRootNode implements BytecodeRootNode {
105+
106+
protected OperationProxyTestRootNode(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
107+
super(language, frameDescriptor);
108+
}
109+
}
110+
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
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.GenerateBytecode;
52+
import com.oracle.truffle.api.bytecode.Operation;
53+
import com.oracle.truffle.api.bytecode.test.error_tests.ExpectError;
54+
import com.oracle.truffle.api.dsl.Specialization;
55+
import com.oracle.truffle.api.frame.FrameDescriptor;
56+
57+
/**
58+
* Test for inheriting classes of operations to share code between them.
59+
*/
60+
public class InheritanceTest {
61+
62+
private static final BytecodeDSLTestLanguage LANGUAGE = null;
63+
64+
private static InheritanceTestRootNode parse(BytecodeParser<InheritanceTestRootNodeGen.Builder> builder) {
65+
BytecodeRootNodes<InheritanceTestRootNode> nodes = InheritanceTestRootNodeGen.create(LANGUAGE, BytecodeConfig.DEFAULT, builder);
66+
return nodes.getNode(0);
67+
}
68+
69+
@Test
70+
public void testSubclassSpecializationsOrder() {
71+
InheritanceTestRootNode root = parse(b -> {
72+
b.beginRoot();
73+
b.beginReturn();
74+
b.beginSubClass();
75+
b.emitLoadArgument(0);
76+
b.endSubClass();
77+
b.endReturn();
78+
b.endRoot();
79+
});
80+
81+
assertEquals("BaseBaseClass.s0", root.getCallTarget().call(3));
82+
assertEquals("BaseClass.s0", root.getCallTarget().call(2));
83+
assertEquals("SubClass.s0", root.getCallTarget().call(1));
84+
}
85+
86+
@GenerateBytecode(//
87+
languageClass = BytecodeDSLTestLanguage.class)
88+
abstract static class InheritanceTestRootNode extends DebugBytecodeRootNode implements BytecodeRootNode {
89+
90+
protected InheritanceTestRootNode(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
91+
super(language, frameDescriptor);
92+
}
93+
94+
static class BaseBaseClass {
95+
// using guards for subclasses are possible
96+
// this allows to make the different only in their guard
97+
@Specialization(guards = "guard3(v)")
98+
// we intentionally use the same specialization method name here
99+
// for all specializations to test referencing them properly in the generated code
100+
public static Object s0(@SuppressWarnings("unused") int v) {
101+
return "BaseBaseClass.s0";
102+
}
103+
104+
static boolean guard2(int v) {
105+
return v >= 2;
106+
}
107+
}
108+
109+
static class BaseClass extends BaseBaseClass {
110+
// make sure we can bind methods in base classes correctly
111+
@Specialization(guards = "guard2(v)")
112+
public static Object s0(int v) {
113+
return "BaseClass.s0";
114+
}
115+
116+
static boolean guard3(int v) {
117+
return v >= 3;
118+
}
119+
}
120+
121+
@Operation
122+
public static final class SubClass extends BaseClass {
123+
@Specialization(guards = "v >= 1")
124+
public static Object s0(int v) {
125+
return "SubClass.s0";
126+
}
127+
}
128+
129+
}
130+
131+
static class BaseBaseClass {
132+
@Specialization(guards = "v >= 1")
133+
public static Object s0(int v) {
134+
return v;
135+
}
136+
137+
static boolean guard1(int v) {
138+
return v >= 2;
139+
}
140+
}
141+
142+
@GenerateBytecode(//
143+
languageClass = BytecodeDSLTestLanguage.class)
144+
abstract static class InheritanceError1Node extends DebugBytecodeRootNode implements BytecodeRootNode {
145+
146+
protected InheritanceError1Node(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
147+
super(language, frameDescriptor);
148+
}
149+
150+
@ExpectError("All super types of operation classes must be declared as static nested classes of the operation root node. " +
151+
"Modify the super class 'BaseBaseClass' to be an inner class of type 'InheritanceError1Node' to resolve this or use @OperationProxy instead.")
152+
static class BaseClass extends BaseBaseClass {
153+
@Specialization(guards = "guard1(v)")
154+
public static Object s0(int v) {
155+
return v;
156+
}
157+
158+
static boolean guard1(int v) {
159+
return v >= 2;
160+
}
161+
}
162+
163+
@Operation
164+
public static final class SubClass extends BaseClass {
165+
@Specialization(guards = "v >= 3")
166+
public static Object s0(int v) {
167+
return v;
168+
}
169+
}
170+
171+
}
172+
173+
@GenerateBytecode(//
174+
languageClass = BytecodeDSLTestLanguage.class)
175+
abstract static class InheritanceError2Node extends DebugBytecodeRootNode implements BytecodeRootNode {
176+
177+
protected InheritanceError2Node(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
178+
super(language, frameDescriptor);
179+
}
180+
181+
static class BaseBaseClass {
182+
183+
@ExpectError("Operation class or super class must not contain non-static members.")
184+
void foo() {
185+
}
186+
187+
}
188+
189+
static class BaseClass extends BaseBaseClass {
190+
@Specialization(guards = "guard1(v)") // bind in base-cass
191+
public static Object s0(int v) {
192+
return v;
193+
}
194+
195+
static boolean guard1(int v) {
196+
return v >= 2;
197+
}
198+
}
199+
200+
@Operation
201+
public static final class SubClass extends BaseClass {
202+
@Specialization(guards = "v >= 3")
203+
public static Object s0(int v) {
204+
return v;
205+
}
206+
207+
@ExpectError("Operation class or super class must not contain non-static members.")
208+
@Override
209+
void foo() {
210+
}
211+
212+
}
213+
214+
@GenerateBytecode(//
215+
languageClass = BytecodeDSLTestLanguage.class)
216+
abstract static class InheritanceError3Node extends DebugBytecodeRootNode implements BytecodeRootNode {
217+
218+
protected InheritanceError3Node(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
219+
super(language, frameDescriptor);
220+
}
221+
222+
@ExpectError("Operation class or super class must not be declared private. Remove the private modifier to make it visible.")
223+
private static class BaseClass {
224+
@Specialization(guards = "guard1(v)") // bind in base-cass
225+
public static Object s0(int v) {
226+
return v;
227+
}
228+
229+
static boolean guard1(int v) {
230+
return v >= 2;
231+
}
232+
}
233+
234+
@Operation
235+
public static final class SubClass extends BaseClass {
236+
237+
}
238+
}
239+
240+
}
241+
242+
}

0 commit comments

Comments
 (0)