Skip to content

Commit 0b7275c

Browse files
eregonwoess
andcommitted
[GR-36894] Add nodes for DynamicObject
* Faster in interpreter and better footprint than DynamicObjectLibrary, because Truffle libraries do not support host inlining and have footprint overhead. * Use a more optimized guard for set() with 2 Shapes. * Only inline cache 1 Shape for GetShapeFlagsNode. * An extra read seems better than more control flow. * Migrate SL to DynamicObject nodes. * Check the old Shape Assumption im the @Specialization.assumptions * This avoids an extra call to putUncached() for host inlining, and ensures the specialization gets removed if the oldShape becomes invalid. * Use putFlags instead of cachedPutFlags to fold during host inlining. * This saves around 6 node cost for DynamicObject.PutFixedKeyNode. * It is only observable if multiple PutFixedKeyNode#put*() methods are used (= different put flags) in the native image. * Remove calls to Location#isFinal(), it's always false. * Link the replacement nodes in DynamicObjectLibrary. * Remove {PutNode,PutFixedKeyNode}#putInt, it boxes anyway so there is no value * Also other variants like putDouble are missing. * Use @fallback to avoid potential issues with a Shape becoming invalid concurrently. * Remove KeyEqualsNode and just compare keys by identity * Reduces subTreeCost of SLReadPropertyNode.readSLObject from 307 to 245. * Use the same Shape check both in guards and specialization body. * Merge inline caches of PutNode and InlinedConstKey into one. * Merge inline caches of {PutNode,PutFixedKeyNode} and PutCache into one. * Handle invalid shapes without removing all cached specializations * Removing the specialization instance itself is fine, the problem is removing all cache specialization due to the `replaces` on doUncached. * See ef7d76cd418e9ebec967e331992e24b57d3c06e0 * Add a property read micro benchmark for SL * Port documentation from DynamicObjectLibrary to DynamicObject nodes * Update docs, 16 bit are actually allowed for user Shape flags * Migrate DynamicObjectBenchmark and DynamicObjectPartialEvaluationTest * [EXPERIMENT] Manually inline ExtLocations.ObjectArrayLocation in GetNode * [EXPERIMENT] Manually inline ExtLocations.IntArrayLocation in GetNode * [EXPERIMENT] Manually inline ExtLocations.ObjectArrayLocation in PutNode * Cache the Location instead of Property in DynamicObject nodes to avoid extra indirection in interpreter * Add interpreter benchmarks for DynamicObject.{GetNode,PutNode} * It is enough to check the new Shape Assumption in PutNode * If the old Shape Assumption is invalidated, it will also invalidate the new Shape Assumption. * Fix bug where doCached was used with an old obsolete shape * [GR-69326] Avoid duplicate property lookup in DynamicObject.PutNode * Extract reusePropertyLookup() and fix RemoveKeyNode * Run all DynamicObject tests with the new DynamicObject nodes too. Co-Authored-By: Andreas Woess <[email protected]>
1 parent a58ab33 commit 0b7275c

33 files changed

+3584
-247
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import com.oracle.truffle.api.nodes.RootNode;
3737
import com.oracle.truffle.api.nodes.UnexpectedResultException;
3838
import com.oracle.truffle.api.object.DynamicObject;
39-
import com.oracle.truffle.api.object.DynamicObjectLibrary;
4039
import com.oracle.truffle.api.object.Shape;
4140
import com.oracle.truffle.runtime.OptimizedCallTarget;
4241

@@ -79,7 +78,7 @@ private TestDynamicObject newInstanceWithoutFields() {
7978
@Test
8079
public void testFieldLocation() {
8180
TestDynamicObject obj = newInstanceWithFields();
82-
DynamicObjectLibrary.getUncached().put(obj, "key", 22);
81+
DynamicObject.PutNode.getUncached().put(obj, "key", 22);
8382

8483
Object[] args = {obj, 22};
8584
OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testFieldStoreLoad");
@@ -107,7 +106,7 @@ public void testFieldLocation() {
107106
@Test
108107
public void testArrayLocation() {
109108
TestDynamicObject obj = newInstanceWithoutFields();
110-
DynamicObjectLibrary.getUncached().put(obj, "key", 22);
109+
DynamicObject.PutNode.getUncached().put(obj, "key", 22);
111110

112111
Object[] args = {obj, 22};
113112
OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testArrayStoreLoad");
@@ -137,7 +136,8 @@ private static OptimizedCallTarget makeCallTarget(AbstractTestNode testNode, Str
137136
}
138137

139138
static class TestDynamicObjectGetAndPutNode extends AbstractTestNode {
140-
@Child DynamicObjectLibrary dynamicObjectLibrary = DynamicObjectLibrary.getFactory().createDispatched(3);
139+
@Child DynamicObject.GetNode getNode = DynamicObject.GetNode.create();
140+
@Child DynamicObject.PutNode putNode = DynamicObject.PutNode.create();
141141

142142
@Override
143143
public int execute(VirtualFrame frame) {
@@ -148,22 +148,22 @@ public int execute(VirtualFrame frame) {
148148
DynamicObject obj = (DynamicObject) arg0;
149149
if (frame.getArguments().length > 1) {
150150
Object arg1 = frame.getArguments()[1];
151-
dynamicObjectLibrary.put(obj, "key", (int) arg1);
151+
putNode.put(obj, "key", (int) arg1);
152152
}
153153
int val;
154154
while (true) {
155155
val = getInt(obj, "key");
156156
if (val >= 42) {
157157
break;
158158
}
159-
dynamicObjectLibrary.put(obj, "key", val + 2);
159+
putNode.put(obj, "key", val + 2);
160160
}
161161
return val;
162162
}
163163

164164
private int getInt(DynamicObject obj, Object key) {
165165
try {
166-
return dynamicObjectLibrary.getIntOrDefault(obj, key, null);
166+
return getNode.getIntOrDefault(obj, key, null);
167167
} catch (UnexpectedResultException e) {
168168
throw CompilerDirectives.shouldNotReachHere();
169169
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
4+
*/
5+
6+
function propRead(obj) {
7+
i = 0;
8+
while(i < 10000000) {
9+
i = i + obj.foo;
10+
}
11+
return i;
12+
}
13+
14+
function run() {
15+
obj = new();
16+
obj.foo = 1;
17+
return propRead(obj);
18+
}
19+
20+
function main() {
21+
return run();
22+
}

truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,62 @@
5151
import org.junit.Test;
5252

5353
import com.oracle.truffle.api.test.AbstractLibraryTest;
54+
import org.junit.runner.RunWith;
55+
import org.junit.runners.Parameterized;
56+
import org.junit.runners.Parameterized.Parameter;
57+
import org.junit.runners.Parameterized.Parameters;
5458

59+
import java.util.List;
60+
61+
@RunWith(Parameterized.class)
5562
public class CachedFallbackTest extends AbstractLibraryTest {
5663

64+
public enum TestRun {
65+
LIBRARY,
66+
NODES;
67+
}
68+
69+
@Parameter(0) public TestRun run;
70+
71+
@Parameters(name = "{0}")
72+
public static List<TestRun> data() {
73+
return List.of(TestRun.values());
74+
}
75+
76+
record CachedGetNodeWrapper(CachedGetNode node, DynamicObject.GetNode getNode) {
77+
public Object execute(DynamicObject obj, Object key) {
78+
if (node != null) {
79+
return node.execute(obj, key);
80+
} else {
81+
return getNode.getOrDefault(obj, key, null);
82+
}
83+
}
84+
}
85+
86+
record CachedPutNodeWrapper(CachedPutNode node, DynamicObject.PutNode putNode) {
87+
public void execute(DynamicObject obj, Object key, Object value) {
88+
if (node != null) {
89+
node.execute(obj, key, value);
90+
} else {
91+
putNode.put(obj, key, value);
92+
}
93+
}
94+
}
95+
96+
CachedGetNodeWrapper getNode() {
97+
return switch (run) {
98+
case LIBRARY -> new CachedGetNodeWrapper(adopt(CachedGetNodeGen.create()), null);
99+
case NODES -> new CachedGetNodeWrapper(null, DynamicObject.GetNode.create());
100+
};
101+
}
102+
103+
CachedPutNodeWrapper putNode() {
104+
return switch (run) {
105+
case LIBRARY -> new CachedPutNodeWrapper(adopt(CachedPutNodeGen.create()), null);
106+
case NODES -> new CachedPutNodeWrapper(null, DynamicObject.PutNode.create());
107+
};
108+
}
109+
57110
@Test
58111
public void testMixedReceiverTypeSameShape() {
59112
Shape shape = Shape.newBuilder().build();
@@ -62,13 +115,13 @@ public void testMixedReceiverTypeSameShape() {
62115
String key = "key";
63116
String val = "value";
64117

65-
CachedPutNode writeNode = adopt(CachedPutNodeGen.create());
118+
var writeNode = putNode();
66119
writeNode.execute(o1, key, val);
67120
writeNode.execute(o2, key, val);
68121

69122
assertSame("expected same shape", o1.getShape(), o2.getShape());
70123

71-
CachedGetNode readNode = adopt(CachedGetNodeGen.create());
124+
var readNode = getNode();
72125
assertEquals(val, readNode.execute(o1, key));
73126
assertEquals(val, readNode.execute(o2, key));
74127
}
@@ -93,7 +146,7 @@ public void testTransition() {
93146

94147
assertSame("expected same shape", o1.getShape(), o2.getShape());
95148

96-
CachedGetNode readNode = adopt(CachedGetNodeGen.create());
149+
var readNode = getNode();
97150
assertEquals(val1, readNode.execute(o1, key1));
98151
assertEquals(val1, readNode.execute(o2, key1));
99152
assertEquals(val2, readNode.execute(o1, key2));
@@ -120,7 +173,7 @@ public void testMixedReceiverTypeSameShapeWithFallback() {
120173

121174
assertSame("expected same shape", o1.getShape(), o2.getShape());
122175

123-
CachedGetNode readNode = adopt(CachedGetNodeGen.create());
176+
var readNode = getNode();
124177
assertEquals(val1, readNode.execute(o1, key1));
125178
assertEquals(val1, readNode.execute(o2, key1));
126179
assertEquals(val2, readNode.execute(o1, key2));

truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,9 @@
5555
import org.junit.runners.Parameterized;
5656
import org.junit.runners.Parameterized.Parameters;
5757

58-
import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest;
59-
6058
@SuppressWarnings("deprecation")
6159
@RunWith(Parameterized.class)
62-
public class ConstantLocationTest extends AbstractParametrizedLibraryTest {
60+
public class ConstantLocationTest extends ParametrizedDynamicObjectTest {
6361

6462
@Parameters(name = "{0}")
6563
public static List<TestRun> data() {
@@ -82,7 +80,7 @@ private DynamicObject newInstanceWithConstant() {
8280
public void testConstantLocation() {
8381
DynamicObject object = newInstanceWithConstant();
8482

85-
DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object);
83+
DynamicObjectLibrary library = createLibrary(object);
8684

8785
Assert.assertSame(value, library.getOrDefault(object, "constant", null));
8886

@@ -113,7 +111,7 @@ public void testConstantLocation() {
113111
public void testMigrateConstantLocation() {
114112
DynamicObject object = newInstanceWithConstant();
115113

116-
DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object);
114+
DynamicObjectLibrary library = createLibrary(object);
117115

118116
Assert.assertSame(shapeWithConstant, object.getShape());
119117
Assert.assertSame(value, library.getOrDefault(object, "constant", null));
@@ -131,7 +129,7 @@ public void testAddConstantLocation() throws com.oracle.truffle.api.object.Incom
131129

132130
DynamicObject object = newInstance();
133131

134-
DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object);
132+
DynamicObjectLibrary library = createLibrary(object);
135133

136134
property.getLocation().set(object, value, rootShape, shapeWithConstant);
137135
Assert.assertSame(shapeWithConstant, object.getShape());
@@ -158,7 +156,7 @@ public void testGetConstantValue() {
158156

159157
DynamicObject object = newInstance();
160158

161-
DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object);
159+
DynamicObjectLibrary library = createLibrary(object);
162160
library.put(object, "other", "otherValue");
163161

164162
Property otherProperty = object.getShape().getProperty("other");

truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import java.util.stream.Stream;
5757

5858
import com.oracle.truffle.api.object.DynamicObject;
59-
import com.oracle.truffle.api.object.DynamicObjectLibrary;
6059
import com.oracle.truffle.api.object.Location;
6160
import com.oracle.truffle.api.object.Property;
6261
import com.oracle.truffle.api.object.Shape;
@@ -159,21 +158,21 @@ private static String shapeLocationsToString(Shape shape) {
159158
return sb.toString();
160159
}
161160

161+
@SuppressWarnings("deprecation")
162162
public static Map<Object, Object> archive(DynamicObject object) {
163-
DynamicObjectLibrary lib = DynamicObjectLibrary.getFactory().getUncached(object);
164163
Map<Object, Object> archive = new HashMap<>();
165-
for (Property property : lib.getPropertyArray(object)) {
166-
archive.put(property.getKey(), lib.getOrDefault(object, property.getKey(), null));
164+
for (Property property : object.getShape().getPropertyList()) {
165+
archive.put(property.getKey(), property.get(object, false));
167166
}
168167
return archive;
169168
}
170169

170+
@SuppressWarnings("deprecation")
171171
public static boolean verifyValues(DynamicObject object, Map<Object, Object> archive) {
172-
DynamicObjectLibrary lib = DynamicObjectLibrary.getFactory().getUncached(object);
173-
for (Property property : lib.getPropertyArray(object)) {
172+
for (Property property : object.getShape().getPropertyList()) {
174173
Object key = property.getKey();
175174
Object before = archive.get(key);
176-
Object after = lib.getOrDefault(object, key, null);
175+
Object after = property.get(object, false);
177176
assertEquals("before != after for key: " + key, after, before);
178177
}
179178
return true;

truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,21 @@
4444
import static org.hamcrest.MatcherAssert.assertThat;
4545

4646
import java.lang.invoke.MethodHandles;
47+
import java.util.List;
4748

48-
import com.oracle.truffle.api.object.DynamicObjectLibrary;
49-
import com.oracle.truffle.api.object.Shape;
5049
import org.junit.Test;
50+
import com.oracle.truffle.api.object.Shape;
51+
import org.junit.runner.RunWith;
52+
import org.junit.runners.Parameterized;
53+
import org.junit.runners.Parameterized.Parameters;
5154

52-
import com.oracle.truffle.api.test.AbstractLibraryTest;
55+
@RunWith(Parameterized.class)
56+
public class DynamicObjectConstructorTest extends ParametrizedDynamicObjectTest {
5357

54-
public class DynamicObjectConstructorTest extends AbstractLibraryTest {
58+
@Parameters(name = "{0}")
59+
public static List<TestRun> data() {
60+
return List.of(TestRun.UNCACHED_ONLY);
61+
}
5562

5663
@Test
5764
public void testIncompatibleShape() {
@@ -65,7 +72,7 @@ public void testIncompatibleShape() {
6572
public void testNonEmptyShape() {
6673
Shape emptyShape = Shape.newBuilder().layout(TestDynamicObjectDefault.class, MethodHandles.lookup()).build();
6774
TestDynamicObjectDefault obj = new TestDynamicObjectDefault(emptyShape);
68-
DynamicObjectLibrary.getUncached().put(obj, "key", "value");
75+
uncachedLibrary().put(obj, "key", "value");
6976
Shape nonEmptyShape = obj.getShape();
7077

7178
assertFails(() -> new TestDynamicObjectDefault(nonEmptyShape), IllegalArgumentException.class,

0 commit comments

Comments
 (0)