From 66818f56098d7b250e6a810725ff0f6174393c5d Mon Sep 17 00:00:00 2001 From: stepan Date: Wed, 15 Jul 2026 17:47:25 +0200 Subject: [PATCH] Quicken attribute reads for int return value --- .../bytecode_dsl/PBytecodeDSLRootNode.java | 149 ++++++++++++++---- 1 file changed, 120 insertions(+), 29 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 3158683f99..715298a66b 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 @@ -315,6 +315,8 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.nodes.SlowPathException; +import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.object.PropertyGetter; import com.oracle.truffle.api.object.Shape; @@ -2015,43 +2017,82 @@ public static boolean canBeSpecialMethod(TruffleString key) { return TpSlots.canBeSpecialMethod(key, CodePointLengthNode.getUncached(), CodePointAtIndexUTF32Node.getUncached()); } - public static Object getValue(PropertyGetter getter, PythonObject obj) { + public static Object getValue(PropertyGetter getter, PythonObject obj) throws FastPathBailoutException { assert obj.checkDictFlags(); - return getter.get(obj); + Object value = getter.get(obj); + if (value == PNone.NO_VALUE) { + throw FastPathBailoutException.INSTANCE; + } + return value; + } + + public static int getIntValue(PropertyGetter getter, PythonObject obj) throws FastPathBailoutException, UnexpectedResultException { + assert obj.checkDictFlags(); + try { + return getter.getInt(obj); + } catch (UnexpectedResultException e) { + if (e.getResult() == PNone.NO_VALUE) { + throw FastPathBailoutException.INSTANCE; + } + throw e; + } + } + + public static final class FastPathBailoutException extends SlowPathException { + private static final long serialVersionUID = 8275772171120097556L; + private static final FastPathBailoutException INSTANCE = new FastPathBailoutException(); + + private FastPathBailoutException() { + } + } + + @Specialization(guards = { + "!canBeSpecialMethod(cachedKey)", // + "!hasMaterializedDict(cachedShape)", "isBuiltinModule(cachedShape)", // + "getter != null", "getter.accepts(receiver)"}, // + rewriteOn = {FastPathBailoutException.class, UnexpectedResultException.class}, limit = "3") + static int doModuleInt(TruffleString key, PythonModule receiver, + @Cached("key") TruffleString cachedKey, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter) throws FastPathBailoutException, UnexpectedResultException { + assert key == cachedKey; // should be a constant operand + return getIntValue(getter, receiver); } @ForceQuickening @Specialization(guards = { "!canBeSpecialMethod(cachedKey)", // "!hasMaterializedDict(cachedShape)", "isBuiltinModule(cachedShape)", // - "getter != null", "getter.accepts(receiver)", // - "!isNoValue(value)"}, limit = "3") + "getter != null", "getter.accepts(receiver)"}, // + replaces = "doModuleInt", rewriteOn = FastPathBailoutException.class, limit = "3") static Object doModule(TruffleString key, PythonModule receiver, @Cached("key") TruffleString cachedKey, @Cached("receiver.getShape()") Shape cachedShape, - @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter, - @Bind("getValue(getter, receiver)") Object value) { + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter) throws FastPathBailoutException { assert key == cachedKey; // should be a constant operand - return value; + return getValue(getter, receiver); } // 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, PropertyGetter cachedPropertyGetter, InlinedBranchProfile hasNonDescriptorValueProfile) { + CallSlotDescrGet callSlotDescrGet, PropertyGetter cachedPropertyGetter, InlinedBranchProfile hasNonDescriptorValueProfile) throws FastPathBailoutException { assert object.checkDictFlags(); Object value = cachedPropertyGetter.get(object); - if (value != PNone.NO_VALUE) { + 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); + Object result = callSlotDescrGet.execute(frame, inliningTarget, valueGet, value, PNone.NO_VALUE, object); + if (result != null) { + return result; + } } } - return null; + throw FastPathBailoutException.INSTANCE; } @Idempotent @@ -2059,38 +2100,74 @@ public static boolean isBuiltinType(Shape cachedShape) { return cachedShape.getDynamicType() == PythonBuiltinClassType.PythonClass; } + @Specialization(guards = { + "!canBeSpecialMethod(cachedKey)", // + "!hasMaterializedDict(cachedShape)", "isBuiltinType(cachedShape)", // + "getter != null", "getter.accepts(receiver)"}, // + rewriteOn = {FastPathBailoutException.class, UnexpectedResultException.class}, limit = "3") + static int doTypeInt(@SuppressWarnings("unused") VirtualFrame frame, TruffleString key, PythonManagedClass receiver, + @Bind Node inliningTarget, + @Cached("key") TruffleString cachedKey, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter, + @SuppressWarnings("unused") @Shared("typeGetObjectSlots") @Cached GetObjectSlotsNode getObjectSlotsNode, + @SuppressWarnings("unused") @Shared("typeCallSlotDescrGet") @Cached CallSlotDescrGet callSlotDescrGet, + @SuppressWarnings("unused") @Shared("typeNonDescriptorProfile") @Cached InlinedBranchProfile hasNonDescriptorValueProfile) throws FastPathBailoutException, + UnexpectedResultException { + assert key == cachedKey; // should be a constant operand + // A primitive int cannot itself be a descriptor, so the direct field value is final. + return getIntValue(getter, receiver); + } + @ForceQuickening @StoreBytecodeIndex // we may be calling the descriptor @Specialization(guards = { "!canBeSpecialMethod(cachedKey)", // "!hasMaterializedDict(cachedShape)", "isBuiltinType(cachedShape)", // - "getter != null", "getter.accepts(receiver)", // - "value != null"}, limit = "3") + "getter != null", "getter.accepts(receiver)"}, // + replaces = "doTypeInt", rewriteOn = FastPathBailoutException.class, limit = "3") static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass receiver, + @Bind Node inliningTarget, @Cached("key") TruffleString cachedKey, @Cached("receiver.getShape()") Shape cachedShape, @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter, - @Cached GetObjectSlotsNode getObjectSlotsNode, - @Cached CallSlotDescrGet callSlotDescrGet, - @Cached InlinedBranchProfile hasNonDescriptorValueProfile, - @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, getter, hasNonDescriptorValueProfile)") Object value) { + @Shared("typeGetObjectSlots") @Cached GetObjectSlotsNode getObjectSlotsNode, + @Shared("typeCallSlotDescrGet") @Cached CallSlotDescrGet callSlotDescrGet, + @Shared("typeNonDescriptorProfile") @Cached InlinedBranchProfile hasNonDescriptorValueProfile) throws FastPathBailoutException { assert key == cachedKey; // should be a constant operand - return value; + return loadTypeInstanceValue(frame, inliningTarget, receiver, getObjectSlotsNode, callSlotDescrGet, getter, hasNonDescriptorValueProfile); } // The convention is that if klass is null, then the object is assumed to be of a builtin type that has the object's or module's tp_getattro - we do not need to recheck that dynamically public static Object loadInstanceValue(Node inliningTarget, PythonObject object, PythonManagedClass klass, TruffleString key, LookupAttributeInMRONode.CachedKeyFastPath getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter, - InlineWeakValueProfile slotsValueProfile) { + InlineWeakValueProfile slotsValueProfile) throws FastPathBailoutException { + checkCanLoadInstanceValue(inliningTarget, object, klass, key, getDesc, cachedShape, slotsValueProfile); + Object value = cachedPropertyGetter.get(object); + if (value == PNone.NO_VALUE) { + throw FastPathBailoutException.INSTANCE; + } + return value; + } + + public static int loadInstanceValueInt(Node inliningTarget, PythonObject object, PythonManagedClass klass, + TruffleString key, LookupAttributeInMRONode.CachedKeyFastPath getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter, + InlineWeakValueProfile slotsValueProfile) throws FastPathBailoutException, UnexpectedResultException { + checkCanLoadInstanceValue(inliningTarget, object, klass, key, getDesc, cachedShape, slotsValueProfile); + return getIntValue(cachedPropertyGetter, object); + } + + private static void checkCanLoadInstanceValue(Node inliningTarget, PythonObject object, PythonManagedClass klass, + TruffleString key, LookupAttributeInMRONode.CachedKeyFastPath getDesc, Shape cachedShape, + InlineWeakValueProfile slotsValueProfile) throws FastPathBailoutException { if (klass == null || hasObjectOrModuleGetattro(inliningTarget, klass, slotsValueProfile)) { Object descr = getDesc.execute(inliningTarget, cachedShape.getDynamicType(), key); if (descr == PNone.NO_VALUE) { assert object.checkDictFlags(); - Object value = cachedPropertyGetter.get(object); - return value == PNone.NO_VALUE ? null : value; + return; } } - return null; + throw FastPathBailoutException.INSTANCE; } private static boolean hasObjectOrModuleGetattro(Node inliningTarget, PythonManagedClass klass, InlineWeakValueProfile slotsValueProfile) { @@ -2111,23 +2188,37 @@ public static boolean isBuiltinWithObjectOrModuleGetattro(Shape cachedShape) { return cachedShape.getDynamicType() instanceof PythonBuiltinClassType type && hasObjectOrModuleGetattro(type.getSlots()); } + @StoreBytecodeIndex // looking up attribute in MRO may have side effects + @Specialization(guards = { + "!hasMaterializedDict(cachedShape)", "managedClass != null || isBuiltinWithObjectOrModuleGetattro(cachedShape)", // + "getter != null", "getter.accepts(receiver)"}, // + rewriteOn = {FastPathBailoutException.class, UnexpectedResultException.class}, limit = "3") + static int doInstanceValueInt(TruffleString key, PythonObject receiver, + @Bind Node inliningTarget, + @Cached("receiver.getShape()") Shape cachedShape, + @Cached("getManagedClassOrNull(cachedShape)") PythonManagedClass managedClass, + @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter, + @Exclusive @Cached LookupAttributeInMRONode.CachedKeyFastPath getDesc, + @Exclusive @Cached InlineWeakValueProfile slotsValueProfile) throws FastPathBailoutException, UnexpectedResultException { + return loadInstanceValueInt(inliningTarget, receiver, managedClass, key, getDesc, cachedShape, getter, slotsValueProfile); + } + @ForceQuickening @Specialization(guards = { "!hasMaterializedDict(cachedShape)", "managedClass != null || isBuiltinWithObjectOrModuleGetattro(cachedShape)", // - "getter != null", "getter.accepts(receiver)", // - "value != null"}, replaces = "doModule", limit = "3") + "getter != null", "getter.accepts(receiver)"}, // + replaces = "doInstanceValueInt", rewriteOn = FastPathBailoutException.class, limit = "3") static Object doInstanceValue(TruffleString key, PythonObject receiver, @Bind Node inliningTarget, @Cached("receiver.getShape()") Shape cachedShape, @Cached("getManagedClassOrNull(cachedShape)") PythonManagedClass managedClass, @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter, - @Cached LookupAttributeInMRONode.CachedKeyFastPath getDesc, - @Cached InlineWeakValueProfile slotsValueProfile, - @Bind("loadInstanceValue(inliningTarget, receiver, managedClass, key, getDesc, cachedShape, getter, slotsValueProfile)") Object value) { - return value; + @Exclusive @Cached LookupAttributeInMRONode.CachedKeyFastPath getDesc, + @Exclusive @Cached InlineWeakValueProfile slotsValueProfile) throws FastPathBailoutException { + return loadInstanceValue(inliningTarget, receiver, managedClass, key, getDesc, cachedShape, getter, slotsValueProfile); } - @Specialization(excludeForUncached = true, replaces = {"doInstanceValue", "doType"}) + @Specialization(excludeForUncached = true, replaces = {"doModule", "doInstanceValue", "doType"}) @StoreBytecodeIndex public static Object doIt(VirtualFrame frame, TruffleString key,