diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java index c24b1ff866..987a445567 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java @@ -1587,7 +1587,10 @@ public TypeBuilder doc(String doc) { private final TpSlots declaredSlots; /** - * The actual slots including slots inherited from base classes + * The actual slots including slots inherited from base classes. + * + * n.b.: this field is positioned to be at the same offset as the one in + * {@link com.oracle.graal.python.builtins.objects.type.PythonManagedClass#tpSlots}. */ private final TpSlots slots; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java index 30be65426e..872546b9dd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java @@ -292,7 +292,8 @@ static HashingStorage clearPlain(DynamicObjectStorage receiver, @Specialization(guards = "isPythonObject(receiver.getStore())") static HashingStorage clearObjectBacked(Node inliningTarget, DynamicObjectStorage receiver, - @Cached HiddenAttr.ReadNode readHiddenAttrNode) { + @Cached HiddenAttr.ReadNode readHiddenAttrNode, + @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { /* * We cannot use resetShape as that would lose hidden keys, such as CLASS or OBJ_ID. * Construct a new storage instead and set it as the object's __dict__'s storage. @@ -301,6 +302,7 @@ static HashingStorage clearObjectBacked(Node inliningTarget, DynamicObjectStorag PythonObject owner = (PythonObject) receiver.getStore(); PDict dict = (PDict) readHiddenAttrNode.execute(inliningTarget, owner, HiddenAttr.DICT, null); if (dict != null && dict.getDictStorage() == receiver) { + setShapeFlagsNode.executeAdd(owner, PythonObject.HAS_DICT | PythonObject.HAS_MATERIALIZED_DICT); dict.setDictStorage(newStorage); } return newStorage; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java index a34728cd16..be5db59202 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java @@ -63,6 +63,7 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorageNodesFactory.HashingStorageSetItemWithHashNodeGen; import com.oracle.graal.python.builtins.objects.common.KeywordsStorage.GetKeywordsStorageItemNode; import com.oracle.graal.python.builtins.objects.common.ObjectHashMap.PutNode; +import com.oracle.graal.python.builtins.objects.object.PythonObject; import com.oracle.graal.python.lib.PyObjectHashNode; import com.oracle.graal.python.lib.PyObjectRichCompareBool; import com.oracle.graal.python.lib.PyUnicodeCheckExactNode; @@ -272,9 +273,13 @@ abstract static class SpecializedSetStringKey extends Node { } static EconomicMapStorage dynamicObjectStorageToEconomicMap(Node inliningTarget, DynamicObjectStorage s, + DynamicObject.SetShapeFlagsNode setShapeFlags, DynamicObject.GetKeyArrayNode getKeyArrayNode, DynamicObject.GetNode getNode, PyObjectHashNode hashNode, ObjectHashMap.PutNode putNode) { DynamicObject store = s.store; + if (store instanceof PythonObject pyObj) { + setShapeFlags.executeAdd(pyObj, PythonObject.HAS_MATERIALIZED_DICT); + } Object[] keys = getKeyArrayNode.execute(store); EconomicMapStorage result = EconomicMapStorage.create(keys.length); ObjectHashMap resultMap = result; @@ -382,9 +387,10 @@ static HashingStorage domTransition(Frame frame, Node inliningTarget, DynamicObj @Cached PyObjectHashNode hashNode, @Cached ObjectHashMap.PutNode putUnsafeNode, @Cached PutNode putNode, + @Cached DynamicObject.SetShapeFlagsNode setShapeFlags, @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode, @Cached DynamicObject.GetNode getNode) { - EconomicMapStorage result = dynamicObjectStorageToEconomicMap(inliningTarget, self, getKeyArrayNode, getNode, hashNode, putUnsafeNode); + EconomicMapStorage result = dynamicObjectStorageToEconomicMap(inliningTarget, self, setShapeFlags, getKeyArrayNode, getNode, hashNode, putUnsafeNode); putNode.execute(frame, inliningTarget, result, key, keyHash, value); return result; } @@ -511,9 +517,10 @@ static HashingStorage domTransition(Frame frame, Node inliningTarget, DynamicObj @Cached PyObjectHashNode hashNode, @Cached ObjectHashMap.PutNode putUnsafeNode, @Cached PutNode putNode, + @Cached DynamicObject.SetShapeFlagsNode setShapeFlags, @Cached DynamicObject.GetKeyArrayNode getKeyArrayNode, @Cached DynamicObject.GetNode getNode) { - EconomicMapStorage result = dynamicObjectStorageToEconomicMap(inliningTarget, self, getKeyArrayNode, getNode, hashNode, putUnsafeNode); + EconomicMapStorage result = dynamicObjectStorageToEconomicMap(inliningTarget, self, setShapeFlags, getKeyArrayNode, getNode, hashNode, putUnsafeNode); putNode.execute(frame, inliningTarget, result, key, hashNode.execute(frame, inliningTarget, key), value); return result; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/PythonObject.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/PythonObject.java index b61ca62fcd..3f9bb0d243 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/PythonObject.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/PythonObject.java @@ -33,11 +33,13 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PythonAbstractObject; +import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage; import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.type.PythonManagedClass; import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsSameTypeNode; import com.oracle.graal.python.nodes.HiddenAttr; import com.oracle.graal.python.nodes.PGuards; +import com.oracle.graal.python.nodes.object.GetDictIfExistsNode; import com.oracle.graal.python.runtime.PythonOptions; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -54,19 +56,27 @@ public class PythonObject extends PythonAbstractObject { public static final byte HAS_SLOTS_BUT_NO_DICT_FLAG = 0b1; /** * Indicates that the shape has some properties that may contain {@link PNone#NO_VALUE} and - * therefore the shape itself is not enough to resolve any lookups. + * therefore the shape itself is not enough to resolve any lookups. This flag is maintained only + * for types and not for all Python objects. */ public static final byte HAS_NO_VALUE_PROPERTIES = 0b10; /** - * Indicates that the object has a dict in the form of an actual dictionary + * Indicates that the object has the dict hidden key set. Use {@link #HAS_MATERIALIZED_DICT} to + * determine if the dict must be used. */ - public static final byte HAS_MATERIALIZED_DICT = 0b100; + public static final byte HAS_DICT = 0b100; + /** + * Indicates that the object has a dict in the form of an actual dictionary that is not backed + * by this object, i.e., is disconnected from the {@link DynamicObject} properties of this + * object. If this flag is set, all property accesses must operate on the dictionary object. + */ + public static final byte HAS_MATERIALIZED_DICT = 0b1000; /** * Indicates that the object is a static base in the CPython's tp_new_wrapper sense. * * @see com.oracle.graal.python.nodes.function.builtins.WrapTpNew */ - public static final byte IS_STATIC_BASE = 0b1000; + public static final byte IS_STATIC_BASE = 0b10000; private Object pythonClass; @@ -82,6 +92,20 @@ public void setDict(Node inliningTarget, HiddenAttr.WriteNode writeNode, PDict d writeNode.execute(inliningTarget, this, HiddenAttr.DICT, dict); } + public boolean checkDictFlags() { + return checkDictFlags(GetDictIfExistsNode.getDictUncached(this)); + } + + public boolean checkDictFlags(PDict dict) { + assert dict == null || hasShapeFlag(HAS_DICT); + assert (dict == null || dict.getDictStorage() instanceof DynamicObjectStorage domStorage && domStorage.getStore() == this) || hasShapeFlag(HAS_MATERIALIZED_DICT); + return true; + } + + private boolean hasShapeFlag(int flag) { + return (GetShapeFlagsNode.getUncached().execute(this) & flag) != 0; + } + @NeverDefault public final Object getPythonClass() { return pythonClass; @@ -89,6 +113,7 @@ public final Object getPythonClass() { public final void setPythonClass(Object pythonClass) { assert getShape().getDynamicType() == PNone.NO_VALUE; + assert PGuards.isPythonClass(pythonClass); this.pythonClass = pythonClass; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonManagedClass.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonManagedClass.java index 9a5692e6b7..9465373571 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonManagedClass.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonManagedClass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. * Copyright (c) 2013, Regents of the University of California * * All rights reserved. @@ -69,13 +69,18 @@ public abstract class PythonManagedClass extends PythonObject implements PythonA private boolean abstractClass; private final PDict subClasses; + /** + * This field is positioned to be at the same offset as the one in + * {@link com.oracle.graal.python.builtins.PythonBuiltinClassType#slots}. I found that the + * compiler in the lower tier will then unify the diamond because it's actually reading at the + * same offset from the object pointer, and that gave a small but measurable speedup. + */ + protected TpSlots tpSlots; @CompilationFinal private Shape instanceShape; private TruffleString name; private TruffleString qualName; private int indexedSlotCount; - protected TpSlots tpSlots; - /** {@code true} if the MRO contains a native class. */ private final boolean needsNativeAllocation; @CompilationFinal private boolean mroInitialized = false; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java index ca36a10476..822379578b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java @@ -1974,14 +1974,19 @@ public static TpSlots executeUncached(Object pythonClass) { return GetTpSlotsNodeGen.getUncached().execute(null, pythonClass); } + /* + * On the fast path this most likely comes from GetCachedTpSlotsNode, which already checked + * PythonBuiltinClassType as specialization before, so in this node we prefer to check for + * PythonManagedClass first. + */ @Specialization - static TpSlots doBuiltinType(PythonBuiltinClassType type) { - return type.getSlots(); + static TpSlots doManaged(PythonManagedClass klass) { + return klass.getTpSlots(); } @Specialization - static TpSlots doManaged(PythonManagedClass klass) { - return klass.getTpSlots(); + static TpSlots doBuiltinType(PythonBuiltinClassType type) { + return type.getSlots(); } @Specialization diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java index 5aca3355ed..f8e1b4fdc3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java @@ -40,6 +40,7 @@ */ package com.oracle.graal.python.nodes; +import static com.oracle.graal.python.builtins.objects.object.PythonObject.HAS_DICT; import static com.oracle.graal.python.builtins.objects.object.PythonObject.HAS_MATERIALIZED_DICT; import static com.oracle.graal.python.nodes.BuiltinNames.J___GRAALPYTHON_INTEROP_BEHAVIOR__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___BASICSIZE__; @@ -50,6 +51,8 @@ import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___WEAKLISTOFFSET__; import com.oracle.graal.python.builtins.objects.PythonAbstractObject; +import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage; +import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.object.PythonObject; import com.oracle.graal.python.nodes.HiddenAttrFactory.ReadNodeGen; import com.oracle.graal.python.nodes.HiddenAttrFactory.WriteNodeGen; @@ -169,10 +172,20 @@ public static void executeUncached(PythonAbstractObject self, HiddenAttr attr, O static void doPythonObjectDict(PythonObject self, HiddenAttr attr, Object value, @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode, @Shared @Cached DynamicObject.PutNode putNode) { - setShapeFlagsNode.executeAdd(self, HAS_MATERIALIZED_DICT); + setShapeFlagsNode.executeAdd(self, HAS_DICT); + if (isGenericDict(self, value)) { + setShapeFlagsNode.executeAdd(self, HAS_MATERIALIZED_DICT); + } putNode.execute(self, DICT.key, value); } + private static boolean isGenericDict(PythonObject self, Object value) { + if (value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dynamicStorage) { + return dynamicStorage.getStore() != self; + } + return true; + } + @Specialization(guards = "attr != DICT || !isPythonObject(self)") static void doGeneric(PythonAbstractObject self, HiddenAttr attr, Object value, @Shared @Cached DynamicObject.PutNode putNode) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedModuleAttributeNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedModuleAttributeNode.java deleted file mode 100644 index 900ece1de6..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedModuleAttributeNode.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 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.attributes; - -import static com.oracle.graal.python.nodes.attributes.MergedObjectTypeModuleGetFixedAttributeNode.hasNoGetAttr; - -import com.oracle.graal.python.builtins.objects.PNone; -import com.oracle.graal.python.builtins.objects.module.ModuleBuiltins; -import com.oracle.graal.python.builtins.objects.module.PythonModule; -import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlot; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrGet.CallSlotDescrGet; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrSet; -import com.oracle.graal.python.nodes.ErrorMessages; -import com.oracle.graal.python.nodes.PNodeWithContext; -import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.runtime.exception.PException; -import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.GenerateCached; -import com.oracle.truffle.api.dsl.GenerateInline; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.profiles.InlinedConditionProfile; -import com.oracle.truffle.api.strings.TruffleString; - -@GenerateInline -@GenerateCached -public abstract class GetFixedModuleAttributeNode extends PNodeWithContext { - - public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object object, TruffleString key, Object type); - - /** - * @see com.oracle.graal.python.builtins.objects.module.ModuleBuiltins.ModuleGetattributeNode - */ - @Specialization - static Object doIt(VirtualFrame frame, Node inliningTarget, Object object, TruffleString key, Object type, - @Cached(value = "create(key)", inline = false) LookupAttributeInMRONode lookup, - @Cached GetObjectSlotsNode getDescrSlotsNode, - @Cached ReadAttributeFromModuleNode readAttributeOfModuleNode, - @Cached InlinedConditionProfile hasDescrProfile, - @Cached InlinedConditionProfile hasDescrGetProfile, - @Cached InlinedConditionProfile hasValueProfile, - @Cached CallSlotDescrGet.Lazy callSlotDescrGet, - @Cached ModuleBuiltins.LazyHandleGetattrExceptionNode handleException, - @Cached PRaiseNode raiseNode) { - assert hasNoGetAttr(type); - - PythonModule module = (PythonModule) object; - try { - Object descr = lookup.execute(type); - boolean hasDescr = hasDescrProfile.profile(inliningTarget, descr != PNone.NO_VALUE); - - TpSlot get = null; - boolean hasDescrGet = false; - boolean getValue = true; - if (hasDescr) { - var descrSlots = getDescrSlotsNode.execute(inliningTarget, descr); - get = descrSlots.tp_descr_get(); - hasDescrGet = hasDescrGetProfile.profile(inliningTarget, get != null); - if (hasDescrGet && TpSlotDescrSet.PyDescr_IsData(descrSlots)) { - // fall through to callSlotDescrGet below to avoid duplicating the call site - getValue = false; - } - } - - if (getValue) { - Object value = readAttributeOfModuleNode.execute(module, key); - if (hasValueProfile.profile(inliningTarget, value != PNone.NO_VALUE)) { - return value; - } - } - - if (hasDescr) { - if (hasDescrGet) { - return callSlotDescrGet.get(inliningTarget).execute(frame, inliningTarget, get, descr, module, type); - } else { - return descr; - } - } - - throw raiseNode.raiseAttributeError(inliningTarget, ErrorMessages.OBJ_P_HAS_NO_ATTR_S, module, key); - } catch (PException e) { - return handleException(frame, inliningTarget, module, key, e, handleException); - } - } - - @InliningCutoff - private static Object handleException(VirtualFrame frame, Node inliningTarget, PythonModule object, TruffleString key, PException e, - ModuleBuiltins.LazyHandleGetattrExceptionNode handleException) { - return handleException.get(inliningTarget).execute(frame, object, key, e); - } -} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedObjectAttributeNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedObjectAttributeNode.java deleted file mode 100644 index e062864482..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedObjectAttributeNode.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 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.attributes; - -import static com.oracle.graal.python.nodes.attributes.MergedObjectTypeModuleGetFixedAttributeNode.hasNoGetAttr; - -import com.oracle.graal.python.builtins.objects.PNone; -import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlot; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrGet.CallSlotDescrGet; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrSet; -import com.oracle.graal.python.nodes.ErrorMessages; -import com.oracle.graal.python.nodes.PNodeWithContext; -import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.GenerateCached; -import com.oracle.truffle.api.dsl.GenerateInline; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.profiles.InlinedConditionProfile; -import com.oracle.truffle.api.strings.TruffleString; - -@GenerateInline -@GenerateCached -public abstract class GetFixedObjectAttributeNode extends PNodeWithContext { - - public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object object, TruffleString key, Object type); - - /** - * @see com.oracle.graal.python.builtins.objects.object.ObjectBuiltins.GetAttributeNode - */ - @Specialization - static Object doIt(VirtualFrame frame, Node inliningTarget, Object object, TruffleString key, Object type, - @Cached(value = "create(key)", inline = false) LookupAttributeInMRONode lookup, - @Cached GetObjectSlotsNode getDescrSlotsNode, - @Cached ReadAttributeFromObjectNode readAttributeOfObjectNode, - @Cached InlinedConditionProfile hasDescrProfile, - @Cached InlinedConditionProfile hasDescrGetProfile, - @Cached InlinedConditionProfile hasValueProfile, - @Cached CallSlotDescrGet.Lazy callSlotDescrGet, - @Cached PRaiseNode raiseNode) { - assert hasNoGetAttr(type); - - Object descr = lookup.execute(type); - boolean hasDescr = hasDescrProfile.profile(inliningTarget, descr != PNone.NO_VALUE); - - TpSlot get = null; - boolean hasDescrGet = false; - boolean getValue = true; - if (hasDescr) { - var descrSlots = getDescrSlotsNode.execute(inliningTarget, descr); - get = descrSlots.tp_descr_get(); - hasDescrGet = hasDescrGetProfile.profile(inliningTarget, get != null); - if (hasDescrGet && TpSlotDescrSet.PyDescr_IsData(descrSlots)) { - // fall through to callSlotDescrGet below to avoid duplicating the call site - getValue = false; - } - } - - if (getValue) { - Object value = readAttributeOfObjectNode.execute(object, key); - if (hasValueProfile.profile(inliningTarget, value != PNone.NO_VALUE)) { - return value; - } - } - - if (hasDescr) { - if (hasDescrGet) { - return callSlotDescrGet.get(inliningTarget).execute(frame, inliningTarget, get, descr, object, type); - } else { - return descr; - } - } - - throw raiseNode.raiseAttributeError(inliningTarget, ErrorMessages.OBJ_P_HAS_NO_ATTR_S, object, key); - } -} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedTypeAttributeNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedTypeAttributeNode.java deleted file mode 100644 index bc371425e0..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedTypeAttributeNode.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 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.attributes; - -import static com.oracle.graal.python.nodes.attributes.MergedObjectTypeModuleGetFixedAttributeNode.hasNoGetAttr; - -import com.oracle.graal.python.builtins.objects.PNone; -import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlot; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrGet.CallSlotDescrGet; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrSet; -import com.oracle.graal.python.nodes.ErrorMessages; -import com.oracle.graal.python.nodes.PNodeWithContext; -import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.GenerateCached; -import com.oracle.truffle.api.dsl.GenerateInline; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.profiles.InlinedBranchProfile; -import com.oracle.truffle.api.profiles.InlinedConditionProfile; -import com.oracle.truffle.api.strings.TruffleString; - -@GenerateInline -@GenerateCached -public abstract class GetFixedTypeAttributeNode extends PNodeWithContext { - - public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object object, TruffleString key, Object type); - - /** - * @see com.oracle.graal.python.builtins.objects.module.ModuleBuiltins.ModuleGetattributeNode - */ - @Specialization - static Object doIt(VirtualFrame frame, Node inliningTarget, Object object, TruffleString key, Object type, - @Cached(value = "create(key)", inline = false) LookupAttributeInMRONode lookup, - @Cached(value = "create(key)", inline = false) LookupAttributeInMRONode readAttributeOfClassNode, - @Cached GetObjectSlotsNode getDescrSlotsNode, - @Cached GetObjectSlotsNode getValueSlotsNode, - @Cached InlinedConditionProfile hasDescrProfile, - @Cached InlinedConditionProfile hasDescrGetProfile, - @Cached InlinedConditionProfile hasValueProfile, - @Cached InlinedBranchProfile hasNonDescriptorValueProfile, - @Cached CallSlotDescrGet.Lazy callSlotDescrGet, - @Cached CallSlotDescrGet.Lazy callSlotValueGet, - @Cached PRaiseNode raiseNode) { - assert hasNoGetAttr(type); - - Object descr = lookup.execute(type); - boolean hasDescr = hasDescrProfile.profile(inliningTarget, descr != PNone.NO_VALUE); - - TpSlot get = null; - boolean hasDescrGet = false; - boolean getValue = true; - if (hasDescr) { - var descrSlots = getDescrSlotsNode.execute(inliningTarget, descr); - get = descrSlots.tp_descr_get(); - hasDescrGet = hasDescrGetProfile.profile(inliningTarget, get != null); - if (hasDescrGet && TpSlotDescrSet.PyDescr_IsData(descrSlots)) { - // fall through to callSlotDescrGet below to avoid duplicating the call site - getValue = false; - } - } - - if (getValue) { - Object value = readAttributeOfClassNode.execute(object); - if (hasValueProfile.profile(inliningTarget, value != PNone.NO_VALUE)) { - var valueGet = getValueSlotsNode.execute(inliningTarget, value).tp_descr_get(); - if (valueGet == null) { - hasNonDescriptorValueProfile.enter(inliningTarget); - return value; - } else { - return callSlotValueGet.get(inliningTarget).execute(frame, inliningTarget, valueGet, value, PNone.NO_VALUE, object); - } - } - } - - if (hasDescr) { - if (hasDescrGet) { - return callSlotDescrGet.get(inliningTarget).execute(frame, inliningTarget, get, descr, object, type); - } else { - return descr; - } - } - - throw raiseNode.raiseAttributeError(inliningTarget, ErrorMessages.TYPE_N_HAS_NO_ATTR, object, key); - } -} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromModuleNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromModuleNode.java index 522ef0f9fd..876b114323 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromModuleNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromModuleNode.java @@ -76,6 +76,7 @@ static Object readModuleAttribute(PythonModule object, TruffleString key, @Cached GetDictIfExistsNode getDict, @Cached HashingStorageGetItemStringKey getItem) { var dict = getDict.execute(object); + assert object.checkDictFlags(dict); Object value = getItem.execute(inliningTarget, dict.getDictStorage(), key); if (value == null) { return PNone.NO_VALUE; 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 5c1a3f8c1a..07bc762e5e 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 @@ -91,6 +91,7 @@ static Object readObjectAttribute(PythonObject object, TruffleString key, @Shared @Cached(inline = true) ReadAttributeFromPythonObjectNode readAttributeFromPythonObjectNode, @Shared @Cached HashingStorageGetItemStringKey getItem) { var dict = getDict.execute(object); + assert object.checkDictFlags(dict); if (profileHasDict.profile(inliningTarget, dict == null)) { return readAttributeFromPythonObjectNode.execute(inliningTarget, object, key); } else { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/WriteAttributeToObjectNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/WriteAttributeToObjectNode.java index 9ab937a7e4..7a95566f8d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/WriteAttributeToObjectNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/WriteAttributeToObjectNode.java @@ -103,6 +103,7 @@ static boolean writeToDynamicStorageNoTypeGuard(PythonObject obj, GetDictIfExist static boolean writeToDynamicStorageNoType(PythonObject object, TruffleString key, Object value, @SuppressWarnings("unused") @Shared("getDict") @Cached GetDictIfExistsNode getDict, @Cached WriteAttributeToPythonObjectNode writeNode) { + assert object.checkDictFlags(null); // Objects w/o dict that are not classes do not have any special handling writeNode.execute(object, key, value); return true; @@ -137,6 +138,7 @@ static boolean writeToDynamicStoragePythonClass(PythonClass klass, TruffleString @Exclusive @Cached InlinedBranchProfile updateFlags, @Cached DynamicObject.PutNode putNode, @Cached DynamicObject.SetShapeFlagsNode setShapeFlagsNode) { + assert klass.checkDictFlags(null); if (value == PNone.NO_VALUE) { updateFlags.enter(inliningTarget); setShapeFlagsNode.executeAdd(klass, HAS_NO_VALUE_PROPERTIES); @@ -162,6 +164,7 @@ static boolean writeToDictNoType(@SuppressWarnings("unused") PythonObject object @Bind("getDict.execute(object)") PDict dict, @Shared("updateStorage") @Cached InlinedBranchProfile updateStorage, @Shared("setHashingStorageItem") @Cached HashingStorageSetItem setHashingStorageItem) { + assert object.checkDictFlags(dict); return writeToDict(dict, key, value, inliningTarget, updateStorage, setHashingStorageItem); } @@ -172,6 +175,7 @@ static boolean writeToDictClass(PythonClass klass, TruffleString key, Object val @Bind("getDict.execute(klass)") PDict dict, @Shared("updateStorage") @Cached InlinedBranchProfile updateStorage, @Shared("setHashingStorageItem") @Cached HashingStorageSetItem setHashingStorageItem) { + assert klass.checkDictFlags(dict); return writeToDictManagedClass(klass, dict, key, value, inliningTarget, updateStorage, setHashingStorageItem); } @@ -182,6 +186,7 @@ static boolean deleteFromPythonObject(PythonObject obj, TruffleString key, Objec @SuppressWarnings("unused") @Shared("getDict") @Cached GetDictIfExistsNode getDict, @Bind("getDict.execute(obj)") PDict dict, @Cached HashingStorageNodes.HashingStorageDelItem hashingStorageDelItem) { + assert obj.checkDictFlags(dict); try { HashingStorage dictStorage = dict.getDictStorage(); return hashingStorageDelItem.execute(inliningTarget, dictStorage, key, dict); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java index 66528e9da1..3f5f77ca7c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java @@ -104,15 +104,17 @@ import com.oracle.graal.python.builtins.objects.iterator.PObjectSequenceIterator; import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.module.ModuleBuiltins; +import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; +import com.oracle.graal.python.builtins.objects.object.PythonObject; import com.oracle.graal.python.builtins.objects.set.PFrozenSet; import com.oracle.graal.python.builtins.objects.set.PSet; import com.oracle.graal.python.builtins.objects.set.SetNodes; import com.oracle.graal.python.builtins.objects.tuple.PTuple; +import com.oracle.graal.python.builtins.objects.type.PythonManagedClass; import com.oracle.graal.python.builtins.objects.type.TpSlots; -import com.oracle.graal.python.builtins.objects.type.TpSlots.GetCachedTpSlotsNode; import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; -import com.oracle.graal.python.builtins.objects.type.TypeBuiltins; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrGet.CallSlotDescrGet; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotIterNext.CallSlotTpIterNextNode; import com.oracle.graal.python.builtins.objects.typing.PTypeAliasType; import com.oracle.graal.python.compiler.CodeUnit; @@ -183,10 +185,8 @@ import com.oracle.graal.python.nodes.argument.keywords.ExpandKeywordStarargsNode; import com.oracle.graal.python.nodes.argument.keywords.NonMappingException; import com.oracle.graal.python.nodes.argument.keywords.SameDictKeyException; -import com.oracle.graal.python.nodes.attributes.GetFixedModuleAttributeNode; -import com.oracle.graal.python.nodes.attributes.GetFixedObjectAttributeNode; -import com.oracle.graal.python.nodes.attributes.GetFixedTypeAttributeNode; -import com.oracle.graal.python.nodes.attributes.MergedObjectTypeModuleGetFixedAttributeNode; +import com.oracle.graal.python.nodes.attributes.GetFixedAttributeNode; +import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode; import com.oracle.graal.python.nodes.attributes.ReadAttributeFromPythonObjectNode; import com.oracle.graal.python.nodes.builtins.ListNodes; import com.oracle.graal.python.nodes.bytecode.CopyDictWithoutKeysNode; @@ -250,6 +250,7 @@ import com.oracle.graal.python.runtime.sequence.storage.ObjectSequenceStorage; import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.graal.python.util.ArrayBuilder; +import com.oracle.graal.python.util.InlineWeakValueProfile; import com.oracle.graal.python.util.PythonUtils; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; @@ -303,6 +304,9 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Property; +import com.oracle.truffle.api.object.PropertyGetter; +import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; import com.oracle.truffle.api.source.Source; @@ -1663,80 +1667,153 @@ public static Object doIt(VirtualFrame frame, @Operation(storeBytecodeIndex = true) @ConstantOperand(type = TruffleString.class) + @ImportStatic({PGuards.class, TpSlots.class}) public static final class GetAttribute { - protected static boolean isObjectGetAttribute(TpSlots slots) { - return slots.tp_getattro() == ObjectBuiltins.SLOTS.tp_getattro(); + static PropertyGetter getPropertyGetterWithFinalAssumption(Shape shape, Object key) { + PropertyGetter getter = shape.makePropertyGetter(key); + if (getter != null) { + Property property = shape.getProperty(key); + if (property != null) { + property.getLocation().getFinalAssumption(); + } + } + return getter; + } + + // Builtin module object fast-path: we know there aren't any descriptors for other than + // dunder (__xxx__) names + public static Object loadModuleValue(PythonModule object, Shape cachedShape, PropertyGetter cachedPropertyGetter) { + // GetClass.GetPythonObjectClassNode would cache on the shape if it can, and read the + // dynamic type from there unless it observes objects where the type was changed. This + // is rare enough that we can pay the price of a useless read here. + Object type = cachedShape.getDynamicType(); + if (type != PythonBuiltinClassType.PythonModule) { + return null; + } + + assert object.checkDictFlags(); + if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = cachedPropertyGetter.get(object); + return value == PNone.NO_VALUE ? null : value; + } + + return null; } - protected static boolean isModuleGetAttribute(TpSlots slots) { - return slots.tp_getattro() == ModuleBuiltins.SLOTS.tp_getattro(); + @ForceQuickening + @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null", + "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") + static Object doModule(TruffleString key, PythonModule receiver, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter, + @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode, + @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, + @Bind("loadModuleValue(receiver, cachedShape, cachedPropertyGetter)") Object value) { + return value; } - protected static boolean isTypeGetAttribute(TpSlots slots) { - return slots.tp_getattro() == TypeBuiltins.SLOTS.tp_getattro(); + // For type instance field: for builtin type we know descriptors only have dunder names + // (__xxx__), so we can skip descriptor check + we need to check the __get__ (tp_descr_get) + // on the resulting value (this is common situation) + public static Object loadTypeInstanceValue(VirtualFrame frame, Node inliningTarget, PythonManagedClass object, GetObjectSlotsNode getValueSlotsNode, + CallSlotDescrGet callSlotDescrGet, Shape cachedShape, PropertyGetter cachedPropertyGetter, InlinedBranchProfile hasNonDescriptorValueProfile) { + Object type = cachedShape.getDynamicType(); + if (type != PythonBuiltinClassType.PythonClass) { + return null; + } + assert object.checkDictFlags(); + if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = cachedPropertyGetter.get(object); + if (value != PNone.NO_VALUE && value != null) { + var valueGet = getValueSlotsNode.execute(inliningTarget, value).tp_descr_get(); + if (valueGet == null) { + hasNonDescriptorValueProfile.enter(inliningTarget); + return value; + } else { + return callSlotDescrGet.execute(frame, inliningTarget, valueGet, value, PNone.NO_VALUE, object); + } + } + } + return null; } @ForceQuickening - @Specialization(guards = "isObjectGetAttribute(slots)", excludeForUncached = true) - public static Object doObject(VirtualFrame frame, - TruffleString name, - Object obj, - @Bind Node inliningTarget, - @Shared @Cached GetClassNode getClassNode, - @Bind("getClassNode.execute(inliningTarget, obj)") Object type, - @Shared @Cached GetCachedTpSlotsNode getSlotsNode, - @Bind("getSlotsNode.execute(inliningTarget, type)") TpSlots slots, - @Shared @Cached(inline = false) GetFixedObjectAttributeNode getObjectAttributeNode) { - return getObjectAttributeNode.execute(frame, inliningTarget, obj, name, type); + @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null", + "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") + static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass receiver, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter, + @Cached GetObjectSlotsNode getObjectSlotsNode, + @Cached CallSlotDescrGet callSlotDescrGet, + @Cached InlinedBranchProfile hasNonDescriptorValueProfile, + @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode, + @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, + @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, cachedShape, cachedPropertyGetter, hasNonDescriptorValueProfile)") Object value) { + return value; } - @ForceQuickening - @Specialization(guards = "isModuleGetAttribute(slots)", excludeForUncached = true) - public static Object doModule(VirtualFrame frame, - TruffleString name, - Object obj, - @Bind Node inliningTarget, - @Shared @Cached GetClassNode getClassNode, - @Bind("getClassNode.execute(inliningTarget, obj)") Object type, - @Shared @Cached GetCachedTpSlotsNode getSlotsNode, - @Bind("getSlotsNode.execute(inliningTarget, type)") TpSlots slots, - @Shared @Cached(inline = false) GetFixedModuleAttributeNode getModuleAttributeNode) { - return getModuleAttributeNode.execute(frame, inliningTarget, obj, name, type); + // Object instance field fast-path: for cases where there is no descriptor and it's just + // simple DOM property read + public static Object loadInstanceValue(Node inliningTarget, PythonObject object, LookupAttributeInMRONode getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter, + InlineWeakValueProfile slotsValueProfile) { + TpSlots slots; + Object type = cachedShape.getDynamicType(); + // If this path works out, PropertyGetter.accepts() guards on the shape. + // After PE the final dynamicType field should dominate the branch and PE should remove + // the slots branch it doesn't need. The + // PythonBuiltinClassType slots are final, so PE can use that, but PythonManagedClass + // slots are not, so we should probably profile? + if (type instanceof PythonBuiltinClassType pbct) { + slots = pbct.getSlots(); + } else if (type instanceof PythonManagedClass klass) { + slots = slotsValueProfile.execute(inliningTarget, klass.getTpSlots()); + } else { + return null; + } + // The next check will fold after PE if the pbct was constant, which is implied by the + // guard in getDesc + if (slots.tp_getattro() == ObjectBuiltins.SLOTS.tp_getattro() || + slots.tp_getattro() == ModuleBuiltins.SLOTS.tp_getattro()) { + Object descr = getDesc.execute(type); + if (descr == PNone.NO_VALUE) { + assert object.checkDictFlags(); + if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = cachedPropertyGetter.get(object); + // Note: the NO_VALUE check is harmless for PE, because it leads to a deopt + // anyway + return value == PNone.NO_VALUE ? null : value; + } + } + } + return null; } @ForceQuickening - @Specialization(guards = "isTypeGetAttribute(slots)", excludeForUncached = true) - public static Object doType(VirtualFrame frame, - TruffleString name, - Object obj, + @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null"}, replaces = "doModule", limit = "3") + static Object doInstanceValue(TruffleString key, PythonObject receiver, @Bind Node inliningTarget, - @Shared @Cached GetClassNode getClassNode, - @Bind("getClassNode.execute(inliningTarget, obj)") Object type, - @Shared @Cached GetCachedTpSlotsNode getSlotsNode, - @Bind("getSlotsNode.execute(inliningTarget, type)") TpSlots slots, - @Shared @Cached(inline = false) GetFixedTypeAttributeNode getTypeAttributeNode) { - return getTypeAttributeNode.execute(frame, inliningTarget, obj, name, type); + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter, + @Cached("create(key)") LookupAttributeInMRONode getDesc, + @Cached InlineWeakValueProfile slotsValueProfile, + @Bind("loadInstanceValue(inliningTarget, receiver, getDesc, cachedShape, cachedPropertyGetter, slotsValueProfile)") Object value) { + return value; } - @Specialization(replaces = {"doObject", "doModule", "doType"}, excludeForUncached = true) + @Specialization(excludeForUncached = true, replaces = {"doInstanceValue", "doType"}) public static Object doIt(VirtualFrame frame, - TruffleString name, + TruffleString key, Object obj, - @Bind Node inliningTarget, - @Shared @Cached GetClassNode getClassNode, - @Shared @Cached GetCachedTpSlotsNode getSlotsNode, - @Shared @Cached(inline = false) MergedObjectTypeModuleGetFixedAttributeNode getAttributeNode) { - Object type = getClassNode.execute(inliningTarget, obj); - TpSlots slots = getSlotsNode.execute(inliningTarget, type); - return getAttributeNode.execute(frame, inliningTarget, obj, name, type, slots); + @Cached("create(key)") GetFixedAttributeNode getAttributeNode) { + return getAttributeNode.execute(frame, obj); } @Specialization(replaces = "doIt") @InliningCutoff - public static Object doItUncached(VirtualFrame frame, TruffleString name, Object obj, - @Bind Node inliningTarget, + public static Object doItUncached(VirtualFrame frame, TruffleString key, Object obj, + @Bind Node inliningTargetForDummy, @Cached PyObjectGetAttr dummyToForceStoreBCI) { - return PyObjectGetAttr.getUncached().execute(frame, inliningTarget, obj, name); + return PyObjectGetAttr.getUncached().execute(frame, null, obj, key); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetClassNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetClassNode.java index f4c91a272c..67f37f8f5d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetClassNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetClassNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -50,6 +50,7 @@ import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes; import com.oracle.graal.python.builtins.objects.ellipsis.PEllipsis; 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.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.dsl.Cached; @@ -147,12 +148,14 @@ public static Object executeUncached(PythonObject object) { public abstract Object execute(Node inliningTarget, PythonAbstractNativeObject object); + // The dynamicType field is final, so the DSL shouldn't generate a runtime guard since we + // cache on the shape @Idempotent - static Object getDynamicType(Shape shape) { - return shape.getDynamicType(); + static boolean dynamicTypeIsPythonClass(Shape shape) { + return PGuards.isPythonClass(shape.getDynamicType()); } - @Specialization(guards = {"object.getShape() == cachedShape", "isPythonClass(getDynamicType(cachedShape))"}, limit = "1") + @Specialization(guards = {"object.getShape() == cachedShape", "dynamicTypeIsPythonClass(cachedShape)"}, limit = "1") static Object doConstantClass(@SuppressWarnings("unused") PythonObject object, @Cached(value = "object.getShape()") Shape cachedShape) { return cachedShape.getDynamicType(); 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 fc77df4d78..6f60853cb1 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 @@ -103,7 +103,7 @@ static PDict getNoDict(@SuppressWarnings("unused") PythonObject object) { @Idempotent protected static boolean hasNoDict(Shape shape) { - return (shape.getFlags() & PythonObject.HAS_MATERIALIZED_DICT) == 0; + return (shape.getFlags() & PythonObject.HAS_DICT) == 0; } @Specialization(guards = {"isSingleContext()", "object == cached", "dictIsConstant(cached)", "dict != null"}, limit = "1")