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..a0228a0f2257 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().execute(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().execute(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.execute(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.execute(obj, "key", val + 2); } return val; } private int getInt(DynamicObject obj, Object key) { try { - return dynamicObjectLibrary.getIntOrDefault(obj, key, null); + return getNode.executeInt(obj, key, null); } catch (UnexpectedResultException e) { throw CompilerDirectives.shouldNotReachHere(); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/ConditionAnchorNode.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/ConditionAnchorNode.java index d3f992d51d1b..32be485e2d14 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/ConditionAnchorNode.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/ConditionAnchorNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -29,18 +29,34 @@ import static jdk.graal.compiler.nodeinfo.NodeCycles.CYCLES_0; import static jdk.graal.compiler.nodeinfo.NodeSize.SIZE_0; +import jdk.graal.compiler.core.common.calc.CanonicalCondition; import jdk.graal.compiler.core.common.type.StampFactory; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.graph.NodeClass; import jdk.graal.compiler.nodeinfo.NodeInfo; import jdk.graal.compiler.nodeinfo.Verbosity; +import jdk.graal.compiler.nodes.calc.CompareNode; import jdk.graal.compiler.nodes.extended.GuardingNode; import jdk.graal.compiler.nodes.extended.ValueAnchorNode; import jdk.graal.compiler.nodes.spi.Canonicalizable; import jdk.graal.compiler.nodes.spi.CanonicalizerTool; import jdk.graal.compiler.nodes.spi.Lowerable; import jdk.graal.compiler.nodes.spi.LoweringTool; +import jdk.graal.compiler.options.OptionValues; +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.MetaAccessProvider; +/** + * A {@link ConditionAnchorNode} is used to anchor a floatable unsafe load or {@linkplain PiNode + * unsafe cast} in the control flow and associate it with a control flow dependency (i.e. a guard), + * represented as a boolean condition. Conditional Elimination then tries to find a guard that + * corresponds to this condition, and rewires the condition anchor's usages to that guard. If no + * such relationship can be established, the condition anchor is replaced with an unconditional + * {@link ValueAnchorNode}. + * + * @see jdk.graal.compiler.phases.common.ConditionalEliminationPhase + * @see jdk.graal.compiler.nodes.extended.GuardedUnsafeLoadNode + */ @NodeInfo(nameTemplate = "ConditionAnchor(!={p#negated})", allowedUsageTypes = Guard, cycles = CYCLES_0, size = SIZE_0) public final class ConditionAnchorNode extends FixedWithNextNode implements Canonicalizable.Unary, Lowerable, GuardingNode { @@ -58,6 +74,37 @@ public ConditionAnchorNode(LogicNode condition, boolean negated) { this.condition = condition; } + /** + * Creates a condition anchor from a boolean value representing the guarding condition. + * + * Note: The caller must handle the case where no anchor is needed for constant true. + */ + public static FixedWithNextNode create(ValueNode condition, ConstantReflectionProvider constantReflection, MetaAccessProvider metaAccess, OptionValues options, NodeView view) { + if (condition.isConstant()) { + return new ValueAnchorNode(); + } else { + return create(CompareNode.createCompareNode(constantReflection, metaAccess, options, null, CanonicalCondition.EQ, condition, ConstantNode.forBoolean(true), view)); + } + } + + /** + * Creates a condition anchor from a logical comparison. + * + * @see #canonical(CanonicalizerTool, Node) + */ + public static FixedWithNextNode create(LogicNode compareNode) { + if (compareNode instanceof LogicConstantNode) { + /* + * Even if the condition is true, an anchor that has usages must still exist since it's + * possible the condition is true for control flow reasons so the Pi stamp is also only + * valid for those reasons. + */ + return new ValueAnchorNode(); + } else { + return new ConditionAnchorNode(compareNode); + } + } + public LogicNode condition() { return condition; } @@ -77,14 +124,12 @@ public String toString(Verbosity verbosity) { @Override public Node canonical(CanonicalizerTool tool, Node forValue) { - if (forValue instanceof LogicNegationNode) { - LogicNegationNode negation = (LogicNegationNode) forValue; + if (forValue instanceof LogicNegationNode negation) { return new ConditionAnchorNode(negation.getValue(), !negated); } - if (forValue instanceof LogicConstantNode) { - LogicConstantNode c = (LogicConstantNode) forValue; + if (forValue instanceof LogicConstantNode c) { // An anchor that still has usages must still exist since it's possible the condition is - // true for control flow reasons so the Pi stamp is also only valid for those reason. + // true for control flow reasons so the Pi stamp is also only valid for those reasons. if (c.getValue() == negated || hasUsages()) { return new ValueAnchorNode(); } else { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java index 48690aa498ad..faba2248b7a2 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java @@ -42,7 +42,6 @@ import com.oracle.truffle.compiler.TruffleCompilationTask; import jdk.graal.compiler.core.common.NumUtil; -import jdk.graal.compiler.core.common.calc.CanonicalCondition; import jdk.graal.compiler.core.common.memory.MemoryOrderMode; import jdk.graal.compiler.core.common.type.IntegerStamp; import jdk.graal.compiler.core.common.type.ObjectStamp; @@ -63,7 +62,6 @@ import jdk.graal.compiler.nodes.FixedGuardNode; import jdk.graal.compiler.nodes.FrameState; import jdk.graal.compiler.nodes.InvokeNode; -import jdk.graal.compiler.nodes.LogicConstantNode; import jdk.graal.compiler.nodes.LogicNode; import jdk.graal.compiler.nodes.NamedLocationIdentity; import jdk.graal.compiler.nodes.NodeView; @@ -72,7 +70,6 @@ import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.ValuePhiNode; -import jdk.graal.compiler.nodes.calc.CompareNode; import jdk.graal.compiler.nodes.calc.ConditionalNode; import jdk.graal.compiler.nodes.calc.IntegerMulHighNode; import jdk.graal.compiler.nodes.calc.RoundNode; @@ -1216,6 +1213,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec ResolvedJavaType javaType = constantReflection.asJavaType(clazz.asConstant()); if (javaType == null) { b.push(JavaKind.Object, object); + return true; } else { TypeReference type; if (isExactType.asJavaConstant().asInt() != 0) { @@ -1227,30 +1225,14 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec boolean trustedNonNull = nonNull.asJavaConstant().asInt() != 0 && Options.TruffleTrustedNonNullCast.getValue(b.getOptions()); Stamp piStamp = StampFactory.object(type, trustedNonNull); - - ConditionAnchorNode valueAnchorNode = null; - if (condition.isConstant() && condition.asJavaConstant().asInt() == 1) { - // Nothing to do. - } else { - boolean skipAnchor = false; - LogicNode compareNode = CompareNode.createCompareNode(object.graph(), CanonicalCondition.EQ, condition, ConstantNode.forBoolean(true, object.graph()), constantReflection, - NodeView.DEFAULT); - - if (compareNode instanceof LogicConstantNode) { - LogicConstantNode logicConstantNode = (LogicConstantNode) compareNode; - if (logicConstantNode.getValue()) { - skipAnchor = true; - } - } - - if (!skipAnchor) { - valueAnchorNode = b.add(new ConditionAnchorNode(compareNode)); - } + ValueNode guard = null; + // If the condition is the constant true then no guard is needed + if (!condition.isConstant() || condition.asJavaConstant().asInt() == 0) { + guard = b.add(ConditionAnchorNode.create(condition, constantReflection, b.getMetaAccess(), b.getOptions(), NodeView.DEFAULT)); } - - b.addPush(JavaKind.Object, trustedBox(type, types, PiNode.create(object, piStamp, valueAnchorNode))); + b.addPush(JavaKind.Object, trustedBox(type, types, PiNode.create(object, piStamp, guard))); + return true; } - return true; } else if (canDelayIntrinsification) { return false; } else { @@ -1308,9 +1290,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec ValueNode guard = null; // If the condition is the constant true then no guard is needed if (!condition.isConstant() || condition.asJavaConstant().asInt() == 0) { - LogicNode compare = b.add(CompareNode.createCompareNode(b.getConstantReflection(), b.getMetaAccess(), b.getOptions(), null, CanonicalCondition.EQ, condition, - ConstantNode.forBoolean(true, object.graph()), NodeView.DEFAULT)); - guard = b.add(new ConditionAnchorNode(compare)); + guard = b.add(ConditionAnchorNode.create(condition, b.getConstantReflection(), b.getMetaAccess(), b.getOptions(), NodeView.DEFAULT)); } b.addPush(returnKind, b.add(new GuardedUnsafeLoadNode(b.addNonNullCast(object), offset, returnKind, locationIdentity, guard, forceLocation))); return true; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java index 41e7c81cf452..57919e18c7d9 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java @@ -37,7 +37,6 @@ import jdk.graal.compiler.core.common.Stride; import jdk.graal.compiler.core.common.StrideUtil; -import jdk.graal.compiler.core.common.calc.CanonicalCondition; import jdk.graal.compiler.core.common.calc.FloatConvert; import jdk.graal.compiler.core.common.spi.ConstantFieldProvider; import jdk.graal.compiler.core.common.type.Stamp; @@ -48,14 +47,11 @@ import jdk.graal.compiler.nodes.ComputeObjectAddressNode; import jdk.graal.compiler.nodes.ConditionAnchorNode; import jdk.graal.compiler.nodes.ConstantNode; -import jdk.graal.compiler.nodes.LogicConstantNode; -import jdk.graal.compiler.nodes.LogicNode; import jdk.graal.compiler.nodes.NamedLocationIdentity; import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.PiNode; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.calc.AddNode; -import jdk.graal.compiler.nodes.calc.CompareNode; import jdk.graal.compiler.nodes.calc.FloatConvertNode; import jdk.graal.compiler.nodes.calc.LeftShiftNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; @@ -111,63 +107,61 @@ public static void register(Architecture architecture, InvocationPlugins plugins registerFramePlugins(plugins); registerBytecodePlugins(plugins); registerCompilerDirectivesPlugins(plugins); + registerDynamicObjectPlugins(plugins); } private static void registerFramePlugins(InvocationPlugins plugins) { plugins.registerIntrinsificationPredicate(t -> t.getName().equals("Lcom/oracle/truffle/api/impl/FrameWithoutBoxing;")); InvocationPlugins.Registration r = new InvocationPlugins.Registration(plugins, "com.oracle.truffle.api.impl.FrameWithoutBoxing"); - r.register(new OptionalInlineOnlyInvocationPlugin("unsafeCast", Object.class, Class.class, boolean.class, boolean.class, boolean.class) { - @Override - public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode object, ValueNode clazz, ValueNode condition, ValueNode nonNull, - ValueNode isExactType) { - if (!clazz.isConstant() || !nonNull.isConstant() || !isExactType.isConstant()) { - b.push(JavaKind.Object, object); - return true; - } - if (!Options.TruffleTrustedTypeCast.getValue(b.getOptions())) { - b.push(JavaKind.Object, object); - return true; - } - ConstantReflectionProvider constantReflection = b.getConstantReflection(); - ResolvedJavaType javaType = constantReflection.asJavaType(clazz.asConstant()); - if (javaType == null) { - b.push(JavaKind.Object, object); - return true; - } - - TypeReference type; - if (isExactType.asJavaConstant().asInt() != 0) { - assert javaType.isConcrete() || javaType.isArray() : "exact type is not a concrete class: " + javaType; - type = TypeReference.createExactTrusted(javaType); - } else { - type = TypeReference.createTrusted(b.getAssumptions(), javaType); - } + r.register(new UnsafeCastPlugin("unsafeCast", true)); + } - boolean trustedNonNull = nonNull.asJavaConstant().asInt() != 0 && Options.TruffleTrustedNonNullCast.getValue(b.getOptions()); - Stamp piStamp = StampFactory.object(type, trustedNonNull); + private static final class UnsafeCastPlugin extends OptionalInlineOnlyInvocationPlugin { + private final boolean injectTrustedFinal; - ConditionAnchorNode valueAnchorNode = null; - if (condition.isConstant() && condition.asJavaConstant().asInt() == 1) { - // Nothing to do. - } else { - boolean skipAnchor = false; - LogicNode compareNode = CompareNode.createCompareNode(object.graph(), CanonicalCondition.EQ, condition, ConstantNode.forBoolean(true, object.graph()), constantReflection, - NodeView.DEFAULT); - if (compareNode instanceof LogicConstantNode) { - LogicConstantNode logicConstantNode = (LogicConstantNode) compareNode; - if (logicConstantNode.getValue()) { - skipAnchor = true; - } - } - if (!skipAnchor) { - valueAnchorNode = b.add(new ConditionAnchorNode(compareNode)); - } - } + UnsafeCastPlugin(String name, boolean injectTrustedFinal) { + super(name, Object.class, Class.class, boolean.class, boolean.class, boolean.class); + this.injectTrustedFinal = injectTrustedFinal; + } - b.addPush(JavaKind.Object, PiNode.create(castTrustedFinalFrameField(b, object), piStamp, valueAnchorNode)); + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode object, ValueNode clazz, ValueNode condition, ValueNode nonNull, + ValueNode isExactType) { + if (!clazz.isConstant() || !nonNull.isConstant() || !isExactType.isConstant()) { + b.push(JavaKind.Object, object); return true; } - }); + if (!Options.TruffleTrustedTypeCast.getValue(b.getOptions())) { + b.push(JavaKind.Object, object); + return true; + } + ConstantReflectionProvider constantReflection = b.getConstantReflection(); + ResolvedJavaType javaType = constantReflection.asJavaType(clazz.asConstant()); + if (javaType == null) { + b.push(JavaKind.Object, object); + return true; + } + + TypeReference type; + if (isExactType.asJavaConstant().asInt() != 0) { + assert javaType.isConcrete() || javaType.isArray() : "exact type is not a concrete class: " + javaType; + type = TypeReference.createExactTrusted(javaType); + } else { + type = TypeReference.createTrusted(b.getAssumptions(), javaType); + } + + boolean trustedNonNull = nonNull.asJavaConstant().asInt() != 0 && Options.TruffleTrustedNonNullCast.getValue(b.getOptions()); + Stamp piStamp = StampFactory.object(type, trustedNonNull); + + ValueNode guard = null; + // If the condition is the constant true then no guard is needed + if (!condition.isConstant() || condition.asJavaConstant().asInt() == 0) { + guard = b.add(ConditionAnchorNode.create(condition, b.getConstantReflection(), b.getMetaAccess(), b.getOptions(), NodeView.DEFAULT)); + } + ValueNode trustedObject = injectTrustedFinal ? castTrustedFinalFrameField(b, object) : object; + b.addPush(JavaKind.Object, PiNode.create(trustedObject, piStamp, guard)); + return true; + } } /** @@ -830,4 +824,10 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec } }); } + + private static void registerDynamicObjectPlugins(InvocationPlugins plugins) { + plugins.registerIntrinsificationPredicate(t -> t.getName().equals("Lcom/oracle/truffle/api/object/UnsafeAccess;")); + InvocationPlugins.Registration r = new InvocationPlugins.Registration(plugins, "com.oracle.truffle.api.object.UnsafeAccess"); + r.register(new UnsafeCastPlugin("hostUnsafeCast", false)); + } } diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index 1bab0733a35b..9834c53d5212 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -37,6 +37,18 @@ This changelog summarizes major changes between Truffle versions relevant to lan * GR-70086: Added `replacementOf` and `replacementMethod` attributes to `GenerateLibrary.Abstract` annotation. They enable automatic generation of legacy delegators during message library evolution, while allowing custom conversions when needed. * GR-70086 Deprecated `Message.resolve(Class, String)`. Use `Message.resolveExact(Class, String, Class...)` with argument types instead. This deprecation was necessary as library messages are no longer unique by message name, if the previous message was deprecated. +* GR-36894: Added `DynamicObject` nodes for dealing with `DynamicObject` properties and shapes, as a more lightweight replacement for `DynamicObjectLibrary`, including: + * `DynamicObject.GetNode`: gets the value of a property or a default value if absent + * `DynamicObject.PutNode`: adds a new property or sets the value of an existing property + * `DynamicObject.ContainsKeyNode`: checks if the object contains a specific property + * `DynamicObject.RemoveKeyNode`: removes a property + * `DynamicObject.GetKeyArrayNode`: gets an array of keys of all the object's properties + * `DynamicObject.CopyPropertiesNode`: copies all properties from one object to another + * `DynamicObject.GetShapeFlagsNode` and `SetShapeFlagsNode`: gets and sets flags in the object's shape, respectively + * `DynamicObject.GetDynamicTypeNode` and `SetDynamicTypeNode`: gets and sets the dynamic type id in the object's shape, respectively + * See [`DynamicObject` javadoc](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObject.html) for a complete list and more information. There's an equivalent for every `DynamicObjectLibrary` message. + * Note: Unlike `DynamicObjectLibrary`, cached property keys are always compared by identity (`==`) rather than equality (`equals`). If you rely on key equality, cache the key using an `equals` guard and pass the cached canonical key to the node. +* GR-36894: Deprecated `DynamicObjectLibrary`. Use `DynamicObject` nodes instead. See the [migration guide](https://github.com/oracle/graal/blob/master/truffle/docs/DynamicObjectLibraryMigration.md) for an overview of the required changes. ## Version 25.0 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/docs/DynamicObjectLibraryMigration.md b/truffle/docs/DynamicObjectLibraryMigration.md new file mode 100644 index 000000000000..b8eb08e59a04 --- /dev/null +++ b/truffle/docs/DynamicObjectLibraryMigration.md @@ -0,0 +1,145 @@ +# Migrating from DynamicObjectLibrary to DynamicObject Nodes + +`DynamicObjectLibrary` has been deprecated in 25.1, and replaced with new, lighter-weight node-based APIs, found as subclasses of `DynamicObject`. +Each `DynamicObjectLibrary` message has an equivalent node replacement. + +| `DynamicObjectLibrary` message | `DynamicObject` node equivalent | Purpose | +|--------------------------------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------------| +| `getOrDefault(obj, key, def)` | `DynamicObject.GetNode.execute(obj, key, def)` | Gets the value of a property or a default value if absent | +| `getIntOrDefault(obj, key, def)` | `DynamicObject.GetNode.executeInt(obj, key, def)` | Gets the value of a property as int or throws UnexpectedResultException | +| `getLongOrDefault(obj, key, def)` | `DynamicObject.GetNode.executeLong(obj, key, def)` | Gets the value of a property as long or throws UnexpectedResultException | +| `getDoubleOrDefault(obj, key, def)` | `DynamicObject.GetNode.executeDouble(obj, key, def)` | Gets the value of a property as double or throws UnexpectedResultException | +| `put(obj, key, val)` | `DynamicObject.PutNode.execute(obj, key, val)` | Adds a new property of sets the value of an existing property | +| `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 | +| `putIfPresent(obj, key, val)` | `DynamicObject.PutNode.executeIfPresent(obj, key, val)` | Sets the value of a property, if present | +| `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 | +| `containsKey(obj, key)` | `DynamicObject.ContainsKeyNode.execute(obj, key)` | Checks for the existence of a property | +| `removeKey(obj, key)` | `DynamicObject.RemoveKeyNode.execute(obj, key)` | Removes a property | +| `getPropertyFlagsOrDefault(obj, key, def)` | `DynamicObject.GetPropertyFlagsNode.execute(obj, key, def)` | Gets the flags of a property or a default value if absent | +| `setPropertyFlags(obj, key, flags)` | `DynamicObject.SetPropertyFlagsNode.execute(obj, key, flags)` | Updates the flags or a property | +| `getKeyArray(obj)` | `DynamicObject.GetKeyArrayNode.execute(obj)` | Returns an array of the keys of all the non-hidden properties | +| `getProperty(obj, key)` | `DynamicObject.GetPropertyNode.execute(obj, key)` | Gets the property descriptor or null if absent | +| `getPropertyArray(obj)` | `DynamicObject.GetPropertyArrayNode.execute(obj)` | Returns an array of the property descriptors of all the non-hidden properties | +| `getShapeFlags(obj)` | `DynamicObject.GetShapeFlagsNode.execute(obj)` | Gets the language-specific shape flags | +| `setShapeFlags(obj, flags)` | `DynamicObject.SetShapeFlagsNode.execute(obj, flags)` | Sets the language-specific shape flags | +| `getDynamicType(obj)` | `DynamicObject.GetDynamicTypeNode.execute(obj)` | Gets the language-specific dynamic type identifier | +| `setDynamicType(obj, type)` | `DynamicObject.SetDynamicTypeNode.execute(obj, type)` | Sets the language-specific dynamic type identifier | +| `updateShape(obj)` | `DynamicObject.UpdateShapeNode.execute(obj)` | Updates the shape if the object has an obsolete shape | +| `resetShape(obj, rootShape)` | `DynamicObject.ResetShapeNode.execute(obj, rootShape)` | Resets the object to an empty shape | +| `markShared(obj)` | `DynamicObject.MarkSharedNode.execute(obj)` | Marks the object as shared | +| `isShared(obj)` | `DynamicObject.IsSharedNode.execute(obj)` | Queries the object's shared state | +| `getShape(obj)` | `obj.getShape()` | No node equivalent, use direct API | + +Note: Unlike `DynamicObjectLibrary`, cached property keys are always compared by identity (`==`) rather than equality (`equals`). +If you rely on key equality, cache the key using an `equals` guard and pass the cached canonical key to the node. + +## Code examples + +### Reading a property + +#### Getting the value of a property with a fixed key or a dynamic key that is already unique or interned + +```java +abstract class GetUniqueKeyNode extends Node { + + abstract Object execute(DynamicObject receiver, Object key); + + @Specialization + static Object doCached(MyDynamicObject receiver, Symbol key, + @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, key, NULL_VALUE); + } +} +``` + +#### Getting the value of a property with a dynamic key and key equality + +```java +@ImportStatic(TruffleString.Encoding.class) +abstract class GetStringKeyNode extends Node { + + abstract Object execute(DynamicObject receiver, Object key); + + @Specialization(guards = "equalNode.execute(key, cachedKey, UTF_16)", limit = "3") + static Object doCached(MyDynamicObject receiver, TruffleString key, + @Cached("key") TruffleString cachedKey, + @Cached TruffleString.EqualNode equalNode, + @Shared @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, cachedKey, NULL_VALUE); + } + + @Specialization(replaces = "doCached") + static Object doGeneric(MyDynamicObject receiver, TruffleString key, + @Shared @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, key, NULL_VALUE); + } +} +``` + +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. + +#### Getting the value of a property with boxing elimination + +```java +abstract class GetUnboxedNode extends Node { + + abstract int executeInt(DynamicObject receiver, Object key) throws UnexpectedResultException; + + abstract Object execute(DynamicObject receiver, Object key); + + @Specialization(rewriteOn = UnexpectedResultException.class) + static int doInt(MyDynamicObject receiver, Symbol key, + @Shared @Cached DynamicObject.GetNode getNode) throws UnexpectedResultException { + return getNode.executeInt(receiver, key, NULL_VALUE); + } + + @Specialization(replaces = "doInt") + static Object doGeneric(MyDynamicObject receiver, Symbol key, + @Shared @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, key, NULL_VALUE); + } +} +``` + +### Writing a property + +#### Adding a property or setting the value of a property with a fixed key or a dynamic key that is already unique or interned + +```java +abstract class SetUniqueKeyNode extends Node { + + abstract void execute(DynamicObject receiver, Object key, Object value); + + @Specialization + static void doCached(MyDynamicObject receiver, Symbol key, Object value, + @Cached DynamicObject.PutNode putNode) { + putNode.execute(receiver, key, value); + } +} +``` + +#### Setting the value of a property with a dynamic key and key equality + +```java +@ImportStatic(TruffleString.Encoding.class) +abstract class SetStringKeyNode extends Node { + + abstract void execute(DynamicObject receiver, Object key, Object value); + + @Specialization(guards = "equalNode.execute(key, cachedKey, UTF_16)", limit = "3") + static void doCached(MyDynamicObject receiver, TruffleString key, Object value, + @Cached("key") TruffleString cachedKey, + @Cached TruffleString.EqualNode equalNode, + @Cached DynamicObject.PutNode putNode) { + putNode.execute(receiver, cachedKey, value); + } + + @Specialization(replaces = "doCached") + static void doGeneric(MyDynamicObject receiver, TruffleString key, + @Shared @Cached DynamicObject.PutNode getNode) { + putNode.execute(receiver, key, value); + } +} +``` + +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. diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index bf94ebad4216..96dbd138787f 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -687,6 +687,7 @@ "annotationProcessors" : ["TRUFFLE_DSL_PROCESSOR"], "checkstyle" : "com.oracle.truffle.api", "javaCompliance" : "17+", + "spotbugsIgnoresGenerated" : True, "workingSets" : "API,Truffle", "graalCompilerSourceEdition": "ignore", }, diff --git a/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/SuppressWarningTest.java b/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/SuppressWarningTest.java index cc0a5c273851..6ed181fd994c 100644 --- a/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/SuppressWarningTest.java +++ b/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/SuppressWarningTest.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. * * The Universal Permissive License (UPL), Version 1.0 @@ -47,7 +47,7 @@ public class SuppressWarningTest { @SuppressWarnings({"unused", "truffle-inlining"}) - public abstract static class DeprecationTestNode extends Node { + public abstract static class DeprecatedTestNode extends Node { abstract int execute(int v); @@ -81,4 +81,39 @@ static boolean deprecatedGuard(int v) { } + @SuppressWarnings({"unused", "truffle-inlining"}) + public abstract static class DeprecationTestNode extends Node { + + abstract int execute(int v); + + /* + * Suppress deprecation warning with "deprecation". + */ + @SuppressWarnings("deprecation") + @Specialization(guards = {"deprecatedGuard(v)", "v == 0"}) + int s0(int v) { + return v; + } + + /* + * Suppress deprecation warning with "all". + */ + @SuppressWarnings("all") + @Specialization(guards = {"deprecatedGuard(v)", "v == 1"}) + int s1(int v) { + return v; + } + + @Specialization + int s2(int v) { + return v; + } + + @Deprecated + static boolean deprecatedGuard(int v) { + return true; + } + + } + } 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..60e71488439a 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 @@ -44,16 +44,69 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; +import java.util.List; + import org.junit.Ignore; 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.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.test.AbstractLibraryTest; +@SuppressWarnings("deprecation") +@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.execute(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.execute(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)); } @@ -83,7 +136,7 @@ public void testTransition() { String key2 = "key2"; String val2 = "value2"; - DynamicObjectLibrary library = adopt(DynamicObjectLibrary.getFactory().create(o1)); + var library = adopt(com.oracle.truffle.api.object.DynamicObjectLibrary.getFactory().create(o1)); assertTrue(library.accepts(o1)); assertTrue(library.accepts(o2)); library.put(o1, key1, val1); @@ -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)); @@ -111,16 +164,16 @@ public void testMixedReceiverTypeSameShapeWithFallback() { String key2 = "key2"; String val2 = "value2"; - DynamicObjectLibrary library1 = adopt(DynamicObjectLibrary.getFactory().create(o1)); + var library1 = adopt(com.oracle.truffle.api.object.DynamicObjectLibrary.getFactory().create(o1)); library1.put(o1, key1, val1); library1.put(o2, key1, val1); - DynamicObjectLibrary library2 = adopt(DynamicObjectLibrary.getFactory().create(o1)); + var library2 = adopt(com.oracle.truffle.api.object.DynamicObjectLibrary.getFactory().create(o1)); library2.put(o1, key2, val2); library2.put(o2, key2, val2); 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/CachedGetNode.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedGetNode.java index 6491497bc887..7d3e7d2139cf 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedGetNode.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedGetNode.java @@ -45,15 +45,15 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; @GenerateInline(false) abstract class CachedGetNode extends Node { public abstract Object execute(DynamicObject obj, Object key); + @SuppressWarnings("deprecation") @Specialization(limit = "3") static Object read(DynamicObject obj, Object key, - @CachedLibrary("obj") DynamicObjectLibrary lib) { + @CachedLibrary("obj") com.oracle.truffle.api.object.DynamicObjectLibrary lib) { return lib.getOrDefault(obj, key, null); } } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedPutNode.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedPutNode.java index e1ec19bcfb07..3642bdb660d5 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedPutNode.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedPutNode.java @@ -45,15 +45,15 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; @GenerateInline(false) abstract class CachedPutNode extends Node { public abstract void execute(DynamicObject obj, Object key, Object value); + @SuppressWarnings("deprecation") @Specialization(limit = "3") static void write(DynamicObject obj, Object key, Object value, - @CachedLibrary("obj") DynamicObjectLibrary lib) { + @CachedLibrary("obj") com.oracle.truffle.api.object.DynamicObjectLibrary lib) { lib.put(obj, key, value); } } 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..fe7bb2010e6b 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 @@ -55,14 +55,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.Assert; + +import com.oracle.truffle.api.Assumption; 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 com.oracle.truffle.api.Assumption; public abstract class DOTestAsserts { @@ -73,8 +72,8 @@ public static T invokeGetter(String methodName, Object receiver) { @SuppressWarnings("unchecked") public static T invokeMethod(String methodName, Object receiver, Object... args) { try { - Method method = Stream.concat(Arrays.stream(receiver.getClass().getMethods()), Arrays.stream(receiver.getClass().getDeclaredMethods())).filter( - m -> m.getName().equals(methodName)).findFirst().orElseThrow( + Method method = allMethods(receiver.getClass()).filter( + m -> m.getName().equals(methodName) && m.getParameterCount() == args.length).findFirst().orElseThrow( () -> new NoSuchElementException("Method " + methodName + " not found in " + receiver.getClass())); method.setAccessible(true); return (T) method.invoke(receiver, args); @@ -83,6 +82,11 @@ public static T invokeMethod(String methodName, Object receiver, Object... a } } + private static Stream allMethods(Class instanceClass) { + var superclasses = Stream.> iterate(instanceClass, c -> c.getSuperclass() != null, Class::getSuperclass); + return superclasses.flatMap(superclass -> Arrays.stream(superclass.getDeclaredMethods())); + } + public static void assertLocationFields(Location location, int prims, int objects) { int primitiveFieldCount = invokeGetter("primitiveFieldCount", location); int objectFieldCount = invokeGetter("objectFieldCount", location); @@ -159,21 +163,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..4fdf12ccd2fc 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,11 +140,11 @@ 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, getAsInt(getNode, o1, k1, null)); assertEquals(v1, getNode.getOrDefault(o2, k1, null)); - assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + assertEquals(v1, getAsInt(getNode, o2, k1, null)); String v2 = "asdf"; uncachedSet(o1, k1, v2); @@ -157,11 +158,10 @@ public void testGet1() throws UnexpectedResultException { assertEquals(v2, e.getResult()); } assertEquals(v1, getNode.getOrDefault(o2, k1, null)); - assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + assertEquals(v1, getAsInt(getNode, 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); @@ -169,6 +169,17 @@ public void testGet1() throws UnexpectedResultException { assertEquals(404, getMissingKey.getIntOrDefault(o1, missingKey, 404)); } + private static int getAsInt(DynamicObjectLibraryWrapper getNode, DynamicObject o1, String missingKey, Object defaultValue) throws UnexpectedResultException { + try { + return getNode.getIntOrDefault(o1, missingKey, defaultValue); + } catch (UnexpectedResultException e) { + if (e.getResult() instanceof Integer intValue) { + return intValue; + } + throw e; + } + } + @Test public void testPut1() { DynamicObject o1 = createEmpty(); @@ -180,7 +191,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 +208,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 +235,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 +252,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 +279,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 +304,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 +312,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 +330,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 +355,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 +363,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 +372,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 +399,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 +434,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 +449,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 +460,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 +471,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 +491,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 +520,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 +539,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 +568,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 +593,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 +642,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 +658,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 +677,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 +703,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 +747,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 +778,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 +789,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 +819,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..7bc399599ce4 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -0,0 +1,1104 @@ +/* + * 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.PutConstantNode createPutConstantNode() { + return switch (run) { + case CACHED -> DynamicObject.PutConstantNode.create(); + case UNCACHED -> DynamicObject.PutConstantNode.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.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.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(@SuppressWarnings("unused") 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(@SuppressWarnings("unused") 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(@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(); + 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.execute(o1, k1, null)); + assertEquals(v1, getAsInt(getNode, o1, k1, null)); + assertEquals(v1, getNode.execute(o2, k1, null)); + assertEquals(v1, getAsInt(getNode, o2, k1, null)); + + String v2 = "asdf"; + uncachedSet(o1, k1, v2); + + getNode = createGetNode(); + assertEquals(v2, getNode.execute(o1, k1, null)); + try { + getNode.executeInt(o1, k1, null); + fail(); + } catch (UnexpectedResultException e) { + assertEquals(v2, e.getResult()); + } + assertEquals(v1, getNode.execute(o2, k1, null)); + assertEquals(v1, getAsInt(getNode, o2, k1, null)); + + String missingKey = "missing"; + var getMissingKey = createGetNode(); + assertEquals(null, getMissingKey.execute(o1, missingKey, null)); + assertEquals(404, getMissingKey.executeInt(o1, missingKey, 404)); + var getMissingKey2 = createGetNode(); + assertEquals(null, getMissingKey2.execute(o1, missingKey, null)); + assertEquals(404, getMissingKey2.executeInt(o1, missingKey, 404)); + } + + private static int getAsInt(DynamicObject.GetNode getNode, DynamicObject o1, String missingKey, Object defaultValue) throws UnexpectedResultException { + try { + return getNode.executeInt(o1, missingKey, defaultValue); + } catch (UnexpectedResultException e) { + if (e.getResult() instanceof Integer intValue) { + return intValue; + } + throw e; + } + } + + @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.execute(o1, key1, intval2); + assertEquals(intval2, uncachedGet(o1, key1)); + setNode.execute(o1, key1, intval1); + assertEquals(intval1, uncachedGet(o1, key1)); + setNode.execute(o2, key1, intval2); + assertEquals(intval2, uncachedGet(o2, key1)); + setNode.execute(o2, key1, intval1); + assertEquals(intval1, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + + String strval1 = "asdf"; + setNode.execute(o1, key1, strval1); + assertEquals(strval1, uncachedGet(o1, key1)); + + String key2 = "key2"; + String strval2 = "qwer"; + var setNode2 = createPutNode(); + setNode2.execute(o1, key2, strval2); + assertEquals(strval2, uncachedGet(o1, key2)); + setNode2.execute(o1, key2, intval1); + assertEquals(intval1, uncachedGet(o1, key2)); + + var setNode3 = createPutNode(); + setNode3.execute(o1, key2, strval1); + assertEquals(strval1, uncachedGet(o1, key2)); + // assertTrue(setNode3.accepts(o1)); + var setNode4 = createPutNode(); + setNode4.execute(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.executeIfPresent(o1, key1, intval2)); + assertEquals(intval2, uncachedGet(o1, key1)); + assertTrue(setNode.executeIfPresent(o1, key1, intval1)); + assertEquals(intval1, uncachedGet(o1, key1)); + assertTrue(setNode.executeIfPresent(o2, key1, intval2)); + assertEquals(intval2, uncachedGet(o2, key1)); + assertTrue(setNode.executeIfPresent(o2, key1, intval1)); + assertEquals(intval1, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + assertTrue(setNode.executeWithFlagsIfPresent(o2, key1, intval2, 0b0)); + assertEquals(intval2, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + + assertTrue(setNode.executeWithFlagsIfPresent(o2, key1, intval1, 0b11)); + assertEquals(intval1, uncachedGet(o2, key1)); + assertEquals(0b11, uncachedGetPropertyFlags(o2, key1, -1)); + assertTrue(setNode.executeWithFlagsIfPresent(o2, key1, intval2, 0b0)); + assertEquals(intval2, uncachedGet(o2, key1)); + assertEquals(0b0, uncachedGetPropertyFlags(o2, key1, -1)); + + String strval1 = "asdf"; + setNode.execute(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.executeIfPresent(o1, key2, strval2)); + assertFalse(setNode2.executeWithFlagsIfPresent(o1, key2, strval2, 0b11)); + assertFalse(containsKeyNode2.execute(o1, key2)); + assertEquals(null, uncachedGet(o1, key2)); + + setNode2.execute(o1, key2, strval2); + assertTrue(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); + assertEquals(strval2, uncachedGet(o1, key2)); + assertEquals(0, uncachedGetPropertyFlags(o1, key2, -1)); + setNode2.executeWithFlags(o2, key2, strval2, 0b11); + assertTrue(DynamicObject.ContainsKeyNode.getUncached().execute(o2, key2)); + assertEquals(strval2, uncachedGet(o2, key2)); + assertEquals(0b11, uncachedGetPropertyFlags(o2, key2, -1)); + + var setNode3 = createPutNode(); + assertTrue(setNode3.executeIfPresent(o1, key2, intval1)); + assertEquals(intval1, uncachedGet(o1, key2)); + } + + @Test + public void testPutIfAbsent() { + String key1 = "key1"; + String key2 = "key2"; + int intval1 = 42; + int intval2 = 43; + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + assertSame(o1.getShape(), o2.getShape()); + + var setNode = createPutNode(); + assertTrue(setNode.executeIfAbsent(o1, key1, intval1)); + assertTrue(setNode.executeWithFlagsIfAbsent(o2, key1, intval1, 0)); + assertSame(o1.getShape(), o2.getShape()); + assertEquals(intval1, uncachedGet(o1, key1)); + assertEquals(intval1, uncachedGet(o2, key1)); + assertEquals(0, uncachedGetPropertyFlags(o1, key1, -1)); + assertEquals(0, uncachedGetPropertyFlags(o2, key1, -1)); + + Shape shapeBefore = o1.getShape(); + assertFalse(setNode.executeIfAbsent(o1, key1, intval2)); + assertFalse(setNode.executeWithFlagsIfAbsent(o1, key1, intval2, 0b11)); + assertEquals(intval1, uncachedGet(o1, key1)); + Shape shapeAfter = o1.getShape(); + assertSame(shapeBefore, shapeAfter); + + assertTrue(setNode.executeWithFlagsIfAbsent(o1, key2, intval2, 0b11)); + assertTrue(setNode.executeIfAbsent(o2, key2, intval2)); + assertEquals(intval2, uncachedGet(o1, key2)); + assertEquals(intval2, uncachedGet(o2, key2)); + assertEquals(0b11, uncachedGetPropertyFlags(o1, key2, -1)); + assertEquals(0, uncachedGetPropertyFlags(o2, key2, -1)); + } + + @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.execute(o1, k1, v2); + assertEquals(v2, uncachedGet(o1, k1)); + assertEquals(0, uncachedGetProperty(o1, k1).getFlags()); + setNode1.execute(o1, k1, v1); + assertEquals(v1, uncachedGet(o1, k1)); + setNode1.execute(o2, k1, v2); + assertEquals(v2, uncachedGet(o2, k1)); + setNode1.execute(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.execute(o1, k1, v3); + assertEquals(v3, uncachedGet(o1, k1)); + + String k2 = "key2"; + String v4 = "qwer"; + var setNode2 = createPutNode(); + + setNode2.execute(o1, k2, v4); + assertEquals(v4, uncachedGet(o1, k2)); + setNode2.execute(o1, k2, v1); + assertEquals(v1, uncachedGet(o1, k2)); + + int f2 = 0x42; + var setNode3 = createPutNode(); + + setNode3.executeWithFlags(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.executeWithFlags(o1, k1, v2, flags); + assertEquals(v2, uncachedGet(o1, k1)); + assertEquals(flags, uncachedGetProperty(o1, k1).getFlags()); + setNode1.executeWithFlags(o1, k1, v1, flags); + assertEquals(v1, uncachedGet(o1, k1)); + setNode1.executeWithFlags(o2, k1, v2, flags); + assertEquals(v2, uncachedGet(o2, k1)); + setNode1.executeWithFlags(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.executeWithFlags(o1, k1, v3, flags); + assertEquals(v3, uncachedGet(o1, k1)); + + String k2 = "key2"; + String v4 = "qwer"; + var setNode2 = createPutNode(); + + setNode2.execute(o1, k2, v4); + assertEquals(v4, uncachedGet(o1, k2)); + setNode2.execute(o1, k2, v1); + assertEquals(v1, uncachedGet(o1, k2)); + + int f2 = 0x42; + var setNode3 = createPutNode(); + + setNode3.executeWithFlags(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.executeWithFlags(o1, "key1", 1, 1); + setNode.executeWithFlags(o1, "key2", 2, 2); + DynamicObject o2 = createEmpty(); + copyPropertiesNode.execute(o1, o2); + assertEquals(1, getNode.execute(o1, "key1", null)); + assertEquals(2, getNode.execute(o1, "key2", null)); + 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.execute(o4, key, 42); + setDynamicTypeNode.execute(o4, myType); + putNode.execute(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.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + 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.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + + DynamicObject o1 = createEmpty(); + setShapeFlagsNode.execute(o1, flags); + assertEquals(flags, getShapeFlagsNode.execute(o1)); + assertTrue(hasShapeFlags(getShapeFlagsNode, o1, flags)); + assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b10)); + assertFalse(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); + addShapeFlags(getShapeFlagsNode, setShapeFlagsNode, o1, 0b1); + assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); + assertEquals(flags | 0b1, 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"; + + 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.execute(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.execute(o1, k1, null)); + assertEquals(v2, getKey2.execute(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 putConstantNode = createPutConstantNode(); + var putNode = createPutNode(); + + putConstantNode.executeWithFlags(o1, k1, v1, 0); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(0, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + putConstantNode.executeWithFlags(o1, k1, v1, flags); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + putNode.execute(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 putConstantNode = createPutConstantNode(); + var putNode = createPutNode(); + + putConstantNode.executeWithFlags(o1, k1, v1, 0); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(0, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + putNode.executeWithFlags(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().execute(o1, "k13", false); + updateAllFlags(o2, 3); + updateAllFlags(o3, 3); + var getNode = createGetNode(); + assertEquals(1, getNode.execute(o3, "k13", null)); + } + + private void fillObjectWithProperties(DynamicObject obj, boolean b) { + DynamicObject.PutNode putNode = 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; + putNode.executeWithFlags(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().execute(obj, key, value); + } + + private static void uncachedPut(DynamicObject obj, Object key, Object value, int flags) { + DynamicObject.PutNode.getUncached().executeWithFlags(obj, key, value, flags); + } + + private static void uncachedSet(DynamicObject obj, Object key, Object value) { + DynamicObject.PutNode.getUncached().executeIfPresent(obj, key, value); + } + + private static Object uncachedGet(DynamicObject obj, Object key) { + return DynamicObject.GetNode.getUncached().execute(obj, key, null); + } + + private static Property uncachedGetProperty(DynamicObject obj, Object key) { + return DynamicObject.GetPropertyNode.getUncached().execute(obj, key); + } + + private static int uncachedGetPropertyFlags(DynamicObject obj, Object key, int defaultValue) { + return DynamicObject.GetPropertyFlagsNode.getUncached().execute(obj, key, defaultValue); + } + + 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.execute(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..daaf878c7628 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 org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.object.test.ObjectModelRegressionTest.TestDynamicObject; -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<>(); @@ -77,12 +84,12 @@ private static void testConcurrentReplaceProperty() throws Throwable { for (int i = 0; i < 2; i++) { int iFixed = i; futures.add(executorService.submit(() -> { - try (Context context = Context.newBuilder().engine(engine).build()) { + try (Context ctx = 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); + Object hostNullValue = ctx.asValue(null); + 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..78ca76109a0e 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 java.util.List; + import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class GR52036 { +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; + +@RunWith(Parameterized.class) +public class GR52036 extends ParametrizedDynamicObjectTest { - private static final DynamicObjectLibrary OBJLIB = DynamicObjectLibrary.getUncached(); + @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 @@ -82,22 +91,22 @@ protected ObjType2(Shape shape) { } Shape initialShape = Shape.newBuilder().build(); - try (Engine engine = Engine.create(); Context context = Context.newBuilder().engine(engine).build()) { + try (Engine engine = Engine.create(); Context ctx = Context.newBuilder().engine(engine).build()) { 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/MergedLibraryTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/MergedLibraryTest.java index bedb83d17029..2e1cc1477c59 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/MergedLibraryTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/MergedLibraryTest.java @@ -43,9 +43,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -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 com.oracle.truffle.api.interop.InteropLibrary; @@ -56,8 +53,11 @@ import com.oracle.truffle.api.library.CachedLibrary; 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.Shape; import com.oracle.truffle.api.test.AbstractLibraryTest; +@SuppressWarnings("deprecation") public class MergedLibraryTest extends AbstractLibraryTest { @ExportLibrary(InteropLibrary.class) @@ -82,25 +82,25 @@ final Object getMembers(boolean includeInternal) throws UnsupportedMessageExcept @ExportMessage(name = "isMemberReadable") @ExportMessage(name = "isMemberModifiable") final boolean isMemberReadable(String member, - @CachedLibrary("this") DynamicObjectLibrary lib) { + @CachedLibrary("this") com.oracle.truffle.api.object.DynamicObjectLibrary lib) { return lib.containsKey(this, member); } @ExportMessage final Object readMember(String member, - @CachedLibrary("this") DynamicObjectLibrary lib) { + @CachedLibrary("this") com.oracle.truffle.api.object.DynamicObjectLibrary lib) { return lib.getOrDefault(this, member, 42); } @ExportMessage final boolean isMemberInsertable(String member, - @CachedLibrary("this") DynamicObjectLibrary lib) { + @CachedLibrary("this") com.oracle.truffle.api.object.DynamicObjectLibrary lib) { return !isMemberReadable(member, lib); } @ExportMessage final void writeMember(String member, Object value, - @CachedLibrary("this") DynamicObjectLibrary lib) { + @CachedLibrary("this") com.oracle.truffle.api.object.DynamicObjectLibrary lib) { lib.put(this, member, value); } } 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..aef27fb6c841 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java @@ -0,0 +1,489 @@ +/* + * 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.Property; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.test.AbstractLibraryTest; + +@SuppressWarnings("deprecation") +@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(com.oracle.truffle.api.object.DynamicObjectLibrary.getFactory().create(receiver)).get()); + case UNCACHED_LIBRARY -> wrap(com.oracle.truffle.api.object.DynamicObjectLibrary.getFactory().getUncached(receiver)); + case DISPATCHED_CACHED_LIBRARY -> wrap(adoptNode(com.oracle.truffle.api.object.DynamicObjectLibrary.getFactory().createDispatched(2)).get()); + case DISPATCHED_UNCACHED_LIBRARY -> wrap(com.oracle.truffle.api.object.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(com.oracle.truffle.api.object.DynamicObjectLibrary.getUncached()); + case CACHED_NODES, UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; + }; + } + + protected abstract static class DynamicObjectLibraryWrapper { + + public abstract boolean accepts(Object receiver); + + public abstract Shape getShape(DynamicObject object); + + public abstract Object getOrDefault(DynamicObject object, Object key, Object defaultValue); + + public abstract int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException; + + public abstract double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException; + + public abstract long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException; + + 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(com.oracle.truffle.api.object.DynamicObjectLibrary library) { + return new DynamicObjectLibraryWrapper() { + + @Override + public boolean accepts(Object receiver) { + return library.accepts(receiver); + } + + @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 int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return library.getIntOrDefault(object, key, defaultValue); + } + + @Override + public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return library.getLongOrDefault(object, key, defaultValue); + } + + @Override + public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return library.getDoubleOrDefault(object, key, defaultValue); + } + + @Override + public void put(DynamicObject object, Object key, Object value) { + library.put(object, key, value); + } + + @Override + public void putInt(DynamicObject object, Object key, int value) { + library.putInt(object, key, value); + } + + @Override + public void putDouble(DynamicObject object, Object key, double value) { + library.putDouble(object, key, value); + } + + @Override + public void putLong(DynamicObject object, Object key, long value) { + library.putLong(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.PutConstantNode putConstantNode; + 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(); + putConstantNode = DynamicObject.PutConstantNode.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(@SuppressWarnings("unused") String uncached) { + getNode = DynamicObject.GetNode.getUncached(); + putNode = DynamicObject.PutNode.getUncached(); + putConstantNode = DynamicObject.PutConstantNode.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.execute(object, key, defaultValue); + } + + @Override + public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return getNode.executeInt(object, key, defaultValue); + } + + @Override + public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return getNode.executeLong(object, key, defaultValue); + } + + @Override + public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return getNode.executeDouble(object, key, defaultValue); + } + + @Override + public void put(DynamicObject object, Object key, Object value) { + putNode.execute(object, key, value); + } + + @Override + public boolean putIfPresent(DynamicObject object, Object key, Object value) { + return putNode.executeIfPresent(object, key, value); + } + + @Override + public void putWithFlags(DynamicObject object, Object key, Object value, int flags) { + putNode.executeWithFlags(object, key, value, flags); + } + + @Override + public void putConstant(DynamicObject object, Object key, Object value, int flags) { + putConstantNode.executeWithFlags(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..75387acfcc3a --- /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.execute(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.execute(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.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchNode.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchNode.java index 0b5a3219b340..ced28d0d0cf5 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchNode.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchNode.java @@ -45,9 +45,8 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -@SuppressWarnings("truffle") +@SuppressWarnings({"deprecation", "truffle-sharing", "truffle-neverdefault"}) public abstract class TestNestedDispatchNode extends Node { final Object key = "testKey"; @@ -57,8 +56,7 @@ public abstract class TestNestedDispatchNode extends Node { @Specialization(guards = {"obj == cachedObj"}, limit = "1") Object cached(DynamicObject obj, @Cached("obj") @SuppressWarnings("unused") DynamicObject cachedObj, - // @CachedLibrary({"obj", "key"}) DynamicObjectLibrary lib, - @CachedLibrary("obj") DynamicObjectLibrary lib, + @CachedLibrary("obj") com.oracle.truffle.api.object.DynamicObjectLibrary lib, @Cached(value = "cachedObj.getShape().getProperty(key) != null") boolean hasProperty, @Cached TestNestedDispatchNode nested) { Object value = lib.getOrDefault(obj, key, null); @@ -73,7 +71,7 @@ Object cached(DynamicObject obj, @Specialization(limit = "3") Object cached(DynamicObject obj, - @CachedLibrary("obj") DynamicObjectLibrary lib, + @CachedLibrary("obj") com.oracle.truffle.api.object.DynamicObjectLibrary lib, @Cached(value = "obj.getShape().getProperty(key) != null", uncached = "obj.getShape().getProperty(key) != null") boolean hasProperty, @Cached TestNestedDispatchNode nested) { Object value = lib.getOrDefault(obj, key, null); diff --git a/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest b/truffle/src/com.oracle.truffle.api.object/snapshot.sigtest index 68657066581b..8839993d8904 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,44 @@ 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 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 IsSharedNode +innr public abstract static MarkSharedNode +innr public abstract static PutConstantNode +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 EMPTY_INT_ARRAY,EMPTY_OBJECT_ARRAY,LOOKUP,SHAPE_CACHE_LIMIT,SHAPE_OFFSET,UNSAFE,extRef,extVal,shape + +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,7 +198,145 @@ 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 abstract double executeDouble(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) throws com.oracle.truffle.api.nodes.UnexpectedResultException +meth public abstract int executeInt(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) throws com.oracle.truffle.api.nodes.UnexpectedResultException +meth public abstract java.lang.Object execute(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public abstract long executeLong(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$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$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) +meth public final boolean executeIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean executeWithFlagsIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final boolean executeWithFlagsIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final void execute(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 static com.oracle.truffle.api.object.DynamicObject$PutConstantNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$PutConstantNode 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 executeIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean executeIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) +meth public final boolean executeWithFlagsIfAbsent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final boolean executeWithFlagsIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object,int) +meth public final void execute(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 static com.oracle.truffle.api.object.DynamicObject$PutNode create() +meth public static com.oracle.truffle.api.object.DynamicObject$PutNode getUncached() +supr com.oracle.truffle.api.nodes.Node + +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() +supr java.lang.Object +hcls ContainsKeyNodeGen,CopyPropertiesNodeGen,GetDynamicTypeNodeGen,GetKeyArrayNodeGen,GetNodeGen,GetPropertyArrayNodeGen,GetPropertyFlagsNodeGen,GetPropertyNodeGen,GetShapeFlagsNodeGen,IsSharedNodeGen,MarkSharedNodeGen,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") 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) meth public abstract boolean putIfPresent(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,java.lang.Object) @@ -187,7 +399,6 @@ meth public abstract void setInt(com.oracle.truffle.api.object.DynamicObject,int anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="22.2") CLSS public abstract com.oracle.truffle.api.object.Location -cons protected init() meth protected double getDouble(com.oracle.truffle.api.object.DynamicObject,boolean) throws com.oracle.truffle.api.nodes.UnexpectedResultException meth protected int getInt(com.oracle.truffle.api.object.DynamicObject,boolean) throws com.oracle.truffle.api.nodes.UnexpectedResultException meth protected long getLong(com.oracle.truffle.api.object.DynamicObject,boolean) throws com.oracle.truffle.api.nodes.UnexpectedResultException @@ -222,6 +433,7 @@ meth public void set(com.oracle.truffle.api.object.DynamicObject,java.lang.Objec meth public void set(com.oracle.truffle.api.object.DynamicObject,java.lang.Object,com.oracle.truffle.api.object.Shape,com.oracle.truffle.api.object.Shape) throws com.oracle.truffle.api.object.IncompatibleLocationException anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="22.2") supr java.lang.Object +hfds FINAL_ASSUMPTION_UPDATER,field,finalAssumption,index hcls LocationVisitor,UncheckedIncompatibleLocationException CLSS public abstract interface com.oracle.truffle.api.object.LongLocation @@ -440,8 +652,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/DebugCounters.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DebugCounters.java index 6d15e20d5f02..aed48b246824 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DebugCounters.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DebugCounters.java @@ -54,4 +54,10 @@ private DebugCounters() { static final DebugCounter propertyAssumptionsBlocked = DebugCounter.create("Property assumptions blocked"); static final DebugCounter transitionSingleEntriesCreated = DebugCounter.create("Transition single-entry maps created"); static final DebugCounter transitionMapsCreated = DebugCounter.create("Transition multi-entry maps created"); + + static final DebugCounter assumedFinalLocationAssumptionCount = DebugCounter.create("Final location assumptions allocated"); + static final DebugCounter assumedFinalLocationAssumptionInvalidationCount = DebugCounter.create("Final location assumptions invalidated"); + static final DebugCounter assumedTypeLocationAssumptionCount = DebugCounter.create("Typed location assumptions allocated"); + static final DebugCounter assumedTypeLocationAssumptionInvalidationCount = DebugCounter.create("Typed location assumptions invalidated"); + static final DebugCounter assumedTypeLocationAssumptionRenewCount = DebugCounter.create("Typed location assumptions renewed"); } 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..fade2ba3ab30 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,23 +48,44 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; +import java.util.Map; +import java.util.Objects; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.HostCompilerDirectives; +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.impl.AbstractAssumption; 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.profiles.InlinedConditionProfile; import sun.misc.Unsafe; /** * 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 - * shape caches to be shared across contexts. + * {@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 * and {@link Shape.Builder#layout(Class, Lookup) Shape.Builder.layout}. @@ -72,28 +93,75 @@ *

* Example: * - *

- * 
- * public class MyObject extends DynamicObject implements TruffleObject {
- *     public MyObject(Shape shape) {
- *         super(shape);
- *     }
- * }
+ * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region =
+ * "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObject"}
  *
- * Shape initialShape = Shape.newBuilder().layout(MyObject.class).build();
+ * 

Using DynamicObject nodes

* - * MyObject obj = new MyObject(initialShape); - *
- *
+ * 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. + * + *

+ * Note: Property keys are always compared using object identity ({@code ==}), never with {@code + * equals}, for efficiency reasons. If the node is not used with a fixed key, and some keys might be + * {@code equals} but not have the same identity ({@code ==}), you must either intern the keys + * first, or cache the key by equality using an inline cache and use the cached key with the + * DynamicObject node to ensure equal keys with different identity will use the same cache entry and + * not overflow the cache: + * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.GetWithKeyEquals"} + * + *

Usage examples:

+ * + *

Simple use of {@link GetNode} with a pre-interned symbol key.

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.GetSimple"} + * + *

Implementing InteropLibrary messages using DynamicObject access nodes:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.ReadMember"} + * + * Member name equality check omitted for brevity. + * + *

Adding extra dynamic fields to a DynamicObject subclass:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObjectWithFields"} * * @see DynamicObject#DynamicObject(Shape) - * @see DynamicObjectLibrary * @see Shape * @see Shape#newBuilder() + * @see DynamicObject.GetNode + * @see DynamicObject.ContainsKeyNode + * @see DynamicObject.PutNode + * @see DynamicObject.GetPropertyNode + * @see DynamicObject.GetPropertyFlagsNode + * @see DynamicObject.SetPropertyFlagsNode + * @see DynamicObject.CopyPropertiesNode + * @see DynamicObject.GetKeyArrayNode + * @see DynamicObject.GetPropertyArrayNode + * @see DynamicObject.RemoveKeyNode + * @see DynamicObject.PutConstantNode + * @see DynamicObject.GetDynamicTypeNode + * @see DynamicObject.SetDynamicTypeNode + * @see DynamicObject.GetShapeFlagsNode + * @see DynamicObject.SetShapeFlagsNode + * @see DynamicObject.UpdateShapeNode + * @see DynamicObject.ResetShapeNode + * @see DynamicObject.IsSharedNode + * @see DynamicObject.MarkSharedNode * @since 0.8 or earlier */ +@SuppressWarnings("deprecation") public abstract class DynamicObject implements TruffleObject { + static final Object[] EMPTY_OBJECT_ARRAY = {}; + static final int[] EMPTY_INT_ARRAY = {}; + private static final MethodHandles.Lookup LOOKUP = internalLookup(); /** @@ -113,9 +181,9 @@ public abstract class DynamicObject implements TruffleObject { private Shape shape; /** Object extension array. */ - @DynamicField private Object[] extRef; + private Object[] extRef = EMPTY_OBJECT_ARRAY; /** Primitive extension array. */ - @DynamicField private int[] extVal; + private int[] extVal = EMPTY_INT_ARRAY; /** * Constructor for {@link DynamicObject} subclasses. Initializes the object with the provided @@ -242,7 +310,7 @@ final Object[] getObjectStore() { } final void setObjectStore(Object[] newArray) { - extRef = newArray; + extRef = Objects.requireNonNull(newArray); } final int[] getPrimitiveStore() { @@ -250,7 +318,7 @@ final int[] getPrimitiveStore() { } final void setPrimitiveStore(int[] newArray) { - extVal = newArray; + extVal = Objects.requireNonNull(newArray); } static Class getDynamicFieldAnnotation() { @@ -261,6 +329,1858 @@ static Lookup internalLookup() { return LOOKUP; } + // NODES + + static final int SHAPE_CACHE_LIMIT = ObjectStorageOptions.CacheLimit; + + /** + * 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 #execute(DynamicObject, Object, Object) + * @see #executeInt(DynamicObject, Object, Object) + * @see #executeLong(DynamicObject, Object, Object) + * @see #executeDouble(DynamicObject, Object, Object) + * @since 25.1 + */ + @GeneratePackagePrivate + @GenerateCached(true) + @GenerateInline(false) + @GenerateUncached + @ImportStatic(DynamicObject.class) + public abstract static class GetNode extends Node { + + GetNode() { + } + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + *

Usage examples:

+ * + *

Simple use of {@link GetNode} with a pre-interned symbol key.

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.GetSimple"} + * + *

Simple use of {@link GetNode} with a string key cached by equality.

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.GetWithKeyEquals"} + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @param defaultValue value to be returned if the property does not exist + * @return the property's value if it exists, else {@code defaultValue}. + * @since 25.1 + */ + public abstract Object execute(DynamicObject receiver, Object key, Object defaultValue); + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @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} + * @see #execute(DynamicObject, Object, Object) + * @since 25.1 + */ + public abstract int executeInt(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @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} + * @see #execute(DynamicObject, Object, Object) + * @since 25.1 + */ + public abstract long executeLong(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; + + /** + * Gets the value of an existing property or returns the provided default value if no such + * property exists. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @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} + * @see #execute(DynamicObject, Object, Object) + * @since 25.1 + */ + public abstract double executeDouble(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.getLongInternal(receiver, cachedShape, 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) { + return cachedLocation.getIntInternal(receiver, cachedShape, 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.getDoubleInternal(receiver, cachedShape, 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) { + return cachedLocation.getInternal(receiver, cachedShape, guard); + } else { + return defaultValue; + } + } + + @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) + static long doGenericLong(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); + if (location != null) { + return location.getLongInternal(receiver, shape, 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 { + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); + if (location != null) { + return location.getIntInternal(receiver, shape, 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 { + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); + if (location != null) { + return location.getDoubleInternal(receiver, shape, 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) { + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); + if (location != null) { + return location.getInternal(receiver, shape, false); + } else { + return defaultValue; + } + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetNode create() { + return DynamicObjectFactory.GetNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetNode getUncached() { + return DynamicObjectFactory.GetNodeGen.getUncached(); + } + } + + /** + * 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 #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 PutConstantNode + * @since 25.1 + */ + @GeneratePackagePrivate + @ImportStatic(DynamicObject.class) + @GenerateUncached + @GenerateCached(true) + @GenerateInline(false) + public abstract static class PutNode extends Node { + + PutNode() { + } + + /** + * 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 #executeWithFlags} to set property flags as well. + * + *

Usage examples:

+ * + *

Simple use of {@link PutNode} with a pre-interned symbol key.

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.PutSimple"} + * + *

Simple use of {@link PutNode} with a string key cached by equality.

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.SetWithKeyEquals"} + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @param value the value to be set + * @see #executeIfPresent (DynamicObject, Object, Object) + * @see #executeWithFlags (DynamicObject, Object, Object, int) + */ + @HostCompilerDirectives.InliningRoot + public final void execute(DynamicObject receiver, Object key, Object value) { + executeImpl(receiver, key, value, 0, Flags.DEFAULT); + } + + /** + * 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 #execute(DynamicObject, Object, Object) + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeIfPresent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, 0, Flags.IF_PRESENT); + } + + /** + * 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 #execute(DynamicObject, Object, Object) + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, 0, Flags.IF_ABSENT); + } + + /** + * Like {@link #execute(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 #execute(DynamicObject, Object, Object) + */ + @HostCompilerDirectives.InliningRoot + public final void executeWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + executeImpl(receiver, key, value, propertyFlags, Flags.DEFAULT | Flags.UPDATE_FLAGS); + } + + /** + * Like {@link #executeIfPresent(DynamicObject, Object, Object)} but also sets property + * flags. + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_PRESENT | Flags.UPDATE_FLAGS); + } + + /** + * Like {@link #executeIfAbsent(DynamicObject, Object, Object)} but also sets property + * flags. + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_ABSENT | Flags.UPDATE_FLAGS); + } + + abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode); + + @SuppressWarnings("unused") + @Specialization(guards = { + "guard", + "key == cachedKey", + "propertyFlagsEqual(propertyFlags, mode, oldShape, newShape, oldProperty, newProperty)", + "newLocation == null || newLocation.canStoreValue(value)", + }, assumptions = "oldShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Bind("shape == oldShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("oldShape.getProperty(key)") Property oldProperty, + @Cached("getNewShape(key, value, propertyFlags, mode, oldProperty, oldShape)") Shape newShape, + @Cached("getNewProperty(oldShape, newShape, key, oldProperty)") Property newProperty, + @Cached("getLocation(newProperty)") Location newLocation, + @Cached("oldShape.getValidAbstractAssumption()") AbstractAssumption oldShapeValidAssumption) { + CompilerAsserts.partialEvaluationConstant(mode); + if ((mode & Flags.IF_ABSENT) != 0 && oldProperty != null) { + return false; + } + if ((mode & Flags.IF_PRESENT) != 0 && oldProperty == null) { + return false; + } + + newLocation.setInternal(receiver, value, guard, oldShape, newShape); + + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + maybeUpdateShape(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()", excludeForUncached = true) + static boolean doInvalid(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + @Specialization(replaces = {"doCached", "doInvalid"}) + static boolean doGeneric(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static PutNode create() { + return DynamicObjectFactory.PutNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static PutNode getUncached() { + return DynamicObjectFactory.PutNodeGen.getUncached(); + } + } + + /** + * 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 #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 + */ + @GeneratePackagePrivate + @ImportStatic(DynamicObject.class) + @GenerateUncached + @GenerateCached(true) + @GenerateInline(false) + public abstract static class PutConstantNode extends Node { + + PutConstantNode() { + } + + /** + * Same as {@link #executeWithFlags}, except the property is added with 0 flags, and if the + * property already exists, its flags will not be updated. + */ + @HostCompilerDirectives.InliningRoot + public final void execute(DynamicObject receiver, Object key, Object value) { + executeImpl(receiver, key, value, 0, Flags.DEFAULT | Flags.CONST); + } + + /** + * Like {@link #execute(DynamicObject, Object, Object)} but only if the property is present. + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeIfPresent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, 0, Flags.IF_PRESENT | Flags.CONST); + } + + /** + * Like {@link #execute(DynamicObject, Object, Object)} but only if the property is absent. + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, 0, Flags.IF_ABSENT | Flags.CONST); + } + + /** + * 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 file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant1"} + * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant2"} + * + * @param key property identifier + * @param value the constant value to be set + * @param propertyFlags property flags or 0 + * @see #execute (DynamicObject, Object, Object) + */ + @HostCompilerDirectives.InliningRoot + public final void executeWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + executeImpl(receiver, key, value, propertyFlags, Flags.DEFAULT | Flags.CONST | Flags.UPDATE_FLAGS); + } + + /** + * Like {@link #executeWithFlags(DynamicObject, Object, Object, int)} but only if the + * property is present. + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_PRESENT | Flags.CONST | Flags.UPDATE_FLAGS); + } + + /** + * Like {@link #executeWithFlags(DynamicObject, Object, Object, int)} but only if the + * property is absent. + */ + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_ABSENT | Flags.CONST | Flags.UPDATE_FLAGS); + } + + abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode); + + @SuppressWarnings("unused") + @Specialization(guards = { + "guard", + "key == cachedKey", + "propertyFlagsEqual(propertyFlags, mode, oldShape, newShape, oldProperty, newProperty)", + "newProperty == null || newProperty.getLocation().canStoreConstant(value)", + }, assumptions = "oldShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Bind("shape == oldShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("oldShape.getProperty(key)") Property oldProperty, + @Cached("getNewShape(key, value, propertyFlags, mode, oldProperty, oldShape)") Shape newShape, + @Cached("getNewProperty(oldShape, newShape, key, oldProperty)") Property newProperty, + @Cached("oldShape.getValidAbstractAssumption()") AbstractAssumption oldShapeValidAssumption) { + CompilerAsserts.partialEvaluationConstant(mode); + if ((mode & Flags.IF_ABSENT) != 0 && oldProperty != null) { + return false; + } + if ((mode & Flags.IF_PRESENT) != 0 && oldProperty == null) { + return false; + } + + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + maybeUpdateShape(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()", excludeForUncached = true) + static boolean doInvalid(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + @Specialization(replaces = {"doCached", "doInvalid"}) + static boolean doGeneric(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static PutConstantNode create() { + return DynamicObjectFactory.PutConstantNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static PutConstantNode getUncached() { + return DynamicObjectFactory.PutConstantNodeGen.getUncached(); + } + } + + /** + * {@return true if the cache entry can be used with the passed property flags and mode} + * + *
    + *
  1. ignore flags => new flags must equal old flags, if any, else passed flags
  2. + *
  3. update flags => new flags must equal passed flags
  4. + *
+ */ + static boolean propertyFlagsEqual(int propertyFlags, int mode, Shape oldShape, Shape newShape, Property oldProperty, Property newProperty) { + if (newProperty == null) { + assert oldProperty == null; + return (mode & Flags.IF_PRESENT) != 0; + } + return (mode & Flags.UPDATE_FLAGS) == 0 + ? oldShape == newShape || (oldProperty == null ? propertyFlags : oldProperty.getFlags()) == newProperty.getFlags() + : propertyFlags == newProperty.getFlags(); + } + + static Shape getNewShape(Object cachedKey, Object value, int newPropertyFlags, int mode, Property existingProperty, Shape oldShape) { + if (existingProperty == null) { + if ((mode & Flags.IF_PRESENT) != 0) { + return oldShape; + } else { + return oldShape.defineProperty(cachedKey, value, newPropertyFlags, mode); + } + } else if ((mode & Flags.IF_ABSENT) != 0) { + return oldShape; + } else if ((mode & Flags.UPDATE_FLAGS) != 0 && newPropertyFlags != existingProperty.getFlags()) { + return oldShape.defineProperty(cachedKey, value, newPropertyFlags, mode); + } + if (existingProperty.getLocation().canStore(value)) { + // set existing + return oldShape; + } else { + // generalize + Shape newShape = oldShape.defineProperty(oldShape, value, existingProperty.getFlags(), mode, existingProperty); + assert newShape != oldShape; + return newShape; + } + } + + static Property getNewProperty(Shape oldShape, Shape newShape, Object cachedKey, Property oldProperty) { + if (newShape == oldShape) { + return oldProperty; + } else { + return newShape.getProperty(cachedKey); + } + } + + static Location getLocation(Property property) { + return property == null ? null : property.getLocation(); + } + + /** + * 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() { + } + + /** + * 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 != to", "shape == cachedShape"}, limit = "SHAPE_CACHE_LIMIT") + static void doCached(DynamicObject from, DynamicObject to, + @Bind("from.getShape()") @SuppressWarnings("unused") Shape shape, + @Cached("shape") @SuppressWarnings("unused") Shape cachedShape, + @Cached(value = "createPropertyGetters(cachedShape)", dimensions = 1) PropertyGetter[] getters, + @Cached(value = "createPutNodes(getters)") PutNode[] putNodes) { + for (int i = 0; i < getters.length; i++) { + PropertyGetter getter = getters[i]; + putNodes[i].executeWithFlags(to, getter.getKey(), getter.get(from), getter.getFlags()); + } + } + + @TruffleBoundary + @Specialization(guards = {"from != to"}, replaces = "doCached") + static void doGeneric(DynamicObject from, DynamicObject to) { + Property[] properties = from.getShape().getPropertyArray(); + for (int i = 0; i < properties.length; i++) { + Property property = properties[i]; + Object value = property.get(from, false); + PutNode.getUncached().executeWithFlags(to, property.getKey(), value, property.getFlags()); + } + } + + @SuppressWarnings("unused") + @Specialization(guards = {"from == to"}) + static void doSameObject(DynamicObject from, DynamicObject to) { + // nothing to do + } + + 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; + } + + static PutNode[] createPutNodes(PropertyGetter[] getters) { + PutNode[] putNodes = new PutNode[getters.length]; + for (int i = 0; i < getters.length; i++) { + putNodes[i] = PutNode.create(); + } + return putNodes; + } + + /** + * @since 25.1 + */ + @NeverDefault + public static CopyPropertiesNode create() { + return DynamicObjectFactory.CopyPropertiesNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static CopyPropertiesNode getUncached() { + return DynamicObjectFactory.CopyPropertiesNodeGen.getUncached(); + } + } + + @TruffleBoundary + static boolean updateShape(DynamicObject object) { + return ObsolescenceStrategy.updateShape(object); + } + + static boolean updateShape(DynamicObject object, Shape currentShape) { + return ObsolescenceStrategy.updateShape(object, currentShape); + } + + /** + * 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() { + } + + /** + * Returns {@code true} if this object contains a property with the given key. + * + *

Usage example:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.ContainsKey"} + * + * Member name equality check omitted for brevity. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @return {@code true} if the object contains a property with this key, else {@code false} + */ + 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 doGeneric(DynamicObject receiver, Object key) { + Shape shape = receiver.getShape(); + return shape.hasProperty(key); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ContainsKeyNode create() { + return DynamicObjectFactory.ContainsKeyNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ContainsKeyNode getUncached() { + return DynamicObjectFactory.ContainsKeyNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * Removes the property with the given key from the object. + * + *

Usage example:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.RemoveKey"} + * + * Member name equality check omitted for brevity. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @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 { + removePropertyGeneric(receiver, oldShape, cachedProperty); + } + return true; + } + + @TruffleBoundary + @Specialization(replaces = "doCached") + static boolean doGeneric(DynamicObject receiver, Object key) { + Shape oldShape = receiver.getShape(); + Property existingProperty = oldShape.getProperty(key); + if (existingProperty == null) { + return false; + } + removePropertyGeneric(receiver, oldShape, existingProperty); + return true; + } + + @TruffleBoundary + static boolean removePropertyGeneric(DynamicObject receiver, Shape cachedShape, Property cachedProperty) { + updateShape(receiver, cachedShape); + 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); + } + } + + /** + * @since 25.1 + */ + @NeverDefault + public static RemoveKeyNode create() { + return DynamicObjectFactory.RemoveKeyNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static RemoveKeyNode getUncached() { + return DynamicObjectFactory.RemoveKeyNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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:

+ * + *

Implementing frozen object check in writeMember:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.WriteMember"} + * + * Member name equality check omitted for brevity. + * + * @return shape flags + * @see SetShapeFlagsNode + * @see Shape.Builder#shapeFlags(int) + * @see Shape#getFlags() + */ + 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 doGeneric(DynamicObject receiver) { + return receiver.getShape().getFlags(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetShapeFlagsNode create() { + return DynamicObjectFactory.GetShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetShapeFlagsNode getUncached() { + return DynamicObjectFactory.GetShapeFlagsNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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:

+ * + *

Implementing frozen object check in writeMember:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.SetShapeFlags"} + * + * @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 Shape.Builder#shapeFlags(int) + */ + public abstract boolean execute(DynamicObject receiver, int newFlags); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "flags == newShape.getFlags()"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("shapeSetFlags(cachedShape, flags)") Shape newShape) { + if (newShape != cachedShape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + @Specialization(replaces = "doCached") + static boolean doGeneric(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); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetShapeFlagsNode create() { + return DynamicObjectFactory.SetShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetShapeFlagsNode getUncached() { + return DynamicObjectFactory.SetShapeFlagsNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 doGeneric(DynamicObject receiver) { + return receiver.getShape().isShared(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static IsSharedNode create() { + return DynamicObjectFactory.IsSharedNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static IsSharedNode getUncached() { + return DynamicObjectFactory.IsSharedNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 doGeneric(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape) { + Shape newShape = shape.makeSharedShape(); + if (newShape != shape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + /** + * @since 25.1 + */ + @NeverDefault + public static MarkSharedNode create() { + return DynamicObjectFactory.MarkSharedNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static MarkSharedNode getUncached() { + return DynamicObjectFactory.MarkSharedNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 doGeneric(DynamicObject receiver) { + return receiver.getShape().getDynamicType(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetDynamicTypeNode create() { + return DynamicObjectFactory.GetDynamicTypeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetDynamicTypeNode getUncached() { + return DynamicObjectFactory.GetDynamicTypeNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 doGeneric(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; + } + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetDynamicTypeNode create() { + return DynamicObjectFactory.SetDynamicTypeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetDynamicTypeNode getUncached() { + return DynamicObjectFactory.SetDynamicTypeNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.GetPropertyEquivalent"} + * + *

Usage example:

+ * + *

Implementing read-only property check in writeMember:

+ * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.WriteMember"} + * + * Member name equality check omitted for brevity. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @param defaultValue value to return if no such property exists + * @return the property flags if the property exists, else {@code defaultValue} + * @see GetPropertyNode + */ + 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") + final int doGeneric(DynamicObject receiver, Object key, int defaultValue, + @Cached InlinedConditionProfile isPropertyNonNull) { + Property property = receiver.getShape().getProperty(key); + return isPropertyNonNull.profile(this, property != null) ? property.getFlags() : defaultValue; + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyFlagsNode create() { + return DynamicObjectFactory.GetPropertyFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyFlagsNode getUncached() { + return DynamicObjectFactory.GetPropertyFlagsNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * Sets the property flags associated with the requested property. + * + * @param key the property key, compared by identity ({@code ==}), not equality + * ({@code equals}). See {@link DynamicObject} for more information. + * @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 { + changePropertyFlagsGeneric(receiver, oldShape, cachedProperty, cachedPropertyFlags); + } + } + return true; + } + + @TruffleBoundary + @Specialization(replaces = "doCached") + static boolean doGeneric(DynamicObject receiver, Object key, int propertyFlags) { + Shape oldShape = receiver.getShape(); + Property existingProperty = oldShape.getProperty(key); + if (existingProperty == null) { + return false; + } + if (existingProperty.getFlags() != propertyFlags) { + changePropertyFlagsGeneric(receiver, oldShape, existingProperty, propertyFlags); + } + return true; + } + + @TruffleBoundary + private static void changePropertyFlagsGeneric(DynamicObject receiver, Shape cachedShape, Property cachedProperty, int propertyFlags) { + assert cachedProperty != null; + assert cachedProperty.getFlags() != propertyFlags; + + updateShape(receiver, cachedShape); + + 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, newShape); + } + } + + static Shape setPropertyFlags(Shape shape, Property cachedProperty, int propertyFlags) { + CompilerAsserts.neverPartOfCompilation(); + if (cachedProperty == null) { + return shape; + } + return shape.setPropertyFlags(cachedProperty, propertyFlags); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetPropertyFlagsNode create() { + return DynamicObjectFactory.SetPropertyFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static SetPropertyFlagsNode getUncached() { + return DynamicObjectFactory.SetPropertyFlagsNodeGen.getUncached(); + } + } + + 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, newShape); + } + } + + /** + * 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() { + } + + /** + * 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 doGenericValid(DynamicObject receiver, + @Bind("receiver.getShape()") Shape shape) { + return false; + } + + @Fallback + static boolean doInvalid(DynamicObject receiver) { + return updateShape(receiver); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static UpdateShapeNode create() { + return DynamicObjectFactory.UpdateShapeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static UpdateShapeNode getUncached() { + return DynamicObjectFactory.UpdateShapeNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 doGeneric(receiver, cachedOtherShape, cachedShape); + } + + @Specialization(replaces = "doCached") + static boolean doGeneric(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; + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ResetShapeNode create() { + return DynamicObjectFactory.ResetShapeNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static ResetShapeNode getUncached() { + return DynamicObjectFactory.ResetShapeNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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 = {"shape == cachedShape", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT") + static Property doCached(@SuppressWarnings("unused") DynamicObject receiver, @SuppressWarnings("unused") Object key, + @Bind("receiver.getShape()") @SuppressWarnings("unused") Shape shape, + @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 doGeneric(DynamicObject receiver, Object key) { + return receiver.getShape().getProperty(key); + } + + /** + * @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 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() { + } + + /** + * 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 file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.GetMembers"} + * + * @return a read-only array of the object's property keys. Do not modify. + */ + 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(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetKeyArrayNode create() { + return DynamicObjectFactory.GetKeyArrayNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetKeyArrayNode getUncached() { + return DynamicObjectFactory.GetKeyArrayNodeGen.getUncached(); + } + } + + /** + * 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() { + } + + /** + * 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. Do not modify. + * @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(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyArrayNode create() { + return DynamicObjectFactory.GetPropertyArrayNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static GetPropertyArrayNode getUncached() { + return DynamicObjectFactory.GetPropertyArrayNodeGen.getUncached(); + } + } + 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..3e504d19204f 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 @@ -95,7 +95,13 @@ * * * @since 20.2.0 + * @see DynamicObject + * @deprecated DynamicObjectLibrary has been replaced by more lightweight DynamicObject nodes. + * Consult the javadoc of the individual messages for the corresponding API replacement, + * or see {@link DynamicObject} for a list of new node-based APIs. + * {@link DynamicObjectLibrary} will be removed in a future release. */ +@Deprecated(since = "25.1") @GenerateLibrary(defaultExportLookupEnabled = false, dynamicDispatchEnabled = false, pushEncapsulatingNode = false) @GenerateLibrary.DefaultExport(DynamicObjectLibraryImpl.class) public abstract class DynamicObjectLibrary extends Library { @@ -156,6 +162,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#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract Object getOrDefault(DynamicObject object, Object key, Object defaultValue); @@ -169,6 +176,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#executeInt(DynamicObject, Object, Object) * @since 20.2.0 */ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -189,6 +197,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#executeDouble(DynamicObject, Object, Object) * @since 20.2.0 */ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -209,6 +218,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#executeLong(DynamicObject, Object, Object) * @since 20.2.0 */ public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -243,6 +253,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#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void put(DynamicObject object, Object key, Object value); @@ -251,6 +262,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * Int-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public void putInt(DynamicObject object, Object key, int value) { @@ -261,6 +273,7 @@ public void putInt(DynamicObject object, Object key, int value) { * Double-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public void putDouble(DynamicObject object, Object key, double value) { @@ -271,6 +284,7 @@ public void putDouble(DynamicObject object, Object key, double value) { * Long-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public void putLong(DynamicObject object, Object key, long value) { @@ -284,6 +298,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#executeIfPresent(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract boolean putIfPresent(DynamicObject object, Object key, Object value); @@ -297,6 +312,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#executeWithFlags(DynamicObject, Object, Object, int) * @since 20.2.0 */ public abstract void putWithFlags(DynamicObject object, Object key, Object value, int flags); @@ -336,6 +352,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.PutConstantNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void putConstant(DynamicObject object, Object key, Object value, int flags); @@ -345,6 +362,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 +383,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 +395,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 +414,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 +446,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 +475,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 +485,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 +507,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 +520,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 +535,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 +546,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 +562,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 +574,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 +632,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 +648,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..8f2280535eb5 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 @@ -390,14 +390,13 @@ static RemovePlan prepareRemove(Shape shapeBefore, Shape shapeAfter, Property re moves.add(move); } } + Location removedPropertyLoc = removedProperty.getLocation(); + if (!removedPropertyLoc.isValue()) { + // Use a no-op move to clear the location of the removed property. Must be first. + moves.add(new Move(removedPropertyLoc, null, removedPropertyLoc.getOrdinal(), Integer.MIN_VALUE)); + } if (canMoveInPlace) { - if (moves.isEmpty()) { - Location removedPropertyLoc = removedProperty.getLocation(); - if (!removedPropertyLoc.isPrimitive()) { - // Use a no-op move to clear the location of the removed property. - moves.add(new Move(removedPropertyLoc, removedPropertyLoc, 0, 0)); - } - } else if (!isSorted(moves)) { + if (!isSorted(moves)) { moves.sort(Move::compareTo); } } @@ -415,6 +414,10 @@ private static boolean isSorted(List moves) { return true; } + /** + * Moves the value from one location to another, and clears the from location. If both locations + * are the same, only clears the location. + */ private static final class Move implements Comparable { private final Location fromLoc; private final Location toLoc; @@ -424,18 +427,22 @@ private static final class Move implements Comparable { private static final Move[] EMPTY_ARRAY = new Move[0]; Move(Location fromLoc, Location toLoc, int fromOrd, int toOrd) { - assert (fromLoc == toLoc) == (fromOrd == toOrd); - this.fromLoc = fromLoc; + assert fromLoc != toLoc; + this.fromLoc = Objects.requireNonNull(fromLoc); this.toLoc = toLoc; this.fromOrd = fromOrd; this.toOrd = toOrd; } void perform(DynamicObject obj) { - if (fromLoc == toLoc) { - return; + if (toLoc != null) { + performSet(obj, performGet(obj)); } - performSet(obj, performGet(obj)); + /* + * It does not matter for correctness whether we clear after the get or set. Clearing + * after the set might be slightly preferable w.r.t. store ordering. + */ + clear(obj); } Object performGet(DynamicObject obj) { @@ -443,11 +450,28 @@ Object performGet(DynamicObject obj) { } void performSet(DynamicObject obj, Object value) { + if (toLoc == null) { + return; // clear only + } toLoc.setSafe(obj, value, false, true); } - void clear(DynamicObject obj) { - // clear location to avoid memory leak + Object performGetAndClear(DynamicObject obj) { + Object value = performGet(obj); + clear(obj); + return value; + } + + /* + * Clears the location to avoid potential memory leaks AND kills the from location identity. + * + * Note: It is important that we always set the "from" location in compiled code to kill the + * location identity at this point, so as to prevent reads from that location identity (of + * the old shape) to move below any write to the same memory location but with a different + * location identity (of the new shape), since that would be seen as independent and + * therefore not kill the "from" location that is being overwritten or cleared. (GR-59981) + */ + private void clear(DynamicObject obj) { fromLoc.clear(obj); } @@ -465,7 +489,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; @@ -498,25 +522,19 @@ void perform(DynamicObject object) { moves[i].perform(object); } - if (moves.length > 0) { - moves[0].clear(object); - } - DynamicObjectSupport.trimToSize(object, shapeBefore, shapeAfter); - object.setShape(shapeAfter); } else { // we cannot perform the moves in place, so stash away the values Object[] tempValues = new Object[moves.length]; for (int i = moves.length - 1; i >= 0; i--) { - tempValues[i] = moves[i].performGet(object); - moves[i].clear(object); + tempValues[i] = moves[i].performGetAndClear(object); } DynamicObjectSupport.resize(object, shapeBefore, shapeAfter); for (int i = moves.length - 1; i >= 0; i--) { moves[i].performSet(object, tempValues[i]); } - object.setShape(shapeAfter); } + object.setShape(shapeAfter); } @TruffleBoundary 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 new file mode 100644 index 000000000000..9b55470045a9 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSnippets.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 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; + +import java.lang.invoke.MethodHandles; + +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.ImportStatic; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +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.Node; +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.strings.TruffleString; + +/** + * Example code snippets for {@link DynamicObject}. + */ +@SuppressWarnings({"unused", "static-method", "truffle-inlining"}) +class DynamicObjectSnippets implements TruffleObject { + + static final Object NULL_VALUE = null; + + static class MyContext { + } + + static class Symbol { + } + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObject" + public class MyObject extends DynamicObject implements TruffleObject { + MyObject(Shape shape) { + super(shape); + } + + static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + } + + public abstract class MyTruffleLanguage extends TruffleLanguage { + final Shape initialShape = Shape.newBuilder().build(); + + public MyObject newObject() { + return new MyObject(initialShape); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObject" + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObjectWithFields" + public class MyObjectWithFields extends DynamicObject implements TruffleObject { + @DynamicField private Object extra1; + @DynamicField private Object extra2; + @DynamicField private long extraLong1; + @DynamicField private long extraLong2; + + MyObjectWithFields(Shape shape) { + super(shape); + } + + static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + } + + Shape initialShape = Shape.newBuilder().layout(MyObject.class, MyObject.LOOKUP).build(); + + MyObject obj = new MyObject(initialShape); + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObjectWithFields" + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetSimple" + abstract static class GetSimpleNode extends Node { + abstract Object execute(DynamicObject receiver, Object key); + + @Specialization + static Object doCached(MyDynamicObjectSubclass receiver, Symbol key, + @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, key, NULL_VALUE); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetSimple" + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutSimple" + abstract static class SetSimpleNode extends Node { + abstract Object execute(DynamicObject receiver, Object key, Object value); + + @Specialization + static Object doCached(MyDynamicObjectSubclass receiver, Symbol key, Object value, + @Cached DynamicObject.PutNode putNode) { + putNode.execute(receiver, key, value); + return value; + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutSimple" + + @GenerateCached(false) + @ImportStatic(TruffleString.Encoding.class) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetWithKeyEquals" + abstract static class GetStringKeyNode extends Node { + abstract Object execute(DynamicObject receiver, Object key); + + @Specialization(guards = "equalNode.execute(key, cachedKey, UTF_16)", limit = "3") + static Object doCached(MyDynamicObjectSubclass receiver, TruffleString key, + @Cached("key") TruffleString cachedKey, + @Cached TruffleString.EqualNode equalNode, + @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, cachedKey, NULL_VALUE); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetWithKeyEquals" + + @GenerateCached(false) + @ImportStatic(TruffleString.Encoding.class) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.SetWithKeyEquals" + abstract static class SetStringKeyNode extends Node { + abstract Object execute(DynamicObject receiver, Object key, Object value); + + @Specialization(guards = "equalNode.execute(key, cachedKey, UTF_16)", limit = "3") + static Object doCached(MyDynamicObjectSubclass receiver, TruffleString key, Object value, + @Cached("key") TruffleString cachedKey, + @Cached TruffleString.EqualNode equalNode, + @Cached DynamicObject.PutNode putNode) { + putNode.execute(receiver, cachedKey, value); + return value; + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.SetWithKeyEquals" + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.ReadMember" + @ExportLibrary(InteropLibrary.class) + static class MyDynamicObjectSubclass extends DynamicObject { + MyDynamicObjectSubclass(Shape shape) { + super(shape); + } + + @ExportMessage + Object readMember(String member, + @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { + Object result = getNode.execute(this, member, null); + if (result == null) { + throw UnknownIdentifierException.create(member); + } + return result; + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.ReadMember" + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.ContainsKey" + @ExportMessage(name = "isMemberReadable") + @ExportMessage(name = "isMemberRemovable") + boolean isMemberReadable(String member, + @Cached @Shared DynamicObject.ContainsKeyNode containsKeyNode) { + return containsKeyNode.execute(this, member); + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.ContainsKey" + + @ExportMessage + boolean hasMembers() { + return true; + } + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetMembers" + @ExportMessage + Object getMembers(boolean includeInternal, + @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; + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetMembers" + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.WriteMember" + static final int READ_ONLY = 1; + static final int MISSING = -1; + static final int FROZEN = 1; + + @ExportMessage + void writeMember(String member, Object value, + @Cached @Shared DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + @Cached @Shared DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode, + @Cached DynamicObject.PutNode putNode) throws UnknownIdentifierException, UnsupportedMessageException { + if ((getShapeFlagsNode.execute(this) & FROZEN) == FROZEN) { + throw UnsupportedMessageException.create(); + } + int flags = getPropertyFlagsNode.execute(this, member, MISSING); + if (flags == MISSING) { + throw UnknownIdentifierException.create(member); + } else if ((flags & READ_ONLY) == READ_ONLY) { + throw UnsupportedMessageException.create(); + } + putNode.execute(this, member, value); + } + + @ExportMessage + boolean isMemberModifiable(String member, + @Cached @Shared DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + @Cached @Shared DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode) { + if ((getShapeFlagsNode.execute(this) & FROZEN) == FROZEN) { + return false; + } + int flags = getPropertyFlagsNode.execute(this, member, MISSING); + return flags != MISSING && (flags & READ_ONLY) == 0; + } + + @ExportMessage + boolean isMemberInsertable(String member, + @Cached @Shared DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + @Cached @Shared DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode) { + if ((getShapeFlagsNode.execute(this) & FROZEN) == FROZEN) { + return false; + } + return getPropertyFlagsNode.execute(this, member, MISSING) == MISSING; + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.WriteMember" + + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.RemoveKey" + @ExportMessage + void removeMember(String member, + @Cached @Shared DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + @Cached DynamicObject.RemoveKeyNode removeKeyNode) throws UnknownIdentifierException, UnsupportedMessageException { + if ((getShapeFlagsNode.execute(this) & FROZEN) == FROZEN) { + throw UnsupportedMessageException.create(); + } + if (!removeKeyNode.execute(this, member)) { + throw UnknownIdentifierException.create(member); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.RemoveKey" + } + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetPropertyEquivalent" + abstract static class GetPropertyEquivalentNode extends Node { + abstract void execute(DynamicObject receiver, Object key); + + @Specialization + int doGeneric(MyDynamicObjectSubclass receiver, Symbol key, int defaultValue, + @Cached DynamicObject.GetPropertyNode getPropertyNode) { + Property property = getPropertyNode.execute(receiver, key); + return property != null ? property.getFlags() : defaultValue; + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.GetPropertyEquivalent" + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant1" + abstract static class DeclarePropertyNode extends Node { + abstract void execute(DynamicObject receiver, Object key); + + @Specialization + static void doCached(MyDynamicObjectSubclass receiver, Symbol key, + @Cached DynamicObject.PutConstantNode putConstantNode) { + // declare property + putConstantNode.execute(receiver, key, NULL_VALUE); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant1" + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant2" + abstract static class InitializePropertyNode extends Node { + abstract void execute(DynamicObject receiver, Object key, Object value); + + @Specialization + static void doCached(MyDynamicObjectSubclass receiver, Symbol key, Object value, + @Cached DynamicObject.PutNode putNode) { + // initialize property + putNode.execute(receiver, key, value); + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant2" + + @GenerateCached(false) + // @start region = "com.oracle.truffle.api.object.DynamicObjectSnippets.SetShapeFlags" + abstract static class FreezeObjectNode extends Node { + static final int FROZEN = 1; + + abstract void execute(DynamicObject receiver); + + @Specialization + static void freeze(DynamicObject receiver, + @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, + @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { + int oldFlags = getShapeFlagsNode.execute(receiver); + int newFlags = oldFlags | FROZEN; + if (newFlags != oldFlags) { + setShapeFlagsNode.execute(receiver, newFlags); + } + } + } + // @end region = "com.oracle.truffle.api.object.DynamicObjectSnippets.SetShapeFlags" + + @GenerateCached(false) + abstract static class GetUnboxedNode extends Node { + + abstract int executeInt(DynamicObject receiver, Object key) throws UnexpectedResultException; + + abstract Object execute(DynamicObject receiver, Object key); + + @Specialization(rewriteOn = UnexpectedResultException.class) + static int doInt(DynamicObject receiver, Symbol key, + @Shared @Cached DynamicObject.GetNode getNode) throws UnexpectedResultException { + return getNode.executeInt(receiver, key, NULL_VALUE); + } + + @Specialization(replaces = "doInt") + static Object doGeneric(DynamicObject receiver, Symbol key, + @Shared @Cached DynamicObject.GetNode getNode) { + return getNode.execute(receiver, key, NULL_VALUE); + } + } +} 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..24423e558350 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 @@ -45,6 +45,7 @@ import java.util.Map; import java.util.Objects; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; final class DynamicObjectSupport { @@ -53,7 +54,8 @@ private DynamicObjectSupport() { } static void setShapeWithStoreFence(DynamicObject object, Shape shape) { - if (shape.isShared()) { + // unconditional fence in the interpreter to avoid an extra shared shape branch + if (CompilerDirectives.inInterpreter() || shape.isShared()) { VarHandle.storeStoreFence(); } object.setShape(shape); @@ -77,6 +79,7 @@ static void grow(DynamicObject object, Shape thisShape, Shape otherShape) { } private static void growObjectStore(DynamicObject object, Shape thisShape, int sourceCapacity, int targetCapacity) { + assert targetCapacity != 0; Object[] newObjectStore = new Object[targetCapacity]; if (sourceCapacity != 0) { int sourceSize = thisShape.getObjectArraySize(); @@ -87,6 +90,7 @@ private static void growObjectStore(DynamicObject object, Shape thisShape, int s } private static void growPrimitiveStore(DynamicObject object, Shape thisShape, int sourceCapacity, int targetCapacity) { + assert targetCapacity != 0; int[] newPrimitiveArray = new int[targetCapacity]; if (sourceCapacity != 0) { int sourceSize = thisShape.getPrimitiveArraySize(); @@ -108,98 +112,102 @@ static void trimToSize(DynamicObject object, Shape thisShape, Shape otherShape) private static void resizeObjectStore(DynamicObject object, Shape oldShape, Shape newShape) { int destinationCapacity = newShape.getObjectArrayCapacity(); + int sourceCapacity = oldShape.getObjectArrayCapacity(); + if (sourceCapacity == destinationCapacity) { + return; + } + Object[] newObjectStore; if (destinationCapacity == 0) { - object.setObjectStore(null); + newObjectStore = DynamicObject.EMPTY_OBJECT_ARRAY; } else { - int sourceCapacity = oldShape.getObjectArrayCapacity(); - if (sourceCapacity != destinationCapacity) { - int sourceSize = oldShape.getObjectArraySize(); - Object[] newObjectStore = new Object[destinationCapacity]; - if (sourceSize != 0) { - Object[] oldObjectStore = object.getObjectStore(); - int destinationSize = newShape.getObjectArraySize(); - int length = Math.min(sourceSize, destinationSize); - UnsafeAccess.arrayCopy(oldObjectStore, newObjectStore, length); - } - object.setObjectStore(newObjectStore); + int sourceSize = oldShape.getObjectArraySize(); + newObjectStore = new Object[destinationCapacity]; + if (sourceSize != 0) { + Object[] oldObjectStore = object.getObjectStore(); + int destinationSize = newShape.getObjectArraySize(); + int length = Math.min(sourceSize, destinationSize); + UnsafeAccess.arrayCopy(oldObjectStore, newObjectStore, length); } } + object.setObjectStore(newObjectStore); } private static void resizePrimitiveStore(DynamicObject object, Shape oldShape, Shape newShape) { assert newShape.hasPrimitiveArray(); int destinationCapacity = newShape.getPrimitiveArrayCapacity(); + int sourceCapacity = oldShape.getPrimitiveArrayCapacity(); + if (sourceCapacity == destinationCapacity) { + return; + } + int[] newPrimitiveArray; if (destinationCapacity == 0) { - object.setPrimitiveStore(null); + newPrimitiveArray = DynamicObject.EMPTY_INT_ARRAY; } else { - int sourceCapacity = oldShape.getPrimitiveArrayCapacity(); - if (sourceCapacity != destinationCapacity) { - int sourceSize = oldShape.getPrimitiveArraySize(); - int[] newPrimitiveArray = new int[destinationCapacity]; - if (sourceSize != 0) { - int[] oldPrimitiveArray = object.getPrimitiveStore(); - int destinationSize = newShape.getPrimitiveArraySize(); - int length = Math.min(sourceSize, destinationSize); - UnsafeAccess.arrayCopy(oldPrimitiveArray, newPrimitiveArray, length); - } - object.setPrimitiveStore(newPrimitiveArray); + int sourceSize = oldShape.getPrimitiveArraySize(); + newPrimitiveArray = new int[destinationCapacity]; + if (sourceSize != 0) { + int[] oldPrimitiveArray = object.getPrimitiveStore(); + int destinationSize = newShape.getPrimitiveArraySize(); + int length = Math.min(sourceSize, destinationSize); + UnsafeAccess.arrayCopy(oldPrimitiveArray, newPrimitiveArray, length); } } + object.setPrimitiveStore(newPrimitiveArray); } private static void trimObjectStore(DynamicObject object, Shape thisShape, Shape newShape) { Object[] oldObjectStore = object.getObjectStore(); int destinationCapacity = newShape.getObjectArrayCapacity(); + int sourceCapacity = thisShape.getObjectArrayCapacity(); + if (sourceCapacity <= destinationCapacity) { + return; + } + Object[] newObjectStore; if (destinationCapacity == 0) { - if (oldObjectStore != null) { - object.setObjectStore(null); - } - // else nothing to do + newObjectStore = DynamicObject.EMPTY_OBJECT_ARRAY; } else { - int sourceCapacity = thisShape.getObjectArrayCapacity(); - if (sourceCapacity > destinationCapacity) { - Object[] newObjectStore = new Object[destinationCapacity]; - int destinationSize = newShape.getObjectArraySize(); - UnsafeAccess.arrayCopy(oldObjectStore, newObjectStore, destinationSize); - object.setObjectStore(newObjectStore); - } + newObjectStore = new Object[destinationCapacity]; + int destinationSize = newShape.getObjectArraySize(); + UnsafeAccess.arrayCopy(oldObjectStore, newObjectStore, destinationSize); } + object.setObjectStore(newObjectStore); } private static void trimPrimitiveStore(DynamicObject object, Shape thisShape, Shape newShape) { int[] oldPrimitiveStore = object.getPrimitiveStore(); int destinationCapacity = newShape.getPrimitiveArrayCapacity(); + int sourceCapacity = thisShape.getPrimitiveArrayCapacity(); + if (sourceCapacity <= destinationCapacity) { + return; + } + int[] newPrimitiveStore; if (destinationCapacity == 0) { - if (oldPrimitiveStore != null) { - object.setPrimitiveStore(null); - } - // else nothing to do + newPrimitiveStore = DynamicObject.EMPTY_INT_ARRAY; } else { - int sourceCapacity = thisShape.getPrimitiveArrayCapacity(); - if (sourceCapacity > destinationCapacity) { - int[] newPrimitiveStore = new int[destinationCapacity]; - int destinationSize = newShape.getPrimitiveArraySize(); - UnsafeAccess.arrayCopy(oldPrimitiveStore, newPrimitiveStore, destinationSize); - object.setPrimitiveStore(newPrimitiveStore); - } + newPrimitiveStore = new int[destinationCapacity]; + int destinationSize = newShape.getPrimitiveArraySize(); + UnsafeAccess.arrayCopy(oldPrimitiveStore, newPrimitiveStore, destinationSize); } + object.setPrimitiveStore(newPrimitiveStore); } + @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/ExtAllocator.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtAllocator.java index 314af4b0b2ce..cf880df6d036 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtAllocator.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtAllocator.java @@ -54,19 +54,11 @@ import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.impl.AbstractAssumption; -import com.oracle.truffle.api.object.ExtLocations.AbstractObjectLocation; -import com.oracle.truffle.api.object.ExtLocations.DoubleArrayLocation; -import com.oracle.truffle.api.object.ExtLocations.DoubleFieldLocation; import com.oracle.truffle.api.object.ExtLocations.DoubleLocation; import com.oracle.truffle.api.object.ExtLocations.InstanceLocation; -import com.oracle.truffle.api.object.ExtLocations.IntArrayLocation; -import com.oracle.truffle.api.object.ExtLocations.IntFieldLocation; import com.oracle.truffle.api.object.ExtLocations.IntLocation; -import com.oracle.truffle.api.object.ExtLocations.LongArrayLocation; -import com.oracle.truffle.api.object.ExtLocations.LongFieldLocation; import com.oracle.truffle.api.object.ExtLocations.LongLocation; -import com.oracle.truffle.api.object.ExtLocations.ObjectArrayLocation; -import com.oracle.truffle.api.object.ExtLocations.ObjectFieldLocation; +import com.oracle.truffle.api.object.ExtLocations.ObjectLocation; import com.oracle.truffle.api.object.ExtLocations.TypeAssumption; import sun.misc.Unsafe; @@ -94,13 +86,11 @@ protected Location moveLocation(Location oldLocation) { final boolean decorateFinal = false; if (oldLocation instanceof IntLocation) { return newIntLocation(decorateFinal, oldLocation, NO_VALUE); - } else if (oldLocation instanceof DoubleLocation) { - return newDoubleLocation(decorateFinal, ((DoubleLocation) oldLocation).isImplicitCastIntToDouble(), oldLocation, NO_VALUE); - } else if (oldLocation instanceof LongLocation) { - return newLongLocation(decorateFinal, ((LongLocation) oldLocation).isImplicitCastIntToLong(), oldLocation, NO_VALUE); - } else if (oldLocation instanceof ObjectFieldLocation) { - return newObjectLocation(decorateFinal, oldLocation, NO_VALUE); - } else if (oldLocation instanceof ObjectArrayLocation) { + } else if (oldLocation instanceof DoubleLocation doubleLocation) { + return newDoubleLocation(decorateFinal, doubleLocation.isImplicitCastIntToDouble(), oldLocation, NO_VALUE); + } else if (oldLocation instanceof LongLocation longLocation) { + return newLongLocation(decorateFinal, longLocation.isImplicitCastIntToLong(), oldLocation, NO_VALUE); + } else if (oldLocation instanceof ObjectLocation) { return newObjectLocation(decorateFinal, oldLocation, NO_VALUE); } assert oldLocation.isValue(); @@ -130,29 +120,29 @@ private Location newObjectArrayLocation(boolean decorateFinal, Location oldLocat return advance(location); } - private static ObjectFieldLocation newObjectFieldLocationWithAssumption(int index, FieldInfo fieldInfo, + private static ObjectLocation newObjectFieldLocationWithAssumption(int index, FieldInfo fieldInfo, TypeAssumption initialTypeAssumption, AbstractAssumption initialFinalAssumption) { - return new ObjectFieldLocation(index, fieldInfo, initialFinalAssumption, initialTypeAssumption); + return ObjectLocation.createObjectFieldLocation(index, fieldInfo, initialFinalAssumption, initialTypeAssumption); } - private static ObjectArrayLocation newObjectArrayLocationWithAssumption(int index, + private static ObjectLocation newObjectArrayLocationWithAssumption(int index, TypeAssumption initialTypeAssumption, AbstractAssumption initialFinalAssumption) { - return new ObjectArrayLocation(index, initialFinalAssumption, initialTypeAssumption); + return ObjectLocation.createObjectArrayLocation(index, initialFinalAssumption, initialTypeAssumption); } private static boolean allowTypeSpeculation(Location oldLocation, Object value) { - return (value != NO_VALUE && oldLocation == null) || (oldLocation instanceof AbstractObjectLocation); + return (value != NO_VALUE && oldLocation == null) || (oldLocation instanceof ObjectLocation); } private static TypeAssumption getTypeAssumption(Location oldLocation, Object value) { if (NewTypeSpeculation && allowTypeSpeculation(oldLocation, value)) { if (value != NO_VALUE && oldLocation == null) { - if (AbstractObjectLocation.LAZY_ASSUMPTION) { + if (ObjectLocation.LAZY_TYPE_ASSUMPTION) { return null; } - return AbstractObjectLocation.createTypeAssumptionFromValue(value); - } else if (oldLocation instanceof AbstractObjectLocation) { - return ((AbstractObjectLocation) oldLocation).getTypeAssumption(); + return ObjectLocation.createTypeAssumptionFromValue(value); + } else if (oldLocation instanceof ObjectLocation objectLocation) { + return objectLocation.getTypeAssumption(); } } return TypeAssumption.ANY; @@ -165,8 +155,8 @@ private static AbstractAssumption getFinalAssumption(Location oldLocation, boole return null; } return InstanceLocation.createFinalAssumption(); - } else if (oldLocation instanceof InstanceLocation) { - return ((InstanceLocation) oldLocation).getFinalAssumptionField(); + } else if (oldLocation instanceof InstanceLocation instanceLocation) { + return instanceLocation.getFinalAssumptionField(); } } return (AbstractAssumption) Assumption.NEVER_VALID; @@ -185,14 +175,14 @@ private Location newIntLocation(boolean decorateFinal, Location oldLocation, Obj if (fieldIndex >= 0) { FieldInfo fieldInfo = l.getPrimitiveField(fieldIndex); AbstractAssumption initialFinalAssumption = getFinalAssumption(oldLocation, decorateFinal); - Location location = new IntFieldLocation(fieldIndex, fieldInfo, initialFinalAssumption); + Location location = IntLocation.createIntFieldLocation(fieldIndex, fieldInfo, initialFinalAssumption); return advance(location); } } if (l.hasPrimitiveExtensionArray()) { int index = alignArrayIndex(primitiveArraySize, INT_ARRAY_SLOT_SIZE); AbstractAssumption initialFinalAssumption = getFinalAssumption(oldLocation, decorateFinal); - Location location = new IntArrayLocation(index, initialFinalAssumption); + Location location = IntLocation.createIntArrayLocation(index, initialFinalAssumption); return advance(location); } } @@ -207,14 +197,14 @@ private Location newDoubleLocation(boolean decorateFinal, boolean allowIntToDoub if (fieldIndex >= 0) { FieldInfo fieldInfo = l.getPrimitiveField(fieldIndex); AbstractAssumption initialFinalAssumption = getFinalAssumption(oldLocation, decorateFinal); - Location location = new DoubleFieldLocation(fieldIndex, fieldInfo, allowIntToDouble, initialFinalAssumption); + Location location = DoubleLocation.createDoubleFieldLocation(fieldIndex, fieldInfo, allowIntToDouble, initialFinalAssumption); return advance(location); } } if (l.hasPrimitiveExtensionArray()) { int index = alignArrayIndex(primitiveArraySize, DOUBLE_ARRAY_SLOT_SIZE); AbstractAssumption initialFinalAssumption = getFinalAssumption(oldLocation, decorateFinal); - Location location = new DoubleArrayLocation(index, allowIntToDouble, initialFinalAssumption); + Location location = DoubleLocation.createDoubleArrayLocation(index, allowIntToDouble, initialFinalAssumption); return advance(location); } } @@ -247,14 +237,14 @@ private Location newLongLocation(boolean decorateFinal, boolean allowIntToLong, if (fieldIndex >= 0) { FieldInfo fieldInfo = l.getPrimitiveField(fieldIndex); AbstractAssumption initialFinalAssumption = getFinalAssumption(oldLocation, decorateFinal); - Location location = new LongFieldLocation(fieldIndex, fieldInfo, allowIntToLong, initialFinalAssumption); + Location location = LongLocation.createLongFieldLocation(fieldIndex, fieldInfo, allowIntToLong, initialFinalAssumption); return advance(location); } } if (l.hasPrimitiveExtensionArray()) { int index = alignArrayIndex(primitiveArraySize, LONG_ARRAY_SLOT_SIZE); AbstractAssumption initialFinalAssumption = getFinalAssumption(oldLocation, decorateFinal); - Location location = new LongArrayLocation(index, allowIntToLong, initialFinalAssumption); + Location location = LongLocation.createLongArrayLocation(index, allowIntToLong, initialFinalAssumption); return advance(location); } } @@ -270,7 +260,7 @@ protected Location locationForValueUpcast(Object value, Location oldLocation, in } // Object-typed locations should be able to store all values and therefore not reach here. - assert !(oldLocation instanceof AbstractObjectLocation) : oldLocation; + assert !(oldLocation instanceof ObjectLocation) : oldLocation; final boolean decorateFinal = false; Location newLocation = null; // if shape is shared, transition to an untyped location directly diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtLocations.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtLocations.java index 7f8491d03689..311a324fd7e4 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtLocations.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ExtLocations.java @@ -50,10 +50,7 @@ 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.Truffle; - import com.oracle.truffle.api.impl.AbstractAssumption; -import sun.misc.Unsafe; /** * Property location. @@ -64,100 +61,18 @@ */ @SuppressWarnings({"deprecation", "cast"}) abstract class ExtLocations { - static final int INT_FIELD_SLOT_SIZE = 1; static final int INT_ARRAY_SLOT_SIZE = 1; static final int DOUBLE_ARRAY_SLOT_SIZE = 2; static final int LONG_ARRAY_SLOT_SIZE = 2; static final int OBJECT_SLOT_SIZE = 1; static final int MAX_DYNAMIC_FIELDS = 1000; - sealed interface TypedLocation { - Class getType(); - } - - sealed interface ObjectLocation extends TypedLocation { - @Override - Class getType(); - - /** If {@code true}, this location does not accept {@code null} values. */ - boolean isNonNull(); - } - - sealed interface IntLocation extends TypedLocation, com.oracle.truffle.api.object.IntLocation { - @Override - int getInt(DynamicObject store, boolean guard); - - @Override - default int getInt(DynamicObject store, Shape shape) { - return getInt(store, store.getShape() == shape); - } - - void setInt(DynamicObject store, int value, boolean guard, boolean init); - - @Override - default Class getType() { - return int.class; - } - - @Override - default void setInt(DynamicObject store, int value, Shape shape) { - setInt(store, value, store.getShape() == shape, false); - } - } - - sealed interface LongLocation extends TypedLocation, com.oracle.truffle.api.object.LongLocation { - @Override - long getLong(DynamicObject store, boolean guard); - - @Override - default long getLong(DynamicObject store, Shape shape) { - return getLong(store, store.getShape() == shape); - } - - void setLong(DynamicObject store, long value, boolean guard, boolean init); - - @Override - default Class getType() { - return long.class; - } - - boolean isImplicitCastIntToLong(); - - @Override - default void setLong(DynamicObject store, long value, Shape shape) { - setLong(store, value, store.getShape() == shape, false); - } - } - - sealed interface DoubleLocation extends TypedLocation, com.oracle.truffle.api.object.DoubleLocation { - @Override - double getDouble(DynamicObject store, boolean guard); - - @Override - default double getDouble(DynamicObject store, Shape shape) { - return getDouble(store, store.getShape() == shape); - } - - void setDouble(DynamicObject store, double value, boolean guard, boolean init); - - @Override - default Class getType() { - return double.class; - } - - boolean isImplicitCastIntToDouble(); - - @Override - default void setDouble(DynamicObject store, double value, Shape shape) { - setDouble(store, value, store.getShape() == shape, false); - } - } - abstract static sealed class ValueLocation extends Location { - private final Object value; + final Object value; ValueLocation(Object value) { + super(); assert !(value instanceof Location); this.value = value; } @@ -197,6 +112,10 @@ public final void set(DynamicObject store, Object value, boolean guard, boolean } } + @Override + void clear(DynamicObject store) { + } + @Override public String toString() { return "=" + value; @@ -211,11 +130,6 @@ public final boolean isValue() { return true; } - @Override - protected int getOrdinal() { - throw CompilerDirectives.shouldNotReachHere(getClass().getName()); - } - /** * Boxed values need to be compared by value not by reference. * @@ -232,16 +146,6 @@ static boolean valueEquals(Object val1, Object val2) { private static boolean equalsBoundary(Object val1, Object val2) { return val1.equals(val2); } - - @Override - public int objectFieldCount() { - return 0; - } - - @Override - public int primitiveFieldCount() { - return 0; - } } static final class ConstantLocation extends ValueLocation { @@ -249,183 +153,134 @@ static final class ConstantLocation extends ValueLocation { ConstantLocation(Object value) { super(value); } - - @Override - public boolean isConstant() { - return true; - } } + /** + * Array or field location. + */ abstract static sealed class InstanceLocation extends Location { - protected final int index; - - @CompilationFinal protected volatile AbstractAssumption finalAssumption; - - private static final AtomicReferenceFieldUpdater FINAL_ASSUMPTION_UPDATER = AtomicReferenceFieldUpdater.newUpdater( - InstanceLocation.class, AbstractAssumption.class, "finalAssumption"); - static final boolean LAZY_FINAL_ASSUMPTION = true; - private static final DebugCounter assumedFinalLocationAssumptionCount = DebugCounter.create("Final location assumptions allocated"); - private static final DebugCounter assumedFinalLocationAssumptionInvalidationCount = DebugCounter.create("Final location assumptions invalidated"); - protected InstanceLocation(int index, AbstractAssumption finalAssumption) { - this.index = index; - this.finalAssumption = finalAssumption; + protected InstanceLocation(int index, FieldInfo field, AbstractAssumption finalAssumption) { + super(index, field, finalAssumption); } - final int getIndex() { - return index; + @Override + public String toString() { + return super.toString() + (isArrayLocation() ? ("[" + getIndex() + "]") : ("@" + getIndex())) + ("[final=" + isAssumedFinal() + "]"); } + } - final AbstractAssumption getFinalAssumptionField() { - return finalAssumption; - } + /** + * Object array or field location with assumption-based type speculation. + */ + static final class ObjectLocation extends InstanceLocation { - static AbstractAssumption createFinalAssumption() { - assumedFinalLocationAssumptionCount.inc(); - return (AbstractAssumption) Truffle.getRuntime().createAssumption("final location"); - } + @CompilationFinal volatile TypeAssumption typeAssumption; - protected final void maybeInvalidateFinalAssumption() { - AbstractAssumption assumption = getFinalAssumptionField(); - if (assumption == null || assumption.isValid()) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - invalidateFinalAssumption(assumption); - } - } + private static final AtomicReferenceFieldUpdater TYPE_ASSUMPTION_UPDATER = AtomicReferenceFieldUpdater.newUpdater( + ObjectLocation.class, TypeAssumption.class, "typeAssumption"); - @SuppressWarnings("unchecked") - private void invalidateFinalAssumption(AbstractAssumption lastAssumption) { - CompilerAsserts.neverPartOfCompilation(); - AtomicReferenceFieldUpdater updater = FINAL_ASSUMPTION_UPDATER; - assumedFinalLocationAssumptionInvalidationCount.inc(); - AbstractAssumption assumption = lastAssumption; - if (assumption == null) { - while (!updater.compareAndSet(this, assumption, (AbstractAssumption) Assumption.NEVER_VALID)) { - assumption = updater.get(this); - if (assumption == Assumption.NEVER_VALID) { - break; - } - assumption.invalidate(); - } - } else if (assumption.isValid()) { - assumption.invalidate(); - updater.set(this, (AbstractAssumption) Assumption.NEVER_VALID); - } - } + static final boolean LAZY_TYPE_ASSUMPTION = false; - /** - * Needs to be implemented so that {@link Location#getFinalAssumption()} is overridden. + /* + * @param typeAssumption initial value of type assumption field */ - @Override - public final AbstractAssumption getFinalAssumption() { - AbstractAssumption assumption = getFinalAssumptionField(); - if (assumption != null) { - return assumption; - } - CompilerDirectives.transferToInterpreterAndInvalidate(); - return initializeFinalAssumption(); + private ObjectLocation(int index, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { + super(index, null, finalAssumption); + this.typeAssumption = typeAssumption; } - @SuppressWarnings("unchecked") - private AbstractAssumption initializeFinalAssumption() { - CompilerAsserts.neverPartOfCompilation(); - AtomicReferenceFieldUpdater updater = FINAL_ASSUMPTION_UPDATER; - AbstractAssumption newAssumption = createFinalAssumption(); - if (updater.compareAndSet(this, null, newAssumption)) { - return newAssumption; - } else { - // if CAS failed, assumption is already initialized; cannot be null after that. - return Objects.requireNonNull(updater.get(this)); - } + /* + * @param typeAssumption initial value of type assumption field + */ + private ObjectLocation(int index, FieldInfo field, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { + super(index, Objects.requireNonNull(field), finalAssumption); + this.typeAssumption = typeAssumption; } - /** - * Needs to be implemented so that {@link Location#isAssumedFinal()} is overridden. - */ - @Override - public final boolean isAssumedFinal() { - AbstractAssumption assumption = getFinalAssumptionField(); - return assumption == null || assumption.isValid(); + static ObjectLocation createObjectArrayLocation(int index, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { + return new ObjectLocation(index, finalAssumption, typeAssumption); } - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + index; - return result; + static ObjectLocation createObjectFieldLocation(int index, FieldInfo field, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { + return new ObjectLocation(index, field, finalAssumption, typeAssumption); } @Override - public boolean equals(Object obj) { - if (!super.equals(obj)) { - return false; + public Object get(DynamicObject store, boolean guard) { + Object value; + if (field == null) { + value = getObjectArrayInternal(store, guard); + } else { + value = getObjectFieldInternal(store, guard); } - InstanceLocation other = (InstanceLocation) obj; - return index == other.index; + return CompilerDirectives.inInterpreter() ? value : assumedTypeCast(value, guard); } - @Override - public String toString() { - return super.toString() + (this instanceof ArrayLocation ? ("[" + index + "]") : ("@" + index)) + ("[final=" + isAssumedFinal() + "]"); + private Object getObjectArrayInternal(DynamicObject store, boolean guard) { + return UnsafeAccess.unsafeGetObject(getObjectArray(store, guard), getObjectArrayOffset(), guard, this); } - @Override - protected final int getOrdinal() { - boolean isPrimitive = this instanceof AbstractPrimitiveFieldLocation || this instanceof AbstractPrimitiveArrayLocation; - int ordinal = (isPrimitive ? -Integer.MAX_VALUE : 0) + getIndex(); - if (this instanceof ArrayLocation) { - ordinal += MAX_DYNAMIC_FIELDS; + private Object getObjectFieldInternal(DynamicObject store, boolean guard) { + if (UseVarHandle) { + return field.varHandle().get(store); } - return ordinal; + field.receiverCheck(store); + return UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); } - } - - sealed interface ArrayLocation { - } - - sealed interface FieldLocation { - } - abstract static sealed class AbstractObjectLocation extends InstanceLocation implements ObjectLocation { - - @CompilationFinal protected volatile TypeAssumption typeAssumption; - - private static final AtomicReferenceFieldUpdater TYPE_ASSUMPTION_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AbstractObjectLocation.class, - TypeAssumption.class, "typeAssumption"); + @Override + protected void set(DynamicObject store, Object value, boolean guard, boolean init) { + if (!init) { + maybeInvalidateFinalAssumption(); + } + maybeInvalidateTypeAssumption(value); + if (field == null) { + setObjectArrayInternal(store, value, guard); + } else { + setObjectFieldInternal(store, value); + } + } - static final boolean LAZY_ASSUMPTION = false; - private static final DebugCounter assumedTypeLocationAssumptionCount = DebugCounter.create("Typed location assumptions allocated"); - private static final DebugCounter assumedTypeLocationAssumptionInvalidationCount = DebugCounter.create("Typed location assumptions invalidated"); - private static final DebugCounter assumedTypeLocationAssumptionRenewCount = DebugCounter.create("Typed location assumptions renewed"); + private void setObjectArrayInternal(DynamicObject store, Object value, boolean guard) { + UnsafeAccess.unsafePutObject(getObjectArray(store, guard), getObjectArrayOffset(), value, this); + } - AbstractObjectLocation(int index, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { - super(index, finalAssumption); - this.typeAssumption = typeAssumption; + private void setObjectFieldInternal(DynamicObject store, Object value) { + if (UseVarHandle) { + field.varHandle().set(store, value); + return; + } + field.receiverCheck(store); + UnsafeAccess.unsafePutObject(store, getFieldOffset(), value, this); } - public final TypeAssumption getTypeAssumption() { - return typeAssumption; + @Override + protected void clear(DynamicObject store) { + if (field == null) { + setObjectArrayInternal(store, null, false); + } else { + setObjectFieldInternal(store, null); + } } @Override - public final boolean canStore(Object value) { + public boolean canStore(Object value) { return true; } @Override - public final Class getType() { + Class getType() { return Object.class; } - @Override - public final boolean isNonNull() { - return false; + TypeAssumption getTypeAssumption() { + return typeAssumption; } - protected final boolean canStoreInternal(Object value) { + boolean canStoreInternal(Object value) { TypeAssumption curr = getTypeAssumption(); if (curr == TypeAssumption.ANY) { return true; @@ -434,25 +289,25 @@ protected final boolean canStoreInternal(Object value) { return !curr.nonNull; } Class type = curr.type; - return type == Object.class || type.isInstance(value); + return type == Object.class || value.getClass() == type || type.isInstance(value); } else { return false; } } - protected final Object assumedTypeCast(Object value, boolean condition) { + Object assumedTypeCast(Object value, boolean condition) { assert CompilerDirectives.inCompiledCode(); TypeAssumption curr = getTypeAssumption(); if (curr != null && curr != TypeAssumption.ANY && curr.getAssumption().isValid()) { Class type = curr.type; boolean nonNull = curr.nonNull; - return UnsafeAccess.unsafeCast(value, type, condition, nonNull); + return UnsafeAccess.unsafeCast(value, type, condition, nonNull, false); } else { return value; } } - protected final Class getAssumedType() { + Class getAssumedType() { TypeAssumption curr = getTypeAssumption(); if (curr != null && curr.getAssumption().isValid()) { return curr.type; @@ -461,7 +316,7 @@ protected final Class getAssumedType() { } } - protected final boolean isAssumedNonNull() { + boolean isAssumedNonNull() { TypeAssumption curr = getTypeAssumption(); if (curr != null && curr.getAssumption().isValid()) { return curr.nonNull; @@ -474,8 +329,8 @@ static TypeAssumption createTypeAssumption(Class type, boolean if (type == Object.class && !nonNull) { return TypeAssumption.ANY; } - assumedTypeLocationAssumptionCount.inc(); - return new TypeAssumption((AbstractAssumption) Truffle.getRuntime().createAssumption("typed object location"), type, nonNull); + DebugCounters.assumedTypeLocationAssumptionCount.inc(); + return new TypeAssumption((AbstractAssumption) Assumption.create("typed object location"), type, nonNull); } static TypeAssumption createTypeAssumptionFromValue(Object value) { @@ -484,7 +339,7 @@ static TypeAssumption createTypeAssumptionFromValue(Object value) { return createTypeAssumption(type, nonNull); } - protected final void maybeInvalidateTypeAssumption(Object value) { + void maybeInvalidateTypeAssumption(Object value) { if (canStoreInternal(value)) { return; } @@ -492,9 +347,9 @@ protected final void maybeInvalidateTypeAssumption(Object value) { invalidateTypeAssumption(value); } - private void invalidateTypeAssumption(Object value) { + void invalidateTypeAssumption(Object value) { CompilerAsserts.neverPartOfCompilation(); - AtomicReferenceFieldUpdater updater = TYPE_ASSUMPTION_UPDATER; + var updater = TYPE_ASSUMPTION_UPDATER; for (;;) { // TERMINATION ARGUMENT: loop will terminate once CAS succeeds TypeAssumption curr = getTypeAssumption(); if (curr == null) { @@ -529,13 +384,13 @@ private void invalidateTypeAssumption(Object value) { if (curr.getAssumption().isValid()) { break; } else { - assumedTypeLocationAssumptionRenewCount.inc(); + DebugCounters.assumedTypeLocationAssumptionRenewCount.inc(); } } else { curr.getAssumption().invalidate("generalizing object type " + TypeAssumption.toString(curr.type, curr.nonNull) + " => " + TypeAssumption.toString(type, nonNull) + " " + this); - assumedTypeLocationAssumptionInvalidationCount.inc(); + DebugCounters.assumedTypeLocationAssumptionInvalidationCount.inc(); } TypeAssumption next = createTypeAssumption(type, nonNull); if (updater.compareAndSet(this, curr, next)) { @@ -544,9 +399,9 @@ private void invalidateTypeAssumption(Object value) { } } - final void mergeTypeAssumption(TypeAssumption other) { + void mergeTypeAssumption(TypeAssumption other) { CompilerAsserts.neverPartOfCompilation(); - AtomicReferenceFieldUpdater updater = TYPE_ASSUMPTION_UPDATER; + var updater = TYPE_ASSUMPTION_UPDATER; for (;;) { // TERMINATION ARGUMENT: loop will terminate once CAS succeeds TypeAssumption curr = getTypeAssumption(); if (curr == null) { @@ -573,13 +428,13 @@ final void mergeTypeAssumption(TypeAssumption other) { if (curr.getAssumption().isValid()) { break; } else { - assumedTypeLocationAssumptionRenewCount.inc(); + DebugCounters.assumedTypeLocationAssumptionRenewCount.inc(); } } else { curr.getAssumption().invalidate("generalizing object type " + TypeAssumption.toString(curr.type, curr.nonNull) + " => " + TypeAssumption.toString(type, nonNull) + " " + this); - assumedTypeLocationAssumptionInvalidationCount.inc(); + DebugCounters.assumedTypeLocationAssumptionInvalidationCount.inc(); } TypeAssumption next = createTypeAssumption(type, nonNull); if (updater.compareAndSet(this, curr, next)) { @@ -589,8 +444,17 @@ final void mergeTypeAssumption(TypeAssumption other) { } @Override - public int primitiveFieldCount() { - return 0; + public int objectFieldCount() { + return isFieldLocation() ? OBJECT_SLOT_SIZE : 0; + } + + @Override + public void accept(LocationVisitor locationVisitor) { + if (field == null) { + locationVisitor.visitObjectArray(getIndex(), OBJECT_SLOT_SIZE); + } else { + locationVisitor.visitObjectField(getIndex(), OBJECT_SLOT_SIZE); + } } @Override @@ -601,271 +465,197 @@ public String toString() { } /** - * Object array location with assumption-based type speculation. + * Non-sealed because there used to be BooleanFieldLocation and Graal.js still uses + * {@link com.oracle.truffle.api.object.BooleanLocation}. If sealed it would cause a javac + * error: + * + *
+     * .../PropertySetNode.java:577: error: incompatible types: Location cannot be converted to BooleanLocation
+     *             this.location = (com.oracle.truffle.api.object.BooleanLocation) property.getLocation();
+     * 
*/ - static final class ObjectArrayLocation extends AbstractObjectLocation implements ArrayLocation { + abstract static non-sealed class AbstractPrimitiveLocation extends InstanceLocation { - ObjectArrayLocation(int index, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { - super(index, finalAssumption, typeAssumption); + AbstractPrimitiveLocation(int index, AbstractAssumption finalAssumption) { + super(index, null, finalAssumption); } - private static Object getArray(DynamicObject store, boolean condition) { - return UnsafeAccess.unsafeCast(store.getObjectStore(), Object[].class, condition, true, true); + AbstractPrimitiveLocation(int index, FieldInfo field, AbstractAssumption finalAssumption) { + super(index, Objects.requireNonNull(field), finalAssumption); + assert field.type() == long.class : field; } - private long getOffset() { - return Unsafe.ARRAY_OBJECT_BASE_OFFSET + (long) Unsafe.ARRAY_OBJECT_INDEX_SCALE * index; + @Override + final int primitiveFieldCount() { + return isFieldLocation() ? 1 : 0; } + } - @Override - public Object get(DynamicObject store, boolean guard) { - Object value = UnsafeAccess.unsafeGetObject(getArray(store, guard), getOffset(), guard, this); - return CompilerDirectives.inInterpreter() ? value : assumedTypeCast(value, guard); + static final class IntLocation extends AbstractPrimitiveLocation implements com.oracle.truffle.api.object.IntLocation { + + private IntLocation(int index, AbstractAssumption finalAssumption) { + super(index, finalAssumption); } - @Override - protected void set(DynamicObject store, Object value, boolean guard, boolean init) { - if (!init) { - maybeInvalidateFinalAssumption(); - } - maybeInvalidateTypeAssumption(value); - setObjectInternal(store, value, guard); + private IntLocation(int index, FieldInfo field, AbstractAssumption finalAssumption) { + super(index, Objects.requireNonNull(field), finalAssumption); } - private void setObjectInternal(DynamicObject store, Object value, boolean guard) { - UnsafeAccess.unsafePutObject(getArray(store, guard), getOffset(), value, this); + static IntLocation createIntArrayLocation(int index, AbstractAssumption finalAssumption) { + return new IntLocation(index, finalAssumption); } - @Override - protected void clear(DynamicObject store) { - UnsafeAccess.unsafePutObject(getArray(store, false), getOffset(), null, this); + static IntLocation createIntFieldLocation(int index, FieldInfo field, AbstractAssumption finalAssumption) { + return new IntLocation(index, field, finalAssumption); } @Override - public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitObjectArray(index, OBJECT_SLOT_SIZE); + public Object get(DynamicObject store, boolean guard) { + return getInt(store, guard); } @Override - public int objectFieldCount() { - return 0; + public int getInt(DynamicObject store, boolean guard) { + if (field == null) { + return getIntArray(store, guard); + } else { + return getIntField(store, guard); + } } - } - - /** - * Object field location with assumption-based type speculation. - */ - static final class ObjectFieldLocation extends AbstractObjectLocation implements FieldLocation { - private final FieldInfo field; - ObjectFieldLocation(int index, FieldInfo field, AbstractAssumption finalAssumption, TypeAssumption typeAssumption) { - super(index, finalAssumption, typeAssumption); - this.field = Objects.requireNonNull(field); + private int getIntArray(DynamicObject store, boolean guard) { + return UnsafeAccess.unsafeGetInt(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), guard, this); } - private Object getInternal(DynamicObject store, boolean guard) { + private int getIntField(DynamicObject store, boolean guard) { if (UseVarHandle) { - return field.varHandle().get(store); + return (int) (long) field.varHandle().get(store); } field.receiverCheck(store); - return UnsafeAccess.unsafeGetObject(store, getOffset(), guard, this); + return (int) UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); } @Override - public Object get(DynamicObject store, boolean guard) { - Object value = getInternal(store, guard); - return CompilerDirectives.inInterpreter() ? value : assumedTypeCast(value, guard); + protected void set(DynamicObject store, Object value, boolean guard, boolean init) { + if (canStore(value)) { + setInt(store, (int) value, guard, init); + } else { + throw incompatibleLocationException(); + } } @Override - protected void set(DynamicObject store, Object value, boolean guard, boolean init) { + public void setInt(DynamicObject store, int value, boolean guard, boolean init) { if (!init) { maybeInvalidateFinalAssumption(); } - maybeInvalidateTypeAssumption(value); - setObjectInternal(store, value); + if (field == null) { + setIntArrayInternal(store, value, guard); + } else { + setIntFieldInternal(store, value); + } } - private void setObjectInternal(DynamicObject store, Object value) { + private void setIntArrayInternal(DynamicObject store, int value, boolean guard) { + UnsafeAccess.unsafePutInt(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), value, this); + } + + private void setIntFieldInternal(DynamicObject store, int value) { if (UseVarHandle) { - field.varHandle().set(store, value); + field.varHandle().set(store, value & 0xffff_ffffL); return; } field.receiverCheck(store); - UnsafeAccess.unsafePutObject(store, getOffset(), value, this); + UnsafeAccess.unsafePutLong(store, getFieldOffset(), value & 0xffff_ffffL, this); } @Override - protected void clear(DynamicObject store) { - UnsafeAccess.unsafePutObject(store, getOffset(), null, this); + void clear(DynamicObject store) { + setInt(store, 0, false, true); } @Override - public int objectFieldCount() { - return OBJECT_SLOT_SIZE; + public boolean canStore(Object value) { + return value instanceof Integer; } @Override - public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitObjectField(index, OBJECT_SLOT_SIZE); + Class getType() { + return int.class; } @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + field.hashCode(); - return result; + int primitiveArrayCount() { + return isArrayLocation() ? INT_ARRAY_SLOT_SIZE : 0; } @Override - public boolean equals(Object obj) { - if (!super.equals(obj)) { - return false; + public void accept(LocationVisitor locationVisitor) { + if (field == null) { + locationVisitor.visitPrimitiveArray(getIndex(), INT_ARRAY_SLOT_SIZE); + } else { + locationVisitor.visitPrimitiveField(getIndex(), 1); } - ObjectFieldLocation other = (ObjectFieldLocation) obj; - return this.field.equals(other.field); } - long getOffset() { - return field.offset(); + @SuppressWarnings("deprecation") + @Override + public int getInt(DynamicObject store, Shape shape) { + return getInt(store, store.getShape() == shape); } - } - /** - * Non-sealed because there used to be BooleanFieldLocation and Graal.js still uses - * {@link com.oracle.truffle.api.object.BooleanLocation}. If sealed it would cause a javac - * error: - * - *
-     * .../PropertySetNode.java:577: error: incompatible types: Location cannot be converted to BooleanLocation
-     *             this.location = (com.oracle.truffle.api.object.BooleanLocation) property.getLocation();
-     * 
- */ - abstract static non-sealed class AbstractPrimitiveFieldLocation extends InstanceLocation implements FieldLocation { + @SuppressWarnings("deprecation") + @Override + public void setInt(DynamicObject store, int value, Shape shape) { + setInt(store, value, store.getShape() == shape, false); + } + } - protected final FieldInfo field; + static final class DoubleLocation extends AbstractPrimitiveLocation implements com.oracle.truffle.api.object.DoubleLocation { + private final boolean allowInt; - AbstractPrimitiveFieldLocation(int index, FieldInfo field, AbstractAssumption finalAssumption) { + DoubleLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { super(index, finalAssumption); - assert field.type() == long.class : field; - this.field = Objects.requireNonNull(field); + this.allowInt = allowInt; } - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + field.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (!super.equals(obj)) { - return false; - } - AbstractPrimitiveFieldLocation other = (AbstractPrimitiveFieldLocation) obj; - return this.field.equals(other.field); - } - - final long getOffset() { - return field.offset(); + DoubleLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { + super(index, field, finalAssumption); + this.allowInt = allowInt; } - @Override - public int objectFieldCount() { - return 0; + static DoubleLocation createDoubleArrayLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { + return new DoubleLocation(index, allowInt, finalAssumption); } - } - - static final class IntFieldLocation extends AbstractPrimitiveFieldLocation implements IntLocation { - IntFieldLocation(int index, FieldInfo field, AbstractAssumption finalAssumption) { - super(index, field, finalAssumption); + static DoubleLocation createDoubleFieldLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { + return new DoubleLocation(index, field, allowInt, finalAssumption); } @Override public Object get(DynamicObject store, boolean guard) { - return getInt(store, guard); - } - - @Override - public int getInt(DynamicObject store, boolean guard) { - if (UseVarHandle) { - return (int) (long) field.varHandle().get(store); - } - field.receiverCheck(store); - return (int) UnsafeAccess.unsafeGetLong(store, getOffset(), guard, this); + return getDouble(store, guard); } @Override - protected void set(DynamicObject store, Object value, boolean guard, boolean init) { - if (canStore(value)) { - setInt(store, (int) value, guard, init); + public double getDouble(DynamicObject store, boolean guard) { + if (field == null) { + return getDoubleArray(store, guard); } else { - throw incompatibleLocationException(); + return getDoubleField(store, guard); } } - @Override - public void setInt(DynamicObject store, int value, boolean guard, boolean init) { - if (!init) { - maybeInvalidateFinalAssumption(); - } - setIntInternal(store, value); + private double getDoubleArray(DynamicObject store, boolean guard) { + return UnsafeAccess.unsafeGetDouble(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), guard, this); } - private void setIntInternal(DynamicObject store, int value) { - if (UseVarHandle) { - field.varHandle().set(store, value & 0xffff_ffffL); - return; - } - field.receiverCheck(store); - UnsafeAccess.unsafePutLong(store, getOffset(), value & 0xffff_ffffL, this); - } - - @Override - public boolean canStore(Object value) { - return value instanceof Integer; - } - - @Override - public Class getType() { - return int.class; - } - - @Override - public int primitiveFieldCount() { - return INT_FIELD_SLOT_SIZE; - } - - @Override - public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitPrimitiveField(getIndex(), INT_FIELD_SLOT_SIZE); - } - } - - static final class DoubleFieldLocation extends AbstractPrimitiveFieldLocation implements DoubleLocation { - private final boolean allowInt; - - DoubleFieldLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { - super(index, field, finalAssumption); - this.allowInt = allowInt; - } - - @Override - public Object get(DynamicObject store, boolean guard) { - return getDouble(store, guard); - } - - @Override - public double getDouble(DynamicObject store, boolean guard) { + private double getDoubleField(DynamicObject store, boolean guard) { if (UseVarHandle) { return Double.longBitsToDouble((long) field.varHandle().get(store)); } field.receiverCheck(store); - return Double.longBitsToDouble(UnsafeAccess.unsafeGetLong(store, getOffset(), guard, this)); + return Double.longBitsToDouble(UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this)); } @Override @@ -873,16 +663,29 @@ public void setDouble(DynamicObject store, double value, boolean guard, boolean if (!init) { maybeInvalidateFinalAssumption(); } - setDoubleInternal(store, value); + if (field == null) { + setDoubleArrayInternal(store, value, guard); + } else { + setDoubleFieldInternal(store, value); + } } - private void setDoubleInternal(DynamicObject store, double value) { + private void setDoubleArrayInternal(DynamicObject store, double value, boolean guard) { + UnsafeAccess.unsafePutDouble(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), value, this); + } + + private void setDoubleFieldInternal(DynamicObject store, double value) { if (UseVarHandle) { field.varHandle().set(store, Double.doubleToRawLongBits(value)); return; } field.receiverCheck(store); - UnsafeAccess.unsafePutLong(store, getOffset(), Double.doubleToRawLongBits(value), this); + UnsafeAccess.unsafePutLong(store, getFieldOffset(), Double.doubleToRawLongBits(value), this); + } + + @Override + void clear(DynamicObject store) { + setDouble(store, 0, false, true); } @Override @@ -894,7 +697,7 @@ protected void set(DynamicObject store, Object value, boolean guard, boolean ini } } - private double doubleValue(Object value) { + double doubleValue(Object value) { if (!allowInt || value instanceof Double) { return ((Double) value).doubleValue(); } else if (value instanceof Integer) { @@ -910,241 +713,100 @@ public boolean canStore(Object value) { } @Override - public double getDouble(DynamicObject store, Shape shape) { - return getDouble(store, checkShape(store, shape)); - } - - @Override - public Class getType() { + Class getType() { return double.class; } @Override - public int primitiveFieldCount() { - return 1; + int primitiveArrayCount() { + return isArrayLocation() ? DOUBLE_ARRAY_SLOT_SIZE : 0; } @Override public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitPrimitiveField(getIndex(), 1); + if (field == null) { + locationVisitor.visitPrimitiveArray(getIndex(), DOUBLE_ARRAY_SLOT_SIZE); + } else { + locationVisitor.visitPrimitiveField(getIndex(), 1); + } } @Override public int hashCode() { - return Objects.hash(super.hashCode(), allowInt); + final int prime = 31; + int hash = super.hashCode(); + hash = hash * prime + Boolean.hashCode(allowInt); + return hash; } @Override public boolean equals(Object obj) { - return super.equals(obj) && this.allowInt == ((DoubleFieldLocation) obj).allowInt; + return super.equals(obj) && this.allowInt == ((DoubleLocation) obj).allowInt; } @Override - public boolean isImplicitCastIntToDouble() { + boolean isImplicitCastIntToDouble() { return allowInt; } - } - - abstract static sealed class AbstractPrimitiveArrayLocation extends InstanceLocation implements ArrayLocation { - - AbstractPrimitiveArrayLocation(int index, AbstractAssumption finalAssumption) { - super(index, finalAssumption); - } - - protected final long getOffset() { - return Unsafe.ARRAY_INT_BASE_OFFSET + (long) Unsafe.ARRAY_INT_INDEX_SCALE * index; - } - - protected abstract int getBytes(); - - protected static Object getArray(DynamicObject store, boolean condition) { - return UnsafeAccess.unsafeCast(store.getPrimitiveStore(), int[].class, condition, true, true); - } - - @Override - public int objectFieldCount() { - return 0; - } - - @Override - public int primitiveFieldCount() { - return 0; - } - } - - static final class IntArrayLocation extends AbstractPrimitiveArrayLocation implements IntLocation { - - IntArrayLocation(int index, AbstractAssumption finalAssumption) { - super(index, finalAssumption); - } - - @Override - public Object get(DynamicObject store, boolean guard) { - return getInt(store, guard); - } - - @Override - protected void set(DynamicObject store, Object value, boolean guard, boolean init) { - if (canStore(value)) { - setInt(store, (int) value, guard, init); - } else { - throw incompatibleLocationException(); - } - } - - @Override - public int getInt(DynamicObject store, boolean guard) { - return UnsafeAccess.unsafeGetInt(getArray(store, guard), getOffset(), guard, this); - } - - private void setIntInternal(DynamicObject store, int value, boolean guard) { - UnsafeAccess.unsafePutInt(getArray(store, guard), getOffset(), value, this); - } - - @Override - public void setInt(DynamicObject store, int value, boolean guard, boolean init) { - if (!init) { - maybeInvalidateFinalAssumption(); - } - setIntInternal(store, value, guard); - } - - @Override - public boolean canStore(Object value) { - return value instanceof Integer; - } - - @Override - public Class getType() { - return int.class; - } + @SuppressWarnings("deprecation") @Override - public int primitiveArrayCount() { - return INT_ARRAY_SLOT_SIZE; - } - - @Override - public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitPrimitiveArray(index, INT_ARRAY_SLOT_SIZE); + public double getDouble(DynamicObject store, Shape shape) { + return getDouble(store, checkShape(store, shape)); } + @SuppressWarnings("deprecation") @Override - protected int getBytes() { - return Integer.BYTES; + public void setDouble(DynamicObject store, double value, Shape shape) { + setDouble(store, value, store.getShape() == shape, false); } } - static final class DoubleArrayLocation extends AbstractPrimitiveArrayLocation implements DoubleLocation { + static final class LongLocation extends AbstractPrimitiveLocation implements com.oracle.truffle.api.object.LongLocation { private final boolean allowInt; - DoubleArrayLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { + LongLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { super(index, finalAssumption); this.allowInt = allowInt; } - @Override - public Object get(DynamicObject store, boolean guard) { - return getDouble(store, guard); + LongLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { + super(index, field, finalAssumption); + this.allowInt = allowInt; } - @Override - protected void set(DynamicObject store, Object value, boolean guard, boolean init) { - if (canStore(value)) { - setDouble(store, doubleValue(value), guard, init); - } else { - throw incompatibleLocationException(); - } + static LongLocation createLongArrayLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { + return new LongLocation(index, allowInt, finalAssumption); } - private double doubleValue(Object value) { - if (!allowInt || value instanceof Double) { - return ((Double) value).doubleValue(); - } else if (value instanceof Integer) { - return ((Integer) value).doubleValue(); - } else { - throw shouldNotReachHere(); - } + static LongLocation createLongFieldLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { + return new LongLocation(index, field, allowInt, finalAssumption); } @Override - public double getDouble(DynamicObject store, boolean guard) { - return UnsafeAccess.unsafeGetDouble(getArray(store, guard), getOffset(), guard, this); - } - - private void setDoubleInternal(DynamicObject store, double value, boolean guard) { - UnsafeAccess.unsafePutDouble(getArray(store, guard), getOffset(), value, this); + public Object get(DynamicObject store, boolean guard) { + return getLong(store, guard); } @Override - public void setDouble(DynamicObject store, double value, boolean guard, boolean init) { - if (!init) { - maybeInvalidateFinalAssumption(); + public long getLong(DynamicObject store, boolean guard) { + if (field == null) { + return getLongArray(store, guard); + } else { + return getLongField(store, guard); } - setDoubleInternal(store, value, guard); } - @Override - public boolean canStore(Object value) { - return value instanceof Double || (allowInt && value instanceof Integer); + private long getLongArray(DynamicObject store, boolean guard) { + return UnsafeAccess.unsafeGetLong(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), guard, this); } - @Override - public Class getType() { - return double.class; - } - - @Override - public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitPrimitiveArray(index, DOUBLE_ARRAY_SLOT_SIZE); - } - - @Override - public int primitiveArrayCount() { - return DOUBLE_ARRAY_SLOT_SIZE; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), allowInt); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj) && this.allowInt == ((DoubleArrayLocation) obj).allowInt; - } - - @Override - public boolean isImplicitCastIntToDouble() { - return allowInt; - } - - @Override - protected int getBytes() { - return Double.BYTES; - } - } - - static final class LongFieldLocation extends AbstractPrimitiveFieldLocation implements LongLocation { - private final boolean allowInt; - - LongFieldLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { - super(index, field, finalAssumption); - this.allowInt = allowInt; - } - - @Override - public Object get(DynamicObject store, boolean guard) { - return getLong(store, guard); - } - - @Override - public long getLong(DynamicObject store, boolean guard) { + private long getLongField(DynamicObject store, boolean guard) { if (UseVarHandle) { return (long) field.varHandle().get(store); } field.receiverCheck(store); - return UnsafeAccess.unsafeGetLong(store, getOffset(), guard, this); + return UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); } @Override @@ -1152,16 +814,29 @@ public void setLong(DynamicObject store, long value, boolean guard, boolean init if (!init) { maybeInvalidateFinalAssumption(); } - setLongInternal(store, value); + if (field == null) { + setLongArrayInternal(store, value, guard); + } else { + setLongFieldInternal(store, value); + } } - private void setLongInternal(DynamicObject store, long value) { + private void setLongArrayInternal(DynamicObject store, long value, boolean guard) { + UnsafeAccess.unsafePutLong(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), value, this); + } + + private void setLongFieldInternal(DynamicObject store, long value) { if (UseVarHandle) { field.varHandle().set(store, value); return; } field.receiverCheck(store); - UnsafeAccess.unsafePutLong(store, getOffset(), value, this); + UnsafeAccess.unsafePutLong(store, getFieldOffset(), value, this); + } + + @Override + void clear(DynamicObject store) { + setLong(store, 0L, false, true); } @Override @@ -1173,7 +848,7 @@ protected void set(DynamicObject store, Object value, boolean guard, boolean ini } } - private long longValue(Object value) { + long longValue(Object value) { if (!allowInt || value instanceof Long) { return ((Long) value).longValue(); } else if (value instanceof Integer) { @@ -1189,128 +864,52 @@ public boolean canStore(Object value) { } @Override - public Class getType() { + Class getType() { return long.class; } @Override - public int primitiveFieldCount() { - return 1; + int primitiveArrayCount() { + return isArrayLocation() ? LONG_ARRAY_SLOT_SIZE : 0; } @Override public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitPrimitiveField(getIndex(), 1); + if (field == null) { + locationVisitor.visitPrimitiveArray(getIndex(), LONG_ARRAY_SLOT_SIZE); + } else { + locationVisitor.visitPrimitiveField(getIndex(), 1); + } } @Override public int hashCode() { - return Objects.hash(super.hashCode(), allowInt); + final int prime = 31; + int hash = super.hashCode(); + hash = hash * prime + Boolean.hashCode(allowInt); + return hash; } @Override public boolean equals(Object obj) { - return super.equals(obj) && this.allowInt == ((LongFieldLocation) obj).allowInt; + return super.equals(obj) && this.allowInt == ((LongLocation) obj).allowInt; } @Override - public boolean isImplicitCastIntToLong() { + boolean isImplicitCastIntToLong() { return allowInt; } - } - - static final class LongArrayLocation extends AbstractPrimitiveArrayLocation implements LongLocation { - private final boolean allowInt; - - LongArrayLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { - super(index, finalAssumption); - this.allowInt = allowInt; - } - - @Override - public Object get(DynamicObject store, boolean guard) { - return getLong(store, guard); - } - - @Override - protected void set(DynamicObject store, Object value, boolean guard, boolean init) { - if (canStore(value)) { - setLong(store, longValue(value), guard, init); - } else { - throw incompatibleLocationException(); - } - } - - private long longValue(Object value) { - if (!allowInt || value instanceof Long) { - return ((Long) value).longValue(); - } else if (value instanceof Integer) { - return ((Integer) value).longValue(); - } else { - throw shouldNotReachHere(); - } - } - - @Override - public long getLong(DynamicObject store, boolean guard) { - return UnsafeAccess.unsafeGetLong(getArray(store, guard), getOffset(), guard, this); - } - - private void setLongInternal(DynamicObject store, long value, boolean guard) { - UnsafeAccess.unsafePutLong(getArray(store, guard), getOffset(), value, this); - } - - @Override - public void setLong(DynamicObject store, long value, boolean guard, boolean init) { - if (!init) { - maybeInvalidateFinalAssumption(); - } - setLongInternal(store, value, guard); - } - - @Override - public boolean canStore(Object value) { - return value instanceof Long || (allowInt && value instanceof Integer); - } + @SuppressWarnings("deprecation") @Override public long getLong(DynamicObject store, Shape shape) { - return getLong(store, checkShape(store, shape)); - } - - @Override - public Class getType() { - return long.class; - } - - @Override - public int primitiveArrayCount() { - return LONG_ARRAY_SLOT_SIZE; - } - - @Override - public void accept(LocationVisitor locationVisitor) { - locationVisitor.visitPrimitiveArray(index, LONG_ARRAY_SLOT_SIZE); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), allowInt); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj) && this.allowInt == ((LongArrayLocation) obj).allowInt; - } - - @Override - public boolean isImplicitCastIntToLong() { - return allowInt; + return getLong(store, store.getShape() == shape); } + @SuppressWarnings("deprecation") @Override - protected int getBytes() { - return Long.BYTES; + public void setLong(DynamicObject store, long value, Shape shape) { + setLong(store, value, store.getShape() == shape, false); } } diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/FieldInfo.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/FieldInfo.java index 9a80029a40c5..e0f055e9ead3 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/FieldInfo.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/FieldInfo.java @@ -42,6 +42,7 @@ import java.lang.invoke.VarHandle; import java.lang.reflect.Field; +import java.util.Objects; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; @@ -158,4 +159,12 @@ private IllegalArgumentException illegalReceiver(DynamicObject store) { CompilerAsserts.neverPartOfCompilation(); return new IllegalArgumentException("Invalid receiver type (expected " + getDeclaringClass() + ", was " + (store == null ? null : store.getClass()) + ")"); } + + DynamicObject unsafeReceiverCast(DynamicObject store) { + if (ObjectStorageOptions.ReceiverCheck) { + return Objects.requireNonNull(tclass.cast(store)); + } else { + return store; + } + } } 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..b29cf00c2f59 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,11 +40,26 @@ */ package com.oracle.truffle.api.object; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.graalvm.nativeimage.ImageInfo; + import com.oracle.truffle.api.Assumption; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.HostCompilerDirectives; +import com.oracle.truffle.api.impl.AbstractAssumption; import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.object.ExtLocations.AbstractPrimitiveLocation; +import com.oracle.truffle.api.object.ExtLocations.ConstantLocation; +import com.oracle.truffle.api.object.ExtLocations.DoubleLocation; +import com.oracle.truffle.api.object.ExtLocations.IntLocation; +import com.oracle.truffle.api.object.ExtLocations.LongLocation; +import com.oracle.truffle.api.object.ExtLocations.ObjectLocation; -import java.util.Objects; +import sun.misc.Unsafe; /** * Property location. @@ -57,12 +72,40 @@ * @since 0.8 or earlier */ public abstract sealed class Location permits ExtLocations.InstanceLocation, ExtLocations.ValueLocation { + + final int index; + final FieldInfo field; + + @CompilationFinal volatile AbstractAssumption finalAssumption; + + private static final AtomicReferenceFieldUpdater FINAL_ASSUMPTION_UPDATER = AtomicReferenceFieldUpdater.newUpdater( + Location.class, AbstractAssumption.class, "finalAssumption"); + /** - * Constructor for subclasses. + * Constructor for instance location. * - * @since 0.8 or earlier + * @param index array index or field index + * @param finalAssumption initial value of final assumption field + */ + Location(int index, FieldInfo field, AbstractAssumption finalAssumption) { + this.index = index; + this.field = field; + this.finalAssumption = finalAssumption; + assert isValidIndex(index) : index; + } + + /** + * Constructor for constant value location. */ - protected Location() { + Location() { + this.index = -1; + this.field = null; + this.finalAssumption = (AbstractAssumption) Assumption.NEVER_VALID; + assert this instanceof ExtLocations.ValueLocation; + } + + static boolean isValidIndex(int index) { + return index >= 0; } /** @since 0.8 or earlier */ @@ -152,6 +195,144 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected return expectDouble(get(store, guard)); } + /** + * Gets this location's value from store. + * + * @param store storage object + * @param expectedShape the expected object shape; must be a shape that contains this location + * @param guard the result of the shape check or {@code false} + * @return the read value + */ + @SuppressWarnings("hiding") + final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard) { + DynamicObject receiver = unsafeNonNullCast(store); + long idx = Integer.toUnsignedLong(index); + FieldInfo field = this.field; + if (this instanceof ObjectLocation objectLocation) { + Object base; + long offset; + Object value; + if (field == null) { + base = getObjectArray(receiver, guard); + offset = computeObjectArrayOffset(idx); + value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); + } else { + base = field.unsafeReceiverCast(receiver); + offset = getFieldOffset(); + value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); + } + return CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard); + } else { + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = computePrimitiveArrayOffset(idx); + if (isIntLocation()) { + return UnsafeAccess.unsafeGetInt(array, offset, guard, this); + } else if (isLongLocation()) { + return UnsafeAccess.unsafeGetLong(array, offset, guard, this); + } else if (isDoubleLocation()) { + return UnsafeAccess.unsafeGetDouble(array, offset, guard, this); + } else { + return ((ConstantLocation) this).get(receiver, guard); + } + } else { + Object base = field.unsafeReceiverCast(receiver); + long longValue = UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); + if (this instanceof IntLocation) { + return (int) longValue; + } else if (this instanceof LongLocation) { + return longValue; + } else { + assert isDoubleLocation(); + return Double.longBitsToDouble(longValue); + } + } + } + } + + /** + * @see #getInternal(DynamicObject, Shape, boolean) + */ + final int getIntInternal(DynamicObject store, Shape expectedShape, boolean guard) throws UnexpectedResultException { + DynamicObject receiver = unsafeNonNullCast(store); + if (isIntLocation()) { + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = getPrimitiveArrayOffset(); + return UnsafeAccess.unsafeGetInt(array, offset, guard, this); + } else { + Object base = field.unsafeReceiverCast(receiver); + long longValue = UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); + return (int) longValue; + } + } + return getIntUnexpected(receiver, expectedShape, guard); + } + + /** + * Slow path of {@link #getIntInternal(DynamicObject, Shape, boolean)} that handles constant + * locations and other primitive locations that always throw {@link UnexpectedResultException}. + */ + private int getIntUnexpected(DynamicObject store, Shape expectedShape, boolean guard) throws UnexpectedResultException { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw new UnexpectedResultException(getInternal(store, expectedShape, guard)); + } + + /** + * @see #getInternal(DynamicObject, Shape, boolean) + */ + final long getLongInternal(DynamicObject store, Shape expectedShape, boolean guard) throws UnexpectedResultException { + DynamicObject receiver = unsafeNonNullCast(store); + if (isLongLocation()) { + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = getPrimitiveArrayOffset(); + return UnsafeAccess.unsafeGetLong(array, offset, guard, this); + } else { + Object base = field.unsafeReceiverCast(receiver); + return UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); + } + } + return getLongUnexpected(receiver, expectedShape, guard); + } + + /** + * Slow path of {@link #getLongInternal(DynamicObject, Shape, boolean)} that handles constant + * locations and other primitive locations that always throw {@link UnexpectedResultException}. + */ + private long getLongUnexpected(DynamicObject store, Shape expectedShape, boolean guard) throws UnexpectedResultException { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw new UnexpectedResultException(getInternal(store, expectedShape, guard)); + } + + /** + * @see #getInternal(DynamicObject, Shape, boolean) + */ + final double getDoubleInternal(DynamicObject store, Shape expectedShape, boolean guard) throws UnexpectedResultException { + DynamicObject receiver = unsafeNonNullCast(store); + if (isDoubleLocation()) { + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = getPrimitiveArrayOffset(); + return UnsafeAccess.unsafeGetDouble(array, offset, guard, this); + } else { + Object base = field.unsafeReceiverCast(receiver); + long longValue = UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); + return Double.longBitsToDouble(longValue); + } + } + return getDoubleUnexpected(receiver, expectedShape, guard); + } + + /** + * Slow path of {@link #getDoubleInternal(DynamicObject, Shape, boolean)} that handles constant + * locations and other primitive locations that always throw {@link UnexpectedResultException}. + */ + private double getDoubleUnexpected(DynamicObject store, Shape expectedShape, boolean guard) throws UnexpectedResultException { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw new UnexpectedResultException(getInternal(store, expectedShape, guard)); + } + /** * Set object value at this location in store. * @@ -162,7 +343,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", "deprecation"}) @Deprecated(since = "22.2") public void set(DynamicObject store, Object value, Shape shape) throws IncompatibleLocationException, FinalLocationException { try { @@ -184,8 +365,9 @@ public void set(DynamicObject store, Object value, Shape shape) throws Incompati @SuppressWarnings({"unused", "deprecation"}) 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(); @@ -257,6 +439,134 @@ final void setDoubleSafe(DynamicObject store, double value, boolean guard, boole setDouble(store, value, guard, init); } + /** + * Stores a value in this location. Grows the object if necessary. It is the caller's + * responsibility to check that the value is compatible with this location first. + * + * @param store storage object + * @param value the value to be stored + * @param guard the result of the shape check guarding this property write or {@code false} + * @param oldShape the expected shape before the set + * @param newShape the expected shape after the set + * @see #canStoreValue(Object). + */ + @SuppressWarnings("hiding") + final void setInternal(DynamicObject store, Object value, boolean guard, Shape oldShape, Shape newShape) { + 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 { + AbstractAssumption assumption = getFinalAssumptionField(); + if (assumption == null || assumption.isValid()) { + invalidateFinalAssumption(assumption); + } + } + if (this instanceof ObjectLocation objectLocation) { + if (!objectLocation.canStoreInternal(value)) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + objectLocation.invalidateTypeAssumption(value); + } + Object base; + long offset; + if (field == null) { + base = getObjectArray(receiver, guard); + offset = computeObjectArrayOffset(idx); + UnsafeAccess.unsafePutObject(base, offset, value, this); + } else { + base = field.unsafeReceiverCast(receiver); + offset = getFieldOffset(); + UnsafeAccess.unsafePutObject(base, offset, value, this); + } + } else { // primitive location + long longValue; + if (isIntLocation()) { + int intValue = (int) value; + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = computePrimitiveArrayOffset(idx); + UnsafeAccess.unsafePutInt(array, offset, intValue, this); + return; + } else { + longValue = Integer.toUnsignedLong(intValue); + } + } else if (this instanceof LongLocation longLocation) { + if (value instanceof Long l) { + longValue = l; + } else if (longLocation.isImplicitCastIntToLong() && value instanceof Integer i) { + longValue = i; + } else { + return; + } + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = computePrimitiveArrayOffset(idx); + UnsafeAccess.unsafePutLong(array, offset, longValue, this); + return; + } + } else if (this instanceof DoubleLocation doubleLocation) { + double doubleValue; + if (value instanceof Double d) { + doubleValue = d; + } else if (doubleLocation.isImplicitCastIntToDouble() && value instanceof Integer i) { + doubleValue = i; + } else { + return; + } + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = computePrimitiveArrayOffset(idx); + UnsafeAccess.unsafePutDouble(array, offset, doubleValue, this); + return; + } else { + longValue = Double.doubleToRawLongBits(doubleValue); + } + } else { + assert isConstantLocation() : this; + return; + } + Object base = field.unsafeReceiverCast(receiver); + long offset = getFieldOffset(); + UnsafeAccess.unsafePutLong(base, offset, longValue, this); + } + } + + private static DynamicObject unsafeNonNullCast(DynamicObject receiver) { + /* + * The shape check already performs an implicit null check, so the receiver is guaranteed to + * be non-null here, but when compiling methods separately we might not know this yet. + * Hence, we use an unsafe cast in the interpreter to avoid compiling any null code paths. + * In PE OTOH, the redundant ValueAnchor+Pi would just mean extra work for the compiler. + */ + if (ImageInfo.inImageBuildtimeCode() && !ObjectStorageOptions.ReceiverCheck) { + return UnsafeAccess.hostUnsafeCast(receiver, DynamicObject.class, false, true, false); + } else { + return receiver; + } + } + + final boolean canStoreValue(Object value) { + if (isObjectLocation()) { + return true; + } else if (isIntLocation()) { + return value instanceof Integer; + } else if (this instanceof LongLocation longLocation) { + return value instanceof Long || (longLocation.isImplicitCastIntToLong() && value instanceof Integer); + } else if (this instanceof DoubleLocation doubleLocation) { + return value instanceof Double || (doubleLocation.isImplicitCastIntToDouble() && value instanceof Integer); + } else { + return canStoreConstant(value); + } + } + + @HostCompilerDirectives.InliningCutoff + final boolean canStoreConstant(Object value) { + return ((ConstantLocation) this).canStore(value); + } + /** * Equivalent to {@link Shape#check(DynamicObject)}. */ @@ -304,7 +614,7 @@ public boolean isFinal() { * @since 0.8 or earlier */ public boolean isConstant() { - return false; + return isConstantLocation(); } /** @@ -314,7 +624,11 @@ public boolean isConstant() { */ @Override public int hashCode() { - return getClass().hashCode(); + final int prime = 31; + int hash = getClass().hashCode(); + hash = hash * prime + Integer.hashCode(index); + hash = hash * prime + Objects.hashCode(field); + return hash; } /** @@ -330,7 +644,9 @@ public boolean equals(Object obj) { if (obj == null) { return false; } - return getClass() == obj.getClass(); + return getClass() == obj.getClass() && obj instanceof Location that && + this.index == that.index && + this.field == that.field; } /** @@ -345,13 +661,17 @@ public String toString() { * Get the number of in-object {@link Object} fields this location requires. Used reflectively * by tests. */ - abstract int objectFieldCount(); + int objectFieldCount() { + return 0; + } /** * Get the number of in-object primitive fields this location requires. Used reflectively by * tests. */ - abstract int primitiveFieldCount(); + int primitiveFieldCount() { + return 0; + } /** * Get the number of primitive array elements this location requires. @@ -371,16 +691,32 @@ static boolean isSameLocation(Location loc1, Location loc2) { return loc1 == loc2 || loc1.equals(loc2); } + final boolean isFieldLocation() { + return field != null; + } + + final boolean isArrayLocation() { + return !isFieldLocation() && !isConstantLocation(); + } + + final boolean isConstantLocation() { + return this instanceof ConstantLocation; + } + + final boolean isObjectLocation() { + return this instanceof ObjectLocation; + } + final boolean isIntLocation() { - return this instanceof ExtLocations.IntLocation; + return this instanceof IntLocation; } final boolean isDoubleLocation() { - return this instanceof ExtLocations.DoubleLocation; + return this instanceof DoubleLocation; } final boolean isLongLocation() { - return this instanceof ExtLocations.LongLocation; + return this instanceof LongLocation; } boolean isImplicitCastIntToLong() { @@ -416,10 +752,49 @@ Class getType() { return null; } - void clear(@SuppressWarnings("unused") DynamicObject store) { + abstract void clear(DynamicObject store); + + final int getOrdinal() { + assert !isConstantLocation() : this; + boolean isPrimitive = this instanceof AbstractPrimitiveLocation; + int ordinal = (isPrimitive ? -Integer.MAX_VALUE : 0) + getIndex(); + if (isArrayLocation()) { + ordinal += ExtLocations.MAX_DYNAMIC_FIELDS; + } + return ordinal; + } + + final int getIndex() { + return index; + } + + final long getFieldOffset() { + return field.offset(); + } + + static long computeObjectArrayOffset(long index) { + return index * Unsafe.ARRAY_OBJECT_INDEX_SCALE + Unsafe.ARRAY_OBJECT_BASE_OFFSET; + } + + static long computePrimitiveArrayOffset(long index) { + return index * Unsafe.ARRAY_INT_INDEX_SCALE + Unsafe.ARRAY_INT_BASE_OFFSET; + } + + final long getObjectArrayOffset() { + return computeObjectArrayOffset(Integer.toUnsignedLong(index)); + } + + final long getPrimitiveArrayOffset() { + return computePrimitiveArrayOffset(Integer.toUnsignedLong(index)); + } + + static Object getObjectArray(DynamicObject store, boolean condition) { + return UnsafeAccess.hostUnsafeCast(store.getObjectStore(), Object[].class, condition, true, true); } - abstract int getOrdinal(); + static Object getPrimitiveArray(DynamicObject store, boolean condition) { + return UnsafeAccess.hostUnsafeCast(store.getPrimitiveStore(), int[].class, condition, true, true); + } static RuntimeException incompatibleLocationException() { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -454,7 +829,8 @@ public boolean isValue() { * @since 0.18 */ public boolean isAssumedFinal() { - return false; + var assumption = getFinalAssumptionField(); + return assumption == null || assumption.isValid(); } /** @@ -464,7 +840,7 @@ public boolean isAssumedFinal() { * @since 0.18 */ public Assumption getFinalAssumption() { - return Assumption.NEVER_VALID; + return getFinalAssumptionInternal(); } /** @@ -475,7 +851,7 @@ public Assumption getFinalAssumption() { */ @SuppressWarnings("deprecation") public boolean isPrimitive() { - return this instanceof DoubleLocation || this instanceof IntLocation || this instanceof LongLocation; + return this instanceof AbstractPrimitiveLocation; } /** @@ -518,4 +894,64 @@ interface LocationVisitor { void visitPrimitiveArray(int index, int count); } + + // final assumption + + final AbstractAssumption getFinalAssumptionField() { + return finalAssumption; + } + + static AbstractAssumption createFinalAssumption() { + DebugCounters.assumedFinalLocationAssumptionCount.inc(); + return (AbstractAssumption) Assumption.create("final location"); + } + + final void maybeInvalidateFinalAssumption() { + var assumption = getFinalAssumptionField(); + if (assumption == null || assumption.isValid()) { + invalidateFinalAssumption(assumption); + } + } + + @SuppressWarnings("unchecked") + private void invalidateFinalAssumption(AbstractAssumption lastAssumption) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + var updater = FINAL_ASSUMPTION_UPDATER; + DebugCounters.assumedFinalLocationAssumptionInvalidationCount.inc(); + AbstractAssumption assumption = lastAssumption; + if (assumption == null) { + while (!updater.compareAndSet(this, assumption, (AbstractAssumption) Assumption.NEVER_VALID)) { + assumption = updater.get(this); + if (assumption == Assumption.NEVER_VALID) { + break; + } + assumption.invalidate(); + } + } else if (assumption.isValid()) { + assumption.invalidate(); + updater.set(this, (AbstractAssumption) Assumption.NEVER_VALID); + } + } + + final AbstractAssumption getFinalAssumptionInternal() { + var assumption = getFinalAssumptionField(); + if (assumption != null) { + return assumption; + } + CompilerDirectives.transferToInterpreterAndInvalidate(); + return initializeFinalAssumption(); + } + + @SuppressWarnings("unchecked") + private AbstractAssumption initializeFinalAssumption() { + CompilerAsserts.neverPartOfCompilation(); + var updater = FINAL_ASSUMPTION_UPDATER; + AbstractAssumption newAssumption = createFinalAssumption(); + if (updater.compareAndSet(this, null, newAssumption)) { + return newAssumption; + } else { + // if CAS failed, assumption is already initialized; cannot be null after that. + return Objects.requireNonNull(updater.get(this)); + } + } } diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObjectStorageOptions.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObjectStorageOptions.java index ad3401d6e151..dc190e7b9d42 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObjectStorageOptions.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObjectStorageOptions.java @@ -47,6 +47,8 @@ final class ObjectStorageOptions { private ObjectStorageOptions() { } + static final boolean ASSERTIONS_ENABLED = assertionsEnabled(); + static final boolean PrimitiveLocations = booleanOption(OPTION_PREFIX + "PrimitiveLocations", true); static final boolean IntegerLocations = booleanOption(OPTION_PREFIX + "IntegerLocations", true); static final boolean DoubleLocations = booleanOption(OPTION_PREFIX + "DoubleLocations", true); @@ -61,6 +63,10 @@ private ObjectStorageOptions() { * If set to true, use VarHandle rather than Unsafe to implement field accesses. */ static final boolean UseVarHandle = booleanOption(OPTION_PREFIX + "UseVarHandle", false); + /** + * Enforce receiver type checks for unsafe field accesses. + */ + static final boolean ReceiverCheck = booleanOption(OPTION_PREFIX + "ReceiverCheck", false) || ASSERTIONS_ENABLED; static final boolean TriePropertyMap = booleanOption(OPTION_PREFIX + "TriePropertyMap", true); static final boolean TrieTransitionMap = booleanOption(OPTION_PREFIX + "TrieTransitionMap", true); @@ -72,6 +78,9 @@ private ObjectStorageOptions() { /** Number of differing, compatible property locations allowed when merging shapes. */ static final int MaxMergeDiff = Integer.getInteger(OPTION_PREFIX + "MaxMergeDiff", 2); + /** Number of shapes and keys to cache per access node before rewriting to the generic case. */ + static final int CacheLimit = Integer.getInteger(OPTION_PREFIX + "CacheLimit", 5); + // Debug options (should be final) static final boolean TraceReshape = booleanOption(OPTION_PREFIX + "TraceReshape", false); @@ -91,4 +100,11 @@ static boolean booleanOption(String name, boolean defaultValue) { String value = System.getProperty(name); return value == null ? defaultValue : value.equalsIgnoreCase("true"); } + + @SuppressWarnings("all") + private static boolean assertionsEnabled() { + boolean enabled = false; + assert (enabled = true) == true; + return enabled; + } } diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Obsolescence.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Obsolescence.java index 8333faf13efb..d48e373936f2 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Obsolescence.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Obsolescence.java @@ -204,14 +204,14 @@ public static boolean isLocationCompatible(LayoutImpl layout, Location thisLoc, protected static boolean isLocationEquivalent(Location thisLoc, Location otherLoc) { if (thisLoc instanceof IntLocation) { return (otherLoc instanceof IntLocation); - } else if (thisLoc instanceof DoubleLocation) { - return (otherLoc instanceof DoubleLocation && ((DoubleLocation) thisLoc).isImplicitCastIntToDouble() && ((DoubleLocation) otherLoc).isImplicitCastIntToDouble()); - } else if (thisLoc instanceof LongLocation) { - return (otherLoc instanceof LongLocation && ((LongLocation) thisLoc).isImplicitCastIntToLong() && ((LongLocation) otherLoc).isImplicitCastIntToLong()); + } else if (thisLoc instanceof DoubleLocation thisDoubleLoc) { + return (otherLoc instanceof DoubleLocation otherDoubleLoc && + thisDoubleLoc.isImplicitCastIntToDouble() == otherDoubleLoc.isImplicitCastIntToDouble()); + } else if (thisLoc instanceof LongLocation thisLongLoc) { + return (otherLoc instanceof LongLocation otherLongLoc && + thisLongLoc.isImplicitCastIntToLong() == otherLongLoc.isImplicitCastIntToLong()); } else if (thisLoc instanceof ObjectLocation) { - return (otherLoc instanceof ObjectLocation && - ((ObjectLocation) thisLoc).getType() == ((ObjectLocation) otherLoc).getType() && - ((ObjectLocation) thisLoc).isNonNull() == ((ObjectLocation) otherLoc).isNonNull()); + return otherLoc instanceof ObjectLocation; } else if (thisLoc.isValue()) { return thisLoc.equals(otherLoc); } else { @@ -226,16 +226,9 @@ protected static boolean isLocationAssignableFrom(LayoutImpl layout, Location de return (source instanceof DoubleLocation || (layout.isAllowedIntToDouble() && source instanceof IntLocation)); } else if (destination instanceof LongLocation) { return (source instanceof LongLocation || (layout.isAllowedIntToLong() && source instanceof IntLocation)); - } else if (destination instanceof ObjectLocation dstObjLoc) { - if (source instanceof ObjectLocation) { - return (dstObjLoc.getType() == Object.class || dstObjLoc.getType().isAssignableFrom(((ObjectLocation) source).getType())) && - (!dstObjLoc.isNonNull() || ((ObjectLocation) source).isNonNull()); - } else if (source instanceof ExtLocations.TypedLocation) { - // Untyped object location is assignable from any primitive type location - return dstObjLoc.getType() == Object.class; - } else { - return false; - } + } else if (destination instanceof ObjectLocation) { + // Object location is assignable from any object or primitive type location + return true; } else if (destination.isValue()) { return destination.equals(source); } else { 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..6ce95963af93 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 @@ -54,18 +54,17 @@ import java.util.Objects; import java.util.stream.Collectors; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.object.Location.LocationVisitor; -import com.oracle.truffle.api.object.Transition.ObjectFlagsTransition; -import com.oracle.truffle.api.object.Transition.ObjectTypeTransition; -import com.oracle.truffle.api.object.Transition.RemovePropertyTransition; import org.graalvm.collections.Pair; import com.oracle.truffle.api.CompilerAsserts; -import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.object.ExtLocations.ObjectLocation; import com.oracle.truffle.api.object.Transition.AbstractReplacePropertyTransition; import com.oracle.truffle.api.object.Transition.AddPropertyTransition; import com.oracle.truffle.api.object.Transition.DirectReplacePropertyTransition; +import com.oracle.truffle.api.object.Transition.ObjectFlagsTransition; +import com.oracle.truffle.api.object.Transition.ObjectTypeTransition; +import com.oracle.truffle.api.object.Transition.RemovePropertyTransition; @SuppressWarnings("deprecation") abstract class ObsolescenceStrategy { @@ -133,9 +132,9 @@ private static void ensureSameTypeOrMoreGeneral(Location generalLocation, Locati if (generalLocation == specificLocation) { return; } - if (generalLocation instanceof ExtLocations.AbstractObjectLocation objLocGeneral && specificLocation instanceof ExtLocations.AbstractObjectLocation objLocSpecific) { - ExtLocations.TypeAssumption assGeneral = objLocGeneral.getTypeAssumption(); - ExtLocations.TypeAssumption assSpecific = objLocSpecific.getTypeAssumption(); + if (generalLocation instanceof ObjectLocation objLocGeneral && specificLocation instanceof ObjectLocation objLocSpecific) { + var assGeneral = objLocGeneral.getTypeAssumption(); + var assSpecific = objLocSpecific.getTypeAssumption(); if (assGeneral != assSpecific) { if (!assGeneral.type.isAssignableFrom(assSpecific.type) || assGeneral.nonNull && !assSpecific.nonNull) { // If assignable check failed, merge type assumptions to ensure safety. @@ -148,47 +147,24 @@ private static void ensureSameTypeOrMoreGeneral(Location generalLocation, Locati private static boolean assertLocationInRange(final Shape shape, final Location location) { final LayoutImpl layout = shape.getLayout(); - location.accept(new LocationVisitor() { - @Override - public void visitPrimitiveField(int index, int count) { - assert index + count <= layout.getPrimitiveFieldCount(); - } - - @Override - public void visitObjectField(int index, int count) { - assert index + count <= layout.getObjectFieldCount(); - } - - @Override - public void visitPrimitiveArray(int index, int count) { - } - - @Override - public void visitObjectArray(int index, int count) { + if (location.isFieldLocation()) { + if (location.isObjectLocation()) { + assert location.getIndex() + location.objectFieldCount() <= layout.getObjectFieldCount() : location; + } else { + assert location.getIndex() + location.primitiveFieldCount() <= layout.getPrimitiveFieldCount() : location; } - }); + } return true; } @TruffleBoundary - static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags, Shape s, Property existingProperty) { - if (existingProperty == null) { - if (Flags.isPutIfPresent(putFlags)) { - return false; - } - } else { - if (Flags.isPutIfAbsent(putFlags)) { - return true; - } else if (!Flags.isUpdateFlags(putFlags) && existingProperty.getLocation().canStore(value)) { - existingProperty.getLocation().setSafe(object, value, false, false); - return true; - } - } - return putGenericSlowPath(object, key, value, newPropertyFlags, putFlags, s, existingProperty); + static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int mode) { + return putGeneric(object, key, value, newPropertyFlags, mode, null, null); } - private static boolean putGenericSlowPath(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags, - Shape initialShape, Property propertyOfInitialShape) { + @TruffleBoundary + static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int mode, + Shape cachedShape, Property propertyOfCachedShape) { CompilerAsserts.neverPartOfCompilation(); updateShape(object); Shape oldShape; @@ -196,42 +172,39 @@ private static boolean putGenericSlowPath(DynamicObject object, Object key, Obje Property property; do { oldShape = object.getShape(); - final Property existingProperty = reusePropertyLookup(key, initialShape, propertyOfInitialShape, oldShape); + final Property existingProperty = reusePropertyLookup(key, cachedShape, propertyOfCachedShape, oldShape); if (existingProperty == null) { - if (Flags.isPutIfPresent(putFlags)) { + if (Flags.isPutIfPresent(mode)) { return false; } else { - newShape = defineProperty(oldShape, key, value, newPropertyFlags, existingProperty, putFlags); + newShape = defineProperty(oldShape, key, value, newPropertyFlags, null, mode); property = newShape.getProperty(key); } - } else if (Flags.isPutIfAbsent(putFlags)) { - return true; - } else if (Flags.isUpdateFlags(putFlags) && newPropertyFlags != existingProperty.getFlags()) { - newShape = defineProperty(oldShape, key, value, newPropertyFlags, existingProperty, putFlags); - property = newShape.getProperty(key); - } else { - if (existingProperty.getLocation().canStore(value)) { + } else if (Flags.isPutIfAbsent(mode)) { + return false; + } else if (!Flags.isUpdateFlags(mode) || newPropertyFlags == existingProperty.getFlags()) { + if (existingProperty.getLocation().canStoreValue(value)) { newShape = oldShape; property = existingProperty; } else { - assert !Flags.isUpdateFlags(putFlags) || newPropertyFlags == existingProperty.getFlags(); - newShape = defineProperty(oldShape, key, value, existingProperty.getFlags(), existingProperty, putFlags); + newShape = defineProperty(oldShape, key, value, existingProperty.getFlags(), existingProperty, mode); property = newShape.getProperty(key); } + } else { + newShape = defineProperty(oldShape, key, value, newPropertyFlags, existingProperty, mode); + property = newShape.getProperty(key); } } while (updateShape(object)); assert object.getShape() == oldShape; + assert !Flags.isPutIfAbsent(mode) || oldShape != newShape; + Location location = property.getLocation(); + location.setInternal(object, value, false, oldShape, newShape); + if (oldShape != newShape) { - DynamicObjectSupport.grow(object, oldShape, newShape); - location.setSafe(object, value, false, true); DynamicObjectSupport.setShapeWithStoreFence(object, newShape); updateShape(object); - } else if (Flags.isPutIfAbsent(putFlags)) { - return true; - } else { - location.setSafe(object, value, false, false); } return true; } @@ -508,11 +481,15 @@ public String getMessage() { } } - @TruffleBoundary static boolean updateShape(DynamicObject object) { - boolean changed = checkForObsoleteShapeAndMigrate(object); - // shape should be valid now, but we cannot assert this due to a possible race - return changed; + return updateShape(object, object.getShape()); + } + + static boolean updateShape(DynamicObject object, Shape currentShape) { + if (currentShape.isValid()) { + return false; + } + return migrateObsoleteShape(object, currentShape); } private static Shape ensureValid(Shape newShape) { @@ -697,7 +674,6 @@ private static void reshape(DynamicObject store) { performCopy(store, toCopy); DynamicObjectSupport.setShapeWithStoreFence(store, newShape); - assert store.getShape() == newShape; } catch (StackOverflowError e) { throw STACK_OVERFLOW_ERROR; } @@ -722,12 +698,11 @@ private static void resizeStore(DynamicObject store, final Shape oldShape, Shape } static boolean checkExtensionArrayInvariants(DynamicObject store, Shape newShape) { - assert store.getShape() == newShape; Object[] objectArray = store.getObjectStore(); - assert (objectArray == null && newShape.getObjectArrayCapacity() == 0) || (objectArray != null && objectArray.length == newShape.getObjectArrayCapacity()); + assert ((objectArray == null ? 0 : objectArray.length) >= newShape.getObjectArrayCapacity()); if (newShape.hasPrimitiveArray()) { int[] primitiveArray = store.getPrimitiveStore(); - assert (primitiveArray == null && newShape.getPrimitiveArrayCapacity() == 0) || (primitiveArray != null && primitiveArray.length == newShape.getPrimitiveArrayCapacity()); + assert ((primitiveArray == null ? 0 : primitiveArray.length) >= newShape.getPrimitiveArrayCapacity()); } return true; } @@ -785,7 +760,7 @@ private static List prepareCopy(DynamicObject fromObject, Shape fromShap Property toProperty = toMapIt.next(); Property fromProperty = fromMap.get(toProperty.getKey()); - // copy only if property has a location and it's not the same as the source location + // copy only if property has a location, and it's not the same as the source location if (!toProperty.getLocation().isValue() && !toProperty.getLocation().equals(fromProperty.getLocation())) { Object value = fromProperty.getLocation().get(fromObject, false); toCopy.add(toProperty); @@ -807,21 +782,14 @@ private static void setPropertyInternal(Property toProperty, DynamicObject toObj toProperty.getLocation().set(toObject, value, false, true); } - private static boolean checkForObsoleteShapeAndMigrate(DynamicObject store) { - Shape currentShape = store.getShape(); - if (currentShape.isValid()) { - return false; - } - CompilerDirectives.transferToInterpreter(); - return migrateObsoleteShape(currentShape, store); - } - - private static boolean migrateObsoleteShape(Shape currentShape, DynamicObject store) { + @TruffleBoundary + private static boolean migrateObsoleteShape(DynamicObject store, Shape currentShape) { CompilerAsserts.neverPartOfCompilation(); synchronized (currentShape.getMutex()) { if (!currentShape.isValid()) { assert !currentShape.isShared(); reshape(store); + // shape should be valid now, but we cannot assert this due to a possible race return true; } return false; diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/PropertyGetter.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/PropertyGetter.java index 9a1ffc71b7ed..ac55f8aebd3f 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/PropertyGetter.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/PropertyGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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. * * The Universal Permissive License (UPL), Version 1.0 @@ -93,7 +93,7 @@ public boolean accepts(DynamicObject receiver) { public Object get(DynamicObject receiver) { boolean guard = accepts(receiver); if (guard) { - return location.get(receiver, guard); + return location.getInternal(receiver, expectedShape, guard); } else { throw illegalArgumentException(); } @@ -115,7 +115,7 @@ public Object get(DynamicObject receiver) { public int getInt(DynamicObject receiver) throws UnexpectedResultException { boolean guard = accepts(receiver); if (guard) { - return location.getInt(receiver, guard); + return location.getIntInternal(receiver, expectedShape, guard); } else { throw illegalArgumentException(); } @@ -137,7 +137,7 @@ public int getInt(DynamicObject receiver) throws UnexpectedResultException { public long getLong(DynamicObject receiver) throws UnexpectedResultException { boolean guard = accepts(receiver); if (guard) { - return location.getLong(receiver, guard); + return location.getLongInternal(receiver, expectedShape, guard); } else { throw illegalArgumentException(); } @@ -159,7 +159,7 @@ public long getLong(DynamicObject receiver) throws UnexpectedResultException { public double getDouble(DynamicObject receiver) throws UnexpectedResultException { boolean guard = accepts(receiver); if (guard) { - return location.getDouble(receiver, guard); + return location.getDoubleInternal(receiver, expectedShape, guard); } else { throw illegalArgumentException(); } 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..77b63c5d256f 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. * @@ -1037,7 +1056,7 @@ protected Shape setDynamicType(Object dynamicType) { * @since 0.8 or earlier */ public Shape getRoot() { - return UnsafeAccess.unsafeCast(root, Shape.class, true, true); + return UnsafeAccess.unsafeCast(root, Shape.class, true, true, false); } /** @@ -1617,7 +1636,7 @@ void invalidateLeafAssumption() { /** * {@return a string representation of the object} - * + * * @since 25.1 */ @Override diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/UnsafeAccess.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/UnsafeAccess.java index 3eb70452008c..9f9b2231108e 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/UnsafeAccess.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/UnsafeAccess.java @@ -74,8 +74,8 @@ static boolean booleanCast(int value) { } /** - * Casts the given value to the value of the given type without any checks. The type, nonNull, - * and exact must evaluate to a constant. The condition parameter gives a hint to the compiler + * Casts the given value to the given type without any checks. The type, nonNull, and exact + * parameters must evaluate to a constant. The condition parameter gives a hint to the compiler * under which circumstances this cast can be moved to an earlier location in the program. * * @param value the value that is known to have the specified type @@ -90,8 +90,20 @@ static T unsafeCast(Object value, Class type, boolean condition, boolean return (T) value; } - static T unsafeCast(Object value, Class type, boolean condition, boolean nonNull) { - return unsafeCast(value, type, condition, nonNull, false); + /** + * Casts the given value to the given type without any checks during host compilation. The type, + * nonNull, and exact parameters must evaluate to a constant for the cast to have any effect. + * + * @param value the value that is known to have the specified type + * @param type the specified new type of the value + * @param condition the condition that makes this cast safe also at an earlier location + * @param nonNull whether value is known to never be null + * @param exact whether the value is known to be of exactly the specified class + * @return the value to be cast to the new type + */ + @SuppressWarnings("unchecked") + static T hostUnsafeCast(Object value, Class type, boolean condition, boolean nonNull, boolean exact) { + return (T) value; } /** diff --git a/truffle/src/com.oracle.truffle.api.staticobject/src/com/oracle/truffle/api/staticobject/SomAccessor.java b/truffle/src/com.oracle.truffle.api.staticobject/src/com/oracle/truffle/api/staticobject/SomAccessor.java index 866cef96c6bd..b6783de2a7a0 100644 --- a/truffle/src/com.oracle.truffle.api.staticobject/src/com/oracle/truffle/api/staticobject/SomAccessor.java +++ b/truffle/src/com.oracle.truffle.api.staticobject/src/com/oracle/truffle/api/staticobject/SomAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -44,12 +44,9 @@ final class SomAccessor extends Accessor { - @SuppressWarnings("unused") static final SomAccessor ACCESSOR = new SomAccessor(); + static final SomAccessor ACCESSOR = new SomAccessor(); static final RuntimeSupport RUNTIME = ACCESSOR.runtimeSupport(); static final LanguageSupport LANGUAGE = ACCESSOR.languageSupport(); static final EngineSupport ENGINE = ACCESSOR.engineSupport(); - static final class SomImpl extends SomSupport { - - } } diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java index 876c2b19226b..6b0c89db9bc4 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java @@ -1160,16 +1160,6 @@ protected IOSupport() { public abstract TruffleProcessBuilder createProcessBuilder(Object polylgotLanguageContext, FileSystem fileSystem, List command); } - public abstract static class SomSupport extends Support { - - static final String IMPL_CLASS_NAME = "com.oracle.truffle.api.staticobject.SomAccessor"; - - protected SomSupport() { - super(IMPL_CLASS_NAME); - } - - } - public abstract static class RuntimeSupport { static final Object PERMISSION = new Object(); diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/expression/DSLExpression.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/expression/DSLExpression.java index d6616d21b6cc..36ac0ef6c985 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/expression/DSLExpression.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/expression/DSLExpression.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -264,7 +264,7 @@ public static DSLExpression resolve(DSLExpressionResolver resolver, MessageConta try { expression.accept(resolver); List deprecatedElements = expression.findBoundDeprecatedElements(); - if (!deprecatedElements.isEmpty() && !TruffleSuppressedWarnings.isSuppressed(container.getMessageElement(), "deprecated")) { + if (!deprecatedElements.isEmpty() && !TruffleSuppressedWarnings.isSuppressed(container.getMessageElement(), TruffleSuppressedWarnings.DEPRECATION, "deprecated")) { AnnotationMirror mirror = container.getMessageAnnotation(); AnnotationValue value = null; if (mirror != null && annotationValueName != null) { @@ -277,7 +277,7 @@ public static DSLExpression resolve(DSLExpressionResolver resolver, MessageConta b.append(String.format("%n - ")); b.append(relativeName); } - b.append(String.format("%nUpdate the usage of the elements or suppress the warning with @SuppressWarnings(\"deprecated\").")); + b.append(String.format("%nUpdate the usage of the elements or suppress the warning with @SuppressWarnings(\"" + TruffleSuppressedWarnings.DEPRECATION + "\").")); container.addWarning(value, b.toString()); } return expression; 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 6092a99e405b..a4c0d056b560 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 @@ -85,7 +85,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; @@ -188,8 +187,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..4dfcb950b2fd 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.execute(receiver, nameTS, null); 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..d4e5f4f29af7 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.execute(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..1adba98eea8a 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,14 @@ 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 key = fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING); + Object result = getNode.execute(this, key, null); if (result == null) { /* Property does not exist. */ throw UnknownIdentifierException.create(name); @@ -222,12 +220,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) { + putNode.execute(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..258791a6b295 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,28 +40,34 @@ */ package org.graalvm.truffle.benchmark; +import java.lang.invoke.MethodHandles; +import java.util.Objects; import java.util.stream.IntStream; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Engine; import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +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; @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 String[] PROPERTY_KEYS = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new); + static final String SOME_KEY = PROPERTY_KEYS[0]; + static final String SOME_KEY_INT = PROPERTY_KEYS[1]; + static final String SOME_KEY_LONG = PROPERTY_KEYS[2]; + static final String SOME_KEY_DOUBLE = PROPERTY_KEYS[3]; private static final class MyDynamicObject extends DynamicObject { private MyDynamicObject(Shape shape) { @@ -69,18 +75,23 @@ private MyDynamicObject(Shape shape) { } } + private static final class MyDynamicObjectWithFields extends DynamicObject { + @DynamicField private long lf1; + @DynamicField private long lf2; + @DynamicField private long lf3; + @DynamicField private Object of0; + @DynamicField private Object of1; + + MyDynamicObjectWithFields(Shape shape) { + super(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 - public void tearDown() { - engine.close(); - } - private void assertSameShape(int i, Shape actualShape) { Shape expectedShape = expectedShapes[i]; if (expectedShape == null) { @@ -93,16 +104,15 @@ 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(); - } - - @TearDown - public void tearDown() { - context.close(); + object = new MyDynamicObject(shared.rootShape); + for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { + String key = PROPERTY_KEYS[i]; + DynamicObject.PutNode.getUncached().execute(object, key, "testValue"); + } } } @@ -112,13 +122,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().execute(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } - perThread.context.leave(); } /** @@ -127,12 +135,106 @@ 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().execute(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } - perThread.context.leave(); + } + + @State(Scope.Benchmark) + public static class AccessNodeBenchState { + + @Param({"false", "true"}) boolean field; + + Shape rootShape; + DynamicObject object; + + final DynamicObject.GetNode getNode = DynamicObject.GetNode.create(); + final DynamicObject.PutNode putNode = DynamicObject.PutNode.create(); + + @Setup + public void setup() { + rootShape = Shape.newBuilder().layout(field ? MyDynamicObjectWithFields.class : MyDynamicObject.class, LOOKUP).build(); + object = field ? new MyDynamicObjectWithFields(rootShape) : new MyDynamicObject(rootShape); + + for (int i = 0; i < 10; i++) { + String key = PROPERTY_KEYS[i]; + if (key.equals(SOME_KEY_INT)) { + DynamicObject.PutNode.getUncached().execute(object, key, i); + } else if (key.equals(SOME_KEY_LONG)) { + DynamicObject.PutNode.getUncached().execute(object, key, (long) i); + } else if (key.equals(SOME_KEY_DOUBLE)) { + DynamicObject.PutNode.getUncached().execute(object, key, (double) i); + } else { + DynamicObject.PutNode.getUncached().execute(object, key, "testValue"); + } + Objects.requireNonNull(DynamicObject.GetNode.getUncached().execute(object, key, null), key); + } + } + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Object get(AccessNodeBenchState state) { + DynamicObject object = state.object; + return state.getNode.execute(object, SOME_KEY, null); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int getInt(AccessNodeBenchState state) throws UnexpectedResultException { + DynamicObject object = state.object; + return state.getNode.executeInt(object, SOME_KEY_INT, null); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long getLong(AccessNodeBenchState state) throws UnexpectedResultException { + DynamicObject object = state.object; + return state.getNode.executeLong(object, SOME_KEY_LONG, null); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public double getDouble(AccessNodeBenchState state) throws UnexpectedResultException { + DynamicObject object = state.object; + return state.getNode.executeDouble(object, SOME_KEY_DOUBLE, null); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void put(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.execute(object, SOME_KEY, "updated value"); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void putInt(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.execute(object, SOME_KEY_INT, 42); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void putLong(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.execute(object, SOME_KEY_LONG, (long) 42); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void putDouble(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.execute(object, SOME_KEY_DOUBLE, (double) 42); } }