Skip to content

Commit e2b01b0

Browse files
committed
[GR-61292] Do not generate state bits for single specialization nodes without explicit cached state.
PullRequest: graal/21940
2 parents 918380b + 03db82f commit e2b01b0

File tree

14 files changed

+424
-158
lines changed

14 files changed

+424
-158
lines changed

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ 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`).
1314

1415
## Version 25.0
1516
* 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/BoxingEliminationTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import com.oracle.truffle.api.dsl.Cached;
6868
import com.oracle.truffle.api.dsl.Specialization;
6969
import com.oracle.truffle.api.dsl.Cached.Shared;
70+
import com.oracle.truffle.api.dsl.Fallback;
7071
import com.oracle.truffle.api.frame.FrameDescriptor;
7172
import com.oracle.truffle.api.frame.FrameSlotTypeException;
7273
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -2491,6 +2492,12 @@ static long doLong(long v) {
24912492
return v;
24922493
}
24932494

2495+
// dummy specialization so we can track quickening
2496+
@Fallback
2497+
static long doFallback(Object v) {
2498+
return (long) v;
2499+
}
2500+
24942501
}
24952502

24962503
@Operation

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,8 +1057,8 @@ public void testImplicitCustomTag() {
10571057

10581058
assertEvents(node,
10591059
events,
1060-
new Event(EventKind.ENTER, 0x0000, 0x0020, null, ExpressionTag.class),
1061-
new Event(EventKind.RETURN_VALUE, 0x0000, 0x0020, 42, ExpressionTag.class));
1060+
new Event(EventKind.ENTER, 0x0000, 0x001c, null, ExpressionTag.class),
1061+
new Event(EventKind.RETURN_VALUE, 0x0000, 0x001c, 42, ExpressionTag.class));
10621062

10631063
}
10641064

@@ -1095,8 +1095,8 @@ public void testImplicitCustomProxyTag() {
10951095

10961096
assertEvents(node,
10971097
events,
1098-
new Event(EventKind.ENTER, 0x0000, 0x0020, null, ExpressionTag.class),
1099-
new Event(EventKind.RETURN_VALUE, 0x0000, 0x0020, 42, ExpressionTag.class));
1098+
new Event(EventKind.ENTER, 0x0000, 0x001c, null, ExpressionTag.class),
1099+
new Event(EventKind.RETURN_VALUE, 0x0000, 0x001c, 42, ExpressionTag.class));
11001100

11011101
}
11021102

@@ -2142,12 +2142,12 @@ public void testYieldWithNestedRoots() {
21422142
"return");
21432143
assertEquals(123L, node.getCallTarget().call());
21442144
assertEvents(node, events,
2145-
new Event(EventKind.ENTER, 0x0000, 0x022, null, StatementTag.class),
2145+
new Event(EventKind.ENTER, 0x0000, 0x01e, null, StatementTag.class),
21462146
new Event(EventKind.ENTER, 0x0000, 0x01e, null, ExpressionTag.class),
21472147
new Event(EventKind.YIELD, 0x0000, 0x01e, 42L, ExpressionTag.class),
21482148
new Event(EventKind.RESUME, 0x0000, 0x01e, null, ExpressionTag.class),
21492149
new Event(EventKind.RETURN_VALUE, 0x0000, 0x01e, 123L, ExpressionTag.class),
2150-
new Event(EventKind.RETURN_VALUE, 0x0000, 0x022, 123L, StatementTag.class));
2150+
new Event(EventKind.RETURN_VALUE, 0x0000, 0x01e, 123L, StatementTag.class));
21512151

21522152
}
21532153

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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.dsl.test;
42+
43+
import static org.junit.Assert.assertEquals;
44+
45+
import java.lang.reflect.Field;
46+
47+
import org.junit.Test;
48+
49+
import com.oracle.truffle.api.dsl.Bind;
50+
import com.oracle.truffle.api.dsl.Cached;
51+
import com.oracle.truffle.api.dsl.GenerateInline;
52+
import com.oracle.truffle.api.dsl.NonIdempotent;
53+
import com.oracle.truffle.api.dsl.Specialization;
54+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.BasicConstantNodeGen;
55+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.CachedInlinedNodeGen;
56+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.CachedSimpleNodeGen;
57+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.InlineMultiInstanceNodeGen;
58+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.InlineSingleInstanceNodeGen;
59+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.MethodGuardNodeGen;
60+
import com.oracle.truffle.api.dsl.test.SingleSpecializationStateTestFactory.TypeGuardNodeGen;
61+
import com.oracle.truffle.api.nodes.Node;
62+
import com.oracle.truffle.api.profiles.InlinedCountingConditionProfile;
63+
64+
@DisableStateBitWidthModfication
65+
public class SingleSpecializationStateTest {
66+
67+
@GenerateInline(false)
68+
abstract static class BasicConstantNode extends BaseNode {
69+
@Specialization
70+
static Object doDefault(Object a) {
71+
return a;
72+
}
73+
}
74+
75+
@Test
76+
public void testBasicConstant() {
77+
assertEquals(0, countStateFields(BasicConstantNodeGen.class));
78+
}
79+
80+
@GenerateInline(false)
81+
abstract static class TypeGuardNode extends BaseNode {
82+
@Specialization
83+
static Object doDefault(int a) {
84+
return a;
85+
}
86+
}
87+
88+
@Test
89+
public void testTypeGuard() {
90+
assertEquals(0, countStateFields(TypeGuardNodeGen.class));
91+
}
92+
93+
@GenerateInline(false)
94+
abstract static class MethodGuardNode extends BaseNode {
95+
@Specialization(guards = "true")
96+
static Object doDefault(Object a) {
97+
return a;
98+
}
99+
}
100+
101+
@Test
102+
public void testMethodGuardNode() {
103+
assertEquals(0, countStateFields(MethodGuardNodeGen.class));
104+
}
105+
106+
@GenerateInline(false)
107+
@SuppressWarnings("truffle-neverdefault")
108+
abstract static class CachedSimpleNode extends BaseNode {
109+
@Specialization
110+
static Object doDefault(Object a,
111+
// all cached state needs to be initialized so we need a state bitset
112+
@SuppressWarnings("unused") @Cached("true") boolean cached) {
113+
return a;
114+
}
115+
}
116+
117+
@Test
118+
public void testCachedNode() {
119+
assertEquals(1, countStateFields(CachedSimpleNodeGen.class));
120+
}
121+
122+
@GenerateInline(false)
123+
@SuppressWarnings("truffle-neverdefault")
124+
abstract static class CachedInlinedNode extends BaseNode {
125+
@Specialization
126+
static Object doDefault(Object a,
127+
// generates fields but no state bitset
128+
@SuppressWarnings("unused") @Cached InlinedCountingConditionProfile cached) {
129+
return a;
130+
}
131+
}
132+
133+
@Test
134+
public void testCachedInlinedNode() {
135+
assertEquals(0, countStateFields(CachedInlinedNodeGen.class));
136+
}
137+
138+
@GenerateInline(false)
139+
@SuppressWarnings("unused")
140+
abstract static class InlineSingleInstanceNode extends BaseNode {
141+
142+
@Specialization(guards = "myGuard(a, cached1)", limit = "1")
143+
static Object doDefault(Object a,
144+
@Cached InlinedCountingConditionProfile cached1) {
145+
return a;
146+
}
147+
148+
@NonIdempotent
149+
static boolean myGuard(Object a, InlinedCountingConditionProfile profile) {
150+
return false;
151+
}
152+
}
153+
154+
@Test
155+
public void testInlineSingleInstanceNode() {
156+
assertEquals(0, countStateFields(InlineSingleInstanceNodeGen.class));
157+
}
158+
159+
@GenerateInline(false)
160+
@SuppressWarnings("unused")
161+
abstract static class InlineMultiInstanceNode extends BaseNode {
162+
163+
@Specialization(guards = "myGuard(node, a, cached1)", limit = "2")
164+
static Object doDefault(Object a,
165+
@Bind Node node,
166+
// inline condition profiles in guards need state
167+
// as it may have multiple instances
168+
@Cached InlinedCountingConditionProfile cached1) {
169+
return a;
170+
}
171+
172+
@NonIdempotent
173+
static boolean myGuard(Node node, Object a, InlinedCountingConditionProfile profile) {
174+
return false;
175+
}
176+
}
177+
178+
@Test
179+
public void testIInlineMultiInstanceNode() {
180+
assertEquals(1, countStateFields(InlineMultiInstanceNodeGen.class));
181+
}
182+
183+
private static int countStateFields(Class<?> c) {
184+
int fieldCount = 0;
185+
for (Field field : c.getDeclaredFields()) {
186+
if (field.getName().startsWith("state")) {
187+
fieldCount++;
188+
}
189+
}
190+
return fieldCount;
191+
}
192+
193+
abstract static class BaseNode extends Node {
194+
195+
abstract Object execute(Object a);
196+
197+
}
198+
199+
}

truffle/src/com.oracle.truffle.api.library.test/src/com/oracle/truffle/api/library/test/GR50026Test.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
import com.oracle.truffle.api.library.Library;
5858
import com.oracle.truffle.api.library.LibraryFactory;
5959
import com.oracle.truffle.api.nodes.Node;
60-
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
60+
import com.oracle.truffle.api.profiles.BranchProfile;
6161
import com.oracle.truffle.api.test.AbstractLibraryTest;
6262

6363
/*
@@ -147,7 +147,7 @@ public static final class TestStateObject {
147147
* version if inlined nodes are used.
148148
*/
149149
@SuppressWarnings("truffle")
150-
String m0(@Cached InlinedBranchProfile node) {
150+
String m0(@Cached BranchProfile node) {
151151
return "m0";
152152
}
153153
}

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/InstructionModel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ public boolean canUseNodeSingleton() {
613613
if (nodeData == null) {
614614
return false;
615615
}
616-
if (nodeData.needsState(ProcessorContext.getInstance())) {
616+
if (nodeData.needsState()) {
617617
return false;
618618
}
619619
for (SpecializationData specialization : nodeData.getReachableSpecializations()) {

0 commit comments

Comments
 (0)