diff --git a/truffle/docs/DynamicObjectModel.md b/truffle/docs/DynamicObjectModel.md index 4bfcc97a9ef5..4adff14aaac1 100644 --- a/truffle/docs/DynamicObjectModel.md +++ b/truffle/docs/DynamicObjectModel.md @@ -6,8 +6,8 @@ permalink: /graalvm-as-a-platform/language-implementation-framework/DynamicObjec --- # Dynamic Object Model -This guide demonstrates how to get started with using the [DynamicObject](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObject.html) and [DynamicObjectLibrary](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObjectLibrary.html) APIs introduced with GraalVM 20.2.0. -The full documentation can be found in the [Javadoc](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObjectLibrary.html). +This guide demonstrates how to use the [`DynamicObject`](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObject.html) and node APIs introduced with GraalVM 25.1. +The full documentation can be found in the [Javadoc](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObject.html). ### Motivation @@ -61,7 +61,7 @@ public class Array extends BasicObject { } ``` -Dynamic object members can be accessed using the `DynamicObjectLibrary`, which can be obtained using the `@CachedLibrary` annotation of the Truffle DSL and `DynamicObjectLibrary.getFactory()` + `getUncached()`, `create(DynamicObject)`, and `createDispatched(int)`. +You can access dynamic object members through `DynamicObject` access nodes. To obtain these nodes, cache them using the `@Cached` annotation provided in the Truffle DSL. Here is an example of how it could be used to implement `InteropLibrary` messages: ```java @ExportLibrary(InteropLibrary.class) @@ -78,9 +78,9 @@ public class SimpleObject extends BasicObject { @ExportMessage Object readMember(String name, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) + @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { - Object result = objectLibrary.getOrDefault(this, name, null); + Object result = getNode.execute(this, name, null); if (result == null) { /* Property does not exist. */ throw UnknownIdentifierException.create(name); @@ -90,14 +90,14 @@ public class SimpleObject extends BasicObject { @ExportMessage void writeMember(String name, Object value, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - objectLibrary.put(this, name, value); + @Cached DynamicObject.PutNode putNode) { + putNode.execute(this, name, value); } @ExportMessage boolean isMemberReadable(String member, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - return objectLibrary.containsKey(this, member); + @Cached DynamicObject.ContainsKeyNode containsKeyNode) { + return containsKeyNode.execute(this, member); } // ... } @@ -106,7 +106,7 @@ public class SimpleObject extends BasicObject { In order to construct instances of these objects, you first need a `Shape` that you can pass to the `DynamicObject` constructor. This shape is created using [`Shape.newBuilder().build()`](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/Shape.Builder.html). The returned shape describes the initial shape of the object and forms the root of a new shape tree. -As you are adding new properties with `DynamicObjectLibrary#put`, the object will mutate into other shapes in this shape tree. +As you are adding new properties with `DynamicObject.PutNode#execute`, the object will mutate into other shapes in this shape tree. Note: You should reuse the same initial shapes because shapes are internally cached per root shape. It is recommended that you store the initial shapes in the `TruffleLanguage` instance, so they can be shared across contexts of the same engine. @@ -137,7 +137,7 @@ public final class MyLanguage extends TruffleLanguage { You can extend the default object layout with extra _dynamic fields_ that you hand over to the dynamic object model by adding `@DynamicField`-annotated field declarations of type `Object` or `long` in your subclasses, and specifying the _layout class_ with `Shape.newBuilder().layout(ExtendedObject.class).build();`. Dynamic fields declared in this class and its superclasses will then automatically be used to store dynamic object properties and allow faster access to properties that fit into this reserved space. -Note: You must not access dynamic fields directly. Always use `DynamicObjectLibrary` for this purpose. +Note: You must not access dynamic fields directly. Always use `DynamicObject` nodes for this purpose. ```java @ExportLibrary(InteropLibrary.class) @@ -158,21 +158,20 @@ public class ExtendedObject extends SimpleObject { ## Caching Considerations -In order to ensure optimal caching, avoid reusing the same cached `DynamicObjectLibrary` for multiple, independent operations (`get`, `put`, etc.). -Try to minimize the number of different shapes and property keys seen by each cached library instance. -When the property keys are known statically (compilation-final), always use a separate `DynamicObjectLibrary` for each property key. -Use dispatched libraries (`@CachedLibrary(limit=...)`) when putting multiple properties in succession. +To ensure optimal caching, avoid reusing the same cached `DynamicObject` node (`GetNode`, `PutNode`, etc.) for multiple independent operations. +Try to minimize the number of different shapes and property keys that each cached node instance encounters. +When the property keys are known statically (compilation-final), always use a separate `DynamicObject` node for each property key. For example: ```java public abstract class MakePairNode extends BinaryExpressionNode { @Specialization Object makePair(Object left, Object right, @CachedLanguage MyLanguage language, - @CachedLibrary(limit = "3") DynamicObjectLibrary putLeft, - @CachedLibrary(limit = "3") DynamicObjectLibrary putRight) { + @Cached DynamicObject.PutNode putLeft, + @Cached DynamicObject.PutNode putRight) { MyObject obj = language.createObject(); - putLeft.put(obj, "left", left); - putRight.put(obj, "right", right); + putLeft.execute(obj, "left", left); + putRight.execute(obj, "right", right); return obj; } } 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 4fdf12ccd2fc..f2665557f6a1 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 @@ -502,7 +502,7 @@ public void testPropertyFlags() { Assert.assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); Shape before = o1.getShape(); - assertTrue(lib.setPropertyFlags(o1, k1, f3)); + assertEquals(!(run == TestRun.CACHED_NODES || run == TestRun.UNCACHED_NODES), lib.setPropertyFlags(o1, k1, f3)); assertFalse(updatePropertyFlags(lib, o1, k1, f -> f | f2)); assertEquals(f3, lib.getPropertyFlagsOrDefault(o1, k1, -1)); Assert.assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); 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 index 7bc399599ce4..b4acb69b8665 100644 --- 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 @@ -43,10 +43,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; 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; @@ -133,6 +133,13 @@ private DynamicObject.PutConstantNode createPutConstantNode() { }; } + private DynamicObject.PutAllNode createPutAllNode() { + return switch (run) { + case CACHED -> DynamicObject.PutAllNode.create(); + case UNCACHED -> DynamicObject.PutAllNode.getUncached(); + }; + } + private DynamicObject.CopyPropertiesNode createCopyPropertiesNode() { return switch (run) { case CACHED -> DynamicObject.CopyPropertiesNode.create(); @@ -238,13 +245,6 @@ private DynamicObject.GetPropertyNode createGetPropertyNode() { }; } - private DynamicObject.GetPropertyNode createGetPropertyNodeForKey(@SuppressWarnings("unused") 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(); @@ -686,19 +686,19 @@ public void testHasAddShapeFlags() { assertTrue(hasShapeFlags(getShapeFlagsNode, o1, flags)); assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b10)); assertFalse(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); - addShapeFlags(getShapeFlagsNode, setShapeFlagsNode, o1, 0b1); + setShapeFlagsNode.executeAdd(o1, 0b1); assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); assertEquals(flags | 0b1, getShapeFlagsNode.execute(o1)); + setShapeFlagsNode.executeRemove(o1, 0b1); + assertEquals(flags, getShapeFlagsNode.execute(o1)); + setShapeFlagsNode.executeRemoveAndAdd(o1, 0b1111, 0b0101); + assertEquals(0b100101, getShapeFlagsNode.execute(o1)); } static boolean hasShapeFlags(DynamicObject.GetShapeFlagsNode getShapeFlagsNode, DynamicObject obj, int flags) { return (getShapeFlagsNode.execute(obj) & flags) == flags; } - static boolean addShapeFlags(DynamicObject.GetShapeFlagsNode getShapeFlagsNode, DynamicObject.SetShapeFlagsNode setShapeFlagsNode, DynamicObject obj, int flags) { - return setShapeFlagsNode.execute(obj, (getShapeFlagsNode.execute(obj) | flags)); - } - @Test public void testMakeShared() { String key = "key"; @@ -728,20 +728,21 @@ public void testPropertyFlags() { 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)); + assertTrue(setPropertyFlagsNode.executeAdd(o1, k1, f2)); assertEquals(f3, getPropertyFlagsNode.execute(o1, k1, -1)); + assertFalse(setPropertyFlagsNode.executeAdd(o1, k1, f2)); 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)); + assertFalse(setPropertyFlagsNode.execute(o1, k1, f3)); + assertFalse(setPropertyFlagsNode.executeAdd(o1, k1, f2)); + assertFalse(setPropertyFlagsNode.executeRemove(o1, k1, 0x100)); assertEquals(f3, getPropertyFlagsNode.execute(o1, k1, -1)); assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); assertSame(before, o1.getShape()); @@ -750,31 +751,21 @@ public void testPropertyFlags() { 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)); + assertTrue(setPropertyFlagsNode.executeAdd(o2, k1, f2)); assertEquals(f3, getPropertyFlagsNode.execute(o2, k1, -1)); assertSame(o1.getShape(), o2.getShape()); + assertTrue(setPropertyFlagsNode.executeRemove(o2, k1, f1)); + assertEquals(f2, getPropertyFlagsNode.execute(o2, k1, -1)); + assertTrue(setPropertyFlagsNode.executeRemoveAndAdd(o2, k1, f3, f1)); + assertEquals(f1, getPropertyFlagsNode.execute(o2, k1, -1)); + assertFalse(setPropertyFlagsNode.executeRemoveAndAdd(o2, k1, f1, f1)); + assertFalse(setPropertyFlagsNode.executeRemoveAndAdd(o2, k1, f3, f1)); + assertEquals(f1, getPropertyFlagsNode.execute(o2, k1, -1)); 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; @@ -827,10 +818,18 @@ public void testResetShape() { Shape emptyShape = o1.getShape(); uncachedPut(o1, "key1", v1, 0); uncachedPut(o1, "key2", v2, 0); + Shape nonEmptyShape = o1.getShape(); resetShapeNode.execute(o1, emptyShape); assertSame(emptyShape, o1.getShape()); - assumeTrue("new layout only", isNewLayout()); + try { + resetShapeNode.execute(o1, nonEmptyShape); + fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + assertSame(emptyShape, o1.getShape()); + } + int flags = 0xf; DynamicObject o2 = createEmpty(); Shape newEmptyShape = Shape.newBuilder().shapeFlags(flags).build(); @@ -971,9 +970,7 @@ public void testPutConstant2() { assertEquals(v1, uncachedGet(o1, k1)); putNode.executeWithFlags(o1, k1, v2, flags); - if (isNewLayout()) { - assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); - } + assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); assertEquals(v2, uncachedGet(o1, k1)); } @@ -1014,6 +1011,46 @@ public void testPropertyAndShapeFlags() { assertEquals(1, getNode.execute(o3, "k13", null)); } + @Test + public void testPutAll() { + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + DynamicObject o4 = createEmpty(); + String k1 = "key1"; + String k2 = "key2"; + String k3 = "key3"; + String k4 = "key4"; + int v1 = 42; + int v2 = 43; + String v3 = "asdf"; + String v4 = "qwer"; + String[] keys = {k1, k2, k3}; + Object[] values1 = {v1, v2, v3}; + Object[] values2 = {v1, v4, v3}; + uncachedPutAll(o1, keys, values1); + DynamicObject.PutAllNode putAllNode = createPutAllNode(); + putAllNode.execute(o2, keys, values1); + assertSame(o1.getShape(), o2.getShape()); + putAllNode.execute(o3, keys, values1); + putAllNode.execute(o3, keys, values2); + putAllNode.execute(o4, keys, values1); + String[] keys4 = {k4}; + DynamicObject.PutAllNode putAllNode4 = createPutAllNode(); + putAllNode4.executeIfPresent(o3, keys4, new Object[]{v4}); + assertFalse(uncachedContainsKey(o3, k4)); + assertNull(uncachedGet(o4, k4)); + putAllNode4.executeIfAbsent(o4, keys4, new Object[]{v4}); + assertTrue(uncachedContainsKey(o4, k4)); + assertEquals(v4, uncachedGet(o4, k4)); + for (int i = 0; i < keys.length; i++) { + assertEquals(values1[i], uncachedGet(o1, keys[i])); + assertEquals(values1[i], uncachedGet(o2, keys[i])); + assertEquals(values2[i], uncachedGet(o3, keys[i])); + assertEquals(values1[i], uncachedGet(o4, keys[i])); + } + } + private void fillObjectWithProperties(DynamicObject obj, boolean b) { DynamicObject.PutNode putNode = createPutNode(); @@ -1078,6 +1115,14 @@ private static int uncachedGetPropertyFlags(DynamicObject obj, Object key, int d return DynamicObject.GetPropertyFlagsNode.getUncached().execute(obj, key, defaultValue); } + private static boolean uncachedContainsKey(DynamicObject obj, Object key) { + return DynamicObject.ContainsKeyNode.getUncached().execute(obj, key); + } + + private static void uncachedPutAll(DynamicObject obj, Object[] keys, Object[] values) { + DynamicObject.PutAllNode.getUncached().execute(obj, keys, values); + } + private static Object newObjectType() { return new Object() { }; @@ -1087,10 +1132,6 @@ 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); diff --git a/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest b/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest index f6a577120f3e..e68fb12ad92e 100644 --- a/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest +++ b/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest @@ -164,6 +164,7 @@ innr public abstract static GetPropertyNode innr public abstract static GetShapeFlagsNode innr public abstract static IsSharedNode innr public abstract static MarkSharedNode +innr public abstract static PutAllNode innr public abstract static PutConstantNode innr public abstract static PutNode innr public abstract static RemoveKeyNode @@ -264,6 +265,21 @@ meth public static com.oracle.truffle.api.object.DynamicObject$MarkSharedNode cr 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$PutAllNode + outer com.oracle.truffle.api.object.DynamicObject +cons public init() +meth public final void execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object[],java.lang.Object[]) +meth public final void executeIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object[],java.lang.Object[]) +meth public final void executeIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object[],java.lang.Object[]) +meth public final void executeWithFlags(com.oracle.truffle.api.object.DynamicObject,java.lang.Object[],java.lang.Object[],int[]) +meth public final void executeWithFlagsIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object[],java.lang.Object[],int[]) +meth public final void executeWithFlagsIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object[],java.lang.Object[],int[]) +meth public static com.oracle.truffle.api.object.DynamicObject$PutAllNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$PutAllNode getUncached() +supr com.oracle.truffle.api.nodes.Node +hfds PUT_CACHE_LIMIT +hcls PutAllCache,PutAllPlan + CLSS public abstract static com.oracle.truffle.api.object.DynamicObject$PutConstantNode outer com.oracle.truffle.api.object.DynamicObject meth public final boolean executeIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) @@ -311,14 +327,20 @@ 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 final boolean execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,int) +meth public final boolean executeAdd(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,int) +meth public final boolean executeRemove(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,int) +meth public final boolean executeRemoveAndAdd(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,int,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 final boolean execute(com.oracle.truffle.api.object.DynamicObject,int) +meth public final boolean executeAdd(com.oracle.truffle.api.object.DynamicObject,int) +meth public final boolean executeRemove(com.oracle.truffle.api.object.DynamicObject,int) +meth public final boolean executeRemoveAndAdd(com.oracle.truffle.api.object.DynamicObject,int,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 @@ -333,7 +355,7 @@ supr com.oracle.truffle.api.nodes.Node CLSS public final com.oracle.truffle.api.object.DynamicObjectFactory cons public init() supr java.lang.Object -hcls ContainsKeyNodeGen,CopyPropertiesNodeGen,GetDynamicTypeNodeGen,GetKeyArrayNodeGen,GetNodeGen,GetPropertyArrayNodeGen,GetPropertyFlagsNodeGen,GetPropertyNodeGen,GetShapeFlagsNodeGen,IsSharedNodeGen,MarkSharedNodeGen,PutConstantNodeGen,PutNodeGen,RemoveKeyNodeGen,ResetShapeNodeGen,SetDynamicTypeNodeGen,SetPropertyFlagsNodeGen,SetShapeFlagsNodeGen,UpdateShapeNodeGen +hcls ContainsKeyNodeGen,CopyPropertiesNodeGen,GetDynamicTypeNodeGen,GetKeyArrayNodeGen,GetNodeGen,GetPropertyArrayNodeGen,GetPropertyFlagsNodeGen,GetPropertyNodeGen,GetShapeFlagsNodeGen,IsSharedNodeGen,MarkSharedNodeGen,PutAllNodeGen,PutConstantNodeGen,PutNodeGen,RemoveKeyNodeGen,ResetShapeNodeGen,SetDynamicTypeNodeGen,SetPropertyFlagsNodeGen,SetShapeFlagsNodeGen,UpdateShapeNodeGen CLSS public abstract com.oracle.truffle.api.object.DynamicObjectLibrary anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="25.1") 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 fade2ba3ab30..d3bfd5445545 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 @@ -50,9 +50,11 @@ import java.lang.reflect.Field; import java.util.Map; import java.util.Objects; +import java.util.stream.IntStream; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.HostCompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; @@ -1320,9 +1322,12 @@ public static GetShapeFlagsNode getUncached() { } /** - * Sets language-specific object shape flags. + * Sets or updates language-specific object shape flags. * * @see #execute(DynamicObject, int) + * @see #executeAdd(DynamicObject, int) + * @see #executeRemove(DynamicObject, int) + * @see #executeRemoveAndAdd(DynamicObject, int, int) * @since 25.1 */ @ImportStatic(DynamicObject.class) @@ -1346,25 +1351,73 @@ public abstract static class SetShapeFlagsNode extends Node { * *

Usage example:

* - *

Implementing frozen object check in writeMember:

+ *

Implementing a freeze object operation:

* * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = * "com.oracle.truffle.api.object.DynamicObjectSnippets.SetShapeFlags"} * + * Note that {@link #executeAdd(DynamicObject, int)} is more efficient and convenient for + * that particular pattern. + * * @param newFlags the flags to set; must be in the range from 0 to 65535 (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 #executeAdd(DynamicObject, int) * @see Shape.Builder#shapeFlags(int) + * @since 25.1 + */ + public final boolean execute(DynamicObject receiver, int newFlags) { + return execute(receiver, 0, newFlags); + } + + /** + * Adds language-specific object shape flags, changing the object's shape if need be. + * + *

Usage example:

+ * + *

Implementing a freeze object operation:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.AddShapeFlags"} + * + * @see #execute(DynamicObject, int) + * @since 25.1 */ - public abstract boolean execute(DynamicObject receiver, int newFlags); + public final boolean executeAdd(DynamicObject receiver, int addedFlags) { + return execute(receiver, ~0, addedFlags); + } + + /** + * Removes language-specific object shape flags, changing the object's shape if need be. + * + * @see #execute(DynamicObject, int) + * @since 25.1 + */ + public final boolean executeRemove(DynamicObject receiver, int removedFlags) { + return execute(receiver, ~removedFlags, 0); + } + + /** + * Removes, then adds language-specific object shape flags, changing the object's shape if + * need be. + * + * @see #execute(DynamicObject, int) + * @since 25.1 + */ + public final boolean executeRemoveAndAdd(DynamicObject receiver, int removedFlags, int addedFlags) { + return execute(receiver, ~removedFlags, addedFlags); + } + + abstract boolean execute(DynamicObject receiver, int andFlags, int orFlags); @SuppressWarnings("unused") - @Specialization(guards = {"shape == cachedShape", "flags == newShape.getFlags()"}, limit = "SHAPE_CACHE_LIMIT") - static boolean doCached(DynamicObject receiver, int flags, + @Specialization(guards = {"shape == cachedShape", "newFlags == newShape.getFlags()"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, int andFlags, int orFlags, @Bind("receiver.getShape()") Shape shape, @Cached("shape") Shape cachedShape, - @Cached("shapeSetFlags(cachedShape, flags)") Shape newShape) { + @Bind("computeFlags(cachedShape, andFlags, orFlags)") int newFlags, + @Cached("shapeSetFlags(cachedShape, newFlags)") Shape newShape) { if (newShape != cachedShape) { receiver.setShape(newShape); return true; @@ -1374,9 +1427,9 @@ static boolean doCached(DynamicObject receiver, int flags, } @Specialization(replaces = "doCached") - static boolean doGeneric(DynamicObject receiver, int flags, + static boolean doGeneric(DynamicObject receiver, int andFlags, int orFlags, @Bind("receiver.getShape()") Shape shape) { - Shape newShape = shapeSetFlags(shape, flags); + Shape newShape = shapeSetFlags(shape, computeFlags(shape, andFlags, orFlags)); if (newShape != shape) { receiver.setShape(newShape); return true; @@ -1389,6 +1442,10 @@ static Shape shapeSetFlags(Shape shape, int newFlags) { return shape.setFlags(newFlags); } + static int computeFlags(Shape shape, int and, int or) { + return (shape.getFlags() & and) | or; + } + /** * @since 25.1 */ @@ -1742,9 +1799,12 @@ public static GetPropertyFlagsNode getUncached() { } /** - * Sets the property flags associated with the requested property key. + * Sets or updates property flags associated with the requested property key. * * @see #execute(DynamicObject, Object, int) + * @see #executeAdd(DynamicObject, Object, int) + * @see #executeRemove(DynamicObject, Object, int) + * @see #executeRemoveAndAdd(DynamicObject, Object, int, int) * @since 25.1 */ @ImportStatic(DynamicObject.class) @@ -1763,17 +1823,53 @@ public abstract static class SetPropertyFlagsNode extends Node { * ({@code equals}). See {@link DynamicObject} for more information. * @return {@code true} if the property was found and its flags were changed, else * {@code false} + * @since 25.1 + */ + public final boolean execute(DynamicObject receiver, Object key, int propertyFlags) { + return execute(receiver, key, 0, propertyFlags); + } + + /** + * Adds property flags associated with the requested property. + * + * @see #execute(DynamicObject, Object, int) + * @since 25.1 */ - public abstract boolean execute(DynamicObject receiver, Object key, int propertyFlags); + public final boolean executeAdd(DynamicObject receiver, Object key, int addedFlags) { + return execute(receiver, key, ~0, addedFlags); + } + + /** + * Removes (clears) property flags associated with the requested property. + * + * @see #execute(DynamicObject, Object, int) + * @since 25.1 + */ + public final boolean executeRemove(DynamicObject receiver, Object key, int removedFlags) { + return execute(receiver, key, ~removedFlags, 0); + } + + /** + * Removes, then adds property flags associated with the requested property. + * + * @see #execute(DynamicObject, Object, int) + * @since 25.1 + */ + public final boolean executeRemoveAndAdd(DynamicObject receiver, Object key, int removedFlags, int addedFlags) { + return execute(receiver, key, ~removedFlags, addedFlags); + } + + abstract boolean execute(DynamicObject receiver, Object key, int andFlags, int orFlags); @SuppressWarnings("unused") @Specialization(guards = {"shape == oldShape", "key == cachedKey", "propertyFlags == cachedPropertyFlags"}, limit = "SHAPE_CACHE_LIMIT") - static boolean doCached(DynamicObject receiver, Object key, int propertyFlags, + static boolean doCached(DynamicObject receiver, Object key, int andFlags, int orFlags, @Bind("receiver.getShape()") Shape shape, @Cached("shape") Shape oldShape, @Cached("key") Object cachedKey, - @Cached("propertyFlags") int cachedPropertyFlags, @Cached("oldShape.getProperty(cachedKey)") Property cachedProperty, + @Bind("computeFlags(cachedProperty, andFlags, orFlags)") int propertyFlags, + @Cached("propertyFlags") int cachedPropertyFlags, @Cached("setPropertyFlags(oldShape, cachedProperty, cachedPropertyFlags)") Shape newShape) { if (cachedProperty == null) { return false; @@ -1787,22 +1883,25 @@ static boolean doCached(DynamicObject receiver, Object key, int propertyFlags, } else { changePropertyFlagsGeneric(receiver, oldShape, cachedProperty, cachedPropertyFlags); } + return true; } - return true; + return false; } @TruffleBoundary @Specialization(replaces = "doCached") - static boolean doGeneric(DynamicObject receiver, Object key, int propertyFlags) { + static boolean doGeneric(DynamicObject receiver, Object key, int andFlags, int orFlags) { Shape oldShape = receiver.getShape(); Property existingProperty = oldShape.getProperty(key); if (existingProperty == null) { return false; } + int propertyFlags = computeFlags(existingProperty, andFlags, orFlags); if (existingProperty.getFlags() != propertyFlags) { changePropertyFlagsGeneric(receiver, oldShape, existingProperty, propertyFlags); + return true; } - return true; + return false; } @TruffleBoundary @@ -1829,6 +1928,13 @@ static Shape setPropertyFlags(Shape shape, Property cachedProperty, int property return shape.setPropertyFlags(cachedProperty, propertyFlags); } + static int computeFlags(Property property, int and, int or) { + if (property == null) { + return 0; + } + return (property.getFlags() & and) | or; + } + /** * @since 25.1 */ @@ -1964,6 +2070,7 @@ static boolean doCached(DynamicObject receiver, Shape otherShape, @Specialization(replaces = "doCached") static boolean doGeneric(DynamicObject receiver, Shape otherShape, @Bind("receiver.getShape()") Shape shape) { + verifyResetShape(shape, otherShape); if (shape == otherShape) { return false; } @@ -2181,6 +2288,377 @@ public static GetPropertyArrayNode getUncached() { } } + /** + * Adds or sets multiple properties in bulk. Behaves like {@link PutNode}, but is usually more + * efficient for cases like object initialization where more than a few properties are added at + * once. + * + * @see #execute(DynamicObject, Object[], Object[]) + * @see #executeIfAbsent(DynamicObject, Object[], Object[]) + * @see #executeIfPresent(DynamicObject, Object[], Object[]) + * @see #executeWithFlags(DynamicObject, Object[], Object[], int[]) + * @see #executeWithFlagsIfAbsent(DynamicObject, Object[], Object[], int[]) + * @see #executeWithFlagsIfPresent(DynamicObject, Object[], Object[], int[]) + * @see PutNode + * @since 25.1 + */ + @ImportStatic(DynamicObject.class) + @GeneratePackagePrivate + @GenerateUncached + @GenerateInline(false) + public abstract static class PutAllNode extends Node { + + /** + * Adds multiple properties or sets the values of multiple existing properties at once. + *

+ * Newly added properties will have flags 0; flags of existing properties will not be + * changed. Use {@link #executeWithFlags} to set property flags as well. + *

+ * Cached property keys are compared by identity ({@code ==}), not equality + * ({@code equals}). + * + *

Usage example:

+ * + *

Simple use of {@link PutAllNode} to allocate and fill an object with properties.

+ *

+ * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.PutAll"} + * + * @param keys the property keys, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @param values the values to be set; needs to be the same length as {@code keys} + * @see #executeIfPresent(DynamicObject, Object[], Object[]) + * @see #executeWithFlags(DynamicObject, Object[], Object[], int[]) + * @since 25.1 + */ + public final void execute(DynamicObject receiver, Object[] keys, Object[] values) { + executeImpl(receiver, keys, values, null, Flags.DEFAULT); + } + + /** + * Like {@link #execute(DynamicObject, Object[], Object[])} but only sets properties that + * are already present. + * + * @see #execute(DynamicObject, Object[], Object[]) + * @since 25.1 + */ + public final void executeIfPresent(DynamicObject receiver, Object[] keys, Object[] values) { + executeImpl(receiver, keys, values, null, Flags.IF_PRESENT); + } + + /** + * Like {@link #execute(DynamicObject, Object[], Object[])} but only adds properties that + * are absent. + * + * @see #execute(DynamicObject, Object[], Object[]) + * @since 25.1 + */ + public final void executeIfAbsent(DynamicObject receiver, Object[] keys, Object[] values) { + executeImpl(receiver, keys, values, null, Flags.IF_ABSENT); + } + + /** + * Like {@link #execute(DynamicObject, Object[], Object[])} but additionally sets property + * flags. If a property already exists, its flags will be updated before the value is set. + * + * @param keys the property keys, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @param values the values to be set; needs to be the same length as {@code keys} + * @param propertyFlags the property flags to be set; needs to be the same length as keys + * @since 25.1 + */ + public final void executeWithFlags(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags) { + executeImpl(receiver, keys, values, propertyFlags, Flags.DEFAULT); + } + + /** + * Like {@link #executeIfPresent(DynamicObject, Object[], Object[])} but also sets property + * flags when a property is present. + * + * @see #executeWithFlags(DynamicObject, Object[], Object[], int[]) + * @see #executeIfPresent(DynamicObject, Object[], Object[]) + * @since 25.1 + */ + public final void executeWithFlagsIfPresent(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags) { + executeImpl(receiver, keys, values, propertyFlags, Flags.IF_PRESENT); + } + + /** + * Like {@link #executeIfAbsent(DynamicObject, Object[], Object[])} but also sets property + * flags when adding a property. + * + * @see #executeWithFlags(DynamicObject, Object[], Object[], int[]) + * @see #executeIfAbsent(DynamicObject, Object[], Object[]) + * @since 25.1 + */ + public final void executeWithFlagsIfAbsent(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags) { + executeImpl(receiver, keys, values, propertyFlags, Flags.IF_ABSENT); + } + + abstract void executeImpl(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags, int mode); + + @SuppressWarnings({"unused"}) + @Specialization(guards = { + "mode == cachedMode", + "keysEqual(cachedKeys, keys)", + "guard", + "plan != null", + "canStoreAll(newProperties, values, propertyFlags)" + }, assumptions = {"oldShapeValidAssumption"}, limit = "SHAPE_CACHE_LIMIT") + static void doCached(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags, int mode, + @Bind Node node, + @Cached(value = "keys", dimensions = 1) Object[] cachedKeys, + @Cached("mode") int cachedMode, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Bind("shape == oldShape") boolean guard, + @Cached(value = "getPropertiesOrNull(oldShape, cachedKeys)", dimensions = 1) Property[] oldProperties, + @Cached("preparePutAll(keys, values, mode, propertyFlags, oldShape, oldProperties)") PutAllPlan plan, + @Bind("plan.newShape()") Shape newShape, + @Bind("plan.newProperties()") Property[] newProperties, + @Cached("oldShape.getValidAbstractAssumption()") AbstractAssumption oldShapeValidAssumption) { + assert keys.length == values.length && (propertyFlags == null || propertyFlags.length == keys.length) : "arrays must have the same length"; + performPutAll(receiver, cachedKeys, values, mode, propertyFlags, oldShape, newShape, oldProperties, newProperties); + } + + /* + * 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()", excludeForUncached = true) + static void doInvalid(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags, int mode) { + doGeneric(receiver, keys, values, propertyFlags, mode); + } + + @TruffleBoundary + @Specialization(replaces = "doCached") + static void doGeneric(DynamicObject receiver, Object[] keys, Object[] values, int[] propertyFlags, int mode) { + CompilerAsserts.neverPartOfCompilation(); + assert keys.length == values.length && (propertyFlags == null || propertyFlags.length == keys.length) : "arrays must have the same length"; + updateShape(receiver); + Shape oldShape = receiver.getShape(); + preparePutAllAndApply(keys, values, mode, propertyFlags, oldShape, null, receiver); + } + + @CompilerDirectives.ValueType + record PutAllPlan(Shape newShape, + @CompilationFinal(dimensions = 1) Property[] oldProperties, + @CompilationFinal(dimensions = 1) Property[] newProperties) { + } + + static PutAllPlan preparePutAll(Object[] keys, Object[] values, int mode, int[] flags, Shape oldShape, Property[] existingPropertiesOpt) { + return preparePutAllAndApply(keys, values, mode, flags, oldShape, existingPropertiesOpt, null); + } + + static PutAllPlan preparePutAllAndApply(Object[] keys, Object[] values, int mode, int[] flags, Shape startShape, Property[] existingPropertiesOpt, DynamicObject object) { + Shape oldShape = startShape; + Shape newShape = startShape; + Property[] oldProperties = existingPropertiesOpt; + Property[] newProperties = new Property[keys.length]; + boolean updatedShape = false; + boolean preparing = object == null; + int i = 0; + while (i < keys.length) { + Object key = keys[i]; + Object value = values[i]; + int propertyFlags = flags == null ? 0 : flags[i]; + Property newProperty; + Property existingProperty; + if (existingPropertiesOpt != null && !updatedShape) { + existingProperty = existingPropertiesOpt[i]; + assert Objects.equals(existingProperty, newShape.getProperty(key)) : key; + } else { + existingProperty = newShape.getProperty(key); + } + if (existingProperty == null) { + if (Flags.isPutIfPresent(mode)) { + newProperty = null; + } else { + newShape = newShape.defineProperty(key, value, propertyFlags, mode, null); + newProperty = newShape.getProperty(key); + } + } else if (Flags.isPutIfAbsent(mode)) { + newProperty = null; + } else { + if (Flags.isUpdateFlags(mode) && propertyFlags != existingProperty.getFlags()) { + newShape = newShape.defineProperty(key, value, propertyFlags, mode, existingProperty); + newProperty = newShape.getProperty(key); + } else { + if (existingProperty.getLocation().canStoreValue(value)) { + newProperty = existingProperty; + } else { + assert !Flags.isUpdateFlags(mode) || propertyFlags == existingProperty.getFlags(); + newShape = newShape.defineProperty(key, value, existingProperty.getFlags(), mode, existingProperty); + newProperty = newShape.getProperty(key); + } + } + } + if (!oldShape.isValid()) { + if (preparing) { + /* + * Preparing is not supported for obsolete shapes, since that might require + * moving around existing properties. Like PutNode, we don't cache obsolete + * shapes and handle them via the generic case. + */ + return null; + } else { + /* + * Must perform shape migration. Since properties may have changed + * locations, restart from the beginning with the updated shape. + */ + updateShape(object); + updatedShape = true; + oldShape = newShape = object.getShape(); + // restart after shape migration + i = 0; + continue; + } + } + newProperties[i] = newProperty; + if (existingProperty != null && newProperty != null) { + // Only allocate the array if not all elements are null. + if (oldProperties == null) { + oldProperties = new Property[keys.length]; + } + if (oldProperties[i] == null) { + oldProperties[i] = existingProperty; + } else { + assert oldProperties[i].equals(existingProperty); + } + } + i++; + } + + if (preparing) { + return new PutAllPlan(newShape, oldProperties, newProperties); + } else { + performPutAll(object, keys, values, mode, flags, oldShape, newShape, oldProperties, newProperties); + return null; // not used in this case, so skip the allocation + } + } + + @ExplodeLoop + static void performPutAll(DynamicObject receiver, Object[] keys, Object[] values, int mode, int[] pflags, + Shape oldShape, Shape newShape, Property[] oldProperties, Property[] newProperties) { + if (oldShape != newShape) { + DynamicObjectSupport.grow(receiver, oldShape, newShape); + } + CompilerAsserts.partialEvaluationConstant(keys.length); + assert oldProperties == null || oldProperties.length == newProperties.length; + for (int i = 0; i < keys.length; i++) { + Object value = values[i]; + Property property = newProperties[i]; + if (property == null) { + /* + * A null property implies we're in IfPresent or IfAbsent mode and means there + * is nothing to do for this property since the property is absent or present, + * respectively. + */ + assert Flags.isPutIfPresent(mode) || Flags.isPutIfAbsent(mode); + continue; + } + /* + * These assertions hold because of cached specialization guards or the + * defineProperty contract in the generic case. + */ + assert property.getKey().equals(keys[i]) && (property.getFlags() == (pflags == null ? 0 : pflags[i]) || !Flags.isUpdateFlags(mode)) : property; + assert property.getLocation().canStoreValue(value) : property; + boolean init = oldProperties == null || oldProperties[i] == null || property.getLocation() != oldProperties[i].getLocation(); + Location location = property.getLocation(); + location.setInternal(receiver, value, false, init); + } + if (oldShape != newShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + maybeUpdateShape(receiver, newShape); + } + assert verifyPropertyValues(receiver, keys, values, mode, oldShape, newShape); + } + + private static boolean verifyPropertyValues(DynamicObject receiver, Object[] keys, Object[] values, int mode, Shape oldShape, Shape newShape) { + return IntStream.range(0, keys.length).allMatch(i -> { + Object key = keys[i]; + if (Flags.isPutIfAbsent(mode) && oldShape.getProperty(key) != null) { + assert newShape.getProperty(key).equals(oldShape.getProperty(key)) : key; + return true; + } + if (Flags.isPutIfPresent(mode) && oldShape.getProperty(key) == null) { + assert newShape.getProperty(key) == null : key; + return true; + } + Object newValue = GetNode.getUncached().execute(receiver, key, null); + assert Objects.equals(values[i], newValue) : "key=" + key + " expectedValue=" + values[i] + " actualValue=" + newValue; + return true; + }); + } + + @ExplodeLoop + static boolean keysEqual(Object[] cachedKeys, Object[] keys) { + CompilerAsserts.partialEvaluationConstant(cachedKeys.length); + for (int i = 0; i < cachedKeys.length; i++) { + if (cachedKeys[i] != keys[i]) { + return false; + } + } + return true; + } + + /** + * Looks up multiple properties at once. Returns null if none of properties are present. + */ + static Property[] getPropertiesOrNull(Shape cachedShape, Object[] keys) { + if (cachedShape.getPropertyMap().isEmpty()) { + return null; + } + Property[] properties = null; + for (int i = 0; i < keys.length; i++) { + Property property = cachedShape.getProperty(keys[i]); + if (property == null) { + continue; + } + if (properties == null) { + properties = new Property[keys.length]; + } + properties[i] = property; + } + return properties; + } + + /** + * Checks if the cached properties can store all values and property flags (if any). + */ + @ExplodeLoop + static boolean canStoreAll(Property[] properties, Object[] values, int[] propertyFlags) { + CompilerAsserts.partialEvaluationConstant(properties.length); + if (values.length != properties.length) { + return false; + } + for (int i = 0; i < properties.length; i++) { + Property property = properties[i]; + if (property == null) { + continue; + } + if (!property.getLocation().canStoreValue(values[i]) || + !(propertyFlags == null || property.getFlags() == propertyFlags[i])) { + return false; + } + } + return true; + } + + @NeverDefault + public static PutAllNode getUncached() { + return DynamicObjectFactory.PutAllNodeGen.getUncached(); + } + + @NeverDefault + public static PutAllNode create() { + return DynamicObjectFactory.PutAllNodeGen.create(); + } + } + 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/DynamicObjectSnippets.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSnippets.java index 9b55470045a9..306eeac3bca9 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSnippets.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSnippets.java @@ -56,6 +56,7 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +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.strings.TruffleString; @@ -365,6 +366,21 @@ static void freeze(DynamicObject receiver, } // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.SetShapeFlags" + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.AddShapeFlags" + abstract static class FreezeNode extends Node { + static final int FROZEN = 1; + + abstract void execute(DynamicObject receiver); + + @Specialization + static void freeze(DynamicObject receiver, + @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { + setShapeFlagsNode.executeAdd(receiver, FROZEN); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.AddShapeFlags" + @GenerateCached(false) abstract static class GetUnboxedNode extends Node { @@ -384,4 +400,30 @@ static Object doGeneric(DynamicObject receiver, Symbol key, return getNode.execute(receiver, key, NULL_VALUE); } } + + abstract static class ExprNode extends Node { + abstract Object execute(); + } + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutAll" + abstract static class ObjectInitializerNode extends Node { + @CompilationFinal private Shape initialShape; + @CompilationFinal(dimensions = 1) private Object[] keys; + @Children private ExprNode[] valueNodes; + + abstract void execute(); + + @ExplodeLoop + @Specialization + void doDefault(@Cached DynamicObject.PutAllNode putAllNode) { + var receiver = new MyDynamicObjectSubclass(initialShape); + var values = new Object[keys.length]; + for (int i = 0; i < keys.length; i++) { + values[i] = valueNodes[i].execute(); + } + putAllNode.execute(receiver, keys, values); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutAll" } 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 6d9a9cf65854..be8595a7e126 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 @@ -452,14 +452,23 @@ final void setDoubleSafe(DynamicObject store, double value, boolean guard, boole */ @SuppressWarnings("hiding") final void setInternal(DynamicObject store, Object value, boolean guard, Shape oldShape, Shape newShape) { + boolean init = newShape != oldShape; + if (init) { + DynamicObjectSupport.grow(store, oldShape, newShape); + } + setInternal(store, value, guard, init); + } + + /** + * @see #setInternal(DynamicObject, Object, boolean, Shape, Shape) + */ + @SuppressWarnings("hiding") + final void setInternal(DynamicObject store, Object value, boolean guard, boolean init) { assert canStoreValue(value) : value; DynamicObject receiver = unsafeNonNullCast(store); long idx = Integer.toUnsignedLong(index); FieldInfo field = this.field; - boolean init = newShape != oldShape; - if (init) { - DynamicObjectSupport.grow(receiver, oldShape, newShape); - } else { + if (!init) { AbstractAssumption assumption = getFinalAssumptionField(); if (assumption == null || assumption.isValid()) { invalidateFinalAssumption(assumption); 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 6ce95963af93..44948525570e 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 @@ -233,7 +233,7 @@ static Shape defineProperty(Shape shape, Object key, Object value, int flags, in static Shape defineProperty(Shape oldShape, Object key, Object value, int propertyFlags, Property existing, int putFlags) { if (existing == null) { - return defineNewProperty(oldShape, key, value, propertyFlags, putFlags); + return ensureValid(defineNewProperty(oldShape, key, value, propertyFlags, putFlags)); } else { if (existing.getFlags() == propertyFlags) { if (existing.getLocation().canStore(value)) { @@ -248,20 +248,37 @@ static Shape defineProperty(Shape oldShape, Object key, Object value, int proper } private static Shape defineNewProperty(Shape oldShape, Object key, Object value, int propertyFlags, int putFlags) { - if (!Flags.isConstant(putFlags)) { + Property property; + AddPropertyTransition addTransition; + if (Flags.isConstant(putFlags)) { + Location location = createLocationForValue(oldShape, value, putFlags); + property = Property.create(key, location, propertyFlags); + addTransition = new AddPropertyTransition(property, location); + } else { + property = null; Class locationType = detectLocationType(value); - if (locationType != null) { - AddPropertyTransition addTransition = new AddPropertyTransition(key, propertyFlags, locationType); - Shape cachedShape = oldShape.queryTransition(addTransition); - if (cachedShape != null) { - return ensureValid(cachedShape); - } - } + addTransition = new AddPropertyTransition(key, propertyFlags, locationType); + } + + oldShape.onPropertyTransition(addTransition); + Shape cachedShape = oldShape.queryTransition(addTransition); + if (cachedShape != null) { + return cachedShape; + } + + if (property == null) { + Location location = createLocationForValue(oldShape, value, putFlags); + property = Property.create(key, location, propertyFlags); + addTransition = newAddPropertyTransition(property); } - Location location = createLocationForValue(oldShape, value, putFlags); - Property property = Property.create(key, location, propertyFlags); - return addProperty(oldShape, property); + Shape newShape = addPropertyTransition(oldShape, property, addTransition); + + Property actualProperty = newShape.getLastProperty(); + // Ensure the actual property location is of the same type or more general. + ensureSameTypeOrMoreGeneral(actualProperty, property); + + return newShape; } private static Class detectLocationType(Object value) { @@ -371,30 +388,32 @@ private static Shape directReplaceProperty(Shape shape, Property oldProperty, Pr return ensureValid ? ensureValid(newShape) : newShape; } - static Shape addProperty(Shape shape, Property property) { - return addProperty(shape, property, true); - } - - private static Shape addProperty(Shape shape, Property property, boolean ensureValid) { + static Shape addProperty(Shape shape, Property property, boolean ensureValid) { Shape newShape = addPropertyInner(shape, property); - - Property actualProperty = newShape.getLastProperty(); - // Ensure the actual property location is of the same type or more general. - ensureSameTypeOrMoreGeneral(actualProperty, property); - return ensureValid ? ensureValid(newShape) : newShape; } private static Shape addPropertyInner(Shape shape, Property property) { - assert !(shape.hasProperty(property.getKey())) : "duplicate property " + property.getKey(); - AddPropertyTransition addTransition = newAddPropertyTransition(property); shape.onPropertyTransition(addTransition); Shape cachedShape = shape.queryTransition(addTransition); + Shape newShape; if (cachedShape != null) { - return cachedShape; + newShape = cachedShape; + } else { + newShape = addPropertyTransition(shape, property, addTransition); } + Property actualProperty = newShape.getLastProperty(); + // Ensure the actual property location is of the same type or more general. + ensureSameTypeOrMoreGeneral(actualProperty, property); + + return newShape; + } + + private static Shape addPropertyTransition(Shape shape, Property property, AddPropertyTransition addTransition) { + assert !(shape.hasProperty(property.getKey())) : "duplicate property " + property.getKey(); + Shape oldShape = ensureSpace(shape, property.getLocation()); Shape newShape = Shape.makeShapeWithAddedProperty(oldShape, addTransition); 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 908f36f5d2ff..69e15b73a110 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 @@ -713,7 +713,7 @@ Location getLocation(Object key) { */ @TruffleBoundary protected Shape addProperty(Property property) { - return ObsolescenceStrategy.addProperty(this, property); + return ObsolescenceStrategy.addProperty(this, property, true); } /**