From 81e8491c15d4d5ea65e076315c23c5e2532d9871 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 2 Jun 2026 14:14:58 +0200 Subject: [PATCH 1/7] Handle native tuples in exception paths --- .../exception/BaseExceptionGroupBuiltins.java | 15 ++++++- .../exception/PrepareExceptionNode.java | 18 +++++--- .../python/lib/PyObjectIsInstanceNode.java | 3 +- .../python/lib/PyObjectIsSubclassNode.java | 3 +- .../lib/PyObjectRecursiveBinaryCheckNode.java | 44 +++++++++++++++++++ .../python/lib/PyTupleCheckExactNode.java | 10 +++++ .../python/nodes/builtins/TupleNodes.java | 4 ++ .../bytecode_dsl/PBytecodeDSLRootNode.java | 7 ++- .../nodes/exception/ExceptMatchNode.java | 27 +++++++----- 9 files changed, 108 insertions(+), 23 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java index 151021bd0c..ca874691b0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java @@ -66,6 +66,7 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.dict.PDict; @@ -355,8 +356,8 @@ private static MatcherType getMatcherType(Node inliningTarget, Object value) { if (isExceptionTypeUncached(value)) { return MatcherType.BY_TYPE; } - if (value instanceof PTuple tuple && PyTupleCheckExactNode.executeUncached(tuple)) { - SequenceStorage storage = tuple.getSequenceStorage(); + SequenceStorage storage = getExactTupleStorage(value); + if (storage != null) { for (int i = 0; i < storage.length(); i++) { Object elem = SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, i); if (!isExceptionTypeUncached(elem)) { @@ -368,6 +369,16 @@ private static MatcherType getMatcherType(Node inliningTarget, Object value) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.EXPECTED_A_FUNCTION_EXCEPTION_TYPE_OR_TUPLE_OF_EXCEPTION_TYPES); } + private static SequenceStorage getExactTupleStorage(Object value) { + if (value instanceof PTuple tuple && PyTupleCheckExactNode.executeUncached(tuple)) { + return tuple.getSequenceStorage(); + } + if (value instanceof PythonAbstractNativeObject nativeTuple && PyTupleCheckExactNode.executeUncached(nativeTuple)) { + return TupleNodes.GetNativeTupleStorage.getUncached().execute(nativeTuple); + } + return null; + } + private static boolean isExceptionTypeUncached(Object value) { return TypeNodes.IsTypeNode.executeUncached(value) && BuiltinClassProfiles.IsBuiltinClassProfile.profileClassSlowPath(value, PythonBuiltinClassType.PBaseException); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/PrepareExceptionNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/PrepareExceptionNode.java index 6a481a4fca..dd31b7aca9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/PrepareExceptionNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/PrepareExceptionNode.java @@ -46,14 +46,15 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; -import com.oracle.graal.python.builtins.objects.common.SequenceNodes; -import com.oracle.graal.python.builtins.objects.tuple.PTuple; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsTypeNode; import com.oracle.graal.python.lib.PyExceptionInstanceCheckNode; import com.oracle.graal.python.lib.PyObjectIsInstanceNode; +import com.oracle.graal.python.lib.PyTupleCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage; import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; import com.oracle.graal.python.runtime.object.PFactory; @@ -96,11 +97,12 @@ static Object doException(@SuppressWarnings("unused") PBaseException exc, @Suppr throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.INSTANCE_EX_MAY_NOT_HAVE_SEP_VALUE); } - @Specialization(guards = {"isTypeNode.execute(inliningTarget, type)", "!isPNone(value)", "!isPTuple(value)"}, limit = "1") + @Specialization(guards = {"isTypeNode.execute(inliningTarget, type)", "!isPNone(value)", "!tupleCheck.execute(inliningTarget, value)"}, limit = "1") static Object doExceptionOrCreate(VirtualFrame frame, Object type, Object value, @Bind Node inliningTarget, @SuppressWarnings("unused") @Exclusive @Cached IsTypeNode isTypeNode, @Exclusive @Cached PyExceptionInstanceCheckNode check, + @SuppressWarnings("unused") @Exclusive @Cached PyTupleCheckNode tupleCheck, @Cached PyObjectIsInstanceNode isInstanceNode, @Cached InlinedConditionProfile isInstanceProfile, @Shared @Cached IsSubtypeNode isSubtypeNode, @@ -136,17 +138,19 @@ static Object doCreate(VirtualFrame frame, Object type, @SuppressWarnings("unuse } } - @Specialization(guards = "isTypeNode.execute(inliningTarget, type)", limit = "1") - static Object doCreateTuple(VirtualFrame frame, Object type, PTuple value, + @Specialization(guards = {"isTypeNode.execute(inliningTarget, type)", "tupleCheck.execute(inliningTarget, value)"}, limit = "1") + static Object doCreateTuple(VirtualFrame frame, Object type, Object value, @Bind Node inliningTarget, @SuppressWarnings("unused") @Exclusive @Cached IsTypeNode isTypeNode, @Exclusive @Cached PyExceptionInstanceCheckNode check, - @Cached SequenceNodes.GetObjectArrayNode getObjectArrayNode, + @SuppressWarnings("unused") @Exclusive @Cached PyTupleCheckNode tupleCheck, + @Exclusive @Cached GetTupleStorage getTupleStorage, + @Exclusive @Cached SequenceStorageNodes.ToArrayNode toArrayNode, @Shared @Cached IsSubtypeNode isSubtypeNode, @Exclusive @Cached PRaiseNode raiseNode, @Shared("callCtor") @Cached CallNode callConstructor) { checkExceptionClass(inliningTarget, type, isSubtypeNode, raiseNode); - Object[] args = getObjectArrayNode.execute(inliningTarget, value); + Object[] args = toArrayNode.execute(inliningTarget, getTupleStorage.execute(inliningTarget, value)); Object instance = callConstructor.execute(frame, type, args); if (check.execute(inliningTarget, instance)) { return instance; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java index df804279a6..a02f2e2860 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java @@ -87,9 +87,10 @@ public static PyObjectIsInstanceNode getUncached() { return PyObjectIsInstanceNodeGen.getUncached(); } - @Specialization(guards = "!isPTuple(cls)", insertBefore = "doTupleConstantLen") + @Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doTupleConstantLen", limit = "1") static boolean isInstance(VirtualFrame frame, Object instance, Object cls, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, + @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, @Cached GetClassNode getClsClassNode, @Cached IsBuiltinClassExactProfile classProfile, @Cached GetClassNode getInstanceClassNode, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java index fb9b43c1e2..7da5ac453f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java @@ -87,9 +87,10 @@ public static PyObjectIsSubclassNode getUncached() { return PyObjectIsSubclassNodeGen.getUncached(); } - @Specialization(guards = "!isPTuple(cls)", insertBefore = "doTupleConstantLen") + @Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doTupleConstantLen", limit = "1") static boolean isSubclass(VirtualFrame frame, Object derived, Object cls, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, + @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, @Cached GetClassNode getClsClassNode, @Cached IsBuiltinClassExactProfile classProfile, @Cached LookupSpecialMethodNode.Dynamic subclassCheckLookup, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java index 0731ba6ab8..6ed84c106d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java @@ -41,10 +41,12 @@ package com.oracle.graal.python.lib; import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.common.SequenceNodes.GetObjectArrayNode; import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; +import com.oracle.graal.python.nodes.builtins.TupleNodes.ConstructTupleNode; import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.PythonOptions; @@ -121,6 +123,18 @@ static boolean doRecursiveWithNode(VirtualFrame frame, Object arg, PTuple clsTup return loopRecursive(frame, arg, clsTuple, inliningTarget, getObjectArrayNode, recursiveNode, depth + 1); } + @Specialization(guards = {"depth < getNodeRecursionLimit(language)", "tupleCheck.execute(inliningTarget, clsTuple)"}, limit = "1", excludeForUncached = true) + static boolean doNativeTupleWithNode(VirtualFrame frame, Object arg, PythonAbstractNativeObject clsTuple, int depth, + @Bind Node inliningTarget, + @SuppressWarnings("unused") @Bind PythonLanguage language, + @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, + @Cached ConstructTupleNode constructTupleNode, + @Cached GetObjectArrayNode getObjectArrayNode, + @Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) { + PTuple tuple = constructTupleNode.execute(frame, clsTuple); + return loopRecursive(frame, arg, tuple, inliningTarget, getObjectArrayNode, recursiveNode, depth + 1); + } + @Specialization(guards = {"depth >= getNodeRecursionLimit(language)"}, excludeForUncached = true) boolean doRecursiveTransition(VirtualFrame frame, Object arg, PTuple clsTuple, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, @@ -136,12 +150,42 @@ boolean doRecursiveTransition(VirtualFrame frame, Object arg, PTuple clsTuple, @ } } + @SuppressWarnings("truffle-static-method") + @Specialization(guards = {"depth >= getNodeRecursionLimit(language)", "tupleCheck.execute(inliningTarget, clsTuple)"}, limit = "1", excludeForUncached = true) + boolean doNativeRecursiveTransition(VirtualFrame frame, Object arg, PythonAbstractNativeObject clsTuple, @SuppressWarnings("unused") int depth, + @Bind Node inliningTarget, + @SuppressWarnings("unused") @Bind PythonLanguage language, + @Cached("createFor($node)") BoundaryCallData boundaryCallData, + @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, + @Cached ConstructTupleNode constructTupleNode, + @Cached GetObjectArrayNode getObjectArrayNode) { + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + try { + // Note: we need actual recursion to trigger the stack overflow error like CPython. + PTuple tuple = constructTupleNode.execute(frame, clsTuple); + return callRecursiveWithNodeTruffleBoundary(inliningTarget, arg, tuple, getObjectArrayNode); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } + } + @Specialization boolean doRecursiveUncached(VirtualFrame frame, Object arg, PTuple clsTuple, @SuppressWarnings("unused") int depth) { assert this instanceof UnadoptableNode; return loopRecursive(frame, arg, clsTuple, null, GetObjectArrayNode.getUncached(), this, -1); } + @SuppressWarnings("truffle-static-method") + @Specialization(guards = "tupleCheck.execute(inliningTarget, clsTuple)", limit = "1") + boolean doNativeRecursiveUncached(VirtualFrame frame, Object arg, PythonAbstractNativeObject clsTuple, @SuppressWarnings("unused") int depth, + @Bind Node inliningTarget, + @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, + @Cached ConstructTupleNode constructTupleNode) { + assert this instanceof UnadoptableNode; + PTuple tuple = constructTupleNode.execute(frame, clsTuple); + return loopRecursive(frame, arg, tuple, null, GetObjectArrayNode.getUncached(), this, -1); + } + @TruffleBoundary private boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object arg, PTuple clsTuple, GetObjectArrayNode getObjectArrayNode) { return loopRecursive(null, arg, clsTuple, inliningTarget, getObjectArrayNode, getUncachedRecursive(), -1); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java index e0280e028b..d059715ffd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java @@ -40,9 +40,13 @@ */ package com.oracle.graal.python.lib; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectExactProfile; +import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; @@ -70,6 +74,12 @@ static boolean doBuiltinTuple(@SuppressWarnings("unused") PTuple tuple) { return true; } + @Specialization + static boolean doNativeTuple(PythonAbstractNativeObject tuple, + @Cached(inline = false) IsBuiltinObjectExactProfile check) { + return check.profileObject(check, tuple, PythonBuiltinClassType.PTuple); + } + @Fallback static boolean doOther(@SuppressWarnings("unused") Object object) { return false; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java index 221eb3401f..16ac0db8d9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java @@ -154,6 +154,10 @@ SequenceStorage getNative(PythonAbstractNativeObject tuple, public abstract static class GetNativeTupleStorage extends Node { public abstract NativeObjectSequenceStorage execute(PythonAbstractNativeObject tuple); + public static GetNativeTupleStorage getUncached() { + return TupleNodesFactory.GetNativeTupleStorageNodeGen.getUncached(); + } + @Specialization NativeObjectSequenceStorage getNative(PythonAbstractNativeObject tuple) { assert PyTupleCheckNode.executeUncached(tuple); 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 30af4c36ed..f9fc7cb2de 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 @@ -178,6 +178,7 @@ import com.oracle.graal.python.lib.PyObjectSizeNode; import com.oracle.graal.python.lib.PyObjectStrAsObjectNode; import com.oracle.graal.python.lib.PySequenceContainsNode; +import com.oracle.graal.python.lib.PyTupleCheckNode; import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.nodes.BuiltinNames; import com.oracle.graal.python.nodes.ErrorMessages; @@ -194,6 +195,7 @@ 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.builtins.TupleNodes.ConstructTupleNode; import com.oracle.graal.python.nodes.bytecode.CopyDictWithoutKeysNode; import com.oracle.graal.python.nodes.bytecode.GetAIterNode; import com.oracle.graal.python.nodes.bytecode.GetANextNode; @@ -4350,7 +4352,8 @@ public static final class SplitExceptionGroups { @TruffleBoundary private static void raiseIfNoExceptionTuples(Node inliningTarget, Object clause, ValidExceptionNode isValidException, IsSubtypeNode isSubtypeNode, SequenceStorageNodes.GetItemScalarNode getItemNode) { - if (clause instanceof PTuple clauseTuple) { + if (PyTupleCheckNode.executeUncached(clause)) { + PTuple clauseTuple = ConstructTupleNode.getUncached().execute(null, clause); SequenceStorage storage = clauseTuple.getSequenceStorage(); int length = storage.length(); for (int i = 0; i < length; i++) { @@ -4363,7 +4366,7 @@ private static void raiseIfNoExceptionTuples(Node inliningTarget, Object clause, @TruffleBoundary private static void raiseIfNoException(Node inliningTarget, Object clause, ValidExceptionNode isValidException, IsSubtypeNode isSubtypeNode, SequenceStorageNodes.GetItemScalarNode getItemNode) { - if (clause instanceof PTuple) { + if (PyTupleCheckNode.executeUncached(clause)) { raiseIfNoExceptionTuples(inliningTarget, clause, isValidException, isSubtypeNode, getItemNode); } else { if (!isValidException.execute(clause)) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/ExceptMatchNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/ExceptMatchNode.java index d626913ac6..bcfbaa552b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/ExceptMatchNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/ExceptMatchNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -42,10 +42,11 @@ import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.exception.PBaseException; -import com.oracle.graal.python.builtins.objects.tuple.PTuple; +import com.oracle.graal.python.lib.PyTupleCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.builtins.TupleNodes; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.runtime.exception.PException; @@ -55,6 +56,7 @@ import com.oracle.truffle.api.bytecode.OperationProxy; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Exclusive; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; @@ -80,9 +82,10 @@ private static void raiseIfNoException(Node inliningTarget, Object clause, Valid } } - @Specialization(guards = "!isPTuple(clause)") + @Specialization(guards = "!tupleCheck.execute(inliningTarget, clause)") public static boolean matchPythonSingle(PException e, Object clause, @Bind Node inliningTarget, + @SuppressWarnings("unused") @Shared("tupleCheck") @Cached PyTupleCheckNode tupleCheck, @Shared @Cached ValidExceptionNode isValidException, @Shared @Cached GetClassNode getClassNode, @Shared @Cached IsSubtypeNode isSubtype) { @@ -90,9 +93,10 @@ public static boolean matchPythonSingle(PException e, Object clause, return isSubtype.execute(getClassNode.execute(inliningTarget, e.getUnreifiedException()), clause); } - @Specialization(guards = "!isPTuple(clause)") + @Specialization(guards = "!tupleCheck.execute(inliningTarget, clause)") public static boolean matchPythonBaseSingle(PBaseException e, Object clause, @Bind Node inliningTarget, + @SuppressWarnings("unused") @Shared("tupleCheck") @Cached PyTupleCheckNode tupleCheck, @Shared @Cached ValidExceptionNode isValidException, @Shared @Cached GetClassNode getClassNode, @Shared @Cached IsSubtypeNode isSubtype) { @@ -100,9 +104,10 @@ public static boolean matchPythonBaseSingle(PBaseException e, Object clause, return isSubtype.execute(getClassNode.execute(inliningTarget, e), clause); } - @Specialization(guards = {"!isPTuple(clause)", "!isPException(e)"}, limit = "1") + @Specialization(guards = {"!tupleCheck.execute(inliningTarget, clause)", "!isPException(e)"}, limit = "1") public static boolean matchJava(AbstractTruffleException e, Object clause, @Bind Node inliningTarget, + @SuppressWarnings("unused") @Shared("tupleCheck") @Cached PyTupleCheckNode tupleCheck, @Shared @Cached ValidExceptionNode isValidException, @CachedLibrary("clause") InteropLibrary clauseLib) { // n.b.: we can only allow Java exceptions in clauses, because we cannot tell for other @@ -120,13 +125,15 @@ public static boolean matchJava(AbstractTruffleException e, Object clause, } } - @Specialization - public static boolean matchTuple(Object e, PTuple clause, + @Specialization(guards = "tupleCheck.execute(inliningTarget, clause)", limit = "1") + public static boolean matchTuple(Object e, Object clause, @Bind Node inliningTarget, - @Cached ExceptMatchNode recursiveNode, - @Cached SequenceStorageNodes.GetItemScalarNode getItemNode) { + @SuppressWarnings("unused") @Exclusive @Cached PyTupleCheckNode tupleCheck, + @Exclusive @Cached ExceptMatchNode recursiveNode, + @Exclusive @Cached TupleNodes.GetTupleStorage getTupleStorage, + @Exclusive @Cached SequenceStorageNodes.GetItemScalarNode getItemNode) { // check for every type in the tuple - SequenceStorage storage = clause.getSequenceStorage(); + SequenceStorage storage = getTupleStorage.execute(inliningTarget, clause); int length = storage.length(); for (int i = 0; i < length; i++) { Object clauseType = getItemNode.execute(inliningTarget, storage, i); From d58e8cf93d5ed0352a31a568fa90bcee373e2cc3 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 2 Jun 2026 20:34:07 +0200 Subject: [PATCH 2/7] Add tiny windows skill --- .agents/skills/windows/SKILL.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .agents/skills/windows/SKILL.md diff --git a/.agents/skills/windows/SKILL.md b/.agents/skills/windows/SKILL.md new file mode 100644 index 0000000000..524de3b6e8 --- /dev/null +++ b/.agents/skills/windows/SKILL.md @@ -0,0 +1,8 @@ +--- +name: windows +description: Windows-specific command guidance. Use when working on Windows and a required CLI tool, especially gdev-cli, is not available directly in PowerShell or cmd. +--- + +# Windows + +On Windows, if `gdev-cli` is not available, it may be available via `wsl bash -l -c gdev-cli`. From 3f702ffebdc8188069738a9487042f1ac9cc778a Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 2 Jun 2026 22:33:30 +0200 Subject: [PATCH 3/7] Refactor classinfo tuple recursion --- .../python/lib/PyObjectIsInstanceNode.java | 2 +- .../python/lib/PyObjectIsSubclassNode.java | 2 +- .../lib/PyObjectRecursiveBinaryCheckNode.java | 107 ++++-------------- 3 files changed, 24 insertions(+), 87 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java index a02f2e2860..84b729f2d8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java @@ -87,7 +87,7 @@ public static PyObjectIsInstanceNode getUncached() { return PyObjectIsInstanceNodeGen.getUncached(); } - @Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doTupleConstantLen", limit = "1") + @Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doRecursiveWithNode", limit = "1") static boolean isInstance(VirtualFrame frame, Object instance, Object cls, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java index 7da5ac453f..4ba85c6c47 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java @@ -87,7 +87,7 @@ public static PyObjectIsSubclassNode getUncached() { return PyObjectIsSubclassNodeGen.getUncached(); } - @Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doTupleConstantLen", limit = "1") + @Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doRecursiveWithNode", limit = "1") static boolean isSubclass(VirtualFrame frame, Object derived, Object cls, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java index 6ed84c106d..6d182dfd60 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java @@ -41,19 +41,16 @@ package com.oracle.graal.python.lib; import com.oracle.graal.python.PythonLanguage; -import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; -import com.oracle.graal.python.builtins.objects.common.SequenceNodes.GetObjectArrayNode; -import com.oracle.graal.python.builtins.objects.tuple.PTuple; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; -import com.oracle.graal.python.nodes.builtins.TupleNodes.ConstructTupleNode; +import com.oracle.graal.python.nodes.builtins.TupleNodes; import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.PythonOptions; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.Idempotent; @@ -62,8 +59,6 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.ExplodeLoop; -import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.UnadoptableNode; @@ -76,8 +71,6 @@ @GenerateCached(false) @SuppressWarnings("truffle-neverdefault") abstract class PyObjectRecursiveBinaryCheckNode extends PNodeWithContext { - static final int MAX_EXPLODE_LOOP = 16; // is also shifted to the left by recursion depth - public final boolean execute(Frame frame, Object arg, Object classinfo) { return executeInternal(frame, arg, classinfo, 0); } @@ -89,111 +82,55 @@ public final boolean execute(Frame frame, Object arg, Object classinfo) { abstract PyObjectRecursiveBinaryCheckNode getUncachedRecursive(); - @Idempotent - protected static int getMaxExplodeLoop(int depth) { - return MAX_EXPLODE_LOOP >> depth; - } - - @Specialization(guards = {"depth < getNodeRecursionLimit(language)", "getLength(clsTuple) == cachedLen", "cachedLen < getMaxExplodeLoop(depth)"}, // - limit = "getVariableArgumentInlineCacheLimit()", excludeForUncached = true) - @ExplodeLoop(kind = LoopExplosionKind.FULL_UNROLL_UNTIL_RETURN) - static boolean doTupleConstantLen(VirtualFrame frame, Object arg, PTuple clsTuple, int depth, - @Bind Node inliningTarget, - @SuppressWarnings("unused") @Bind PythonLanguage language, - @Cached("getLength(clsTuple)") int cachedLen, - @Shared @Cached GetObjectArrayNode getObjectArrayNode, - @Shared @Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) { - Object[] array = getObjectArrayNode.execute(inliningTarget, clsTuple); - int newDepth = depth + 1; - for (int i = 0; i < cachedLen; i++) { - Object cls = array[i]; - if (recursiveNode.executeInternal(frame, arg, cls, newDepth)) { - return true; - } - } - return false; - } - - @Specialization(guards = "depth < getNodeRecursionLimit(language)", replaces = "doTupleConstantLen", excludeForUncached = true) - static boolean doRecursiveWithNode(VirtualFrame frame, Object arg, PTuple clsTuple, int depth, - @Bind Node inliningTarget, - @SuppressWarnings("unused") @Bind PythonLanguage language, - @Shared @Cached GetObjectArrayNode getObjectArrayNode, - @Shared @Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) { - return loopRecursive(frame, arg, clsTuple, inliningTarget, getObjectArrayNode, recursiveNode, depth + 1); - } - @Specialization(guards = {"depth < getNodeRecursionLimit(language)", "tupleCheck.execute(inliningTarget, clsTuple)"}, limit = "1", excludeForUncached = true) - static boolean doNativeTupleWithNode(VirtualFrame frame, Object arg, PythonAbstractNativeObject clsTuple, int depth, + static boolean doRecursiveWithNode(VirtualFrame frame, Object arg, Object clsTuple, int depth, @Bind Node inliningTarget, @SuppressWarnings("unused") @Bind PythonLanguage language, @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, - @Cached ConstructTupleNode constructTupleNode, - @Cached GetObjectArrayNode getObjectArrayNode, + @Cached TupleNodes.GetTupleStorage getTupleStorage, + @Cached SequenceStorageNodes.ToArrayNode toArrayNode, @Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) { - PTuple tuple = constructTupleNode.execute(frame, clsTuple); - return loopRecursive(frame, arg, tuple, inliningTarget, getObjectArrayNode, recursiveNode, depth + 1); - } - - @Specialization(guards = {"depth >= getNodeRecursionLimit(language)"}, excludeForUncached = true) - boolean doRecursiveTransition(VirtualFrame frame, Object arg, PTuple clsTuple, @SuppressWarnings("unused") int depth, - @Bind Node inliningTarget, - @SuppressWarnings("unused") @Bind PythonLanguage language, - @Cached("createFor($node)") BoundaryCallData boundaryCallData, - @Shared @Cached GetObjectArrayNode getObjectArrayNode) { - Object state = BoundaryCallContext.enter(frame, boundaryCallData); - try { - // Note: we need actual recursion to trigger the stack overflow error like CPython. - return callRecursiveWithNodeTruffleBoundary(inliningTarget, arg, clsTuple, getObjectArrayNode); - } finally { - BoundaryCallContext.exit(frame, boundaryCallData, state); - } + return loopRecursive(frame, arg, clsTuple, inliningTarget, getTupleStorage, toArrayNode, recursiveNode, depth + 1); } @SuppressWarnings("truffle-static-method") @Specialization(guards = {"depth >= getNodeRecursionLimit(language)", "tupleCheck.execute(inliningTarget, clsTuple)"}, limit = "1", excludeForUncached = true) - boolean doNativeRecursiveTransition(VirtualFrame frame, Object arg, PythonAbstractNativeObject clsTuple, @SuppressWarnings("unused") int depth, + boolean doRecursiveTransition(VirtualFrame frame, Object arg, Object clsTuple, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, @SuppressWarnings("unused") @Bind PythonLanguage language, @Cached("createFor($node)") BoundaryCallData boundaryCallData, @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, - @Cached ConstructTupleNode constructTupleNode, - @Cached GetObjectArrayNode getObjectArrayNode) { + @Cached TupleNodes.GetTupleStorage getTupleStorage, + @Cached SequenceStorageNodes.ToArrayNode toArrayNode) { Object state = BoundaryCallContext.enter(frame, boundaryCallData); try { // Note: we need actual recursion to trigger the stack overflow error like CPython. - PTuple tuple = constructTupleNode.execute(frame, clsTuple); - return callRecursiveWithNodeTruffleBoundary(inliningTarget, arg, tuple, getObjectArrayNode); + return callRecursiveWithNodeTruffleBoundary(inliningTarget, arg, clsTuple, getTupleStorage, toArrayNode); } finally { BoundaryCallContext.exit(frame, boundaryCallData, state); } } - @Specialization - boolean doRecursiveUncached(VirtualFrame frame, Object arg, PTuple clsTuple, @SuppressWarnings("unused") int depth) { - assert this instanceof UnadoptableNode; - return loopRecursive(frame, arg, clsTuple, null, GetObjectArrayNode.getUncached(), this, -1); - } - @SuppressWarnings("truffle-static-method") @Specialization(guards = "tupleCheck.execute(inliningTarget, clsTuple)", limit = "1") - boolean doNativeRecursiveUncached(VirtualFrame frame, Object arg, PythonAbstractNativeObject clsTuple, @SuppressWarnings("unused") int depth, + boolean doRecursiveUncached(VirtualFrame frame, Object arg, Object clsTuple, @SuppressWarnings("unused") int depth, @Bind Node inliningTarget, @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, - @Cached ConstructTupleNode constructTupleNode) { + @Cached TupleNodes.GetTupleStorage getTupleStorage, + @Cached SequenceStorageNodes.ToArrayNode toArrayNode) { assert this instanceof UnadoptableNode; - PTuple tuple = constructTupleNode.execute(frame, clsTuple); - return loopRecursive(frame, arg, tuple, null, GetObjectArrayNode.getUncached(), this, -1); + return loopRecursive(frame, arg, clsTuple, inliningTarget, getTupleStorage, toArrayNode, this, -1); } @TruffleBoundary - private boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object arg, PTuple clsTuple, GetObjectArrayNode getObjectArrayNode) { - return loopRecursive(null, arg, clsTuple, inliningTarget, getObjectArrayNode, getUncachedRecursive(), -1); + private boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object arg, Object clsTuple, TupleNodes.GetTupleStorage getTupleStorage, + SequenceStorageNodes.ToArrayNode toArrayNode) { + return loopRecursive(null, arg, clsTuple, inliningTarget, getTupleStorage, toArrayNode, getUncachedRecursive(), -1); } - private static boolean loopRecursive(VirtualFrame frame, Object arg, PTuple clsTuple, Node inliningTarget, GetObjectArrayNode getObjectArrayNode, PyObjectRecursiveBinaryCheckNode node, - int depth) { - for (Object cls : getObjectArrayNode.execute(inliningTarget, clsTuple)) { + private static boolean loopRecursive(VirtualFrame frame, Object arg, Object clsTuple, Node inliningTarget, TupleNodes.GetTupleStorage getTupleStorage, + SequenceStorageNodes.ToArrayNode toArrayNode, PyObjectRecursiveBinaryCheckNode node, int depth) { + for (Object cls : getTupleArray(inliningTarget, clsTuple, getTupleStorage, toArrayNode)) { if (node.executeInternal(frame, arg, cls, depth)) { return true; } @@ -201,8 +138,8 @@ private static boolean loopRecursive(VirtualFrame frame, Object arg, PTuple clsT return false; } - protected static int getLength(PTuple t) { - return t.getSequenceStorage().length(); + private static Object[] getTupleArray(Node inliningTarget, Object clsTuple, TupleNodes.GetTupleStorage getTupleStorage, SequenceStorageNodes.ToArrayNode toArrayNode) { + return toArrayNode.execute(inliningTarget, getTupleStorage.execute(inliningTarget, clsTuple)); } @Idempotent From 5b84e9146a47e03ffc2b85c702614c88a8293177 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 2 Jun 2026 22:37:00 +0200 Subject: [PATCH 4/7] Fix Java style formatting --- .../python/builtins/objects/type/slots/TpSlotDescrGet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java index 53b0239c54..09c6fbe747 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java @@ -101,7 +101,7 @@ private TpSlotDescrGet() { } public abstract static sealed class TpSlotDescrGetBuiltin extends TpSlotBuiltin// - permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { + permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { static final BuiltinSlotWrapperSignature SIGNATURE = BuiltinSlotWrapperSignature.of(2, J_DOLLAR_SELF, "obj", "type"); static final PExternalFunctionWrapper WRAPPER = PExternalFunctionWrapper.DESCR_GET; From e623f320895a8a77e788868aa1dfcf4df481965e Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 3 Jun 2026 09:39:02 +0200 Subject: [PATCH 5/7] Fix CI Java style formatting --- .../python/builtins/objects/type/slots/TpSlotDescrGet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java index 09c6fbe747..53b0239c54 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java @@ -101,7 +101,7 @@ private TpSlotDescrGet() { } public abstract static sealed class TpSlotDescrGetBuiltin extends TpSlotBuiltin// - permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { + permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { static final BuiltinSlotWrapperSignature SIGNATURE = BuiltinSlotWrapperSignature.of(2, J_DOLLAR_SELF, "obj", "type"); static final PExternalFunctionWrapper WRAPPER = PExternalFunctionWrapper.DESCR_GET; From 41776ceafc5b028ba30f4054408bb24f30c6d825 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 3 Jun 2026 13:32:59 +0200 Subject: [PATCH 6/7] Minor hardening for Windows CI workers --- .../src/tests/test_patched_pip.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_patched_pip.py b/graalpython/com.oracle.graal.python.test/src/tests/test_patched_pip.py index 0ccb44d59a..21b91b57f1 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_patched_pip.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_patched_pip.py @@ -153,7 +153,12 @@ def build_package(self, name, version): def add_package_to_index(self, name, version, dist_type): package = self.build_package(name, version)[dist_type] - shutil.copy(package, self.index_dir) + # extra careful, not using shutil.copy so we get more detailed traceback + # on some flaky windows CI workers + self.assertTrue(package.exists(), f"Built package disappeared: {package}") + destination = self.index_dir / package.name + with open(package, 'rb') as src, open(destination, 'wb') as dst: + shutil.copyfileobj(src, dst) def run_venv_pip_install(self, package, extra_env=None, assert_stderr_matches=None): env = self.pip_env.copy() From 6ad997e570c14d8ddc0c20f8db6f0e1244e987ff Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 3 Jun 2026 14:45:18 +0200 Subject: [PATCH 7/7] Fix tuple check copyright year --- .../src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java index d059715ffd..464979834a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0