Skip to content

Commit 4cf6015

Browse files
committed
[GR-64594] Bytecode DSL: Improve Builder Performance.
PullRequest: graal/20972
2 parents 2d64c2a + 56071e0 commit 4cf6015

File tree

20 files changed

+4937
-3432
lines changed

20 files changed

+4937
-3432
lines changed

truffle/CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ This changelog summarizes major changes between Truffle versions relevant to lan
55
## Version 25.0
66
* 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.
77
* GR-61493 Added `RootNode.prepareForCall` which allows root nodes to prepare themselves for use as a call target (or to validate whether they can be used as a call target).
8-
* GR-57063 Bytecode-DSL: Improved variadic support. Variadic operands compile and execute now more efficiently with fewer reallocations.
9-
* GR-57063 Bytecode-DSL: Added an `startOffset` parameter to `@Variadic` which allows to reserve a fixed number of slots in the object array for custom use.
10-
* GR-57063 Bytecode-DSL: The `@Variadic` annotation can now also be used on `@Operation` annotated classes to indicate that the operation produces a dynamic variadic return value. Dynamic variadic return values are efficiently flattened into the object array of a variadic operand.
11-
* GR-57063 Bytecode-DSL: Added a `variadicStackLimit` parameter to `@GenerateBytecode` that allows to specify how many variable arguments are stored on the stack before they are collapsed into an object array.
8+
* GR-57063 Bytecode DSL: Improved variadic support. Variadic operands compile and execute now more efficiently with fewer reallocations.
9+
* GR-57063 Bytecode DSL: Added an `startOffset` parameter to `@Variadic` which allows to reserve a fixed number of slots in the object array for custom use.
10+
* GR-57063 Bytecode DSL: The `@Variadic` annotation can now also be used on `@Operation` annotated classes to indicate that the operation produces a dynamic variadic return value. Dynamic variadic return values are efficiently flattened into the object array of a variadic operand.
11+
* GR-57063 Bytecode DSL: Added a `variadicStackLimit` parameter to `@GenerateBytecode` that allows to specify how many variable arguments are stored on the stack before they are collapsed into an object array.
1212
* GR-50017 TruffleStringIterator.NextNode and TruffleStringIterator.PreviousNode now require the iterated string's encoding as a parameter for performance reasons.
1313
* GR-63075 Java host interop again inherits public method methods from non-public base classes if public access is enabled. This was originally changed in 24.1.
1414
* GR-63201 Added `TruffleLanguage.Registration.optionalResources` and `TruffleInstrument.Registration.optionalResources` attributes to support optional resources which implementations are not available at the runtime. Optional resources, if omitted at runtime, can still be used as long as their resource path is specified via the `polyglot.engine.resourcePath.<componentId>` system property.
@@ -25,6 +25,9 @@ This changelog summarizes major changes between Truffle versions relevant to lan
2525
* GR-64488 Added `TruffleFile#getFileStoreInfo()` providing access to disk-related metadata such as total size, usable space, unallocated space and block size.
2626
* GR-43908 Added a deoptimization cycle detection to Truffle. This feature is enabled by default when running optimized on JDK 25 and later. Whenever a deoptimization cycle is detected, the compilation fails with a permanent bailout that contains the Java stack trace of the location. If this error is encountered too frequently it can be disabled by setting `compiler.DeoptCycleDetectionThreshold` to `-1`. For further information, please see the extended [deopt cycle detection](https://github.com/oracle/graal/blob/master/truffle/docs/Optimizing.md#automatic-detection-of-deoptimization-cycles) guide.
2727
* Added `compiler.DeoptCycleDetectionThreshold` and `compiler.DeoptCycleDetectionAllowedRepeats` options which allow to fine-tune the sensitivity of the deoptimization loop detection.
28+
* GR-64594 Bytecode DSL: Improved builder performance. Creating bytecode nodes with Bytecode DSL is now 40% faster and requires 5 times less temporary memory.
29+
* GR-64594 Bytecode DSL: Builder instances now have a `toString()` implementation that prints current operations as well as the instructions that have been already emitted. This should make it easier to debug problems with builder usage.
30+
* GR-64594 Bytecode DSL: Added `@GenerateBytecode(..., additionalAssertions=true)` to enable additional assertions for Bytecode DSL implementation bugs. This feature can also be enabled with `-A.truffle.dsl.AdditionalAssertions=true` at Java source compile time. These assertions are intentionally disabled by default, as they can lead to slow-downs even when assertions are disabled.
2831

2932
## Version 24.2.0
3033
* GR-60636 Truffle now stops compiling when the code cache fills up on HotSpot. A warning is printed when that happens.
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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.assertArrayEquals;
44+
import static org.junit.Assert.assertEquals;
45+
import static org.junit.Assert.assertNotEquals;
46+
import static org.junit.Assert.assertThrows;
47+
48+
import java.util.stream.IntStream;
49+
50+
import org.junit.Test;
51+
52+
import com.oracle.truffle.api.bytecode.BytecodeSupport.ConstantsBuffer;
53+
54+
public class ConstantsBufferTest {
55+
56+
@Test
57+
public void linearPathDeduplicatesBelowThreshold() {
58+
ConstantsBuffer b = new ConstantsBuffer();
59+
int a = b.add("A");
60+
int a2 = b.add(new String("A"));
61+
assertEquals(a, a2);
62+
assertEquals(1, b.materialize().length);
63+
b.clear();
64+
}
65+
66+
@Test
67+
public void migrationHappensExactlyAtThreshold() {
68+
ConstantsBuffer b = new ConstantsBuffer();
69+
IntStream.range(0, 8).forEach(i -> b.add(i));
70+
int dup = b.add(3);
71+
assertEquals(3, dup);
72+
assertEquals(8, b.materialize().length);
73+
b.clear();
74+
}
75+
76+
@Test
77+
public void mapDeduplicatesAndRehashes() {
78+
ConstantsBuffer b = new ConstantsBuffer();
79+
int n = 1_000;
80+
for (int i = 0; i < n; i++) {
81+
b.add(i);
82+
}
83+
for (int i = 0; i < n; i++) {
84+
assertEquals(i, b.add(i));
85+
}
86+
assertEquals(n, b.materialize().length);
87+
b.clear();
88+
}
89+
90+
@Test
91+
public void addNullAllocatesIndependentSlots() {
92+
ConstantsBuffer b = new ConstantsBuffer();
93+
int i1 = b.addNull();
94+
int i2 = b.addNull();
95+
assertNotEquals(i1, i2);
96+
assertArrayEquals(new Object[]{null, null},
97+
b.materialize());
98+
b.clear();
99+
}
100+
101+
@Test
102+
public void materialiseResetsForReuse() {
103+
ConstantsBuffer b = new ConstantsBuffer();
104+
b.add("X");
105+
b.materialize();
106+
assertEquals(0, b.materialize().length);
107+
b.add("Y");
108+
assertEquals(1, b.materialize().length);
109+
b.clear();
110+
}
111+
112+
@Test
113+
public void clearShrinksLargeBuffer() {
114+
ConstantsBuffer b = new ConstantsBuffer();
115+
IntStream.range(0, 600).forEach(b::add);
116+
b.materialize();
117+
// should trigger down size
118+
b.clear();
119+
b.add("Z");
120+
assertEquals(1, b.materialize().length);
121+
}
122+
123+
@Test
124+
public void collisionStress() {
125+
ConstantsBuffer b = new ConstantsBuffer();
126+
for (int i = 0; i < 10_000; i++) {
127+
b.add(new Colliding(i));
128+
}
129+
assertEquals(10_000, b.materialize().length);
130+
b.clear();
131+
}
132+
133+
@Test
134+
public void addRejectsNull() {
135+
ConstantsBuffer b = new ConstantsBuffer();
136+
assertThrows(NullPointerException.class, () -> b.add(null));
137+
assertEquals(0, b.materialize().length);
138+
b.clear();
139+
}
140+
141+
@Test
142+
public void clearWithoutMaterialiseFails() {
143+
ConstantsBuffer b = new ConstantsBuffer();
144+
b.add("A");
145+
assertThrows(IllegalStateException.class, b::clear);
146+
assertEquals(1, b.materialize().length);
147+
b.clear();
148+
}
149+
150+
@Test
151+
public void manyNulls1() {
152+
ConstantsBuffer b = new ConstantsBuffer();
153+
assertEquals(0, b.add(42));
154+
IntStream.range(0, 600).forEach(i -> b.addNull());
155+
assertEquals(0, b.add(42));
156+
157+
assertEquals(601, b.materialize().length);
158+
b.clear();
159+
}
160+
161+
@Test
162+
public void manyNulls2() {
163+
ConstantsBuffer b = new ConstantsBuffer();
164+
IntStream.range(0, 8).forEach(i -> b.add(i));
165+
IntStream.range(0, 600).forEach(i -> b.addNull());
166+
for (int i = 0; i < 8; i++) {
167+
assertEquals(i, b.add(i));
168+
}
169+
assertEquals(608, b.materialize().length);
170+
b.clear();
171+
}
172+
173+
/** distinct objects that all share the same hashCode to stress collision handling. */
174+
private static final class Colliding {
175+
final int id;
176+
177+
Colliding(int id) {
178+
this.id = id;
179+
}
180+
181+
@Override
182+
public boolean equals(Object o) {
183+
return o instanceof Colliding c && c.id == id;
184+
}
185+
186+
@Override
187+
public int hashCode() {
188+
return 42;
189+
}
190+
}
191+
192+
}

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
*/
103103
@GenerateBytecodeTestVariants({
104104
@Variant(suffix = "Base", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
105+
additionalAssertions = true, //
105106
enableYield = true, //
106107
enableMaterializedLocalAccesses = true, //
107108
enableSerialization = true, //
@@ -110,13 +111,15 @@
110111
allowUnsafe = false, //
111112
variadicStackLimit = "4")),
112113
@Variant(suffix = "Unsafe", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
114+
additionalAssertions = true, //
113115
enableYield = true, //
114116
enableMaterializedLocalAccesses = true, //
115117
enableSerialization = true, //
116118
enableTagInstrumentation = true, //
117119
enableSpecializationIntrospection = true, //
118120
variadicStackLimit = "8")),
119121
@Variant(suffix = "WithUncached", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
122+
additionalAssertions = true, //
120123
enableYield = true, //
121124
enableMaterializedLocalAccesses = true, //
122125
enableSerialization = true, //
@@ -126,6 +129,7 @@
126129
enableSpecializationIntrospection = true, //
127130
variadicStackLimit = "16")),
128131
@Variant(suffix = "WithBE", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
132+
additionalAssertions = true, //
129133
enableYield = true, //
130134
enableMaterializedLocalAccesses = true, //
131135
enableSerialization = true, //
@@ -134,6 +138,7 @@
134138
boxingEliminationTypes = {boolean.class, long.class}, //
135139
variadicStackLimit = "4")),
136140
@Variant(suffix = "WithOptimizations", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
141+
additionalAssertions = true, //
137142
enableYield = true, //
138143
enableMaterializedLocalAccesses = true, //
139144
enableSerialization = true, //
@@ -142,6 +147,7 @@
142147
defaultLocalValue = "LOCAL_DEFAULT_VALUE", //
143148
variadicStackLimit = "8")),
144149
@Variant(suffix = "WithRootScoping", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
150+
additionalAssertions = true, //
145151
enableYield = true, //
146152
enableMaterializedLocalAccesses = true, //
147153
enableSerialization = true, //
@@ -151,6 +157,7 @@
151157
defaultLocalValue = "LOCAL_DEFAULT_VALUE", //
152158
variadicStackLimit = "16")),
153159
@Variant(suffix = "WithStoreBytecodeIndexInFrame", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
160+
additionalAssertions = true, //
154161
enableYield = true, //
155162
enableMaterializedLocalAccesses = true, //
156163
enableSerialization = true, //
@@ -163,6 +170,7 @@
163170
variadicStackLimit = "4")),
164171
// A typical "production" configuration with all of the bells and whistles.
165172
@Variant(suffix = "ProductionBlockScoping", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
173+
additionalAssertions = true, //
166174
enableYield = true, //
167175
enableMaterializedLocalAccesses = true, //
168176
enableSerialization = true, //
@@ -173,6 +181,7 @@
173181
boxingEliminationTypes = {boolean.class, long.class}, //
174182
variadicStackLimit = "8")),
175183
@Variant(suffix = "ProductionRootScoping", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
184+
additionalAssertions = true, //
176185
enableYield = true, //
177186
enableMaterializedLocalAccesses = true, //
178187
enableSerialization = true, //

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/ExceptionHandlerTableTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ public void testTryFinallyNestedEarlyExitsInFinally() {
594594
b.endTryFinally();
595595
b.endRoot();
596596
});
597+
597598
assertEquals(null, root.getCallTarget().call(false));
598599
assertEquals(42L, root.getCallTarget().call(true));
599600
assertHandlers(root,

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/LocalsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ public void testMaterializedAccessUpdatesTag() {
702702
assertEquals("x", x.getName());
703703
assertNull(x.getTypeProfile());
704704

705-
// Force cached.
705+
// force cached
706706
outer.getBytecodeNode().setUncachedThreshold(0);
707707

708708
assertEquals(42L, outer.getCallTarget().call(false));

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ hfds parser
184184

185185
CLSS public final com.oracle.truffle.api.bytecode.BytecodeSupport
186186
innr public final static CloneReferenceList
187+
innr public final static ConstantsBuffer
187188
supr java.lang.Object
188189

189190
CLSS public final static com.oracle.truffle.api.bytecode.BytecodeSupport$CloneReferenceList<%0 extends java.lang.Object>
@@ -194,6 +195,17 @@ meth public void forEach(java.util.function.Consumer<{com.oracle.truffle.api.byt
194195
supr java.lang.Object
195196
hfds references,size
196197

198+
CLSS public final static com.oracle.truffle.api.bytecode.BytecodeSupport$ConstantsBuffer
199+
outer com.oracle.truffle.api.bytecode.BytecodeSupport
200+
cons public init()
201+
meth public int add(java.lang.Object)
202+
meth public int addNull()
203+
meth public java.lang.Object[] create()
204+
meth public java.lang.Object[] materialize()
205+
meth public void clear()
206+
supr java.lang.Object
207+
hfds EMPTY,EMPTY_ARRAY,HASH_THRESHOLD,INITIAL_CAPACITY,MAX_CAPACITY,constants,keys,maxSize,size,values
208+
197209
CLSS public final !enum com.oracle.truffle.api.bytecode.BytecodeTier
198210
fld public final static com.oracle.truffle.api.bytecode.BytecodeTier CACHED
199211
fld public final static com.oracle.truffle.api.bytecode.BytecodeTier UNCACHED
@@ -289,6 +301,7 @@ CLSS public abstract interface !annotation com.oracle.truffle.api.bytecode.Gener
289301
anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=SOURCE)
290302
anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
291303
intf java.lang.annotation.Annotation
304+
meth public abstract !hasdefault boolean additionalAssertions()
292305
meth public abstract !hasdefault boolean allowUnsafe()
293306
meth public abstract !hasdefault boolean enableBlockScoping()
294307
meth public abstract !hasdefault boolean enableBytecodeDebugListener()

0 commit comments

Comments
 (0)