From 921aa67f0daf4ca8c0e0db88c1a1a8a223af45aa Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 18 Sep 2025 12:52:48 +0200 Subject: [PATCH 01/33] [GR-36894] Add nodes for DynamicObject * Faster in interpreter and better footprint than DynamicObjectLibrary, because Truffle libraries do not support host inlining and have footprint overhead. * Use a more optimized guard for set() with 2 Shapes. * Only inline cache 1 Shape for GetShapeFlagsNode. * An extra read seems better than more control flow. * Migrate SL to DynamicObject nodes. * Check the old Shape Assumption in the @Specialization.assumptions * This avoids an extra call to putGeneric() for host inlining, and ensures the specialization gets removed if the oldShape becomes invalid. * Use putFlags instead of cachedPutFlags to fold during host inlining. * This saves around 6 node cost for DynamicObject.PutFixedKeyNode. * It is only observable if multiple PutNode#put*() methods are used (= different put flags) in the native image. * Remove calls to Location#isFinal(), it's always false. * Link the replacement nodes in DynamicObjectLibrary. * Remove {PutNode,PutFixedKeyNode}#putInt, it boxes anyway so there is no value * Also other variants like putDouble are missing. * Use @Fallback to avoid potential issues with a Shape becoming invalid concurrently. * Remove KeyEqualsNode and just compare keys by identity * Reduces subTreeCost of SLReadPropertyNode.readSLObject from 307 to 245. * Use the same Shape check both in guards and specialization body. * Merge inline caches of PutNode and InlinedConstKey into one. * Merge inline caches of {PutNode,PutFixedKeyNode} and PutCache into one. * Handle invalid shapes without removing all cached specializations * Removing the specialization instance itself is fine, the problem is removing all cache specialization due to the `replaces` on doUncached. * Add a property read micro benchmark for SL * Port documentation from DynamicObjectLibrary to DynamicObject nodes * Update docs, 16 bit are actually allowed for user Shape flags * Migrate DynamicObjectBenchmark and DynamicObjectPartialEvaluationTest * [EXPERIMENT] Manually inline ExtLocations.ObjectArrayLocation in GetNode * [EXPERIMENT] Manually inline ExtLocations.IntArrayLocation in GetNode * [EXPERIMENT] Manually inline ExtLocations.ObjectArrayLocation in PutNode * Cache the Location instead of Property in DynamicObject nodes to avoid extra indirection in interpreter * Add interpreter benchmarks for DynamicObject.{GetNode,PutNode} * It is enough to check the new Shape Assumption in PutNode * If the old Shape Assumption is invalidated, it will also invalidate the new Shape Assumption. * Fix bug where doCached was used with an old obsolete shape * [GR-69326] Avoid duplicate property lookup in DynamicObject.PutNode * Extract reusePropertyLookup() and fix RemoveKeyNode * Run all DynamicObject tests with the new DynamicObject nodes too. * Ignore spotbugs warnings in generated code. * Add separate PutConstantNode. Co-authored-by: Andreas Woess --- .../DynamicObjectPartialEvaluationTest.java | 16 +- .../benchmarks/interpreter/property_read.sl | 22 + truffle/mx.truffle/suite.py | 1 + .../api/object/test/CachedFallbackTest.java | 61 +- .../api/object/test/ConstantLocationTest.java | 12 +- .../api/object/test/DOTestAsserts.java | 13 +- .../test/DynamicObjectConstructorTest.java | 17 +- .../object/test/DynamicObjectLibraryTest.java | 55 +- .../object/test/DynamicObjectNodesTest.java | 1048 ++++++++ .../api/object/test/DynamicTypeTest.java | 8 +- .../truffle/api/object/test/GR42603.java | 31 +- .../truffle/api/object/test/GR52036.java | 39 +- .../api/object/test/ImplicitCastTest.java | 16 +- .../api/object/test/LeakCheckTest.java | 59 +- .../truffle/api/object/test/LocationTest.java | 20 +- .../test/ObjectModelRegressionTest.java | 53 +- .../test/ParametrizedDynamicObjectTest.java | 265 ++ .../test/PolymorphicPrimitivesTest.java | 20 +- .../api/object/test/PropertyGetterTest.java | 26 +- .../api/object/test/RemoveKeyTest.java | 18 +- .../api/object/test/SharedShapeTest.java | 20 +- .../test/TestNestedDispatchGetNode.java | 87 + .../truffle/api/object/DynamicObject.java | 2190 ++++++++++++++++- .../api/object/DynamicObjectLibrary.java | 28 +- .../api/object/DynamicObjectLibraryImpl.java | 2 +- .../api/object/DynamicObjectSupport.java | 6 +- .../oracle/truffle/api/object/Location.java | 9 +- .../api/object/ObsolescenceStrategy.java | 13 +- .../com/oracle/truffle/api/object/Shape.java | 21 +- .../src/com/oracle/truffle/sl/SLLanguage.java | 7 +- .../nodes/expression/SLReadPropertyNode.java | 10 +- .../nodes/expression/SLWritePropertyNode.java | 10 +- .../oracle/truffle/sl/runtime/SLObject.java | 29 +- .../benchmark/DynamicObjectBenchmark.java | 44 +- 34 files changed, 4011 insertions(+), 265 deletions(-) create mode 100644 truffle/benchmarks/interpreter/property_read.sl create mode 100644 truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java create mode 100644 truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java create mode 100644 truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java index 23612ccd0231..e726ff538e46 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.runtime.OptimizedCallTarget; @@ -79,7 +78,7 @@ private TestDynamicObject newInstanceWithoutFields() { @Test public void testFieldLocation() { TestDynamicObject obj = newInstanceWithFields(); - DynamicObjectLibrary.getUncached().put(obj, "key", 22); + DynamicObject.PutNode.getUncached().put(obj, "key", 22); Object[] args = {obj, 22}; OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testFieldStoreLoad"); @@ -107,7 +106,7 @@ public void testFieldLocation() { @Test public void testArrayLocation() { TestDynamicObject obj = newInstanceWithoutFields(); - DynamicObjectLibrary.getUncached().put(obj, "key", 22); + DynamicObject.PutNode.getUncached().put(obj, "key", 22); Object[] args = {obj, 22}; OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testArrayStoreLoad"); @@ -137,7 +136,8 @@ private static OptimizedCallTarget makeCallTarget(AbstractTestNode testNode, Str } static class TestDynamicObjectGetAndPutNode extends AbstractTestNode { - @Child DynamicObjectLibrary dynamicObjectLibrary = DynamicObjectLibrary.getFactory().createDispatched(3); + @Child DynamicObject.GetNode getNode = DynamicObject.GetNode.create(); + @Child DynamicObject.PutNode putNode = DynamicObject.PutNode.create(); @Override public int execute(VirtualFrame frame) { @@ -148,7 +148,7 @@ public int execute(VirtualFrame frame) { DynamicObject obj = (DynamicObject) arg0; if (frame.getArguments().length > 1) { Object arg1 = frame.getArguments()[1]; - dynamicObjectLibrary.put(obj, "key", (int) arg1); + putNode.put(obj, "key", (int) arg1); } int val; while (true) { @@ -156,14 +156,14 @@ public int execute(VirtualFrame frame) { if (val >= 42) { break; } - dynamicObjectLibrary.put(obj, "key", val + 2); + putNode.put(obj, "key", val + 2); } return val; } private int getInt(DynamicObject obj, Object key) { try { - return dynamicObjectLibrary.getIntOrDefault(obj, key, null); + return getNode.getIntOrDefault(obj, key, null); } catch (UnexpectedResultException e) { throw CompilerDirectives.shouldNotReachHere(); } diff --git a/truffle/benchmarks/interpreter/property_read.sl b/truffle/benchmarks/interpreter/property_read.sl new file mode 100644 index 000000000000..f3fabe7ba2ce --- /dev/null +++ b/truffle/benchmarks/interpreter/property_read.sl @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + */ + +function propRead(obj) { + i = 0; + while(i < 10000000) { + i = i + obj.foo; + } + return i; +} + +function run() { + obj = new(); + obj.foo = 1; + return propRead(obj); +} + +function main() { + return run(); +} diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index 9936319637bd..4aad66bc6d30 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -686,6 +686,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.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java index 27b0cf05e631..f25eac8bb9ef 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/CachedFallbackTest.java @@ -51,9 +51,62 @@ import org.junit.Test; import com.oracle.truffle.api.test.AbstractLibraryTest; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import java.util.List; + +@RunWith(Parameterized.class) public class CachedFallbackTest extends AbstractLibraryTest { + public enum TestRun { + LIBRARY, + NODES; + } + + @Parameter(0) public TestRun run; + + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.values()); + } + + record CachedGetNodeWrapper(CachedGetNode node, DynamicObject.GetNode getNode) { + public Object execute(DynamicObject obj, Object key) { + if (node != null) { + return node.execute(obj, key); + } else { + return getNode.getOrDefault(obj, key, null); + } + } + } + + record CachedPutNodeWrapper(CachedPutNode node, DynamicObject.PutNode putNode) { + public void execute(DynamicObject obj, Object key, Object value) { + if (node != null) { + node.execute(obj, key, value); + } else { + putNode.put(obj, key, value); + } + } + } + + CachedGetNodeWrapper getNode() { + return switch (run) { + case LIBRARY -> new CachedGetNodeWrapper(adopt(CachedGetNodeGen.create()), null); + case NODES -> new CachedGetNodeWrapper(null, DynamicObject.GetNode.create()); + }; + } + + CachedPutNodeWrapper putNode() { + return switch (run) { + case LIBRARY -> new CachedPutNodeWrapper(adopt(CachedPutNodeGen.create()), null); + case NODES -> new CachedPutNodeWrapper(null, DynamicObject.PutNode.create()); + }; + } + @Test public void testMixedReceiverTypeSameShape() { Shape shape = Shape.newBuilder().build(); @@ -62,13 +115,13 @@ public void testMixedReceiverTypeSameShape() { String key = "key"; String val = "value"; - CachedPutNode writeNode = adopt(CachedPutNodeGen.create()); + var writeNode = putNode(); writeNode.execute(o1, key, val); writeNode.execute(o2, key, val); assertSame("expected same shape", o1.getShape(), o2.getShape()); - CachedGetNode readNode = adopt(CachedGetNodeGen.create()); + var readNode = getNode(); assertEquals(val, readNode.execute(o1, key)); assertEquals(val, readNode.execute(o2, key)); } @@ -93,7 +146,7 @@ public void testTransition() { assertSame("expected same shape", o1.getShape(), o2.getShape()); - CachedGetNode readNode = adopt(CachedGetNodeGen.create()); + var readNode = getNode(); assertEquals(val1, readNode.execute(o1, key1)); assertEquals(val1, readNode.execute(o2, key1)); assertEquals(val2, readNode.execute(o1, key2)); @@ -120,7 +173,7 @@ public void testMixedReceiverTypeSameShapeWithFallback() { assertSame("expected same shape", o1.getShape(), o2.getShape()); - CachedGetNode readNode = adopt(CachedGetNodeGen.create()); + var readNode = getNode(); assertEquals(val1, readNode.execute(o1, key1)); assertEquals(val1, readNode.execute(o2, key1)); assertEquals(val2, readNode.execute(o1, key2)); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ConstantLocationTest.java index 36ecd976a881..ca6931607739 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 @@ -55,11 +55,9 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; - @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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary library = createLibrary(object); library.put(object, "other", "otherValue"); Property otherProperty = object.getShape().getProperty("other"); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java index 134675065eea..d9877972a193 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DOTestAsserts.java @@ -56,7 +56,6 @@ import java.util.stream.Stream; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Location; import com.oracle.truffle.api.object.Property; import com.oracle.truffle.api.object.Shape; @@ -159,21 +158,21 @@ private static String shapeLocationsToString(Shape shape) { return sb.toString(); } + @SuppressWarnings("deprecation") public static Map archive(DynamicObject object) { - DynamicObjectLibrary lib = DynamicObjectLibrary.getFactory().getUncached(object); Map archive = new HashMap<>(); - for (Property property : lib.getPropertyArray(object)) { - archive.put(property.getKey(), lib.getOrDefault(object, property.getKey(), null)); + for (Property property : object.getShape().getPropertyList()) { + archive.put(property.getKey(), property.get(object, false)); } return archive; } + @SuppressWarnings("deprecation") public static boolean verifyValues(DynamicObject object, Map archive) { - DynamicObjectLibrary lib = DynamicObjectLibrary.getFactory().getUncached(object); - for (Property property : lib.getPropertyArray(object)) { + for (Property property : object.getShape().getPropertyList()) { Object key = property.getKey(); Object before = archive.get(key); - Object after = lib.getOrDefault(object, key, null); + Object after = property.get(object, false); assertEquals("before != after for key: " + key, after, before); } return true; diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java index e24688056cfc..e85cdb0d7976 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectConstructorTest.java @@ -44,14 +44,21 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.lang.invoke.MethodHandles; +import java.util.List; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Shape; import org.junit.Test; +import com.oracle.truffle.api.object.Shape; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractLibraryTest; +@RunWith(Parameterized.class) +public class DynamicObjectConstructorTest extends ParametrizedDynamicObjectTest { -public class DynamicObjectConstructorTest extends AbstractLibraryTest { + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.UNCACHED_ONLY); + } @Test public void testIncompatibleShape() { @@ -65,7 +72,7 @@ public void testIncompatibleShape() { public void testNonEmptyShape() { Shape emptyShape = Shape.newBuilder().layout(TestDynamicObjectDefault.class, MethodHandles.lookup()).build(); TestDynamicObjectDefault obj = new TestDynamicObjectDefault(emptyShape); - DynamicObjectLibrary.getUncached().put(obj, "key", "value"); + uncachedLibrary().put(obj, "key", "value"); Shape nonEmptyShape = obj.getShape(); assertFails(() -> new TestDynamicObjectDefault(nonEmptyShape), IllegalArgumentException.class, diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java index 05864f9ab992..81274f41b541 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 @@ -68,11 +68,10 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.nodes.UnexpectedResultException; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) -public class DynamicObjectLibraryTest extends AbstractParametrizedLibraryTest { +public class DynamicObjectLibraryTest extends ParametrizedDynamicObjectTest { @Parameter(1) public Supplier emptyObjectSupplier; private DynamicObject createEmpty() { @@ -105,21 +104,23 @@ private static void addParams(Collection params, Supplier getKeyList(DynamicObject obj) { - DynamicObjectLibrary objectLibrary = createLibrary(DynamicObjectLibrary.class, obj); + DynamicObjectLibrary 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..b90d740323d6 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -0,0 +1,1048 @@ +/* + * 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.HasShapeFlagsNode createHasShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.HasShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.HasShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.ResetShapeNode createResetShapeNode() { + return switch (run) { + case CACHED -> DynamicObject.ResetShapeNode.create(); + case UNCACHED -> DynamicObject.ResetShapeNode.getUncached(); + }; + } + + private DynamicObject.SetShapeFlagsNode createSetShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.SetShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.SetShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.AddShapeFlagsNode createAddShapeFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.AddShapeFlagsNode.create(); + case UNCACHED -> DynamicObject.AddShapeFlagsNode.getUncached(); + }; + } + + private DynamicObject.IsSharedNode createIsSharedNode() { + return switch (run) { + case CACHED -> DynamicObject.IsSharedNode.create(); + case UNCACHED -> DynamicObject.IsSharedNode.getUncached(); + }; + } + + private DynamicObject.MarkSharedNode createMarkSharedNode() { + return switch (run) { + case CACHED -> DynamicObject.MarkSharedNode.create(); + case UNCACHED -> DynamicObject.MarkSharedNode.getUncached(); + }; + } + + private DynamicObject.SetPropertyFlagsNode createSetPropertyFlagsNode() { + return switch (run) { + case CACHED -> DynamicObject.SetPropertyFlagsNode.create(); + case UNCACHED -> DynamicObject.SetPropertyFlagsNode.getUncached(); + }; + } + + private DynamicObject.SetPropertyFlagsNode createSetPropertyFlagsNodeForKey(@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.getOrDefault(o1, k1, null)); + assertEquals(v1, getNode.getIntOrDefault(o1, k1, null)); + assertEquals(v1, getNode.getOrDefault(o2, k1, null)); + assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + + String v2 = "asdf"; + uncachedSet(o1, k1, v2); + + getNode = createGetNode(); + assertEquals(v2, getNode.getOrDefault(o1, k1, null)); + try { + getNode.getIntOrDefault(o1, k1, null); + fail(); + } catch (UnexpectedResultException e) { + assertEquals(v2, e.getResult()); + } + assertEquals(v1, getNode.getOrDefault(o2, k1, null)); + assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + + String missingKey = "missing"; + var getMissingKey = createGetNode(); + assertEquals(null, getMissingKey.getOrDefault(o1, missingKey, null)); + assertEquals(404, getMissingKey.getIntOrDefault(o1, missingKey, 404)); + var getMissingKey2 = createGetNode(); + assertEquals(null, getMissingKey2.getOrDefault(o1, missingKey, null)); + assertEquals(404, getMissingKey2.getIntOrDefault(o1, missingKey, 404)); + } + + @Test + public void testPut1() { + DynamicObject o1 = createEmpty(); + String key1 = "key1"; + int intval1 = 42; + int intval2 = 43; + uncachedPut(o1, key1, intval1, 0); + DynamicObject o2 = createEmpty(); + uncachedPut(o2, key1, intval1, 0); + assertSame(o1.getShape(), o2.getShape()); + + var setNode = createPutNode(); + setNode.put(o1, key1, intval2); + assertEquals(intval2, uncachedGet(o1, key1)); + setNode.put(o1, key1, intval1); + assertEquals(intval1, uncachedGet(o1, key1)); + setNode.put(o2, key1, intval2); + assertEquals(intval2, uncachedGet(o2, key1)); + setNode.put(o2, key1, intval1); + assertEquals(intval1, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + + String strval1 = "asdf"; + setNode.put(o1, key1, strval1); + assertEquals(strval1, uncachedGet(o1, key1)); + + String key2 = "key2"; + String strval2 = "qwer"; + var setNode2 = createPutNode(); + setNode2.put(o1, key2, strval2); + assertEquals(strval2, uncachedGet(o1, key2)); + setNode2.put(o1, key2, intval1); + assertEquals(intval1, uncachedGet(o1, key2)); + + var setNode3 = createPutNode(); + setNode3.put(o1, key2, strval1); + assertEquals(strval1, uncachedGet(o1, key2)); + // assertTrue(setNode3.accepts(o1)); + var setNode4 = createPutNode(); + setNode4.put(o1, key2, intval2); + assertEquals(intval2, uncachedGet(o1, key2)); + // assertTrue(setNode4.accepts(o1)); + } + + @Test + public void testPutIfPresent() { + DynamicObject o1 = createEmpty(); + String key1 = "key1"; + int intval1 = 42; + int intval2 = 43; + uncachedPut(o1, key1, intval1, 0); + DynamicObject o2 = createEmpty(); + uncachedPut(o2, key1, intval1, 0); + assertSame(o1.getShape(), o2.getShape()); + + var setNode = createPutNode(); + assertTrue(setNode.putIfPresent(o1, key1, intval2)); + assertEquals(intval2, uncachedGet(o1, key1)); + assertTrue(setNode.putIfPresent(o1, key1, intval1)); + assertEquals(intval1, uncachedGet(o1, key1)); + assertTrue(setNode.putIfPresent(o2, key1, intval2)); + assertEquals(intval2, uncachedGet(o2, key1)); + assertTrue(setNode.putIfPresent(o2, key1, intval1)); + assertEquals(intval1, uncachedGet(o2, key1)); + assertSame(o1.getShape(), o2.getShape()); + + String strval1 = "asdf"; + setNode.put(o1, key1, strval1); + assertEquals(strval1, uncachedGet(o1, key1)); + + String key2 = "key2"; + String strval2 = "qwer"; + var setNode2 = createPutNode(); + var containsKeyNode2 = createContainsKeyNode(); + assertFalse(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); + assertFalse(setNode2.putIfPresent(o1, key2, strval2)); + // assertTrue(setNode2.accepts(o1)); + assertFalse(containsKeyNode2.execute(o1, key2)); + assertEquals(null, uncachedGet(o1, key2)); + + setNode2.put(o1, key2, strval2); + assertTrue(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); + assertEquals(strval2, uncachedGet(o1, key2)); + + var setNode3 = createPutNode(); + assertTrue(setNode3.putIfPresent(o1, key2, intval1)); + assertEquals(intval1, uncachedGet(o1, key2)); + } + + @Test + public void testPut2() { + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + uncachedPut(o3, k1, v1, 0); + + var setNode1 = createPutNode(); + setNode1.put(o1, k1, v2); + assertEquals(v2, uncachedGet(o1, k1)); + assertEquals(0, uncachedGetProperty(o1, k1).getFlags()); + setNode1.put(o1, k1, v1); + assertEquals(v1, uncachedGet(o1, k1)); + setNode1.put(o2, k1, v2); + assertEquals(v2, uncachedGet(o2, k1)); + setNode1.put(o2, k1, v1); + assertEquals(v1, uncachedGet(o2, k1)); + assertSame(o1.getShape(), o2.getShape()); + + assertEquals(v1, uncachedGet(o3, k1)); + assertSame(o1.getShape(), o3.getShape()); + uncachedPut(o3, k1, v1); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(0, uncachedGetProperty(o3, k1).getFlags()); + assertSame(o1.getShape(), o3.getShape()); + + String v3 = "asdf"; + setNode1.put(o1, k1, v3); + assertEquals(v3, uncachedGet(o1, k1)); + + String k2 = "key2"; + String v4 = "qwer"; + var setNode2 = createPutNode(); + + setNode2.put(o1, k2, v4); + assertEquals(v4, uncachedGet(o1, k2)); + setNode2.put(o1, k2, v1); + assertEquals(v1, uncachedGet(o1, k2)); + + int f2 = 0x42; + var setNode3 = createPutNode(); + + setNode3.putWithFlags(o3, k1, v1, f2); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(f2, uncachedGetProperty(o3, k1).getFlags()); + } + + @Test + public void testPutWithFlags1() { + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + uncachedPut(o3, k1, v1, 0); + + int flags = 0xf; + var setNode1 = createPutNode(); + setNode1.putWithFlags(o1, k1, v2, flags); + assertEquals(v2, uncachedGet(o1, k1)); + assertEquals(flags, uncachedGetProperty(o1, k1).getFlags()); + setNode1.putWithFlags(o1, k1, v1, flags); + assertEquals(v1, uncachedGet(o1, k1)); + setNode1.putWithFlags(o2, k1, v2, flags); + assertEquals(v2, uncachedGet(o2, k1)); + setNode1.putWithFlags(o2, k1, v1, flags); + assertEquals(v1, uncachedGet(o2, k1)); + assertSame(o1.getShape(), o2.getShape()); + + assertEquals(v1, uncachedGet(o3, k1)); + assertNotSame(o1.getShape(), o3.getShape()); + uncachedPut(o3, k1, v1, flags); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(flags, uncachedGetProperty(o3, k1).getFlags()); + // assertSame(o1.getShape(), o3.getShape()); + + String v3 = "asdf"; + setNode1.putWithFlags(o1, k1, v3, flags); + assertEquals(v3, uncachedGet(o1, k1)); + + String k2 = "key2"; + String v4 = "qwer"; + var setNode2 = createPutNode(); + + setNode2.put(o1, k2, v4); + assertEquals(v4, uncachedGet(o1, k2)); + setNode2.put(o1, k2, v1); + assertEquals(v1, uncachedGet(o1, k2)); + + int f2 = 0x42; + var setNode3 = createPutNode(); + + setNode3.putWithFlags(o3, k1, v1, f2); + assertEquals(v1, uncachedGet(o3, k1)); + assertEquals(f2, uncachedGetProperty(o3, k1).getFlags()); + } + + @Test + public void testCopyProperties() { + var getNode = createGetNode(); + var setNode = createPutNode(); + var copyPropertiesNode = createCopyPropertiesNode(); + var getPropertyFlagsNode = createGetPropertyFlagsNode(); + + DynamicObject o1 = createEmpty(); + setNode.putWithFlags(o1, "key1", 1, 1); + setNode.putWithFlags(o1, "key2", 2, 2); + DynamicObject o2 = createEmpty(); + copyPropertiesNode.execute(o1, o2); + assertEquals(1, getNode.getOrNull(o1, "key1")); + assertEquals(2, getNode.getOrNull(o1, "key2")); + assertEquals(1, getPropertyFlagsNode.execute(o1, "key1", 0)); + assertEquals(2, getPropertyFlagsNode.execute(o1, "key2", 0)); + assertSame(o1.getShape(), o2.getShape()); + } + + @Test + public void testTypeIdAndShapeFlags() { + Object myType = newObjectType(); + int flags = 42; + String key = "key1"; + + DynamicObject.SetDynamicTypeNode setDynamicTypeNode = createSetDynamicTypeNode(); + DynamicObject.GetDynamicTypeNode getDynamicTypeNode = createGetDynamicTypeNode(); + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + var putNode = createPutNode(); + + DynamicObject o1 = createEmpty(); + setDynamicTypeNode.execute(o1, myType); + assertSame(myType, getDynamicTypeNode.execute(o1)); + + DynamicObject o2 = createEmpty(); + setDynamicTypeNode.execute(o2, myType); + assertSame(myType, getDynamicTypeNode.execute(o2)); + assertSame(o1.getShape(), o2.getShape()); + + DynamicObject o3 = createEmpty(); + setShapeFlagsNode.execute(o3, flags); + setDynamicTypeNode.execute(o3, myType); + assertSame(myType, getDynamicTypeNode.execute(o3)); + assertEquals(flags, getShapeFlagsNode.execute(o3)); + + DynamicObject o4 = createEmpty(); + setShapeFlagsNode.execute(o4, flags); + putNode.put(o4, key, 42); + setDynamicTypeNode.execute(o4, myType); + putNode.put(o4, key, "value"); + assertSame(myType, getDynamicTypeNode.execute(o4)); + + assertSame(myType, getDynamicTypeNode.execute(o4)); + assertSame(myType, getDynamicTypeNode.execute(o4)); + Object myType2 = newObjectType(); + setDynamicTypeNode.execute(o4, myType2); + assertSame(myType2, getDynamicTypeNode.execute(o4)); + } + + @Test + public void testShapeFlags() { + final int flags = 0b101010; + + DynamicObject.SetDynamicTypeNode setDynamicTypeNode = createSetDynamicTypeNode(); + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.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.HasShapeFlagsNode hasShapeFlagsNode = createHasShapeFlagsNode(); + DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); + DynamicObject.AddShapeFlagsNode addShapeFlagsNode = createAddShapeFlagsNode(); + + DynamicObject o1 = createEmpty(); + setShapeFlagsNode.execute(o1, flags); + assertEquals(flags, getShapeFlagsNode.execute(o1)); + assertTrue(hasShapeFlagsNode.execute(o1, flags)); + assertTrue(hasShapeFlagsNode.execute(o1, 0b10)); + assertFalse(hasShapeFlagsNode.execute(o1, 0b11)); + addShapeFlagsNode.execute(o1, 0b1); + assertTrue(hasShapeFlagsNode.execute(o1, 0b11)); + assertEquals(flags | 0b1, getShapeFlagsNode.execute(o1)); + } + + @Test + public void testMakeShared() { + String key = "key"; + + DynamicObject.IsSharedNode isSharedNode = createIsSharedNode(); + DynamicObject.MarkSharedNode markSharedNode = createMarkSharedNode(); + var putNode = createPutNode(); + var containsKeyNode = createContainsKeyNode(); + + DynamicObject o1 = createEmpty(); + assertFalse(isSharedNode.execute(o1)); + markSharedNode.execute(o1); + assertTrue(isSharedNode.execute(o1)); + putNode.put(o1, key, "value"); + assertTrue(isSharedNode.execute(o1)); + assertTrue(containsKeyNode.execute(o1, key)); + } + + @Test + public void testPropertyFlags() { + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + int f1 = 0xf; + int f2 = 0x10; + int f3 = 0x1f; + + DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode = createSetPropertyFlagsNodeForKey(k1); + DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode = createGetPropertyFlagsNodeForKey(k1); + DynamicObject.GetPropertyNode getPropertyNode = createGetPropertyNodeForKey(k1); + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, k1, v1, 0); + assertTrue(setPropertyFlagsNode.execute(o1, k1, f1)); + assertEquals(f1, getPropertyFlagsNode.execute(o1, k1, -1)); + assertEquals(f1, uncachedGetProperty(o1, k1).getFlags()); + assertTrue(updatePropertyFlags(getPropertyNode, setPropertyFlagsNode, o1, k1, f -> f | f2)); + assertEquals(f3, getPropertyFlagsNode.execute(o1, k1, -1)); + assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); + + Shape before = o1.getShape(); + assertTrue(setPropertyFlagsNode.execute(o1, k1, f3)); + assertFalse(updatePropertyFlags(getPropertyNode, setPropertyFlagsNode, o1, k1, f -> f | f2)); + assertEquals(f3, getPropertyFlagsNode.execute(o1, k1, -1)); + assertEquals(f3, uncachedGetProperty(o1, k1).getFlags()); + assertSame(before, o1.getShape()); + + DynamicObject o2 = createEmpty(); + uncachedPut(o2, k1, v2, 0); + assertTrue(setPropertyFlagsNode.execute(o2, k1, f1)); + assertEquals(f1, getPropertyFlagsNode.execute(o2, k1, -1)); + assertTrue(updatePropertyFlags(getPropertyNode, setPropertyFlagsNode, o2, k1, f -> f | f2)); + assertEquals(f3, getPropertyFlagsNode.execute(o2, k1, -1)); + assertSame(o1.getShape(), o2.getShape()); + + DynamicObject o3 = createEmpty(); + assertFalse(setPropertyFlagsNode.execute(o3, k1, f1)); + } + + private static boolean updatePropertyFlags(DynamicObject.GetPropertyNode getPropertyNode, + DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode, + DynamicObject obj, + String key, + IntUnaryOperator updateFunction) { + Property property = getPropertyNode.execute(obj, key); + if (property == null) { + return false; + } + int oldFlags = property.getFlags(); + int newFlags = updateFunction.applyAsInt(oldFlags); + if (oldFlags == newFlags) { + return false; + } + return setPropertyFlagsNode.execute(obj, key, newFlags); + } + + @Test + public void testRemove() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + String k1 = "key1"; + String k2 = "key2"; + String k3 = "key3"; + String k4 = "key4"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, k1, v1, 0); + uncachedPut(o1, k2, v2, 0); + uncachedPut(o1, k3, v3, 0); + + var removeKey1 = createRemoveKeyNode(); + var removeKey3 = createRemoveKeyNode(); + var removeKey4 = createRemoveKeyNode(); + var getKey1 = createGetNode(); + var getKey2 = createGetNode(); + + assertFalse(removeKey4.execute(o1, k4)); + assertTrue(removeKey3.execute(o1, k3)); + assertEquals(Arrays.asList(k1, k2), getKeyList(o1)); + uncachedPut(o1, k3, v3, 0); + assertEquals(Arrays.asList(k1, k2, k3), getKeyList(o1)); + assertTrue(removeKey3.execute(o1, k3)); + assertEquals(Arrays.asList(k1, k2), getKeyList(o1)); + uncachedPut(o1, k3, v3, 0); + assertTrue(removeKey1.execute(o1, k1)); + assertEquals(Arrays.asList(k2, k3), getKeyList(o1)); + uncachedPut(o1, k1, v1, 0); + assertEquals(Arrays.asList(k2, k3, k1), getKeyList(o1)); + assertTrue(removeKey3.execute(o1, k3)); + assertEquals(Arrays.asList(k2, k1), getKeyList(o1)); + assertEquals(v1, getKey1.getOrDefault(o1, k1, null)); + assertEquals(v2, getKey2.getOrDefault(o1, k2, null)); + } + + @Test + public void testResetShape() { + int v1 = 42; + int v2 = 43; + + DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); + DynamicObject.ResetShapeNode resetShapeNode = createResetShapeNode(); + + DynamicObject o1 = createEmpty(); + Shape emptyShape = o1.getShape(); + uncachedPut(o1, "key1", v1, 0); + uncachedPut(o1, "key2", v2, 0); + resetShapeNode.execute(o1, emptyShape); + assertSame(emptyShape, o1.getShape()); + + assumeTrue("new layout only", isNewLayout()); + int flags = 0xf; + DynamicObject o2 = createEmpty(); + Shape newEmptyShape = Shape.newBuilder().shapeFlags(flags).build(); + uncachedPut(o2, "key1", v1, 0); + uncachedPut(o2, "key2", v2, 0); + resetShapeNode.execute(o2, newEmptyShape); + assertSame(newEmptyShape, o2.getShape()); + assertEquals(flags, getShapeFlagsNode.execute(o2)); + } + + @Test + public void testGetKeysAndProperties() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject.GetKeyArrayNode getKeyArrayNode = createGetKeyArrayNode(); + DynamicObject.GetPropertyArrayNode getPropertyArrayNode = createGetPropertyArrayNode(); + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 1); + uncachedPut(o1, "key2", v2, 2); + uncachedPut(o1, "key3", v3, 3); + + Object[] keyArray = getKeyArrayNode.execute(o1); + Property[] properties = getPropertyArrayNode.execute(o1); + assertEquals(Arrays.asList("key1", "key2", "key3"), getKeyList(o1)); + assertEquals(3, o1.getShape().getPropertyCount()); + for (int i = 0, j = 1; i < 3; i++, j++) { + assertEquals(keyArray[i], properties[i].getKey()); + assertEquals(j, properties[i].getFlags()); + } + } + + @Test + public void testGetKeysAndPropertiesFromShape() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 1); + uncachedPut(o1, "key2", v2, 2); + uncachedPut(o1, "key3", v3, 3); + + Object[] keyArray = getKeyList(o1).toArray(); + Property[] properties = o1.getShape().getPropertyList().toArray(new Property[0]); + assertEquals(Arrays.asList("key1", "key2", "key3"), getKeyList(o1)); + assertEquals(3, o1.getShape().getPropertyCount()); + for (int i = 0, j = 1; i < 3; i++, j++) { + assertEquals(keyArray[i], properties[i].getKey()); + assertEquals(j, properties[i].getFlags()); + } + } + + @Test + public void testAllPropertiesMatch() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 41); + uncachedPut(o1, "key2", v2, 42); + uncachedPut(o1, "key3", v3, 43); + + assertFalse(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); + var removeKey1 = createRemoveKeyNode(); + removeKey1.execute(o1, "key1"); + assertTrue(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); + } + + @Test + public void testGetProperty() { + int v1 = 42; + int v2 = 43; + Object v3 = "value"; + + DynamicObject o1 = createEmpty(); + uncachedPut(o1, "key1", v1, 1); + uncachedPut(o1, "key2", v2, 2); + uncachedPut(o1, "key3", v3, 3); + + DynamicObject.GetPropertyNode getPropertyNode = createGetPropertyNode(); + DynamicObject.GetPropertyFlagsNode getPropertyFlagsNode = createGetPropertyFlagsNode(); + + // assertTrue(lib.accepts(o1)); + for (int i = 1; i <= 3; i++) { + Object key = "key" + i; + assertSame(o1.getShape().getProperty(key), getPropertyNode.execute(o1, key)); + assertEquals(i, o1.getShape().getProperty(key).getFlags()); + assertEquals(i, getPropertyFlagsNode.execute(o1, key, -1)); + } + // assertTrue(lib.accepts(o1)); + } + + @Test + public void testPutConstant1() { + DynamicObject o1 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + int flags = 0xf; + + var putConstantNode = createPutConstantNode(); + var putNode = createPutNode(); + + putConstantNode.putConstantWithFlags(o1, k1, v1, 0); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(0, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + putConstantNode.putConstantWithFlags(o1, k1, v1, flags); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + putNode.put(o1, k1, v2); + assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v2, uncachedGet(o1, k1)); + } + + @Test + public void testPutConstant2() { + DynamicObject o1 = createEmpty(); + String k1 = "key1"; + int v1 = 42; + int v2 = 43; + int flags = 0xf; + + var putConstantNode = createPutConstantNode(); + var putNode = createPutNode(); + + putConstantNode.putConstantWithFlags(o1, k1, v1, 0); + assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); + assertEquals(0, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v1, uncachedGet(o1, k1)); + + putNode.putWithFlags(o1, k1, v2, flags); + if (isNewLayout()) { + assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); + } + assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); + assertEquals(v2, uncachedGet(o1, k1)); + } + + @Test + public void testCachedShape() { + String key = "testKey"; + DynamicObject o1 = createEmpty(); + DynamicObject o2 = createEmpty(); + DynamicObject o3 = createEmpty(); + + uncachedPut(o1, key, o2); + uncachedPut(o2, key, o3); + uncachedPut(o3, key, 42); + + TestNestedDispatchGetNode node = adoptNode(TestNestedDispatchGetNodeGen.create()).get(); + assertEquals(42, node.execute(o1)); + assertEquals(42, node.execute(o1)); + assertEquals(42, node.execute(o2)); + assertEquals(42, node.execute(o1)); + assertEquals(42, node.execute(o2)); + assertEquals(42, node.execute(o2)); + } + + @Test + public void testPropertyAndShapeFlags() { + DynamicObject o1 = createEmpty(); + fillObjectWithProperties(o1, false); + updateAllFlags(o1, 3); + DynamicObject o2 = createEmpty(); + fillObjectWithProperties(o2, true); + DynamicObject o3 = createEmpty(); + fillObjectWithProperties(o3, false); + DynamicObject.PutNode.getUncached().put(o1, "k13", false); + updateAllFlags(o2, 3); + updateAllFlags(o3, 3); + var getNode = createGetNode(); + assertEquals(1, getNode.getOrDefault(o3, "k13", null)); + } + + private void fillObjectWithProperties(DynamicObject obj, boolean b) { + DynamicObject.PutNode library = createPutNode(); + + for (int i = 0; i < 20; i++) { + Object value; + if (i % 2 == 0) { + if (i == 14) { + value = "string"; + } else { + value = new ArrayList<>(); + } + } else { + if (b && i == 13) { + value = new ArrayList<>(); + } else { + value = 1; + } + } + int flags = (i == 17 || i == 13) ? 1 : 3; + library.putWithFlags(obj, "k" + i, value, flags); + } + } + + private void updateAllFlags(DynamicObject obj, int flags) { + DynamicObject.SetPropertyFlagsNode setPropertyFlagsNode = createSetPropertyFlagsNode(); + DynamicObject.GetPropertyArrayNode getPropertyArrayNode = createGetPropertyArrayNode(); + + for (Property property : getPropertyArrayNode.execute(obj)) { + int oldFlags = property.getFlags(); + int newFlags = oldFlags | flags; + if (newFlags != oldFlags) { + Object key = property.getKey(); + setPropertyFlagsNode.execute(obj, key, newFlags); + } + } + + DynamicObject.SetShapeFlagsNode setShapeFlags = createSetShapeFlagsNode(); + setShapeFlags.execute(obj, flags); + } + + private static void uncachedPut(DynamicObject obj, Object key, Object value) { + DynamicObject.PutNode.getUncached().put(obj, key, value); + } + + private static void uncachedPut(DynamicObject obj, Object key, Object value, int flags) { + DynamicObject.PutNode.getUncached().putWithFlags(obj, key, value, flags); + } + + private static void uncachedSet(DynamicObject obj, Object key, Object value) { + DynamicObject.PutNode.getUncached().putIfPresent(obj, key, value); + } + + private static Object uncachedGet(DynamicObject obj, Object key) { + return DynamicObject.GetNode.getUncached().getOrDefault(obj, key, null); + } + + private static Property uncachedGetProperty(DynamicObject obj, Object key) { + return DynamicObject.GetPropertyNode.getUncached().execute(obj, key); + } + + private static Object newObjectType() { + return new Object() { + }; + } + + private static List getKeyList(DynamicObject obj) { + return Arrays.asList(DynamicObject.GetKeyArrayNode.getUncached().execute(obj)); + } + + private static boolean isNewLayout() { + return true; + } + + @GenerateInline(false) + public abstract static class TestGet extends Node { + public abstract Object execute(DynamicObject obj); + + @Specialization + public Object doGet(DynamicObject obj, + @Cached DynamicObject.GetNode get) { + return get.getOrDefault(obj, "test", null); + } + } +} diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java index 25894288f003..f73d9c496caa 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 @@ -52,10 +52,8 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; - @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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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..b4721cfd0cd3 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 @@ -59,11 +59,9 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; - @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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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..24678421d221 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 @@ -67,11 +67,9 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; - @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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary library = createLibrary(object); library.putConstant(object, "a", new Object(), 0); assertTrue(library.containsKey(object, "a")); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java index 04ab66547682..b7bdc0b81591 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 @@ -68,11 +68,10 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.Assumption; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; @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 +115,7 @@ public void testDefinePropertyWithFlagsChangeAndFinalInvalidation() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.put(a, "r", 1); library.put(a, "x", 2); @@ -148,7 +147,7 @@ public void testAddNewPropertyOnObsoleteShape() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.put(a, "x", 1); library.put(b, "x", 1); @@ -169,7 +168,7 @@ public void testRemovePropertyOnObsoleteShape() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.put(a, "x", ""); library.put(b, "x", ""); @@ -191,7 +190,7 @@ public void testReplaceDeclaredProperty() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.putConstant(a, "a", null, 0); @@ -209,7 +208,7 @@ public void testReplaceDeclaredProperty2() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.putConstant(a, "a", null, 0); @@ -228,7 +227,7 @@ public void testDeclaredPropertyShapeTransitionCount() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); for (int i = 0; i < 26; i++) { library.putConstant(a, Character.toString((char) ('a' + i)), null, 0); @@ -259,7 +258,7 @@ public void testChangePropertyFlagsWithObsolescence() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.putWithFlags(a, "s", 42, 0); library.putIfPresent(a, "s", 43); @@ -284,7 +283,7 @@ public void testChangePropertyFlagsWithObsolescenceGR53902() { DynamicObject y = newInstance(emptyShape); DynamicObject z = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.putWithFlags(a, "s", 42, 0); library.putIfPresent(a, "s", 43); @@ -310,7 +309,7 @@ public void testChangePropertyTypeWithObsolescence() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.putWithFlags(a, "s", 13.37, 0); library.putWithFlags(a, "x", 42, 0); @@ -329,7 +328,7 @@ public void testAssumedFinalLocationShapeTransitionCount() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); for (int i = 0; i < 26; i++) { library.put(a, Character.toString((char) ('a' + i)), 0); @@ -355,9 +354,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); + DynamicObjectLibrary library = createLibrary(obj); library.put(obj, "a", 42); library.put(obj, "b", b); @@ -387,9 +386,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); + DynamicObjectLibrary library = createLibrary(obj); library.putConstant(obj, "a", a, 3); library.put(obj, "b", b); @@ -420,7 +419,7 @@ public void testTryMergeShapes() { DynamicObject b = new TestDynamicObject(emptyShape); DynamicObject c = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.setShapeFlags(a, 1); library.put(a, "a", 1); @@ -469,7 +468,7 @@ public void testTryMergeShapes2() { DynamicObject a = new TestDynamicObject(emptyShape); DynamicObject b = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.put(a, "a", 1); Shape notObsoletedParent = a.getShape(); @@ -502,7 +501,7 @@ public void testBooleanLocationTypeAssumption() { DynamicObject obj = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, obj); + DynamicObjectLibrary library = createLibrary(obj); library.put(obj, "b1", true); library.put(obj, "b2", true); @@ -522,7 +521,7 @@ public void testPropertyAssumptionInvalidation() { DynamicObject a = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(DynamicObjectLibrary.class, a); + DynamicObjectLibrary library = createLibrary(a); library.put(a, "a", 1); library.put(a, "b", 2); @@ -558,8 +557,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); + DynamicObjectLibrary on = createLibrary(h1); + DynamicObjectLibrary off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -589,8 +588,8 @@ public void testPropertyAssumptionInvalidAfterReplace1() { int flag = 2; DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(DynamicObjectLibrary.class, h1); - DynamicObjectLibrary off = createLibrary(DynamicObjectLibrary.class, h1); + DynamicObjectLibrary on = createLibrary(h1); + DynamicObjectLibrary off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -623,8 +622,8 @@ public void testPropertyAssumptionInvalidAfterReplace2() { int flag = 2; DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(DynamicObjectLibrary.class, h1); - DynamicObjectLibrary off = createLibrary(DynamicObjectLibrary.class, h1); + DynamicObjectLibrary on = createLibrary(h1); + DynamicObjectLibrary off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -656,7 +655,7 @@ public void testPropertyAssumptionInvalidAfterTypeTransition() { Shape emptyShape = Shape.newBuilder().propertyAssumptions(true).build(); DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary lib = createLibrary(DynamicObjectLibrary.class, h1); + DynamicObjectLibrary 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..92c0b0628f1c --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java @@ -0,0 +1,265 @@ +/* + * 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 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.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + +import com.oracle.truffle.api.test.AbstractLibraryTest; + +@RunWith(Parameterized.class) +public abstract class ParametrizedDynamicObjectTest extends AbstractLibraryTest { + + public enum TestRun { + CACHED_LIBRARY, + UNCACHED_LIBRARY, + DISPATCHED_CACHED_LIBRARY, + DISPATCHED_UNCACHED_LIBRARY, + CACHED_NODES, + UNCACHED_NODES; + + public static final TestRun[] DISPATCHED_ONLY = {DISPATCHED_CACHED_LIBRARY, DISPATCHED_UNCACHED_LIBRARY, CACHED_NODES, UNCACHED_NODES}; + public static final TestRun[] UNCACHED_ONLY = {DISPATCHED_UNCACHED_LIBRARY, UNCACHED_NODES}; + } + + @Parameter // first data value (0) is default + public /* NOT private */ TestRun run; + + protected final DynamicObjectLibrary createLibrary(Object receiver) { + return switch (run) { + case CACHED_LIBRARY -> adoptNode(DynamicObjectLibrary.getFactory().create(receiver)).get(); + case UNCACHED_LIBRARY -> DynamicObjectLibrary.getFactory().getUncached(receiver); + case DISPATCHED_CACHED_LIBRARY -> adoptNode(DynamicObjectLibrary.getFactory().createDispatched(2)).get(); + case DISPATCHED_UNCACHED_LIBRARY -> DynamicObjectLibrary.getUncached(); + case CACHED_NODES -> new NodesFakeDynamicObjectLibrary(); + case UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; + }; + + } + + protected final DynamicObjectLibrary createLibrary() { + assert run != TestRun.CACHED_LIBRARY; + assert run != TestRun.UNCACHED_LIBRARY; + return createLibrary(null); + } + + protected final DynamicObjectLibrary uncachedLibrary() { + return switch (run) { + case CACHED_LIBRARY, UNCACHED_LIBRARY, DISPATCHED_CACHED_LIBRARY, DISPATCHED_UNCACHED_LIBRARY -> + DynamicObjectLibrary.getUncached(); + case CACHED_NODES, UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; + }; + } + + static final DynamicObjectLibrary UNCACHED_NODES_LIBRARY = new NodesFakeDynamicObjectLibrary("uncached"); + + static class NodesFakeDynamicObjectLibrary extends DynamicObjectLibrary { + + 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(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.getOrDefault(object, key, defaultValue); + } + + @Override + public void put(DynamicObject object, Object key, Object value) { + putNode.put(object, key, value); + } + + @Override + public boolean putIfPresent(DynamicObject object, Object key, Object value) { + return putNode.putIfPresent(object, key, value); + } + + @Override + public void putWithFlags(DynamicObject object, Object key, Object value, int flags) { + putNode.putWithFlags(object, key, value, flags); + } + + @Override + public void putConstant(DynamicObject object, Object key, Object value, int flags) { + putConstantNode.putConstantWithFlags(object, key, value, flags); + } + + @Override + public boolean removeKey(DynamicObject object, Object key) { + return removeKeyNode.execute(object, key); + } + + @Override + public boolean setDynamicType(DynamicObject object, Object type) { + return setDynamicTypeNode.execute(object, type); + } + + @Override + public Object getDynamicType(DynamicObject object) { + return getDynamicTypeNode.execute(object); + } + + @Override + public boolean containsKey(DynamicObject object, Object key) { + return containsKeyNode.execute(object, key); + } + + @Override + public int getShapeFlags(DynamicObject object) { + return getShapeFlagsNode.execute(object); + } + + @Override + public boolean setShapeFlags(DynamicObject object, int flags) { + return setShapeFlagsNode.execute(object, flags); + } + + @Override + public Property getProperty(DynamicObject object, Object key) { + return getPropertyNode.execute(object, key); + } + + @Override + public boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags) { + return setPropertyFlagsNode.execute(object, key, propertyFlags); + } + + @Override + public void markShared(DynamicObject object) { + markSharedNode.execute(object); + } + + @Override + public boolean isShared(DynamicObject object) { + return isSharedNode.execute(object); + } + + @Override + public boolean updateShape(DynamicObject object) { + return updateShapeNode.execute(object); + } + + @Override + public boolean resetShape(DynamicObject object, Shape otherShape) { + return resetShapeNode.execute(object, otherShape); + } + + @Override + public Object[] getKeyArray(DynamicObject object) { + return getKeyArrayNode.execute(object); + } + + @Override + public Property[] getPropertyArray(DynamicObject object) { + return getPropertyArrayNode.execute(object); + } + } + +} diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PolymorphicPrimitivesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/PolymorphicPrimitivesTest.java index 5e63fa5c3468..598063a9134c 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 @@ -53,17 +53,15 @@ 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.object.test.ObjectModelRegressionTest.TestDynamicObject; 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.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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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..c2fcf7f2acbc 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 @@ -53,9 +53,19 @@ import org.junit.Test; import com.oracle.truffle.api.nodes.UnexpectedResultException; -import com.oracle.truffle.api.test.AbstractLibraryTest; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class PropertyGetterTest extends AbstractLibraryTest { +import java.util.List; + +@RunWith(Parameterized.class) +public class PropertyGetterTest extends ParametrizedDynamicObjectTest { + + @Parameters(name = "{0}") + public static List data() { + return List.of(TestRun.DISPATCHED_ONLY); + } @Test public void testPropertyGetter() throws Exception { @@ -73,9 +83,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); + DynamicObjectLibrary lib = createLibrary(); + lib.putWithFlags(o1, key, val, 13); + lib.putWithFlags(o2, key, val, 13); assertSame("expected same shape", o1.getShape(), o2.getShape()); @@ -90,9 +100,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..efbabc617e15 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 @@ -53,10 +53,8 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; - @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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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..fd014428847f 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 @@ -61,11 +61,9 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import com.oracle.truffle.api.test.AbstractParametrizedLibraryTest; - @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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary 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); + DynamicObjectLibrary library = createLibrary(object); library.put(object, "a", 1); library.markShared(object); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java new file mode 100644 index 000000000000..1dc61b64dff3 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/TestNestedDispatchGetNode.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.object.test; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.DynamicObject.GetNode; + +@SuppressWarnings("truffle") +public abstract class TestNestedDispatchGetNode extends Node { + + final Object key = "testKey"; + + public abstract Object execute(DynamicObject obj); + + @Specialization(guards = {"obj == cachedObj"}, limit = "1") + Object cached(DynamicObject obj, + @Cached("obj") @SuppressWarnings("unused") DynamicObject cachedObj, + @Cached GetNode getNode, + @Cached(value = "cachedObj.getShape().getProperty(key) != null") boolean hasProperty, + @Cached TestNestedDispatchNode nested) { + Object value = getNode.getOrDefault(obj, key, null); + if (hasProperty) { + assert value != null; + if (value instanceof DynamicObject) { + return nested.execute((DynamicObject) value); + } + } + return value; + } + + @Specialization(limit = "3") + Object cached(DynamicObject obj, + @Cached GetNode getNode, + @Cached(value = "obj.getShape().getProperty(key) != null", allowUncached = true) boolean hasProperty, + @Cached TestNestedDispatchNode nested) { + Object value = getNode.getOrDefault(obj, key, null); + if (hasProperty) { + assert value != null; + if (value instanceof DynamicObject) { + return nested.execute((DynamicObject) value); + } + } + return value; + } + +} diff --git a/truffle/src/com.oracle.truffle.api.object/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..cfe35bd48c29 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObject.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -48,22 +48,40 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; +import java.util.Map; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GeneratePackagePrivate; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NeverDefault; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.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 sun.misc.Unsafe; +// @formatter:off /** * Represents a dynamic object, members of which can be dynamically added and removed at run time. * - * To use it, extend {@link DynamicObject} and use {@link DynamicObjectLibrary} for object accesses. + * To use it, extend {@link DynamicObject} and use nodes nested under DynamicObject such as {@link DynamicObject.GetNode} for object accesses. * * When {@linkplain DynamicObject#DynamicObject(Shape) constructing} a {@link DynamicObject}, it has * to be initialized with an empty initial shape. Initial shapes are created using - * {@link Shape#newBuilder()} and should ideally be shared per TruffleLanguage instance to allow + * {@link Shape#newBuilder()} and should ideally be shared per {@link TruffleLanguage} instance to allow * shape caches to be shared across contexts. * * Subclasses can provide in-object dynamic field slots using the {@link DynamicField} annotation @@ -71,9 +89,7 @@ * *

* Example: - * - *

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

General documentation about DynamicObject nodes

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

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

Usage examples:

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

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

Usage example:

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

Usage example:

+ * + * {@snippet : + * @ExportMessage + * Object writeMember(String member, Object value, + * @Cached DynamicObject.PutNode putNode) { + * putNode.put(this, member, value); + * } + * } + * + * @param key the property key + * @param value the value to be set + * @see #putIfPresent(DynamicObject, Object, Object) + * @see #putWithFlags(DynamicObject, Object, Object, int) + */ + // @formatter:on + public final void put(DynamicObject receiver, Object key, Object value) { + executeImpl(receiver, key, value, Flags.DEFAULT, 0); + } + + /** + * Sets the value of the property if present, otherwise returns {@code false}. + * + * @param key property identifier + * @param value value to be set + * @return {@code true} if the property was present and the value set, otherwise + * {@code false} + * @see #put(DynamicObject, Object, Object) + */ + public final boolean putIfPresent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, Flags.IF_PRESENT, 0); + } + + /** + * Sets the value of the property if absent, otherwise returns {@code false}. + * + * @param key property identifier + * @param value value to be set + * @return {@code true} if the property was absent and the value set, otherwise + * {@code false} + * @see #put(DynamicObject, Object, Object) + */ + public final boolean putIfAbsent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, Flags.IF_ABSENT, 0); + } + + /** + * Like {@link #put(DynamicObject, Object, Object)}, but additionally assigns flags to the + * property. If the property already exists, its flags will be updated before the value is + * set. + * + * @param key property identifier + * @param value value to be set + * @param propertyFlags flags to be set + * @see #put(DynamicObject, Object, Object) + */ + public final void putWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + executeImpl(receiver, key, value, Flags.DEFAULT | Flags.UPDATE_FLAGS, propertyFlags); + } + + /** + * Like {@link #putIfPresent(DynamicObject, Object, Object)} but also sets property flags. + */ + public final boolean putWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.UPDATE_FLAGS, propertyFlags); + } + + /** + * Like {@link #putIfAbsent(DynamicObject, Object, Object)} but also sets property flags. + */ + public final boolean putWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.UPDATE_FLAGS, propertyFlags); + } + + // private + + abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags); + + @SuppressWarnings("unused") + @Specialization(guards = { + "guard", + "key == cachedKey", + "mode == cachedMode", + "propertyFlags == cachedPropertyFlags", + "newLocation == null || canStore(newLocation, value)", + }, assumptions = "newShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape oldShape, + @Bind("shape == oldShape") boolean guard, + @Cached("key") Object cachedKey, + @Cached("mode") int cachedMode, + @Cached("propertyFlags") int cachedPropertyFlags, + @Cached("oldShape.getProperty(key)") Property oldProperty, + @Cached("getNewShapeAndCheckOldShapeStillValid(key, value, cachedPropertyFlags, cachedMode, oldProperty, oldShape)") Shape newShape, + @Cached("getNewLocation(oldShape, newShape, key, oldProperty)") Location newLocation, + @Cached("newShape.getValidAbstractAssumption()") AbstractAssumption newShapeValidAssumption) { + // We use mode instead of cachedMode to fold it during host inlining + CompilerAsserts.partialEvaluationConstant(mode); + if ((mode & Flags.IF_ABSENT) != 0 && oldProperty != null) { + return false; + } + if ((mode & Flags.IF_PRESENT) != 0 && oldProperty == null) { + return false; + } else { + boolean addingNewProperty = newShape != oldShape; + if (addingNewProperty) { + DynamicObjectSupport.grow(receiver, oldShape, newShape); + } + + if (newLocation instanceof ExtLocations.ObjectArrayLocation objectArrayLocation) { + objectArrayLocation.set(receiver, value, guard, addingNewProperty); + } else { + newLocation.set(receiver, value, guard, addingNewProperty); + } + + if (addingNewProperty) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + } + return true; + } + } + + /* + * This specialization is necessary because we don't want to remove doCached specialization + * instances with valid shapes. Yet we have to handle obsolete shapes, and we prefer to do + * that here than inside the doCached method. This also means new shapes being seen can + * still create new doCached instances, which is important once we see objects with the new + * non-obsolete shape. + */ + @Specialization(guards = "!receiver.getShape().isValid()") + static boolean doInvalid(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags) { + return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); + } + + @Specialization(replaces = {"doCached", "doInvalid"}) + static boolean doGeneric(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags) { + 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 #putConstant(DynamicObject, Object, Object) + * @see #putConstantIfAbsent(DynamicObject, Object, Object) + * @see #putConstantIfPresent(DynamicObject, Object, Object) + * @see #putConstantWithFlags(DynamicObject, Object, Object, int) + * @see #putConstantWithFlagsIfAbsent(DynamicObject, Object, Object, int) + * @see #putConstantWithFlagsIfPresent(DynamicObject, Object, Object, int) + * @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 #putConstantWithFlags}, except the property is added with 0 flags, and if + * the property already exists, its flags will not be updated. + */ + public final void putConstant(DynamicObject receiver, Object key, Object value) { + executeImpl(receiver, key, value, Flags.DEFAULT | Flags.CONST, 0); + } + + /** + * Like {@link #putConstant(DynamicObject, Object, Object)} but only if the property is + * present. + */ + public final boolean putConstantIfPresent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.CONST, 0); + } + + /** + * Like {@link #putConstant(DynamicObject, Object, Object)} but only if the property is + * absent. + */ + public final boolean putConstantIfAbsent(DynamicObject receiver, Object key, Object value) { + return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.CONST, 0); + } + + // @formatter:off + /** + * Adds a property with a constant value or replaces an existing one. If the property already + * exists, its flags will be updated. + * + * The constant value is stored in the shape rather than the object instance and a new shape + * will be allocated if it does not already exist. + * + * A typical use case for this method is setting the initial default value of a declared, but + * yet uninitialized, property. This defers storage allocation and type speculation until the + * first actual value is set. + * + *

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

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

Usage example:

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

Usage example:

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

Usage example:

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

Usage example:

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

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

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

Usage example:

+ * + * {@snippet : + * @Specialization + * static void freeze(DynamicObject receiver, + * @Cached DynamicObject.AddShapeFlagsNode addShapeFlagsNode) { + * addShapeFlagsNode.execute(receiver, FROZEN); + * } + * } + * + * @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) + */ + // @formatter:on + public abstract boolean execute(DynamicObject receiver, int newFlags); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "flags == newFlags"}, limit = "SHAPE_CACHE_LIMIT") + static boolean doCached(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("flags") int newFlags, + @Cached("shapeAddFlags(cachedShape, newFlags)") Shape newShape) { + if (newShape != cachedShape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + @Specialization(replaces = "doCached") + static boolean doGeneric(DynamicObject receiver, int flags, + @Bind("receiver.getShape()") Shape shape) { + Shape newShape = shapeAddFlags(shape, flags); + if (newShape != shape) { + receiver.setShape(newShape); + return true; + } else { + return false; + } + } + + static Shape shapeAddFlags(Shape shape, int newFlags) { + return shape.setFlags(shape.getFlags() | newFlags); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static AddShapeFlagsNode create() { + return DynamicObjectFactory.AddShapeFlagsNodeGen.create(); + } + + /** + * @since 25.1 + */ + @NeverDefault + public static AddShapeFlagsNode getUncached() { + return DynamicObjectFactory.AddShapeFlagsNodeGen.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() { + } + + // @formatter:off + /** + * Gets the property flags associated with the requested property key. Returns the + * {@code defaultValue} if the object contains no such property. If the property exists but no + * flags were explicitly set, returns the default of 0. + * + *

+ * Convenience method equivalent to: + * + * {@snippet : + * @Specialization + * int getPropertyFlags(@Cached GetPropertyNode getPropertyNode) { + * Property property = getPropertyNode.execute(object, key); + * return property != null ? property.getFlags() : defaultValue; + * } + * } + * + * @param key the property key + * @param defaultValue value to return if no such property exists + * @return the property flags if the property exists, else {@code defaultValue} + * @see GetPropertyNode + */ + // @formatter:on + public abstract int execute(DynamicObject receiver, Object key, int defaultValue); + + @SuppressWarnings("unused") + @Specialization(guards = {"shape == cachedShape", "key == cachedKey"}, limit = "SHAPE_CACHE_LIMIT") + static int doCached(DynamicObject receiver, Object key, int defaultValue, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Cached("key") Object cachedKey, + @Cached("cachedShape.getProperty(cachedKey)") Property cachedProperty) { + return cachedProperty != null ? cachedProperty.getFlags() : defaultValue; + } + + @Specialization(replaces = "doCached") + static int doGeneric(DynamicObject receiver, Object key, int defaultValue) { + Property property = receiver.getShape().getProperty(key); + return 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 + * @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); + + Shape oldShape = receiver.getShape(); + final Property existingProperty = reusePropertyLookup(cachedShape, cachedProperty, oldShape); + Shape newShape = oldShape.setPropertyFlags(existingProperty, propertyFlags); + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + updateShape(receiver); + } + } + + static Shape setPropertyFlags(Shape shape, Property cachedProperty, int propertyFlags) { + CompilerAsserts.neverPartOfCompilation(); + if (cachedProperty == null) { + return shape; + } + return shape.setPropertyFlags(cachedProperty, propertyFlags); + } + + /** + * @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); + } + } + + /** + * 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() { + } + + // @formatter:off + /** + * Gets a snapshot of the object's property keys, in insertion order. The returned array may + * have been cached and must not be mutated. + * + * Properties with a {@link HiddenKey} are not included. + * + *

Usage example:

+ * + * The example below shows how the returned keys array could be translated to an interop array + * for use with InteropLibrary. + * + * {@snippet : + * @ExportMessage + * Object getMembers( + * @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode) { + * return new Keys(getKeyArrayNode.execute(this)); + * } + * + * @ExportLibrary(InteropLibrary.class) + * static final class Keys implements TruffleObject { + * + * @CompilationFinal(dimensions = 1) final Object[] keys; + * + * Keys(Object[] keys) { + * this.keys = keys; + * } + * + * @ExportMessage + * boolean hasArrayElements() { + * return true; + * } + * + * @ExportMessage + * Object readArrayElement(long index) throws InvalidArrayIndexException { + * if (!isArrayElementReadable(index)) { + * throw InvalidArrayIndexException.create(index); + * } + * return keys[(int) index]; + * } + * + * @ExportMessage + * long getArraySize() { + * return keys.length; + * } + * + * @ExportMessage + * boolean isArrayElementReadable(long index) { + * return index >= 0 && index < keys.length; + * } + * } + * } + * + * @return a read-only array of the object's property keys. + */ + // @formatter:on + public abstract Object[] execute(DynamicObject receiver); + + @SuppressWarnings("unused") + @Specialization(guards = "receiver.getShape() == cachedShape", limit = "SHAPE_CACHE_LIMIT") + static Object[] doCached(DynamicObject receiver, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached(value = "getKeyArray(cachedShape)", dimensions = 1) Object[] cachedKeyArray) { + return cachedKeyArray; + } + + @Specialization(replaces = "doCached") + static Object[] getKeyArray(DynamicObject receiver) { + return getKeyArray(receiver.getShape()); + } + + static Object[] getKeyArray(Shape shape) { + return shape.getKeyArray(); + } + + /** + * @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. + * @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..11d7a03972fc 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 @@ -106,7 +106,7 @@ public abstract class DynamicObjectLibrary extends Library { /** * @since 20.2.0 */ - DynamicObjectLibrary() { + public DynamicObjectLibrary() { } /** @@ -156,6 +156,7 @@ public static DynamicObjectLibrary getUncached() { * @param key the property key * @param defaultValue value to be returned if the property does not exist * @return the property's value if it exists, else {@code defaultValue}. + * @see DynamicObject.GetNode#getOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract Object getOrDefault(DynamicObject object, Object key, Object defaultValue); @@ -169,6 +170,7 @@ public static DynamicObjectLibrary getUncached() { * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not an {@code int} * @see #getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#getIntOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -189,6 +191,7 @@ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not a {@code double} * @see #getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#getDoubleOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -209,6 +212,7 @@ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaul * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not a {@code long} * @see #getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#getLongOrDefault(DynamicObject, Object, Object) * @since 20.2.0 */ public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -243,6 +247,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * @see #putLong(DynamicObject, Object, long) * @see #putIfPresent(DynamicObject, Object, Object) * @see #putWithFlags(DynamicObject, Object, Object, int) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void put(DynamicObject object, Object key, Object value); @@ -251,6 +256,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * Int-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public void putInt(DynamicObject object, Object key, int value) { @@ -261,6 +267,7 @@ public void putInt(DynamicObject object, Object key, int value) { * Double-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public void putDouble(DynamicObject object, Object key, double value) { @@ -271,6 +278,7 @@ public void putDouble(DynamicObject object, Object key, double value) { * Long-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) * @since 20.2.0 */ public void putLong(DynamicObject object, Object key, long value) { @@ -284,6 +292,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param value value to be set * @return {@code true} if the property was present and the value set, otherwise {@code false} * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#putIfPresent(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract boolean putIfPresent(DynamicObject object, Object key, Object value); @@ -297,6 +306,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param flags flags to be set * @see #put(DynamicObject, Object, Object) * @see #setPropertyFlags(DynamicObject, Object, int) + * @see DynamicObject.PutNode#putWithFlags(DynamicObject, Object, Object, int) * @since 20.2.0 */ public abstract void putWithFlags(DynamicObject object, Object key, Object value, int flags); @@ -336,6 +346,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param value the constant value to be set * @param flags property flags or 0 * @see #put(DynamicObject, Object, Object) + * @see DynamicObject.PutConstantNode#putConstant(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void putConstant(DynamicObject object, Object key, Object value, int flags); @@ -345,6 +356,7 @@ public void putLong(DynamicObject object, Object key, long value) { * * @param key the property key * @return {@code true} if the property was removed or {@code false} if property was not found + * @see DynamicObject.RemoveKeyNode * @since 20.2.0 */ public abstract boolean removeKey(DynamicObject object, Object key); @@ -365,6 +377,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @return {@code true} if the type (and the object's shape) changed * @since 20.2.0 * @see #getDynamicType(DynamicObject) + * @see DynamicObject.SetDynamicTypeNode */ public abstract boolean setDynamicType(DynamicObject object, Object type); @@ -376,6 +389,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @since 20.2.0 * @see #setDynamicType(DynamicObject, Object) * @see Shape#getDynamicType() + * @see DynamicObject.GetDynamicTypeNode */ public abstract Object getDynamicType(DynamicObject object); @@ -394,6 +408,7 @@ public void putLong(DynamicObject object, Object key, long value) { * * @param key the property key * @return {@code true} if the object contains a property with this key, else {@code false} + * @see DynamicObject.ContainsKeyNode * @since 20.2.0 */ public abstract boolean containsKey(DynamicObject object, Object key); @@ -425,6 +440,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @see #setShapeFlags(DynamicObject, int) * @see Shape.Builder#shapeFlags(int) * @see Shape#getFlags() + * @see DynamicObject.GetShapeFlagsNode * @since 20.2.0 */ public abstract int getShapeFlags(DynamicObject object); @@ -453,6 +469,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @throws IllegalArgumentException if the flags are not in the allowed range. * @see #getShapeFlags(DynamicObject) * @see Shape.Builder#shapeFlags(int) + * @see DynamicObject.SetShapeFlagsNode * @since 20.2.0 */ public abstract boolean setShapeFlags(DynamicObject object, int flags); @@ -462,6 +479,7 @@ public void putLong(DynamicObject object, Object key, long value) { * {@code null} if the object contains no such property. * * @return {@link Property} if the property exists, else {@code null} + * @see DynamicObject.GetPropertyNode * @since 20.2.0 */ public abstract Property getProperty(DynamicObject object, Object key); @@ -483,6 +501,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param defaultValue value to return if no such property exists * @return the property flags if the property exists, else {@code defaultValue} * @see #getProperty(DynamicObject, Object) + * @see DynamicObject.GetPropertyFlagsNode * @since 20.2.0 */ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int defaultValue) { @@ -495,6 +514,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * @param key the property key * @return {@code true} if the property was found and its flags were changed, else {@code false} + * @see DynamicObject.SetPropertyFlagsNode * @since 20.2.0 */ public abstract boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags); @@ -509,6 +529,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * @throws UnsupportedOperationException if the object is already {@linkplain #isShared shared}. * @see #isShared(DynamicObject) + * @see DynamicObject.MarkSharedNode * @since 20.2.0 */ public abstract void markShared(DynamicObject object); @@ -519,6 +540,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * @return {@code true} if the object is shared * @see #markShared(DynamicObject) * @see Shape#isShared() + * @see DynamicObject.IsSharedNode * @since 20.2.0 */ public abstract boolean isShared(DynamicObject object); @@ -534,6 +556,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * cached which can cause unnecessary cache polymorphism and invalidations. * * @return {@code true} if the object's shape was changed, otherwise {@code false}. + * @see DynamicObject.UpdateShapeNode * @since 20.2.0 */ public abstract boolean updateShape(DynamicObject object); @@ -545,6 +568,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * @param otherShape the desired shape * @return {@code true} if the object's shape was changed * @throws IllegalArgumentException if the shape contains instance properties + * @see DynamicObject.ResetShapeNode * @since 20.2.0 */ public abstract boolean resetShape(DynamicObject object, Shape otherShape); @@ -602,6 +626,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * * @return a read-only array of the object's property keys. + * @see DynamicObject.GetKeyArrayNode * @since 20.2.0 */ public abstract Object[] getKeyArray(DynamicObject object); @@ -617,6 +642,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int * * @return a read-only array of the object's properties. * @see #getKeyArray(DynamicObject) + * @see DynamicObject.GetPropertyArrayNode * @since 20.2.0 */ public abstract Property[] getPropertyArray(DynamicObject object); diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java index 30693fb5f3f6..541bfc63b91f 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibraryImpl.java @@ -465,7 +465,7 @@ public int compareTo(Move other) { } } - private static final class RemovePlan { + static final class RemovePlan { private static final int MAX_UNROLL = 32; @CompilationFinal(dimensions = 1) private final Move[] moves; diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java index 015fab3020e4..e7f99d9dbc55 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSupport.java @@ -185,21 +185,23 @@ private static void trimPrimitiveStore(DynamicObject object, Shape thisShape, Sh } } + @SuppressWarnings("deprecation") static Map archive(DynamicObject object) { Map archive = new HashMap<>(); Property[] properties = (object.getShape()).getPropertyArray(); for (Property property : properties) { - archive.put(property.getKey(), DynamicObjectLibrary.getUncached().getOrDefault(object, property.getKey(), null)); + archive.put(property.getKey(), property.getLocation().get(object, false)); } return archive; } + @SuppressWarnings("deprecation") static boolean verifyValues(DynamicObject object, Map archive) { Property[] properties = (object.getShape()).getPropertyArray(); for (Property property : properties) { Object key = property.getKey(); Object before = archive.get(key); - Object after = DynamicObjectLibrary.getUncached().getOrDefault(object, key, null); + Object after = property.getLocation().get(object, false); assert Objects.equals(after, before) : "before != after for key: " + key; } return true; diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java index f8b7341b8d34..995be564f6d0 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java @@ -40,12 +40,12 @@ */ package com.oracle.truffle.api.object; +import java.util.Objects; + import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.nodes.UnexpectedResultException; -import java.util.Objects; - /** * Property location. * @@ -162,7 +162,7 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected * @throws FinalLocationException for effectively final fields * @since 0.8 or earlier */ - @SuppressWarnings("deprecation") + @SuppressWarnings({"unused", "deprecation"}) @Deprecated(since = "22.2") public void set(DynamicObject store, Object value, Shape shape) throws IncompatibleLocationException, FinalLocationException { try { @@ -184,8 +184,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(); 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..b92e401c5ecb 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObsolescenceStrategy.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/ObsolescenceStrategy.java @@ -170,6 +170,13 @@ public void visitObjectArray(int index, int count) { return true; } + @TruffleBoundary + static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags) { + Shape shape = object.getShape(); + Property existingProperty = shape.getProperty(key); + return putGeneric(object, key, value, newPropertyFlags, putFlags, shape, existingProperty); + } + @TruffleBoundary static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags, Shape s, Property existingProperty) { if (existingProperty == null) { @@ -178,7 +185,7 @@ static boolean putGeneric(DynamicObject object, Object key, Object value, int ne } } else { if (Flags.isPutIfAbsent(putFlags)) { - return true; + return false; } else if (!Flags.isUpdateFlags(putFlags) && existingProperty.getLocation().canStore(value)) { existingProperty.getLocation().setSafe(object, value, false, false); return true; @@ -205,7 +212,7 @@ private static boolean putGenericSlowPath(DynamicObject object, Object key, Obje property = newShape.getProperty(key); } } else if (Flags.isPutIfAbsent(putFlags)) { - return true; + return false; } else if (Flags.isUpdateFlags(putFlags) && newPropertyFlags != existingProperty.getFlags()) { newShape = defineProperty(oldShape, key, value, newPropertyFlags, existingProperty, putFlags); property = newShape.getProperty(key); @@ -229,7 +236,7 @@ private static boolean putGenericSlowPath(DynamicObject object, Object key, Obje DynamicObjectSupport.setShapeWithStoreFence(object, newShape); updateShape(object); } else if (Flags.isPutIfAbsent(putFlags)) { - return true; + return false; } else { location.setSafe(object, value, false, false); } diff --git a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java index 1613891dc1ee..5aca1ab72ea0 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Shape.java @@ -62,7 +62,6 @@ import java.util.function.IntPredicate; import java.util.function.Predicate; -import com.oracle.truffle.api.impl.AbstractAssumption; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.collections.Pair; @@ -75,6 +74,7 @@ import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.dsl.Idempotent; import com.oracle.truffle.api.dsl.NonIdempotent; +import com.oracle.truffle.api.impl.AbstractAssumption; /** * A Shape is an immutable descriptor of the current object "shape" of a DynamicObject, i.e., object @@ -698,6 +698,12 @@ public Property getProperty(Object key) { return propertyMap.get(key); } + @TruffleBoundary + Location getLocation(Object key) { + Property property = propertyMap.get(key); + return property == null ? null : property.getLocation(); + } + /** * Add a new property in the map, yielding a new or cached Shape object. * @@ -735,6 +741,11 @@ protected Shape defineProperty(Object key, Object value, int propertyFlags, int return ObsolescenceStrategy.defineProperty(this, key, value, propertyFlags, putFlags); } + @TruffleBoundary + Shape defineProperty(Object key, Object value, int propertyFlags, int putFlags, Property existing) { + return ObsolescenceStrategy.defineProperty(this, key, value, propertyFlags, existing, putFlags); + } + /** * Add or replace shape-constant property. * @@ -928,6 +939,14 @@ protected Shape replaceProperty(Property oldProperty, Property newProperty) { return ObsolescenceStrategy.replaceProperty(this, oldProperty, newProperty); } + @TruffleBoundary + Shape setPropertyFlags(Property oldProperty, int newFlags) { + if (oldProperty.getFlags() == newFlags) { + return this; + } + return replaceProperty(oldProperty, oldProperty.copyWithFlags(newFlags)); + } + /** * Get the last property. * diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java index 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..938faa02dfc8 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLReadPropertyNode.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLReadPropertyNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -52,7 +52,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; -import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.sl.SLException; import com.oracle.truffle.sl.nodes.SLExpressionNode; @@ -89,13 +89,13 @@ public static Object readArray(Object receiver, Object index, } } - @Specialization(limit = "LIBRARY_LIMIT") + @Specialization public static Object readSLObject(SLObject receiver, Object name, @Bind Node node, - @CachedLibrary("receiver") DynamicObjectLibrary objectLibrary, + @Cached DynamicObject.GetNode getNode, @Cached SLToTruffleStringNode toTruffleStringNode) { TruffleString nameTS = toTruffleStringNode.execute(node, name); - Object result = objectLibrary.getOrDefault(receiver, nameTS, null); + Object result = getNode.getOrNull(receiver, nameTS); if (result == null) { // read was not successful. In SL we only have basic support for errors. throw SLException.undefinedProperty(node, nameTS); diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java index 2ef03c8030d0..c907b929308b 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/expression/SLWritePropertyNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -53,7 +53,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; -import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.sl.SLException; import com.oracle.truffle.sl.nodes.SLExpressionNode; import com.oracle.truffle.sl.nodes.util.SLToMemberNode; @@ -93,12 +93,12 @@ public static Object writeArray(Object receiver, Object index, Object value, return value; } - @Specialization(limit = "LIBRARY_LIMIT") + @Specialization public static Object writeSLObject(SLObject receiver, Object name, Object value, @Bind Node node, - @CachedLibrary("receiver") DynamicObjectLibrary objectLibrary, + @Cached DynamicObject.PutNode putNode, @Cached SLToTruffleStringNode toTruffleStringNode) { - objectLibrary.put(receiver, toTruffleStringNode.execute(node, name), value); + putNode.put(receiver, toTruffleStringNode.execute(node, name), value); return value; } diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java index b28fb4057cad..897b073a84ee 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -54,7 +54,6 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.api.utilities.TriState; @@ -143,19 +142,17 @@ boolean hasMembers() { @ExportMessage void removeMember(String member, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) throws UnknownIdentifierException { + @Cached DynamicObject.RemoveKeyNode removeKeyNode) throws UnknownIdentifierException { TruffleString memberTS = fromJavaStringNode.execute(member, SLLanguage.STRING_ENCODING); - if (objectLibrary.containsKey(this, memberTS)) { - objectLibrary.removeKey(this, memberTS); - } else { + if (!removeKeyNode.execute(this, memberTS)) { throw UnknownIdentifierException.create(member); } } @ExportMessage Object getMembers(@SuppressWarnings("unused") boolean includeInternal, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - return new Keys(objectLibrary.getKeyArray(this)); + @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode) { + return new Keys(getKeyArrayNode.execute(this)); } @ExportMessage(name = "isMemberReadable") @@ -163,8 +160,8 @@ Object getMembers(@SuppressWarnings("unused") boolean includeInternal, @ExportMessage(name = "isMemberRemovable") boolean existsMember(String member, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - return objectLibrary.containsKey(this, fromJavaStringNode.execute(member, SLLanguage.STRING_ENCODING)); + @Cached @Shared DynamicObject.ContainsKeyNode containsKeyNode) { + return containsKeyNode.execute(this, fromJavaStringNode.execute(member, SLLanguage.STRING_ENCODING)); } @ExportMessage @@ -207,13 +204,13 @@ boolean isArrayElementReadable(long index) { } /** - * {@link DynamicObjectLibrary} provides the polymorphic inline cache for reading properties. + * {@link DynamicObject.GetNode} provides the polymorphic inline cache for reading properties. */ @ExportMessage Object readMember(String name, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) throws UnknownIdentifierException { - Object result = objectLibrary.getOrDefault(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), null); + @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { + Object result = getNode.getOrNull(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING)); if (result == null) { /* Property does not exist. */ throw UnknownIdentifierException.create(name); @@ -222,12 +219,12 @@ Object readMember(String name, } /** - * {@link DynamicObjectLibrary} provides the polymorphic inline cache for writing properties. + * {@link DynamicObject.PutNode} provides the polymorphic inline cache for writing properties. */ @ExportMessage void writeMember(String name, Object value, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, - @CachedLibrary("this") DynamicObjectLibrary objectLibrary) { - objectLibrary.put(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), value); + @Cached DynamicObject.PutNode putNode) { + putNode.put(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), value); } } diff --git a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java index f7e0d08099b6..db15a1c2e2fd 100644 --- a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java +++ b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/DynamicObjectBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -40,6 +40,7 @@ */ package org.graalvm.truffle.benchmark; +import java.lang.invoke.MethodHandles; import java.util.stream.IntStream; import org.graalvm.polyglot.Context; @@ -53,15 +54,19 @@ import org.openjdk.jmh.annotations.Warmup; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Shape; @Warmup(iterations = 10, time = 1) -@SuppressWarnings("deprecation") public class DynamicObjectBenchmark extends TruffleBenchmark { static final String TEST_LANGUAGE = "benchmark-test-language"; private static final int PROPERTY_KEYS_PER_ITERATION = 1000; + static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + static final DynamicObject.GetNode GET_NODE = DynamicObject.GetNode.create(); + static final DynamicObject.PutNode PUT_NODE = DynamicObject.PutNode.create(); + static final String[] PROPERTY_KEYS = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new); + static final String SOME_KEY = PROPERTY_KEYS[10]; private static final class MyDynamicObject extends DynamicObject { private MyDynamicObject(Shape shape) { @@ -72,8 +77,7 @@ private MyDynamicObject(Shape shape) { @State(Scope.Benchmark) public static class SharedEngineState { final Engine engine = Engine.newBuilder().allowExperimentalOptions(true).option("engine.Compilation", "false").build(); - final Shape rootShape = Shape.newBuilder().build(); - final String[] propertyKeys = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new); + final Shape rootShape = Shape.newBuilder().layout(MyDynamicObject.class, LOOKUP).build(); final Shape[] expectedShapes = new Shape[PROPERTY_KEYS_PER_ITERATION]; @TearDown @@ -94,14 +98,22 @@ private void assertSameShape(int i, Shape actualShape) { @State(Scope.Thread) public static class PerThreadContextState { Context context; + DynamicObject object; @Setup public void setup(SharedEngineState shared) { context = Context.newBuilder(TEST_LANGUAGE).engine(shared.engine).build(); + context.enter(); + + object = new MyDynamicObject(shared.rootShape); + for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { + DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); + } } @TearDown public void tearDown() { + context.leave(); context.close(); } } @@ -112,13 +124,11 @@ public void tearDown() { @Benchmark @Threads(8) public void shapeTransitionMapContended(SharedEngineState shared, PerThreadContextState perThread) { - perThread.context.enter(); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { DynamicObject object = new MyDynamicObject(shared.rootShape); - DynamicObjectLibrary.getUncached().put(object, shared.propertyKeys[i], "testValue"); + DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } - perThread.context.leave(); } /** @@ -127,12 +137,24 @@ public void shapeTransitionMapContended(SharedEngineState shared, PerThreadConte @Benchmark @Threads(1) public void shapeTransitionMapUncontended(SharedEngineState shared, PerThreadContextState perThread) { - perThread.context.enter(); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { DynamicObject object = new MyDynamicObject(shared.rootShape); - DynamicObjectLibrary.getUncached().put(object, shared.propertyKeys[i], "testValue"); + DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } - perThread.context.leave(); + } + + @Benchmark + @Threads(1) + public Object get(SharedEngineState shared, PerThreadContextState perThread) { + DynamicObject object = perThread.object; + return GET_NODE.getOrDefault(object, SOME_KEY, null); + } + + @Benchmark + @Threads(1) + public void put(SharedEngineState shared, PerThreadContextState perThread) { + DynamicObject object = perThread.object; + PUT_NODE.put(object, SOME_KEY, "updated value"); } } From 9b781e2dbbb6482fd3257a874073eabbf5a92b58 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 3 Oct 2025 16:37:09 +0200 Subject: [PATCH 02/33] Wrap DynamicObjectLibrary for tests and fix visibility. --- .../api/object/test/ConstantLocationTest.java | 16 +- .../object/test/DynamicObjectLibraryTest.java | 99 ++++--- .../api/object/test/DynamicTypeTest.java | 8 +- .../api/object/test/ImplicitCastTest.java | 22 +- .../truffle/api/object/test/LocationTest.java | 26 +- .../test/ObjectModelRegressionTest.java | 51 ++-- .../test/ParametrizedDynamicObjectTest.java | 254 ++++++++++++++++-- .../test/PolymorphicPrimitivesTest.java | 20 +- .../api/object/test/PropertyGetterTest.java | 15 +- .../api/object/test/RemoveKeyTest.java | 20 +- .../api/object/test/SharedShapeTest.java | 24 +- .../api/object/DynamicObjectLibrary.java | 2 +- 12 files changed, 389 insertions(+), 168 deletions(-) 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 ca6931607739..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,6 +51,10 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +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 ParametrizedDynamicObjectTest { @@ -80,7 +80,7 @@ private DynamicObject newInstanceWithConstant() { public void testConstantLocation() { DynamicObject object = newInstanceWithConstant(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); Assert.assertSame(value, library.getOrDefault(object, "constant", null)); @@ -111,7 +111,7 @@ public void testConstantLocation() { public void testMigrateConstantLocation() { DynamicObject object = newInstanceWithConstant(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); Assert.assertSame(shapeWithConstant, object.getShape()); Assert.assertSame(value, library.getOrDefault(object, "constant", null)); @@ -129,7 +129,7 @@ public void testAddConstantLocation() throws com.oracle.truffle.api.object.Incom DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); property.getLocation().set(object, value, rootShape, shapeWithConstant); Assert.assertSame(shapeWithConstant, object.getShape()); @@ -156,7 +156,7 @@ public void testGetConstantValue() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(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/DynamicObjectLibraryTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java index 81274f41b541..e73ce9fcb4ff 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectLibraryTest.java @@ -56,10 +56,6 @@ import java.util.function.IntUnaryOperator; import java.util.function.Supplier; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; -import com.oracle.truffle.api.object.Property; -import com.oracle.truffle.api.object.Shape; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -68,6 +64,10 @@ 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.Property; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) @@ -97,35 +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() { + private DynamicObjectLibraryWrapper createDispatchedLibrary() { if (run == TestRun.CACHED_LIBRARY) { - return adopt(DynamicObjectLibrary.getFactory().createDispatched(5)); + return wrap(adopt(DynamicObjectLibrary.getFactory().createDispatched(5))); } else if (run == TestRun.UNCACHED_LIBRARY) { - return DynamicObjectLibrary.getUncached(); + return wrap(DynamicObjectLibrary.getUncached()); } return createLibrary(); } - private DynamicObjectLibrary createLibraryForReceiver(DynamicObject receiver) { - DynamicObjectLibrary objectLibrary = createLibrary(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(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(); } @@ -140,7 +140,7 @@ public void testGet1() throws UnexpectedResultException { uncachedPut(o2, k1, v1, 0); assertSame(o1.getShape(), o2.getShape()); - DynamicObjectLibrary getNode = createLibraryForReceiverAndKey(o1, k1); + var getNode = createLibraryForReceiverAndKey(o1, k1); assertEquals(v1, getNode.getOrDefault(o1, k1, null)); assertEquals(v1, getNode.getIntOrDefault(o1, k1, null)); assertEquals(v1, getNode.getOrDefault(o2, k1, null)); @@ -161,8 +161,7 @@ public void testGet1() throws UnexpectedResultException { assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); String missingKey = "missing"; - DynamicObjectLibrary getMissingKey; - getMissingKey = createLibraryForReceiverAndKey(o1, missingKey); + var getMissingKey = createLibraryForReceiverAndKey(o1, missingKey); assertEquals(null, getMissingKey.getOrDefault(o1, missingKey, null)); assertEquals(404, getMissingKey.getIntOrDefault(o1, missingKey, 404)); getMissingKey = createLibraryForReceiver(o1); @@ -181,7 +180,7 @@ public void testPut1() { uncachedPut(o2, key1, intval1, 0); assertSame(o1.getShape(), o2.getShape()); - DynamicObjectLibrary setNode = createLibraryForReceiverAndKey(o1, key1); + var setNode = createLibraryForReceiverAndKey(o1, key1); setNode.put(o1, key1, intval2); assertEquals(intval2, uncachedGet(o1, key1)); setNode.putInt(o1, key1, intval1); @@ -198,17 +197,17 @@ public void testPut1() { String key2 = "key2"; String strval2 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForReceiverAndKey(o1, key2); + var setNode2 = createLibraryForReceiverAndKey(o1, key2); setNode2.put(o1, key2, strval2); assertEquals(strval2, uncachedGet(o1, key2)); setNode2.putInt(o1, key2, intval1); assertEquals(intval1, uncachedGet(o1, key2)); - DynamicObjectLibrary setNode3 = createLibraryForReceiverAndKey(o1, key2); + var setNode3 = createLibraryForReceiverAndKey(o1, key2); setNode3.put(o1, key2, strval1); assertEquals(strval1, uncachedGet(o1, key2)); assertTrue(setNode3.accepts(o1)); - DynamicObjectLibrary setNode4 = createLibraryForReceiverAndKey(o1, key2); + var setNode4 = createLibraryForReceiverAndKey(o1, key2); setNode4.putInt(o1, key2, intval2); assertEquals(intval2, uncachedGet(o1, key2)); assertTrue(setNode4.accepts(o1)); @@ -225,7 +224,7 @@ public void testPutIfPresent() { uncachedPut(o2, key1, intval1, 0); assertSame(o1.getShape(), o2.getShape()); - DynamicObjectLibrary setNode = createLibraryForKey(key1); + var setNode = createLibraryForKey(key1); assertTrue(setNode.putIfPresent(o1, key1, intval2)); assertEquals(intval2, uncachedGet(o1, key1)); assertTrue(setNode.putIfPresent(o1, key1, intval1)); @@ -242,7 +241,7 @@ public void testPutIfPresent() { String key2 = "key2"; String strval2 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForReceiverAndKey(o1, key2); + var setNode2 = createLibraryForReceiverAndKey(o1, key2); assertFalse(uncachedLibrary().containsKey(o1, key2)); assertFalse(setNode2.putIfPresent(o1, key2, strval2)); assertTrue(setNode2.accepts(o1)); @@ -254,7 +253,7 @@ public void testPutIfPresent() { 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)); } @@ -269,7 +268,7 @@ public void testPut2() { int v2 = 43; uncachedPut(o3, k1, v1, 0); - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.put(o1, k1, v2); assertEquals(v2, uncachedGet(o1, k1)); Assert.assertEquals(0, uncachedGetProperty(o1, k1).getFlags()); @@ -294,7 +293,7 @@ public void testPut2() { String k2 = "key2"; String v4 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForKey(k2); + var setNode2 = createLibraryForKey(k2); setNode2.put(o1, k2, v4); assertEquals(v4, uncachedGet(o1, k2)); @@ -302,7 +301,7 @@ public void testPut2() { assertEquals(v1, uncachedGet(o1, k2)); int f2 = 0x42; - DynamicObjectLibrary setNode3 = createLibraryForKey(k1); + var setNode3 = createLibraryForKey(k1); setNode3.putWithFlags(o3, k1, v1, f2); assertEquals(v1, uncachedGet(o3, k1)); @@ -320,7 +319,7 @@ public void testPutWithFlags1() { uncachedPut(o3, k1, v1, 0); int flags = 0xf; - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.putWithFlags(o1, k1, v2, flags); assertEquals(v2, uncachedGet(o1, k1)); Assert.assertEquals(flags, uncachedGetProperty(o1, k1).getFlags()); @@ -345,7 +344,7 @@ public void testPutWithFlags1() { String k2 = "key2"; String v4 = "qwer"; - DynamicObjectLibrary setNode2 = createLibraryForKey(k2); + var setNode2 = createLibraryForKey(k2); setNode2.put(o1, k2, v4); assertEquals(v4, uncachedGet(o1, k2)); @@ -353,7 +352,7 @@ public void testPutWithFlags1() { assertEquals(v1, uncachedGet(o1, k2)); int f2 = 0x42; - DynamicObjectLibrary setNode3 = createLibraryForKey(k1); + var setNode3 = createLibraryForKey(k1); setNode3.putWithFlags(o3, k1, v1, f2); assertEquals(v1, uncachedGet(o3, k1)); @@ -362,7 +361,7 @@ public void testPutWithFlags1() { @Test public void testTypeIdAndShapeFlags() { - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); Object myType = newObjectType(); int flags = 42; String key = "key1"; @@ -389,7 +388,7 @@ 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(); @@ -400,7 +399,7 @@ public void testTypeIdAndShapeFlags() { @Test public void testShapeFlags() { - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); int flags = 42; DynamicObject o1 = createEmpty(); @@ -424,7 +423,7 @@ 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; @@ -439,7 +438,7 @@ public void testUpdateShapeFlags() { int f2 = 0x10; int f3 = 0x1f; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); uncachedPut(o1, "key", 42, 0); assertTrue(lib.setShapeFlags(o1, f1)); @@ -450,7 +449,7 @@ public void testUpdateShapeFlags() { assertEquals(f3, o1.getShape().getFlags()); } - private static boolean updateShapeFlags(DynamicObjectLibrary lib, DynamicObject obj, IntUnaryOperator updateFunction) { + private static boolean updateShapeFlags(DynamicObjectLibraryWrapper lib, DynamicObject obj, IntUnaryOperator updateFunction) { int oldFlags = lib.getShapeFlags(obj); int newFlags = updateFunction.applyAsInt(oldFlags); if (oldFlags == newFlags) { @@ -461,7 +460,7 @@ private static boolean updateShapeFlags(DynamicObjectLibrary lib, DynamicObject @Test public void testMakeShared() { - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); assertFalse(lib.isShared(o1)); @@ -481,7 +480,7 @@ public void testPropertyFlags() { int f2 = 0x10; int f3 = 0x1f; - DynamicObjectLibrary lib = createLibraryForKey(k1); + var lib = createLibraryForKey(k1); DynamicObject o1 = createEmpty(); uncachedPut(o1, k1, v1, 0); assertTrue(lib.setPropertyFlags(o1, k1, f1)); @@ -510,7 +509,7 @@ public void testPropertyFlags() { assertFalse(lib.setPropertyFlags(o3, k1, f1)); } - private static boolean updatePropertyFlags(DynamicObjectLibrary lib, DynamicObject obj, String key, IntUnaryOperator updateFunction) { + private static boolean updatePropertyFlags(DynamicObjectLibraryWrapper lib, DynamicObject obj, String key, IntUnaryOperator updateFunction) { Property property = lib.getProperty(obj, key); if (property == null) { return false; @@ -529,7 +528,7 @@ public void testRemove() { int v2 = 43; Object v3 = "value"; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); uncachedPut(o1, "key1", v1, 0); uncachedPut(o1, "key2", v2, 0); @@ -558,7 +557,7 @@ public void testResetShape() { int v1 = 42; int v2 = 43; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); Shape emptyShape = o1.getShape(); uncachedPut(o1, "key1", v1, 0); @@ -583,7 +582,7 @@ public void testGetKeysAndProperties() { int v2 = 43; Object v3 = "value"; - DynamicObjectLibrary lib = createDispatchedLibrary(); + var lib = createDispatchedLibrary(); DynamicObject o1 = createEmpty(); uncachedPut(o1, "key1", v1, 1); uncachedPut(o1, "key2", v2, 2); @@ -632,7 +631,7 @@ public void testAllPropertiesMatch() { uncachedPut(o1, "key3", v3, 43); assertFalse(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); - DynamicObjectLibrary lib2 = createLibraryForKey("key1"); + var lib2 = createLibraryForKey("key1"); lib2.removeKey(o1, "key1"); assertTrue(o1.getShape().allPropertiesMatch(p -> p.getFlags() >= 42)); } @@ -648,7 +647,7 @@ public void testGetProperty() { uncachedPut(o1, "key2", v2, 2); uncachedPut(o1, "key3", v3, 3); - DynamicObjectLibrary lib = createLibraryForReceiver(o1); + var lib = createLibraryForReceiver(o1); assertTrue(lib.accepts(o1)); for (int i = 1; i <= 3; i++) { Object key = "key" + i; @@ -667,7 +666,7 @@ public void testPutConstant1() { int v2 = 43; int flags = 0xf; - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.putConstant(o1, k1, v1, 0); assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); @@ -693,7 +692,7 @@ public void testPutConstant2() { int v2 = 43; int flags = 0xf; - DynamicObjectLibrary setNode1 = createLibraryForKey(k1); + var setNode1 = createLibraryForKey(k1); setNode1.putConstant(o1, k1, v1, 0); assertTrue(o1.getShape().getProperty(k1).getLocation().isConstant()); @@ -740,12 +739,12 @@ public void testPropertyAndShapeFlags() { uncachedLibrary().put(o1, "k13", false); updateAllFlags(o2, 3); updateAllFlags(o3, 3); - DynamicObjectLibrary library = createLibrary(o1); + var library = createLibrary(o1); assertEquals(1, library.getOrDefault(o3, "k13", null)); } private void fillObjectWithProperties(DynamicObject obj, boolean b) { - DynamicObjectLibrary library = createLibrary(obj); + var library = createLibrary(obj); for (int i = 0; i < 20; i++) { Object value; @@ -768,7 +767,7 @@ private void fillObjectWithProperties(DynamicObject obj, boolean b) { } private void updateAllFlags(DynamicObject obj, int flags) { - DynamicObjectLibrary propertyFlags = createLibrary(obj); + var propertyFlags = createLibrary(obj); for (Property property : propertyFlags.getPropertyArray(obj)) { int oldFlags = property.getFlags(); @@ -779,7 +778,7 @@ private void updateAllFlags(DynamicObject obj, int flags) { } } - DynamicObjectLibrary shapeFlags = createLibrary(obj); + var shapeFlags = createLibrary(obj); shapeFlags.setShapeFlags(obj, flags); } @@ -809,7 +808,7 @@ private static Object newObjectType() { } private List getKeyList(DynamicObject obj) { - DynamicObjectLibrary objectLibrary = createLibrary(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/DynamicTypeTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicTypeTest.java index f73d9c496caa..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,13 +45,13 @@ 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.object.Shape; + @RunWith(Parameterized.class) public class DynamicTypeTest extends ParametrizedDynamicObjectTest { @@ -65,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(obj); + var lib = createLibrary(obj); assertSame(dynamicType, lib.getDynamicType(obj)); dynamicType = new Object(); lib.setDynamicType(obj, dynamicType); @@ -77,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(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/ImplicitCastTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ImplicitCastTest.java index b4721cfd0cd3..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,6 +54,11 @@ 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.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 ParametrizedDynamicObjectTest { @@ -96,7 +96,7 @@ private static DynamicObject newInstance() { public void testIntOther() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", intVal); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -113,7 +113,7 @@ public void testIntOther() { public void testOtherInt() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", otherVal); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -130,7 +130,7 @@ public void testOtherInt() { public void testIntOtherDoesNotGoBack() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", intVal); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -153,7 +153,7 @@ public void testIntOtherDoesNotGoBack() { public void testIntObject() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", intVal); library.put(object, "a", ""); @@ -166,7 +166,7 @@ public void testIntObject() { public void testIntOtherObject() { DynamicObject object = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", intVal); library.put(object, "a", otherVal); @@ -180,7 +180,7 @@ public void testIntOtherObject() { public void testLocationDecoratorEquals() { DynamicObject object1 = newInstanceWithImplicitCast(); - DynamicObjectLibrary library = createLibrary(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/LocationTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/LocationTest.java index 24678421d221..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,6 +62,11 @@ 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.Location; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; + @SuppressWarnings("deprecation") @RunWith(Parameterized.class) public class LocationTest extends ParametrizedDynamicObjectTest { @@ -101,7 +101,7 @@ private DynamicObject newInstance() { public void testOnlyObjectLocationForObject() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "obj", new Object()); Location location = object.getShape().getProperty("obj").getLocation(); @@ -114,7 +114,7 @@ public void testOnlyObjectLocationForObject() { public void testOnlyPrimLocationForPrimitive() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "prim", 42); Location location = object.getShape().getProperty("prim").getLocation(); @@ -127,7 +127,7 @@ public void testOnlyPrimLocationForPrimitive() { public void testPrim2Object() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "foo", 42); Location location1 = object.getShape().getProperty("foo").getLocation(); @@ -146,7 +146,7 @@ public void testPrim2Object() { public void testUnrelatedPrimitivesGoToObject() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "foo", 42L); Location location1 = object.getShape().getProperty("foo").getLocation(); @@ -165,7 +165,7 @@ public void testUnrelatedPrimitivesGoToObject() { public void testChangeFlagsReuseLocation() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "foo", 42); Location location = object.getShape().getProperty("foo").getLocation(); @@ -182,7 +182,7 @@ public void testChangeFlagsReuseLocation() { public void testChangeFlagsChangeLocation() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "foo", 42); Location location = object.getShape().getProperty("foo").getLocation(); @@ -199,7 +199,7 @@ public void testChangeFlagsChangeLocation() { public void testDelete() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", 1); library.put(object, "b", 2); @@ -225,7 +225,7 @@ public void testLocationDecoratorEquals() { public void testDeleteDeclaredProperty() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.putConstant(object, "a", new Object(), 0); assertTrue(library.containsKey(object, "a")); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ObjectModelRegressionTest.java index b7bdc0b81591..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,6 +65,8 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.Assumption; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; @SuppressWarnings("deprecation") @RunWith(Parameterized.class) @@ -115,7 +114,7 @@ public void testDefinePropertyWithFlagsChangeAndFinalInvalidation() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.put(a, "r", 1); library.put(a, "x", 2); @@ -147,7 +146,7 @@ public void testAddNewPropertyOnObsoleteShape() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.put(a, "x", 1); library.put(b, "x", 1); @@ -168,7 +167,7 @@ public void testRemovePropertyOnObsoleteShape() { DynamicObject a = newInstance(emptyShape); DynamicObject b = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.put(a, "x", ""); library.put(b, "x", ""); @@ -190,7 +189,7 @@ public void testReplaceDeclaredProperty() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.putConstant(a, "a", null, 0); @@ -208,7 +207,7 @@ public void testReplaceDeclaredProperty2() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.putConstant(a, "a", null, 0); @@ -227,7 +226,7 @@ public void testDeclaredPropertyShapeTransitionCount() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); for (int i = 0; i < 26; i++) { library.putConstant(a, Character.toString((char) ('a' + i)), null, 0); @@ -258,7 +257,7 @@ public void testChangePropertyFlagsWithObsolescence() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.putWithFlags(a, "s", 42, 0); library.putIfPresent(a, "s", 43); @@ -283,7 +282,7 @@ public void testChangePropertyFlagsWithObsolescenceGR53902() { DynamicObject y = newInstance(emptyShape); DynamicObject z = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.putWithFlags(a, "s", 42, 0); library.putIfPresent(a, "s", 43); @@ -309,7 +308,7 @@ public void testChangePropertyTypeWithObsolescence() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.putWithFlags(a, "s", 13.37, 0); library.putWithFlags(a, "x", 42, 0); @@ -328,7 +327,7 @@ public void testAssumedFinalLocationShapeTransitionCount() { Shape emptyShape = newEmptyShape(); DynamicObject a = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); for (int i = 0; i < 26; i++) { library.put(a, Character.toString((char) ('a' + i)), 0); @@ -356,7 +355,7 @@ public void testChangeFlagsAndType() { DynamicObject temp = new TestDynamicObject(emptyShape); createLibrary(temp).putWithFlags(temp, "a", a, 8); - DynamicObjectLibrary library = createLibrary(obj); + var library = createLibrary(obj); library.put(obj, "a", 42); library.put(obj, "b", b); @@ -388,7 +387,7 @@ public void testChangeFlagsConstantToNonConstant() { DynamicObject temp = new TestDynamicObject(emptyShape); createLibrary(temp).putWithFlags(temp, "a", a, 8); - DynamicObjectLibrary library = createLibrary(obj); + var library = createLibrary(obj); library.putConstant(obj, "a", a, 3); library.put(obj, "b", b); @@ -419,7 +418,7 @@ public void testTryMergeShapes() { DynamicObject b = new TestDynamicObject(emptyShape); DynamicObject c = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.setShapeFlags(a, 1); library.put(a, "a", 1); @@ -468,7 +467,7 @@ public void testTryMergeShapes2() { DynamicObject a = new TestDynamicObject(emptyShape); DynamicObject b = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.put(a, "a", 1); Shape notObsoletedParent = a.getShape(); @@ -501,7 +500,7 @@ public void testBooleanLocationTypeAssumption() { DynamicObject obj = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(obj); + var library = createLibrary(obj); library.put(obj, "b1", true); library.put(obj, "b2", true); @@ -521,7 +520,7 @@ public void testPropertyAssumptionInvalidation() { DynamicObject a = new TestDynamicObject(emptyShape); - DynamicObjectLibrary library = createLibrary(a); + var library = createLibrary(a); library.put(a, "a", 1); library.put(a, "b", 2); @@ -557,8 +556,8 @@ public void testPropertyAssumptionInvalidAfterRemove() { Shape emptyShape = Shape.newBuilder().propertyAssumptions(true).build(); DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(h1); - DynamicObjectLibrary off = createLibrary(h1); + var on = createLibrary(h1); + var off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -588,8 +587,8 @@ public void testPropertyAssumptionInvalidAfterReplace1() { int flag = 2; DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(h1); - DynamicObjectLibrary off = createLibrary(h1); + var on = createLibrary(h1); + var off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -622,8 +621,8 @@ public void testPropertyAssumptionInvalidAfterReplace2() { int flag = 2; DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary on = createLibrary(h1); - DynamicObjectLibrary off = createLibrary(h1); + var on = createLibrary(h1); + var off = createLibrary(h1); // initialize caches on.put(h1, "name", h1); @@ -655,7 +654,7 @@ public void testPropertyAssumptionInvalidAfterTypeTransition() { Shape emptyShape = Shape.newBuilder().propertyAssumptions(true).build(); DynamicObject h1 = new TestDynamicObject(emptyShape); - DynamicObjectLibrary lib = createLibrary(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 index 92c0b0628f1c..b3e9b8cca926 100644 --- 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 @@ -40,14 +40,15 @@ */ package com.oracle.truffle.api.object.test; -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.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.DynamicObjectLibrary; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.test.AbstractLibraryTest; @RunWith(Parameterized.class) @@ -68,35 +69,243 @@ public enum TestRun { @Parameter // first data value (0) is default public /* NOT private */ TestRun run; - protected final DynamicObjectLibrary createLibrary(Object receiver) { + protected final DynamicObjectLibraryWrapper createLibrary(Object receiver) { return switch (run) { - case CACHED_LIBRARY -> adoptNode(DynamicObjectLibrary.getFactory().create(receiver)).get(); - case UNCACHED_LIBRARY -> DynamicObjectLibrary.getFactory().getUncached(receiver); - case DISPATCHED_CACHED_LIBRARY -> adoptNode(DynamicObjectLibrary.getFactory().createDispatched(2)).get(); - case DISPATCHED_UNCACHED_LIBRARY -> DynamicObjectLibrary.getUncached(); + case CACHED_LIBRARY -> wrap(adoptNode(DynamicObjectLibrary.getFactory().create(receiver)).get()); + case UNCACHED_LIBRARY -> wrap(DynamicObjectLibrary.getFactory().getUncached(receiver)); + case DISPATCHED_CACHED_LIBRARY -> wrap(adoptNode(DynamicObjectLibrary.getFactory().createDispatched(2)).get()); + case DISPATCHED_UNCACHED_LIBRARY -> wrap(DynamicObjectLibrary.getUncached()); case CACHED_NODES -> new NodesFakeDynamicObjectLibrary(); case UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; }; } - protected final DynamicObjectLibrary createLibrary() { + protected final DynamicObjectLibraryWrapper createLibrary() { assert run != TestRun.CACHED_LIBRARY; assert run != TestRun.UNCACHED_LIBRARY; return createLibrary(null); } - protected final DynamicObjectLibrary uncachedLibrary() { + protected final DynamicObjectLibraryWrapper uncachedLibrary() { return switch (run) { case CACHED_LIBRARY, UNCACHED_LIBRARY, DISPATCHED_CACHED_LIBRARY, DISPATCHED_UNCACHED_LIBRARY -> - DynamicObjectLibrary.getUncached(); + wrap(DynamicObjectLibrary.getUncached()); case CACHED_NODES, UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; }; } - static final DynamicObjectLibrary UNCACHED_NODES_LIBRARY = new NodesFakeDynamicObjectLibrary("uncached"); + 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(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 DynamicObjectLibrary { + static class NodesFakeDynamicObjectLibrary extends DynamicObjectLibraryWrapper { final DynamicObject.GetNode getNode; final DynamicObject.PutNode putNode; @@ -136,7 +345,7 @@ static class NodesFakeDynamicObjectLibrary extends DynamicObjectLibrary { getPropertyArrayNode = DynamicObject.GetPropertyArrayNode.create(); } - NodesFakeDynamicObjectLibrary(String uncached) { + NodesFakeDynamicObjectLibrary(@SuppressWarnings("unused") String uncached) { getNode = DynamicObject.GetNode.getUncached(); putNode = DynamicObject.PutNode.getUncached(); putConstantNode = DynamicObject.PutConstantNode.getUncached(); @@ -171,6 +380,21 @@ public Object getOrDefault(DynamicObject object, Object key, Object defaultValue return getNode.getOrDefault(object, key, defaultValue); } + @Override + public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return getNode.getIntOrDefault(object, key, defaultValue); + } + + @Override + public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return getNode.getLongOrDefault(object, key, defaultValue); + } + + @Override + public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { + return getNode.getDoubleOrDefault(object, key, defaultValue); + } + @Override public void put(DynamicObject object, Object key, Object value) { putNode.put(object, key, value); 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 598063a9134c..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,15 +50,15 @@ 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 com.oracle.truffle.api.object.test.ObjectModelRegressionTest.TestDynamicObject; 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.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 ParametrizedDynamicObjectTest { @@ -85,7 +85,7 @@ public void testIntLongBoxed() { Shape emptyShape = newEmptyShape(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "x", 42L); @@ -102,7 +102,7 @@ public void testImplicitCastIntToLong() { Shape emptyShape = newEmptyShapeWithImplicitCastIntToLong(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "x", 42L); @@ -121,7 +121,7 @@ public void testIntLongPolymorphic1() { Shape emptyShape = newEmptyShape(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "x", 42L); @@ -140,7 +140,7 @@ public void testIntLongPolymorphic2() { Shape emptyShape = newEmptyShape(); DynamicObject object1 = newInstance(emptyShape); - DynamicObjectLibrary library = createLibrary(object1); + var library = createLibrary(object1); library.put(object1, "x", 42); library.put(object1, "y", Integer.MAX_VALUE); @@ -173,7 +173,7 @@ public void testIntLongPolymorphic2() { public void testIntLongPolymorphic3() { Shape emptyShape = newEmptyShape(); DynamicObject o = newInstance(emptyShape); - DynamicObjectLibrary lib = createLibrary(o); + var lib = createLibrary(o); for (int i = -6; i < 0; i++) { lib.put(o, i, 0); } @@ -303,7 +303,7 @@ public void testIntLongPolymorphic3() { } private void verifySet(DynamicObject o, Object[] v, int i, Object value) { - DynamicObjectLibrary library = createLibrary(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 c2fcf7f2acbc..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,18 +46,17 @@ 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.PropertyGetter; -import com.oracle.truffle.api.object.Shape; -import org.junit.Test; +import java.util.List; -import com.oracle.truffle.api.nodes.UnexpectedResultException; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import java.util.List; +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.PropertyGetter; +import com.oracle.truffle.api.object.Shape; @RunWith(Parameterized.class) public class PropertyGetterTest extends ParametrizedDynamicObjectTest { @@ -83,7 +82,7 @@ public void testPropertyGetter() throws Exception { double doubleVal = 3.14159265359; String doubleKey = "doubleKey"; - DynamicObjectLibrary lib = createLibrary(); + var lib = createLibrary(); lib.putWithFlags(o1, key, val, 13); lib.putWithFlags(o2, key, val, 13); 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 efbabc617e15..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,14 +45,14 @@ 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.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; + @RunWith(Parameterized.class) public class RemoveKeyTest extends ParametrizedDynamicObjectTest { @@ -67,7 +67,7 @@ public static List data() { public void testRemoveAfterReplace() { DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary in = createLibrary(obj); + var in = createLibrary(obj); in.put(obj, "date", new Object()); in.put(obj, "time", new Object()); in.put(obj, "zone", new Object()); @@ -97,7 +97,7 @@ public void testRemoveAfterReplace() { Map archive = DOTestAsserts.archive(obj); - DynamicObjectLibrary rm = createLibrary(obj); + var rm = createLibrary(obj); rm.removeKey(obj, "time"); DOTestAsserts.verifyValues(obj, archive); @@ -107,7 +107,7 @@ public void testRemoveAfterReplace() { public void testRemoveAfterReplaceGR30786() { DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary in = createLibrary(obj); + var in = createLibrary(obj); in.put(obj, "head", new Object()); in.put(obj, "fun", new Object()); in.put(obj, "body", new Object()); @@ -141,7 +141,7 @@ public void testRemoveAfterReplaceGR30786() { Map archive = DOTestAsserts.archive(obj); - DynamicObjectLibrary rm = createLibrary(obj); + var rm = createLibrary(obj); rm.removeKey(obj, "fun"); DOTestAsserts.verifyValues(obj, archive); @@ -155,7 +155,7 @@ public void testReversePairwise() { Object undefined = new Object(); DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary lib = createLibrary(obj); + var lib = createLibrary(obj); lib.put(obj, "length", 10.0); lib.put(obj, "0", true); @@ -212,7 +212,7 @@ public void testReverseSequential() { Object undefined = new Object(); DynamicObject obj = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary lib = createLibrary(obj); + var lib = createLibrary(obj); lib.put(obj, "length", 10.0); lib.put(obj, "0", true); @@ -270,7 +270,7 @@ public void testReverseSequential() { @Test public void testRemoveUsingFallback() { DynamicObject obj1 = new TestDynamicObjectDefault(rootShape); - DynamicObjectLibrary lib = createLibrary(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 fd014428847f..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,16 +51,16 @@ 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.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 ParametrizedDynamicObjectTest { @@ -85,7 +85,7 @@ private DynamicObject newInstanceShared() { public void testDifferentLocationsImplicitCast() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", 1); Location location1 = object.getShape().getProperty("a").getLocation(); @@ -104,7 +104,7 @@ public void testDifferentLocationsImplicitCast() { public void testNoReuseOfPreviousLocation() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", 0); library.put(object, "a", 1); @@ -143,7 +143,7 @@ public void testNoReuseOfPreviousLocation() { public void testCanReuseLocationsUntilShared() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", 1); Location locationA1 = object.getShape().getProperty("a").getLocation(); @@ -202,7 +202,7 @@ public void testShapeIsSharedAndIdentity() { Assert.assertSame(sharedShape, rootShape.makeSharedShape()); Assert.assertEquals(true, sharedShape.isShared()); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.markShared(object); Assert.assertSame(sharedShape, object.getShape()); @@ -228,7 +228,7 @@ public void testShapeIsSharedAndIdentity() { public void testReuseReplaceProperty() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", 0); library.put(object, "a", 1); @@ -242,7 +242,7 @@ public void testReuseReplaceProperty() { public void testDeleteFromSharedShape() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); Shape emptyShape = object.getShape(); library.put(object, "a", 1); @@ -261,7 +261,7 @@ public void testDeleteFromSharedShape() { public void testDeleteFromSharedShape2() { DynamicObject object = newInstanceShared(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); Shape emptyShape = object.getShape(); library.put(object, "a", 1); @@ -277,7 +277,7 @@ public void testDeleteFromSharedShape2() { public void testReplaceProperty() { DynamicObject object = newInstance(); - DynamicObjectLibrary library = createLibrary(object); + var library = createLibrary(object); library.put(object, "a", 1); library.markShared(object); 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 11d7a03972fc..7819bb8d189f 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 @@ -106,7 +106,7 @@ public abstract class DynamicObjectLibrary extends Library { /** * @since 20.2.0 */ - public DynamicObjectLibrary() { + DynamicObjectLibrary() { } /** From bb975e843c324e9b059fb26a53d9fb9c0e26ea83 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 13 Oct 2025 18:10:27 +0200 Subject: [PATCH 03/33] Add DynamicObject benchmarks for primitive get and set and field access. --- .../benchmark/DynamicObjectBenchmark.java | 118 ++++++++++++++++-- 1 file changed, 108 insertions(+), 10 deletions(-) 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 db15a1c2e2fd..7d12df1d0040 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 @@ -46,6 +46,8 @@ 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; @@ -53,6 +55,7 @@ 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.Shape; @@ -63,10 +66,11 @@ public class DynamicObjectBenchmark extends TruffleBenchmark { private static final int PROPERTY_KEYS_PER_ITERATION = 1000; static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - static final DynamicObject.GetNode GET_NODE = DynamicObject.GetNode.create(); - static final DynamicObject.PutNode PUT_NODE = DynamicObject.PutNode.create(); static final String[] PROPERTY_KEYS = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new); - static final String SOME_KEY = PROPERTY_KEYS[10]; + 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) { @@ -74,6 +78,18 @@ 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(); @@ -107,7 +123,8 @@ public void setup(SharedEngineState shared) { object = new MyDynamicObject(shared.rootShape); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { - DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); + String key = PROPERTY_KEYS[i]; + DynamicObject.PutNode.getUncached().put(object, key, "testValue"); } } @@ -144,17 +161,98 @@ public void shapeTransitionMapUncontended(SharedEngineState shared, PerThreadCon } } + @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().put(object, key, i); + } else if (key.equals(SOME_KEY_LONG)) { + DynamicObject.PutNode.getUncached().put(object, key, (long) i); + } else if (key.equals(SOME_KEY_DOUBLE)) { + DynamicObject.PutNode.getUncached().put(object, key, (double) i); + } else { + DynamicObject.PutNode.getUncached().put(object, key, "testValue"); + } + } + } + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Object get(AccessNodeBenchState state) { + DynamicObject object = state.object; + return state.getNode.getOrDefault(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.getIntOrDefault(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.getLongOrDefault(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.getDoubleOrDefault(object, SOME_KEY_DOUBLE, null); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void put(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.put(object, SOME_KEY, "updated value"); + } + + @Benchmark + @Threads(1) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void putInt(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.put(object, SOME_KEY_INT, 42); + } + @Benchmark @Threads(1) - public Object get(SharedEngineState shared, PerThreadContextState perThread) { - DynamicObject object = perThread.object; - return GET_NODE.getOrDefault(object, SOME_KEY, null); + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void putLong(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.put(object, SOME_KEY_LONG, (long) 42); } @Benchmark @Threads(1) - public void put(SharedEngineState shared, PerThreadContextState perThread) { - DynamicObject object = perThread.object; - PUT_NODE.put(object, SOME_KEY, "updated value"); + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void putDouble(AccessNodeBenchState state) { + DynamicObject object = state.object; + state.putNode.put(object, SOME_KEY_DOUBLE, (double) 42); } } From 3c3b90b25de221bc22f6195a1326c0989e97d293 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 31 Oct 2025 06:39:43 +0100 Subject: [PATCH 04/33] No need to enter a context for DynamicObject benchmarking. --- .../benchmark/DynamicObjectBenchmark.java | 20 ------------------- 1 file changed, 20 deletions(-) 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 7d12df1d0040..4a200c99ba25 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 @@ -43,15 +43,12 @@ import java.lang.invoke.MethodHandles; 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; @@ -62,7 +59,6 @@ @Warmup(iterations = 10, time = 1) 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(); @@ -92,15 +88,9 @@ private static final class MyDynamicObjectWithFields extends DynamicObject { @State(Scope.Benchmark) public static class SharedEngineState { - final Engine engine = Engine.newBuilder().allowExperimentalOptions(true).option("engine.Compilation", "false").build(); 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) { @@ -113,26 +103,16 @@ private void assertSameShape(int i, Shape actualShape) { @State(Scope.Thread) public static class PerThreadContextState { - Context context; DynamicObject object; @Setup public void setup(SharedEngineState shared) { - context = Context.newBuilder(TEST_LANGUAGE).engine(shared.engine).build(); - context.enter(); - object = new MyDynamicObject(shared.rootShape); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { String key = PROPERTY_KEYS[i]; DynamicObject.PutNode.getUncached().put(object, key, "testValue"); } } - - @TearDown - public void tearDown() { - context.leave(); - context.close(); - } } /** From 6a56eaee963597126ca2249ec7384355b30fccb1 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 4 Nov 2025 21:23:18 +0100 Subject: [PATCH 05/33] Rename getOrDefault to execute. --- .../DynamicObjectPartialEvaluationTest.java | 2 +- .../api/object/test/CachedFallbackTest.java | 2 +- .../object/test/DynamicObjectNodesTest.java | 38 ++++++------- .../test/ParametrizedDynamicObjectTest.java | 8 +-- .../test/TestNestedDispatchGetNode.java | 4 +- .../truffle/api/object/DynamicObject.java | 57 +++++++------------ .../api/object/DynamicObjectLibrary.java | 8 +-- .../nodes/expression/SLReadPropertyNode.java | 2 +- .../oracle/truffle/sl/runtime/SLObject.java | 3 +- .../benchmark/DynamicObjectBenchmark.java | 10 ++-- 10 files changed, 59 insertions(+), 75 deletions(-) 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 e726ff538e46..cb9ba9fc0d8e 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 @@ -163,7 +163,7 @@ public int execute(VirtualFrame frame) { private int getInt(DynamicObject obj, Object key) { try { - return getNode.getIntOrDefault(obj, key, null); + return getNode.executeInt(obj, key, null); } catch (UnexpectedResultException e) { throw CompilerDirectives.shouldNotReachHere(); } 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 f25eac8bb9ef..1bdf74d2aa94 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 @@ -78,7 +78,7 @@ public Object execute(DynamicObject obj, Object key) { if (node != null) { return node.execute(obj, key); } else { - return getNode.getOrDefault(obj, key, null); + return getNode.execute(obj, key, null); } } } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java index b90d740323d6..281f5231ef94 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -284,32 +284,32 @@ public void testGet1() throws UnexpectedResultException { assertSame(o1.getShape(), o2.getShape()); var getNode = createGetNode(); - assertEquals(v1, getNode.getOrDefault(o1, k1, null)); - assertEquals(v1, getNode.getIntOrDefault(o1, k1, null)); - assertEquals(v1, getNode.getOrDefault(o2, k1, null)); - assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + assertEquals(v1, getNode.execute(o1, k1, null)); + assertEquals(v1, getNode.executeInt(o1, k1, null)); + assertEquals(v1, getNode.execute(o2, k1, null)); + assertEquals(v1, getNode.executeInt(o2, k1, null)); String v2 = "asdf"; uncachedSet(o1, k1, v2); getNode = createGetNode(); - assertEquals(v2, getNode.getOrDefault(o1, k1, null)); + assertEquals(v2, getNode.execute(o1, k1, null)); try { - getNode.getIntOrDefault(o1, k1, null); + getNode.executeInt(o1, k1, null); fail(); } catch (UnexpectedResultException e) { assertEquals(v2, e.getResult()); } - assertEquals(v1, getNode.getOrDefault(o2, k1, null)); - assertEquals(v1, getNode.getIntOrDefault(o2, k1, null)); + assertEquals(v1, getNode.execute(o2, k1, null)); + assertEquals(v1, getNode.executeInt(o2, k1, null)); String missingKey = "missing"; var getMissingKey = createGetNode(); - assertEquals(null, getMissingKey.getOrDefault(o1, missingKey, null)); - assertEquals(404, getMissingKey.getIntOrDefault(o1, missingKey, 404)); + assertEquals(null, getMissingKey.execute(o1, missingKey, null)); + assertEquals(404, getMissingKey.executeInt(o1, missingKey, 404)); var getMissingKey2 = createGetNode(); - assertEquals(null, getMissingKey2.getOrDefault(o1, missingKey, null)); - assertEquals(404, getMissingKey2.getIntOrDefault(o1, missingKey, 404)); + assertEquals(null, getMissingKey2.execute(o1, missingKey, null)); + assertEquals(404, getMissingKey2.executeInt(o1, missingKey, 404)); } @Test @@ -514,8 +514,8 @@ public void testCopyProperties() { setNode.putWithFlags(o1, "key2", 2, 2); DynamicObject o2 = createEmpty(); copyPropertiesNode.execute(o1, o2); - assertEquals(1, getNode.getOrNull(o1, "key1")); - assertEquals(2, getNode.getOrNull(o1, "key2")); + assertEquals(1, 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()); @@ -759,8 +759,8 @@ public void testRemove() { assertEquals(Arrays.asList(k2, k3, k1), getKeyList(o1)); assertTrue(removeKey3.execute(o1, k3)); assertEquals(Arrays.asList(k2, k1), getKeyList(o1)); - assertEquals(v1, getKey1.getOrDefault(o1, k1, null)); - assertEquals(v2, getKey2.getOrDefault(o1, k2, null)); + assertEquals(v1, getKey1.execute(o1, k1, null)); + assertEquals(v2, getKey2.execute(o1, k2, null)); } @Test @@ -959,7 +959,7 @@ public void testPropertyAndShapeFlags() { updateAllFlags(o2, 3); updateAllFlags(o3, 3); var getNode = createGetNode(); - assertEquals(1, getNode.getOrDefault(o3, "k13", null)); + assertEquals(1, getNode.execute(o3, "k13", null)); } private void fillObjectWithProperties(DynamicObject obj, boolean b) { @@ -1015,7 +1015,7 @@ private static void uncachedSet(DynamicObject obj, Object key, Object value) { } private static Object uncachedGet(DynamicObject obj, Object key) { - return DynamicObject.GetNode.getUncached().getOrDefault(obj, key, null); + return DynamicObject.GetNode.getUncached().execute(obj, key, null); } private static Property uncachedGetProperty(DynamicObject obj, Object key) { @@ -1042,7 +1042,7 @@ public abstract static class TestGet extends Node { @Specialization public Object doGet(DynamicObject obj, @Cached DynamicObject.GetNode get) { - return get.getOrDefault(obj, "test", null); + return get.execute(obj, "test", null); } } } 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 index b3e9b8cca926..db6520ea549e 100644 --- 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 @@ -377,22 +377,22 @@ public Shape getShape(DynamicObject object) { @Override public Object getOrDefault(DynamicObject object, Object key, Object defaultValue) { - return getNode.getOrDefault(object, key, defaultValue); + return getNode.execute(object, key, defaultValue); } @Override public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { - return getNode.getIntOrDefault(object, key, defaultValue); + return getNode.executeInt(object, key, defaultValue); } @Override public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { - return getNode.getLongOrDefault(object, key, defaultValue); + return getNode.executeLong(object, key, defaultValue); } @Override public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { - return getNode.getDoubleOrDefault(object, key, defaultValue); + return getNode.executeDouble(object, key, defaultValue); } @Override 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 index 1dc61b64dff3..75387acfcc3a 100644 --- 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 @@ -59,7 +59,7 @@ Object cached(DynamicObject obj, @Cached GetNode getNode, @Cached(value = "cachedObj.getShape().getProperty(key) != null") boolean hasProperty, @Cached TestNestedDispatchNode nested) { - Object value = getNode.getOrDefault(obj, key, null); + Object value = getNode.execute(obj, key, null); if (hasProperty) { assert value != null; if (value instanceof DynamicObject) { @@ -74,7 +74,7 @@ Object cached(DynamicObject obj, @Cached GetNode getNode, @Cached(value = "obj.getShape().getProperty(key) != null", allowUncached = true) boolean hasProperty, @Cached TestNestedDispatchNode nested) { - Object value = getNode.getOrDefault(obj, key, null); + Object value = getNode.execute(obj, key, null); if (hasProperty) { assert value != null; if (value instanceof DynamicObject) { 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 cfe35bd48c29..a666e3b9e9cf 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 @@ -121,7 +121,7 @@ * @Cached TruffleString.EqualNode equalNode, * @Cached TruffleString cachedKey, * @Cached @Exclusive DynamicObject.GetNode getNode) { - * return getNode.getOrDefault(receiver, cachedKey, NULL_VALUE); + * return getNode.execute(receiver, cachedKey, NULL_VALUE); * } * } * @@ -131,7 +131,7 @@ * @Specialization(limit = "3") * static Object read(MyDynamicObjectSubclass receiver, Object key, * @Cached DynamicObject.GetNode getNode) { - * return getNode.getOrDefault(receiver, key, NULL_VALUE); + * return getNode.execute(receiver, key, NULL_VALUE); * } * } * @@ -139,7 +139,7 @@ * @ExportMessage * Object readMember(String name, * @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { - * Object result = getNode.getOrDefault(this, name, null); + * Object result = getNode.execute(this, name, null); * if (result == null) { * throw UnknownIdentifierException.create(name); * } @@ -348,11 +348,10 @@ static Lookup internalLookup() { *

    * Specialized return type variants are available for when a primitive result is expected. * - * @see #getOrNull(DynamicObject, Object) - * @see #getOrDefault(DynamicObject, Object, Object) - * @see #getIntOrDefault(DynamicObject, Object, Object) - * @see #getLongOrDefault(DynamicObject, Object, Object) - * @see #getDoubleOrDefault(DynamicObject, Object, Object) + * @see #execute(DynamicObject, Object, Object) + * @see #executeInt(DynamicObject, Object, Object) + * @see #executeLong(DynamicObject, Object, Object) + * @see #executeDouble(DynamicObject, Object, Object) * @since 25.1 */ @GeneratePackagePrivate @@ -365,13 +364,6 @@ public abstract static class GetNode extends Node { GetNode() { } - /** - * The same as {@code getOrDefault(receiver, key, null)}. - */ - public final Object getOrNull(DynamicObject receiver, Object key) { - return getOrDefault(receiver, key, null); - } - // @formatter:off /** * Gets the value of an existing property or returns the provided default value if no such property exists. @@ -382,18 +374,17 @@ public final Object getOrNull(DynamicObject receiver, Object key) { * @Specialization(limit = "3") * static Object read(DynamicObject receiver, Object key, * @Cached DynamicObject.GetNode getNode) { - * return getNode.getOrDefault(receiver, key, NULL_VALUE); + * return getNode.execute(receiver, key, NULL_VALUE); * } * } * * @param key the property key * @param defaultValue value to be returned if the property does not exist * @return the property's value if it exists, else {@code defaultValue}. + * @since 25.1 */ // @formatter:on - public final Object getOrDefault(DynamicObject receiver, Object key, Object defaultValue) { - return executeImpl(receiver, key, defaultValue); - } + 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 @@ -404,10 +395,10 @@ public final Object getOrDefault(DynamicObject receiver, Object key, Object defa * @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 final int getIntOrDefault(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { - return executeImplInt(receiver, key, defaultValue); - } + 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 @@ -418,10 +409,10 @@ public final int getIntOrDefault(DynamicObject receiver, Object key, Object defa * @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 final long getLongOrDefault(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { - return executeImplLong(receiver, key, defaultValue); - } + 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 @@ -432,20 +423,10 @@ public final long getLongOrDefault(DynamicObject receiver, Object key, Object de * @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 final double getDoubleOrDefault(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { - return executeImplDouble(receiver, key, defaultValue); - } - - // private - - abstract Object executeImpl(DynamicObject receiver, Object key, Object defaultValue); - - abstract int executeImplInt(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; - - abstract long executeImplLong(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; - - abstract double executeImplDouble(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException; + 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) 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 7819bb8d189f..dd12cfa449bf 100644 --- a/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibrary.java +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectLibrary.java @@ -156,7 +156,7 @@ public static DynamicObjectLibrary getUncached() { * @param key the property key * @param defaultValue value to be returned if the property does not exist * @return the property's value if it exists, else {@code defaultValue}. - * @see DynamicObject.GetNode#getOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract Object getOrDefault(DynamicObject object, Object key, Object defaultValue); @@ -170,7 +170,7 @@ public static DynamicObjectLibrary getUncached() { * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not an {@code int} * @see #getOrDefault(DynamicObject, Object, Object) - * @see DynamicObject.GetNode#getIntOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#executeInt(DynamicObject, Object, Object) * @since 20.2.0 */ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -191,7 +191,7 @@ public int getIntOrDefault(DynamicObject object, Object key, Object defaultValue * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not a {@code double} * @see #getOrDefault(DynamicObject, Object, Object) - * @see DynamicObject.GetNode#getDoubleOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#executeDouble(DynamicObject, Object, Object) * @since 20.2.0 */ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { @@ -212,7 +212,7 @@ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaul * @return the property's value if it exists, else {@code defaultValue}. * @throws UnexpectedResultException if the (default) value is not a {@code long} * @see #getOrDefault(DynamicObject, Object, Object) - * @see DynamicObject.GetNode#getLongOrDefault(DynamicObject, Object, Object) + * @see DynamicObject.GetNode#executeLong(DynamicObject, Object, Object) * @since 20.2.0 */ public long getLongOrDefault(DynamicObject object, Object key, Object defaultValue) throws UnexpectedResultException { 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 938faa02dfc8..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 @@ -95,7 +95,7 @@ public static Object readSLObject(SLObject receiver, Object name, @Cached DynamicObject.GetNode getNode, @Cached SLToTruffleStringNode toTruffleStringNode) { TruffleString nameTS = toTruffleStringNode.execute(node, name); - Object result = getNode.getOrNull(receiver, nameTS); + 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/runtime/SLObject.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLObject.java index 897b073a84ee..880cf2fd01b4 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 @@ -210,7 +210,8 @@ boolean isArrayElementReadable(long index) { Object readMember(String name, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { - Object result = getNode.getOrNull(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING)); + 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); 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 4a200c99ba25..e4e2bb4df8e1 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 @@ -41,6 +41,7 @@ package org.graalvm.truffle.benchmark; import java.lang.invoke.MethodHandles; +import java.util.Objects; import java.util.stream.IntStream; import org.openjdk.jmh.annotations.Benchmark; @@ -168,6 +169,7 @@ public void setup() { } else { DynamicObject.PutNode.getUncached().put(object, key, "testValue"); } + Objects.requireNonNull(DynamicObject.GetNode.getUncached().execute(object, key, null), key); } } } @@ -177,7 +179,7 @@ public void setup() { @CompilerControl(CompilerControl.Mode.DONT_INLINE) public Object get(AccessNodeBenchState state) { DynamicObject object = state.object; - return state.getNode.getOrDefault(object, SOME_KEY, null); + return state.getNode.execute(object, SOME_KEY, null); } @Benchmark @@ -185,7 +187,7 @@ public Object get(AccessNodeBenchState state) { @CompilerControl(CompilerControl.Mode.DONT_INLINE) public int getInt(AccessNodeBenchState state) throws UnexpectedResultException { DynamicObject object = state.object; - return state.getNode.getIntOrDefault(object, SOME_KEY_INT, null); + return state.getNode.executeInt(object, SOME_KEY_INT, null); } @Benchmark @@ -193,7 +195,7 @@ public int getInt(AccessNodeBenchState state) throws UnexpectedResultException { @CompilerControl(CompilerControl.Mode.DONT_INLINE) public long getLong(AccessNodeBenchState state) throws UnexpectedResultException { DynamicObject object = state.object; - return state.getNode.getLongOrDefault(object, SOME_KEY_LONG, null); + return state.getNode.executeLong(object, SOME_KEY_LONG, null); } @Benchmark @@ -201,7 +203,7 @@ public long getLong(AccessNodeBenchState state) throws UnexpectedResultException @CompilerControl(CompilerControl.Mode.DONT_INLINE) public double getDouble(AccessNodeBenchState state) throws UnexpectedResultException { DynamicObject object = state.object; - return state.getNode.getDoubleOrDefault(object, SOME_KEY_DOUBLE, null); + return state.getNode.executeDouble(object, SOME_KEY_DOUBLE, null); } @Benchmark From 312226e4ba7740f44834542f0c1badd9561384b1 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 4 Nov 2025 21:41:50 +0100 Subject: [PATCH 06/33] Rename put to execute and add InliningRoot annotation. --- .../DynamicObjectPartialEvaluationTest.java | 8 +- .../api/object/test/CachedFallbackTest.java | 2 +- .../object/test/DynamicObjectNodesTest.java | 98 ++++++++-------- .../test/ParametrizedDynamicObjectTest.java | 8 +- .../truffle/api/object/DynamicObject.java | 111 ++++++++++-------- .../api/object/DynamicObjectLibrary.java | 14 +-- .../nodes/expression/SLWritePropertyNode.java | 2 +- .../oracle/truffle/sl/runtime/SLObject.java | 2 +- .../benchmark/DynamicObjectBenchmark.java | 22 ++-- 9 files changed, 140 insertions(+), 127 deletions(-) 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 cb9ba9fc0d8e..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 @@ -78,7 +78,7 @@ private TestDynamicObject newInstanceWithoutFields() { @Test public void testFieldLocation() { TestDynamicObject obj = newInstanceWithFields(); - DynamicObject.PutNode.getUncached().put(obj, "key", 22); + DynamicObject.PutNode.getUncached().execute(obj, "key", 22); Object[] args = {obj, 22}; OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testFieldStoreLoad"); @@ -106,7 +106,7 @@ public void testFieldLocation() { @Test public void testArrayLocation() { TestDynamicObject obj = newInstanceWithoutFields(); - DynamicObject.PutNode.getUncached().put(obj, "key", 22); + DynamicObject.PutNode.getUncached().execute(obj, "key", 22); Object[] args = {obj, 22}; OptimizedCallTarget callTarget = makeCallTarget(new TestDynamicObjectGetAndPutNode(), "testArrayStoreLoad"); @@ -148,7 +148,7 @@ public int execute(VirtualFrame frame) { DynamicObject obj = (DynamicObject) arg0; if (frame.getArguments().length > 1) { Object arg1 = frame.getArguments()[1]; - putNode.put(obj, "key", (int) arg1); + putNode.execute(obj, "key", (int) arg1); } int val; while (true) { @@ -156,7 +156,7 @@ public int execute(VirtualFrame frame) { if (val >= 42) { break; } - putNode.put(obj, "key", val + 2); + putNode.execute(obj, "key", val + 2); } return val; } 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 1bdf74d2aa94..0a4e5a2a9010 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 @@ -88,7 +88,7 @@ public void execute(DynamicObject obj, Object key, Object value) { if (node != null) { node.execute(obj, key, value); } else { - putNode.put(obj, key, value); + putNode.execute(obj, key, value); } } } diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java index 281f5231ef94..f1f52e890ac0 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -324,34 +324,34 @@ public void testPut1() { assertSame(o1.getShape(), o2.getShape()); var setNode = createPutNode(); - setNode.put(o1, key1, intval2); + setNode.execute(o1, key1, intval2); assertEquals(intval2, uncachedGet(o1, key1)); - setNode.put(o1, key1, intval1); + setNode.execute(o1, key1, intval1); assertEquals(intval1, uncachedGet(o1, key1)); - setNode.put(o2, key1, intval2); + setNode.execute(o2, key1, intval2); assertEquals(intval2, uncachedGet(o2, key1)); - setNode.put(o2, key1, intval1); + setNode.execute(o2, key1, intval1); assertEquals(intval1, uncachedGet(o2, key1)); assertSame(o1.getShape(), o2.getShape()); String strval1 = "asdf"; - setNode.put(o1, key1, strval1); + setNode.execute(o1, key1, strval1); assertEquals(strval1, uncachedGet(o1, key1)); String key2 = "key2"; String strval2 = "qwer"; var setNode2 = createPutNode(); - setNode2.put(o1, key2, strval2); + setNode2.execute(o1, key2, strval2); assertEquals(strval2, uncachedGet(o1, key2)); - setNode2.put(o1, key2, intval1); + setNode2.execute(o1, key2, intval1); assertEquals(intval1, uncachedGet(o1, key2)); var setNode3 = createPutNode(); - setNode3.put(o1, key2, strval1); + setNode3.execute(o1, key2, strval1); assertEquals(strval1, uncachedGet(o1, key2)); // assertTrue(setNode3.accepts(o1)); var setNode4 = createPutNode(); - setNode4.put(o1, key2, intval2); + setNode4.execute(o1, key2, intval2); assertEquals(intval2, uncachedGet(o1, key2)); // assertTrue(setNode4.accepts(o1)); } @@ -368,18 +368,18 @@ public void testPutIfPresent() { assertSame(o1.getShape(), o2.getShape()); var setNode = createPutNode(); - assertTrue(setNode.putIfPresent(o1, key1, intval2)); + assertTrue(setNode.executeIfPresent(o1, key1, intval2)); assertEquals(intval2, uncachedGet(o1, key1)); - assertTrue(setNode.putIfPresent(o1, key1, intval1)); + assertTrue(setNode.executeIfPresent(o1, key1, intval1)); assertEquals(intval1, uncachedGet(o1, key1)); - assertTrue(setNode.putIfPresent(o2, key1, intval2)); + assertTrue(setNode.executeIfPresent(o2, key1, intval2)); assertEquals(intval2, uncachedGet(o2, key1)); - assertTrue(setNode.putIfPresent(o2, key1, intval1)); + assertTrue(setNode.executeIfPresent(o2, key1, intval1)); assertEquals(intval1, uncachedGet(o2, key1)); assertSame(o1.getShape(), o2.getShape()); String strval1 = "asdf"; - setNode.put(o1, key1, strval1); + setNode.execute(o1, key1, strval1); assertEquals(strval1, uncachedGet(o1, key1)); String key2 = "key2"; @@ -387,17 +387,17 @@ public void testPutIfPresent() { var setNode2 = createPutNode(); var containsKeyNode2 = createContainsKeyNode(); assertFalse(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); - assertFalse(setNode2.putIfPresent(o1, key2, strval2)); + assertFalse(setNode2.executeIfPresent(o1, key2, strval2)); // assertTrue(setNode2.accepts(o1)); assertFalse(containsKeyNode2.execute(o1, key2)); assertEquals(null, uncachedGet(o1, key2)); - setNode2.put(o1, key2, strval2); + setNode2.execute(o1, key2, strval2); assertTrue(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); assertEquals(strval2, uncachedGet(o1, key2)); var setNode3 = createPutNode(); - assertTrue(setNode3.putIfPresent(o1, key2, intval1)); + assertTrue(setNode3.executeIfPresent(o1, key2, intval1)); assertEquals(intval1, uncachedGet(o1, key2)); } @@ -412,14 +412,14 @@ public void testPut2() { uncachedPut(o3, k1, v1, 0); var setNode1 = createPutNode(); - setNode1.put(o1, k1, v2); + setNode1.execute(o1, k1, v2); assertEquals(v2, uncachedGet(o1, k1)); assertEquals(0, uncachedGetProperty(o1, k1).getFlags()); - setNode1.put(o1, k1, v1); + setNode1.execute(o1, k1, v1); assertEquals(v1, uncachedGet(o1, k1)); - setNode1.put(o2, k1, v2); + setNode1.execute(o2, k1, v2); assertEquals(v2, uncachedGet(o2, k1)); - setNode1.put(o2, k1, v1); + setNode1.execute(o2, k1, v1); assertEquals(v1, uncachedGet(o2, k1)); assertSame(o1.getShape(), o2.getShape()); @@ -431,22 +431,22 @@ public void testPut2() { assertSame(o1.getShape(), o3.getShape()); String v3 = "asdf"; - setNode1.put(o1, k1, v3); + setNode1.execute(o1, k1, v3); assertEquals(v3, uncachedGet(o1, k1)); String k2 = "key2"; String v4 = "qwer"; var setNode2 = createPutNode(); - setNode2.put(o1, k2, v4); + setNode2.execute(o1, k2, v4); assertEquals(v4, uncachedGet(o1, k2)); - setNode2.put(o1, k2, v1); + setNode2.execute(o1, k2, v1); assertEquals(v1, uncachedGet(o1, k2)); int f2 = 0x42; var setNode3 = createPutNode(); - setNode3.putWithFlags(o3, k1, v1, f2); + setNode3.executeWithFlags(o3, k1, v1, f2); assertEquals(v1, uncachedGet(o3, k1)); assertEquals(f2, uncachedGetProperty(o3, k1).getFlags()); } @@ -463,14 +463,14 @@ public void testPutWithFlags1() { int flags = 0xf; var setNode1 = createPutNode(); - setNode1.putWithFlags(o1, k1, v2, flags); + setNode1.executeWithFlags(o1, k1, v2, flags); assertEquals(v2, uncachedGet(o1, k1)); assertEquals(flags, uncachedGetProperty(o1, k1).getFlags()); - setNode1.putWithFlags(o1, k1, v1, flags); + setNode1.executeWithFlags(o1, k1, v1, flags); assertEquals(v1, uncachedGet(o1, k1)); - setNode1.putWithFlags(o2, k1, v2, flags); + setNode1.executeWithFlags(o2, k1, v2, flags); assertEquals(v2, uncachedGet(o2, k1)); - setNode1.putWithFlags(o2, k1, v1, flags); + setNode1.executeWithFlags(o2, k1, v1, flags); assertEquals(v1, uncachedGet(o2, k1)); assertSame(o1.getShape(), o2.getShape()); @@ -482,22 +482,22 @@ public void testPutWithFlags1() { // assertSame(o1.getShape(), o3.getShape()); String v3 = "asdf"; - setNode1.putWithFlags(o1, k1, v3, flags); + setNode1.executeWithFlags(o1, k1, v3, flags); assertEquals(v3, uncachedGet(o1, k1)); String k2 = "key2"; String v4 = "qwer"; var setNode2 = createPutNode(); - setNode2.put(o1, k2, v4); + setNode2.execute(o1, k2, v4); assertEquals(v4, uncachedGet(o1, k2)); - setNode2.put(o1, k2, v1); + setNode2.execute(o1, k2, v1); assertEquals(v1, uncachedGet(o1, k2)); int f2 = 0x42; var setNode3 = createPutNode(); - setNode3.putWithFlags(o3, k1, v1, f2); + setNode3.executeWithFlags(o3, k1, v1, f2); assertEquals(v1, uncachedGet(o3, k1)); assertEquals(f2, uncachedGetProperty(o3, k1).getFlags()); } @@ -510,8 +510,8 @@ public void testCopyProperties() { var getPropertyFlagsNode = createGetPropertyFlagsNode(); DynamicObject o1 = createEmpty(); - setNode.putWithFlags(o1, "key1", 1, 1); - setNode.putWithFlags(o1, "key2", 2, 2); + 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)); @@ -550,9 +550,9 @@ public void testTypeIdAndShapeFlags() { DynamicObject o4 = createEmpty(); setShapeFlagsNode.execute(o4, flags); - putNode.put(o4, key, 42); + putNode.execute(o4, key, 42); setDynamicTypeNode.execute(o4, myType); - putNode.put(o4, key, "value"); + putNode.execute(o4, key, "value"); assertSame(myType, getDynamicTypeNode.execute(o4)); assertSame(myType, getDynamicTypeNode.execute(o4)); @@ -660,7 +660,7 @@ public void testMakeShared() { assertFalse(isSharedNode.execute(o1)); markSharedNode.execute(o1); assertTrue(isSharedNode.execute(o1)); - putNode.put(o1, key, "value"); + putNode.execute(o1, key, "value"); assertTrue(isSharedNode.execute(o1)); assertTrue(containsKeyNode.execute(o1, key)); } @@ -886,17 +886,17 @@ public void testPutConstant1() { var putConstantNode = createPutConstantNode(); var putNode = createPutNode(); - putConstantNode.putConstantWithFlags(o1, k1, v1, 0); + 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.putConstantWithFlags(o1, k1, v1, flags); + 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.put(o1, k1, v2); + putNode.execute(o1, k1, v2); assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); assertEquals(flags, o1.getShape().getProperty(k1).getFlags()); assertEquals(v2, uncachedGet(o1, k1)); @@ -913,12 +913,12 @@ public void testPutConstant2() { var putConstantNode = createPutConstantNode(); var putNode = createPutNode(); - putConstantNode.putConstantWithFlags(o1, k1, v1, 0); + 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.putWithFlags(o1, k1, v2, flags); + putNode.executeWithFlags(o1, k1, v2, flags); if (isNewLayout()) { assertFalse(o1.getShape().getProperty(k1).getLocation().isConstant()); } @@ -955,7 +955,7 @@ public void testPropertyAndShapeFlags() { fillObjectWithProperties(o2, true); DynamicObject o3 = createEmpty(); fillObjectWithProperties(o3, false); - DynamicObject.PutNode.getUncached().put(o1, "k13", false); + DynamicObject.PutNode.getUncached().execute(o1, "k13", false); updateAllFlags(o2, 3); updateAllFlags(o3, 3); var getNode = createGetNode(); @@ -963,7 +963,7 @@ public void testPropertyAndShapeFlags() { } private void fillObjectWithProperties(DynamicObject obj, boolean b) { - DynamicObject.PutNode library = createPutNode(); + DynamicObject.PutNode putNode = createPutNode(); for (int i = 0; i < 20; i++) { Object value; @@ -981,7 +981,7 @@ private void fillObjectWithProperties(DynamicObject obj, boolean b) { } } int flags = (i == 17 || i == 13) ? 1 : 3; - library.putWithFlags(obj, "k" + i, value, flags); + putNode.executeWithFlags(obj, "k" + i, value, flags); } } @@ -1003,15 +1003,15 @@ private void updateAllFlags(DynamicObject obj, int flags) { } private static void uncachedPut(DynamicObject obj, Object key, Object value) { - DynamicObject.PutNode.getUncached().put(obj, key, value); + DynamicObject.PutNode.getUncached().execute(obj, key, value); } private static void uncachedPut(DynamicObject obj, Object key, Object value, int flags) { - DynamicObject.PutNode.getUncached().putWithFlags(obj, key, value, flags); + DynamicObject.PutNode.getUncached().executeWithFlags(obj, key, value, flags); } private static void uncachedSet(DynamicObject obj, Object key, Object value) { - DynamicObject.PutNode.getUncached().putIfPresent(obj, key, value); + DynamicObject.PutNode.getUncached().executeIfPresent(obj, key, value); } private static Object uncachedGet(DynamicObject obj, Object key) { 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 index db6520ea549e..7a1883a19a82 100644 --- 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 @@ -397,22 +397,22 @@ public double getDoubleOrDefault(DynamicObject object, Object key, Object defaul @Override public void put(DynamicObject object, Object key, Object value) { - putNode.put(object, key, value); + putNode.execute(object, key, value); } @Override public boolean putIfPresent(DynamicObject object, Object key, Object value) { - return putNode.putIfPresent(object, key, value); + return putNode.executeIfPresent(object, key, value); } @Override public void putWithFlags(DynamicObject object, Object key, Object value, int flags) { - putNode.putWithFlags(object, key, value, flags); + putNode.executeWithFlags(object, key, value, flags); } @Override public void putConstant(DynamicObject object, Object key, Object value, int flags) { - putConstantNode.putConstantWithFlags(object, key, value, flags); + putConstantNode.executeWithFlags(object, key, value, flags); } @Override 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 a666e3b9e9cf..cb6bea3c2887 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 @@ -53,6 +53,7 @@ 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; @@ -561,12 +562,12 @@ public static GetNode getUncached() { * Additional variants allow setting property flags, only setting the property if it's either * absent or present, and setting constant properties stored in the shape. * - * @see #put(DynamicObject, Object, Object) - * @see #putIfAbsent(DynamicObject, Object, Object) - * @see #putIfPresent(DynamicObject, Object, Object) - * @see #putWithFlags(DynamicObject, Object, Object, int) - * @see #putWithFlagsIfAbsent(DynamicObject, Object, Object, int) - * @see #putWithFlagsIfPresent(DynamicObject, Object, Object, int) + * @see #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 */ @@ -585,7 +586,7 @@ public abstract static class PutNode extends Node { * Sets the value of an existing property or adds a new property if no such property exists. * * A newly added property will have flags 0; flags of existing properties will not be changed. - * Use {@link #putWithFlags} to set property flags as well. + * Use {@link #executeWithFlags} to set property flags as well. * *

    Usage example:

    * @@ -593,17 +594,18 @@ public abstract static class PutNode extends Node { * @ExportMessage * Object writeMember(String member, Object value, * @Cached DynamicObject.PutNode putNode) { - * putNode.put(this, member, value); + * putNode.execute(this, member, value); * } * } * * @param key the property key * @param value the value to be set - * @see #putIfPresent(DynamicObject, Object, Object) - * @see #putWithFlags(DynamicObject, Object, Object, int) + * @see #executeIfPresent (DynamicObject, Object, Object) + * @see #executeWithFlags (DynamicObject, Object, Object, int) */ // @formatter:on - public final void put(DynamicObject receiver, Object key, Object value) { + @HostCompilerDirectives.InliningRoot + public final void execute(DynamicObject receiver, Object key, Object value) { executeImpl(receiver, key, value, Flags.DEFAULT, 0); } @@ -614,9 +616,10 @@ public final void put(DynamicObject receiver, Object key, Object 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 #execute(DynamicObject, Object, Object) */ - public final boolean putIfPresent(DynamicObject receiver, Object key, Object value) { + @HostCompilerDirectives.InliningRoot + public final boolean executeIfPresent(DynamicObject receiver, Object key, Object value) { return executeImpl(receiver, key, value, Flags.IF_PRESENT, 0); } @@ -627,37 +630,43 @@ public final boolean putIfPresent(DynamicObject receiver, Object key, Object val * @param value value to be set * @return {@code true} if the property was absent and the value set, otherwise * {@code false} - * @see #put(DynamicObject, Object, Object) + * @see #execute(DynamicObject, Object, Object) */ - public final boolean putIfAbsent(DynamicObject receiver, Object key, Object value) { + @HostCompilerDirectives.InliningRoot + public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object value) { return executeImpl(receiver, key, value, Flags.IF_ABSENT, 0); } /** - * Like {@link #put(DynamicObject, Object, Object)}, but additionally assigns flags to the - * property. If the property already exists, its flags will be updated before the value is - * set. + * 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 #put(DynamicObject, Object, Object) + * @see #execute(DynamicObject, Object, Object) */ - public final void putWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + @HostCompilerDirectives.InliningRoot + public final void executeWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { executeImpl(receiver, key, value, Flags.DEFAULT | Flags.UPDATE_FLAGS, propertyFlags); } /** - * Like {@link #putIfPresent(DynamicObject, Object, Object)} but also sets property flags. + * Like {@link #executeIfPresent(DynamicObject, Object, Object)} but also sets property + * flags. */ - public final boolean putWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.UPDATE_FLAGS, propertyFlags); } /** - * Like {@link #putIfAbsent(DynamicObject, Object, Object)} but also sets property flags. + * Like {@link #executeIfAbsent(DynamicObject, Object, Object)} but also sets property + * flags. */ - public final boolean putWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.UPDATE_FLAGS, propertyFlags); } @@ -749,12 +758,12 @@ public static PutNode getUncached() { * 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 #putConstant(DynamicObject, Object, Object) - * @see #putConstantIfAbsent(DynamicObject, Object, Object) - * @see #putConstantIfPresent(DynamicObject, Object, Object) - * @see #putConstantWithFlags(DynamicObject, Object, Object, int) - * @see #putConstantWithFlagsIfAbsent(DynamicObject, Object, Object, int) - * @see #putConstantWithFlagsIfPresent(DynamicObject, Object, Object, int) + * @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 */ @@ -769,26 +778,27 @@ public abstract static class PutConstantNode extends Node { } /** - * Same as {@link #putConstantWithFlags}, except the property is added with 0 flags, and if - * the property already exists, its flags will not be updated. + * Same as {@link #executeWithFlags}, except the property is added with 0 flags, and if the + * property already exists, its flags will not be updated. */ - public final void putConstant(DynamicObject receiver, Object key, Object value) { + @HostCompilerDirectives.InliningRoot + public final void execute(DynamicObject receiver, Object key, Object value) { executeImpl(receiver, key, value, Flags.DEFAULT | Flags.CONST, 0); } /** - * Like {@link #putConstant(DynamicObject, Object, Object)} but only if the property is - * present. + * Like {@link #execute(DynamicObject, Object, Object)} but only if the property is present. */ - public final boolean putConstantIfPresent(DynamicObject receiver, Object key, Object value) { + @HostCompilerDirectives.InliningRoot + public final boolean executeIfPresent(DynamicObject receiver, Object key, Object value) { return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.CONST, 0); } /** - * Like {@link #putConstant(DynamicObject, Object, Object)} but only if the property is - * absent. + * Like {@link #execute(DynamicObject, Object, Object)} but only if the property is absent. */ - public final boolean putConstantIfAbsent(DynamicObject receiver, Object key, Object value) { + @HostCompilerDirectives.InliningRoot + public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object value) { return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.CONST, 0); } @@ -827,26 +837,29 @@ public final boolean putConstantIfAbsent(DynamicObject receiver, Object key, Obj * @param key property identifier * @param value the constant value to be set * @param propertyFlags property flags or 0 - * @see #putConstant(DynamicObject, Object, Object) + * @see #execute (DynamicObject, Object, Object) */ // @formatter:on - public void putConstantWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { + @HostCompilerDirectives.InliningRoot + public final void executeWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { executeImpl(receiver, key, value, Flags.DEFAULT | Flags.CONST | Flags.UPDATE_FLAGS, propertyFlags); } /** - * Like {@link #putConstantWithFlags(DynamicObject, Object, Object, int)} but only if the + * Like {@link #executeWithFlags(DynamicObject, Object, Object, int)} but only if the * property is present. */ - public final boolean putConstantWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.CONST | Flags.UPDATE_FLAGS, propertyFlags); } /** - * Like {@link #putConstantWithFlags(DynamicObject, Object, Object, int)} but only if the + * Like {@link #executeWithFlags(DynamicObject, Object, Object, int)} but only if the * property is absent. */ - public final boolean putConstantWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { + @HostCompilerDirectives.InliningRoot + public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.CONST | Flags.UPDATE_FLAGS, propertyFlags); } @@ -1011,7 +1024,7 @@ static void doCached(DynamicObject from, DynamicObject to, @Cached DynamicObject.PutNode putNode) { for (int i = 0; i < getters.length; i++) { PropertyGetter getter = getters[i]; - putNode.putWithFlags(to, getter.getKey(), getter.get(from), getter.getFlags()); + putNode.executeWithFlags(to, getter.getKey(), getter.get(from), getter.getFlags()); } } @@ -1021,7 +1034,7 @@ static void doGeneric(DynamicObject from, DynamicObject to) { Property[] properties = from.getShape().getPropertyArray(); for (int i = 0; i < properties.length; i++) { Property property = properties[i]; - PutNode.getUncached().putWithFlags(to, property.getKey(), property.get(from, false), property.getFlags()); + PutNode.getUncached().executeWithFlags(to, property.getKey(), property.get(from, false), property.getFlags()); } } @@ -1282,7 +1295,7 @@ public abstract static class GetShapeFlagsNode extends Node { * if ((getShapeFlagsNode.execute(receiver) & FROZEN) != 0) { * throw UnsupportedMessageException.create(); * } - * putNode.put(this, member, value); + * putNode.execute(this, member, value); * } * } * @@ -1363,7 +1376,7 @@ public abstract static class HasShapeFlagsNode extends Node { * if (hasShapeFlagsNode.execute(receiver, FROZEN)) { * throw UnsupportedMessageException.create(); * } - * putNode.put(this, member, value); + * putNode.execute(this, member, value); * } * } * 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 dd12cfa449bf..c0f69c124792 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 @@ -247,7 +247,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * @see #putLong(DynamicObject, Object, long) * @see #putIfPresent(DynamicObject, Object, Object) * @see #putWithFlags(DynamicObject, Object, Object, int) - * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract void put(DynamicObject object, Object key, Object value); @@ -256,7 +256,7 @@ public long getLongOrDefault(DynamicObject object, Object key, Object defaultVal * Int-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) - * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public void putInt(DynamicObject object, Object key, int value) { @@ -267,7 +267,7 @@ public void putInt(DynamicObject object, Object key, int value) { * Double-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) - * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public void putDouble(DynamicObject object, Object key, double value) { @@ -278,7 +278,7 @@ public void putDouble(DynamicObject object, Object key, double value) { * Long-typed variant of {@link #put}. * * @see #put(DynamicObject, Object, Object) - * @see DynamicObject.PutNode#put(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#execute(DynamicObject, Object, Object) * @since 20.2.0 */ public void putLong(DynamicObject object, Object key, long value) { @@ -292,7 +292,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param value value to be set * @return {@code true} if the property was present and the value set, otherwise {@code false} * @see #put(DynamicObject, Object, Object) - * @see DynamicObject.PutNode#putIfPresent(DynamicObject, Object, Object) + * @see DynamicObject.PutNode#executeIfPresent(DynamicObject, Object, Object) * @since 20.2.0 */ public abstract boolean putIfPresent(DynamicObject object, Object key, Object value); @@ -306,7 +306,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param flags flags to be set * @see #put(DynamicObject, Object, Object) * @see #setPropertyFlags(DynamicObject, Object, int) - * @see DynamicObject.PutNode#putWithFlags(DynamicObject, Object, Object, int) + * @see DynamicObject.PutNode#executeWithFlags(DynamicObject, Object, Object, int) * @since 20.2.0 */ public abstract void putWithFlags(DynamicObject object, Object key, Object value, int flags); @@ -346,7 +346,7 @@ public void putLong(DynamicObject object, Object key, long value) { * @param value the constant value to be set * @param flags property flags or 0 * @see #put(DynamicObject, Object, Object) - * @see DynamicObject.PutConstantNode#putConstant(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); 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 c907b929308b..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 @@ -98,7 +98,7 @@ public static Object writeSLObject(SLObject receiver, Object name, Object value, @Bind Node node, @Cached DynamicObject.PutNode putNode, @Cached SLToTruffleStringNode toTruffleStringNode) { - putNode.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 880cf2fd01b4..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 @@ -226,6 +226,6 @@ Object readMember(String name, void writeMember(String name, Object value, @Cached @Shared("fromJavaStringNode") TruffleString.FromJavaStringNode fromJavaStringNode, @Cached DynamicObject.PutNode putNode) { - putNode.put(this, fromJavaStringNode.execute(name, SLLanguage.STRING_ENCODING), value); + 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 e4e2bb4df8e1..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 @@ -111,7 +111,7 @@ public void setup(SharedEngineState shared) { object = new MyDynamicObject(shared.rootShape); for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { String key = PROPERTY_KEYS[i]; - DynamicObject.PutNode.getUncached().put(object, key, "testValue"); + DynamicObject.PutNode.getUncached().execute(object, key, "testValue"); } } } @@ -124,7 +124,7 @@ public void setup(SharedEngineState shared) { public void shapeTransitionMapContended(SharedEngineState shared, PerThreadContextState perThread) { for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { DynamicObject object = new MyDynamicObject(shared.rootShape); - DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); + DynamicObject.PutNode.getUncached().execute(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } } @@ -137,7 +137,7 @@ public void shapeTransitionMapContended(SharedEngineState shared, PerThreadConte public void shapeTransitionMapUncontended(SharedEngineState shared, PerThreadContextState perThread) { for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) { DynamicObject object = new MyDynamicObject(shared.rootShape); - DynamicObject.PutNode.getUncached().put(object, PROPERTY_KEYS[i], "testValue"); + DynamicObject.PutNode.getUncached().execute(object, PROPERTY_KEYS[i], "testValue"); shared.assertSameShape(i, object.getShape()); } } @@ -161,13 +161,13 @@ public void setup() { for (int i = 0; i < 10; i++) { String key = PROPERTY_KEYS[i]; if (key.equals(SOME_KEY_INT)) { - DynamicObject.PutNode.getUncached().put(object, key, i); + DynamicObject.PutNode.getUncached().execute(object, key, i); } else if (key.equals(SOME_KEY_LONG)) { - DynamicObject.PutNode.getUncached().put(object, key, (long) i); + DynamicObject.PutNode.getUncached().execute(object, key, (long) i); } else if (key.equals(SOME_KEY_DOUBLE)) { - DynamicObject.PutNode.getUncached().put(object, key, (double) i); + DynamicObject.PutNode.getUncached().execute(object, key, (double) i); } else { - DynamicObject.PutNode.getUncached().put(object, key, "testValue"); + DynamicObject.PutNode.getUncached().execute(object, key, "testValue"); } Objects.requireNonNull(DynamicObject.GetNode.getUncached().execute(object, key, null), key); } @@ -211,7 +211,7 @@ public double getDouble(AccessNodeBenchState state) throws UnexpectedResultExcep @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void put(AccessNodeBenchState state) { DynamicObject object = state.object; - state.putNode.put(object, SOME_KEY, "updated value"); + state.putNode.execute(object, SOME_KEY, "updated value"); } @Benchmark @@ -219,7 +219,7 @@ public void put(AccessNodeBenchState state) { @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void putInt(AccessNodeBenchState state) { DynamicObject object = state.object; - state.putNode.put(object, SOME_KEY_INT, 42); + state.putNode.execute(object, SOME_KEY_INT, 42); } @Benchmark @@ -227,7 +227,7 @@ public void putInt(AccessNodeBenchState state) { @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void putLong(AccessNodeBenchState state) { DynamicObject object = state.object; - state.putNode.put(object, SOME_KEY_LONG, (long) 42); + state.putNode.execute(object, SOME_KEY_LONG, (long) 42); } @Benchmark @@ -235,6 +235,6 @@ public void putLong(AccessNodeBenchState state) { @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void putDouble(AccessNodeBenchState state) { DynamicObject object = state.object; - state.putNode.put(object, SOME_KEY_DOUBLE, (double) 42); + state.putNode.execute(object, SOME_KEY_DOUBLE, (double) 42); } } From 33c7f56e0a32a2b758df3d3635a5735e88a5ba33 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 16 Oct 2025 21:07:22 +0200 Subject: [PATCH 07/33] Remove unsafeCast without exact parameter. --- .../src/com/oracle/truffle/api/object/ExtLocations.java | 4 ++-- .../src/com/oracle/truffle/api/object/Shape.java | 4 ++-- .../src/com/oracle/truffle/api/object/UnsafeAccess.java | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) 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..75fab30380c2 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 @@ -51,8 +51,8 @@ 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; /** @@ -446,7 +446,7 @@ protected final Object assumedTypeCast(Object value, boolean condition) { 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; } 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 5aca1ab72ea0..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 @@ -1056,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); } /** @@ -1636,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..1801b8aa84a6 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 @@ -90,10 +90,6 @@ 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); - } - /** * Like {@link System#arraycopy}, but kills any location. */ From d4b3d69cb90eb208f17705eb46f04860228b9925 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 8 Oct 2025 17:26:13 +0200 Subject: [PATCH 08/33] Initialize extension arrays with empty array constant instead of null. --- .../truffle/api/object/DynamicObject.java | 7 +- .../api/object/DynamicObjectSupport.java | 96 ++++++++++--------- 2 files changed, 55 insertions(+), 48 deletions(-) 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 cb6bea3c2887..8b29d11600c6 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 @@ -173,6 +173,9 @@ @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(); /** @@ -192,9 +195,9 @@ public abstract class DynamicObject implements TruffleObject { private Shape shape; /** Object extension array. */ - @DynamicField private Object[] extRef; + @DynamicField private Object[] extRef = EMPTY_OBJECT_ARRAY; /** Primitive extension array. */ - @DynamicField private int[] extVal; + @DynamicField private int[] extVal = EMPTY_INT_ARRAY; /** * Constructor for {@link DynamicObject} subclasses. Initializes the object with the provided 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 e7f99d9dbc55..bbcb4d76b532 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 @@ -77,6 +77,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 +88,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,81 +110,83 @@ 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") From bb3e0ed382746516896d9399f4dcd1cb838d3b27 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 15 Oct 2025 11:10:06 +0200 Subject: [PATCH 09/33] Refactor Location classes. * Move index, field info, and final assumption to Location base class. * Merge array and field location classes. * Add non-virtual Location.canStoreValue(). * Add non-virtual Location.get and set methods. * Make isAssumedFinal() and isConstant() non-virtual. * Add exact-type fast path for type.isInstance(value). * Remove internal location interfaces. * Implement clear() for primitive locations. --- .../api/object/test/DOTestAsserts.java | 15 +- .../truffle/api/object/DebugCounters.java | 6 + .../truffle/api/object/DynamicObject.java | 12 +- .../truffle/api/object/ExtAllocator.java | 58 +- .../truffle/api/object/ExtLocations.java | 1013 +++++------------ .../oracle/truffle/api/object/Location.java | 506 +++++++- .../truffle/api/object/Obsolescence.java | 27 +- .../api/object/ObsolescenceStrategy.java | 48 +- 8 files changed, 868 insertions(+), 817 deletions(-) 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 d9877972a193..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,13 +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.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 { @@ -72,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); @@ -82,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); 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 8b29d11600c6..1e7d258c7bdf 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 @@ -456,7 +456,7 @@ static int doCachedInt(DynamicObject receiver, Object key, Object defaultValue, @Cached("key") Object cachedKey, @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { if (cachedLocation != null) { - if (cachedLocation instanceof ExtLocations.IntArrayLocation intArrayLocation) { + if (cachedLocation instanceof ExtLocations.IntLocation intArrayLocation) { return intArrayLocation.getInt(receiver, guard); } else { return cachedLocation.getInt(receiver, guard); @@ -490,9 +490,9 @@ static Object doCached(DynamicObject receiver, Object key, Object defaultValue, @Cached("key") Object cachedKey, @Cached("cachedShape.getLocation(key)") Location cachedLocation) { if (cachedLocation != null) { - if (cachedLocation instanceof ExtLocations.ObjectArrayLocation objectArrayLocation) { + if (cachedLocation instanceof ExtLocations.ObjectLocation objectArrayLocation) { return objectArrayLocation.get(receiver, guard); - } else if (cachedLocation instanceof ExtLocations.IntArrayLocation intArrayLocation) { + } else if (cachedLocation instanceof ExtLocations.IntLocation intArrayLocation) { return intArrayLocation.get(receiver, guard); } else { return cachedLocation.get(receiver, guard); @@ -709,7 +709,7 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo DynamicObjectSupport.grow(receiver, oldShape, newShape); } - if (newLocation instanceof ExtLocations.ObjectArrayLocation objectArrayLocation) { + if (newLocation instanceof ExtLocations.ObjectLocation objectArrayLocation) { objectArrayLocation.set(receiver, value, guard, addingNewProperty); } else { newLocation.set(receiver, value, guard, addingNewProperty); @@ -902,7 +902,7 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo DynamicObjectSupport.grow(receiver, oldShape, newShape); } - if (newLocation instanceof ExtLocations.ObjectArrayLocation objectArrayLocation) { + if (newLocation instanceof ExtLocations.ObjectLocation objectArrayLocation) { objectArrayLocation.set(receiver, value, guard, addingNewProperty); } else { newLocation.set(receiver, value, guard, addingNewProperty); @@ -950,7 +950,7 @@ public static PutConstantNode getUncached() { } static boolean canStore(Location newLocation, Object value) { - return newLocation instanceof ExtLocations.AbstractObjectLocation || newLocation.canStore(value); + return newLocation instanceof ExtLocations.ObjectLocation || newLocation.canStore(value); } static Shape getNewShape(Object cachedKey, Object value, int newPropertyFlags, int mode, Property existingProperty, Shape oldShape) { 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 75fab30380c2..def9e029f0ac 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,11 +50,8 @@ 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; } @@ -211,11 +126,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 +142,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 +149,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,13 +285,13 @@ 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()) { @@ -452,7 +303,7 @@ protected final Object assumedTypeCast(Object value, boolean condition) { } } - protected final Class getAssumedType() { + Class getAssumedType() { TypeAssumption curr = getTypeAssumption(); if (curr != null && curr.getAssumption().isValid()) { return curr.type; @@ -461,7 +312,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 +325,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 +335,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 +343,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 +380,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 +395,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 +424,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 +440,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 +461,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 setIntArrayInternal(DynamicObject store, int value, boolean guard) { + UnsafeAccess.unsafePutInt(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), value, this); } - private void setObjectInternal(DynamicObject store, Object value) { + 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; + DoubleLocation(int index, FieldInfo field, boolean allowInt, AbstractAssumption finalAssumption) { + super(index, field, finalAssumption); + this.allowInt = allowInt; } - @Override - public boolean equals(Object obj) { - if (!super.equals(obj)) { - return false; - } - AbstractPrimitiveFieldLocation other = (AbstractPrimitiveFieldLocation) obj; - return this.field.equals(other.field); + static DoubleLocation createDoubleArrayLocation(int index, boolean allowInt, AbstractAssumption finalAssumption) { + return new DoubleLocation(index, allowInt, finalAssumption); } - final long getOffset() { - return field.offset(); - } - - @Override - public int objectFieldCount() { - return 0; - } - } - - 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(); - } - } - - @Override - public void setInt(DynamicObject store, int value, boolean guard, boolean init) { - if (!init) { - maybeInvalidateFinalAssumption(); - } - setIntInternal(store, value); - } - - private void setIntInternal(DynamicObject store, int value) { - if (UseVarHandle) { - field.varHandle().set(store, value & 0xffff_ffffL); - return; + return getDoubleField(store, guard); } - 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); + private double getDoubleArray(DynamicObject store, boolean guard) { + return UnsafeAccess.unsafeGetDouble(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), guard, this); } - @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 +659,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 setDoubleArrayInternal(DynamicObject store, double value, boolean guard) { + UnsafeAccess.unsafePutDouble(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), value, this); } - private void setDoubleInternal(DynamicObject store, double value) { + 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 +693,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 +709,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); - } - - @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); + private long getLongArray(DynamicObject store, boolean guard) { + return UnsafeAccess.unsafeGetLong(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), guard, this); } - @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 +810,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 setLongArrayInternal(DynamicObject store, long value, boolean guard) { + UnsafeAccess.unsafePutLong(getPrimitiveArray(store, guard), getPrimitiveArrayOffset(), value, this); } - private void setLongInternal(DynamicObject store, long value) { + 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 +844,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 +860,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/Location.java b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/Location.java index 995be564f6d0..42cba27461ee 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 static com.oracle.truffle.api.object.ObjectStorageOptions.UseVarHandle; + import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 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 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 */ - protected Location() { + 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. + */ + 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,204 @@ 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 + */ + final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard) { + if (this instanceof ObjectLocation objectLocation) { + Object value; + if (field == null) { + Object array = getObjectArray(store, guard); + long offset = getObjectArrayOffset(); + value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); + } else { + if (UseVarHandle) { + value = field.varHandle().get(store); + } else { + field.receiverCheck(store); + value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); + } + } + return CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard); + } else { + if (field == null) { + Object array = getPrimitiveArray(store, guard); + long offset = getPrimitiveArrayOffset(); + 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(store, guard); + } + } else { + long longValue; + if (UseVarHandle) { + longValue = (long) field.varHandle().get(store); + } else { + field.receiverCheck(store); + longValue = UnsafeAccess.unsafeGetLong(store, 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 { + if (isIntLocation()) { + if (field == null) { + Object array = getPrimitiveArray(store, guard); + long offset = getPrimitiveArrayOffset(); + return UnsafeAccess.unsafeGetInt(array, offset, guard, this); + } else { + long longValue; + if (UseVarHandle) { + longValue = (long) field.varHandle().get(store); + } else { + field.receiverCheck(store); + longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + } + return (int) longValue; + } + } else if (this instanceof ObjectLocation objectLocation) { + Object value; + if (field == null) { + Object array = getObjectArray(store, guard); + long offset = getObjectArrayOffset(); + value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); + } else { + if (UseVarHandle) { + value = field.varHandle().get(store); + } else { + field.receiverCheck(store); + value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); + } + } + return expectInteger(CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard)); + } + return getIntUnexpected(store, 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 { + if (isLongLocation()) { + if (field == null) { + Object array = getPrimitiveArray(store, guard); + long offset = getPrimitiveArrayOffset(); + return UnsafeAccess.unsafeGetLong(array, offset, guard, this); + } else { + long longValue; + if (UseVarHandle) { + longValue = (long) field.varHandle().get(store); + } else { + field.receiverCheck(store); + longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + } + return longValue; + } + } else if (this instanceof ObjectLocation objectLocation) { + Object value; + if (field == null) { + Object array = getObjectArray(store, guard); + long offset = getObjectArrayOffset(); + value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); + } else { + if (UseVarHandle) { + value = field.varHandle().get(store); + } else { + field.receiverCheck(store); + value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); + } + } + return expectLong(CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard)); + } + return getLongUnexpected(store, 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 { + if (isDoubleLocation()) { + if (field == null) { + Object array = getPrimitiveArray(store, guard); + long offset = getPrimitiveArrayOffset(); + return UnsafeAccess.unsafeGetDouble(array, offset, guard, this); + } else { + long longValue; + if (UseVarHandle) { + longValue = (long) field.varHandle().get(store); + } else { + field.receiverCheck(store); + longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + } + return Double.longBitsToDouble(longValue); + } + } else if (this instanceof ObjectLocation objectLocation) { + Object value; + if (field == null) { + Object array = getObjectArray(store, guard); + long offset = getObjectArrayOffset(); + value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); + } else { + if (UseVarHandle) { + value = field.varHandle().get(store); + } else { + field.receiverCheck(store); + value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); + } + } + return expectDouble(CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard)); + } + return getDoubleUnexpected(store, 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. * @@ -258,6 +499,122 @@ 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 receiver 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). + */ + final void setInternal(DynamicObject receiver, Object value, boolean guard, Shape oldShape, Shape newShape) { + assert canStoreValue(value) : value; + 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); + } + if (field == null) { + Object array = getObjectArray(receiver, guard); + long offset = getObjectArrayOffset(); + UnsafeAccess.unsafePutObject(array, offset, value, this); + } else { + if (UseVarHandle) { + field.varHandle().set(receiver, value); + } else { + field.receiverCheck(receiver); + long offset = getFieldOffset(); + UnsafeAccess.unsafePutObject(receiver, offset, value, this); + } + } + } else { // primitive location + long longValue; + if (isIntLocation()) { + int intValue = (int) value; + if (field == null) { + Object array = getPrimitiveArray(receiver, guard); + long offset = getPrimitiveArrayOffset(); + 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 = getPrimitiveArrayOffset(); + 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 = getPrimitiveArrayOffset(); + UnsafeAccess.unsafePutDouble(array, offset, doubleValue, this); + return; + } else { + longValue = Double.doubleToRawLongBits(doubleValue); + } + } else { + assert isConstantLocation() : this; + return; + } + if (UseVarHandle) { + field.varHandle().set(receiver, longValue); + } else { + field.receiverCheck(receiver); + long offset = getFieldOffset(); + UnsafeAccess.unsafePutLong(receiver, offset, longValue, this); + } + } + } + + 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)}. */ @@ -305,7 +662,7 @@ public boolean isFinal() { * @since 0.8 or earlier */ public boolean isConstant() { - return false; + return isConstantLocation(); } /** @@ -315,7 +672,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; } /** @@ -331,7 +692,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; } /** @@ -346,13 +709,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. @@ -372,16 +739,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() { @@ -420,7 +803,39 @@ Class getType() { void clear(@SuppressWarnings("unused") DynamicObject store) { } - abstract int getOrdinal(); + 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(); + } + + final long getObjectArrayOffset() { + return Integer.toUnsignedLong(index) * Unsafe.ARRAY_OBJECT_INDEX_SCALE + Unsafe.ARRAY_OBJECT_BASE_OFFSET; + } + + final long getPrimitiveArrayOffset() { + return Integer.toUnsignedLong(index) * Unsafe.ARRAY_INT_INDEX_SCALE + Unsafe.ARRAY_INT_BASE_OFFSET; + } + + static Object getObjectArray(DynamicObject store, boolean condition) { + return UnsafeAccess.unsafeCast(store.getObjectStore(), Object[].class, condition, true, true); + } + + static Object getPrimitiveArray(DynamicObject store, boolean condition) { + return UnsafeAccess.unsafeCast(store.getPrimitiveStore(), int[].class, condition, true, true); + } static RuntimeException incompatibleLocationException() { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -455,7 +870,8 @@ public boolean isValue() { * @since 0.18 */ public boolean isAssumedFinal() { - return false; + var assumption = getFinalAssumptionField(); + return assumption == null || assumption.isValid(); } /** @@ -465,7 +881,7 @@ public boolean isAssumedFinal() { * @since 0.18 */ public Assumption getFinalAssumption() { - return Assumption.NEVER_VALID; + return getFinalAssumptionInternal(); } /** @@ -476,7 +892,7 @@ public Assumption getFinalAssumption() { */ @SuppressWarnings("deprecation") public boolean isPrimitive() { - return this instanceof DoubleLocation || this instanceof IntLocation || this instanceof LongLocation; + return this instanceof AbstractPrimitiveLocation; } /** @@ -519,4 +935,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/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 b92e401c5ecb..56fd948ce041 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,18 @@ 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 +133,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,25 +148,13 @@ 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; } @@ -704,7 +692,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; } @@ -729,12 +716,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; } @@ -792,7 +778,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); From f3399cf6bdc30182256d622f761c1a201fea999b Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 28 Oct 2025 18:41:30 +0100 Subject: [PATCH 10/33] Use final Location get and set. --- .../truffle/api/object/DynamicObject.java | 84 ++++++------------- .../truffle/api/object/PropertyGetter.java | 10 +-- 2 files changed, 32 insertions(+), 62 deletions(-) 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 1e7d258c7bdf..f94ca9b8f924 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 @@ -441,7 +441,7 @@ static long doCachedLong(DynamicObject receiver, Object key, Object defaultValue @Cached("key") Object cachedKey, @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { if (cachedLocation != null) { - return cachedLocation.getLong(receiver, guard); + return cachedLocation.getLongInternal(receiver, cachedShape, guard); } else { return Location.expectLong(defaultValue); } @@ -456,11 +456,7 @@ static int doCachedInt(DynamicObject receiver, Object key, Object defaultValue, @Cached("key") Object cachedKey, @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { if (cachedLocation != null) { - if (cachedLocation instanceof ExtLocations.IntLocation intArrayLocation) { - return intArrayLocation.getInt(receiver, guard); - } else { - return cachedLocation.getInt(receiver, guard); - } + return cachedLocation.getIntInternal(receiver, cachedShape, guard); } else { return Location.expectInteger(defaultValue); } @@ -475,7 +471,7 @@ static double doCachedDouble(DynamicObject receiver, Object key, Object defaultV @Cached("key") Object cachedKey, @Cached("cachedShape.getLocation(key)") Location cachedLocation) throws UnexpectedResultException { if (cachedLocation != null) { - return cachedLocation.getDouble(receiver, guard); + return cachedLocation.getDoubleInternal(receiver, cachedShape, guard); } else { return Location.expectDouble(defaultValue); } @@ -490,13 +486,7 @@ static Object doCached(DynamicObject receiver, Object key, Object defaultValue, @Cached("key") Object cachedKey, @Cached("cachedShape.getLocation(key)") Location cachedLocation) { if (cachedLocation != null) { - if (cachedLocation instanceof ExtLocations.ObjectLocation objectArrayLocation) { - return objectArrayLocation.get(receiver, guard); - } else if (cachedLocation instanceof ExtLocations.IntLocation intArrayLocation) { - return intArrayLocation.get(receiver, guard); - } else { - return cachedLocation.get(receiver, guard); - } + return cachedLocation.getInternal(receiver, cachedShape, guard); } else { return defaultValue; } @@ -504,9 +494,10 @@ static Object doCached(DynamicObject receiver, Object key, Object defaultValue, @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) static long doGenericLong(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { - Location location = receiver.getShape().getLocation(key); + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); if (location != null) { - return location.getLong(receiver, false); + return location.getLongInternal(receiver, shape, false); } else { return Location.expectLong(defaultValue); } @@ -514,9 +505,10 @@ static long doGenericLong(DynamicObject receiver, Object key, Object defaultValu @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) static int doGenericInt(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { - Location location = receiver.getShape().getLocation(key); + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); if (location != null) { - return location.getInt(receiver, false); + return location.getIntInternal(receiver, shape, false); } else { return Location.expectInteger(defaultValue); } @@ -524,9 +516,10 @@ static int doGenericInt(DynamicObject receiver, Object key, Object defaultValue) @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached"}, rewriteOn = UnexpectedResultException.class) static double doGenericDouble(DynamicObject receiver, Object key, Object defaultValue) throws UnexpectedResultException { - Location location = receiver.getShape().getLocation(key); + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); if (location != null) { - return location.getDouble(receiver, false); + return location.getDoubleInternal(receiver, shape, false); } else { return Location.expectDouble(defaultValue); } @@ -535,9 +528,10 @@ static double doGenericDouble(DynamicObject receiver, Object key, Object default @TruffleBoundary @Specialization(replaces = {"doCachedLong", "doCachedInt", "doCachedDouble", "doCached", "doGenericLong", "doGenericInt", "doGenericDouble"}) static Object doGeneric(DynamicObject receiver, Object key, Object defaultValue) { - Location location = receiver.getShape().getLocation(key); + Shape shape = receiver.getShape(); + Location location = shape.getLocation(key); if (location != null) { - return location.get(receiver, false); + return location.getInternal(receiver, shape, false); } else { return defaultValue; } @@ -683,7 +677,7 @@ public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key "key == cachedKey", "mode == cachedMode", "propertyFlags == cachedPropertyFlags", - "newLocation == null || canStore(newLocation, value)", + "newLocation == null || newLocation.canStoreValue(value)", }, assumptions = "newShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") static boolean doCached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags, @Bind("receiver.getShape()") Shape shape, @@ -703,23 +697,14 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo } if ((mode & Flags.IF_PRESENT) != 0 && oldProperty == null) { return false; - } else { - boolean addingNewProperty = newShape != oldShape; - if (addingNewProperty) { - DynamicObjectSupport.grow(receiver, oldShape, newShape); - } + } - if (newLocation instanceof ExtLocations.ObjectLocation objectArrayLocation) { - objectArrayLocation.set(receiver, value, guard, addingNewProperty); - } else { - newLocation.set(receiver, value, guard, addingNewProperty); - } + newLocation.setInternal(receiver, value, guard, oldShape, newShape); - if (addingNewProperty) { - DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); - } - return true; + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); } + return true; } /* @@ -876,7 +861,7 @@ public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key "key == cachedKey", "mode == cachedMode", "propertyFlags == cachedPropertyFlags", - "newLocation == null || canStore(newLocation, value)", + "newLocation == null || newLocation.canStoreConstant(value)", }, assumptions = "newShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") static boolean doCached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags, @Bind("receiver.getShape()") Shape shape, @@ -896,23 +881,12 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo } if ((mode & Flags.IF_PRESENT) != 0 && oldProperty == null) { return false; - } else { - boolean addingNewProperty = newShape != oldShape; - if (addingNewProperty) { - DynamicObjectSupport.grow(receiver, oldShape, newShape); - } - - if (newLocation instanceof ExtLocations.ObjectLocation objectArrayLocation) { - objectArrayLocation.set(receiver, value, guard, addingNewProperty); - } else { - newLocation.set(receiver, value, guard, addingNewProperty); - } + } - if (addingNewProperty) { - DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); - } - return true; + if (newShape != oldShape) { + DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); } + return true; } /* @@ -949,10 +923,6 @@ public static PutConstantNode getUncached() { } } - static boolean canStore(Location newLocation, Object value) { - return newLocation instanceof ExtLocations.ObjectLocation || newLocation.canStore(value); - } - static Shape getNewShape(Object cachedKey, Object value, int newPropertyFlags, int mode, Property existingProperty, Shape oldShape) { if (existingProperty == null) { if ((mode & Flags.IF_PRESENT) != 0) { 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(); } From 9e0776b2491bdbcace7fa313633e03f75ebf9ce5 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 16 Oct 2025 02:41:41 +0200 Subject: [PATCH 11/33] Remove unused SomSupport. --- .../oracle/truffle/api/staticobject/SomAccessor.java | 7 ++----- .../src/com/oracle/truffle/api/impl/Accessor.java | 10 ---------- 2 files changed, 2 insertions(+), 15 deletions(-) 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(); From 4de8fab5a71c6dbff83d54cb17a2c52363c787b7 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 7 Nov 2025 20:00:53 +0100 Subject: [PATCH 12/33] Unify condition anchor handling. --- .../compiler/nodes/ConditionAnchorNode.java | 57 +++++++++-- .../TruffleGraphBuilderPlugins.java | 36 ++----- .../TruffleInvocationPlugins.java | 95 +++++++++---------- 3 files changed, 103 insertions(+), 85 deletions(-) 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..4952bff63e65 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; @@ -116,58 +112,55 @@ public static void register(Architecture architecture, InvocationPlugins 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; + } } /** From 8a41b5abf7d8c0b47069ab34e1dc14909925589e Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 17 Oct 2025 13:36:12 +0200 Subject: [PATCH 13/33] Add UnsafeAccess.hostUnsafeCast and mark extension arrays as non-null. Ensure extension arrays are seen as non-null in host compilation, too. Remove DynamicField annotation from the array fields to not needlessly mark them as unsafe-accessed. --- .../TruffleInvocationPlugins.java | 7 +++++++ .../truffle/api/object/DynamicObject.java | 9 +++++---- .../oracle/truffle/api/object/Location.java | 4 ++-- .../truffle/api/object/UnsafeAccess.java | 20 +++++++++++++++++-- 4 files changed, 32 insertions(+), 8 deletions(-) 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 4952bff63e65..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 @@ -107,6 +107,7 @@ public static void register(Architecture architecture, InvocationPlugins plugins registerFramePlugins(plugins); registerBytecodePlugins(plugins); registerCompilerDirectivesPlugins(plugins); + registerDynamicObjectPlugins(plugins); } private static void registerFramePlugins(InvocationPlugins plugins) { @@ -823,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/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 f94ca9b8f924..a05fc75ff7e4 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 @@ -49,6 +49,7 @@ 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; @@ -195,9 +196,9 @@ public abstract class DynamicObject implements TruffleObject { private Shape shape; /** Object extension array. */ - @DynamicField private Object[] extRef = EMPTY_OBJECT_ARRAY; + private Object[] extRef = EMPTY_OBJECT_ARRAY; /** Primitive extension array. */ - @DynamicField private int[] extVal = EMPTY_INT_ARRAY; + private int[] extVal = EMPTY_INT_ARRAY; /** * Constructor for {@link DynamicObject} subclasses. Initializes the object with the provided @@ -324,7 +325,7 @@ final Object[] getObjectStore() { } final void setObjectStore(Object[] newArray) { - extRef = newArray; + extRef = Objects.requireNonNull(newArray); } final int[] getPrimitiveStore() { @@ -332,7 +333,7 @@ final int[] getPrimitiveStore() { } final void setPrimitiveStore(int[] newArray) { - extVal = newArray; + extVal = Objects.requireNonNull(newArray); } static Class getDynamicFieldAnnotation() { 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 42cba27461ee..30a73bbfdcc1 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 @@ -830,11 +830,11 @@ final long getPrimitiveArrayOffset() { } static Object getObjectArray(DynamicObject store, boolean condition) { - return UnsafeAccess.unsafeCast(store.getObjectStore(), Object[].class, condition, true, true); + return UnsafeAccess.hostUnsafeCast(store.getObjectStore(), Object[].class, condition, true, true); } static Object getPrimitiveArray(DynamicObject store, boolean condition) { - return UnsafeAccess.unsafeCast(store.getPrimitiveStore(), int[].class, condition, true, true); + return UnsafeAccess.hostUnsafeCast(store.getPrimitiveStore(), int[].class, condition, true, true); } static RuntimeException incompatibleLocationException() { 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 1801b8aa84a6..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,6 +90,22 @@ static T unsafeCast(Object value, Class type, boolean condition, boolean return (T) value; } + /** + * 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; + } + /** * Like {@link System#arraycopy}, but kills any location. */ From d2b501bb603a378191f8141c29993ddac8d7f064 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 6 Nov 2025 19:09:56 +0100 Subject: [PATCH 14/33] Add tests for PutNode.executeIfAbsent and executeWithFlagsIfPresent. --- .../object/test/DynamicObjectNodesTest.java | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java index f1f52e890ac0..367deec0a510 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -377,6 +377,16 @@ public void testPutIfPresent() { 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); @@ -388,19 +398,58 @@ public void testPutIfPresent() { var containsKeyNode2 = createContainsKeyNode(); assertFalse(DynamicObject.ContainsKeyNode.getUncached().execute(o1, key2)); assertFalse(setNode2.executeIfPresent(o1, key2, strval2)); - // assertTrue(setNode2.accepts(o1)); + 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(); @@ -1022,6 +1071,10 @@ 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() { }; From 754b643dd6f2bbae84a3f6a498bd54dc22b5e737 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 6 Nov 2025 17:27:29 +0100 Subject: [PATCH 15/33] Use one PutNode per property in CopyPropertiesNode. --- .../truffle/api/object/DynamicObject.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) 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 a05fc75ff7e4..8cc964bb45c9 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 @@ -990,28 +990,35 @@ public abstract static class CopyPropertiesNode extends Node { public abstract void execute(DynamicObject from, DynamicObject to); @ExplodeLoop - @Specialization(guards = "shape == cachedShape", limit = "SHAPE_CACHE_LIMIT") + @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 DynamicObject.PutNode putNode) { + @Cached(value = "createPutNodes(getters)") PutNode[] putNodes) { for (int i = 0; i < getters.length; i++) { PropertyGetter getter = getters[i]; - putNode.executeWithFlags(to, getter.getKey(), getter.get(from), getter.getFlags()); + putNodes[i].executeWithFlags(to, getter.getKey(), getter.get(from), getter.getFlags()); } } @TruffleBoundary - @Specialization(replaces = "doCached") + @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]; - PutNode.getUncached().executeWithFlags(to, property.getKey(), property.get(from, false), property.getFlags()); + 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]; @@ -1023,6 +1030,14 @@ static PropertyGetter[] createPropertyGetters(Shape shape) { 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 */ From 850bd4af4e895687d3c3a31352a8cd3ce641b5d7 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 6 Nov 2025 20:31:01 +0100 Subject: [PATCH 16/33] Revise PutNode implementation. * Remove cached mode; check if the property flags match the expected flags according to mode. * Check oldShape valid assumption in specialization and update shape if newShape is obsolete. --- .../truffle/api/object/DynamicObject.java | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) 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 8cc964bb45c9..4dec05c9a361 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 @@ -604,7 +604,7 @@ public abstract static class PutNode extends Node { // @formatter:on @HostCompilerDirectives.InliningRoot public final void execute(DynamicObject receiver, Object key, Object value) { - executeImpl(receiver, key, value, Flags.DEFAULT, 0); + executeImpl(receiver, key, value, 0, Flags.DEFAULT); } /** @@ -618,7 +618,7 @@ public final void execute(DynamicObject receiver, Object key, Object value) { */ @HostCompilerDirectives.InliningRoot public final boolean executeIfPresent(DynamicObject receiver, Object key, Object value) { - return executeImpl(receiver, key, value, Flags.IF_PRESENT, 0); + return executeImpl(receiver, key, value, 0, Flags.IF_PRESENT); } /** @@ -632,7 +632,7 @@ public final boolean executeIfPresent(DynamicObject receiver, Object key, Object */ @HostCompilerDirectives.InliningRoot public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object value) { - return executeImpl(receiver, key, value, Flags.IF_ABSENT, 0); + return executeImpl(receiver, key, value, 0, Flags.IF_ABSENT); } /** @@ -647,7 +647,7 @@ public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object */ @HostCompilerDirectives.InliningRoot public final void executeWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { - executeImpl(receiver, key, value, Flags.DEFAULT | Flags.UPDATE_FLAGS, propertyFlags); + executeImpl(receiver, key, value, propertyFlags, Flags.DEFAULT | Flags.UPDATE_FLAGS); } /** @@ -656,7 +656,7 @@ public final void executeWithFlags(DynamicObject receiver, Object key, Object va */ @HostCompilerDirectives.InliningRoot public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { - return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.UPDATE_FLAGS, propertyFlags); + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_PRESENT | Flags.UPDATE_FLAGS); } /** @@ -665,33 +665,30 @@ public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object ke */ @HostCompilerDirectives.InliningRoot public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { - return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.UPDATE_FLAGS, propertyFlags); + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_ABSENT | Flags.UPDATE_FLAGS); } // private - abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags); + abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode); @SuppressWarnings("unused") @Specialization(guards = { "guard", "key == cachedKey", - "mode == cachedMode", - "propertyFlags == cachedPropertyFlags", + "propertyFlagsEqual(propertyFlags, mode, oldShape, newShape, oldProperty, newProperty)", "newLocation == null || newLocation.canStoreValue(value)", - }, assumptions = "newShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") - static boolean doCached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags, + }, 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("mode") int cachedMode, - @Cached("propertyFlags") int cachedPropertyFlags, @Cached("oldShape.getProperty(key)") Property oldProperty, - @Cached("getNewShapeAndCheckOldShapeStillValid(key, value, cachedPropertyFlags, cachedMode, oldProperty, oldShape)") Shape newShape, - @Cached("getNewLocation(oldShape, newShape, key, oldProperty)") Location newLocation, - @Cached("newShape.getValidAbstractAssumption()") AbstractAssumption newShapeValidAssumption) { - // We use mode instead of cachedMode to fold it during host inlining + @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; @@ -704,6 +701,7 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo if (newShape != oldShape) { DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + maybeUpdateShape(receiver, newShape); } return true; } @@ -715,13 +713,13 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo * still create new doCached instances, which is important once we see objects with the new * non-obsolete shape. */ - @Specialization(guards = "!receiver.getShape().isValid()") - static boolean doInvalid(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags) { + @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 mode, int propertyFlags) { + static boolean doGeneric(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode) { return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); } @@ -772,7 +770,7 @@ public abstract static class PutConstantNode extends Node { */ @HostCompilerDirectives.InliningRoot public final void execute(DynamicObject receiver, Object key, Object value) { - executeImpl(receiver, key, value, Flags.DEFAULT | Flags.CONST, 0); + executeImpl(receiver, key, value, 0, Flags.DEFAULT | Flags.CONST); } /** @@ -780,7 +778,7 @@ public final void execute(DynamicObject receiver, Object key, Object value) { */ @HostCompilerDirectives.InliningRoot public final boolean executeIfPresent(DynamicObject receiver, Object key, Object value) { - return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.CONST, 0); + return executeImpl(receiver, key, value, 0, Flags.IF_PRESENT | Flags.CONST); } /** @@ -788,7 +786,7 @@ public final boolean executeIfPresent(DynamicObject receiver, Object key, Object */ @HostCompilerDirectives.InliningRoot public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object value) { - return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.CONST, 0); + return executeImpl(receiver, key, value, 0, Flags.IF_ABSENT | Flags.CONST); } // @formatter:off @@ -831,7 +829,7 @@ public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object // @formatter:on @HostCompilerDirectives.InliningRoot public final void executeWithFlags(DynamicObject receiver, Object key, Object value, int propertyFlags) { - executeImpl(receiver, key, value, Flags.DEFAULT | Flags.CONST | Flags.UPDATE_FLAGS, propertyFlags); + executeImpl(receiver, key, value, propertyFlags, Flags.DEFAULT | Flags.CONST | Flags.UPDATE_FLAGS); } /** @@ -840,7 +838,7 @@ public final void executeWithFlags(DynamicObject receiver, Object key, Object va */ @HostCompilerDirectives.InliningRoot public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object key, Object value, int propertyFlags) { - return executeImpl(receiver, key, value, Flags.IF_PRESENT | Flags.CONST | Flags.UPDATE_FLAGS, propertyFlags); + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_PRESENT | Flags.CONST | Flags.UPDATE_FLAGS); } /** @@ -849,33 +847,29 @@ public final boolean executeWithFlagsIfPresent(DynamicObject receiver, Object ke */ @HostCompilerDirectives.InliningRoot public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key, Object value, int propertyFlags) { - return executeImpl(receiver, key, value, Flags.IF_ABSENT | Flags.CONST | Flags.UPDATE_FLAGS, propertyFlags); + return executeImpl(receiver, key, value, propertyFlags, Flags.IF_ABSENT | Flags.CONST | Flags.UPDATE_FLAGS); } // private - abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags); + abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode); @SuppressWarnings("unused") @Specialization(guards = { "guard", "key == cachedKey", - "mode == cachedMode", - "propertyFlags == cachedPropertyFlags", - "newLocation == null || newLocation.canStoreConstant(value)", - }, assumptions = "newShapeValidAssumption", limit = "SHAPE_CACHE_LIMIT") - static boolean doCached(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags, + "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("mode") int cachedMode, - @Cached("propertyFlags") int cachedPropertyFlags, @Cached("oldShape.getProperty(key)") Property oldProperty, - @Cached("getNewShapeAndCheckOldShapeStillValid(key, value, cachedPropertyFlags, cachedMode, oldProperty, oldShape)") Shape newShape, - @Cached("getNewLocation(oldShape, newShape, key, oldProperty)") Location newLocation, - @Cached("newShape.getValidAbstractAssumption()") AbstractAssumption newShapeValidAssumption) { - // We use mode instead of cachedMode to fold it during host inlining + @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; @@ -886,6 +880,7 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo if (newShape != oldShape) { DynamicObjectSupport.setShapeWithStoreFence(receiver, newShape); + maybeUpdateShape(receiver, newShape); } return true; } @@ -897,13 +892,13 @@ static boolean doCached(DynamicObject receiver, Object key, Object value, int mo * still create new doCached instances, which is important once we see objects with the new * non-obsolete shape. */ - @Specialization(guards = "!receiver.getShape().isValid()") - static boolean doInvalid(DynamicObject receiver, Object key, Object value, int mode, int propertyFlags) { + @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 mode, int propertyFlags) { + static boolean doGeneric(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode) { return ObsolescenceStrategy.putGeneric(receiver, key, value, propertyFlags, mode); } @@ -924,6 +919,24 @@ public static PutConstantNode 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) { @@ -947,24 +960,18 @@ static Shape getNewShape(Object cachedKey, Object value, int newPropertyFlags, i } } - // defineProperty() might obsolete the oldShape and we don't handle invalid shape -> valid - // shape transitions on the fast path (in doCached) - static Shape getNewShapeAndCheckOldShapeStillValid(Object cachedKey, Object value, int newPropertyFlags, int putFlags, Property existingProperty, Shape oldShape) { - Shape newShape = getNewShape(cachedKey, value, newPropertyFlags, putFlags, existingProperty, oldShape); - if (!oldShape.isValid()) { - return oldShape; // return an invalid shape to not use this specialization - } - return newShape; - } - - static Location getNewLocation(Shape oldShape, Shape newShape, Object cachedKey, Property oldProperty) { + static Property getNewProperty(Shape oldShape, Shape newShape, Object cachedKey, Property oldProperty) { if (newShape == oldShape) { - return oldProperty == null ? null : oldProperty.getLocation(); + return oldProperty; } else { - return newShape.getLocation(cachedKey); + 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. From 75c6c6d90bf9884daea5e6f36d69d102ab07ec07 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 4 Nov 2025 21:46:17 +0100 Subject: [PATCH 17/33] Remove HasShapeFlagsNode. --- .../object/test/DynamicObjectNodesTest.java | 20 ++--- .../truffle/api/object/DynamicObject.java | 82 ------------------- 2 files changed, 8 insertions(+), 94 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java index 367deec0a510..106b157e2051 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -175,13 +175,6 @@ private DynamicObject.GetShapeFlagsNode createGetShapeFlagsNode() { }; } - private DynamicObject.HasShapeFlagsNode createHasShapeFlagsNode() { - return switch (run) { - case CACHED -> DynamicObject.HasShapeFlagsNode.create(); - case UNCACHED -> DynamicObject.HasShapeFlagsNode.getUncached(); - }; - } - private DynamicObject.ResetShapeNode createResetShapeNode() { return switch (run) { case CACHED -> DynamicObject.ResetShapeNode.create(); @@ -681,21 +674,24 @@ public void testHasAddShapeFlags() { final int flags = 0b101010; DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); - DynamicObject.HasShapeFlagsNode hasShapeFlagsNode = createHasShapeFlagsNode(); DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); DynamicObject.AddShapeFlagsNode addShapeFlagsNode = createAddShapeFlagsNode(); DynamicObject o1 = createEmpty(); setShapeFlagsNode.execute(o1, flags); assertEquals(flags, getShapeFlagsNode.execute(o1)); - assertTrue(hasShapeFlagsNode.execute(o1, flags)); - assertTrue(hasShapeFlagsNode.execute(o1, 0b10)); - assertFalse(hasShapeFlagsNode.execute(o1, 0b11)); + assertTrue(hasShapeFlags(getShapeFlagsNode, o1, flags)); + assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b10)); + assertFalse(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); addShapeFlagsNode.execute(o1, 0b1); - assertTrue(hasShapeFlagsNode.execute(o1, 0b11)); + 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; + } + @Test public void testMakeShared() { String key = "key"; 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 4dec05c9a361..8061e46bf518 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 @@ -1295,10 +1295,7 @@ public abstract static class GetShapeFlagsNode extends Node { * } * } * - * Note that {@link HasShapeFlagsNode} is more convenient for that particular pattern. - * * @return shape flags - * @see HasShapeFlagsNode * @see SetShapeFlagsNode * @see Shape.Builder#shapeFlags(int) * @see Shape#getFlags() @@ -1336,85 +1333,6 @@ public static GetShapeFlagsNode getUncached() { } } - /** - * Checks if the language-specific object shape flags include the given flags. - * - * @see #execute(DynamicObject, int) - * @since 25.1 - */ - @ImportStatic(DynamicObject.class) - @GeneratePackagePrivate - @GenerateUncached - @GenerateInline(false) - public abstract static class HasShapeFlagsNode extends Node { - - HasShapeFlagsNode() { - } - - // @formatter:off - /** - * Checks if the language-specific object shape flags contains the given flags, previously set using - * {@link SetShapeFlagsNode} or - * {@link Shape.Builder#shapeFlags(int)}. If no shape flags were explicitly set, the default of - * false is returned. - * - * These flags may be used to tag objects that possess characteristics that need to be queried - * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. - * - *

    Usage example:

    - * - * {@snippet : - * @ExportMessage - * Object writeMember(String member, Object value, - * @Cached DynamicObject.HasShapeFlagsNode hasShapeFlagsNode, - * @Cached DynamicObject.PutNode putNode) - * throws UnsupportedMessageException { - * if (hasShapeFlagsNode.execute(receiver, FROZEN)) { - * throw UnsupportedMessageException.create(); - * } - * putNode.execute(this, member, value); - * } - * } - * - * @return whether the shape flags contain (all of) the given flags - * @see GetShapeFlagsNode - * @see SetShapeFlagsNode - * @see Shape.Builder#shapeFlags(int) - * @see Shape#getFlags() - */ - // @formatter:on - public abstract boolean execute(DynamicObject receiver, int flags); - - @SuppressWarnings("unused") - @Specialization(guards = "shape == cachedShape", limit = "1") - static boolean doCached(DynamicObject receiver, int flags, - @Bind("receiver.getShape()") Shape shape, - @Cached("shape") Shape cachedShape) { - return (cachedShape.getFlags() & flags) == flags; - } - - @Specialization(replaces = "doCached") - static boolean doGeneric(DynamicObject receiver, int flags) { - return (receiver.getShape().getFlags() & flags) == flags; - } - - /** - * @since 25.1 - */ - @NeverDefault - public static HasShapeFlagsNode create() { - return DynamicObjectFactory.HasShapeFlagsNodeGen.create(); - } - - /** - * @since 25.1 - */ - @NeverDefault - public static HasShapeFlagsNode getUncached() { - return DynamicObjectFactory.HasShapeFlagsNodeGen.getUncached(); - } - } - /** * Sets language-specific object shape flags. * From 1b9f51af7c46116151b51d31e4b2a1aa64c267f6 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 4 Nov 2025 21:46:35 +0100 Subject: [PATCH 18/33] Remove AddShapeFlagsNode. --- .../object/test/DynamicObjectNodesTest.java | 14 +-- .../truffle/api/object/DynamicObject.java | 105 ------------------ 2 files changed, 5 insertions(+), 114 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java index 106b157e2051..8db090f8b4f9 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -189,13 +189,6 @@ private DynamicObject.SetShapeFlagsNode createSetShapeFlagsNode() { }; } - private DynamicObject.AddShapeFlagsNode createAddShapeFlagsNode() { - return switch (run) { - case CACHED -> DynamicObject.AddShapeFlagsNode.create(); - case UNCACHED -> DynamicObject.AddShapeFlagsNode.getUncached(); - }; - } - private DynamicObject.IsSharedNode createIsSharedNode() { return switch (run) { case CACHED -> DynamicObject.IsSharedNode.create(); @@ -675,7 +668,6 @@ public void testHasAddShapeFlags() { DynamicObject.GetShapeFlagsNode getShapeFlagsNode = createGetShapeFlagsNode(); DynamicObject.SetShapeFlagsNode setShapeFlagsNode = createSetShapeFlagsNode(); - DynamicObject.AddShapeFlagsNode addShapeFlagsNode = createAddShapeFlagsNode(); DynamicObject o1 = createEmpty(); setShapeFlagsNode.execute(o1, flags); @@ -683,7 +675,7 @@ public void testHasAddShapeFlags() { assertTrue(hasShapeFlags(getShapeFlagsNode, o1, flags)); assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b10)); assertFalse(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); - addShapeFlagsNode.execute(o1, 0b1); + addShapeFlags(getShapeFlagsNode, setShapeFlagsNode, o1, 0b1); assertTrue(hasShapeFlags(getShapeFlagsNode, o1, 0b11)); assertEquals(flags | 0b1, getShapeFlagsNode.execute(o1)); } @@ -692,6 +684,10 @@ static boolean hasShapeFlags(DynamicObject.GetShapeFlagsNode getShapeFlagsNode, 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"; 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 8061e46bf518..1d513dd77d6d 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 @@ -1369,13 +1369,10 @@ public abstract static class SetShapeFlagsNode extends Node { * } * } * - * Note that {@link AddShapeFlagsNode} is more efficient and convenient for that particular pattern. - * * @param newFlags the flags to set; must be in the range from 0 to 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 AddShapeFlagsNode * @see Shape.Builder#shapeFlags(int) */ // @formatter:on @@ -1429,108 +1426,6 @@ public static SetShapeFlagsNode getUncached() { } } - /** - * Adds language-specific object shape flags. - * - * @see #execute(DynamicObject, int) - * @since 25.1 - */ - @ImportStatic(DynamicObject.class) - @GeneratePackagePrivate - @GenerateUncached - @GenerateInline(false) - public abstract static class AddShapeFlagsNode extends Node { - - AddShapeFlagsNode() { - } - - // @formatter:off - /** - * Adds language-specific object shape flags, changing the object's shape if need be. - * - * These flags may be used to tag objects that possess characteristics that need to be queried - * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. - *

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

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

    Usage example:

    - * - * {@snippet : - * @Specialization - * static void freeze(DynamicObject receiver, - * @Cached DynamicObject.AddShapeFlagsNode addShapeFlagsNode) { - * addShapeFlagsNode.execute(receiver, FROZEN); - * } - * } - * - * @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) - */ - // @formatter:on - public abstract boolean execute(DynamicObject receiver, int newFlags); - - @SuppressWarnings("unused") - @Specialization(guards = {"shape == cachedShape", "flags == newFlags"}, limit = "SHAPE_CACHE_LIMIT") - static boolean doCached(DynamicObject receiver, int flags, - @Bind("receiver.getShape()") Shape shape, - @Cached("shape") Shape cachedShape, - @Cached("flags") int newFlags, - @Cached("shapeAddFlags(cachedShape, newFlags)") Shape newShape) { - if (newShape != cachedShape) { - receiver.setShape(newShape); - return true; - } else { - return false; - } - } - - @Specialization(replaces = "doCached") - static boolean doGeneric(DynamicObject receiver, int flags, - @Bind("receiver.getShape()") Shape shape) { - Shape newShape = shapeAddFlags(shape, flags); - if (newShape != shape) { - receiver.setShape(newShape); - return true; - } else { - return false; - } - } - - static Shape shapeAddFlags(Shape shape, int newFlags) { - return shape.setFlags(shape.getFlags() | newFlags); - } - - /** - * @since 25.1 - */ - @NeverDefault - public static AddShapeFlagsNode create() { - return DynamicObjectFactory.AddShapeFlagsNodeGen.create(); - } - - /** - * @since 25.1 - */ - @NeverDefault - public static AddShapeFlagsNode getUncached() { - return DynamicObjectFactory.AddShapeFlagsNodeGen.getUncached(); - } - } - /** * Checks whether this object is marked as shared. * From f69c4462cd058ed861b10024ba175a21b2b070f2 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 4 Nov 2025 21:53:38 +0100 Subject: [PATCH 19/33] Simplify SetShapeFlagsNode. --- .../src/com/oracle/truffle/api/object/DynamicObject.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 1d513dd77d6d..9a91079cce60 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 @@ -1379,12 +1379,11 @@ public abstract static class SetShapeFlagsNode extends Node { public abstract boolean execute(DynamicObject receiver, int newFlags); @SuppressWarnings("unused") - @Specialization(guards = {"shape == cachedShape", "flags == newFlags"}, limit = "SHAPE_CACHE_LIMIT") + @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("flags") int newFlags, - @Cached("shapeSetFlags(cachedShape, newFlags)") Shape newShape) { + @Cached("shapeSetFlags(cachedShape, flags)") Shape newShape) { if (newShape != cachedShape) { receiver.setShape(newShape); return true; From a6e2daf92cb3616c04ac3349af9741363c07d447 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 6 Nov 2025 16:13:10 +0100 Subject: [PATCH 20/33] Make store fence unconditional in the interpreter to avoid shared shape check. --- .../com/oracle/truffle/api/object/DynamicObjectSupport.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 bbcb4d76b532..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); From 2f76e7dbc4ee53826de32b20a9d870a36594d414 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 7 Nov 2025 05:28:23 +0100 Subject: [PATCH 21/33] Use a common read/write for object locations. --- .../oracle/truffle/api/object/Location.java | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) 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 30a73bbfdcc1..8908da84bde0 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 @@ -206,17 +206,23 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard) { if (this instanceof ObjectLocation objectLocation) { Object value; - if (field == null) { - Object array = getObjectArray(store, guard); - long offset = getObjectArrayOffset(); - value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); - } else { - if (UseVarHandle) { - value = field.varHandle().get(store); + done: { + Object base; + long offset; + if (field == null) { + base = getObjectArray(store, guard); + offset = getObjectArrayOffset(); } else { - field.receiverCheck(store); - value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); + if (UseVarHandle) { + value = field.varHandle().get(store); + break done; + } else { + field.receiverCheck(store); + base = store; + offset = getFieldOffset(); + } } + value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); } return CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard); } else { @@ -526,19 +532,22 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap CompilerDirectives.transferToInterpreterAndInvalidate(); objectLocation.invalidateTypeAssumption(value); } + Object base; + long offset; if (field == null) { - Object array = getObjectArray(receiver, guard); - long offset = getObjectArrayOffset(); - UnsafeAccess.unsafePutObject(array, offset, value, this); + base = getObjectArray(receiver, guard); + offset = getObjectArrayOffset(); } else { if (UseVarHandle) { field.varHandle().set(receiver, value); + return; } else { field.receiverCheck(receiver); - long offset = getFieldOffset(); - UnsafeAccess.unsafePutObject(receiver, offset, value, this); + base = receiver; + offset = getFieldOffset(); } } + UnsafeAccess.unsafePutObject(base, offset, value, this); } else { // primitive location long longValue; if (isIntLocation()) { From e6cf92d3ebbae2369713e5393b3b505ac8f81ee1 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 7 Nov 2025 14:14:13 +0100 Subject: [PATCH 22/33] Throw UnexpectedResultException for boxed values, too. --- .../object/test/DynamicObjectLibraryTest.java | 17 +++++-- .../object/test/DynamicObjectNodesTest.java | 17 +++++-- .../oracle/truffle/api/object/Location.java | 45 ------------------- 3 files changed, 28 insertions(+), 51 deletions(-) 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 e73ce9fcb4ff..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 @@ -142,9 +142,9 @@ public void testGet1() throws UnexpectedResultException { 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); @@ -158,7 +158,7 @@ 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"; var getMissingKey = createLibraryForReceiverAndKey(o1, missingKey); @@ -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(); diff --git a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java index 8db090f8b4f9..7bc399599ce4 100644 --- a/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java +++ b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/DynamicObjectNodesTest.java @@ -271,9 +271,9 @@ public void testGet1() throws UnexpectedResultException { var getNode = createGetNode(); assertEquals(v1, getNode.execute(o1, k1, null)); - assertEquals(v1, getNode.executeInt(o1, k1, null)); + assertEquals(v1, getAsInt(getNode, o1, k1, null)); assertEquals(v1, getNode.execute(o2, k1, null)); - assertEquals(v1, getNode.executeInt(o2, k1, null)); + assertEquals(v1, getAsInt(getNode, o2, k1, null)); String v2 = "asdf"; uncachedSet(o1, k1, v2); @@ -287,7 +287,7 @@ public void testGet1() throws UnexpectedResultException { assertEquals(v2, e.getResult()); } assertEquals(v1, getNode.execute(o2, k1, null)); - assertEquals(v1, getNode.executeInt(o2, k1, null)); + assertEquals(v1, getAsInt(getNode, o2, k1, null)); String missingKey = "missing"; var getMissingKey = createGetNode(); @@ -298,6 +298,17 @@ public void testGet1() throws UnexpectedResultException { 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(); 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 8908da84bde0..2a517afb825c 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 @@ -277,21 +277,6 @@ final int getIntInternal(DynamicObject store, Shape expectedShape, boolean guard } return (int) longValue; } - } else if (this instanceof ObjectLocation objectLocation) { - Object value; - if (field == null) { - Object array = getObjectArray(store, guard); - long offset = getObjectArrayOffset(); - value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); - } else { - if (UseVarHandle) { - value = field.varHandle().get(store); - } else { - field.receiverCheck(store); - value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); - } - } - return expectInteger(CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard)); } return getIntUnexpected(store, expectedShape, guard); } @@ -324,21 +309,6 @@ final long getLongInternal(DynamicObject store, Shape expectedShape, boolean gua } return longValue; } - } else if (this instanceof ObjectLocation objectLocation) { - Object value; - if (field == null) { - Object array = getObjectArray(store, guard); - long offset = getObjectArrayOffset(); - value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); - } else { - if (UseVarHandle) { - value = field.varHandle().get(store); - } else { - field.receiverCheck(store); - value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); - } - } - return expectLong(CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard)); } return getLongUnexpected(store, expectedShape, guard); } @@ -371,21 +341,6 @@ final double getDoubleInternal(DynamicObject store, Shape expectedShape, boolean } return Double.longBitsToDouble(longValue); } - } else if (this instanceof ObjectLocation objectLocation) { - Object value; - if (field == null) { - Object array = getObjectArray(store, guard); - long offset = getObjectArrayOffset(); - value = UnsafeAccess.unsafeGetObject(array, offset, guard, this); - } else { - if (UseVarHandle) { - value = field.varHandle().get(store); - } else { - field.receiverCheck(store); - value = UnsafeAccess.unsafeGetObject(store, getFieldOffset(), guard, this); - } - } - return expectDouble(CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard)); } return getDoubleUnexpected(store, expectedShape, guard); } From 5a629b0d46df4999fc5d24bcd0fd9aff27348871 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 15:12:18 +0100 Subject: [PATCH 23/33] Always clear the source location when moving locations. --- .../api/object/DynamicObjectLibraryImpl.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) 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 541bfc63b91f..db4bc9f35cb1 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 @@ -393,10 +393,8 @@ static RemovePlan prepareRemove(Shape shapeBefore, Shape shapeAfter, Property re 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)); - } + // 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)) { moves.sort(Move::compareTo); } @@ -415,6 +413,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; @@ -432,10 +434,10 @@ private static final class Move implements Comparable { } void perform(DynamicObject obj) { - if (fromLoc == toLoc) { - return; + if (fromLoc != toLoc) { + performSet(obj, performGet(obj)); } - performSet(obj, performGet(obj)); + clear(obj); } Object performGet(DynamicObject obj) { @@ -446,8 +448,14 @@ void performSet(DynamicObject obj, Object value) { 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; + } + + private void clear(DynamicObject obj) { + // clear the location to avoid memory leak AND kill the location identity fromLoc.clear(obj); } @@ -498,25 +506,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 From b964096e5a57233acbe9847e7e5169a4a12a4303 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 16:06:06 +0100 Subject: [PATCH 24/33] Add system property for shape/key cache limit. --- .../src/com/oracle/truffle/api/object/DynamicObject.java | 2 +- .../com/oracle/truffle/api/object/ObjectStorageOptions.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) 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 9a91079cce60..61f0fcadb0bf 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 @@ -346,7 +346,7 @@ static Lookup internalLookup() { // NODES - static final int SHAPE_CACHE_LIMIT = 5; + static final int SHAPE_CACHE_LIMIT = ObjectStorageOptions.CacheLimit; /** * Gets the value of a property or returns a default value if no such property exists. 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..6f752b376c24 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 @@ -72,6 +72,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); From 36c0c9783f6919c6e98f7b25b3729f3e010df5a9 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 17:20:15 +0100 Subject: [PATCH 25/33] Improve DynamicObject javadoc. --- .../truffle/api/object/DynamicObject.java | 368 ++++++++---------- .../api/object/DynamicObjectSnippets.java | 366 +++++++++++++++++ 2 files changed, 520 insertions(+), 214 deletions(-) create mode 100644 truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSnippets.java 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 61f0fcadb0bf..735c0e8447db 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 @@ -75,102 +75,86 @@ import sun.misc.Unsafe; -// @formatter:off /** * Represents a dynamic object, members of which can be dynamically added and removed at run time. * - * To use it, extend {@link DynamicObject} and use nodes nested under DynamicObject such as {@link DynamicObject.GetNode} 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 {@link 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}. * *

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

    Using DynamicObject nodes

    * - *

    General documentation about DynamicObject nodes

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

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

    Usage examples:

    * - * {@snippet : - * @Specialization(limit = "3") - * static Object read(MyDynamicObjectSubclass receiver, Object key, - * @Cached DynamicObject.GetNode getNode) { - * return getNode.execute(receiver, key, NULL_VALUE); - * } - * } + *

    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 : - * @ExportMessage - * Object readMember(String name, - * @Cached DynamicObject.GetNode getNode) throws UnknownIdentifierException { - * Object result = getNode.execute(this, name, null); - * if (result == null) { - * throw UnknownIdentifierException.create(name); - * } - * return result; - * } - * } + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.MyObjectWithFields"} * * @see DynamicObject#DynamicObject(Shape) * @see Shape * @see Shape#newBuilder() * @see DynamicObject.GetNode * @see DynamicObject.ContainsKeyNode + * @see DynamicObject.PutNode * @see DynamicObject.GetPropertyNode * @see DynamicObject.GetPropertyFlagsNode - * @see DynamicObject.PutNode + * @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.GetKeyArrayNode - * @see DynamicObject.GetPropertyArrayNode - * @see DynamicObject.RemoveKeyNode * @see DynamicObject.UpdateShapeNode + * @see DynamicObject.ResetShapeNode * @see DynamicObject.IsSharedNode * @see DynamicObject.MarkSharedNode * @since 0.8 or earlier */ -// @formatter:on @SuppressWarnings("deprecation") public abstract class DynamicObject implements TruffleObject { @@ -369,33 +353,36 @@ public abstract static class GetNode extends Node { GetNode() { } - // @formatter:off /** - * Gets the value of an existing property or returns the provided default value if no such property exists. + * Gets the value of an existing property or returns the provided default value if no such + * property exists. * - *

    Usage example:

    + *

    Usage examples:

    * - * {@snippet : - * @Specialization(limit = "3") - * static Object read(DynamicObject receiver, Object key, - * @Cached DynamicObject.GetNode getNode) { - * return getNode.execute(receiver, key, NULL_VALUE); - * } - * } + *

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

    * - * @param key the property 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 */ - // @formatter:on 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 + * @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 @@ -409,7 +396,8 @@ public abstract static class GetNode extends Node { * Gets the value of an existing property or returns the provided default value if no such * property exists. * - * @param key the property key + * @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 @@ -423,7 +411,8 @@ public abstract static class GetNode extends Node { * Gets the value of an existing property or returns the provided default value if no such * property exists. * - * @param key the property key + * @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 @@ -579,29 +568,30 @@ public abstract static class PutNode extends Node { PutNode() { } - // @formatter:off /** * Sets the value of an existing property or adds a new property if no such property exists. * - * A newly added property will have flags 0; flags of existing properties will not be changed. - * Use {@link #executeWithFlags} to set property flags as well. + * 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 example:

    + *

    Usage examples:

    + * + *

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

    * - * {@snippet : - * @ExportMessage - * Object writeMember(String member, Object value, - * @Cached DynamicObject.PutNode putNode) { - * putNode.execute(this, member, value); - * } - * } + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.PutSimple"} * - * @param key the property key + *

    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) */ - // @formatter:on @HostCompilerDirectives.InliningRoot public final void execute(DynamicObject receiver, Object key, Object value) { executeImpl(receiver, key, value, 0, Flags.DEFAULT); @@ -668,8 +658,6 @@ public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key return executeImpl(receiver, key, value, propertyFlags, Flags.IF_ABSENT | Flags.UPDATE_FLAGS); } - // private - abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode); @SuppressWarnings("unused") @@ -789,44 +777,41 @@ public final boolean executeIfAbsent(DynamicObject receiver, Object key, Object return executeImpl(receiver, key, value, 0, Flags.IF_ABSENT | Flags.CONST); } - // @formatter:off /** - * Adds a property with a constant value or replaces an existing one. If the property already - * exists, its flags will be updated. + * 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. + * 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. + * 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. + * Note: the value is strongly referenced from the shape property map. It should ideally be + * a value type or light-weight object without any references to guest language objects in + * order to prevent potential memory leaks from holding onto the Shape in inline caches. The + * Shape transition itself is weak, so the previous shapes will not hold strongly on the + * value. * *

    Usage example:

    * - * {@snippet : - * // declare property - * putConstantNode.putConstant(receiver, key, NULL_VALUE); + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.PutConstant1"} * - * // initialize property - * putNode.put(receiver, key, value); - * } + * {@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) */ - // @formatter:on @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); @@ -850,8 +835,6 @@ public final boolean executeWithFlagsIfAbsent(DynamicObject receiver, Object key return executeImpl(receiver, key, value, propertyFlags, Flags.IF_ABSENT | Flags.CONST | Flags.UPDATE_FLAGS); } - // private - abstract boolean executeImpl(DynamicObject receiver, Object key, Object value, int propertyFlags, int mode); @SuppressWarnings("unused") @@ -1082,22 +1065,20 @@ public abstract static class ContainsKeyNode extends Node { ContainsKeyNode() { } - // @formatter:off /** * Returns {@code true} if this object contains a property with the given key. * - * {@snippet : - * @ExportMessage - * boolean isMemberReadable(String name, - * @Cached DynamicObject.ContainsKeyNode containsKeyNode) { - * return containsKeyNode.execute(this, name); - * } - * } + *

    Usage example:

    + * + * {@snippet file = "com/oracle/truffle/api/object/DynamicObjectSnippets.java" region = + * "com.oracle.truffle.api.object.DynamicObjectSnippets.ContainsKey"} * - * @param key the property key + * 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} */ - // @formatter:on public abstract boolean execute(DynamicObject receiver, Object key); @SuppressWarnings("unused") @@ -1151,7 +1132,15 @@ public abstract static class RemoveKeyNode extends Node { /** * Removes the property with the given key from the object. * - * @param key the property key + *

    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 */ @@ -1270,37 +1259,29 @@ public abstract static class GetShapeFlagsNode extends Node { GetShapeFlagsNode() { } - // @formatter:off /** * Gets the language-specific object shape flags previously set using - * {@link SetShapeFlagsNode} or - * {@link Shape.Builder#shapeFlags(int)}. If no shape flags were explicitly set, the default of - * 0 is returned. + * {@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. + * These flags may be used to tag objects that possess characteristics that need to be + * queried efficiently on fast and slow paths. For example, they can be used to mark objects + * as frozen. * *

    Usage example:

    * - * {@snippet : - * @ExportMessage - * Object writeMember(String member, Object value, - * @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, - * @Cached DynamicObject.PutNode putNode) - * throws UnsupportedMessageException { - * if ((getShapeFlagsNode.execute(receiver) & FROZEN) != 0) { - * throw UnsupportedMessageException.create(); - * } - * putNode.execute(this, member, value); - * } - * } + *

    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() */ - // @formatter:on public abstract int execute(DynamicObject receiver); @SuppressWarnings("unused") @@ -1348,26 +1329,22 @@ public abstract static class SetShapeFlagsNode extends Node { SetShapeFlagsNode() { } - // @formatter:off /** * Sets language-specific object shape flags, changing the object's shape if need be. * - * These flags may be used to tag objects that possess characteristics that need to be queried - * efficiently on fast and slow paths. For example, they can be used to mark objects as frozen. + * 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. + * Only the lowest 16 bits (i.e. values in the range 0 to 65535) are allowed, the remaining + * bits are currently reserved. * *

    Usage example:

    * - * {@snippet : - * @Specialization - * static void freeze(DynamicObject receiver, - * @Cached DynamicObject.GetShapeFlagsNode getShapeFlagsNode, - * @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { - * setShapeFlagsNode.execute(receiver, getShapeFlagsNode.execute(receiver) | FROZEN); - * } - * } + *

    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. @@ -1375,7 +1352,6 @@ public abstract static class SetShapeFlagsNode extends Node { * @see GetShapeFlagsNode * @see Shape.Builder#shapeFlags(int) */ - // @formatter:on public abstract boolean execute(DynamicObject receiver, int newFlags); @SuppressWarnings("unused") @@ -1698,29 +1674,32 @@ public abstract static class GetPropertyFlagsNode extends Node { GetPropertyFlagsNode() { } - // @formatter:off /** * Gets the property flags associated with the requested property key. Returns the - * {@code defaultValue} if the object contains no such property. If the property exists but no - * flags were explicitly set, returns the default of 0. + * {@code defaultValue} if the object contains no such property. If the property exists but + * no flags were explicitly set, returns the default of 0. * *

    * Convenience method equivalent to: * - * {@snippet : - * @Specialization - * int getPropertyFlags(@Cached GetPropertyNode getPropertyNode) { - * Property property = getPropertyNode.execute(object, key); - * return property != null ? property.getFlags() : defaultValue; - * } - * } + * {@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:

    * - * @param key the property key + * {@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 */ - // @formatter:on public abstract int execute(DynamicObject receiver, Object key, int defaultValue); @SuppressWarnings("unused") @@ -1774,7 +1753,8 @@ public abstract static class SetPropertyFlagsNode extends Node { /** * Sets the property flags associated with the requested property. * - * @param key the property key + * @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} */ @@ -2084,7 +2064,6 @@ public abstract static class GetKeyArrayNode extends Node { GetKeyArrayNode() { } - // @formatter:off /** * Gets a snapshot of the object's property keys, in insertion order. The returned array may * have been cached and must not be mutated. @@ -2093,53 +2072,14 @@ public abstract static class GetKeyArrayNode extends Node { * *

    Usage example:

    * - * The example below shows how the returned keys array could be translated to an interop array - * for use with InteropLibrary. - * - * {@snippet : - * @ExportMessage - * Object getMembers( - * @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode) { - * return new Keys(getKeyArrayNode.execute(this)); - * } - * - * @ExportLibrary(InteropLibrary.class) - * static final class Keys implements TruffleObject { - * - * @CompilationFinal(dimensions = 1) final Object[] keys; - * - * Keys(Object[] keys) { - * this.keys = keys; - * } - * - * @ExportMessage - * boolean hasArrayElements() { - * return true; - * } - * - * @ExportMessage - * Object readArrayElement(long index) throws InvalidArrayIndexException { - * if (!isArrayElementReadable(index)) { - * throw InvalidArrayIndexException.create(index); - * } - * return keys[(int) index]; - * } - * - * @ExportMessage - * long getArraySize() { - * return keys.length; - * } - * - * @ExportMessage - * boolean isArrayElementReadable(long index) { - * return index >= 0 && index < keys.length; - * } - * } - * } - * - * @return a read-only array of the object's property keys. - */ - // @formatter:on + * 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") @@ -2200,7 +2140,7 @@ public abstract static class GetPropertyArrayNode extends Node { * 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. + * @return a read-only array of the object's properties. Do not modify. * @see GetKeyArrayNode */ public abstract Property[] execute(DynamicObject receiver); 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..1ecab98a2803 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.object/src/com/oracle/truffle/api/object/DynamicObjectSnippets.java @@ -0,0 +1,366 @@ +/* + * 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.strings.TruffleString; + +/** + * Example code snippets for {@link DynamicObject}. + */ +@SuppressWarnings({"unused", "static-method"}) +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" +} From 36d6c3eace32363ffa3f05e7893f9ce7726dd8fc Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 23:29:37 +0100 Subject: [PATCH 26/33] Add condition profile. --- .../src/com/oracle/truffle/api/object/DynamicObject.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 735c0e8447db..be0103124d50 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 @@ -72,6 +72,7 @@ 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; @@ -1713,9 +1714,10 @@ static int doCached(DynamicObject receiver, Object key, int defaultValue, } @Specialization(replaces = "doCached") - static int doGeneric(DynamicObject receiver, Object key, int defaultValue) { + final int doGeneric(DynamicObject receiver, Object key, int defaultValue, + @Cached InlinedConditionProfile isPropertyNonNull) { Property property = receiver.getShape().getProperty(key); - return property != null ? property.getFlags() : defaultValue; + return isPropertyNonNull.profile(this, property != null) ? property.getFlags() : defaultValue; } /** From ffac21464f84cfba6dd2c3fb1c4e590319d55958 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 21:26:31 +0100 Subject: [PATCH 27/33] Fix Truffle DSL not honoring `@SuppressWarnings("deprecation")`. --- .../api/dsl/test/SuppressWarningTest.java | 39 ++++++++++++++++++- .../processor/expression/DSLExpression.java | 6 +-- 2 files changed, 40 insertions(+), 5 deletions(-) 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.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; From 52bb34be6e2b72ca84f67593d87d4fc5fd4232ea Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 16:33:38 +0100 Subject: [PATCH 28/33] Deprecate DynamicObjectLibrary. --- .../api/object/test/CachedFallbackTest.java | 18 +++++++++--------- .../truffle/api/object/test/CachedGetNode.java | 4 ++-- .../truffle/api/object/test/CachedPutNode.java | 4 ++-- .../api/object/test/MergedLibraryTest.java | 14 +++++++------- .../test/ParametrizedDynamicObjectTest.java | 14 +++++++------- .../object/test/TestNestedDispatchNode.java | 8 +++----- .../api/object/DynamicObjectLibrary.java | 6 ++++++ 7 files changed, 36 insertions(+), 32 deletions(-) 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 0a4e5a2a9010..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,20 +44,20 @@ 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 com.oracle.truffle.api.test.AbstractLibraryTest; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -import java.util.List; +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 { @@ -136,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); @@ -164,10 +164,10 @@ 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); 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/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/ParametrizedDynamicObjectTest.java b/truffle/src/com.oracle.truffle.api.object.test/src/com/oracle/truffle/api/object/test/ParametrizedDynamicObjectTest.java index 7a1883a19a82..aef27fb6c841 100644 --- 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 @@ -46,11 +46,11 @@ import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Property; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.test.AbstractLibraryTest; +@SuppressWarnings("deprecation") @RunWith(Parameterized.class) public abstract class ParametrizedDynamicObjectTest extends AbstractLibraryTest { @@ -71,10 +71,10 @@ public enum TestRun { protected final DynamicObjectLibraryWrapper createLibrary(Object receiver) { return switch (run) { - case CACHED_LIBRARY -> wrap(adoptNode(DynamicObjectLibrary.getFactory().create(receiver)).get()); - case UNCACHED_LIBRARY -> wrap(DynamicObjectLibrary.getFactory().getUncached(receiver)); - case DISPATCHED_CACHED_LIBRARY -> wrap(adoptNode(DynamicObjectLibrary.getFactory().createDispatched(2)).get()); - case DISPATCHED_UNCACHED_LIBRARY -> wrap(DynamicObjectLibrary.getUncached()); + case CACHED_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; }; @@ -90,7 +90,7 @@ protected final DynamicObjectLibraryWrapper createLibrary() { protected final DynamicObjectLibraryWrapper uncachedLibrary() { return switch (run) { case CACHED_LIBRARY, UNCACHED_LIBRARY, DISPATCHED_CACHED_LIBRARY, DISPATCHED_UNCACHED_LIBRARY -> - wrap(DynamicObjectLibrary.getUncached()); + wrap(com.oracle.truffle.api.object.DynamicObjectLibrary.getUncached()); case CACHED_NODES, UNCACHED_NODES -> UNCACHED_NODES_LIBRARY; }; } @@ -163,7 +163,7 @@ public final int getPropertyFlagsOrDefault(DynamicObject object, Object key, int public abstract Property[] getPropertyArray(DynamicObject object); } - protected static DynamicObjectLibraryWrapper wrap(DynamicObjectLibrary library) { + protected static DynamicObjectLibraryWrapper wrap(com.oracle.truffle.api.object.DynamicObjectLibrary library) { return new DynamicObjectLibraryWrapper() { @Override 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/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 c0f69c124792..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 { From 58ef6971a4bd029d31f8809a5cf94adb265efc2c Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 3 Oct 2025 16:34:59 +0200 Subject: [PATCH 29/33] Update truffle sigtest snapshot. --- .../snapshot.sigtest | 221 +++++++++++++++++- 1 file changed, 214 insertions(+), 7 deletions(-) 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 - From 147f8c145aaf2636af7ea11a24730022cd18fe36 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 10 Nov 2025 23:37:46 +0100 Subject: [PATCH 30/33] Remove VarHandle-based field access. --- .../oracle/truffle/api/object/Location.java | 88 +++++-------------- 1 file changed, 24 insertions(+), 64 deletions(-) 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 2a517afb825c..379e04dd3a69 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,8 +40,6 @@ */ package com.oracle.truffle.api.object; -import static com.oracle.truffle.api.object.ObjectStorageOptions.UseVarHandle; - import java.util.Objects; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -205,25 +203,17 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected */ final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard) { if (this instanceof ObjectLocation objectLocation) { - Object value; - done: { - Object base; - long offset; - if (field == null) { - base = getObjectArray(store, guard); - offset = getObjectArrayOffset(); - } else { - if (UseVarHandle) { - value = field.varHandle().get(store); - break done; - } else { - field.receiverCheck(store); - base = store; - offset = getFieldOffset(); - } - } - value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); + Object base; + long offset; + if (field == null) { + base = getObjectArray(store, guard); + offset = getObjectArrayOffset(); + } else { + field.receiverCheck(store); + base = store; + offset = getFieldOffset(); } + Object value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); return CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard); } else { if (field == null) { @@ -239,13 +229,8 @@ final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard return ((ConstantLocation) this).get(store, guard); } } else { - long longValue; - if (UseVarHandle) { - longValue = (long) field.varHandle().get(store); - } else { - field.receiverCheck(store); - longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); - } + field.receiverCheck(store); + long longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); if (this instanceof IntLocation) { return (int) longValue; } else if (this instanceof LongLocation) { @@ -268,13 +253,8 @@ final int getIntInternal(DynamicObject store, Shape expectedShape, boolean guard long offset = getPrimitiveArrayOffset(); return UnsafeAccess.unsafeGetInt(array, offset, guard, this); } else { - long longValue; - if (UseVarHandle) { - longValue = (long) field.varHandle().get(store); - } else { - field.receiverCheck(store); - longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); - } + field.receiverCheck(store); + long longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); return (int) longValue; } } @@ -300,14 +280,8 @@ final long getLongInternal(DynamicObject store, Shape expectedShape, boolean gua long offset = getPrimitiveArrayOffset(); return UnsafeAccess.unsafeGetLong(array, offset, guard, this); } else { - long longValue; - if (UseVarHandle) { - longValue = (long) field.varHandle().get(store); - } else { - field.receiverCheck(store); - longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); - } - return longValue; + field.receiverCheck(store); + return UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); } } return getLongUnexpected(store, expectedShape, guard); @@ -332,13 +306,8 @@ final double getDoubleInternal(DynamicObject store, Shape expectedShape, boolean long offset = getPrimitiveArrayOffset(); return UnsafeAccess.unsafeGetDouble(array, offset, guard, this); } else { - long longValue; - if (UseVarHandle) { - longValue = (long) field.varHandle().get(store); - } else { - field.receiverCheck(store); - longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); - } + field.receiverCheck(store); + long longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); return Double.longBitsToDouble(longValue); } } @@ -493,14 +462,9 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap base = getObjectArray(receiver, guard); offset = getObjectArrayOffset(); } else { - if (UseVarHandle) { - field.varHandle().set(receiver, value); - return; - } else { - field.receiverCheck(receiver); - base = receiver; - offset = getFieldOffset(); - } + field.receiverCheck(receiver); + base = receiver; + offset = getFieldOffset(); } UnsafeAccess.unsafePutObject(base, offset, value, this); } else { // primitive location @@ -550,13 +514,9 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap assert isConstantLocation() : this; return; } - if (UseVarHandle) { - field.varHandle().set(receiver, longValue); - } else { - field.receiverCheck(receiver); - long offset = getFieldOffset(); - UnsafeAccess.unsafePutLong(receiver, offset, longValue, this); - } + field.receiverCheck(receiver); + long offset = getFieldOffset(); + UnsafeAccess.unsafePutLong(receiver, offset, longValue, this); } } From ecab15b8488eb2e1845e93a4058fdbb9be7b097b Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 11 Nov 2025 00:14:32 +0100 Subject: [PATCH 31/33] Simplify putGeneric and use setInternal. --- .../truffle/api/object/DynamicObject.java | 12 ++- .../api/object/ObsolescenceStrategy.java | 83 +++++++------------ 2 files changed, 37 insertions(+), 58 deletions(-) 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 be0103124d50..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 @@ -1051,6 +1051,10 @@ 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. * @@ -1189,7 +1193,7 @@ static boolean doGeneric(DynamicObject receiver, Object key) { @TruffleBoundary static boolean removePropertyGeneric(DynamicObject receiver, Shape cachedShape, Property cachedProperty) { - updateShape(receiver); + updateShape(receiver, cachedShape); Shape oldShape = receiver.getShape(); Property existingProperty = reusePropertyLookup(cachedShape, cachedProperty, oldShape); @@ -1806,14 +1810,14 @@ private static void changePropertyFlagsGeneric(DynamicObject receiver, Shape cac assert cachedProperty != null; assert cachedProperty.getFlags() != propertyFlags; - updateShape(receiver); + 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); + updateShape(receiver, newShape); } } @@ -1853,7 +1857,7 @@ static Property reusePropertyLookup(Shape cachedShape, Property cachedProperty, static void maybeUpdateShape(DynamicObject store, Shape newShape) { CompilerAsserts.partialEvaluationConstant(newShape); if (!newShape.isValid()) { - updateShape(store); + updateShape(store, newShape); } } 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 56fd948ce041..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 @@ -57,7 +57,6 @@ 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; @@ -159,31 +158,13 @@ private static boolean assertLocationInRange(final Shape shape, final Location l } @TruffleBoundary - static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags) { - Shape shape = object.getShape(); - Property existingProperty = shape.getProperty(key); - return putGeneric(object, key, value, newPropertyFlags, putFlags, shape, existingProperty); + static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int mode) { + return putGeneric(object, key, value, newPropertyFlags, mode, null, null); } @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 false; - } 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); - } - - private static boolean putGenericSlowPath(DynamicObject object, Object key, Object value, int newPropertyFlags, int putFlags, - Shape initialShape, Property propertyOfInitialShape) { + static boolean putGeneric(DynamicObject object, Object key, Object value, int newPropertyFlags, int mode, + Shape cachedShape, Property propertyOfCachedShape) { CompilerAsserts.neverPartOfCompilation(); updateShape(object); Shape oldShape; @@ -191,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)) { + } else if (Flags.isPutIfAbsent(mode)) { return false; - } 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.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 false; - } else { - location.setSafe(object, value, false, false); } return true; } @@ -503,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) { @@ -800,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; From ec2d80c603484be3bf09f96af791d1544f81c2f1 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 11 Nov 2025 14:53:42 +0100 Subject: [PATCH 32/33] Hoist index read. --- .../oracle/truffle/api/object/Location.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) 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 379e04dd3a69..58bc4ac6c9f7 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 @@ -202,23 +202,26 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected * @return the read value */ final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard) { + long idx = Integer.toUnsignedLong(index); if (this instanceof ObjectLocation objectLocation) { Object base; long offset; + Object value; if (field == null) { base = getObjectArray(store, guard); - offset = getObjectArrayOffset(); + offset = computeObjectArrayOffset(idx); + value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); } else { field.receiverCheck(store); base = store; offset = getFieldOffset(); + value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); } - Object value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); return CompilerDirectives.inInterpreter() ? value : objectLocation.assumedTypeCast(value, guard); } else { if (field == null) { Object array = getPrimitiveArray(store, guard); - long offset = getPrimitiveArrayOffset(); + long offset = computePrimitiveArrayOffset(idx); if (isIntLocation()) { return UnsafeAccess.unsafeGetInt(array, offset, guard, this); } else if (isLongLocation()) { @@ -442,6 +445,7 @@ final void setDoubleSafe(DynamicObject store, double value, boolean guard, boole */ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shape oldShape, Shape newShape) { assert canStoreValue(value) : value; + long idx = Integer.toUnsignedLong(index); boolean init = newShape != oldShape; if (init) { DynamicObjectSupport.grow(receiver, oldShape, newShape); @@ -460,20 +464,21 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap long offset; if (field == null) { base = getObjectArray(receiver, guard); - offset = getObjectArrayOffset(); + offset = computeObjectArrayOffset(idx); + UnsafeAccess.unsafePutObject(base, offset, value, this); } else { field.receiverCheck(receiver); base = receiver; offset = getFieldOffset(); + UnsafeAccess.unsafePutObject(base, offset, value, this); } - 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 = getPrimitiveArrayOffset(); + long offset = computePrimitiveArrayOffset(idx); UnsafeAccess.unsafePutInt(array, offset, intValue, this); return; } else { @@ -489,7 +494,7 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap } if (field == null) { Object array = getPrimitiveArray(receiver, guard); - long offset = getPrimitiveArrayOffset(); + long offset = computePrimitiveArrayOffset(idx); UnsafeAccess.unsafePutLong(array, offset, longValue, this); return; } @@ -504,7 +509,7 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap } if (field == null) { Object array = getPrimitiveArray(receiver, guard); - long offset = getPrimitiveArrayOffset(); + long offset = computePrimitiveArrayOffset(idx); UnsafeAccess.unsafePutDouble(array, offset, doubleValue, this); return; } else { @@ -745,12 +750,20 @@ 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 Integer.toUnsignedLong(index) * Unsafe.ARRAY_OBJECT_INDEX_SCALE + Unsafe.ARRAY_OBJECT_BASE_OFFSET; + return computeObjectArrayOffset(Integer.toUnsignedLong(index)); } final long getPrimitiveArrayOffset() { - return Integer.toUnsignedLong(index) * Unsafe.ARRAY_INT_INDEX_SCALE + Unsafe.ARRAY_INT_BASE_OFFSET; + return computePrimitiveArrayOffset(Integer.toUnsignedLong(index)); } static Object getObjectArray(DynamicObject store, boolean condition) { From 3dffe983fcc2f246d5182e382127633a6faef2db Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 11 Nov 2025 00:03:32 +0100 Subject: [PATCH 33/33] Omit receiver type check. --- .../oracle/truffle/api/object/FieldInfo.java | 9 +++ .../oracle/truffle/api/object/Location.java | 71 ++++++++++++------- .../api/object/ObjectStorageOptions.java | 13 ++++ 3 files changed, 68 insertions(+), 25 deletions(-) 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..b86861f202c6 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.ReceiverCast) { + 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 58bc4ac6c9f7..d9757dd8e98e 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 @@ -201,26 +201,28 @@ protected double getDouble(DynamicObject store, boolean guard) throws Unexpected * @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(store, guard); + base = getObjectArray(receiver, guard); offset = computeObjectArrayOffset(idx); value = UnsafeAccess.unsafeGetObject(base, offset, guard, this); } else { - field.receiverCheck(store); - base = store; + 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(store, guard); + Object array = getPrimitiveArray(receiver, guard); long offset = computePrimitiveArrayOffset(idx); if (isIntLocation()) { return UnsafeAccess.unsafeGetInt(array, offset, guard, this); @@ -229,11 +231,11 @@ final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard } else if (isDoubleLocation()) { return UnsafeAccess.unsafeGetDouble(array, offset, guard, this); } else { - return ((ConstantLocation) this).get(store, guard); + return ((ConstantLocation) this).get(receiver, guard); } } else { - field.receiverCheck(store); - long longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + 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) { @@ -250,18 +252,19 @@ final Object getInternal(DynamicObject store, Shape expectedShape, boolean guard * @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(store, guard); + Object array = getPrimitiveArray(receiver, guard); long offset = getPrimitiveArrayOffset(); return UnsafeAccess.unsafeGetInt(array, offset, guard, this); } else { - field.receiverCheck(store); - long longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + Object base = field.unsafeReceiverCast(receiver); + long longValue = UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); return (int) longValue; } } - return getIntUnexpected(store, expectedShape, guard); + return getIntUnexpected(receiver, expectedShape, guard); } /** @@ -277,17 +280,18 @@ private int getIntUnexpected(DynamicObject store, Shape expectedShape, boolean g * @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(store, guard); + Object array = getPrimitiveArray(receiver, guard); long offset = getPrimitiveArrayOffset(); return UnsafeAccess.unsafeGetLong(array, offset, guard, this); } else { - field.receiverCheck(store); - return UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + Object base = field.unsafeReceiverCast(receiver); + return UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); } } - return getLongUnexpected(store, expectedShape, guard); + return getLongUnexpected(receiver, expectedShape, guard); } /** @@ -303,18 +307,19 @@ private long getLongUnexpected(DynamicObject store, Shape expectedShape, boolean * @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(store, guard); + Object array = getPrimitiveArray(receiver, guard); long offset = getPrimitiveArrayOffset(); return UnsafeAccess.unsafeGetDouble(array, offset, guard, this); } else { - field.receiverCheck(store); - long longValue = UnsafeAccess.unsafeGetLong(store, getFieldOffset(), guard, this); + Object base = field.unsafeReceiverCast(receiver); + long longValue = UnsafeAccess.unsafeGetLong(base, getFieldOffset(), guard, this); return Double.longBitsToDouble(longValue); } } - return getDoubleUnexpected(store, expectedShape, guard); + return getDoubleUnexpected(receiver, expectedShape, guard); } /** @@ -436,16 +441,19 @@ final void setDoubleSafe(DynamicObject store, double value, boolean guard, boole * 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 receiver storage object + * @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). */ - final void setInternal(DynamicObject receiver, Object value, boolean guard, Shape oldShape, Shape newShape) { + @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); @@ -467,8 +475,7 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap offset = computeObjectArrayOffset(idx); UnsafeAccess.unsafePutObject(base, offset, value, this); } else { - field.receiverCheck(receiver); - base = receiver; + base = field.unsafeReceiverCast(receiver); offset = getFieldOffset(); UnsafeAccess.unsafePutObject(base, offset, value, this); } @@ -519,9 +526,23 @@ final void setInternal(DynamicObject receiver, Object value, boolean guard, Shap assert isConstantLocation() : this; return; } - field.receiverCheck(receiver); + Object base = field.unsafeReceiverCast(receiver); long offset = getFieldOffset(); - UnsafeAccess.unsafePutLong(receiver, offset, longValue, this); + 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 (CompilerDirectives.inCompiledCode() || ObjectStorageOptions.ReceiverCast) { + return receiver; + } else { + return UnsafeAccess.hostUnsafeCast(receiver, DynamicObject.class, false, true, false); } } 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 6f752b376c24..7e79034e56f3 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 ReceiverCast = booleanOption(OPTION_PREFIX + "ReceiverCast", false) || ASSERTIONS_ENABLED; static final boolean TriePropertyMap = booleanOption(OPTION_PREFIX + "TriePropertyMap", true); static final boolean TrieTransitionMap = booleanOption(OPTION_PREFIX + "TrieTransitionMap", true); @@ -94,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; + } }