|
| 1 | +# Migrating from DynamicObjectLibrary to DynamicObject Nodes |
| 2 | + |
| 3 | +`DynamicObjectLibrary` has been deprecated in 25.1, and replaced with new, lighter-weight node-based APIs, found as subclasses of `DynamicObject`. |
| 4 | +Each `DynamicObjectLibrary` message has an equivalent node replacement. |
| 5 | + |
| 6 | +| `DynamicObjectLibrary` message | `DynamicObject` node equivalent | Purpose | |
| 7 | +|--------------------------------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------------| |
| 8 | +| `getOrDefault(obj, key, def)` | `DynamicObject.GetNode.execute(obj, key, def)` | Gets the value of a property or a default value if absent | |
| 9 | +| `getIntOrDefault(obj, key, def)` | `DynamicObject.GetNode.executeInt(obj, key, def)` | Gets the value of a property as int or throws UnexpectedResultException | |
| 10 | +| `getLongOrDefault(obj, key, def)` | `DynamicObject.GetNode.executeLong(obj, key, def)` | Gets the value of a property as long or throws UnexpectedResultException | |
| 11 | +| `getDoubleOrDefault(obj, key, def)` | `DynamicObject.GetNode.executeDouble(obj, key, def)` | Gets the value of a property as double or throws UnexpectedResultException | |
| 12 | +| `put(obj, key, val)` | `DynamicObject.PutNode.execute(obj, key, val)` | Adds a new property of sets the value of an existing property | |
| 13 | +| `putWithFlags(obj, key, val, flags)` | `DynamicObject.PutNode.executeWithFlags(obj, key, val, flags)` | Adds a new property or sets the value and flags of an existing property | |
| 14 | +| `putIfPresent(obj, key, val)` | `DynamicObject.PutNode.executeIfPresent(obj, key, val)` | Sets the value of a property, if present | |
| 15 | +| `putConstant(obj, key, val, flags)` | `DynamicObject.PutConstantNode.executeWithFlags(obj, key, val, flags)` | Adds or replaces a property with a constant shape-bound value, use sparingly | |
| 16 | +| `containsKey(obj, key)` | `DynamicObject.ContainsKeyNode.execute(obj, key)` | Checks for the existence of a property | |
| 17 | +| `removeKey(obj, key)` | `DynamicObject.RemoveKeyNode.execute(obj, key)` | Removes a property | |
| 18 | +| `getPropertyFlagsOrDefault(obj, key, def)` | `DynamicObject.GetPropertyFlagsNode.execute(obj, key, def)` | Gets the flags of a property or a default value if absent | |
| 19 | +| `setPropertyFlags(obj, key, flags)` | `DynamicObject.SetPropertyFlagsNode.execute(obj, key, flags)` | Updates the flags or a property | |
| 20 | +| `getKeyArray(obj)` | `DynamicObject.GetKeyArrayNode.execute(obj)` | Returns an array of the keys of all the non-hidden properties | |
| 21 | +| `getProperty(obj, key)` | `DynamicObject.GetPropertyNode.execute(obj, key)` | Gets the property descriptor or null if absent | |
| 22 | +| `getPropertyArray(obj)` | `DynamicObject.GetPropertyArrayNode.execute(obj)` | Returns an array of the property descriptors of all the non-hidden properties | |
| 23 | +| `getShapeFlags(obj)` | `DynamicObject.GetShapeFlagsNode.execute(obj)` | Gets the language-specific shape flags | |
| 24 | +| `setShapeFlags(obj, flags)` | `DynamicObject.SetShapeFlagsNode.execute(obj, flags)` | Sets the language-specific shape flags | |
| 25 | +| `getDynamicType(obj)` | `DynamicObject.GetDynamicTypeNode.execute(obj)` | Gets the language-specific dynamic type identifier | |
| 26 | +| `setDynamicType(obj, type)` | `DynamicObject.SetDynamicTypeNode.execute(obj, type)` | Sets the language-specific dynamic type identifier | |
| 27 | +| `updateShape(obj)` | `DynamicObject.UpdateShapeNode.execute(obj)` | Updates the shape if the object has an obsolete shape | |
| 28 | +| `resetShape(obj, rootShape)` | `DynamicObject.ResetShapeNode.execute(obj, rootShape)` | Resets the object to an empty shape | |
| 29 | +| `markShared(obj)` | `DynamicObject.MarkSharedNode.execute(obj)` | Marks the object as shared | |
| 30 | +| `isShared(obj)` | `DynamicObject.IsSharedNode.execute(obj)` | Queries the object's shared state | |
| 31 | +| `getShape(obj)` | `obj.getShape()` | No node equivalent, use direct API | |
| 32 | + |
| 33 | +Note: Unlike `DynamicObjectLibrary`, cached property keys are always compared by identity (`==`) rather than equality (`equals`). |
| 34 | +If you rely on key equality, cache the key using an `equals` guard and pass the cached canonical key to the node. |
| 35 | + |
| 36 | +## Code examples |
| 37 | + |
| 38 | +### Reading a property |
| 39 | + |
| 40 | +#### Getting the value of a property with a fixed key or a dynamic key that is already unique or interned |
| 41 | + |
| 42 | +```java |
| 43 | +abstract class GetUniqueKeyNode extends Node { |
| 44 | + |
| 45 | + abstract Object execute(DynamicObject receiver, Object key); |
| 46 | + |
| 47 | + @Specialization |
| 48 | + static Object doCached(MyDynamicObject receiver, Symbol key, |
| 49 | + @Cached DynamicObject.GetNode getNode) { |
| 50 | + return getNode.execute(receiver, key, NULL_VALUE); |
| 51 | + } |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +#### Getting the value of a property with a dynamic key and key equality |
| 56 | + |
| 57 | +```java |
| 58 | +@ImportStatic(TruffleString.Encoding.class) |
| 59 | +abstract class GetStringKeyNode extends Node { |
| 60 | + |
| 61 | + abstract Object execute(DynamicObject receiver, Object key); |
| 62 | + |
| 63 | + @Specialization(guards = "equalNode.execute(key, cachedKey, UTF_16)", limit = "3") |
| 64 | + static Object doCached(MyDynamicObject receiver, TruffleString key, |
| 65 | + @Cached("key") TruffleString cachedKey, |
| 66 | + @Cached TruffleString.EqualNode equalNode, |
| 67 | + @Shared @Cached DynamicObject.GetNode getNode) { |
| 68 | + return getNode.execute(receiver, cachedKey, NULL_VALUE); |
| 69 | + } |
| 70 | + |
| 71 | + @Specialization(replaces = "doCached") |
| 72 | + static Object doGeneric(MyDynamicObject receiver, TruffleString key, |
| 73 | + @Shared @Cached DynamicObject.GetNode getNode) { |
| 74 | + return getNode.execute(receiver, key, NULL_VALUE); |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +Note that key interning is not required since only the cached code path compares keys by identity (`==`), the generic code path still uses `equals` to compare the property keys. |
| 80 | + |
| 81 | +#### Getting the value of a property with boxing elimination |
| 82 | + |
| 83 | +```java |
| 84 | +abstract class GetUnboxedNode extends Node { |
| 85 | + |
| 86 | + abstract int executeInt(DynamicObject receiver, Object key) throws UnexpectedResultException; |
| 87 | + |
| 88 | + abstract Object execute(DynamicObject receiver, Object key); |
| 89 | + |
| 90 | + @Specialization(rewriteOn = UnexpectedResultException.class) |
| 91 | + static int doInt(MyDynamicObject receiver, Symbol key, |
| 92 | + @Shared @Cached DynamicObject.GetNode getNode) throws UnexpectedResultException { |
| 93 | + return getNode.executeInt(receiver, key, NULL_VALUE); |
| 94 | + } |
| 95 | + |
| 96 | + @Specialization(replaces = "doInt") |
| 97 | + static Object doGeneric(MyDynamicObject receiver, Symbol key, |
| 98 | + @Shared @Cached DynamicObject.GetNode getNode) { |
| 99 | + return getNode.execute(receiver, key, NULL_VALUE); |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +### Writing a property |
| 105 | + |
| 106 | +#### Adding a property or setting the value of a property with a fixed key or a dynamic key that is already unique or interned |
| 107 | + |
| 108 | +```java |
| 109 | +abstract class SetUniqueKeyNode extends Node { |
| 110 | + |
| 111 | + abstract void execute(DynamicObject receiver, Object key, Object value); |
| 112 | + |
| 113 | + @Specialization |
| 114 | + static void doCached(MyDynamicObject receiver, Symbol key, Object value, |
| 115 | + @Cached DynamicObject.PutNode putNode) { |
| 116 | + putNode.execute(receiver, key, value); |
| 117 | + } |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +#### Setting the value of a property with a dynamic key and key equality |
| 122 | + |
| 123 | +```java |
| 124 | +@ImportStatic(TruffleString.Encoding.class) |
| 125 | +abstract class SetStringKeyNode extends Node { |
| 126 | + |
| 127 | + abstract void execute(DynamicObject receiver, Object key, Object value); |
| 128 | + |
| 129 | + @Specialization(guards = "equalNode.execute(key, cachedKey, UTF_16)", limit = "3") |
| 130 | + static void doCached(MyDynamicObject receiver, TruffleString key, Object value, |
| 131 | + @Cached("key") TruffleString cachedKey, |
| 132 | + @Cached TruffleString.EqualNode equalNode, |
| 133 | + @Cached DynamicObject.PutNode putNode) { |
| 134 | + putNode.execute(receiver, cachedKey, value); |
| 135 | + } |
| 136 | + |
| 137 | + @Specialization(replaces = "doCached") |
| 138 | + static void doGeneric(MyDynamicObject receiver, TruffleString key, |
| 139 | + @Shared @Cached DynamicObject.PutNode getNode) { |
| 140 | + putNode.execute(receiver, key, value); |
| 141 | + } |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +Note that key interning is not required since only the cached code path compares keys by identity (`==`), the generic code path still uses `equals` to compare the property keys. |
0 commit comments