diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java index 23612ccd0231..e726ff538e46 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.runtime.OptimizedCallTarget; @@ -79,7 +78,7 @@ private TestDynamicObject newInstanceWithoutFields() { @Test public void testFieldLocation() { TestDynamicObject obj = newInstanceWithFields(); - DynamicObjectLibrary.getUncached().put(obj, "key", 22); + DynamicObject.PutNode.getUncached().put(obj, "key", 22); Object[] args = {obj, 22}; OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testFieldStoreLoad"); @@ -107,7 +106,7 @@ public void testFieldLocation() { @Test public void testArrayLocation() { TestDynamicObject obj = newInstanceWithoutFields(); - DynamicObjectLibrary.getUncached().put(obj, "key", 22); + DynamicObject.PutNode.getUncached().put(obj, "key", 22); Object[] args = {obj, 22}; OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testArrayStoreLoad"); @@ -137,7 +136,8 @@ private static OptimizedCallTarget makeCallTarget(AbstractTestNode testNode, Str } static class TestDynamicObjectGetAndPutNode extends AbstractTestNode { - @Child DynamicObjectLibrary dynamicObjectLibrary = DynamicObjectLibrary.getFactory().createDispatched(3); + @Child DynamicObject.GetNode getNode = DynamicObject.GetNode.create(); + @Child DynamicObject.PutNode putNode = DynamicObject.PutNode.create(); @Override public int execute(VirtualFrame frame) { @@ -148,7 +148,7 @@ public int execute(VirtualFrame frame) { DynamicObject obj = (DynamicObject) arg0; if (frame.getArguments().length > 1) { Object arg1 = frame.getArguments()[1]; - dynamicObjectLibrary.put(obj, "key", (int) arg1); + putNode.put(obj, "key", (int) arg1); } int val; while (true) { @@ -156,14 +156,14 @@ public int execute(VirtualFrame frame) { if (val >= 42) { break; } - dynamicObjectLibrary.put(obj, "key", val + 2); + putNode.put(obj, "key", val + 2); } return val; } private int getInt(DynamicObject obj, Object key) { try { - return dynamicObjectLibrary.getIntOrDefault(obj, key, null); + return getNode.getIntOrDefault(obj, key, null); } catch (UnexpectedResultException e) { throw CompilerDirectives.shouldNotReachHere(); } diff --git a/truffle/benchmarks/interpreter/property_read.sl b/truffle/benchmarks/interpreter/property_read.sl new file mode 100644 index 000000000000..f3fabe7ba2ce --- /dev/null +++ b/truffle/benchmarks/interpreter/property_read.sl @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + */ + +function propRead(obj) { + i = 0; + while(i < 10000000) { + i = i + obj.foo; + } + return i; +} + +function run() { + obj = new(); + obj.foo = 1; + return propRead(obj); +} + +function main() { + return run(); +} diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java index 27b0cf05e631..f25eac8bb9ef 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java @@ -51,9 +51,62 @@ import org.junit.Test; import com.oracle.truffle.api.test.AbstractLibraryTest; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import java.util.List; + +@RunWith(Parameterized.class) public class CachedFallbackTest extends AbstractLibraryTest { + public enum TestRun { + LIBRARY, + NODES; + } + + @Parameter(0) public TestRun run; + + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.values()); + } + + record CachedGetNodeWrapper(CachedGetNode node, DynamicObject.GetNode getNode) { + public Object execute(DynamicObject obj, Object key) { + if (node != null) { + return node.execute(obj, key); + } else { + return getNode.getOrDefault(obj, key, null); + } + } + } + + record CachedPutNodeWrapper(CachedPutNode node, DynamicObject.PutNode putNode) { + public void execute(DynamicObject obj, Object key, Object value) { + if (node != null) { + node.execute(obj, key, value); + } else { + putNode.put(obj, key, value); + } + } + } + + CachedGetNodeWrapper getNode() { + return switch (run) { + case LIBRARY -> new CachedGetNodeWrapper(adopt(CachedGetNodeGen.create()), null); + case NODES -> new CachedGetNodeWrapper(null, DynamicObject.GetNode.create()); + }; + } + + CachedPutNodeWrapper putNode() { + return switch (run) { + case LIBRARY -> new CachedPutNodeWrapper(adopt(CachedPutNodeGen.create()), null); + case NODES -> new CachedPutNodeWrapper(null, DynamicObject.PutNode.create()); + }; + } + @Test public void testMixedReceiverTypeSameShape() { Shape shape = Shape.newBuilder().build(); @@ -62,13 +115,13 @@ public void testMixedReceiverTypeSameShape() { String key = "key"; String val = "value"; - CachedPutNode writeNode = adopt(CachedPutNodeGen.create()); + var writeNode = putNode(); writeNode.execute(o1, key, val); writeNode.execute(o2, key, val); assertSame("expected same shape", o1.getShape(), o2.getShape()); - CachedGetNode readNode = adopt(CachedGetNodeGen.create()); + var readNode = getNode(); assertEquals(val, readNode.execute(o1, key)); assertEquals(val, readNode.execute(o2, key)); } @@ -93,7 +146,7 @@ public void testTransition() { assertSame("expected same shape", o1.getShape(), o2.getShape()); - CachedGetNode readNode = adopt(CachedGetNodeGen.create()); + var readNode = getNode(); assertEquals(val1, readNode.execute(o1, key1)); assertEquals(val1, readNode.execute(o2, key1)); assertEquals(val2, readNode.execute(o1, key2)); @@ -120,7 +173,7 @@ public void testMixedReceiverTypeSameShapeWithFallback() { assertSame("expected same shape", o1.getShape(), o2.getShape()); - CachedGetNode readNode = adopt(CachedGetNodeGen.create()); + var readNode = getNode(); assertEquals(val1, readNode.execute(o1, key1)); assertEquals(val1, readNode.execute(o2, key1)); assertEquals(val2, readNode.execute(o1, key2)); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java index 36ecd976a881..86b3c0f81ad9 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java @@ -43,10 +43,6 @@ import java.util.Arrays; import java.util.List; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Property; -import com.oracle.truffle.api.object.Shape; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.Assert; @@ -55,11 +51,13 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class ConstantLocationTest extends AbstractParametrizedLibraryTest { +public class ConstantLocationTest extends ParametrizedDynamicObjectTest { @Parameters(name = "{0}") public static List data() { @@ -82,7 +80,7 @@ private DynamicObject newInstanceWithConstant() { public void testConstantLocation() { DynamicObject object = newInstanceWithConstant(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); Assert.assertSame(value, library.getOrDefault(object, "constant", null)); @@ -113,7 +111,7 @@ public void testConstantLocation() { public void testMigrateConstantLocation() { DynamicObject object = newInstanceWithConstant(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); Assert.assertSame(shapeWithConstant, object.getShape()); Assert.assertSame(value, library.getOrDefault(object, "constant", null)); @@ -131,7 +129,7 @@ public void testAddConstantLocation() throws com.oracle.truffle.api.object.Incom DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); property.getLocation().set(object, value, rootShape, shapeWithConstant); Assert.assertSame(shapeWithConstant, object.getShape()); @@ -158,7 +156,7 @@ public void testGetConstantValue() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "other", "otherValue"); Property otherProperty = object.getShape().getProperty("other"); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java index 134675065eea..d9877972a193 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java @@ -56,7 +56,6 @@ import java.util.stream.Stream; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Location; import com.oracle.truffle.api.object.Property; import com.oracle.truffle.api.object.Shape; @@ -159,21 +158,21 @@ private static String shapeLocationsToString(Shape shape) { return sb.toString(); } + @SuppressWarnings("deprecation") public static Map archive(DynamicObject object) { - DynamicObjectLibrary lib = DynamicObjectLibrary.getFactory().getUncached(object); Map archive = new HashMap<>(); - for (Property property : lib.getPropertyArray(object)) { - archive.put(property.getKey(), lib.getOrDefault(object, property.getKey(), null)); + for (Property property : object.getShape().getPropertyList()) { + archive.put(property.getKey(), property.get(object, false)); } return archive; } + @SuppressWarnings("deprecation") public static boolean verifyValues(DynamicObject object, Map archive) { - DynamicObjectLibrary lib = DynamicObjectLibrary.getFactory().getUncached(object); - for (Property property : lib.getPropertyArray(object)) { + for (Property property : object.getShape().getPropertyList()) { Object key = property.getKey(); Object before = archive.get(key); - Object after = lib.getOrDefault(object, key, null); + Object after = property.get(object, false); assertEquals("before != after for key: " + key, after, before); } return true; diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java index e24688056cfc..e85cdb0d7976 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java @@ -44,14 +44,21 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.lang.invoke.MethodHandles; +import java.util.List; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.junit.Test; +import com.oracle.truffle.api.object.Shape; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractLibraryTest; +@RunWith(Parameterized.class) +public class DynamicObjectConstructorTest extends ParametrizedDynamicObjectTest { -public class DynamicObjectConstructorTest extends AbstractLibraryTest { + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.UNCACHED_ONLY); + } @Test public void testIncompatibleShape() { @@ -65,7 +72,7 @@ public void testIncompatibleShape() { public void testNonEmptyShape() { Shape emptyShape = Shape.newBuilder().layout(TestDynamicObjectDefault.class, MethodHandles.lookup()).build(); TestDynamicObjectDefault obj = new TestDynamicObjectDefault(emptyShape); - DynamicObjectLibrary.getUncached().put(obj, "key", "value"); + uncachedLibrary().put(obj, "key", "value"); Shape nonEmptyShape = obj.getShape(); assertFails(() -> new TestDynamicObjectDefault(nonEmptyShape), IllegalArgumentException.class, diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java index 05864f9ab992..e73ce9fcb4ff 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java @@ -56,10 +56,6 @@ import java.util.function.IntUnaryOperator; import java.util.function.Supplier; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Property; -import com.oracle.truffle.api.object.Shape; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -68,11 +64,14 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.nodes.UnexpectedResultException; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class DynamicObjectLibraryTest extends AbstractParametrizedLibraryTest { +public class DynamicObjectLibraryTest extends ParametrizedDynamicObjectTest { @Parameter(1) public Supplier emptyObjectSupplier; private DynamicObject createEmpty() { @@ -98,33 +97,35 @@ public static Collection parameters() { return params; } - private static void addParams(Collection params, Supplier supplier) { + private static void addParams(Collection params, Supplier supplier) { for (TestRun run : TestRun.values()) { params.add(new Object[]{run, supplier}); } } - private DynamicObjectLibrary createDispatchedLibrary() { - if (run == TestRun.DISPATCHED_CACHED || run == TestRun.CACHED) { - return adopt(DynamicObjectLibrary.getFactory().createDispatched(5)); + private DynamicObjectLibraryWrapper createDispatchedLibrary() { + if (run == TestRun.CACHED_LIBRARY) { + return wrap(adopt(DynamicObjectLibrary.getFactory().createDispatched(5))); + } else if (run == TestRun.UNCACHED_LIBRARY) { + return wrap(DynamicObjectLibrary.getUncached()); } - return DynamicObjectLibrary.getUncached(); + return createLibrary(); } - private DynamicObjectLibrary createLibraryForReceiver(DynamicObject receiver) { - DynamicObjectLibrary objectLibrary = createLibrary(DynamicObjectLibrary.class, receiver); + private DynamicObjectLibraryWrapper createLibraryForReceiver(DynamicObject receiver) { + var objectLibrary = createLibrary(receiver); assertTrue(objectLibrary.accepts(receiver)); return objectLibrary; } - private DynamicObjectLibrary createLibraryForReceiverAndKey(DynamicObject receiver, Object key) { + private DynamicObjectLibraryWrapper createLibraryForReceiverAndKey(DynamicObject receiver, Object key) { assertFalse(key instanceof DynamicObject); - DynamicObjectLibrary objectLibrary = createLibrary(DynamicObjectLibrary.class, receiver); + var objectLibrary = createLibrary(receiver); assertTrue(objectLibrary.accepts(receiver)); return objectLibrary; } - private DynamicObjectLibrary createLibraryForKey(Object key) { + private DynamicObjectLibraryWrapper createLibraryForKey(Object key) { assertFalse(key instanceof DynamicObject); return createDispatchedLibrary(); } @@ -139,7 +140,7 @@ public void testGet1() throws UnexpectedResultException { uncachedPut(o2, k1, v1, 0); assertSame(o1.getShape(), o2.getShape()); - DynamicObjectLibrary getNode = createLibraryForReceiverAndKey(o1, k1); + var getNode = createLibraryForReceiverAndKey(o1, k1); assertEquals(v1, getNode.getOrDefault(o1, k1, null)); assertEquals(v1, getNode.getIntOrDefault(o1, k1, null)); assertEquals(v1, getNode.getOrDefault(o2, k1, null)); @@ -160,8 +161,7 @@ public void testGet1() throws UnexpectedResultException { assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); String missingKey = "missing"; - DynamicObjectLibrary getMissingKey; - getMissingKey = createLibraryForReceiverAndKey(o1, missingKey); + var getMissingKey = createLibraryForReceiverAndKey(o1, missingKey); assertEquals(null, getMissingKey.getOrDefault(o1, missingKey, null)); assertEquals(404, getMissingKey.getIntOrDefault(o1, missingKey, 404)); getMissingKey = createLibraryForReceiver(o1); @@ -180,7 +180,7 @@ public void testPut1() { uncachedPut(o2, key1, intval1, 0); assertSame(o1.getShape(), o2.getShape()); - DynamicObjectLibrary setNode = createLibraryForReceiverAndKey(o1, key1); + var setNode = createLibraryForReceiverAndKey(o1, key1); setNode.put(o1, key1, intval2); assertEquals(intval2, uncachedGet(o1, key1)); setNode.putInt(o1, key1, intval1); @@ -197,17 +197,17 @@ public void testPut1() { String key2 = "key2"; String strval2 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForReceiverAndKey(o1, key2); + var setNode2 = createLibraryForReceiverAndKey(o1, key2); setNode2.put(o1, key2, strval2); assertEquals(strval2, uncachedGet(o1, key2)); setNode2.putInt(o1, key2, intval1); assertEquals(intval1, uncachedGet(o1, key2)); - DynamicObjectLibrary setNode3 = createLibraryForReceiverAndKey(o1, key2); + var setNode3 = createLibraryForReceiverAndKey(o1, key2); setNode3.put(o1, key2, strval1); assertEquals(strval1, uncachedGet(o1, key2)); assertTrue(setNode3.accepts(o1)); - DynamicObjectLibrary setNode4 = createLibraryForReceiverAndKey(o1, key2); + var setNode4 = createLibraryForReceiverAndKey(o1, key2); setNode4.putInt(o1, key2, intval2); assertEquals(intval2, uncachedGet(o1, key2)); assertTrue(setNode4.accepts(o1)); @@ -224,7 +224,7 @@ public void testPutIfPresent() { uncachedPut(o2, key1, intval1, 0); assertSame(o1.getShape(), o2.getShape()); - DynamicObjectLibrary setNode = createLibraryForKey(key1); + var setNode = createLibraryForKey(key1); assertTrue(setNode.putIfPresent(o1, key1, intval2)); assertEquals(intval2, uncachedGet(o1, key1)); assertTrue(setNode.putIfPresent(o1, key1, intval1)); @@ -241,19 +241,19 @@ public void testPutIfPresent() { String key2 = "key2"; String strval2 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForReceiverAndKey(o1, key2); - assertFalse(DynamicObjectLibrary.getUncached().containsKey(o1, key2)); + var setNode2 = createLibraryForReceiverAndKey(o1, key2); + assertFalse(uncachedLibrary().containsKey(o1, key2)); assertFalse(setNode2.putIfPresent(o1, key2, strval2)); assertTrue(setNode2.accepts(o1)); assertFalse(setNode2.containsKey(o1, key2)); assertEquals(null, uncachedGet(o1, key2)); setNode2.put(o1, key2, strval2); - assertEquals(run != TestRun.CACHED, setNode2.accepts(o1)); - assertTrue(DynamicObjectLibrary.getUncached().containsKey(o1, key2)); + assertEquals(run != TestRun.CACHED_LIBRARY, setNode2.accepts(o1)); + assertTrue(uncachedLibrary().containsKey(o1, key2)); assertEquals(strval2, uncachedGet(o1, key2)); - DynamicObjectLibrary setNode3 = createLibraryForReceiverAndKey(o1, key2); + var setNode3 = createLibraryForReceiverAndKey(o1, key2); assertTrue(setNode3.putIfPresent(o1, key2, intval1)); assertEquals(intval1, uncachedGet(o1, key2)); } @@ -268,7 +268,7 @@ public void testPut2() { int v2 = 43; uncachedPut(o3, k1, v1, 0); - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.put(o1, k1, v2); assertEquals(v2, uncachedGet(o1, k1)); Assert.assertEquals(0, uncachedGetProperty(o1, k1).getFlags()); @@ -293,7 +293,7 @@ public void testPut2() { String k2 = "key2"; String v4 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForKey(k2); + var setNode2 = createLibraryForKey(k2); setNode2.put(o1, k2, v4); assertEquals(v4, uncachedGet(o1, k2)); @@ -301,7 +301,7 @@ public void testPut2() { assertEquals(v1, uncachedGet(o1, k2)); int f2 = 0x42; - DynamicObjectLibrary setNode3 = createLibraryForKey(k1); + var setNode3 = createLibraryForKey(k1); setNode3.putWithFlags(o3, k1, v1, f2); assertEquals(v1, uncachedGet(o3, k1)); @@ -319,7 +319,7 @@ public void testPutWithFlags1() { uncachedPut(o3, k1, v1, 0); int flags = 0xf; - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.putWithFlags(o1, k1, v2, flags); assertEquals(v2, uncachedGet(o1, k1)); Assert.assertEquals(flags, uncachedGetProperty(o1, k1).getFlags()); @@ -344,7 +344,7 @@ public void testPutWithFlags1() { String k2 = "key2"; String v4 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForKey(k2); + var setNode2 = createLibraryForKey(k2); setNode2.put(o1, k2, v4); assertEquals(v4, uncachedGet(o1, k2)); @@ -352,7 +352,7 @@ public void testPutWithFlags1() { assertEquals(v1, uncachedGet(o1, k2)); int f2 = 0x42; - DynamicObjectLibrary setNode3 = createLibraryForKey(k1); + var setNode3 = createLibraryForKey(k1); setNode3.putWithFlags(o3, k1, v1, f2); assertEquals(v1, uncachedGet(o3, k1)); @@ -361,7 +361,7 @@ public void testPutWithFlags1() { @Test public void testTypeIdAndShapeFlags() { - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); Object myType = newObjectType(); int flags = 42; String key = "key1"; @@ -388,18 +388,18 @@ public void testTypeIdAndShapeFlags() { lib.put(o4, key, "value"); assertSame(myType, lib.getDynamicType(o4)); - DynamicObjectLibrary cached = createLibraryForReceiver(o4); + var cached = createLibraryForReceiver(o4); assertSame(myType, cached.getDynamicType(o4)); assertSame(myType, cached.getDynamicType(o4)); Object myType2 = newObjectType(); cached.setDynamicType(o4, myType2); - assertEquals(run != TestRun.CACHED, cached.accepts(o4)); + assertEquals(run != TestRun.CACHED_LIBRARY, cached.accepts(o4)); assertSame(myType2, lib.getDynamicType(o4)); } @Test public void testShapeFlags() { - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); int flags = 42; DynamicObject o1 = createEmpty(); @@ -423,12 +423,12 @@ public void testShapeFlags() { lib.markShared(o4); assertEquals(flags, lib.getShapeFlags(o2)); - DynamicObjectLibrary cached = createLibraryForReceiver(o4); + var cached = createLibraryForReceiver(o4); assertEquals(flags, cached.getShapeFlags(o4)); assertEquals(flags, cached.getShapeFlags(o4)); int flags2 = 43; cached.setShapeFlags(o4, flags2); - assertEquals(run != TestRun.CACHED, cached.accepts(o4)); + assertEquals(run != TestRun.CACHED_LIBRARY, cached.accepts(o4)); assertEquals(flags2, lib.getShapeFlags(o4)); } @@ -438,7 +438,7 @@ public void testUpdateShapeFlags() { int f2 = 0x10; int f3 = 0x1f; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); uncachedPut(o1, "key", 42, 0); assertTrue(lib.setShapeFlags(o1, f1)); @@ -449,7 +449,7 @@ public void testUpdateShapeFlags() { assertEquals(f3, o1.getShape().getFlags()); } - private static boolean updateShapeFlags(DynamicObjectLibrary lib, DynamicObject obj, IntUnaryOperator updateFunction) { + private static boolean updateShapeFlags(DynamicObjectLibraryWrapper lib, DynamicObject obj, IntUnaryOperator updateFunction) { int oldFlags = lib.getShapeFlags(obj); int newFlags = updateFunction.applyAsInt(oldFlags); if (oldFlags == newFlags) { @@ -460,7 +460,7 @@ private static boolean updateShapeFlags(DynamicObjectLibrary lib, DynamicObject @Test public void testMakeShared() { - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); assertFalse(lib.isShared(o1)); @@ -480,7 +480,7 @@ public void testPropertyFlags() { int f2 = 0x10; int f3 = 0x1f; - DynamicObjectLibrary lib = createLibraryForKey(k1); + var lib = createLibraryForKey(k1); DynamicObject o1 = createEmpty(); uncachedPut(o1, k1, v1, 0); assertTrue(lib.setPropertyFlags(o1, k1, f1)); @@ -509,7 +509,7 @@ public void testPropertyFlags() { assertFalse(lib.setPropertyFlags(o3, k1, f1)); } - private static boolean updatePropertyFlags(DynamicObjectLibrary lib, DynamicObject obj, String key, IntUnaryOperator updateFunction) { + private static boolean updatePropertyFlags(DynamicObjectLibraryWrapper lib, DynamicObject obj, String key, IntUnaryOperator updateFunction) { Property property = lib.getProperty(obj, key); if (property == null) { return false; @@ -528,7 +528,7 @@ public void testRemove() { int v2 = 43; Object v3 = "value"; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); uncachedPut(o1, "key1", v1, 0); uncachedPut(o1, "key2", v2, 0); @@ -557,7 +557,7 @@ public void testResetShape() { int v1 = 42; int v2 = 43; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); Shape emptyShape = o1.getShape(); uncachedPut(o1, "key1", v1, 0); @@ -582,7 +582,7 @@ public void testGetKeysAndProperties() { int v2 = 43; Object v3 = "value"; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); uncachedPut(o1, "key1", v1, 1); uncachedPut(o1, "key2", v2, 2); @@ -631,7 +631,7 @@ public void testAllPropertiesMatch() { uncachedPut(o1, "key3", v3, 43); assertFalse(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); - DynamicObjectLibrary lib2 = createLibraryForKey("key1"); + var lib2 = createLibraryForKey("key1"); lib2.removeKey(o1, "key1"); assertTrue(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); } @@ -647,7 +647,7 @@ public void testGetProperty() { uncachedPut(o1, "key2", v2, 2); uncachedPut(o1, "key3", v3, 3); - DynamicObjectLibrary lib = createLibraryForReceiver(o1); + var lib = createLibraryForReceiver(o1); assertTrue(lib.accepts(o1)); for (int i = 1; i <= 3; i++) { Object key = "key" + i; @@ -666,7 +666,7 @@ public void testPutConstant1() { int v2 = 43; int flags = 0xf; - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.putConstant(o1, k1, v1, 0); assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); @@ -692,7 +692,7 @@ public void testPutConstant2() { int v2 = 43; int flags = 0xf; - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.putConstant(o1, k1, v1, 0); assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); @@ -736,15 +736,15 @@ public void testPropertyAndShapeFlags() { fillObjectWithProperties(o2, true); DynamicObject o3 = createEmpty(); fillObjectWithProperties(o3, false); - DynamicObjectLibrary.getUncached().put(o1, "k13", false); + uncachedLibrary().put(o1, "k13", false); updateAllFlags(o2, 3); updateAllFlags(o3, 3); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, o1); + var library = createLibrary(o1); assertEquals(1, library.getOrDefault(o3, "k13", null)); } private void fillObjectWithProperties(DynamicObject obj, boolean b) { - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, obj); + var library = createLibrary(obj); for (int i = 0; i < 20; i++) { Object value; @@ -767,7 +767,7 @@ private void fillObjectWithProperties(DynamicObject obj, boolean b) { } private void updateAllFlags(DynamicObject obj, int flags) { - DynamicObjectLibrary propertyFlags = createLibrary(DynamicObjectLibrary.class, obj); + var propertyFlags = createLibrary(obj); for (Property property : propertyFlags.getPropertyArray(obj)) { int oldFlags = property.getFlags(); @@ -778,28 +778,28 @@ private void updateAllFlags(DynamicObject obj, int flags) { } } - DynamicObjectLibrary shapeFlags = createLibrary(DynamicObjectLibrary.class, obj); + var shapeFlags = createLibrary(obj); shapeFlags.setShapeFlags(obj, flags); } - private static void uncachedPut(DynamicObject obj, Object key, Object value) { - DynamicObjectLibrary.getUncached().put(obj, key, value); + private void uncachedPut(DynamicObject obj, Object key, Object value) { + uncachedLibrary().put(obj, key, value); } - private static void uncachedPut(DynamicObject obj, Object key, Object value, int flags) { - DynamicObjectLibrary.getUncached().putWithFlags(obj, key, value, flags); + private void uncachedPut(DynamicObject obj, Object key, Object value, int flags) { + uncachedLibrary().putWithFlags(obj, key, value, flags); } - private static void uncachedSet(DynamicObject obj, Object key, Object value) { - DynamicObjectLibrary.getUncached().putIfPresent(obj, key, value); + private void uncachedSet(DynamicObject obj, Object key, Object value) { + uncachedLibrary().putIfPresent(obj, key, value); } - private static Object uncachedGet(DynamicObject obj, Object key) { - return DynamicObjectLibrary.getUncached().getOrDefault(obj, key, null); + private Object uncachedGet(DynamicObject obj, Object key) { + return uncachedLibrary().getOrDefault(obj, key, null); } - private static Property uncachedGetProperty(DynamicObject obj, Object key) { - return DynamicObjectLibrary.getUncached().getProperty(obj, key); + private Property uncachedGetProperty(DynamicObject obj, Object key) { + return uncachedLibrary().getProperty(obj, key); } private static Object newObjectType() { @@ -808,7 +808,7 @@ private static Object newObjectType() { } private List getKeyList(DynamicObject obj) { - DynamicObjectLibrary objectLibrary = createLibrary(DynamicObjectLibrary.class, obj); + var objectLibrary = createLibrary(obj); return Arrays.asList(objectLibrary.getKeyArray(obj)); } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java new file mode 100644 index 000000000000..7b66839ac868 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.object.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.IntUnaryOperator; +import java.util.function.Supplier; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest; + +@RunWith(Parameterized.class) +public class DynamicObjectNodesTest extends AbstractPolyglotTest { + + public enum TestRun { + CACHED, + UNCACHED; + + public boolean isCached() { + return this == CACHED; + } + } + + @Parameter(0) public TestRun run; + @Parameter(1) public Supplier emptyObjectSupplier; + + private DynamicObject createEmpty() { + return emptyObjectSupplier.get(); + } + + @Parameters + public static Collection parameters() { + Collection params = new ArrayList<>(); + + Shape shapeMin = Shape.newBuilder().build(); + Supplier minimalSupplier = () -> new TestDynamicObjectMinimal(shapeMin); + addParams(params, minimalSupplier); + + Shape shapeDef = Shape.newBuilder().layout(TestDynamicObjectDefault.class, MethodHandles.lookup()).build(); + Supplier defaultSupplier = () -> new TestDynamicObjectDefault(shapeDef); + addParams(params, defaultSupplier); + + return params; + } + + private static void addParams(Collection params, Supplier supplier) { + for (TestRun run : TestRun.values()) { + params.add(new Object[]{run, supplier}); + } + } + + private DynamicObject.GetNode createGetNode() { + return switch (run) { + case CACHED -> DynamicObject.GetNode.create(); + case UNCACHED -> DynamicObject.GetNode.getUncached(); + }; + } + + private DynamicObject.PutNode createPutNode() { + return switch (run) { + case CACHED -> DynamicObject.PutNode.create(); + case UNCACHED -> DynamicObject.PutNode.getUncached(); + }; + } + + private DynamicObject.CopyPropertiesNode createCopyPropertiesNode() { + return switch (run) { + case CACHED -> DynamicObject.CopyPropertiesNode.create(); + case UNCACHED -> DynamicObject.CopyPropertiesNode.getUncached(); + }; + } + + private DynamicObject.RemoveKeyNode createRemoveKeyNode() { + return switch (run) { + case CACHED -> DynamicObject.RemoveKeyNode.create(); + case UNCACHED -> DynamicObject.RemoveKeyNode.getUncached(); + }; + } + + private DynamicObject.ContainsKeyNode createContainsKeyNode() { + return switch (run) { + case CACHED -> DynamicObject.ContainsKeyNode.create(); + case UNCACHED -> DynamicObject.ContainsKeyNode.getUncached(); + }; + } + + private DynamicObject.GetDynamicTypeNode createGetDynamicTypeNode() { + return switch (run) { + case CACHED -> DynamicObject.GetDynamicTypeNode.create(); + case UNCACHED -> DynamicObject.GetDynamicTypeNode.getUncached(); + }; + } + + private DynamicObject.SetDynamicTypeNode createSetDynamicTypeNode() { + return switch (run) { + case CACHED -> DynamicObject.SetDynamicTypeNode.create(); + case UNCACHED -> DynamicObject.SetDynamicTypeNode.getUncached(); + }; + } + + private DynamicObject.GetShapeFlagsNode createGetShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.GetShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.GetShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.HasShapeFlagsNode createHasShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.HasShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.HasShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.ResetShapeNode createResetShapeNode() { + return switch (run) { + case CACHED -> DynamicObject.ResetShapeNode.create(); + case UNCACHED -> DynamicObject.ResetShapeNode.getUncached(); + }; + } + + private DynamicObject.SetShapeFlagsNode createSetShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.SetShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.SetShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.AddShapeFlagsNode createAddShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.AddShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.AddShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.IsSharedNode createIsSharedNode() { + return switch (run) { + case CACHED -> DynamicObject.IsSharedNode.create(); + case UNCACHED -> DynamicObject.IsSharedNode.getUncached(); + }; + } + + private DynamicObject.MarkSharedNode createMarkSharedNode() { + return switch (run) { + case CACHED -> DynamicObject.MarkSharedNode.create(); + case UNCACHED -> DynamicObject.MarkSharedNode.getUncached(); + }; + } + + private DynamicObject.SetPropertyFlagsNode createSetPropertyFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.SetPropertyFlagsNode.create(); + case UNCACHED -> DynamicObject.SetPropertyFlagsNode.getUncached(); + }; + } + + private DynamicObject.SetPropertyFlagsNode createSetPropertyFlagsNodeForKey(Object k) { + return switch (run) { + case CACHED -> DynamicObject.SetPropertyFlagsNode.create(); + case UNCACHED -> DynamicObject.SetPropertyFlagsNode.getUncached(); + }; + } + + private DynamicObject.GetPropertyFlagsNode createGetPropertyFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.GetPropertyFlagsNode.create(); + case UNCACHED -> DynamicObject.GetPropertyFlagsNode.getUncached(); + }; + } + + private DynamicObject.GetPropertyFlagsNode createGetPropertyFlagsNodeForKey(Object k) { + return switch (run) { + case CACHED -> DynamicObject.GetPropertyFlagsNode.create(); + case UNCACHED -> DynamicObject.GetPropertyFlagsNode.getUncached(); + }; + } + + private DynamicObject.GetPropertyNode createGetPropertyNode() { + return switch (run) { + case CACHED -> DynamicObject.GetPropertyNode.create(); + case UNCACHED -> DynamicObject.GetPropertyNode.getUncached(); + }; + } + + private DynamicObject.GetPropertyNode createGetPropertyNodeForKey(Object k) { + return switch (run) { + case CACHED -> DynamicObject.GetPropertyNode.create(); + case UNCACHED -> DynamicObject.GetPropertyNode.getUncached(); + }; + } + + private DynamicObject.GetKeyArrayNode createGetKeyArrayNode() { + return switch (run) { + case CACHED -> DynamicObject.GetKeyArrayNode.create(); + case UNCACHED -> DynamicObject.GetKeyArrayNode.getUncached(); + }; + } + + private DynamicObject.GetPropertyArrayNode createGetPropertyArrayNode() { + return switch (run) { + case CACHED -> DynamicObject.GetPropertyArrayNode.create(); + case UNCACHED -> DynamicObject.GetPropertyArrayNode.getUncached(); + }; + } + + @Test + public void testGet1() throws UnexpectedResultException { + DynamicObject o1 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + uncachedPut(o1, k1, v1, 0); + DynamicObject o2 = createEmpty(); + uncachedPut(o2, k1, v1, 0); + assertSame(o1.getShape(), o2.getShape()); + + var getNode = createGetNode(); + assertEquals(v1, getNode.getOrDefault(o1, k1, null)); + assertEquals(v1, getNode.getIntOrDefault(o1, k1, null)); + assertEquals(v1, getNode.getOrDefault(o2, k1, null)); + assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + + String v2 = "asdf"; + uncachedSet(o1, k1, v2); + + getNode = createGetNode(); + assertEquals(v2, getNode.getOrDefault(o1, k1, null)); + try { + getNode.getIntOrDefault(o1, k1, null); + fail(); + } catch (UnexpectedResultException e) { + assertEquals(v2, e.getResult()); + } + assertEquals(v1, getNode.getOrDefault(o2, k1, null)); + assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + + String missingKey = "missing"; + var getMissingKey = createGetNode(); + assertEquals(null, getMissingKey.getOrDefault(o1, missingKey, null)); + assertEquals(404, getMissingKey.getIntOrDefault(o1, missingKey, 404)); + var getMissingKey2 = createGetNode(); + assertEquals(null, getMissingKey2.getOrDefault(o1, missingKey, null)); + assertEquals(404, getMissingKey2.getIntOrDefault(o1, missingKey, 404)); + } + + @Test + public void testPut1() { + DynamicObject o1 = createEmpty(); + String key1 = "key1"; + int intval1 = 42; + int intval2 = 43; + uncachedPut(o1, key1, intval1, 0); + DynamicObject o2 = createEmpty(); + uncachedPut(o2, key1, intval1, 0); + assertSame(o1.getShape(), o2.getShape()); + + var setNode = createPutNode(); + setNode.put(o1, key1, intval2); + assertEquals(intval2, uncachedGet(o1, key1)); + setNode.put(o1, key1, intval1); + assertEquals(intval1, uncachedGet(o1, key1)); + setNode.put(o2, key1, intval2); + assertEquals(intval2, uncachedGet(o2, key1)); + setNode.put(o2, key1, intval1); + assertEquals(intval1, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + + String strval1 = "asdf"; + setNode.put(o1, key1, strval1); + assertEquals(strval1, uncachedGet(o1, key1)); + + String key2 = "key2"; + String strval2 = "qwer"; + var setNode2 = createPutNode(); + setNode2.put(o1, key2, strval2); + assertEquals(strval2, uncachedGet(o1, key2)); + setNode2.put(o1, key2, intval1); + assertEquals(intval1, uncachedGet(o1, key2)); + + var setNode3 = createPutNode(); + setNode3.put(o1, key2, strval1); + assertEquals(strval1, uncachedGet(o1, key2)); + // assertTrue(setNode3.accepts(o1)); + var setNode4 = createPutNode(); + setNode4.put(o1, key2, intval2); + assertEquals(intval2, uncachedGet(o1, key2)); + // assertTrue(setNode4.accepts(o1)); + } + + @Test + public void testPutIfPresent() { + DynamicObject o1 = createEmpty(); + String key1 = "key1"; + int intval1 = 42; + int intval2 = 43; + uncachedPut(o1, key1, intval1, 0); + DynamicObject o2 = createEmpty(); + uncachedPut(o2, key1, intval1, 0); + assertSame(o1.getShape(), o2.getShape()); + + var setNode = createPutNode(); + assertTrue(setNode.putIfPresent(o1, key1, intval2)); + assertEquals(intval2, uncachedGet(o1, key1)); + assertTrue(setNode.putIfPresent(o1, key1, intval1)); + assertEquals(intval1, uncachedGet(o1, key1)); + assertTrue(setNode.putIfPresent(o2, key1, intval2)); + assertEquals(intval2, uncachedGet(o2, key1)); + assertTrue(setNode.putIfPresent(o2, key1, intval1)); + assertEquals(intval1, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + + String strval1 = "asdf"; + setNode.put(o1, key1, strval1); + assertEquals(strval1, uncachedGet(o1, key1)); + + String key2 = "key2"; + String strval2 = "qwer"; + var setNode2 = createPutNode(); + var containsKeyNode2 = createContainsKeyNode(); + assertFalse(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); + assertFalse(setNode2.putIfPresent(o1, key2, strval2)); + // assertTrue(setNode2.accepts(o1)); + assertFalse(containsKeyNode2.execute(o1, key2)); + assertEquals(null, uncachedGet(o1, key2)); + + setNode2.put(o1, key2, strval2); + assertTrue(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); + assertEquals(strval2, uncachedGet(o1, key2)); + + var setNode3 = createPutNode(); + assertTrue(setNode3.putIfPresent(o1, key2, intval1)); + assertEquals(intval1, uncachedGet(o1, key2)); + } + + @Test + public void testPut2() { + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + uncachedPut(o3, k1, v1, 0); + + var setNode1 = createPutNode(); + setNode1.put(o1, k1, v2); + assertEquals(v2, uncachedGet(o1, k1)); + assertEquals(0, uncachedGetProperty(o1, k1).getFlags()); + setNode1.put(o1, k1, v1); + assertEquals(v1, uncachedGet(o1, k1)); + setNode1.put(o2, k1, v2); + assertEquals(v2, uncachedGet(o2, k1)); + setNode1.put(o2, k1, v1); + assertEquals(v1, uncachedGet(o2, k1)); + assertSame(o1.getShape(), o2.getShape()); + + assertEquals(v1, uncachedGet(o3, k1)); + assertSame(o1.getShape(), o3.getShape()); + uncachedPut(o3, k1, v1); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(0, uncachedGetProperty(o3, k1).getFlags()); + assertSame(o1.getShape(), o3.getShape()); + + String v3 = "asdf"; + setNode1.put(o1, k1, v3); + assertEquals(v3, uncachedGet(o1, k1)); + + String k2 = "key2"; + String v4 = "qwer"; + var setNode2 = createPutNode(); + + setNode2.put(o1, k2, v4); + assertEquals(v4, uncachedGet(o1, k2)); + setNode2.put(o1, k2, v1); + assertEquals(v1, uncachedGet(o1, k2)); + + int f2 = 0x42; + var setNode3 = createPutNode(); + + setNode3.putWithFlags(o3, k1, v1, f2); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(f2, uncachedGetProperty(o3, k1).getFlags()); + } + + @Test + public void testPutWithFlags1() { + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + uncachedPut(o3, k1, v1, 0); + + int flags = 0xf; + var setNode1 = createPutNode(); + setNode1.putWithFlags(o1, k1, v2, flags); + assertEquals(v2, uncachedGet(o1, k1)); + assertEquals(flags, uncachedGetProperty(o1, k1).getFlags()); + setNode1.putWithFlags(o1, k1, v1, flags); + assertEquals(v1, uncachedGet(o1, k1)); + setNode1.putWithFlags(o2, k1, v2, flags); + assertEquals(v2, uncachedGet(o2, k1)); + setNode1.putWithFlags(o2, k1, v1, flags); + assertEquals(v1, uncachedGet(o2, k1)); + assertSame(o1.getShape(), o2.getShape()); + + assertEquals(v1, uncachedGet(o3, k1)); + assertNotSame(o1.getShape(), o3.getShape()); + uncachedPut(o3, k1, v1, flags); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(flags, uncachedGetProperty(o3, k1).getFlags()); + // assertSame(o1.getShape(), o3.getShape()); + + String v3 = "asdf"; + setNode1.putWithFlags(o1, k1, v3, flags); + assertEquals(v3, uncachedGet(o1, k1)); + + String k2 = "key2"; + String v4 = "qwer"; + var setNode2 = createPutNode(); + + setNode2.put(o1, k2, v4); + assertEquals(v4, uncachedGet(o1, k2)); + setNode2.put(o1, k2, v1); + assertEquals(v1, uncachedGet(o1, k2)); + + int f2 = 0x42; + var setNode3 = createPutNode(); + + setNode3.putWithFlags(o3, k1, v1, f2); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(f2, uncachedGetProperty(o3, k1).getFlags()); + } + + @Test + public void testCopyProperties() { + var getNode = createGetNode(); + var setNode = createPutNode(); + var copyPropertiesNode = createCopyPropertiesNode(); + var getPropertyFlagsNode = createGetPropertyFlagsNode(); + + DynamicObject o1 = createEmpty(); + setNode.putWithFlags(o1, "key1", 1, 1); + setNode.putWithFlags(o1, "key2", 2, 2); + DynamicObject o2 = createEmpty(); + copyPropertiesNode.execute(o1, o2); + assertEquals(1, getNode.getOrNull(o1, "key1")); + assertEquals(2, getNode.getOrNull(o1, "key2")); + assertEquals(1, getPropertyFlagsNode.execute(o1, "key1", 0)); + assertEquals(2, getPropertyFlagsNode.execute(o1, "key2", 0)); + assertSame(o1.getShape(), o2.getShape()); + } + + @Test + public void testTypeIdAndShapeFlags() { + Object myType = newObjectType(); + int flags = 42; + String key = "key1"; + + DynamicObject.SetDynamicTypeNode setDynamicTypeNode = createSetDynamicTypeNode(); + DynamicObject.GetDynamicTypeNode getDynamicTypeNode = createGetDynamicTypeNode(); + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + var putNode = createPutNode(); + + DynamicObject o1 = createEmpty(); + setDynamicTypeNode.execute(o1, myType); + assertSame(myType, getDynamicTypeNode.execute(o1)); + + DynamicObject o2 = createEmpty(); + setDynamicTypeNode.execute(o2, myType); + assertSame(myType, getDynamicTypeNode.execute(o2)); + assertSame(o1.getShape(), o2.getShape()); + + DynamicObject o3 = createEmpty(); + setShapeFlagsNode.execute(o3, flags); + setDynamicTypeNode.execute(o3, myType); + assertSame(myType, getDynamicTypeNode.execute(o3)); + assertEquals(flags, getShapeFlagsNode.execute(o3)); + + DynamicObject o4 = createEmpty(); + setShapeFlagsNode.execute(o4, flags); + putNode.put(o4, key, 42); + setDynamicTypeNode.execute(o4, myType); + putNode.put(o4, key, "value"); + assertSame(myType, getDynamicTypeNode.execute(o4)); + + assertSame(myType, getDynamicTypeNode.execute(o4)); + assertSame(myType, getDynamicTypeNode.execute(o4)); + Object myType2 = newObjectType(); + setDynamicTypeNode.execute(o4, myType2); + assertSame(myType2, getDynamicTypeNode.execute(o4)); + } + + @Test + public void testShapeFlags() { + final int flags = 0b101010; + + DynamicObject.SetDynamicTypeNode setDynamicTypeNode = createSetDynamicTypeNode(); + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.HasShapeFlagsNode hasShapeFlagsNode = createHasShapeFlagsNode(); + DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + DynamicObject.AddShapeFlagsNode addShapeFlagsNode = createAddShapeFlagsNode(); + DynamicObject.MarkSharedNode markSharedNode = createMarkSharedNode(); + + DynamicObject o1 = createEmpty(); + setShapeFlagsNode.execute(o1, flags); + assertEquals(flags, getShapeFlagsNode.execute(o1)); + + DynamicObject o2 = createEmpty(); + setShapeFlagsNode.execute(o2, flags); + assertEquals(flags, getShapeFlagsNode.execute(o2)); + assertSame(o1.getShape(), o2.getShape()); + + DynamicObject o3 = createEmpty(); + setShapeFlagsNode.execute(o3, 1); + setDynamicTypeNode.execute(o3, newObjectType()); + setShapeFlagsNode.execute(o3, flags); + setDynamicTypeNode.execute(o3, newObjectType()); + assertEquals(flags, getShapeFlagsNode.execute(o2)); + + DynamicObject o4 = createEmpty(); + setShapeFlagsNode.execute(o4, flags); + markSharedNode.execute(o4); + assertEquals(flags, getShapeFlagsNode.execute(o2)); + + assertEquals(flags, getShapeFlagsNode.execute(o4)); + int flags2 = 43; + setShapeFlagsNode.execute(o4, flags2); + // assertEquals(run != TestRun.CACHED, cached.accepts(o4)); + assertEquals(flags2, getShapeFlagsNode.execute(o4)); + } + + @Test + public void testUpdateShapeFlags() { + int f1 = 0xf; + int f2 = 0x10; + int f3 = 0x1f; + + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key", 42, 0); + assertTrue(setShapeFlagsNode.execute(o1, f1)); + assertEquals(f1, getShapeFlagsNode.execute(o1)); + assertEquals(f1, o1.getShape().getFlags()); + assertTrue(updateShapeFlags(getShapeFlagsNode, setShapeFlagsNode, o1, f -> f | f2)); + assertEquals(f3, getShapeFlagsNode.execute(o1)); + assertEquals(f3, o1.getShape().getFlags()); + } + + private static boolean updateShapeFlags(DynamicObject.GetShapeFlagsNode getShapeFlagsNode, DynamicObject.SetShapeFlagsNode setShapeFlagsNode, DynamicObject obj, IntUnaryOperator updateFunction) { + int oldFlags = getShapeFlagsNode.execute(obj); + int newFlags = updateFunction.applyAsInt(oldFlags); + if (oldFlags == newFlags) { + return false; + } + return setShapeFlagsNode.execute(obj, newFlags); + } + + @Test + public void testHasAddShapeFlags() { + final int flags = 0b101010; + + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.HasShapeFlagsNode hasShapeFlagsNode = createHasShapeFlagsNode(); + DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + DynamicObject.AddShapeFlagsNode addShapeFlagsNode = createAddShapeFlagsNode(); + + DynamicObject o1 = createEmpty(); + setShapeFlagsNode.execute(o1, flags); + assertEquals(flags, getShapeFlagsNode.execute(o1)); + assertTrue(hasShapeFlagsNode.execute(o1, flags)); + assertTrue(hasShapeFlagsNode.execute(o1, 0b10)); + assertFalse(hasShapeFlagsNode.execute(o1, 0b11)); + addShapeFlagsNode.execute(o1, 0b1); + assertTrue(hasShapeFlagsNode.execute(o1, 0b11)); + assertEquals(flags | 0b1, getShapeFlagsNode.execute(o1)); + } + + @Test + public void testMakeShared() { + String key = "key"; + + DynamicObject.IsSharedNode isSharedNode = createIsSharedNode(); + DynamicObject.MarkSharedNode markSharedNode = createMarkSharedNode(); + var putNode = createPutNode(); + var containsKeyNode = createContainsKeyNode(); + + DynamicObject o1 = createEmpty(); + assertFalse(isSharedNode.execute(o1)); + markSharedNode.execute(o1); + assertTrue(isSharedNode.execute(o1)); + putNode.put(o1, key, "value"); + assertTrue(isSharedNode.execute(o1)); + assertTrue(containsKeyNode.execute(o1, key)); + } + + @Test + public void testPropertyFlags() { + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + int f1 = 0xf; + int f2 = 0x10; + int f3 = 0x1f; + + DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode = createSetPropertyFlagsNodeForKey(k1); + DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode = createGetPropertyFlagsNodeForKey(k1); + DynamicObject.GetPropertyNode getPropertyNode = createGetPropertyNodeForKey(k1); + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, k1, v1, 0); + assertTrue(setPropertyFlagsNode.execute(o1, k1, f1)); + assertEquals(f1, getPropertyFlagsNode.execute(o1, k1, -1)); + assertEquals(f1, uncachedGetProperty(o1, k1).getFlags()); + assertTrue(updatePropertyFlags(getPropertyNode, setPropertyFlagsNode, o1, k1, f -> f | f2)); + assertEquals(f3, getPropertyFlagsNode.execute(o1, k1, -1)); + assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); + + Shape before = o1.getShape(); + assertTrue(setPropertyFlagsNode.execute(o1, k1, f3)); + assertFalse(updatePropertyFlags(getPropertyNode, setPropertyFlagsNode, o1, k1, f -> f | f2)); + assertEquals(f3, getPropertyFlagsNode.execute(o1, k1, -1)); + assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); + assertSame(before, o1.getShape()); + + DynamicObject o2 = createEmpty(); + uncachedPut(o2, k1, v2, 0); + assertTrue(setPropertyFlagsNode.execute(o2, k1, f1)); + assertEquals(f1, getPropertyFlagsNode.execute(o2, k1, -1)); + assertTrue(updatePropertyFlags(getPropertyNode, setPropertyFlagsNode, o2, k1, f -> f | f2)); + assertEquals(f3, getPropertyFlagsNode.execute(o2, k1, -1)); + assertSame(o1.getShape(), o2.getShape()); + + DynamicObject o3 = createEmpty(); + assertFalse(setPropertyFlagsNode.execute(o3, k1, f1)); + } + + private static boolean updatePropertyFlags(DynamicObject.GetPropertyNode getPropertyNode, + DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode, + DynamicObject obj, + String key, + IntUnaryOperator updateFunction) { + Property property = getPropertyNode.execute(obj, key); + if (property == null) { + return false; + } + int oldFlags = property.getFlags(); + int newFlags = updateFunction.applyAsInt(oldFlags); + if (oldFlags == newFlags) { + return false; + } + return setPropertyFlagsNode.execute(obj, key, newFlags); + } + + @Test + public void testRemove() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + String k1 = "key1"; + String k2 = "key2"; + String k3 = "key3"; + String k4 = "key4"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, k1, v1, 0); + uncachedPut(o1, k2, v2, 0); + uncachedPut(o1, k3, v3, 0); + + var removeKey1 = createRemoveKeyNode(); + var removeKey3 = createRemoveKeyNode(); + var removeKey4 = createRemoveKeyNode(); + var getKey1 = createGetNode(); + var getKey2 = createGetNode(); + + assertFalse(removeKey4.execute(o1, k4)); + assertTrue(removeKey3.execute(o1, k3)); + assertEquals(Arrays.asList(k1, k2), getKeyList(o1)); + uncachedPut(o1, k3, v3, 0); + assertEquals(Arrays.asList(k1, k2, k3), getKeyList(o1)); + assertTrue(removeKey3.execute(o1, k3)); + assertEquals(Arrays.asList(k1, k2), getKeyList(o1)); + uncachedPut(o1, k3, v3, 0); + assertTrue(removeKey1.execute(o1, k1)); + assertEquals(Arrays.asList(k2, k3), getKeyList(o1)); + uncachedPut(o1, k1, v1, 0); + assertEquals(Arrays.asList(k2, k3, k1), getKeyList(o1)); + assertTrue(removeKey3.execute(o1, k3)); + assertEquals(Arrays.asList(k2, k1), getKeyList(o1)); + assertEquals(v1, getKey1.getOrDefault(o1, k1, null)); + assertEquals(v2, getKey2.getOrDefault(o1, k2, null)); + } + + @Test + public void testResetShape() { + int v1 = 42; + int v2 = 43; + + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.ResetShapeNode resetShapeNode = createResetShapeNode(); + + DynamicObject o1 = createEmpty(); + Shape emptyShape = o1.getShape(); + uncachedPut(o1, "key1", v1, 0); + uncachedPut(o1, "key2", v2, 0); + resetShapeNode.execute(o1, emptyShape); + assertSame(emptyShape, o1.getShape()); + + assumeTrue("new layout only", isNewLayout()); + int flags = 0xf; + DynamicObject o2 = createEmpty(); + Shape newEmptyShape = Shape.newBuilder().shapeFlags(flags).build(); + uncachedPut(o2, "key1", v1, 0); + uncachedPut(o2, "key2", v2, 0); + resetShapeNode.execute(o2, newEmptyShape); + assertSame(newEmptyShape, o2.getShape()); + assertEquals(flags, getShapeFlagsNode.execute(o2)); + } + + @Test + public void testGetKeysAndProperties() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject.GetKeyArrayNode getKeyArrayNode = createGetKeyArrayNode(); + DynamicObject.GetPropertyArrayNode getPropertyArrayNode = createGetPropertyArrayNode(); + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 1); + uncachedPut(o1, "key2", v2, 2); + uncachedPut(o1, "key3", v3, 3); + + Object[] keyArray = getKeyArrayNode.execute(o1); + Property[] properties = getPropertyArrayNode.execute(o1); + assertEquals(Arrays.asList("key1", "key2", "key3"), getKeyList(o1)); + assertEquals(3, o1.getShape().getPropertyCount()); + for (int i = 0, j = 1; i < 3; i++, j++) { + assertEquals(keyArray[i], properties[i].getKey()); + assertEquals(j, properties[i].getFlags()); + } + } + + @Test + public void testGetKeysAndPropertiesFromShape() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 1); + uncachedPut(o1, "key2", v2, 2); + uncachedPut(o1, "key3", v3, 3); + + Object[] keyArray = getKeyList(o1).toArray(); + Property[] properties = o1.getShape().getPropertyList().toArray(new Property[0]); + assertEquals(Arrays.asList("key1", "key2", "key3"), getKeyList(o1)); + assertEquals(3, o1.getShape().getPropertyCount()); + for (int i = 0, j = 1; i < 3; i++, j++) { + assertEquals(keyArray[i], properties[i].getKey()); + assertEquals(j, properties[i].getFlags()); + } + } + + @Test + public void testAllPropertiesMatch() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 41); + uncachedPut(o1, "key2", v2, 42); + uncachedPut(o1, "key3", v3, 43); + + assertFalse(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); + var removeKey1 = createRemoveKeyNode(); + removeKey1.execute(o1, "key1"); + assertTrue(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); + } + + @Test + public void testGetProperty() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 1); + uncachedPut(o1, "key2", v2, 2); + uncachedPut(o1, "key3", v3, 3); + + DynamicObject.GetPropertyNode getPropertyNode = createGetPropertyNode(); + DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode = createGetPropertyFlagsNode(); + + // assertTrue(lib.accepts(o1)); + for (int i = 1; i <= 3; i++) { + Object key = "key" + i; + assertSame(o1.getShape().getProperty(key), getPropertyNode.execute(o1, key)); + assertEquals(i, o1.getShape().getProperty(key).getFlags()); + assertEquals(i, getPropertyFlagsNode.execute(o1, key, -1)); + } + // assertTrue(lib.accepts(o1)); + } + + @Test + public void testPutConstant1() { + DynamicObject o1 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + int flags = 0xf; + + var setNode1 = createPutNode(); + + setNode1.putConstantWithFlags(o1, k1, v1, 0); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(0, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + setNode1.putConstantWithFlags(o1, k1, v1, flags); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + setNode1.put(o1, k1, v2); + assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v2, uncachedGet(o1, k1)); + } + + @Test + public void testPutConstant2() { + DynamicObject o1 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + int flags = 0xf; + + var setNode1 = createPutNode(); + + setNode1.putConstantWithFlags(o1, k1, v1, 0); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(0, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + setNode1.putWithFlags(o1, k1, v2, flags); + if (isNewLayout()) { + assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); + } + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v2, uncachedGet(o1, k1)); + } + + @Test + public void testCachedShape() { + String key = "testKey"; + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + + uncachedPut(o1, key, o2); + uncachedPut(o2, key, o3); + uncachedPut(o3, key, 42); + + TestNestedDispatchGetNode node = adoptNode(TestNestedDispatchGetNodeGen.create()).get(); + assertEquals(42, node.execute(o1)); + assertEquals(42, node.execute(o1)); + assertEquals(42, node.execute(o2)); + assertEquals(42, node.execute(o1)); + assertEquals(42, node.execute(o2)); + assertEquals(42, node.execute(o2)); + } + + @Test + public void testPropertyAndShapeFlags() { + DynamicObject o1 = createEmpty(); + fillObjectWithProperties(o1, false); + updateAllFlags(o1, 3); + DynamicObject o2 = createEmpty(); + fillObjectWithProperties(o2, true); + DynamicObject o3 = createEmpty(); + fillObjectWithProperties(o3, false); + DynamicObject.PutNode.getUncached().put(o1, "k13", false); + updateAllFlags(o2, 3); + updateAllFlags(o3, 3); + var getNode = createGetNode(); + assertEquals(1, getNode.getOrDefault(o3, "k13", null)); + } + + private void fillObjectWithProperties(DynamicObject obj, boolean b) { + DynamicObject.PutNode library = createPutNode(); + + for (int i = 0; i < 20; i++) { + Object value; + if (i % 2 == 0) { + if (i == 14) { + value = "string"; + } else { + value = new ArrayList<>(); + } + } else { + if (b && i == 13) { + value = new ArrayList<>(); + } else { + value = 1; + } + } + int flags = (i == 17 || i == 13) ? 1 : 3; + library.putWithFlags(obj, "k" + i, value, flags); + } + } + + private void updateAllFlags(DynamicObject obj, int flags) { + DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode = createSetPropertyFlagsNode(); + DynamicObject.GetPropertyArrayNode getPropertyArrayNode = createGetPropertyArrayNode(); + + for (Property property : getPropertyArrayNode.execute(obj)) { + int oldFlags = property.getFlags(); + int newFlags = oldFlags | flags; + if (newFlags != oldFlags) { + Object key = property.getKey(); + setPropertyFlagsNode.execute(obj, key, newFlags); + } + } + + DynamicObject.SetShapeFlagsNode setShapeFlags = createSetShapeFlagsNode(); + setShapeFlags.execute(obj, flags); + } + + private static void uncachedPut(DynamicObject obj, Object key, Object value) { + DynamicObject.PutNode.getUncached().put(obj, key, value); + } + + private static void uncachedPut(DynamicObject obj, Object key, Object value, int flags) { + DynamicObject.PutNode.getUncached().putWithFlags(obj, key, value, flags); + } + + private static void uncachedSet(DynamicObject obj, Object key, Object value) { + DynamicObject.PutNode.getUncached().putIfPresent(obj, key, value); + } + + private static Object uncachedGet(DynamicObject obj, Object key) { + return DynamicObject.GetNode.getUncached().getOrDefault(obj, key, null); + } + + private static Property uncachedGetProperty(DynamicObject obj, Object key) { + return DynamicObject.GetPropertyNode.getUncached().execute(obj, key); + } + + private static Object newObjectType() { + return new Object() { + }; + } + + private static List getKeyList(DynamicObject obj) { + return Arrays.asList(DynamicObject.GetKeyArrayNode.getUncached().execute(obj)); + } + + private static boolean isNewLayout() { + return true; + } + + @GenerateInline(false) + public abstract static class TestGet extends Node { + public abstract Object execute(DynamicObject obj); + + @Specialization + public Object doGet(DynamicObject obj, + @Cached DynamicObject.GetNode get) { + return get.getOrDefault(obj, "test", null); + } + } +} diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java index 25894288f003..0a8123945573 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java @@ -45,17 +45,15 @@ import java.util.Arrays; import java.util.List; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.Shape; @RunWith(Parameterized.class) -public class DynamicTypeTest extends AbstractParametrizedLibraryTest { +public class DynamicTypeTest extends ParametrizedDynamicObjectTest { @Parameters(name = "{0}") public static List data() { @@ -67,7 +65,7 @@ public void testDynamicTypeCanBeAnyObject() { Object dynamicType = new Object(); Shape emptyShape = Shape.newBuilder().dynamicType(dynamicType).build(); TestDynamicObjectMinimal obj = new TestDynamicObjectMinimal(emptyShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, obj); + var lib = createLibrary(obj); assertSame(dynamicType, lib.getDynamicType(obj)); dynamicType = new Object(); lib.setDynamicType(obj, dynamicType); @@ -79,7 +77,7 @@ public void testDynamicTypeCannotBeNull() { assertFails(() -> Shape.newBuilder().dynamicType(null).build(), NullPointerException.class); Shape emptyShape = Shape.newBuilder().dynamicType(new Object()).build(); TestDynamicObjectMinimal obj = new TestDynamicObjectMinimal(emptyShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, obj); + var lib = createLibrary(obj); assertFails(() -> lib.setDynamicType(obj, null), NullPointerException.class); } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR42603.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR42603.java index 2245e2630e4b..aac7fa8324d2 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR42603.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR42603.java @@ -47,17 +47,24 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; import org.junit.Test; import com.oracle.truffle.api.object.test.ObjectModelRegressionTest.TestDynamicObject; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class GR42603 { +@RunWith(Parameterized.class) +public class GR42603 extends ParametrizedDynamicObjectTest { + + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.UNCACHED_ONLY); + } - private static final DynamicObjectLibrary OBJLIB = DynamicObjectLibrary.getUncached(); private static final int FROZEN_FLAG = 1; @Test @@ -67,7 +74,7 @@ public void testReplacePropertyRace() throws Throwable { } } - private static void testConcurrentReplaceProperty() throws Throwable { + private void testConcurrentReplaceProperty() throws Throwable { ExecutorService executorService = Executors.newFixedThreadPool(2); List> futures = new ArrayList<>(); @@ -79,10 +86,10 @@ private static void testConcurrentReplaceProperty() throws Throwable { futures.add(executorService.submit(() -> { try (Context context = Context.newBuilder().engine(engine).build()) { TestDynamicObject object = newEmptyObject(rootShape); - OBJLIB.put(object, "propertyBefore", newEmptyObject(rootShape)); + uncachedLibrary().put(object, "propertyBefore", newEmptyObject(rootShape)); boolean assignObject = iFixed == 0; Object hostNullValue = context.asValue(null); - OBJLIB.put(object, "offendingProperty", assignObject ? object : hostNullValue); + uncachedLibrary().put(object, "offendingProperty", assignObject ? object : hostNullValue); freezeObject(object); Shape shape = object.getShape(); @@ -111,9 +118,9 @@ private static TestDynamicObject newEmptyObject(Shape rootShape) { return new TestDynamicObject(rootShape); } - private static void freezeObject(TestDynamicObject object) { - for (Object key : OBJLIB.getKeyArray(object)) { - OBJLIB.setPropertyFlags(object, key, FROZEN_FLAG); + private void freezeObject(TestDynamicObject object) { + for (Object key : uncachedLibrary().getKeyArray(object)) { + uncachedLibrary().setPropertyFlags(object, key, FROZEN_FLAG); } } } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR52036.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR52036.java index e7adf28eebe6..5eb59323acb9 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR52036.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/GR52036.java @@ -48,16 +48,25 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; import org.junit.Test; -public class GR52036 { +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; - private static final DynamicObjectLibrary OBJLIB = DynamicObjectLibrary.getUncached(); +import java.util.List; + +@RunWith(Parameterized.class) +public class GR52036 extends ParametrizedDynamicObjectTest { + + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.UNCACHED_ONLY); + } /** * Regression test for a case of two locations sharing the same type assumption, where one @@ -86,18 +95,18 @@ protected ObjType2(Shape shape) { var o1 = new ObjType1(initialShape); var o2 = new ObjType1(initialShape); - OBJLIB.put(o1, "a", new ObjType1(initialShape)); - OBJLIB.put(o1, "b", new ObjType1(initialShape)); - OBJLIB.put(o1, "c", new ObjType1(initialShape)); + uncachedLibrary().put(o1, "a", new ObjType1(initialShape)); + uncachedLibrary().put(o1, "b", new ObjType1(initialShape)); + uncachedLibrary().put(o1, "c", new ObjType1(initialShape)); - OBJLIB.put(o2, "a", new ObjType1(initialShape)); - OBJLIB.put(o2, "b", new ObjType1(initialShape)); - OBJLIB.put(o2, "c", new ObjType1(initialShape)); + uncachedLibrary().put(o2, "a", new ObjType1(initialShape)); + uncachedLibrary().put(o2, "b", new ObjType1(initialShape)); + uncachedLibrary().put(o2, "c", new ObjType1(initialShape)); assertSame("Objects must have the same shape", o1.getShape(), o2.getShape()); // Remove property "b", shifting the location of "c". - OBJLIB.removeKey(o1, "b"); + uncachedLibrary().removeKey(o1, "b"); var location1 = o1.getShape().getProperty("c").getLocation(); var location2 = o2.getShape().getProperty("c").getLocation(); @@ -108,7 +117,7 @@ protected ObjType2(Shape shape) { assertTrue(commonTypeAssumption.isValid()); // Change the type of "c", invalidating the type assumption. - OBJLIB.put(o1, "c", new ObjType2(initialShape)); + uncachedLibrary().put(o1, "c", new ObjType2(initialShape)); assertFalse("Invalidated type assumption", commonTypeAssumption.isValid()); assertTrue("New type assumption should be valid", getTypeAssumption(location1).isValid()); assertNotSame("Type assumption of location1 has been replaced", getTypeAssumptionRecord(location1), commonTypeAssumptionRecord); @@ -117,7 +126,7 @@ protected ObjType2(Shape shape) { assertSame("Type assumption of location2 has not been replaced", getTypeAssumptionRecord(location2), commonTypeAssumptionRecord); // Assign "c" a value still compatible with the invalidated type assumption. - OBJLIB.put(o2, "c", new ObjType1(initialShape)); + uncachedLibrary().put(o2, "c", new ObjType1(initialShape)); assertTrue("New type assumption of location2 should be valid", getTypeAssumption(location2).isValid()); } } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ImplicitCastTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ImplicitCastTest.java index 41a4181f57a7..b11d8a4bcf13 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ImplicitCastTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ImplicitCastTest.java @@ -47,11 +47,6 @@ import java.util.Collections; import java.util.List; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Location; -import com.oracle.truffle.api.object.Shape; -import com.oracle.truffle.api.object.Shape.Builder; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,11 +54,14 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Location; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.object.Shape.Builder; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class ImplicitCastTest extends AbstractParametrizedLibraryTest { +public class ImplicitCastTest extends ParametrizedDynamicObjectTest { @Parameters public static Collection data() { @@ -98,7 +96,7 @@ private static DynamicObject newInstance() { public void testIntOther() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", intVal); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -115,7 +113,7 @@ public void testIntOther() { public void testOtherInt() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", otherVal); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -132,7 +130,7 @@ public void testOtherInt() { public void testIntOtherDoesNotGoBack() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", intVal); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -155,7 +153,7 @@ public void testIntOtherDoesNotGoBack() { public void testIntObject() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", intVal); library.put(object, "a", ""); @@ -168,7 +166,7 @@ public void testIntObject() { public void testIntOtherObject() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", intVal); library.put(object, "a", otherVal); @@ -182,7 +180,7 @@ public void testIntOtherObject() { public void testLocationDecoratorEquals() { DynamicObject object1 = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object1); + var library = createLibrary(object1); library.put(object1, "a", otherVal); Location location1 = object1.getShape().getProperty("a").getLocation(); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LeakCheckTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LeakCheckTest.java index 4e88177c592d..f8abff7ef18a 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LeakCheckTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LeakCheckTest.java @@ -50,13 +50,20 @@ import java.util.List; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; @SuppressWarnings("deprecation") -public class LeakCheckTest { - private static final DynamicObjectLibrary LIBRARY = DynamicObjectLibrary.getUncached(); +@RunWith(Parameterized.class) +public class LeakCheckTest extends ParametrizedDynamicObjectTest { + + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.UNCACHED_ONLY); + } private static Shape newEmptyShape() { return Shape.newBuilder().layout(TestDynamicObjectDefault.class, MethodHandles.lookup()).build(); @@ -77,9 +84,9 @@ public void leakCheck() { for (int i = 0; i < 100; i++) { DynamicObject obj = newInstance(emptyShape); for (int j = 0; j < 1000; j++) { - LIBRARY.put(obj, "a" + Math.random(), Math.random()); - LIBRARY.put(obj, "b" + Math.random(), Math.random()); - LIBRARY.put(obj, "c" + Math.random(), Math.random()); + uncachedLibrary().put(obj, "a" + Math.random(), Math.random()); + uncachedLibrary().put(obj, "b" + Math.random(), Math.random()); + uncachedLibrary().put(obj, "c" + Math.random(), Math.random()); } fullShapeRefs.add(new WeakReference<>(obj.getShape())); } @@ -103,7 +110,7 @@ public void constantPropertyLeakCheck() { for (int i = 0; i < 100000; i++) { DynamicObject obj = newInstance(emptyShape); Leak value = new Leak(); - LIBRARY.putConstant(obj, "a" + i, value, 0); + uncachedLibrary().putConstant(obj, "a" + i, value, 0); Shape shape = obj.getShape(); value.shape = shape; @@ -120,7 +127,7 @@ public void constantPropertyLeakCheck() { // trigger transition map cleanup DynamicObject obj = newInstance(emptyShape); - LIBRARY.putConstant(obj, "const", new Leak(), 0); + uncachedLibrary().putConstant(obj, "const", new Leak(), 0); Reference.reachabilityFence(emptyShape); } @@ -137,7 +144,7 @@ public void dynamicTypeLeakCheck() { for (int i = 0; i < 100000; i++) { DynamicObject obj = newInstance(emptyShape); Leak value = new Leak(); - LIBRARY.setDynamicType(obj, value); + uncachedLibrary().setDynamicType(obj, value); Shape shape = obj.getShape(); value.shape = shape; @@ -154,7 +161,7 @@ public void dynamicTypeLeakCheck() { // trigger transition map cleanup DynamicObject obj = newInstance(emptyShape); - LIBRARY.setDynamicType(obj, new Leak()); + uncachedLibrary().setDynamicType(obj, new Leak()); Reference.reachabilityFence(emptyShape); } @@ -175,11 +182,11 @@ public void constantPropertyLeakCheckSingleTransition() { Leak leak; leak = new Leak(); - LIBRARY.putConstant(obj, "a", leak, 0); + uncachedLibrary().putConstant(obj, "a", leak, 0); leak.shape = obj.getShape(); - LIBRARY.putConstant(obj, "b", leak, 0); + uncachedLibrary().putConstant(obj, "b", leak, 0); leak.shape = obj.getShape(); - LIBRARY.putConstant(obj, "c", leak, 0); + uncachedLibrary().putConstant(obj, "c", leak, 0); leak.shape = obj.getShape(); Shape shape = obj.getShape(); @@ -212,15 +219,15 @@ public void dynamicTypeLeakCheckSingleTransition() { DynamicObject obj = newInstance(emptyShape); Leak leak1 = new Leak(); - LIBRARY.setDynamicType(obj, leak1); + uncachedLibrary().setDynamicType(obj, leak1); leak1.shape = obj.getShape(); Leak leak2 = new Leak(); - LIBRARY.setDynamicType(obj, leak2); + uncachedLibrary().setDynamicType(obj, leak2); leak2.shape = obj.getShape(); Leak leak3 = new Leak(); - LIBRARY.setDynamicType(obj, leak3); + uncachedLibrary().setDynamicType(obj, leak3); leak3.shape = obj.getShape(); Shape shape = obj.getShape(); @@ -255,18 +262,18 @@ public void testWeakKeyStaysAlive() { Leak const1 = new Leak(); Leak const2 = new Leak(); Leak const3 = new Leak(); - LIBRARY.putConstant(obj, "const1", const1, 0); - LIBRARY.putConstant(obj, "const2", const2, 0); - LIBRARY.putConstant(obj, "const3", const3, 0); + uncachedLibrary().putConstant(obj, "const1", const1, 0); + uncachedLibrary().putConstant(obj, "const2", const2, 0); + uncachedLibrary().putConstant(obj, "const3", const3, 0); Shape prevShape = obj.getShape(); System.gc(); obj = newInstance(emptyShape); - LIBRARY.putConstant(obj, "const1", const1, 0); - LIBRARY.putConstant(obj, "const2", const2, 0); - LIBRARY.putConstant(obj, "const3", const3, 0); + uncachedLibrary().putConstant(obj, "const1", const1, 0); + uncachedLibrary().putConstant(obj, "const2", const2, 0); + uncachedLibrary().putConstant(obj, "const3", const3, 0); Shape currShape = obj.getShape(); assertSame(prevShape, currShape); @@ -274,14 +281,14 @@ public void testWeakKeyStaysAlive() { // switch from single transition to transition map obj = newInstance(emptyShape); Leak const4 = new Leak(); - LIBRARY.putConstant(obj, "const4", const4, 0); + uncachedLibrary().putConstant(obj, "const4", const4, 0); System.gc(); obj = newInstance(emptyShape); - LIBRARY.putConstant(obj, "const1", const1, 0); - LIBRARY.putConstant(obj, "const2", const2, 0); - LIBRARY.putConstant(obj, "const3", const3, 0); + uncachedLibrary().putConstant(obj, "const1", const1, 0); + uncachedLibrary().putConstant(obj, "const2", const2, 0); + uncachedLibrary().putConstant(obj, "const3", const3, 0); currShape = obj.getShape(); assertSame(prevShape, currShape); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LocationTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LocationTest.java index c6bb1398f900..944f7ccace4d 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LocationTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LocationTest.java @@ -54,11 +54,6 @@ import java.util.Arrays; import java.util.List; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Location; -import com.oracle.truffle.api.object.Property; -import com.oracle.truffle.api.object.Shape; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -67,11 +62,14 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Location; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class LocationTest extends AbstractParametrizedLibraryTest { +public class LocationTest extends ParametrizedDynamicObjectTest { @Parameter(1) public boolean useLookup; @@ -103,7 +101,7 @@ private DynamicObject newInstance() { public void testOnlyObjectLocationForObject() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "obj", new Object()); Location location = object.getShape().getProperty("obj").getLocation(); @@ -116,7 +114,7 @@ public void testOnlyObjectLocationForObject() { public void testOnlyPrimLocationForPrimitive() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "prim", 42); Location location = object.getShape().getProperty("prim").getLocation(); @@ -129,7 +127,7 @@ public void testOnlyPrimLocationForPrimitive() { public void testPrim2Object() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "foo", 42); Location location1 = object.getShape().getProperty("foo").getLocation(); @@ -148,7 +146,7 @@ public void testPrim2Object() { public void testUnrelatedPrimitivesGoToObject() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "foo", 42L); Location location1 = object.getShape().getProperty("foo").getLocation(); @@ -167,7 +165,7 @@ public void testUnrelatedPrimitivesGoToObject() { public void testChangeFlagsReuseLocation() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "foo", 42); Location location = object.getShape().getProperty("foo").getLocation(); @@ -184,7 +182,7 @@ public void testChangeFlagsReuseLocation() { public void testChangeFlagsChangeLocation() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "foo", 42); Location location = object.getShape().getProperty("foo").getLocation(); @@ -201,7 +199,7 @@ public void testChangeFlagsChangeLocation() { public void testDelete() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", 1); library.put(object, "b", 2); @@ -227,7 +225,7 @@ public void testLocationDecoratorEquals() { public void testDeleteDeclaredProperty() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.putConstant(object, "a", new Object(), 0); assertTrue(library.containsKey(object, "a")); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java index 04ab66547682..15686b8313a1 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java @@ -56,9 +56,6 @@ import java.util.List; import java.util.function.BiConsumer; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.Test; @@ -68,11 +65,12 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.Assumption; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class ObjectModelRegressionTest extends AbstractParametrizedLibraryTest { +public class ObjectModelRegressionTest extends ParametrizedDynamicObjectTest { @Parameter(1) public Class layoutClass; @Parameter(2) public boolean useLookup; @@ -116,7 +114,7 @@ public void testDefinePropertyWithFlagsChangeAndFinalInvalidation() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.put(a, "r", 1); library.put(a, "x", 2); @@ -148,7 +146,7 @@ public void testAddNewPropertyOnObsoleteShape() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.put(a, "x", 1); library.put(b, "x", 1); @@ -169,7 +167,7 @@ public void testRemovePropertyOnObsoleteShape() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.put(a, "x", ""); library.put(b, "x", ""); @@ -191,7 +189,7 @@ public void testReplaceDeclaredProperty() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.putConstant(a, "a", null, 0); @@ -209,7 +207,7 @@ public void testReplaceDeclaredProperty2() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.putConstant(a, "a", null, 0); @@ -228,7 +226,7 @@ public void testDeclaredPropertyShapeTransitionCount() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); for (int i = 0; i < 26; i++) { library.putConstant(a, Character.toString((char) ('a' + i)), null, 0); @@ -259,7 +257,7 @@ public void testChangePropertyFlagsWithObsolescence() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.putWithFlags(a, "s", 42, 0); library.putIfPresent(a, "s", 43); @@ -284,7 +282,7 @@ public void testChangePropertyFlagsWithObsolescenceGR53902() { DynamicObject y = newInstance(emptyShape); DynamicObject z = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.putWithFlags(a, "s", 42, 0); library.putIfPresent(a, "s", 43); @@ -310,7 +308,7 @@ public void testChangePropertyTypeWithObsolescence() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.putWithFlags(a, "s", 13.37, 0); library.putWithFlags(a, "x", 42, 0); @@ -329,7 +327,7 @@ public void testAssumedFinalLocationShapeTransitionCount() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); for (int i = 0; i < 26; i++) { library.put(a, Character.toString((char) ('a' + i)), 0); @@ -355,9 +353,9 @@ public void testChangeFlagsAndType() { DynamicObject obj = new TestDynamicObject(emptyShape); DynamicObject temp = new TestDynamicObject(emptyShape); - createLibrary(DynamicObjectLibrary.class, temp).putWithFlags(temp, "a", a, 8); + createLibrary(temp).putWithFlags(temp, "a", a, 8); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, obj); + var library = createLibrary(obj); library.put(obj, "a", 42); library.put(obj, "b", b); @@ -387,9 +385,9 @@ public void testChangeFlagsConstantToNonConstant() { DynamicObject obj = new TestDynamicObject(emptyShape); DynamicObject temp = new TestDynamicObject(emptyShape); - createLibrary(DynamicObjectLibrary.class, temp).putWithFlags(temp, "a", a, 8); + createLibrary(temp).putWithFlags(temp, "a", a, 8); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, obj); + var library = createLibrary(obj); library.putConstant(obj, "a", a, 3); library.put(obj, "b", b); @@ -420,7 +418,7 @@ public void testTryMergeShapes() { DynamicObject b = new TestDynamicObject(emptyShape); DynamicObject c = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.setShapeFlags(a, 1); library.put(a, "a", 1); @@ -469,7 +467,7 @@ public void testTryMergeShapes2() { DynamicObject a = new TestDynamicObject(emptyShape); DynamicObject b = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.put(a, "a", 1); Shape notObsoletedParent = a.getShape(); @@ -502,7 +500,7 @@ public void testBooleanLocationTypeAssumption() { DynamicObject obj = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, obj); + var library = createLibrary(obj); library.put(obj, "b1", true); library.put(obj, "b2", true); @@ -522,7 +520,7 @@ public void testPropertyAssumptionInvalidation() { DynamicObject a = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + var library = createLibrary(a); library.put(a, "a", 1); library.put(a, "b", 2); @@ -558,8 +556,8 @@ public void testPropertyAssumptionInvalidAfterRemove() { Shape emptyShape = Shape.newBuilder().propertyAssumptions(true).build(); DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(DynamicObjectLibrary.class, h1); - DynamicObjectLibrary off = createLibrary(DynamicObjectLibrary.class, h1); + var on = createLibrary(h1); + var off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -589,8 +587,8 @@ public void testPropertyAssumptionInvalidAfterReplace1() { int flag = 2; DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(DynamicObjectLibrary.class, h1); - DynamicObjectLibrary off = createLibrary(DynamicObjectLibrary.class, h1); + var on = createLibrary(h1); + var off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -623,8 +621,8 @@ public void testPropertyAssumptionInvalidAfterReplace2() { int flag = 2; DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(DynamicObjectLibrary.class, h1); - DynamicObjectLibrary off = createLibrary(DynamicObjectLibrary.class, h1); + var on = createLibrary(h1); + var off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -656,7 +654,7 @@ public void testPropertyAssumptionInvalidAfterTypeTransition() { Shape emptyShape = Shape.newBuilder().propertyAssumptions(true).build(); DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, h1); + var lib = createLibrary(h1); // initialize caches lib.put(h1, "name", 42); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java new file mode 100644 index 000000000000..14834be87d6b --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.object.test; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.test.AbstractLibraryTest; + +@RunWith(Parameterized.class) +public abstract class ParametrizedDynamicObjectTest extends AbstractLibraryTest { + + public enum TestRun { + CACHED_LIBRARY, + UNCACHED_LIBRARY, + DISPATCHED_CACHED_LIBRARY, + DISPATCHED_UNCACHED_LIBRARY, + CACHED_NODES, + UNCACHED_NODES; + + public static final TestRun[] DISPATCHED_ONLY = {DISPATCHED_CACHED_LIBRARY, DISPATCHED_UNCACHED_LIBRARY, CACHED_NODES, UNCACHED_NODES}; + public static final TestRun[] UNCACHED_ONLY = {DISPATCHED_UNCACHED_LIBRARY, UNCACHED_NODES}; + } + + @Parameter // first data value (0) is default + public /* NOT private */ TestRun run; + + protected final DynamicObjectLibraryWrapper createLibrary(Object receiver) { + return switch (run) { + case CACHED_LIBRARY -> wrap(adoptNode(DynamicObjectLibrary.getFactory().create(receiver)).get()); + case UNCACHED_LIBRARY -> wrap(DynamicObjectLibrary.getFactory().getUncached(receiver)); + case DISPATCHED_CACHED_LIBRARY -> wrap(adoptNode(DynamicObjectLibrary.getFactory().createDispatched(2)).get()); + case DISPATCHED_UNCACHED_LIBRARY -> wrap(DynamicObjectLibrary.getUncached()); + case CACHED_NODES -> new NodesFakeDynamicObjectLibrary(); + case UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; + }; + + } + + protected final DynamicObjectLibraryWrapper createLibrary() { + assert run != TestRun.CACHED_LIBRARY; + assert run != TestRun.UNCACHED_LIBRARY; + return createLibrary(null); + } + + protected final DynamicObjectLibraryWrapper uncachedLibrary() { + return switch (run) { + case CACHED_LIBRARY, UNCACHED_LIBRARY, DISPATCHED_CACHED_LIBRARY, DISPATCHED_UNCACHED_LIBRARY -> + wrap(DynamicObjectLibrary.getUncached()); + case CACHED_NODES, UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; + }; + } + + protected abstract static class DynamicObjectLibraryWrapper { + + public boolean accepts(Object receiver) { + return true; + } + + public abstract Shape getShape(DynamicObject object); + + public abstract Object getOrDefault(DynamicObject object, Object key, Object defaultValue); + + public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + Object value = getOrDefault(object, key, defaultValue); + if (value instanceof Integer) { + return (int) value; + } else { + throw new UnexpectedResultException(value); + } + } + + public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + Object value = getOrDefault(object, key, defaultValue); + if (value instanceof Double) { + return (double) value; + } else { + throw new UnexpectedResultException(value); + } + } + + public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + Object value = getOrDefault(object, key, defaultValue); + if (value instanceof Long) { + return (long) value; + } else { + throw new UnexpectedResultException(value); + } + } + + public abstract void put(DynamicObject object, Object key, Object value); + + public void putInt(DynamicObject object, Object key, int value) { + put(object, key, value); + } + + public void putDouble(DynamicObject object, Object key, double value) { + put(object, key, value); + } + + public void putLong(DynamicObject object, Object key, long value) { + put(object, key, value); + } + + public abstract boolean putIfPresent(DynamicObject object, Object key, Object value); + + public abstract void putWithFlags(DynamicObject object, Object key, Object value, int flags); + + public abstract void putConstant(DynamicObject object, Object key, Object value, int flags); + + public abstract boolean removeKey(DynamicObject object, Object key); + + public abstract boolean setDynamicType(DynamicObject object, Object type); + + public abstract Object getDynamicType(DynamicObject object); + + public abstract boolean containsKey(DynamicObject object, Object key); + + public abstract int getShapeFlags(DynamicObject object); + + public abstract boolean setShapeFlags(DynamicObject object, int flags); + + public abstract Property getProperty(DynamicObject object, Object key); + + public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int defaultValue) { + Property property = getProperty(object, key); + return property != null ? property.getFlags() : defaultValue; + } + + public abstract boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags); + + public abstract void markShared(DynamicObject object); + + public abstract boolean isShared(DynamicObject object); + + public abstract boolean updateShape(DynamicObject object); + + public abstract boolean resetShape(DynamicObject object, Shape otherShape); + + public abstract Object[] getKeyArray(DynamicObject object); + + public abstract Property[] getPropertyArray(DynamicObject object); + } + + protected static DynamicObjectLibraryWrapper wrap(DynamicObjectLibrary library) { + return new DynamicObjectLibraryWrapper() { + + @Override + public Shape getShape(DynamicObject object) { + return library.getShape(object); + } + + @Override + public Object getOrDefault(DynamicObject object, Object key, Object defaultValue) { + return library.getOrDefault(object, key, defaultValue); + } + + @Override + public void put(DynamicObject object, Object key, Object value) { + library.put(object, key, value); + } + + @Override + public boolean putIfPresent(DynamicObject object, Object key, Object value) { + return library.putIfPresent(object, key, value); + } + + @Override + public void putWithFlags(DynamicObject object, Object key, Object value, int flags) { + library.putWithFlags(object, key, value, flags); + } + + @Override + public void putConstant(DynamicObject object, Object key, Object value, int flags) { + library.putConstant(object, key, value, flags); + } + + @Override + public boolean removeKey(DynamicObject object, Object key) { + return library.removeKey(object, key); + } + + @Override + public boolean setDynamicType(DynamicObject object, Object type) { + return library.setDynamicType(object, type); + } + + @Override + public Object getDynamicType(DynamicObject object) { + return library.getDynamicType(object); + } + + @Override + public boolean containsKey(DynamicObject object, Object key) { + return library.containsKey(object, key); + } + + @Override + public int getShapeFlags(DynamicObject object) { + return library.getShapeFlags(object); + } + + @Override + public boolean setShapeFlags(DynamicObject object, int flags) { + return library.setShapeFlags(object, flags); + } + + @Override + public Property getProperty(DynamicObject object, Object key) { + return library.getProperty(object, key); + } + + @Override + public boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags) { + return library.setPropertyFlags(object, key, propertyFlags); + } + + @Override + public void markShared(DynamicObject object) { + library.markShared(object); + } + + @Override + public boolean isShared(DynamicObject object) { + return library.isShared(object); + } + + @Override + public boolean updateShape(DynamicObject object) { + return library.updateShape(object); + } + + @Override + public boolean resetShape(DynamicObject object, Shape otherShape) { + return library.resetShape(object, otherShape); + } + + @Override + public Object[] getKeyArray(DynamicObject object) { + return library.getKeyArray(object); + } + + @Override + public Property[] getPropertyArray(DynamicObject object) { + return library.getPropertyArray(object); + } + }; + } + + static final DynamicObjectLibraryWrapper UNCACHED_NODES_LIBRARY = new NodesFakeDynamicObjectLibrary("uncached"); + + static class NodesFakeDynamicObjectLibrary extends DynamicObjectLibraryWrapper { + + final DynamicObject.GetNode getNode; + final DynamicObject.PutNode putNode; + final DynamicObject.RemoveKeyNode removeKeyNode; + final DynamicObject.SetDynamicTypeNode setDynamicTypeNode; + final DynamicObject.GetDynamicTypeNode getDynamicTypeNode; + final DynamicObject.ContainsKeyNode containsKeyNode; + final DynamicObject.GetShapeFlagsNode getShapeFlagsNode; + final DynamicObject.SetShapeFlagsNode setShapeFlagsNode; + final DynamicObject.GetPropertyNode getPropertyNode; + final DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode; + final DynamicObject.MarkSharedNode markSharedNode; + final DynamicObject.IsSharedNode isSharedNode; + final DynamicObject.UpdateShapeNode updateShapeNode; + final DynamicObject.ResetShapeNode resetShapeNode; + final DynamicObject.GetKeyArrayNode getKeyArrayNode; + final DynamicObject.GetPropertyArrayNode getPropertyArrayNode; + + NodesFakeDynamicObjectLibrary() { + getNode = DynamicObject.GetNode.create(); + putNode = DynamicObject.PutNode.create(); + removeKeyNode = DynamicObject.RemoveKeyNode.create(); + setDynamicTypeNode = DynamicObject.SetDynamicTypeNode.create(); + getDynamicTypeNode = DynamicObject.GetDynamicTypeNode.create(); + containsKeyNode = DynamicObject.ContainsKeyNode.create(); + getShapeFlagsNode = DynamicObject.GetShapeFlagsNode.create(); + setShapeFlagsNode = DynamicObject.SetShapeFlagsNode.create(); + getPropertyNode = DynamicObject.GetPropertyNode.create(); + setPropertyFlagsNode = DynamicObject.SetPropertyFlagsNode.create(); + markSharedNode = DynamicObject.MarkSharedNode.create(); + isSharedNode = DynamicObject.IsSharedNode.create(); + updateShapeNode = DynamicObject.UpdateShapeNode.create(); + resetShapeNode = DynamicObject.ResetShapeNode.create(); + getKeyArrayNode = DynamicObject.GetKeyArrayNode.create(); + getPropertyArrayNode = DynamicObject.GetPropertyArrayNode.create(); + } + + NodesFakeDynamicObjectLibrary(String uncached) { + getNode = DynamicObject.GetNode.getUncached(); + putNode = DynamicObject.PutNode.getUncached(); + removeKeyNode = DynamicObject.RemoveKeyNode.getUncached(); + setDynamicTypeNode = DynamicObject.SetDynamicTypeNode.getUncached(); + getDynamicTypeNode = DynamicObject.GetDynamicTypeNode.getUncached(); + containsKeyNode = DynamicObject.ContainsKeyNode.getUncached(); + getShapeFlagsNode = DynamicObject.GetShapeFlagsNode.getUncached(); + setShapeFlagsNode = DynamicObject.SetShapeFlagsNode.getUncached(); + getPropertyNode = DynamicObject.GetPropertyNode.getUncached(); + setPropertyFlagsNode = DynamicObject.SetPropertyFlagsNode.getUncached(); + markSharedNode = DynamicObject.MarkSharedNode.getUncached(); + isSharedNode = DynamicObject.IsSharedNode.getUncached(); + updateShapeNode = DynamicObject.UpdateShapeNode.getUncached(); + resetShapeNode = DynamicObject.ResetShapeNode.getUncached(); + getKeyArrayNode = DynamicObject.GetKeyArrayNode.getUncached(); + getPropertyArrayNode = DynamicObject.GetPropertyArrayNode.getUncached(); + } + + @Override + public boolean accepts(Object receiver) { + return true; + } + + @Override + public Shape getShape(DynamicObject object) { + return object.getShape(); + } + + @Override + public Object getOrDefault(DynamicObject object, Object key, Object defaultValue) { + return getNode.getOrDefault(object, key, defaultValue); + } + + @Override + public void put(DynamicObject object, Object key, Object value) { + putNode.put(object, key, value); + } + + @Override + public boolean putIfPresent(DynamicObject object, Object key, Object value) { + return putNode.putIfPresent(object, key, value); + } + + @Override + public void putWithFlags(DynamicObject object, Object key, Object value, int flags) { + putNode.putWithFlags(object, key, value, flags); + } + + @Override + public void putConstant(DynamicObject object, Object key, Object value, int flags) { + putNode.putConstantWithFlags(object, key, value, flags); + } + + @Override + public boolean removeKey(DynamicObject object, Object key) { + return removeKeyNode.execute(object, key); + } + + @Override + public boolean setDynamicType(DynamicObject object, Object type) { + return setDynamicTypeNode.execute(object, type); + } + + @Override + public Object getDynamicType(DynamicObject object) { + return getDynamicTypeNode.execute(object); + } + + @Override + public boolean containsKey(DynamicObject object, Object key) { + return containsKeyNode.execute(object, key); + } + + @Override + public int getShapeFlags(DynamicObject object) { + return getShapeFlagsNode.execute(object); + } + + @Override + public boolean setShapeFlags(DynamicObject object, int flags) { + return setShapeFlagsNode.execute(object, flags); + } + + @Override + public Property getProperty(DynamicObject object, Object key) { + return getPropertyNode.execute(object, key); + } + + @Override + public boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags) { + return setPropertyFlagsNode.execute(object, key, propertyFlags); + } + + @Override + public void markShared(DynamicObject object) { + markSharedNode.execute(object); + } + + @Override + public boolean isShared(DynamicObject object) { + return isSharedNode.execute(object); + } + + @Override + public boolean updateShape(DynamicObject object) { + return updateShapeNode.execute(object); + } + + @Override + public boolean resetShape(DynamicObject object, Shape otherShape) { + return resetShapeNode.execute(object, otherShape); + } + + @Override + public Object[] getKeyArray(DynamicObject object) { + return getKeyArrayNode.execute(object); + } + + @Override + public Property[] getPropertyArray(DynamicObject object) { + return getPropertyArrayNode.execute(object); + } + } + +} diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PolymorphicPrimitivesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PolymorphicPrimitivesTest.java index 5e63fa5c3468..a76b9a58f59e 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PolymorphicPrimitivesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PolymorphicPrimitivesTest.java @@ -50,20 +50,18 @@ import java.util.Arrays; import java.util.List; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.object.test.ObjectModelRegressionTest.TestDynamicObject; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class PolymorphicPrimitivesTest extends AbstractParametrizedLibraryTest { +public class PolymorphicPrimitivesTest extends ParametrizedDynamicObjectTest { @Parameters(name = "{0}") public static List data() { @@ -87,7 +85,7 @@ public void testIntLongBoxed() { Shape emptyShape = newEmptyShape(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "x", 42L); @@ -104,7 +102,7 @@ public void testImplicitCastIntToLong() { Shape emptyShape = newEmptyShapeWithImplicitCastIntToLong(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "x", 42L); @@ -123,7 +121,7 @@ public void testIntLongPolymorphic1() { Shape emptyShape = newEmptyShape(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "x", 42L); @@ -142,7 +140,7 @@ public void testIntLongPolymorphic2() { Shape emptyShape = newEmptyShape(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "y", Integer.MAX_VALUE); @@ -158,7 +156,7 @@ public void testIntLongPolymorphic2() { assertEquals(object1.getShape().getProperty("y").getLocation(), object2.getShape().getProperty("y").getLocation()); object2 = newInstance(emptyShape); - library = createLibrary(DynamicObjectLibrary.class, object2); + library = createLibrary(object2); library.put(object2, "x", 42L); library.put(object2, "y", Integer.MAX_VALUE); @@ -175,7 +173,7 @@ public void testIntLongPolymorphic2() { public void testIntLongPolymorphic3() { Shape emptyShape = newEmptyShape(); DynamicObject o = newInstance(emptyShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, o); + var lib = createLibrary(o); for (int i = -6; i < 0; i++) { lib.put(o, i, 0); } @@ -305,7 +303,7 @@ public void testIntLongPolymorphic3() { } private void verifySet(DynamicObject o, Object[] v, int i, Object value) { - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, o); + var library = createLibrary(o); for (int j = 0; j < v.length; j++) { assertEquals(v[j], library.getOrDefault(o, j, null)); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PropertyGetterTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PropertyGetterTest.java index 0160891e9c03..fa931e6e0ee1 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PropertyGetterTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PropertyGetterTest.java @@ -46,16 +46,25 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.PropertyGetter; import com.oracle.truffle.api.object.Shape; -import org.junit.Test; -import com.oracle.truffle.api.nodes.UnexpectedResultException; -import com.oracle.truffle.api.test.AbstractLibraryTest; +@RunWith(Parameterized.class) +public class PropertyGetterTest extends ParametrizedDynamicObjectTest { -public class PropertyGetterTest extends AbstractLibraryTest { + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.DISPATCHED_ONLY); + } @Test public void testPropertyGetter() throws Exception { @@ -73,9 +82,9 @@ public void testPropertyGetter() throws Exception { double doubleVal = 3.14159265359; String doubleKey = "doubleKey"; - DynamicObjectLibrary uncached = DynamicObjectLibrary.getUncached(); - uncached.putWithFlags(o1, key, val, 13); - uncached.putWithFlags(o2, key, val, 13); + var lib = createLibrary(); + lib.putWithFlags(o1, key, val, 13); + lib.putWithFlags(o2, key, val, 13); assertSame("expected same shape", o1.getShape(), o2.getShape()); @@ -90,9 +99,9 @@ public void testPropertyGetter() throws Exception { assertFails(() -> getter.getInt(o3), IllegalArgumentException.class); assertFails(() -> getter.getInt(o1), UnexpectedResultException.class, e -> assertEquals(val, e.getResult())); - uncached.put(o1, intKey, intVal); - uncached.put(o1, longKey, longVal); - uncached.put(o1, doubleKey, doubleVal); + lib.put(o1, intKey, intVal); + lib.put(o1, longKey, longVal); + lib.put(o1, doubleKey, doubleVal); PropertyGetter intGetter = o1.getShape().makePropertyGetter(intKey); PropertyGetter longGetter = o1.getShape().makePropertyGetter(longKey); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/RemoveKeyTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/RemoveKeyTest.java index 2d30b3efee1e..7c139611e7b3 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/RemoveKeyTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/RemoveKeyTest.java @@ -45,18 +45,16 @@ import java.util.List; import java.util.Map; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; @RunWith(Parameterized.class) -public class RemoveKeyTest extends AbstractParametrizedLibraryTest { +public class RemoveKeyTest extends ParametrizedDynamicObjectTest { @Parameters(name = "{0}") public static List data() { @@ -69,7 +67,7 @@ public static List data() { public void testRemoveAfterReplace() { DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary in = createLibrary(DynamicObjectLibrary.class, obj); + var in = createLibrary(obj); in.put(obj, "date", new Object()); in.put(obj, "time", new Object()); in.put(obj, "zone", new Object()); @@ -99,7 +97,7 @@ public void testRemoveAfterReplace() { Map archive = DOTestAsserts.archive(obj); - DynamicObjectLibrary rm = createLibrary(DynamicObjectLibrary.class, obj); + var rm = createLibrary(obj); rm.removeKey(obj, "time"); DOTestAsserts.verifyValues(obj, archive); @@ -109,7 +107,7 @@ public void testRemoveAfterReplace() { public void testRemoveAfterReplaceGR30786() { DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary in = createLibrary(DynamicObjectLibrary.class, obj); + var in = createLibrary(obj); in.put(obj, "head", new Object()); in.put(obj, "fun", new Object()); in.put(obj, "body", new Object()); @@ -143,7 +141,7 @@ public void testRemoveAfterReplaceGR30786() { Map archive = DOTestAsserts.archive(obj); - DynamicObjectLibrary rm = createLibrary(DynamicObjectLibrary.class, obj); + var rm = createLibrary(obj); rm.removeKey(obj, "fun"); DOTestAsserts.verifyValues(obj, archive); @@ -157,7 +155,7 @@ public void testReversePairwise() { Object undefined = new Object(); DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, obj); + var lib = createLibrary(obj); lib.put(obj, "length", 10.0); lib.put(obj, "0", true); @@ -214,7 +212,7 @@ public void testReverseSequential() { Object undefined = new Object(); DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, obj); + var lib = createLibrary(obj); lib.put(obj, "length", 10.0); lib.put(obj, "0", true); @@ -272,7 +270,7 @@ public void testReverseSequential() { @Test public void testRemoveUsingFallback() { DynamicObject obj1 = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, obj1); + var lib = createLibrary(obj1); lib.put(obj1, "length", 10.0); lib.put(obj1, "0", true); lib.put(obj1, "1", 11); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/SharedShapeTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/SharedShapeTest.java index 975772eeea55..d3af84b80b4c 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/SharedShapeTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/SharedShapeTest.java @@ -51,21 +51,19 @@ import java.util.Arrays; import java.util.List; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Location; -import com.oracle.truffle.api.object.Shape; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Location; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class SharedShapeTest extends AbstractParametrizedLibraryTest { +public class SharedShapeTest extends ParametrizedDynamicObjectTest { @Parameters(name = "{0}") public static List data() { @@ -87,7 +85,7 @@ private DynamicObject newInstanceShared() { public void testDifferentLocationsImplicitCast() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", 1); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -106,7 +104,7 @@ public void testDifferentLocationsImplicitCast() { public void testNoReuseOfPreviousLocation() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", 0); library.put(object, "a", 1); @@ -145,7 +143,7 @@ public void testNoReuseOfPreviousLocation() { public void testCanReuseLocationsUntilShared() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", 1); Location locationA1 = object.getShape().getProperty("a").getLocation(); @@ -204,7 +202,7 @@ public void testShapeIsSharedAndIdentity() { Assert.assertSame(sharedShape, rootShape.makeSharedShape()); Assert.assertEquals(true, sharedShape.isShared()); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.markShared(object); Assert.assertSame(sharedShape, object.getShape()); @@ -230,7 +228,7 @@ public void testShapeIsSharedAndIdentity() { public void testReuseReplaceProperty() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", 0); library.put(object, "a", 1); @@ -244,7 +242,7 @@ public void testReuseReplaceProperty() { public void testDeleteFromSharedShape() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); Shape emptyShape = object.getShape(); library.put(object, "a", 1); @@ -263,7 +261,7 @@ public void testDeleteFromSharedShape() { public void testDeleteFromSharedShape2() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); Shape emptyShape = object.getShape(); library.put(object, "a", 1); @@ -279,7 +277,7 @@ public void testDeleteFromSharedShape2() { public void testReplaceProperty() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, object); + var library = createLibrary(object); library.put(object, "a", 1); library.markShared(object); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java new file mode 100644 index 000000000000..1dc61b64dff3 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.object.test; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.DynamicObject.GetNode; + +@SuppressWarnings("truffle") +public abstract class TestNestedDispatchGetNode extends Node { + + final Object key = "testKey"; + + public abstract Object execute(DynamicObject obj); + + @Specialization(guards = {"obj == cachedObj"}, limit = "1") + Object cached(DynamicObject obj, + @Cached("obj") @SuppressWarnings("unused") DynamicObject cachedObj, + @Cached GetNode getNode, + @Cached(value = "cachedObj.getShape().getProperty(key) != null") boolean hasProperty, + @Cached TestNestedDispatchNode nested) { + Object value = getNode.getOrDefault(obj, key, null); + if (hasProperty) { + assert value != null; + if (value instanceof DynamicObject) { + return nested.execute((DynamicObject) value); + } + } + return value; + } + + @Specialization(limit = "3") + Object cached(DynamicObject obj, + @Cached GetNode getNode, + @Cached(value = "obj.getShape().getProperty(key) != null", allowUncached = true) boolean hasProperty, + @Cached TestNestedDispatchNode nested) { + Object value = getNode.getOrDefault(obj, key, null); + if (hasProperty) { + assert value != null; + if (value instanceof DynamicObject) { + return nested.execute((DynamicObject) value); + } + } + return value; + } + +} diff --git a/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest b/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest index 68657066581b..16fc7b05e6bf 100644 --- a/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest +++ b/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest @@ -1,6 +1,47 @@ #Signature file v4.1 #Version +CLSS public abstract interface !annotation com.oracle.truffle.api.dsl.GenerateCached + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=CLASS) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) +intf java.lang.annotation.Annotation +meth public abstract !hasdefault boolean alwaysInlineCached() +meth public abstract !hasdefault boolean inherit() +meth public abstract !hasdefault boolean value() + +CLSS public abstract interface !annotation com.oracle.truffle.api.dsl.GenerateInline + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=CLASS) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) +intf java.lang.annotation.Annotation +meth public abstract !hasdefault boolean inherit() +meth public abstract !hasdefault boolean inlineByDefault() +meth public abstract !hasdefault boolean value() + +CLSS public abstract interface !annotation com.oracle.truffle.api.dsl.GeneratePackagePrivate + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=CLASS) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) +intf java.lang.annotation.Annotation + +CLSS public abstract interface !annotation com.oracle.truffle.api.dsl.GenerateUncached + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=CLASS) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) +intf java.lang.annotation.Annotation +meth public abstract !hasdefault boolean inherit() +meth public abstract !hasdefault boolean value() + +CLSS public abstract interface !annotation com.oracle.truffle.api.dsl.GeneratedBy + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) +intf java.lang.annotation.Annotation +meth public abstract !hasdefault java.lang.String methodName() +meth public abstract java.lang.Class value() + +CLSS public abstract interface !annotation com.oracle.truffle.api.dsl.ImportStatic + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=CLASS) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) +intf java.lang.annotation.Annotation +meth public abstract java.lang.Class[] value() + CLSS public abstract interface com.oracle.truffle.api.interop.TruffleObject CLSS public abstract interface !annotation com.oracle.truffle.api.library.ExportLibrary @@ -112,11 +153,52 @@ meth public abstract void setDouble(com.oracle.truffle.api.object.DynamicObject, CLSS public abstract com.oracle.truffle.api.object.DynamicObject cons protected init(com.oracle.truffle.api.object.Shape) innr protected abstract interface static !annotation DynamicField +innr public abstract static AddShapeFlagsNode +innr public abstract static ContainsKeyNode +innr public abstract static CopyPropertiesNode +innr public abstract static GetDynamicTypeNode +innr public abstract static GetKeyArrayNode +innr public abstract static GetNode +innr public abstract static GetPropertyArrayNode +innr public abstract static GetPropertyFlagsNode +innr public abstract static GetPropertyNode +innr public abstract static GetShapeFlagsNode +innr public abstract static HasShapeFlagsNode +innr public abstract static IsSharedNode +innr public abstract static MarkSharedNode +innr public abstract static PutNode +innr public abstract static RemoveKeyNode +innr public abstract static ResetShapeNode +innr public abstract static SetDynamicTypeNode +innr public abstract static SetPropertyFlagsNode +innr public abstract static SetShapeFlagsNode +innr public abstract static UpdateShapeNode intf com.oracle.truffle.api.interop.TruffleObject meth protected java.lang.Object clone() throws java.lang.CloneNotSupportedException meth public final com.oracle.truffle.api.object.Shape getShape() supr java.lang.Object -hfds LOOKUP,SHAPE_OFFSET,UNSAFE,extRef,extVal,shape +hfds LOOKUP,SHAPE_CACHE_LIMIT,SHAPE_OFFSET,UNSAFE,extRef,extVal,shape + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$AddShapeFlagsNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,int) +meth public static com.oracle.truffle.api.object.DynamicObject$AddShapeFlagsNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$AddShapeFlagsNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$ContainsKeyNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object) +meth public static com.oracle.truffle.api.object.DynamicObject$ContainsKeyNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$ContainsKeyNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$CopyPropertiesNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract void execute(com.oracle.truffle.api.object.DynamicObject,com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$CopyPropertiesNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$CopyPropertiesNode getUncached() +supr com.oracle.truffle.api.nodes.Node CLSS protected abstract interface static !annotation com.oracle.truffle.api.object.DynamicObject$DynamicField outer com.oracle.truffle.api.object.DynamicObject @@ -124,6 +206,155 @@ CLSS protected abstract interface static !annotation com.oracle.truffle.api.obje anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[FIELD]) intf java.lang.annotation.Annotation +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetDynamicTypeNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract java.lang.Object execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$GetDynamicTypeNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetDynamicTypeNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetKeyArrayNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract java.lang.Object[] execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$GetKeyArrayNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetKeyArrayNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetNode + outer com.oracle.truffle.api.object.DynamicObject +meth public final double getDoubleOrDefault(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) throws com.oracle.truffle.api.nodes.UnexpectedResultException +meth public final int getIntOrDefault(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) throws com.oracle.truffle.api.nodes.UnexpectedResultException +meth public final java.lang.Object getOrDefault(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final java.lang.Object getOrNull(com.oracle.truffle.api.object.DynamicObject,java.lang.Object) +meth public final long getLongOrDefault(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) throws com.oracle.truffle.api.nodes.UnexpectedResultException +meth public static com.oracle.truffle.api.object.DynamicObject$GetNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetPropertyArrayNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract com.oracle.truffle.api.object.Property[] execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$GetPropertyArrayNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetPropertyArrayNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetPropertyFlagsNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract int execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,int) +meth public static com.oracle.truffle.api.object.DynamicObject$GetPropertyFlagsNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetPropertyFlagsNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetPropertyNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract com.oracle.truffle.api.object.Property execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object) +meth public static com.oracle.truffle.api.object.DynamicObject$GetPropertyNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetPropertyNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$GetShapeFlagsNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract int execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$GetShapeFlagsNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetShapeFlagsNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$HasShapeFlagsNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,int) +meth public static com.oracle.truffle.api.object.DynamicObject$HasShapeFlagsNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$HasShapeFlagsNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$IsSharedNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$IsSharedNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$IsSharedNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$MarkSharedNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$MarkSharedNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$MarkSharedNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$PutNode + outer com.oracle.truffle.api.object.DynamicObject +meth public final boolean putConstantIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean putConstantIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean putConstantWithFlagsIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final boolean putConstantWithFlagsIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final boolean putIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean putIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean putWithFlagsIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final boolean putWithFlagsIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final void put(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final void putConstant(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final void putWithFlags(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public static com.oracle.truffle.api.object.DynamicObject$PutNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$PutNode getUncached() +meth public void putConstantWithFlags(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +supr com.oracle.truffle.api.nodes.Node +hfds CONST_VALUE,DEFAULT,IF_ABSENT,IF_PRESENT,UPDATE_FLAGS + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$RemoveKeyNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object) +meth public static com.oracle.truffle.api.object.DynamicObject$RemoveKeyNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$RemoveKeyNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$ResetShapeNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,com.oracle.truffle.api.object.Shape) +meth public static com.oracle.truffle.api.object.DynamicObject$ResetShapeNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$ResetShapeNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$SetDynamicTypeNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object) +meth public static com.oracle.truffle.api.object.DynamicObject$SetDynamicTypeNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$SetDynamicTypeNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$SetPropertyFlagsNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,int) +meth public static com.oracle.truffle.api.object.DynamicObject$SetPropertyFlagsNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$SetPropertyFlagsNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$SetShapeFlagsNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject,int) +meth public static com.oracle.truffle.api.object.DynamicObject$SetShapeFlagsNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$SetShapeFlagsNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$UpdateShapeNode + outer com.oracle.truffle.api.object.DynamicObject +meth public abstract boolean execute(com.oracle.truffle.api.object.DynamicObject) +meth public static com.oracle.truffle.api.object.DynamicObject$UpdateShapeNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$UpdateShapeNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +CLSS public final com.oracle.truffle.api.object.DynamicObjectFactory +cons public init() +innr public final static GetNodeGen +supr java.lang.Object +hcls AddShapeFlagsNodeGen,ContainsKeyNodeGen,CopyPropertiesNodeGen,GetDynamicTypeNodeGen,GetKeyArrayNodeGen,GetPropertyArrayNodeGen,GetPropertyFlagsNodeGen,GetPropertyNodeGen,GetShapeFlagsNodeGen,HasShapeFlagsNodeGen,IsSharedNodeGen,MarkSharedNodeGen,PutNodeGen,RemoveKeyNodeGen,ResetShapeNodeGen,SetDynamicTypeNodeGen,SetPropertyFlagsNodeGen,SetShapeFlagsNodeGen,UpdateShapeNodeGen + +CLSS public final static com.oracle.truffle.api.object.DynamicObjectFactory$GetNodeGen + outer com.oracle.truffle.api.object.DynamicObjectFactory +meth public static com.oracle.truffle.api.object.DynamicObject$GetNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$GetNode getUncached() +supr com.oracle.truffle.api.object.DynamicObject$GetNode +hfds CACHED_CACHE_UPDATER,UNCACHED,cached_cache,state_0_ +hcls CachedData,Uncached + CLSS public abstract com.oracle.truffle.api.object.DynamicObjectLibrary meth public abstract boolean containsKey(com.oracle.truffle.api.object.DynamicObject,java.lang.Object) meth public abstract boolean isShared(com.oracle.truffle.api.object.DynamicObject) @@ -440,8 +671,3 @@ CLSS public abstract interface !annotation java.lang.annotation.Target intf java.lang.annotation.Annotation meth public abstract java.lang.annotation.ElementType[] value() -CLSS public abstract interface !annotation jdk.internal.vm.annotation.AOTSafeClassInitializer - anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME) - anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) -intf java.lang.annotation.Annotation - diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObject.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObject.java index 9697d2869120..69eda0a85f70 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObject.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -48,22 +48,42 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; +import java.util.Map; +import com.oracle.truffle.api.Assumption; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GeneratePackagePrivate; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NeverDefault; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.object.DynamicObjectLibraryImpl.RemovePlan; +import com.oracle.truffle.api.object.DynamicObjectFactory.GetNodeGen; +import com.oracle.truffle.api.object.DynamicObjectFactory.PutNodeGen; import sun.misc.Unsafe; +// @formatter:off /** * Represents a dynamic object, members of which can be dynamically added and removed at run time. * - * To use it, extend {@link DynamicObject} and use {@link DynamicObjectLibrary} for object accesses. + * To use it, extend {@link DynamicObject} and use nodes nested under DynamicObject such as {@link DynamicObject.GetNode} for object accesses. * * When {@linkplain DynamicObject#DynamicObject(Shape) constructing} a {@link DynamicObject}, it has * to be initialized with an empty initial shape. Initial shapes are created using - * {@link Shape#newBuilder()} and should ideally be shared per TruffleLanguage instance to allow + * {@link Shape#newBuilder()} and should ideally be shared per {@link TruffleLanguage} instance to allow * shape caches to be shared across contexts. * * Subclasses can provide in-object dynamic field slots using the {@link DynamicField} annotation @@ -71,9 +91,7 @@ * *

* Example: - * - *

- * 
+ * {@snippet :
  * public class MyObject extends DynamicObject implements TruffleObject {
  *     public MyObject(Shape shape) {
  *         super(shape);
@@ -83,15 +101,76 @@
  * Shape initialShape = Shape.newBuilder().layout(MyObject.class).build();
  *
  * MyObject obj = new MyObject(initialShape);
- * 
- * 
+ * } + * + *

General documentation about DynamicObject nodes

+ * + * DynamicObject nodes is the central interface for accessing and mutating properties and other state (flags, + * dynamic type) of {@link DynamicObject}s. + * All nodes provide cached and uncached variants. + * + *

+ * Property keys are always compared using object identity ({@code ==}), never with {@code equals}. + * This is because it is far more efficient for host inlining that way, and caching by {@code equals} is only needed in some cases. + * If some keys might be {@code equals} but not have the same identity ({@code ==}), + * it can be worthwhile to "intern" the key using an inline cache before using the DynamicObject node: + * {@snippet : + * import com.oracle.truffle.api.dsl.Cached.Exclusive; + * import com.oracle.truffle.api.strings.TruffleString; + * + * @Specialization(guards = "equalNode.execute(key, cachedKey, ENCODING)", limit = "3") + * static Object read(MyDynamicObjectSubclass receiver, TruffleString key, + * @Cached TruffleString.EqualNode equalNode, + * @Cached TruffleString cachedKey, + * @Cached @Exclusive DynamicObject.GetNode getNode) { + * return getNode.getOrDefault(receiver, cachedKey, NULL_VALUE); + * } + * } + * + *

Usage examples:

+ * + * {@snippet : + * @Specialization(limit = "3") + * static Object read(MyDynamicObjectSubclass receiver, Object key, + * @Cached DynamicObject.GetNode getNode) { + * return getNode.getOrDefault(receiver, key, NULL_VALUE); + * } + * } + * + * {@snippet : + * @ExportMessage + * Object readMember(String name, + * @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { + * Object result = getNode.getOrDefault(this, name, null); + * if (result == null) { + * throw UnknownIdentifierException.create(name); + * } + * return result; + * } + * } * * @see DynamicObject#DynamicObject(Shape) - * @see DynamicObjectLibrary * @see Shape * @see Shape#newBuilder() + * @see DynamicObject.GetNode + * @see DynamicObject.ContainsKeyNode + * @see DynamicObject.GetPropertyNode + * @see DynamicObject.GetPropertyFlagsNode + * @see DynamicObject.PutNode + * @see DynamicObject.GetDynamicTypeNode + * @see DynamicObject.SetDynamicTypeNode + * @see DynamicObject.GetShapeFlagsNode + * @see DynamicObject.SetShapeFlagsNode + * @see DynamicObject.GetKeyArrayNode + * @see DynamicObject.GetPropertyArrayNode + * @see DynamicObject.RemoveKeyNode + * @see DynamicObject.UpdateShapeNode + * @see DynamicObject.IsSharedNode + * @see DynamicObject.MarkSharedNode * @since 0.8 or earlier */ +// @formatter:on +@SuppressWarnings("deprecation") public abstract class DynamicObject implements TruffleObject { private static final MethodHandles.Lookup LOOKUP = internalLookup(); @@ -261,6 +340,2022 @@ static Lookup internalLookup() { return LOOKUP; } + // NODES + + static final int SHAPE_CACHE_LIMIT = 5; + + /** + * Gets the value of a property or returns a default value if no such property exists. + *

+ * Specialized return type variants are available for when a primitive result is expected. + * + * @see #getOrNull(DynamicObject, Object) + * @see #getOrDefault(DynamicObject, Object, Object) + * @see #getIntOrDefault(DynamicObject, Object, Object) + * @see #getLongOrDefault(DynamicObject, Object, Object) + * @see #getDoubleOrDefault(DynamicObject, Object, Object) + * @since 25.1 + */ + @GenerateCached(true) + @GenerateInline(false) + @GenerateUncached + @ImportStatic(DynamicObject.class) + public abstract static class GetNode extends Node { + + GetNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetNode create() { + return GetNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetNode getUncached() { + return GetNodeGen.getUncached(); + } + + /** + * The same as {@code getOrDefault(receiver, key, null)}. + */ + public final Object getOrNull(DynamicObject receiver, Object key) { + return getOrDefault(receiver, key, null); + } + + // @formatter:off + /** + * Gets the value of an existing property or returns the provided default value if no such property exists. + * + *

Usage example:

+ * + * {@snippet : + * @Specialization(limit = "3") + * static Object read(DynamicObject receiver, Object key, + * @Cached DynamicObject.GetNode getNode) { + * return getNode.getOrDefault(receiver, key, NULL_VALUE); + * } + * } + * + * @param key the property key + * @param defaultValue value to be returned if the property does not exist + * @return the property's value if it exists, else {@code defaultValue}. + */ + // @formatter:on + public final Object getOrDefault(DynamicObject receiver, Object key, Object defaultValue) { + return executeImpl(receiver, key, defaultValue); + } + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + * @param key the property key + * @param defaultValue the value to be returned if the property does not exist + * @return the property's value if it exists, else {@code defaultValue}. + * @throws UnexpectedResultException if the value (or default value if the property is + * missing) is not an {@code int} + */ + public final int getIntOrDefault(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + return executeImplInt(receiver, key, defaultValue); + } + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + * @param key the property key + * @param defaultValue the value to be returned if the property does not exist + * @return the property's value if it exists, else {@code defaultValue}. + * @throws UnexpectedResultException if the value (or default value if the property is + * missing) is not an {@code long} + */ + public final long getLongOrDefault(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + return executeImplLong(receiver, key, defaultValue); + } + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + * @param key the property key + * @param defaultValue the value to be returned if the property does not exist + * @return the property's value if it exists, else {@code defaultValue}. + * @throws UnexpectedResultException if the value (or default value if the property is + * missing) is not an {@code double} + */ + public final double getDoubleOrDefault(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + return executeImplDouble(receiver, key, defaultValue); + } + + // private + + abstract Object executeImpl(DynamicObject receiver, Object key, Object defaultValue); + + abstract int executeImplInt(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; + + abstract long executeImplLong(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; + + abstract double executeImplDouble(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; + + @SuppressWarnings("unused") + @Specialization(guards = {"guard", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT", rewriteOn = UnexpectedResultException.class) + static long doCachedLong(DynamicObject receiver, Object key, Object defaultValue, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { + if (cachedLocation != null) { + return cachedLocation.getLong(receiver, guard); + } else { + return Location.expectLong(defaultValue); + } + } + + @SuppressWarnings("unused") + @Specialization(guards = {"guard", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT", rewriteOn = UnexpectedResultException.class) + static int doCachedInt(DynamicObject receiver, Object key, Object defaultValue, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { + if (cachedLocation != null) { + if (cachedLocation instanceof ExtLocations.IntArrayLocation intArrayLocation) { + return intArrayLocation.getInt(receiver, guard); + } else { + return cachedLocation.getInt(receiver, guard); + } + } else { + return Location.expectInteger(defaultValue); + } + } + + @SuppressWarnings("unused") + @Specialization(guards = {"guard", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT", rewriteOn = UnexpectedResultException.class) + static double doCachedDouble(DynamicObject receiver, Object key, Object defaultValue, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { + if (cachedLocation != null) { + return cachedLocation.getDouble(receiver, guard); + } else { + return Location.expectDouble(defaultValue); + } + } + + @SuppressWarnings("unused") + @Specialization(guards = {"guard", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT", replaces = {"doCachedLong", "doCachedInt", "doCachedDouble"}) + static Object doCached(DynamicObject receiver, Object key, Object defaultValue, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("cachedShape.getLocation(key)") Location cachedLocation) { + if (cachedLocation != null) { + if (cachedLocation instanceof ExtLocations.ObjectArrayLocation objectArrayLocation) { + return objectArrayLocation.get(receiver, guard); + } else if (cachedLocation instanceof ExtLocations.IntArrayLocation intArrayLocation) { + return intArrayLocation.get(receiver, guard); + } else { + return cachedLocation.get(receiver, guard); + } + } else { + return defaultValue; + } + } + + @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) + static long doGenericLong(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + Location location = receiver.getShape().getLocation(key); + if (location != null) { + return location.getLong(receiver, false); + } else { + return Location.expectLong(defaultValue); + } + } + + @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) + static int doGenericInt(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + Location location = receiver.getShape().getLocation(key); + if (location != null) { + return location.getInt(receiver, false); + } else { + return Location.expectInteger(defaultValue); + } + } + + @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) + static double doGenericDouble(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + Location location = receiver.getShape().getLocation(key); + if (location != null) { + return location.getDouble(receiver, false); + } else { + return Location.expectDouble(defaultValue); + } + } + + @TruffleBoundary + @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached", "doGenericLong", "doGenericInt", "doGenericDouble"}) + static Object doGeneric(DynamicObject receiver, Object key, Object defaultValue) { + Location location = receiver.getShape().getLocation(key); + if (location != null) { + return location.get(receiver, false); + } else { + return defaultValue; + } + } + + } + + /** + * Sets the value of an existing property or adds a new property if no such property exists. + * Additional variants allow setting property flags, only setting the property if it's either + * absent or present, and setting constant properties stored in the shape. + * + * @see #put(DynamicObject, Object, Object) + * @see #putIfAbsent(DynamicObject, Object, Object) + * @see #putIfPresent(DynamicObject, Object, Object) + * @see #putWithFlags(DynamicObject, Object, Object, int) + * @see #putWithFlagsIfAbsent(DynamicObject, Object, Object, int) + * @see #putWithFlagsIfPresent(DynamicObject, Object, Object, int) + * @see #putConstant(DynamicObject, Object, Object) + * @see #putConstantIfAbsent(DynamicObject, Object, Object) + * @see #putConstantIfPresent(DynamicObject, Object, Object) + * @see #putConstantWithFlags(DynamicObject, Object, Object, int) + * @see #putConstantWithFlagsIfAbsent(DynamicObject, Object, Object, int) + * @see #putConstantWithFlagsIfPresent(DynamicObject, Object, Object, int) + * @since 25.1 + */ + @GeneratePackagePrivate + @ImportStatic(DynamicObject.class) + @GenerateUncached + @GenerateCached(true) + @GenerateInline(false) + public abstract static class PutNode extends Node { + + PutNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static PutNode create() { + return PutNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static PutNode getUncached() { + return PutNodeGen.getUncached(); + } + + // @formatter:off + /** + * Sets the value of an existing property or adds a new property if no such property exists. + * + * A newly added property will have flags 0; flags of existing properties will not be changed. + * Use {@link #putWithFlags} to set property flags as well. + * + *

Usage example:

+ * + * {@snippet : + * @ExportMessage + * Object writeMember(String member, Object value, + * @Cached DynamicObject.PutNode putNode) { + * putNode.put(this, member, value); + * } + * } + * + * @param key the property key + * @param value the value to be set + * @see #putIfPresent(DynamicObject, Object, Object) + * @see #putWithFlags(DynamicObject, Object, Object, int) + */ + // @formatter:on + public final void put(DynamicObject receiver, Object key, Object value) { + executeImpl(receiver, key, value, DEFAULT, 0); + } + + /** + * Sets the value of the property if present, otherwise returns {@code false}. + * + * @param key property identifier + * @param value value to be set + * @return {@code true} if the property was present and the value set, otherwise + * {@code false} + * @see #put(DynamicObject, Object, Object) + */ + public final boolean putIfPresent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, IF_PRESENT, 0); + } + + /** + * Sets the value of the property if absent, otherwise returns {@code false}. + * + * @param key property identifier + * @param value value to be set + * @return {@code true} if the property was absent and the value set, otherwise + * {@code false} + * @see #put(DynamicObject, Object, Object) + */ + public final boolean putIfAbsent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, IF_ABSENT, 0); + } + + /** + * Same as {@link #putConstantWithFlags} with propertyFlags 0. + */ + public final void putConstant(DynamicObject receiver, Object key, Object value) { + executeImpl(receiver, key, value, DEFAULT | CONST_VALUE, 0); + } + + /** + * Like {@link #putConstant(DynamicObject, Object, Object)} but only if the property is + * present. + */ + public final boolean putConstantIfPresent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, IF_PRESENT | CONST_VALUE, 0); + } + + /** + * Like {@link #putConstant(DynamicObject, Object, Object)} but only if the property is + * absent. + */ + public final boolean putConstantIfAbsent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, IF_ABSENT | CONST_VALUE, 0); + } + + /** + * Like {@link #put(DynamicObject, Object, Object)}, but additionally assigns flags to the + * property. If the property already exists, its flags will be updated before the value is + * set. + * + * @param key property identifier + * @param value value to be set + * @param propertyFlags flags to be set + * @see #put(DynamicObject, Object, Object) + */ + public final void putWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + executeImpl(receiver, key, value, DEFAULT | UPDATE_FLAGS, propertyFlags); + } + + /** + * Like {@link #putIfPresent(DynamicObject, Object, Object)} but also sets property flags. + */ + public final boolean putWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, IF_PRESENT | UPDATE_FLAGS, propertyFlags); + } + + /** + * Like {@link #putIfAbsent(DynamicObject, Object, Object)} but also sets property flags. + */ + public final boolean putWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, IF_ABSENT | UPDATE_FLAGS, propertyFlags); + } + + // @formatter:off + /** + * Adds a property with a constant value or replaces an existing one. If the property already + * exists, its flags will be updated. + * + * The constant value is stored in the shape rather than the object instance and a new shape + * will be allocated if it does not already exist. + * + * A typical use case for this method is setting the initial default value of a declared, but + * yet uninitialized, property. This defers storage allocation and type speculation until the + * first actual value is set. + * + *

+ * Warning: this method will lead to a shape transition every time a new value is set and should + * be used sparingly (with at most one constant value per property) since it could cause an + * excessive amount of shapes to be created. + *

+ * Note: the value is strongly referenced from the shape property map. It should ideally be a + * value type or light-weight object without any references to guest language objects in order + * to prevent potential memory leaks from holding onto the Shape in inline caches. The Shape + * transition itself is weak, so the previous shapes will not hold strongly on the value. + * + *

Usage example:

+ * + * {@snippet : + * // declare property + * objLib.putConstant(receiver, key, NULL_VALUE, 0); + * + * // initialize property + * objLib.put(receiver, key, value); + * } + * + * @param key property identifier + * @param value the constant value to be set + * @param propertyFlags property flags or 0 + * @see #put(DynamicObject, Object, Object) + */ + // @formatter:on + public void putConstantWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + executeImpl(receiver, key, value, DEFAULT | CONST_VALUE | UPDATE_FLAGS, propertyFlags); + } + + /** + * Like {@link #putConstantWithFlags(DynamicObject, Object, Object, int)} but only if the + * property is present. + */ + public final boolean putConstantWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, IF_PRESENT | CONST_VALUE | UPDATE_FLAGS, propertyFlags); + } + + /** + * Like {@link #putConstantWithFlags(DynamicObject, Object, Object, int)} but only if the + * property is absent. + */ + public final boolean putConstantWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, IF_ABSENT | CONST_VALUE | UPDATE_FLAGS, propertyFlags); + } + + // private + + static final int DEFAULT = Flags.DEFAULT; + static final int IF_PRESENT = Flags.IF_PRESENT; + static final int IF_ABSENT = Flags.IF_ABSENT; + static final int UPDATE_FLAGS = Flags.UPDATE_FLAGS; + static final int CONST_VALUE = Flags.CONST; + + abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags); + + @SuppressWarnings("unused") + @Specialization(guards = { + "guard", + "key == cachedKey", + "mode == cachedMode", + "propertyFlags == cachedPropertyFlags", + "newLocation == null || canStore(newLocation, value)", + }, assumptions = "newShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Bind("shape == oldShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("mode") int cachedMode, + @Cached("propertyFlags") int cachedPropertyFlags, + @Cached("oldShape.getProperty(key)") Property oldProperty, + @Cached("getNewShapeAndCheckOldShapeStillValid(key, value, cachedPropertyFlags, cachedMode, oldProperty, oldShape)") Shape newShape, + @Cached("getNewLocation(oldShape, newShape, key, oldProperty)") Location newLocation, + @Cached("newShape.getValidAssumption()") Assumption newShapeValidAssumption) { + // We use mode instead of cachedMode to fold it during host inlining + CompilerAsserts.partialEvaluationConstant(mode); + if (newLocation == null) { + assert (mode & IF_PRESENT) != 0; + return false; + } + if ((mode & IF_ABSENT) != 0) { + return true; + } else { + boolean addingNewProperty = newShape != oldShape; + if (addingNewProperty) { + DynamicObjectSupport.grow(receiver, oldShape, newShape); + } + + if (newLocation instanceof ExtLocations.ObjectArrayLocation objectArrayLocation) { + objectArrayLocation.set(receiver, value, guard, addingNewProperty); + } else { + newLocation.set(receiver, value, guard, addingNewProperty); + } + + if (addingNewProperty) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + } + return true; + } + } + + /* + * This specialization is necessary because we don't want to remove doCached specialization + * instances with valid shapes. Yet we have to handle obsolete shapes, and we prefer to do + * that here than inside the doCached method. This also means new shapes being seen can + * still create new doCached instances, which is important once we see objects with the new + * non-obsolete shape. + */ + @Specialization(guards = "!receiver.getShape().isValid()") + static boolean doInvalid(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + @Specialization(replaces = {"doCached", "doInvalid"}) + static boolean doUncached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + static boolean canStore(Location newLocation, Object value) { + return newLocation instanceof ExtLocations.AbstractObjectLocation || newLocation.canStore(value); + } + + static Shape getNewShape(Object cachedKey, Object value, int newPropertyFlags, int putFlags, Property existingProperty, Shape oldShape) { + if (existingProperty == null) { + if ((putFlags & IF_PRESENT) != 0) { + return oldShape; + } else { + return oldShape.defineProperty(cachedKey, value, newPropertyFlags, putFlags); + } + } else if ((putFlags & IF_ABSENT) != 0) { + return oldShape; + } else if ((putFlags & UPDATE_FLAGS) != 0 && newPropertyFlags != existingProperty.getFlags()) { + return oldShape.defineProperty(cachedKey, value, newPropertyFlags, putFlags); + } + if (existingProperty.getLocation().canStore(value)) { + // set existing + return oldShape; + } else { + // generalize + Shape newShape = oldShape.defineProperty(oldShape, value, existingProperty.getFlags(), putFlags, existingProperty); + assert newShape != oldShape; + return newShape; + } + } + + // defineProperty() might obsolete the oldShape and we don't handle invalid shape -> valid + // shape transitions on the fast path (in doCached) + static Shape getNewShapeAndCheckOldShapeStillValid(Object cachedKey, Object value, int newPropertyFlags, int putFlags, Property existingProperty, Shape oldShape) { + Shape newShape = getNewShape(cachedKey, value, newPropertyFlags, putFlags, existingProperty, oldShape); + if (!oldShape.isValid()) { + return oldShape; // return an invalid shape to not use this specialization + } + return newShape; + } + + static Location getNewLocation(Shape oldShape, Shape newShape, Object cachedKey, Property oldProperty) { + if (newShape == oldShape) { + return oldProperty == null ? null : oldProperty.getLocation(); + } else { + return newShape.getLocation(cachedKey); + } + } + + } + + /** + * Copies all properties of a DynamicObject to another, preserving property flags. Does not copy + * hidden properties. + * + * @see #execute(DynamicObject, DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class CopyPropertiesNode extends Node { + + CopyPropertiesNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static CopyPropertiesNode create() { + return DynamicObjectFactory.CopyPropertiesNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static CopyPropertiesNode getUncached() { + return DynamicObjectFactory.CopyPropertiesNodeGen.getUncached(); + } + + /** + * Copies all properties of a DynamicObject to another, preserving property flags. Does not + * copy hidden properties. + * + * @since 25.1 + */ + public abstract void execute(DynamicObject from, DynamicObject to); + + @ExplodeLoop + @Specialization(guards = "from.getShape() == cachedShape", limit = "SHAPE_CACHE_LIMIT") + static void doCached(DynamicObject from, DynamicObject to, + @Cached("shape") @SuppressWarnings("unused") Shape cachedShape, + @Cached(value = "createPropertyGetters(cachedShape)", dimensions = 1) PropertyGetter[] getters, + @Cached DynamicObject.PutNode putNode) { + for (int i = 0; i < getters.length; i++) { + PropertyGetter getter = getters[i]; + putNode.putWithFlags(to, getter.getKey(), getter.get(from), getter.getFlags()); + } + } + + @TruffleBoundary + @Specialization(replaces = "doCached") + static void doUncached(DynamicObject from, DynamicObject to) { + Property[] properties = from.getShape().getPropertyArray(); + for (int i = 0; i < properties.length; i++) { + Property property = properties[i]; + PutNode.getUncached().putWithFlags(to, property.getKey(), property.get(from, false), property.getFlags()); + } + } + + static PropertyGetter[] createPropertyGetters(Shape shape) { + Property[] properties = shape.getPropertyArray(); + PropertyGetter[] getters = new PropertyGetter[properties.length]; + int i = 0; + for (Property property : properties) { + getters[i] = shape.makePropertyGetter(property.getKey()); + i++; + } + return getters; + } + + } + + @TruffleBoundary + static boolean updateShape(DynamicObject object) { + return ObsolescenceStrategy.updateShape(object); + } + + /** + * Checks if this object contains a property with the given key. + * + * @see #execute(DynamicObject, Object) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class ContainsKeyNode extends Node { + + ContainsKeyNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ContainsKeyNode create() { + return DynamicObjectFactory.ContainsKeyNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ContainsKeyNode getUncached() { + return DynamicObjectFactory.ContainsKeyNodeGen.getUncached(); + } + + // @formatter:off + /** + * Returns {@code true} if this object contains a property with the given key. + * + * {@snippet : + * @ExportMessage + * boolean isMemberReadable(String name, + * @Cached DynamicObject.ContainsKeyNode containsKeyNode) { + * return containsKeyNode.execute(this, name); + * } + * } + * + * @param key the property key + * @return {@code true} if the object contains a property with this key, else {@code false} + */ + // @formatter:on + public abstract boolean execute(DynamicObject receiver, Object key); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("key") Object cachedKey, + @Cached("cachedShape.hasProperty(cachedKey)") boolean cachedResult) { + return cachedResult; + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, Object key) { + Shape shape = receiver.getShape(); + return shape.hasProperty(key); + } + + } + + /** + * Removes the property with the given key from the object. + * + * @see #execute(DynamicObject, Object) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class RemoveKeyNode extends Node { + + RemoveKeyNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static RemoveKeyNode create() { + return DynamicObjectFactory.RemoveKeyNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static RemoveKeyNode getUncached() { + return DynamicObjectFactory.RemoveKeyNodeGen.getUncached(); + } + + /** + * Removes the property with the given key from the object. + * + * @param key the property key + * @return {@code true} if the property was removed or {@code false} if property was not + * found + */ + public abstract boolean execute(DynamicObject receiver, Object key); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == oldShape", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Cached("key") Object cachedKey, + @Cached("oldShape.getProperty(cachedKey)") Property cachedProperty, + @Cached("removeProperty(oldShape, cachedProperty)") Shape newShape, + @Cached("makeRemovePlanOrNull(oldShape, newShape, cachedProperty)") RemovePlan removePlan) { + if (cachedProperty == null) { + // nothing to do + return false; + } + if (oldShape.isValid()) { + if (newShape != oldShape) { + if (!oldShape.isShared()) { + removePlan.execute(receiver); + maybeUpdateShape(receiver, newShape); + } else { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + } + } + } else { + removePropertyUncached(receiver, oldShape, cachedProperty); + } + return true; + } + + @TruffleBoundary + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, Object key) { + Shape oldShape = receiver.getShape(); + Property existingProperty = oldShape.getProperty(key); + if (existingProperty == null) { + return false; + } + removePropertyUncached(receiver, oldShape, existingProperty); + return true; + } + + @TruffleBoundary + static boolean removePropertyUncached(DynamicObject receiver, Shape cachedShape, Property cachedProperty) { + updateShape(receiver); + Shape oldShape = receiver.getShape(); + Property existingProperty = reusePropertyLookup(cachedShape, cachedProperty, oldShape); + + Map archive = null; + assert (archive = DynamicObjectSupport.archive(receiver)) != null; + + Shape newShape = oldShape.removeProperty(existingProperty); + assert oldShape != newShape; + assert receiver.getShape() == oldShape; + + if (!oldShape.isShared()) { + RemovePlan plan = DynamicObjectLibraryImpl.prepareRemove(oldShape, newShape, existingProperty); + plan.execute(receiver); + } else { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + } + + assert DynamicObjectSupport.verifyValues(receiver, archive); + maybeUpdateShape(receiver, newShape); + return true; + } + + static Shape removeProperty(Shape shape, Property cachedProperty) { + CompilerAsserts.neverPartOfCompilation(); + if (cachedProperty == null) { + return shape; + } + return shape.removeProperty(cachedProperty); + } + + static RemovePlan makeRemovePlanOrNull(Shape oldShape, Shape newShape, Property removedProperty) { + if (oldShape.isShared() || oldShape == newShape) { + return null; + } else { + return DynamicObjectLibraryImpl.prepareRemove(oldShape, newShape, removedProperty); + } + } + + } + + /** + * Gets the language-specific object shape flags. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class GetShapeFlagsNode extends Node { + + GetShapeFlagsNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetShapeFlagsNode create() { + return DynamicObjectFactory.GetShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetShapeFlagsNode getUncached() { + return DynamicObjectFactory.GetShapeFlagsNodeGen.getUncached(); + } + + // @formatter:off + /** + * Gets the language-specific object shape flags previously set using + * {@link SetShapeFlagsNode} or + * {@link Shape.Builder#shapeFlags(int)}. If no shape flags were explicitly set, the default of + * 0 is returned. + * + * These flags may be used to tag objects that possess characteristics that need to be queried + * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. + * + *

Usage example:

+ * + * {@snippet : + * @ExportMessage + * Object writeMember(String member, Object value, + * @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + * @Cached DynamicObject.PutNode putNode) + * throws UnsupportedMessageException { + * if ((getShapeFlagsNode.execute(receiver) & FROZEN) != 0) { + * throw UnsupportedMessageException.create(); + * } + * putNode.put(this, member, value); + * } + * } + * + * Note that {@link HasShapeFlagsNode} is more convenient for that particular pattern. + * + * @return shape flags + * @see HasShapeFlagsNode + * @see SetShapeFlagsNode + * @see Shape.Builder#shapeFlags(int) + * @see Shape#getFlags() + */ + // @formatter:on + public abstract int execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "shape == cachedShape", limit = "1") + static int doCached(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape) { + return cachedShape.getFlags(); + } + + @Specialization(replaces = "doCached") + static int doUncached(DynamicObject receiver) { + return receiver.getShape().getFlags(); + } + + } + + /** + * Checks if the language-specific object shape flags include the given flags. + * + * @see #execute(DynamicObject, int) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class HasShapeFlagsNode extends Node { + + HasShapeFlagsNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static HasShapeFlagsNode create() { + return DynamicObjectFactory.HasShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static HasShapeFlagsNode getUncached() { + return DynamicObjectFactory.HasShapeFlagsNodeGen.getUncached(); + } + + // @formatter:off + /** + * Checks if the language-specific object shape flags contains the given flags, previously set using + * {@link SetShapeFlagsNode} or + * {@link Shape.Builder#shapeFlags(int)}. If no shape flags were explicitly set, the default of + * false is returned. + * + * These flags may be used to tag objects that possess characteristics that need to be queried + * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. + * + *

Usage example:

+ * + * {@snippet : + * @ExportMessage + * Object writeMember(String member, Object value, + * @Cached DynamicObject.HasShapeFlagsNode hasShapeFlagsNode, + * @Cached DynamicObject.PutNode putNode) + * throws UnsupportedMessageException { + * if (hasShapeFlagsNode.execute(receiver, FROZEN)) { + * throw UnsupportedMessageException.create(); + * } + * putNode.put(this, member, value); + * } + * } + * + * @return whether the shape flags contain (all of) the given flags + * @see GetShapeFlagsNode + * @see SetShapeFlagsNode + * @see Shape.Builder#shapeFlags(int) + * @see Shape#getFlags() + */ + // @formatter:on + public abstract boolean execute(DynamicObject receiver, int flags); + + @SuppressWarnings("unused") + @Specialization(guards = "shape == cachedShape", limit = "1") + static boolean doCached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape) { + return (cachedShape.getFlags() & flags) == flags; + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, int flags) { + return (receiver.getShape().getFlags() & flags) == flags; + } + + } + + /** + * Sets language-specific object shape flags. + * + * @see #execute(DynamicObject, int) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class SetShapeFlagsNode extends Node { + + SetShapeFlagsNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetShapeFlagsNode create() { + return DynamicObjectFactory.SetShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetShapeFlagsNode getUncached() { + return DynamicObjectFactory.SetShapeFlagsNodeGen.getUncached(); + } + + // @formatter:off + /** + * Sets language-specific object shape flags, changing the object's shape if need be. + * + * These flags may be used to tag objects that possess characteristics that need to be queried + * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. + * + * Only the lowest 16 bits (i.e. values in the range 0 to 65535) are allowed, the remaining bits + * are currently reserved. + * + *

Usage example:

+ * + * {@snippet : + * @Specialization + * static void preventExtensions(DynamicObject receiver, + * @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + * @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { + * setShapeFlagsNode.execute(receiver, getShapeFlagsNode.execute(receiver) | FROZEN); + * } + * } + * + * Note that {@link AddShapeFlagsNode} is more efficient and convenient for that particular pattern. + * + * @param newFlags the flags to set; must be in the range from 0 to 255 (inclusive). + * @return {@code true} if the object's shape changed, {@code false} if no change was made. + * @throws IllegalArgumentException if the flags are not in the allowed range. + * @see GetShapeFlagsNode + * @see AddShapeFlagsNode + * @see Shape.Builder#shapeFlags(int) + */ + // @formatter:on + public abstract boolean execute(DynamicObject receiver, int newFlags); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "flags == newFlags"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("flags") int newFlags, + @Cached("shapeSetFlags(cachedShape, newFlags)") Shape newShape) { + if (newShape != cachedShape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape) { + Shape newShape = shapeSetFlags(shape, flags); + if (newShape != shape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + static Shape shapeSetFlags(Shape shape, int newFlags) { + return shape.setFlags(newFlags); + } + + } + + /** + * Adds language-specific object shape flags. + * + * @see #execute(DynamicObject, int) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class AddShapeFlagsNode extends Node { + + AddShapeFlagsNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static AddShapeFlagsNode create() { + return DynamicObjectFactory.AddShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static AddShapeFlagsNode getUncached() { + return DynamicObjectFactory.AddShapeFlagsNodeGen.getUncached(); + } + + // @formatter:off + /** + * Adds language-specific object shape flags, changing the object's shape if need be. + * + * These flags may be used to tag objects that possess characteristics that need to be queried + * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. + *

+ * Only the lowest 16 bits (i.e. values in the range 0 to 65535) are allowed, the remaining bits + * are currently reserved. + *

+ * Equivalent to: + * {@snippet : + * @Specialization + * static void addFlags(DynamicObject receiver, int newFlags, + * @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + * @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { + * setShapeFlagsNode.execute(receiver, getShapeFlagsNode.execute(receiver) | newFlags); + * } + * } + * + *

Usage example:

+ * + * {@snippet : + * @Specialization + * static void preventExtensions(DynamicObject receiver, + * @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + * @Cached DynamicObject.AddShapeFlagsNode addShapeFlagsNode) { + * addShapeFlagsNode.execute(receiver, FROZEN); + * } + * } + * + * @param newFlags the flags to set; must be in the range from 0 to 255 (inclusive). + * @return {@code true} if the object's shape changed, {@code false} if no change was made. + * @throws IllegalArgumentException if the flags are not in the allowed range. + * @see GetShapeFlagsNode + * @see Shape.Builder#shapeFlags(int) + */ + // @formatter:on + public abstract boolean execute(DynamicObject receiver, int newFlags); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "flags == newFlags"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("flags") int newFlags, + @Cached("shapeAddFlags(cachedShape, newFlags)") Shape newShape) { + if (newShape != cachedShape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape) { + Shape newShape = shapeAddFlags(shape, flags); + if (newShape != shape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + static Shape shapeAddFlags(Shape shape, int newFlags) { + return shape.setFlags(shape.getFlags() | newFlags); + } + + } + + /** + * Checks whether this object is marked as shared. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class IsSharedNode extends Node { + + IsSharedNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static IsSharedNode create() { + return DynamicObjectFactory.IsSharedNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static IsSharedNode getUncached() { + return DynamicObjectFactory.IsSharedNodeGen.getUncached(); + } + + /** + * Checks whether this object is marked as shared. + * + * @return {@code true} if the object is shared + * @see MarkSharedNode + * @see Shape#isShared() + */ + public abstract boolean execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "shape == cachedShape", limit = "1") + static boolean doCached(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape) { + return cachedShape.isShared(); + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver) { + return receiver.getShape().isShared(); + } + + } + + /** + * Marks this object as shared. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class MarkSharedNode extends Node { + + MarkSharedNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static MarkSharedNode create() { + return DynamicObjectFactory.MarkSharedNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static MarkSharedNode getUncached() { + return DynamicObjectFactory.MarkSharedNodeGen.getUncached(); + } + + /** + * Marks this object as shared. + *

+ * Makes the object use a shared variant of the {@link Shape}, to allow safe usage of this + * object between threads. Objects with a shared {@link Shape} will not reuse storage + * locations for other fields. In combination with careful synchronization on writes, this + * can prevent reading out-of-thin-air values. + * + * @throws UnsupportedOperationException if the object is already {@link IsSharedNode + * shared}. + * @see IsSharedNode + */ + public abstract boolean execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "shape == cachedShape", limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("cachedShape.makeSharedShape()") Shape newShape) { + if (newShape != cachedShape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape) { + Shape newShape = shape.makeSharedShape(); + if (newShape != shape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + } + + /** + * Gets the language-specific dynamic type identifier currently associated with this object. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class GetDynamicTypeNode extends Node { + + GetDynamicTypeNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetDynamicTypeNode create() { + return DynamicObjectFactory.GetDynamicTypeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetDynamicTypeNode getUncached() { + return DynamicObjectFactory.GetDynamicTypeNodeGen.getUncached(); + } + + /** + * Gets the dynamic type identifier currently associated with this object. What this type + * represents is completely up to the language. For example, it could be a guest-language + * class. + * + * @return the object type + * @see DynamicObject.SetDynamicTypeNode + */ + public abstract Object execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "shape == cachedShape", limit = "1") + static Object doCached(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape) { + return cachedShape.getDynamicType(); + } + + @Specialization(replaces = "doCached") + static Object doUncached(DynamicObject receiver) { + return receiver.getShape().getDynamicType(); + } + + } + + /** + * Sets the language-specific dynamic type identifier. + * + * @see #execute(DynamicObject, Object) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class SetDynamicTypeNode extends Node { + + SetDynamicTypeNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetDynamicTypeNode create() { + return DynamicObjectFactory.SetDynamicTypeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetDynamicTypeNode getUncached() { + return DynamicObjectFactory.SetDynamicTypeNodeGen.getUncached(); + } + + /** + * Sets the object's dynamic type identifier. What this type represents is completely up to + * the language. For example, it could be a guest-language class. + * + * The type object is strongly referenced from the shape. It should ideally be a singleton + * or light-weight object without any references to guest language objects in order to keep + * the memory footprint low and prevent potential memory leaks from holding onto the Shape + * in inline caches. The Shape transition itself is weak, so the previous shapes will not + * hold strongly on the type object. + * + * Type objects are always compared by object identity, never {@code equals}. + * + * @param type a non-null type identifier defined by the guest language. + * @return {@code true} if the type (and the object's shape) changed + * @see DynamicObject.GetDynamicTypeNode + */ + public abstract boolean execute(DynamicObject receiver, Object type); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "objectType == newObjectType"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object objectType, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("objectType") Object newObjectType, + @Cached("cachedShape.setDynamicType(newObjectType)") Shape newShape) { + if (newShape != cachedShape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, Object objectType, + @Bind("receiver.getShape()") Shape shape) { + Shape newShape = shape.setDynamicType(objectType); + if (newShape != shape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + } + + /** + * Gets the property flags associated with the requested property key. + * + * @see #execute(DynamicObject, Object, int) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class GetPropertyFlagsNode extends Node { + + GetPropertyFlagsNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyFlagsNode create() { + return DynamicObjectFactory.GetPropertyFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyFlagsNode getUncached() { + return DynamicObjectFactory.GetPropertyFlagsNodeGen.getUncached(); + } + + // @formatter:off + /** + * Gets the property flags associated with the requested property key. Returns the + * {@code defaultValue} if the object contains no such property. If the property exists but no + * flags were explicitly set, returns the default of 0. + * + *

+ * Convenience method equivalent to: + * + * {@snippet : + * @Specialization + * int getPropertyFlags(@Cached GetPropertyNode getPropertyNode) { + * Property property = getPropertyNode.execute(object, key); + * return property != null ? property.getFlags() : defaultValue; + * } + * } + * + * @param key the property key + * @param defaultValue value to return if no such property exists + * @return the property flags if the property exists, else {@code defaultValue} + * @see GetPropertyNode + */ + // @formatter:on + public abstract int execute(DynamicObject receiver, Object key, int defaultValue); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT") + static int doCached(DynamicObject receiver, Object key, int defaultValue, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("key") Object cachedKey, + @Cached("cachedShape.getProperty(cachedKey)") Property cachedProperty) { + return cachedProperty != null ? cachedProperty.getFlags() : defaultValue; + } + + @Specialization(replaces = "doCached") + static int doUncached(DynamicObject receiver, Object key, int defaultValue) { + Property property = receiver.getShape().getProperty(key); + return property != null ? property.getFlags() : defaultValue; + } + + } + + /** + * Sets the property flags associated with the requested property key. + * + * @see #execute(DynamicObject, Object, int) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class SetPropertyFlagsNode extends Node { + + SetPropertyFlagsNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetPropertyFlagsNode create() { + return DynamicObjectFactory.SetPropertyFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetPropertyFlagsNode getUncached() { + return DynamicObjectFactory.SetPropertyFlagsNodeGen.getUncached(); + } + + /** + * Sets the property flags associated with the requested property. + * + * @param key the property key + * @return {@code true} if the property was found and its flags were changed, else + * {@code false} + */ + public abstract boolean execute(DynamicObject receiver, Object key, int propertyFlags); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == oldShape", "key == cachedKey", "propertyFlags == cachedPropertyFlags"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, int propertyFlags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Cached("key") Object cachedKey, + @Cached("propertyFlags") int cachedPropertyFlags, + @Cached("oldShape.getProperty(cachedKey)") Property cachedProperty, + @Cached("setPropertyFlags(oldShape, cachedProperty, cachedPropertyFlags)") Shape newShape) { + if (cachedProperty == null) { + return false; + } + if (cachedProperty.getFlags() != cachedPropertyFlags) { + if (oldShape.isValid()) { + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + maybeUpdateShape(receiver, newShape); + } + } else { + changePropertyFlagsUncached(receiver, oldShape, cachedProperty, cachedPropertyFlags); + } + } + return true; + } + + @TruffleBoundary + @Specialization(replaces = "doCached") + static boolean doSetPropertyFlags(DynamicObject receiver, Object key, int propertyFlags) { + Shape oldShape = receiver.getShape(); + Property existingProperty = oldShape.getProperty(key); + if (existingProperty == null) { + return false; + } + if (existingProperty.getFlags() != propertyFlags) { + changePropertyFlagsUncached(receiver, oldShape, existingProperty, propertyFlags); + } + return true; + } + + @TruffleBoundary + private static void changePropertyFlagsUncached(DynamicObject receiver, Shape cachedShape, Property cachedProperty, int propertyFlags) { + assert cachedProperty != null; + assert cachedProperty.getFlags() != propertyFlags; + + updateShape(receiver); + + Shape oldShape = receiver.getShape(); + final Property existingProperty = reusePropertyLookup(cachedShape, cachedProperty, oldShape); + Shape newShape = oldShape.setPropertyFlags(existingProperty, propertyFlags); + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + updateShape(receiver); + } + } + + static Shape setPropertyFlags(Shape shape, Property cachedProperty, int propertyFlags) { + CompilerAsserts.neverPartOfCompilation(); + if (cachedProperty == null) { + return shape; + } + return shape.setPropertyFlags(cachedProperty, propertyFlags); + } + + } + + static Property reusePropertyLookup(Shape cachedShape, Property cachedProperty, Shape updatedShape) { + if (updatedShape == cachedShape) { + return cachedProperty; + } else { + return updatedShape.getProperty(cachedProperty.getKey()); + } + } + + static void maybeUpdateShape(DynamicObject store, Shape newShape) { + CompilerAsserts.partialEvaluationConstant(newShape); + if (!newShape.isValid()) { + updateShape(store); + } + } + + /** + * Ensures the object's shape is up-to-date. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class UpdateShapeNode extends Node { + + UpdateShapeNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static UpdateShapeNode create() { + return DynamicObjectFactory.UpdateShapeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static UpdateShapeNode getUncached() { + return DynamicObjectFactory.UpdateShapeNodeGen.getUncached(); + } + + /** + * Ensures the object's shape is up-to-date. If the object's shape has been marked as + * {@link Shape#isValid() invalid}, this method will attempt to bring the object into a + * valid shape again. If the object's shape is already {@link Shape#isValid() valid}, this + * method will have no effect. + *

+ * This method does not need to be called normally; all the messages in this library will + * work on invalid shapes as well, but it can be useful in some cases to avoid such shapes + * being cached which can cause unnecessary cache polymorphism and invalidations. + * + * @return {@code true} if the object's shape was changed, otherwise {@code false}. + */ + public abstract boolean execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "cachedShape.isValid()"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCachedValid(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape) { + return false; + } + + @SuppressWarnings("unused") + @Specialization(guards = "shape.isValid()", replaces = "doCachedValid") + static boolean doUncachedValid(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape) { + return false; + } + + @Fallback + static boolean doInvalid(DynamicObject receiver) { + return updateShape(receiver); + } + + } + + /** + * Empties and resets the object to the given root shape. + * + * @see #execute(DynamicObject, Shape) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class ResetShapeNode extends Node { + + ResetShapeNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ResetShapeNode create() { + return DynamicObjectFactory.ResetShapeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ResetShapeNode getUncached() { + return DynamicObjectFactory.ResetShapeNodeGen.getUncached(); + } + + /** + * Empties and resets the object to the given root shape, which must not contain any + * instance properties (but may contain properties with a constant value). + * + * @param newShape the desired shape + * @return {@code true} if the object's shape was changed + * @throws IllegalArgumentException if the shape contains instance properties + */ + public abstract boolean execute(DynamicObject receiver, Shape newShape); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "otherShape == cachedOtherShape"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Shape otherShape, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("verifyResetShape(cachedShape, otherShape)") Shape cachedOtherShape) { + return doUncached(receiver, cachedOtherShape, cachedShape); + } + + @Specialization(replaces = "doCached") + static boolean doUncached(DynamicObject receiver, Shape otherShape, + @Bind("receiver.getShape()") Shape shape) { + if (shape == otherShape) { + return false; + } + DynamicObjectSupport.resize(receiver, shape, otherShape); + DynamicObjectSupport.setShapeWithStoreFence(receiver, otherShape); + return true; + } + + static Shape verifyResetShape(Shape currentShape, Shape otherShape) { + if (otherShape.hasInstanceProperties()) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw new IllegalArgumentException("Shape must not contain any instance properties."); + } + if (currentShape != otherShape) { + DynamicObjectSupport.invalidateAllPropertyAssumptions(currentShape); + } + return otherShape; + } + + } + + /** + * Gets a {@linkplain Property property descriptor} for the requested property key or + * {@code null} if no such property exists. + * + * @see #execute(DynamicObject, Object) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class GetPropertyNode extends Node { + + GetPropertyNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyNode create() { + return DynamicObjectFactory.GetPropertyNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyNode getUncached() { + return DynamicObjectFactory.GetPropertyNodeGen.getUncached(); + } + + /** + * Gets a {@linkplain Property property descriptor} for the requested property key. Returns + * {@code null} if the object contains no such property. + * + * @return {@link Property} if the property exists, else {@code null} + */ + public abstract Property execute(DynamicObject receiver, Object key); + + @Specialization(guards = {"receiver.getShape() == cachedShape", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT") + static Property doCached(@SuppressWarnings("unused") DynamicObject receiver, @SuppressWarnings("unused") Object key, + @Cached("shape") @SuppressWarnings("unused") Shape cachedShape, + @Cached("key") @SuppressWarnings("unused") Object cachedKey, + @Cached("cachedShape.getProperty(cachedKey)") Property cachedProperty) { + return cachedProperty; + } + + @Specialization(replaces = "doCached") + static Property doUncached(DynamicObject receiver, Object key) { + return receiver.getShape().getProperty(key); + } + + } + + /** + * Gets a snapshot of the object's property keys, in insertion order. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class GetKeyArrayNode extends Node { + + GetKeyArrayNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetKeyArrayNode create() { + return DynamicObjectFactory.GetKeyArrayNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetKeyArrayNode getUncached() { + return DynamicObjectFactory.GetKeyArrayNodeGen.getUncached(); + } + + // @formatter:off + /** + * Gets a snapshot of the object's property keys, in insertion order. The returned array may + * have been cached and must not be mutated. + * + * Properties with a {@link HiddenKey} are not included. + * + *

Usage example:

+ * + * The example below shows how the returned keys array could be translated to an interop array + * for use with InteropLibrary. + * + * {@snippet : + * @ExportMessage + * Object getMembers( + * @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode) { + * return new Keys(getKeyArrayNode.execute(this)); + * } + * + * @ExportLibrary(InteropLibrary.class) + * static final class Keys implements TruffleObject { + * + * @CompilationFinal(dimensions = 1) final Object[] keys; + * + * Keys(Object[] keys) { + * this.keys = keys; + * } + * + * @ExportMessage + * boolean hasArrayElements() { + * return true; + * } + * + * @ExportMessage + * Object readArrayElement(long index) throws InvalidArrayIndexException { + * if (!isArrayElementReadable(index)) { + * throw InvalidArrayIndexException.create(index); + * } + * return keys[(int) index]; + * } + * + * @ExportMessage + * long getArraySize() { + * return keys.length; + * } + * + * @ExportMessage + * boolean isArrayElementReadable(long index) { + * return index >= 0 && index < keys.length; + * } + * } + * } + * + * @return a read-only array of the object's property keys. + */ + // @formatter:on + public abstract Object[] execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "receiver.getShape() == cachedShape", limit = "SHAPE_CACHE_LIMIT") + static Object[] doCached(DynamicObject receiver, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached(value = "getKeyArray(cachedShape)", dimensions = 1) Object[] cachedKeyArray) { + return cachedKeyArray; + } + + @Specialization(replaces = "doCached") + static Object[] getKeyArray(DynamicObject receiver) { + return getKeyArray(receiver.getShape()); + } + + static Object[] getKeyArray(Shape shape) { + return shape.getKeyArray(); + } + + } + + /** + * Gets a snapshot of the object's {@linkplain Property properties}, in insertion order. + * + * @see #execute(DynamicObject) + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class GetPropertyArrayNode extends Node { + + GetPropertyArrayNode() { + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyArrayNode create() { + return DynamicObjectFactory.GetPropertyArrayNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyArrayNode getUncached() { + return DynamicObjectFactory.GetPropertyArrayNodeGen.getUncached(); + } + + /** + * Gets an array snapshot of the object's properties, in insertion order. The returned array + * may have been cached and must not be mutated. + * + * Properties with a {@link HiddenKey} are not included. + * + * Similar to {@link GetKeyArrayNode} but allows the properties' flags to be queried + * simultaneously which may be relevant for quick filtering. + * + * @return a read-only array of the object's properties. + * @see GetKeyArrayNode + */ + public abstract Property[] execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "receiver.getShape() == cachedShape", limit = "SHAPE_CACHE_LIMIT") + static Property[] doCached(DynamicObject receiver, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached(value = "cachedShape.getPropertyArray()", dimensions = 1) Property[] cachedPropertyArray) { + return cachedPropertyArray; + } + + @Specialization(replaces = "doCached") + static Property[] getPropertyArray(DynamicObject receiver) { + return receiver.getShape().getPropertyArray(); + } + + } + private static final Unsafe UNSAFE; private static final long SHAPE_OFFSET; static { diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibrary.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibrary.java index 0ced0f49d25e..791b9ec9a3e6 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibrary.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibrary.java @@ -156,6 +156,7 @@ public static DynamicObjectLibrary getUncached() { * @param key the property key * @param defaultValue value to be returned if the property does not exist * @return the property's value if it exists, else {@code defaultValue}. + * @see DynamicObject.GetNode#getOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract Object getOrDefault(DynamicObject object, Object key, Object defaultValue); @@ -169,6 +170,7 @@ public static DynamicObjectLibrary getUncached() { * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not an {@code int} * @see #getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#getIntOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -189,6 +191,7 @@ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not a {@code double} * @see #getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#getDoubleOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -209,6 +212,7 @@ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaul * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not a {@code long} * @see #getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#getLongOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -243,6 +247,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * @see #putLong(DynamicObject, Object, long) * @see #putIfPresent(DynamicObject, Object, Object) * @see #putWithFlags(DynamicObject, Object, Object, int) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void put(DynamicObject object, Object key, Object value); @@ -251,6 +256,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * Int-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public void putInt(DynamicObject object, Object key, int value) { @@ -261,6 +267,7 @@ public void putInt(DynamicObject object, Object key, int value) { * Double-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public void putDouble(DynamicObject object, Object key, double value) { @@ -271,6 +278,7 @@ public void putDouble(DynamicObject object, Object key, double value) { * Long-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public void putLong(DynamicObject object, Object key, long value) { @@ -284,6 +292,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param value value to be set * @return {@code true} if the property was present and the value set, otherwise {@code false} * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#putIfPresent(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract boolean putIfPresent(DynamicObject object, Object key, Object value); @@ -297,6 +306,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param flags flags to be set * @see #put(DynamicObject, Object, Object) * @see #setPropertyFlags(DynamicObject, Object, int) + * @see DynamicObject.PutNode#putWithFlags(DynamicObject, Object, Object, int) * @since 20.2.0 */ public abstract void putWithFlags(DynamicObject object, Object key, Object value, int flags); @@ -336,6 +346,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param value the constant value to be set * @param flags property flags or 0 * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#putConstant(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void putConstant(DynamicObject object, Object key, Object value, int flags); @@ -345,6 +356,7 @@ public void putLong(DynamicObject object, Object key, long value) { * * @param key the property key * @return {@code true} if the property was removed or {@code false} if property was not found + * @see DynamicObject.RemoveKeyNode * @since 20.2.0 */ public abstract boolean removeKey(DynamicObject object, Object key); @@ -365,6 +377,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @return {@code true} if the type (and the object's shape) changed * @since 20.2.0 * @see #getDynamicType(DynamicObject) + * @see DynamicObject.SetDynamicTypeNode */ public abstract boolean setDynamicType(DynamicObject object, Object type); @@ -376,6 +389,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @since 20.2.0 * @see #setDynamicType(DynamicObject, Object) * @see Shape#getDynamicType() + * @see DynamicObject.GetDynamicTypeNode */ public abstract Object getDynamicType(DynamicObject object); @@ -394,6 +408,7 @@ public void putLong(DynamicObject object, Object key, long value) { * * @param key the property key * @return {@code true} if the object contains a property with this key, else {@code false} + * @see DynamicObject.ContainsKeyNode * @since 20.2.0 */ public abstract boolean containsKey(DynamicObject object, Object key); @@ -425,6 +440,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @see #setShapeFlags(DynamicObject, int) * @see Shape.Builder#shapeFlags(int) * @see Shape#getFlags() + * @see DynamicObject.GetShapeFlagsNode * @since 20.2.0 */ public abstract int getShapeFlags(DynamicObject object); @@ -453,6 +469,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @throws IllegalArgumentException if the flags are not in the allowed range. * @see #getShapeFlags(DynamicObject) * @see Shape.Builder#shapeFlags(int) + * @see DynamicObject.SetShapeFlagsNode * @since 20.2.0 */ public abstract boolean setShapeFlags(DynamicObject object, int flags); @@ -462,6 +479,7 @@ public void putLong(DynamicObject object, Object key, long value) { * {@code null} if the object contains no such property. * * @return {@link Property} if the property exists, else {@code null} + * @see DynamicObject.GetPropertyNode * @since 20.2.0 */ public abstract Property getProperty(DynamicObject object, Object key); @@ -483,6 +501,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param defaultValue value to return if no such property exists * @return the property flags if the property exists, else {@code defaultValue} * @see #getProperty(DynamicObject, Object) + * @see DynamicObject.GetPropertyFlagsNode * @since 20.2.0 */ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int defaultValue) { @@ -495,6 +514,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * @param key the property key * @return {@code true} if the property was found and its flags were changed, else {@code false} + * @see DynamicObject.SetPropertyFlagsNode * @since 20.2.0 */ public abstract boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags); @@ -509,6 +529,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * @throws UnsupportedOperationException if the object is already {@linkplain #isShared shared}. * @see #isShared(DynamicObject) + * @see DynamicObject.MarkSharedNode * @since 20.2.0 */ public abstract void markShared(DynamicObject object); @@ -519,6 +540,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * @return {@code true} if the object is shared * @see #markShared(DynamicObject) * @see Shape#isShared() + * @see DynamicObject.IsSharedNode * @since 20.2.0 */ public abstract boolean isShared(DynamicObject object); @@ -534,6 +556,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * cached which can cause unnecessary cache polymorphism and invalidations. * * @return {@code true} if the object's shape was changed, otherwise {@code false}. + * @see DynamicObject.UpdateShapeNode * @since 20.2.0 */ public abstract boolean updateShape(DynamicObject object); @@ -545,6 +568,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * @param otherShape the desired shape * @return {@code true} if the object's shape was changed * @throws IllegalArgumentException if the shape contains instance properties + * @see DynamicObject.ResetShapeNode * @since 20.2.0 */ public abstract boolean resetShape(DynamicObject object, Shape otherShape); @@ -602,6 +626,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * * @return a read-only array of the object's property keys. + * @see DynamicObject.GetKeyArrayNode * @since 20.2.0 */ public abstract Object[] getKeyArray(DynamicObject object); @@ -617,6 +642,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * @return a read-only array of the object's properties. * @see #getKeyArray(DynamicObject) + * @see DynamicObject.GetPropertyArrayNode * @since 20.2.0 */ public abstract Property[] getPropertyArray(DynamicObject object); diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java index 30693fb5f3f6..541bfc63b91f 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java @@ -465,7 +465,7 @@ public int compareTo(Move other) { } } - private static final class RemovePlan { + static final class RemovePlan { private static final int MAX_UNROLL = 32; @CompilationFinal(dimensions = 1) private final Move[] moves; diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java index 015fab3020e4..e7f99d9dbc55 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java @@ -185,21 +185,23 @@ private static void trimPrimitiveStore(DynamicObject object, Shape thisShape, Sh } } + @SuppressWarnings("deprecation") static Map archive(DynamicObject object) { Map archive = new HashMap<>(); Property[] properties = (object.getShape()).getPropertyArray(); for (Property property : properties) { - archive.put(property.getKey(), DynamicObjectLibrary.getUncached().getOrDefault(object, property.getKey(), null)); + archive.put(property.getKey(), property.getLocation().get(object, false)); } return archive; } + @SuppressWarnings("deprecation") static boolean verifyValues(DynamicObject object, Map archive) { Property[] properties = (object.getShape()).getPropertyArray(); for (Property property : properties) { Object key = property.getKey(); Object before = archive.get(key); - Object after = DynamicObjectLibrary.getUncached().getOrDefault(object, key, null); + Object after = property.getLocation().get(object, false); assert Objects.equals(after, before) : "before != after for key: " + key; } return true; diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java index f8b7341b8d34..9a74974a2119 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java @@ -40,12 +40,12 @@ */ package com.oracle.truffle.api.object; +import java.util.Objects; + import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.nodes.UnexpectedResultException; -import java.util.Objects; - /** * Property location. * @@ -162,7 +162,7 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected * @throws FinalLocationException for effectively final fields * @since 0.8 or earlier */ - @SuppressWarnings("deprecation") + @SuppressWarnings("unused") @Deprecated(since = "22.2") public void set(DynamicObject store, Object value, Shape shape) throws IncompatibleLocationException, FinalLocationException { try { @@ -181,11 +181,12 @@ public void set(DynamicObject store, Object value, Shape shape) throws Incompati * @since 0.8 or earlier */ @Deprecated(since = "22.2") - @SuppressWarnings({"unused", "deprecation"}) + @SuppressWarnings("unused") public void set(DynamicObject store, Object value, Shape oldShape, Shape newShape) throws IncompatibleLocationException { if (canStore(value)) { + boolean guard = checkShape(store, oldShape); DynamicObjectSupport.grow(store, oldShape, newShape); - setSafe(store, value, false, true); + setSafe(store, value, guard, true); DynamicObjectSupport.setShapeWithStoreFence(store, newShape); } else { throw incompatibleLocation(); diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObsolescenceStrategy.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObsolescenceStrategy.java index e979a00d0cc0..517548663a18 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObsolescenceStrategy.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObsolescenceStrategy.java @@ -170,6 +170,13 @@ public void visitObjectArray(int index, int count) { return true; } + @TruffleBoundary + static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags) { + Shape shape = object.getShape(); + Property existingProperty = shape.getProperty(key); + return putGeneric(object, key, value, newPropertyFlags, putFlags, shape, existingProperty); + } + @TruffleBoundary static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags, Shape s, Property existingProperty) { if (existingProperty == null) { diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java index 1613891dc1ee..5aca1ab72ea0 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java @@ -62,7 +62,6 @@ import java.util.function.IntPredicate; import java.util.function.Predicate; -import com.oracle.truffle.api.impl.AbstractAssumption; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.collections.Pair; @@ -75,6 +74,7 @@ import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.dsl.Idempotent; import com.oracle.truffle.api.dsl.NonIdempotent; +import com.oracle.truffle.api.impl.AbstractAssumption; /** * A Shape is an immutable descriptor of the current object "shape" of a DynamicObject, i.e., object @@ -698,6 +698,12 @@ public Property getProperty(Object key) { return propertyMap.get(key); } + @TruffleBoundary + Location getLocation(Object key) { + Property property = propertyMap.get(key); + return property == null ? null : property.getLocation(); + } + /** * Add a new property in the map, yielding a new or cached Shape object. * @@ -735,6 +741,11 @@ protected Shape defineProperty(Object key, Object value, int propertyFlags, int return ObsolescenceStrategy.defineProperty(this, key, value, propertyFlags, putFlags); } + @TruffleBoundary + Shape defineProperty(Object key, Object value, int propertyFlags, int putFlags, Property existing) { + return ObsolescenceStrategy.defineProperty(this, key, value, propertyFlags, existing, putFlags); + } + /** * Add or replace shape-constant property. * @@ -928,6 +939,14 @@ protected Shape replaceProperty(Property oldProperty, Property newProperty) { return ObsolescenceStrategy.replaceProperty(this, oldProperty, newProperty); } + @TruffleBoundary + Shape setPropertyFlags(Property oldProperty, int newFlags) { + if (oldProperty.getFlags() == newFlags) { + return this; + } + return replaceProperty(oldProperty, oldProperty.copyWithFlags(newFlags)); + } + /** * Get the last property. * diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java index b2867b0563a3..bbd345c7c41b 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -83,7 +83,6 @@ import com.oracle.truffle.api.nodes.NodeUtil; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; @@ -187,8 +186,8 @@ *
  • Function calls: {@link SLInvokeNode invocations} are efficiently implemented with * {@link SLFunction polymorphic inline caches}. *
  • Object access: {@link SLReadPropertyNode} and {@link SLWritePropertyNode} use a cached - * {@link DynamicObjectLibrary} as the polymorphic inline cache for property reads and writes, - * respectively. + * {@link DynamicObject.GetNode} and {@link DynamicObject.PutNode} as the polymorphic inline cache + * for property reads and writes, respectively. * * *

    diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLReadPropertyNode.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLReadPropertyNode.java index 058f15ea2f8c..938faa02dfc8 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLReadPropertyNode.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLReadPropertyNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -52,7 +52,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; -import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.sl.SLException; import com.oracle.truffle.sl.nodes.SLExpressionNode; @@ -89,13 +89,13 @@ public static Object readArray(Object receiver, Object index, } } - @Specialization(limit = "LIBRARY_LIMIT") + @Specialization public static Object readSLObject(SLObject receiver, Object name, @Bind Node node, - @CachedLibrary("receiver") DynamicObjectLibrary objectLibrary, + @Cached DynamicObject.GetNode getNode, @Cached SLToTruffleStringNode toTruffleStringNode) { TruffleString nameTS = toTruffleStringNode.execute(node, name); - Object result = objectLibrary.getOrDefault(receiver, nameTS, null); + Object result = getNode.getOrNull(receiver, nameTS); if (result == null) { // read was not successful. In SL we only have basic support for errors. throw SLException.undefinedProperty(node, nameTS); diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java index 2ef03c8030d0..c907b929308b 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -53,7 +53,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; -import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.sl.SLException; import com.oracle.truffle.sl.nodes.SLExpressionNode; import com.oracle.truffle.sl.nodes.util.SLToMemberNode; @@ -93,12 +93,12 @@ public static Object writeArray(Object receiver, Object index, Object value, return value; } - @Specialization(limit = "LIBRARY_LIMIT") + @Specialization public static Object writeSLObject(SLObject receiver, Object name, Object value, @Bind Node node, - @CachedLibrary("receiver") DynamicObjectLibrary objectLibrary, + @Cached DynamicObject.PutNode putNode, @Cached SLToTruffleStringNode toTruffleStringNode) { - objectLibrary.put(receiver, toTruffleStringNode.execute(node, name), value); + putNode.put(receiver, toTruffleStringNode.execute(node, name), value); return value; } diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java index b28fb4057cad..e40ae60f3e82 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -54,7 +54,6 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.api.utilities.TriState; @@ -143,19 +142,17 @@ boolean hasMembers() { @ExportMessage void removeMember(String member, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) throws UnknownIdentifierException { + @Cached DynamicObject.RemoveKeyNode removeKeyNode) throws UnknownIdentifierException { TruffleString memberTS = fromJavaStringNode.execute(member, SLLanguage.STRING_ENCODING); - if (objectLibrary.containsKey(this, memberTS)) { - objectLibrary.removeKey(this, memberTS); - } else { + if (!removeKeyNode.execute(this, memberTS)) { throw UnknownIdentifierException.create(member); } } @ExportMessage Object getMembers(@SuppressWarnings("unused") boolean includeInternal, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - return new Keys(objectLibrary.getKeyArray(this)); + @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode) { + return new Keys(getKeyArrayNode.execute(this)); } @ExportMessage(name = "isMemberReadable") @@ -163,8 +160,8 @@ Object getMembers(@SuppressWarnings("unused") boolean includeInternal, @ExportMessage(name = "isMemberRemovable") boolean existsMember(String member, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - return objectLibrary.containsKey(this, fromJavaStringNode.execute(member, SLLanguage.STRING_ENCODING)); + @Cached @Shared DynamicObject.ContainsKeyNode containsKeyNode) { + return containsKeyNode.execute(this, fromJavaStringNode.execute(member, SLLanguage.STRING_ENCODING)); } @ExportMessage @@ -207,13 +204,13 @@ boolean isArrayElementReadable(long index) { } /** - * {@link DynamicObjectLibrary} provides the polymorphic inline cache for reading properties. + * {@link DynamicObject.GetNode} provides the polymorphic inline cache for reading properties. */ @ExportMessage Object readMember(String name, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) throws UnknownIdentifierException { - Object result = objectLibrary.getOrDefault(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), null); + @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { + Object result = getNode.getOrNull(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING)); if (result == null) { /* Property does not exist. */ throw UnknownIdentifierException.create(name); @@ -222,12 +219,12 @@ Object readMember(String name, } /** - * {@link DynamicObjectLibrary} provides the polymorphic inline cache for writing properties. + * {@link DynamicObject.PutNode} provides the polymorphic inline cache for writing properties. */ @ExportMessage void writeMember(String name, Object value, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - objectLibrary.put(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), value); + @Cached DynamicObject.PutNode putNode) throws UnknownIdentifierException { + putNode.put(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), value); } } diff --git a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java index f7e0d08099b6..db15a1c2e2fd 100644 --- a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java +++ b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -40,6 +40,7 @@ */ package org.graalvm.truffle.benchmark; +import java.lang.invoke.MethodHandles; import java.util.stream.IntStream; import org.graalvm.polyglot.Context; @@ -53,15 +54,19 @@ import org.openjdk.jmh.annotations.Warmup; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; @Warmup(iterations = 10, time = 1) -@SuppressWarnings("deprecation") public class DynamicObjectBenchmark extends TruffleBenchmark { static final String TEST_LANGUAGE = "benchmark-test-language"; private static final int PROPERTY_KEYS_PER_ITERATION = 1000; + static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + static final DynamicObject.GetNode GET_NODE = DynamicObject.GetNode.create(); + static final DynamicObject.PutNode PUT_NODE = DynamicObject.PutNode.create(); + static final String[] PROPERTY_KEYS = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new); + static final String SOME_KEY = PROPERTY_KEYS[10]; private static final class MyDynamicObject extends DynamicObject { private MyDynamicObject(Shape shape) { @@ -72,8 +77,7 @@ private MyDynamicObject(Shape shape) { @State(Scope.Benchmark) public static class SharedEngineState { final Engine engine = Engine.newBuilder().allowExperimentalOptions(true).option("engine.Compilation", "false").build(); - final Shape rootShape = Shape.newBuilder().build(); - final String[] propertyKeys = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new); + final Shape rootShape = Shape.newBuilder().layout(MyDynamicObject.class, LOOKUP).build(); final Shape[] expectedShapes = new Shape[PROPERTY_KEYS_PER_ITERATION]; @TearDown @@ -94,14 +98,22 @@ private void assertSameShape(int i, Shape actualShape) { @State(Scope.Thread) public static class PerThreadContextState { Context context; + DynamicObject object; @Setup public void setup(SharedEngineState shared) { context = Context.newBuilder(TEST_LANGUAGE).engine(shared.engine).build(); + context.enter(); + + object = new MyDynamicObject(shared.rootShape); + for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { + DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); + } } @TearDown public void tearDown() { + context.leave(); context.close(); } } @@ -112,13 +124,11 @@ public void tearDown() { @Benchmark @Threads(8) public void shapeTransitionMapContended(SharedEngineState shared, PerThreadContextState perThread) { - perThread.context.enter(); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { DynamicObject object = new MyDynamicObject(shared.rootShape); - DynamicObjectLibrary.getUncached().put(object, shared.propertyKeys[i], "testValue"); + DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } - perThread.context.leave(); } /** @@ -127,12 +137,24 @@ public void shapeTransitionMapContended(SharedEngineState shared, PerThreadConte @Benchmark @Threads(1) public void shapeTransitionMapUncontended(SharedEngineState shared, PerThreadContextState perThread) { - perThread.context.enter(); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { DynamicObject object = new MyDynamicObject(shared.rootShape); - DynamicObjectLibrary.getUncached().put(object, shared.propertyKeys[i], "testValue"); + DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } - perThread.context.leave(); + } + + @Benchmark + @Threads(1) + public Object get(SharedEngineState shared, PerThreadContextState perThread) { + DynamicObject object = perThread.object; + return GET_NODE.getOrDefault(object, SOME_KEY, null); + } + + @Benchmark + @Threads(1) + public void put(SharedEngineState shared, PerThreadContextState perThread) { + DynamicObject object = perThread.object; + PUT_NODE.put(object, SOME_KEY, "updated value"); } }