From 535114d07842b9f789908ae31a4efc9b31ed2263 Mon Sep 17 00:00:00 2001 From: stepan Date: Wed, 22 Apr 2026 19:18:24 +0200 Subject: [PATCH 1/7] Interpreter optimizations for attribute lookup Co-authored-by: Tim Felgentreff --- .../builtins/PythonBuiltinClassType.java | 5 +- .../objects/common/HashingStorageNodes.java | 11 +- .../builtins/objects/object/PythonObject.java | 34 +++- .../objects/type/PythonManagedClass.java | 11 +- .../python/builtins/objects/type/TpSlots.java | 13 +- .../oracle/graal/python/nodes/HiddenAttr.java | 15 +- .../ReadAttributeFromModuleNode.java | 1 + .../ReadAttributeFromObjectNode.java | 1 + .../WriteAttributeToObjectNode.java | 5 + .../bytecode_dsl/PBytecodeDSLRootNode.java | 189 ++++++++++++------ .../python/nodes/object/GetClassNode.java | 11 +- .../nodes/object/GetDictIfExistsNode.java | 2 +- .../util/DynamicObjectInternalAccessor.java | 113 +++++++++++ 13 files changed, 332 insertions(+), 79 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java 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/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..27d95ae702 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,26 +56,33 @@ 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; @SuppressWarnings("this-escape") // escapes in the assertion public PythonObject(Object pythonClass, Shape instanceShape) { super(instanceShape); - assert pythonClass != null; assert !PGuards.isPythonClass(getShape().getDynamicType()) || IsSameTypeNode.executeUncached(getShape().getDynamicType(), pythonClass) : getShape().getDynamicType() + " vs " + pythonClass; this.pythonClass = pythonClass; } @@ -82,6 +91,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 +112,7 @@ public final Object getPythonClass() { public final void setPythonClass(Object pythonClass) { assert getShape().getDynamicType() == PNone.NO_VALUE; + assert pythonClass instanceof PythonManagedClass; 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/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..5d2fe4184f 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.DynamicObjectInternalAccessor; import com.oracle.graal.python.util.PythonUtils; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; @@ -303,6 +304,8 @@ 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.Location; +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 +1666,150 @@ 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 Location getLocationWithFinalAssumption(Shape shape, Object key) { + return DynamicObjectInternalAccessor.getLocationWithFinalAssumption(shape, key); + } + + // 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, Location cachedLocation, boolean cachedShapeGuard) { + Shape shape = object.getShape(); + // 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 = shape.getDynamicType(); + if (type != PythonBuiltinClassType.PythonModule) { + return null; + } + + assert object.checkDictFlags(); + if ((shape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = DynamicObjectInternalAccessor.getLocationInternal(cachedLocation, object, cachedShape, cachedShapeGuard); + 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 = {"guard", "cachedLocation != null", "value != null", "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") + static Object doModule(TruffleString key, PythonModule receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("getLocationWithFinalAssumption(cachedShape, key)") Location cachedLocation, + @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode, + @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, + @Bind("loadModuleValue(receiver, cachedShape, cachedLocation, guard)") 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, Location cachedLocation, boolean cachedShapeGuard) { + Shape shape = object.getShape(); + Object type = shape.getDynamicType(); + if (type != PythonBuiltinClassType.PythonClass) { + return null; + } + assert object.checkDictFlags(); + if ((shape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = DynamicObjectInternalAccessor.getLocationInternal(cachedLocation, object, cachedShape, cachedShapeGuard); + if (value != PNone.NO_VALUE && value != null) { + var valueGet = getValueSlotsNode.execute(inliningTarget, value).tp_descr_get(); + if (valueGet == null) { + // TODO: 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 = {"guard", "cachedLocation != null", "value != null", "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") + static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("getLocationWithFinalAssumption(cachedShape, key)") Location cachedLocation, + @Cached GetObjectSlotsNode getObjectSlotsNode, + @Cached CallSlotDescrGet callSlotDescrGet, + @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode, + @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, + @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, cachedShape, cachedLocation, guard)") 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(PythonObject object, LookupAttributeInMRONode getDesc, Shape cachedShape, Location cachedLocation, boolean cachedShapeGuard) { + TpSlots slots; + Shape shape = object.getShape(); + Object type = shape.getDynamicType(); + // If this path works out, the DynamicObject.GetNode will read and cache the shape. + // After PE it should pull up the guard, and the final dynamicType field will dominate + // the branch and PE will 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) { + // TODO: InlineWeakValueProfile? + slots = 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 ((shape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = DynamicObjectInternalAccessor.getLocationInternal(cachedLocation, object, cachedShape, cachedShapeGuard); + // 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, - @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); + @Specialization(guards = {"guard", "cachedLocation != null", "value != null"}, replaces = "doModule", limit = "3") + static Object doInstanceValue(TruffleString key, PythonObject receiver, + @Bind("receiver.getShape()") Shape shape, + @Cached("shape") Shape cachedShape, + @Bind("shape == cachedShape") boolean guard, + @Cached("getLocationWithFinalAssumption(cachedShape, key)") Location cachedLocation, + @Cached("create(key)") LookupAttributeInMRONode getDesc, + @Bind("loadInstanceValue(receiver, getDesc, cachedShape, cachedLocation, guard)") 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") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java new file mode 100644 index 0000000000..824b33775a --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java @@ -0,0 +1,113 @@ +/* + * 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.util; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Location; +import com.oracle.truffle.api.object.Shape; + +import sun.misc.Unsafe; + +/** + * POC-only reflective bridge to Truffle object internals. + */ +public final class DynamicObjectInternalAccessor { + private static final Unsafe UNSAFE = PythonUtils.initUnsafe(); + private static final MethodHandles.Lookup TRUSTED_LOOKUP = getTrustedLookup(); + + private static final MethodHandle SHAPE_GET_LOCATION = unreflectExact(Shape.class, "getLocation", + MethodType.methodType(Location.class, Shape.class, Object.class), Object.class); + private static final MethodHandle LOCATION_GET_FINAL_ASSUMPTION_INTERNAL = unreflectExact(Location.class, "getFinalAssumptionInternal", + MethodType.methodType(Object.class, Location.class)); + private static final MethodHandle LOCATION_GET_INTERNAL = unreflectExact(Location.class, "getInternal", + MethodType.methodType(Object.class, Location.class, DynamicObject.class, Shape.class, boolean.class), DynamicObject.class, Shape.class, boolean.class); + + private DynamicObjectInternalAccessor() { + } + + public static Location getLocationWithFinalAssumption(Shape shape, Object key) { + try { + Location location = (Location) SHAPE_GET_LOCATION.invokeExact(shape, key); + if (location != null) { + Object ignored = LOCATION_GET_FINAL_ASSUMPTION_INTERNAL.invokeExact(location); + assert ignored != null; + } + return location; + } catch (Throwable e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw new RuntimeException("Failed to call Shape.getLocation/getFinalAssumptionInternal via MethodHandle", e); + } + } + + public static Object getLocationInternal(Location location, DynamicObject object, Shape shape, boolean guard) { + try { + return LOCATION_GET_INTERNAL.invokeExact(location, object, shape, guard); + } catch (Throwable e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw new RuntimeException("Failed to call Location.getInternal via MethodHandle", e); + } + } + + private static MethodHandle unreflectExact(Class owner, String name, MethodType type, Class... parameterTypes) { + try { + Method method = owner.getDeclaredMethod(name, parameterTypes); + return TRUSTED_LOOKUP.unreflect(method).asType(type); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to initialize MethodHandle access to " + owner.getName() + "." + name, e); + } + } + + private static MethodHandles.Lookup getTrustedLookup() { + try { + Field implLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + return (MethodHandles.Lookup) UNSAFE.getObject(UNSAFE.staticFieldBase(implLookup), UNSAFE.staticFieldOffset(implLookup)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to access MethodHandles.Lookup.IMPL_LOOKUP", e); + } + } +} From 243ec0ca9143ccc8048031cf3b1312ff5e7e2c82 Mon Sep 17 00:00:00 2001 From: stepan Date: Thu, 23 Apr 2026 12:12:37 +0200 Subject: [PATCH 2/7] Fix assertions in PythonObject --- .../graal/python/builtins/objects/object/PythonObject.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 27d95ae702..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 @@ -83,6 +83,7 @@ public class PythonObject extends PythonAbstractObject { @SuppressWarnings("this-escape") // escapes in the assertion public PythonObject(Object pythonClass, Shape instanceShape) { super(instanceShape); + assert pythonClass != null; assert !PGuards.isPythonClass(getShape().getDynamicType()) || IsSameTypeNode.executeUncached(getShape().getDynamicType(), pythonClass) : getShape().getDynamicType() + " vs " + pythonClass; this.pythonClass = pythonClass; } @@ -112,7 +113,7 @@ public final Object getPythonClass() { public final void setPythonClass(Object pythonClass) { assert getShape().getDynamicType() == PNone.NO_VALUE; - assert pythonClass instanceof PythonManagedClass; + assert PGuards.isPythonClass(pythonClass); this.pythonClass = pythonClass; } From f7af2baf92578ddac0fcb436d87ada487950f64c Mon Sep 17 00:00:00 2001 From: stepan Date: Thu, 23 Apr 2026 14:04:36 +0200 Subject: [PATCH 3/7] Properly maintain the HAS_DICT and HAS_MATERIALIZED_DICT flags --- .../python/builtins/objects/common/DynamicObjectStorage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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; From e42c9e5a955e56c7cc5825458eb1b43f6c7f25bf Mon Sep 17 00:00:00 2001 From: stepan Date: Thu, 23 Apr 2026 16:55:15 +0200 Subject: [PATCH 4/7] Use PropertyGetter instead of reaching to Truffle internals --- .../bytecode_dsl/PBytecodeDSLRootNode.java | 80 ++++++------- .../util/DynamicObjectInternalAccessor.java | 113 ------------------ 2 files changed, 40 insertions(+), 153 deletions(-) delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java 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 5d2fe4184f..d19e0e6a71 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 @@ -250,7 +250,6 @@ 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.DynamicObjectInternalAccessor; import com.oracle.graal.python.util.PythonUtils; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; @@ -304,7 +303,8 @@ 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.Location; +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; @@ -1668,25 +1668,31 @@ public static Object doIt(VirtualFrame frame, @ConstantOperand(type = TruffleString.class) @ImportStatic({PGuards.class, TpSlots.class}) public static final class GetAttribute { - static Location getLocationWithFinalAssumption(Shape shape, Object key) { - return DynamicObjectInternalAccessor.getLocationWithFinalAssumption(shape, key); + 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, Location cachedLocation, boolean cachedShapeGuard) { - Shape shape = object.getShape(); + 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 = shape.getDynamicType(); + Object type = cachedShape.getDynamicType(); if (type != PythonBuiltinClassType.PythonModule) { return null; } assert object.checkDictFlags(); - if ((shape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { - Object value = DynamicObjectInternalAccessor.getLocationInternal(cachedLocation, object, cachedShape, cachedShapeGuard); + if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { + Object value = cachedPropertyGetter.get(object); return value == PNone.NO_VALUE ? null : value; } @@ -1694,15 +1700,14 @@ public static Object loadModuleValue(PythonModule object, Shape cachedShape, Loc } @ForceQuickening - @Specialization(guards = {"guard", "cachedLocation != null", "value != null", "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") + @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null", + "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") static Object doModule(TruffleString key, PythonModule receiver, - @Bind("receiver.getShape()") Shape shape, - @Cached("shape") Shape cachedShape, - @Bind("shape == cachedShape") boolean guard, - @Cached("getLocationWithFinalAssumption(cachedShape, key)") Location cachedLocation, + @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, cachedLocation, guard)") Object value) { + @Bind("loadModuleValue(receiver, cachedShape, cachedPropertyGetter)") Object value) { return value; } @@ -1710,15 +1715,14 @@ static Object doModule(TruffleString key, PythonModule receiver, // (__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, Location cachedLocation, boolean cachedShapeGuard) { - Shape shape = object.getShape(); - Object type = shape.getDynamicType(); + CallSlotDescrGet callSlotDescrGet, Shape cachedShape, PropertyGetter cachedPropertyGetter) { + Object type = cachedShape.getDynamicType(); if (type != PythonBuiltinClassType.PythonClass) { return null; } assert object.checkDictFlags(); - if ((shape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { - Object value = DynamicObjectInternalAccessor.getLocationInternal(cachedLocation, object, cachedShape, cachedShapeGuard); + 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) { @@ -1733,29 +1737,27 @@ public static Object loadTypeInstanceValue(VirtualFrame frame, Node inliningTarg } @ForceQuickening - @Specialization(guards = {"guard", "cachedLocation != null", "value != null", "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") + @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null", + "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3") static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass receiver, - @Bind("receiver.getShape()") Shape shape, - @Cached("shape") Shape cachedShape, - @Bind("shape == cachedShape") boolean guard, - @Cached("getLocationWithFinalAssumption(cachedShape, key)") Location cachedLocation, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter, @Cached GetObjectSlotsNode getObjectSlotsNode, @Cached CallSlotDescrGet callSlotDescrGet, @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode, @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, - @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, cachedShape, cachedLocation, guard)") Object value) { + @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, cachedShape, cachedPropertyGetter)") Object value) { return value; } // Object instance field fast-path: for cases where there is no descriptor and it's just // simple DOM property read - public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRONode getDesc, Shape cachedShape, Location cachedLocation, boolean cachedShapeGuard) { + public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRONode getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter) { TpSlots slots; - Shape shape = object.getShape(); - Object type = shape.getDynamicType(); - // If this path works out, the DynamicObject.GetNode will read and cache the shape. - // After PE it should pull up the guard, and the final dynamicType field will dominate - // the branch and PE will remove the slots branch it doesn't need. The + 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) { @@ -1773,8 +1775,8 @@ public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRO Object descr = getDesc.execute(type); if (descr == PNone.NO_VALUE) { assert object.checkDictFlags(); - if ((shape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) { - Object value = DynamicObjectInternalAccessor.getLocationInternal(cachedLocation, object, cachedShape, cachedShapeGuard); + 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; @@ -1785,14 +1787,12 @@ public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRO } @ForceQuickening - @Specialization(guards = {"guard", "cachedLocation != null", "value != null"}, replaces = "doModule", limit = "3") + @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null"}, replaces = "doModule", limit = "3") static Object doInstanceValue(TruffleString key, PythonObject receiver, - @Bind("receiver.getShape()") Shape shape, - @Cached("shape") Shape cachedShape, - @Bind("shape == cachedShape") boolean guard, - @Cached("getLocationWithFinalAssumption(cachedShape, key)") Location cachedLocation, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter, @Cached("create(key)") LookupAttributeInMRONode getDesc, - @Bind("loadInstanceValue(receiver, getDesc, cachedShape, cachedLocation, guard)") Object value) { + @Bind("loadInstanceValue(receiver, getDesc, cachedShape, cachedPropertyGetter)") Object value) { return value; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java deleted file mode 100644 index 824b33775a..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/DynamicObjectInternalAccessor.java +++ /dev/null @@ -1,113 +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.util; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.api.object.Location; -import com.oracle.truffle.api.object.Shape; - -import sun.misc.Unsafe; - -/** - * POC-only reflective bridge to Truffle object internals. - */ -public final class DynamicObjectInternalAccessor { - private static final Unsafe UNSAFE = PythonUtils.initUnsafe(); - private static final MethodHandles.Lookup TRUSTED_LOOKUP = getTrustedLookup(); - - private static final MethodHandle SHAPE_GET_LOCATION = unreflectExact(Shape.class, "getLocation", - MethodType.methodType(Location.class, Shape.class, Object.class), Object.class); - private static final MethodHandle LOCATION_GET_FINAL_ASSUMPTION_INTERNAL = unreflectExact(Location.class, "getFinalAssumptionInternal", - MethodType.methodType(Object.class, Location.class)); - private static final MethodHandle LOCATION_GET_INTERNAL = unreflectExact(Location.class, "getInternal", - MethodType.methodType(Object.class, Location.class, DynamicObject.class, Shape.class, boolean.class), DynamicObject.class, Shape.class, boolean.class); - - private DynamicObjectInternalAccessor() { - } - - public static Location getLocationWithFinalAssumption(Shape shape, Object key) { - try { - Location location = (Location) SHAPE_GET_LOCATION.invokeExact(shape, key); - if (location != null) { - Object ignored = LOCATION_GET_FINAL_ASSUMPTION_INTERNAL.invokeExact(location); - assert ignored != null; - } - return location; - } catch (Throwable e) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw new RuntimeException("Failed to call Shape.getLocation/getFinalAssumptionInternal via MethodHandle", e); - } - } - - public static Object getLocationInternal(Location location, DynamicObject object, Shape shape, boolean guard) { - try { - return LOCATION_GET_INTERNAL.invokeExact(location, object, shape, guard); - } catch (Throwable e) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw new RuntimeException("Failed to call Location.getInternal via MethodHandle", e); - } - } - - private static MethodHandle unreflectExact(Class owner, String name, MethodType type, Class... parameterTypes) { - try { - Method method = owner.getDeclaredMethod(name, parameterTypes); - return TRUSTED_LOOKUP.unreflect(method).asType(type); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to initialize MethodHandle access to " + owner.getName() + "." + name, e); - } - } - - private static MethodHandles.Lookup getTrustedLookup() { - try { - Field implLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); - return (MethodHandles.Lookup) UNSAFE.getObject(UNSAFE.staticFieldBase(implLookup), UNSAFE.staticFieldOffset(implLookup)); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to access MethodHandles.Lookup.IMPL_LOOKUP", e); - } - } -} From 0e4fa21886304701ca13206c3d5c0dc64ccdabcf Mon Sep 17 00:00:00 2001 From: stepan Date: Fri, 24 Apr 2026 13:50:27 +0200 Subject: [PATCH 5/7] Remove now unused GetFixed{XYZ}AttributeNode classes --- .../GetFixedModuleAttributeNode.java | 132 ------------------ .../GetFixedObjectAttributeNode.java | 116 --------------- .../attributes/GetFixedTypeAttributeNode.java | 126 ----------------- 3 files changed, 374 deletions(-) delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedModuleAttributeNode.java delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedObjectAttributeNode.java delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/GetFixedTypeAttributeNode.java 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); - } -} From f5050c6891c48b19327c19f533e58c1a5e3d678a Mon Sep 17 00:00:00 2001 From: stepan Date: Mon, 27 Apr 2026 13:55:40 +0200 Subject: [PATCH 6/7] Add InlineWeakValueProfile for slots in GetAttribute bytecode fast-path --- .../nodes/bytecode_dsl/PBytecodeDSLRootNode.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 d19e0e6a71..ceb729005d 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 @@ -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; @@ -1752,7 +1753,8 @@ static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass r // Object instance field fast-path: for cases where there is no descriptor and it's just // simple DOM property read - public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRONode getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter) { + 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. @@ -1763,8 +1765,7 @@ public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRO if (type instanceof PythonBuiltinClassType pbct) { slots = pbct.getSlots(); } else if (type instanceof PythonManagedClass klass) { - // TODO: InlineWeakValueProfile? - slots = klass.getTpSlots(); + slots = slotsValueProfile.execute(inliningTarget, klass.getTpSlots()); } else { return null; } @@ -1789,10 +1790,12 @@ public static Object loadInstanceValue(PythonObject object, LookupAttributeInMRO @ForceQuickening @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null"}, replaces = "doModule", limit = "3") static Object doInstanceValue(TruffleString key, PythonObject receiver, + @Bind Node inliningTarget, @Cached("receiver.getShape()") Shape cachedShape, @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter, @Cached("create(key)") LookupAttributeInMRONode getDesc, - @Bind("loadInstanceValue(receiver, getDesc, cachedShape, cachedPropertyGetter)") Object value) { + @Cached InlineWeakValueProfile slotsValueProfile, + @Bind("loadInstanceValue(inliningTarget, receiver, getDesc, cachedShape, cachedPropertyGetter, slotsValueProfile)") Object value) { return value; } From 80c58414d2e59741395a0e31856028e1c20351b1 Mon Sep 17 00:00:00 2001 From: stepan Date: Mon, 27 Apr 2026 14:37:54 +0200 Subject: [PATCH 7/7] Add InlinedBranchProfile for 'no descriptor' branch in GetAttribute bytecode fast-path --- .../python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 ceb729005d..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 @@ -1716,7 +1716,7 @@ static Object doModule(TruffleString key, PythonModule receiver, // (__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) { + CallSlotDescrGet callSlotDescrGet, Shape cachedShape, PropertyGetter cachedPropertyGetter, InlinedBranchProfile hasNonDescriptorValueProfile) { Object type = cachedShape.getDynamicType(); if (type != PythonBuiltinClassType.PythonClass) { return null; @@ -1727,7 +1727,7 @@ public static Object loadTypeInstanceValue(VirtualFrame frame, Node inliningTarg if (value != PNone.NO_VALUE && value != null) { var valueGet = getValueSlotsNode.execute(inliningTarget, value).tp_descr_get(); if (valueGet == null) { - // TODO: hasNonDescriptorValueProfile.enter(inliningTarget); + hasNonDescriptorValueProfile.enter(inliningTarget); return value; } else { return callSlotDescrGet.execute(frame, inliningTarget, valueGet, value, PNone.NO_VALUE, object); @@ -1745,9 +1745,10 @@ static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass r @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)") Object value) { + @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, cachedShape, cachedPropertyGetter, hasNonDescriptorValueProfile)") Object value) { return value; }