From 3088d3027475c6ef87c457a4ceb9d583f191fd03 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 15 Sep 2026 11:49:23 +0200 Subject: [PATCH] Optimize ReadAttributeFromPythonObjectNode using the HAS_MATERIALIZED_DICT Shape flag --- .../ReadAttributeFromObjectNode.java | 25 ++++- .../ReadAttributeFromPythonObjectNode.java | 6 -- .../nodes/object/GetDictIfExistsNode.java | 12 +-- .../object/GetDictIfMaterializedNode.java | 96 +++++++++++++++++++ 4 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfMaterializedNode.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromObjectNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromObjectNode.java index 07bc762e5e..fd9feda051 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromObjectNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromObjectNode.java @@ -45,17 +45,23 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItemStringKey; import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.object.PythonObject; +import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.object.GetDictIfExistsNode; +import com.oracle.graal.python.nodes.object.GetDictIfMaterializedNode; +import com.oracle.graal.python.util.PythonUtils; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateInline; 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.ReportPolymorphism; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.PropertyGetter; +import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.profiles.InlinedConditionProfile; import com.oracle.truffle.api.strings.TruffleString; @@ -65,6 +71,7 @@ @ReportPolymorphism @GenerateUncached @GenerateInline(false) +@ImportStatic({PGuards.class, PythonUtils.class}) public abstract class ReadAttributeFromObjectNode extends PNodeWithContext { @NeverDefault @@ -82,12 +89,24 @@ public static ReadAttributeFromObjectNode getUncached() { public abstract Object execute(PythonAbstractNativeObject object, TruffleString key); + // fast-path for objects without "materialized" dict + @Specialization(guards = {"!hasMaterializedDict(cachedShape)", "key == cachedKey", "getter != null", "getter.accepts(object)"}, limit = "2") + static Object readDirect(PythonObject object, TruffleString key, + @Bind Node inliningTarget, + @Cached("object.getShape()") Shape cachedShape, + @Cached("key") TruffleString cachedKey, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter, + @Cached ReadAttributeFromPythonObjectNode.ReceiverCast receiverCastNode) { + assert object.checkDictFlags(); + return getter.get(receiverCastNode.execute(inliningTarget, object)); + } + // any python object attribute read - @Specialization + @Specialization(replaces = "readDirect") static Object readObjectAttribute(PythonObject object, TruffleString key, @Bind Node inliningTarget, @Shared @Cached InlinedConditionProfile profileHasDict, - @Shared @Cached GetDictIfExistsNode getDict, + @Cached GetDictIfMaterializedNode getDict, @Shared @Cached(inline = true) ReadAttributeFromPythonObjectNode readAttributeFromPythonObjectNode, @Shared @Cached HashingStorageGetItemStringKey getItem) { var dict = getDict.execute(object); @@ -107,7 +126,7 @@ static Object readObjectAttribute(PythonObject object, TruffleString key, @Specialization static Object readNativeObject(PythonAbstractNativeObject object, TruffleString key, @Bind Node inliningTarget, - @Shared @Cached GetDictIfExistsNode getDict, + @Cached GetDictIfExistsNode getDict, @Shared @Cached HashingStorageGetItemStringKey getItem) { PDict dict = getDict.execute(object); if (dict != null) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromPythonObjectNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromPythonObjectNode.java index 528520e27b..95b589c2c0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromPythonObjectNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromPythonObjectNode.java @@ -76,16 +76,10 @@ public static Object executeUncached(PythonObject object, TruffleString key, Obj return getUncached().execute(getUncached(), object, key, defaultValue); } - public final Object execute(PythonObject object, TruffleString key, Object defaultValue) { - return execute(this, object, key, defaultValue); - } - public final Object execute(PythonObject object, TruffleString key) { return execute(this, object, key, PNone.NO_VALUE); } - // used only by DynamicObjectStorage, which will be removed during the transition from - // DynamicObject to ObjectHashMap public final Object execute(DynamicObject object, TruffleString key, Object defaultValue) { return execute(this, object, key, defaultValue); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java index e65e86f0f7..b8942ff541 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java @@ -80,8 +80,6 @@ import com.oracle.truffle.api.dsl.NeverDefault; 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.PropertyGetter; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.profiles.InlinedBranchProfile; @@ -99,14 +97,6 @@ public static GetDictIfExistsNode create() { public abstract PDict execute(PythonObject object); - /** - * Use this node when the shape is already cached. Use - * {@link PropertyGetter#accepts(DynamicObject)} to check the cached shape in a guard. Note that this does not initialize the final property assumption! - */ - public static PropertyGetter createDictPropertyGetter(Shape shape) { - return HiddenAttr.DICT.createPropertyGetter(shape); - } - @Specialization(guards = {"object.getShape() == cachedShape", "hasNoDict(cachedShape)"}, limit = "1") static PDict getNoDictCachedShape(@SuppressWarnings("unused") PythonObject object, @SuppressWarnings("unused") @Cached("object.getShape()") Shape cachedShape) { @@ -133,7 +123,7 @@ static PDict getConstant(@SuppressWarnings("unused") PythonObject object, } @Idempotent - protected boolean dictIsConstant(PythonObject object) { + protected static boolean dictIsConstant(PythonObject object) { return object instanceof PythonModule || object instanceof PythonManagedClass; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfMaterializedNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfMaterializedNode.java new file mode 100644 index 0000000000..b66f50f97a --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfMaterializedNode.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021, 2026, 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.graal.python.nodes.object; + +import com.oracle.graal.python.builtins.objects.dict.PDict; +import com.oracle.graal.python.builtins.objects.object.PythonObject; +import com.oracle.graal.python.nodes.HiddenAttr; +import com.oracle.graal.python.nodes.HiddenAttr.ReadNode; +import com.oracle.graal.python.nodes.PGuards; +import com.oracle.graal.python.nodes.PNodeWithContext; +import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.Shape; + +/** + * Like {@link GetDictIfExistsNode}, but gets the dict only if it is materialized and reads/writes must be wired through it. + */ +@GenerateUncached +@GenerateInline(false) // footprint reduction 36 -> 17 +@ImportStatic({GetDictIfExistsNode.class, PGuards.class}) +public abstract class GetDictIfMaterializedNode extends PNodeWithContext { + + public abstract PDict execute(PythonObject object); + + @Specialization(guards = {"object.getShape() == cachedShape", "!hasMaterializedDict(cachedShape)"}, limit = "1") + static PDict getNoDictCachedShape(@SuppressWarnings("unused") PythonObject object, + @SuppressWarnings("unused") @Cached("object.getShape()") Shape cachedShape) { + assert object.checkDictFlags(); + return null; + } + + @Specialization(guards = "!hasMaterializedDict(object.getShape())", replaces = "getNoDictCachedShape") + static PDict getNoDict(@SuppressWarnings("unused") PythonObject object) { + assert object.checkDictFlags(); + return null; + } + + @Specialization(guards = {"isSingleContext()", "hasMaterializedDict(object.getShape())", "object == cached", "dictIsConstant(cached)", "dict != null"}, limit = "1") + static PDict getConstant(@SuppressWarnings("unused") PythonObject object, + @SuppressWarnings("unused") @Cached(value = "object", weak = true) PythonObject cached, + @Cached(value = "getDictUncached(object)", weak = true) PDict dict) { + return dict; + } + + @Specialization(guards = "hasMaterializedDict(object.getShape())", replaces = "getConstant") + @InliningCutoff + static PDict doPythonObject(PythonObject object, + @Bind Node inliningTarget, + @Cached ReadNode readHiddenAttrNode) { + return (PDict) readHiddenAttrNode.execute(inliningTarget, object, HiddenAttr.DICT, null); + } +}