From 436c5717185c2cca72cbaa1627fdf835afe5ce7b Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 14 May 2026 10:42:44 +0200 Subject: [PATCH 1/6] Avoid forceCached for MakeFunction --- .../bytecode_dsl/PBytecodeDSLRootNode.java | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 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 f62b38057c..f3429dc6a8 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 @@ -59,6 +59,7 @@ import java.math.BigInteger; import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; @@ -376,7 +377,6 @@ @ShortCircuitOperation(name = "BoolOr", booleanConverter = PyObjectIsTrueNode.class, operator = Operator.OR_RETURN_VALUE) @ShortCircuitOperation(name = "PrimitiveBoolAnd", operator = Operator.AND_RETURN_VALUE) public abstract class PBytecodeDSLRootNode extends PRootNode implements BytecodeRootNode { - public static final int EXPLODE_LOOP_THRESHOLD = 30; private static final BytecodeConfig TRACE_AND_PROFILE_CONFIG = PBytecodeDSLRootNodeGen.newConfigBuilder().// addInstrumentation(TraceOrProfileCall.class).// addInstrumentation(TraceLine.class).// @@ -413,6 +413,12 @@ private static final class TracingNodes extends Node { @CompilationFinal protected transient int yieldFromGeneratorIndex = -1; @CompilationFinal(dimensions = 1) protected transient Assumption[] cellEffectivelyFinalAssumptions; + /* + * We don't want to store the assumption in MakeFunction node to be able to have an uncached version of it. + * So we put it into the root of the function that MakeFunction is creating. + */ + private final transient AtomicReference functionCodeFinalAssumption = new AtomicReference<>(); + private transient boolean pythonInternal; @CompilationFinal private transient boolean internal; @@ -1132,6 +1138,18 @@ public boolean hasYieldFromGenerator() { return yieldFromGeneratorIndex != -1; } + public Assumption getFunctionCodeFinalAssumption() { + CompilerAsserts.neverPartOfCompilation(); + Assumption assumption = functionCodeFinalAssumption.get(); + if (assumption == null) { + assumption = Truffle.getRuntime().createAssumption("code stable assumption"); + if (!functionCodeFinalAssumption.compareAndSet(null, assumption)) { + assumption = functionCodeFinalAssumption.get(); + } + } + return assumption; + } + @Operation @ConstantOperand(type = int.class) public static final class ArrayIndex { @@ -1495,12 +1513,12 @@ public static Object perform(VirtualFrame frame, LocalAccessor attributes, Objec } } - @Operation(storeBytecodeIndex = true, forceCached = true) + @Operation(storeBytecodeIndex = true) @ConstantOperand(type = TruffleString.class, name = "name") @ConstantOperand(type = TruffleString.class, name = "qualifiedName") @ConstantOperand(type = int.class) public static final class MakeFunction { - @Specialization(guards = "isSingleContext(rootNode)") + @Specialization(guards = "isSingleContext(rootNode)", excludeForUncached = true) public static Object functionSingleContext(VirtualFrame frame, TruffleString name, TruffleString qualifiedName, @@ -1511,27 +1529,27 @@ public static Object functionSingleContext(VirtualFrame frame, Object annotations, @Bind PBytecodeDSLRootNode rootNode, @Bind("getCodeUnit(rootNode, codeIndex)") BytecodeDSLCodeUnit codeUnit, - @Cached("getCode(frame, codeIndex, codeUnit)") PCode cachedCode, - @Shared @Cached("createCodeStableAssumption()") Assumption codeStableAssumption, + @Cached("getCode(frame, codeIndex, codeUnit)") PCode code, + @Cached(value = "getCodeStableAssumption(code)", uncached = "getCodeStableAssumption(code)") Assumption codeStableAssumption, @Shared @Cached DynamicObject.PutNode putNode) { return createFunction(frame, name, qualifiedName, codeUnit.getDocstring(), - cachedCode, defaults, kwDefaultsObject, closure, annotations, codeStableAssumption, rootNode, putNode); + code, defaults, kwDefaultsObject, closure, annotations, codeStableAssumption, rootNode, putNode); } @Specialization(replaces = "functionSingleContext") public static Object functionMultiContext(VirtualFrame frame, TruffleString name, TruffleString qualifiedName, - int codeIndex, + @SuppressWarnings("unused") int codeIndex, Object[] defaults, Object[] kwDefaultsObject, Object closure, Object annotations, @Bind PBytecodeDSLRootNode rootNode, - @Shared @Cached("createCodeStableAssumption()") Assumption codeStableAssumption, + @Bind("getCodeUnit(rootNode, codeIndex)") BytecodeDSLCodeUnit codeUnit, + @Bind("getCode(frame, codeIndex, codeUnit)") PCode code, + @Cached(value = "getCodeStableAssumption(code)", uncached = "getCodeStableAssumption(code)") Assumption codeStableAssumption, @Shared @Cached DynamicObject.PutNode putNode) { - BytecodeDSLCodeUnit codeUnit = getCodeUnit(rootNode, codeIndex); - PCode code = getCode(frame, codeIndex, codeUnit); return createFunction(frame, name, qualifiedName, codeUnit.getDocstring(), code, defaults, kwDefaultsObject, closure, annotations, codeStableAssumption, rootNode, putNode); } @@ -1542,8 +1560,9 @@ protected static boolean isSingleContext(Node node) { } @NeverDefault - static Assumption createCodeStableAssumption() { - return Truffle.getRuntime().createAssumption("code stable assumption"); + static Assumption getCodeStableAssumption(PCode code) { + PBytecodeDSLRootNode rootNode = (PBytecodeDSLRootNode) code.getRootCallTarget().getRootNode(); + return rootNode.getFunctionCodeFinalAssumption(); } @NeverDefault From cd08da42bcbf58b7ac9a9030f9f4797c7eda6879 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 14 May 2026 11:16:57 +0200 Subject: [PATCH 2/6] Remove forceCached from yield from operations --- .../exception/StopIterationBuiltins.java | 6 +- .../lib/PyGenFetchStopIterationValue.java | 68 +++++++++++++++++++ .../graal/python/nodes/bytecode/SendNode.java | 15 ++-- .../python/nodes/bytecode/ThrowNode.java | 12 ++-- .../bytecode_dsl/PBytecodeDSLRootNode.java | 28 ++++---- 5 files changed, 98 insertions(+), 31 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyGenFetchStopIterationValue.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/StopIterationBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/StopIterationBuiltins.java index d12f45e0bd..7613409bda 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/StopIterationBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/StopIterationBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, 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 @@ -76,7 +76,7 @@ protected List> getNodeFa @Slot(value = SlotKind.tp_init, isComplex = true) @SlotSignature(minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true) @GenerateNodeFactory - public abstract static class StopIterationInitNode extends PythonVarargsBuiltinNode { + abstract static class StopIterationInitNode extends PythonVarargsBuiltinNode { @Specialization static Object init(VirtualFrame frame, PBaseException self, Object[] args, PKeyword[] keywords, @@ -89,7 +89,7 @@ static Object init(VirtualFrame frame, PBaseException self, Object[] args, PKeyw @Builtin(name = "value", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true, doc = "generator return value") @GenerateNodeFactory - public abstract static class StopIterationValueNode extends PythonBinaryBuiltinNode { + abstract static class StopIterationValueNode extends PythonBinaryBuiltinNode { public final Object execute(PBaseException self) { return execute(null, self, PNone.NO_VALUE); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyGenFetchStopIterationValue.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyGenFetchStopIterationValue.java new file mode 100644 index 0000000000..deddf53367 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyGenFetchStopIterationValue.java @@ -0,0 +1,68 @@ +/* + * 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.lib; + +import com.oracle.graal.python.builtins.objects.exception.BaseExceptionAttrNode; +import com.oracle.graal.python.builtins.objects.exception.PBaseException; +import com.oracle.graal.python.builtins.objects.exception.StopIterationBuiltins; +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.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; + +@GenerateUncached +@GenerateInline +@GenerateCached(false) +public abstract class PyGenFetchStopIterationValue extends Node { + public abstract Object execute(Node inliningTarget, PBaseException self); + + public static Object executeUncached(PBaseException self) { + return PyGenFetchStopIterationValueNodeGen.getUncached().execute(null, self); + } + + @Specialization + static Object doFetch(@SuppressWarnings("unused") Node inliningTarget, PBaseException self, + @Cached BaseExceptionAttrNode attrNode) { + return attrNode.get(self, 0, StopIterationBuiltins.STOP_ITERATION_ATTR_FACTORY); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/SendNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/SendNode.java index 66d86e18d0..8fd4a716fa 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/SendNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/SendNode.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 @@ -44,13 +44,13 @@ import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.exception.PBaseException; -import com.oracle.graal.python.builtins.objects.exception.StopIterationBuiltins; import com.oracle.graal.python.builtins.objects.generator.CommonGeneratorBuiltins; import com.oracle.graal.python.builtins.objects.generator.PGenerator; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotIterNext.CallSlotTpIterNextNode; import com.oracle.graal.python.lib.IteratorExhausted; +import com.oracle.graal.python.lib.PyGenFetchStopIterationValue; import com.oracle.graal.python.lib.PyIterCheckNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.nodes.PNodeWithContext; @@ -77,7 +77,7 @@ static boolean doGenerator(VirtualFrame virtualFrame, int stackTop, PGenerator g @Bind Node inliningTarget, @Cached CommonGeneratorBuiltins.SendNode sendNode, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { try { Object value = sendNode.execute(virtualFrame, generator, arg); virtualFrame.setObject(stackTop, value); @@ -96,7 +96,7 @@ static boolean doIterator(VirtualFrame virtualFrame, int stackTop, Object iter, @Cached CallSlotTpIterNextNode callIterNext, @Exclusive @Cached InlinedBranchProfile exhaustedNoException, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { try { Object value = callIterNext.execute(virtualFrame, inliningTarget, slots.tp_iternext(), iter); virtualFrame.setObject(stackTop, value); @@ -121,7 +121,7 @@ static boolean doOther(VirtualFrame virtualFrame, int stackTop, Object obj, Obje @Bind Node inliningTarget, @Cached PyObjectCallMethodObjArgs callMethodNode, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { try { Object value = callMethodNode.execute(virtualFrame, inliningTarget, obj, T_SEND, arg); virtualFrame.setObject(stackTop, value); @@ -132,10 +132,9 @@ static boolean doOther(VirtualFrame virtualFrame, int stackTop, Object obj, Obje } } - private static void handleException(VirtualFrame frame, PException e, Node inliningTarget, IsBuiltinObjectProfile stopIterationProfile, StopIterationBuiltins.StopIterationValueNode getValue, - int stackTop) { + private static void handleException(VirtualFrame frame, PException e, Node inliningTarget, IsBuiltinObjectProfile stopIterationProfile, PyGenFetchStopIterationValue getValue, int stackTop) { e.expectStopIteration(inliningTarget, stopIterationProfile); - Object value = getValue.execute((PBaseException) e.getUnreifiedException()); + Object value = getValue.execute(inliningTarget, (PBaseException) e.getUnreifiedException()); frame.setObject(stackTop, null); frame.setObject(stackTop - 1, value); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/ThrowNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/ThrowNode.java index 69ae0e9209..46649e41c0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/ThrowNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/ThrowNode.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 @@ -47,9 +47,9 @@ import com.oracle.graal.python.builtins.objects.exception.ExceptionNodes; import com.oracle.graal.python.builtins.objects.exception.GetEscapedExceptionNode; import com.oracle.graal.python.builtins.objects.exception.PBaseException; -import com.oracle.graal.python.builtins.objects.exception.StopIterationBuiltins; import com.oracle.graal.python.builtins.objects.generator.CommonGeneratorBuiltins; import com.oracle.graal.python.builtins.objects.generator.PGenerator; +import com.oracle.graal.python.lib.PyGenFetchStopIterationValue; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.WriteUnraisableNode; @@ -85,7 +85,7 @@ static boolean doGenerator(VirtualFrame frame, int stackTop, PGenerator generato @Exclusive @Cached GetEscapedExceptionNode getEscapedExceptionNode, @Exclusive @Cached IsBuiltinObjectProfile profileExit, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { Object exceptionObject = getEscapedExceptionNode.execute(inliningTarget, exception); if (profileExit.profileObject(inliningTarget, exceptionObject, GeneratorExit)) { closeNode.execute(frame, generator); @@ -115,7 +115,7 @@ static boolean doOther(VirtualFrame frame, int stackTop, Object obj, AbstractTru @Exclusive @Cached GetEscapedExceptionNode getEscapedExceptionNode, @Exclusive @Cached IsBuiltinObjectProfile profileExit, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { Object exceptionObject = getEscapedExceptionNode.execute(inliningTarget, exception); if (profileExit.profileObject(inliningTarget, exceptionObject, GeneratorExit)) { Object close = PNone.NO_VALUE; @@ -147,10 +147,10 @@ static boolean doOther(VirtualFrame frame, int stackTop, Object obj, AbstractTru } private static void handleException(VirtualFrame frame, Node inliningTarget, PException e, - IsBuiltinObjectProfile stopIterationProfile, StopIterationBuiltins.StopIterationValueNode getValue, + IsBuiltinObjectProfile stopIterationProfile, PyGenFetchStopIterationValue getValue, int stackTop) { e.expectStopIteration(inliningTarget, stopIterationProfile); - Object value = getValue.execute((PBaseException) e.getUnreifiedException()); + Object value = getValue.execute(inliningTarget, (PBaseException) e.getUnreifiedException()); frame.setObject(stackTop, null); frame.setObject(stackTop - 1, value); } 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 f3429dc6a8..2c5c3271c9 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 @@ -89,7 +89,6 @@ import com.oracle.graal.python.builtins.objects.exception.ExceptionNodes; import com.oracle.graal.python.builtins.objects.exception.PBaseException; import com.oracle.graal.python.builtins.objects.exception.PBaseExceptionGroup; -import com.oracle.graal.python.builtins.objects.exception.StopIterationBuiltins; import com.oracle.graal.python.builtins.objects.frame.PFrame; import com.oracle.graal.python.builtins.objects.function.PArguments; import com.oracle.graal.python.builtins.objects.function.PFunction; @@ -122,6 +121,7 @@ import com.oracle.graal.python.compiler.OpCodes.MakeTypeParamKind; import com.oracle.graal.python.compiler.ParserCallbacksImpl; import com.oracle.graal.python.lib.IteratorExhausted; +import com.oracle.graal.python.lib.PyGenFetchStopIterationValue; import com.oracle.graal.python.lib.PyIterCheckNode; import com.oracle.graal.python.lib.PyIterNextNode; import com.oracle.graal.python.lib.PyNumberAddNode; @@ -3705,13 +3705,13 @@ public static Object doObject(Object sendValue, } /** Used in implementation of {@code yield from} */ - @Operation(storeBytecodeIndex = true, forceCached = true) + @Operation(storeBytecodeIndex = true) @ConstantOperand(type = LocalAccessor.class) @ConstantOperand(type = LocalAccessor.class) public static final class YieldFromSend { private static final TruffleString T_SEND = tsLiteral("send"); - @Specialization + @Specialization(excludeForUncached = true) static boolean doGenerator(VirtualFrame virtualFrame, LocalAccessor yieldedValue, LocalAccessor returnedValue, @@ -3721,7 +3721,7 @@ static boolean doGenerator(VirtualFrame virtualFrame, @Bind BytecodeNode bytecode, @Cached CommonGeneratorBuiltins.SendNode sendNode, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { try { Object value = sendNode.execute(virtualFrame, generator, arg); yieldedValue.setObject(bytecode, virtualFrame, value); @@ -3749,7 +3749,7 @@ static boolean doIterator(VirtualFrame virtualFrame, @Cached CallSlotTpIterNextNode callIterNext, @Exclusive @Cached InlinedBranchProfile exhaustedNoException, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { try { Object value = callIterNext.execute(virtualFrame, inliningTarget, slots.tp_iternext(), iter); yieldedValue.setObject(bytecode, virtualFrame, value); @@ -3775,7 +3775,7 @@ static boolean doOther(VirtualFrame virtualFrame, @Bind("$bytecodeIndex") int bci, @Cached PyObjectCallMethodObjArgs callMethodNode, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { try { Object value = callMethodNode.execute(virtualFrame, inliningTarget, obj, T_SEND, arg); yieldedValue.setObject(bytecode, virtualFrame, value); @@ -3788,16 +3788,16 @@ static boolean doOther(VirtualFrame virtualFrame, private static void handleException(VirtualFrame frame, PException e, Node inliningTarget, BytecodeNode bytecode, IsBuiltinObjectProfile stopIterationProfile, - StopIterationBuiltins.StopIterationValueNode getValue, + PyGenFetchStopIterationValue getValue, LocalAccessor returnedValue) { e.expectStopIteration(inliningTarget, stopIterationProfile); - returnedValue.setObject(bytecode, frame, getValue.execute((PBaseException) e.getUnreifiedException())); + returnedValue.setObject(bytecode, frame, getValue.execute(inliningTarget, (PBaseException) e.getUnreifiedException())); } } /** used in the implementation of {@code yield from} */ - @Operation(storeBytecodeIndex = true, forceCached = true) + @Operation(storeBytecodeIndex = true) @ConstantOperand(type = LocalAccessor.class) @ConstantOperand(type = LocalAccessor.class) public static final class YieldFromThrow { @@ -3805,7 +3805,7 @@ public static final class YieldFromThrow { private static final TruffleString T_CLOSE = tsLiteral("close"); private static final TruffleString T_THROW = tsLiteral("throw"); - @Specialization + @Specialization(excludeForUncached = true) static boolean doGenerator(VirtualFrame frame, LocalAccessor yieldedValue, LocalAccessor returnedValue, @@ -3817,7 +3817,7 @@ static boolean doGenerator(VirtualFrame frame, @Cached CommonGeneratorBuiltins.CloseNode closeNode, @Exclusive @Cached IsBuiltinObjectProfile profileExit, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { if (profileExit.profileException(inliningTarget, exception, GeneratorExit)) { closeNode.execute(frame, generator); throw exception; @@ -3848,7 +3848,7 @@ static boolean doOther(VirtualFrame frame, @Cached WriteUnraisableNode writeUnraisableNode, @Exclusive @Cached IsBuiltinObjectProfile profileExit, @Exclusive @Cached IsBuiltinObjectProfile stopIterationProfile, - @Exclusive @Cached StopIterationBuiltins.StopIterationValueNode getValue) { + @Exclusive @Cached PyGenFetchStopIterationValue getValue) { PException pException = (PException) exception; if (profileExit.profileException(inliningTarget, pException, GeneratorExit)) { Object close = PNone.NO_VALUE; @@ -3878,10 +3878,10 @@ static boolean doOther(VirtualFrame frame, } private static void handleException(VirtualFrame frame, PException e, Node inliningTarget, BytecodeNode bytecode, - IsBuiltinObjectProfile stopIterationProfile, StopIterationBuiltins.StopIterationValueNode getValue, + IsBuiltinObjectProfile stopIterationProfile, PyGenFetchStopIterationValue getValue, LocalAccessor returnedValue) { e.expectStopIteration(inliningTarget, stopIterationProfile); - returnedValue.setObject(bytecode, frame, getValue.execute((PBaseException) e.getUnreifiedException())); + returnedValue.setObject(bytecode, frame, getValue.execute(inliningTarget, (PBaseException) e.getUnreifiedException())); } } From 85d0a1e910904fa9daeb25eaf414c1c2f3810bc5 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 14 May 2026 14:34:58 +0200 Subject: [PATCH 3/6] Extract PyObjectIsInstanceNode --- .../builtins/modules/BuiltinFunctions.java | 208 +----------------- .../builtins/modules/SysModuleBuiltins.java | 5 +- .../modules/WarningsModuleBuiltins.java | 16 +- .../modules/cext/PythonCextErrBuiltins.java | 10 +- .../cext/PythonCextObjectBuiltins.java | 12 +- .../exception/PrepareExceptionNode.java | 8 +- .../builtins/objects/object/ObjectNodes.java | 8 +- .../builtins/objects/type/TypeNodes.java | 2 + .../objects/types/UnionTypeBuiltins.java | 13 +- .../python/lib/PyObjectIsInstanceNode.java | 118 ++++++++++ .../python/lib/PyObjectIsSubclassNode.java | 112 ++++++++++ .../lib/PyObjectRecursiveBinaryCheckNode.java | 168 ++++++++++++++ .../python/nodes/bytecode/MatchClassNode.java | 46 ++-- .../bytecode_dsl/PBytecodeDSLRootNode.java | 2 +- .../graal/python/runtime/PythonOptions.java | 9 - 15 files changed, 475 insertions(+), 262 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java index 3bd8506ad7..d5960c4a7a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java @@ -146,7 +146,6 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageIteratorKey; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageIteratorNext; import com.oracle.graal.python.builtins.objects.common.PHashingCollection; -import com.oracle.graal.python.builtins.objects.common.SequenceNodes.GetObjectArrayNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.ellipsis.EllipsisBuiltins; @@ -167,7 +166,6 @@ import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; -import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsTypeNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotIterNext.CallSlotTpIterNextNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotUnaryFunc.CallSlotUnaryNode; @@ -196,6 +194,8 @@ import com.oracle.graal.python.lib.PyObjectGetAttrO; import com.oracle.graal.python.lib.PyObjectGetIter; import com.oracle.graal.python.lib.PyObjectHashNode; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; +import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectIsTrueNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectLookupAttrO; @@ -245,7 +245,6 @@ import com.oracle.graal.python.nodes.function.builtins.PythonUnaryClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; -import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassExactProfile; import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.object.GetOrCreateDictNode; @@ -296,7 +295,6 @@ import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateNodeFactory; -import com.oracle.truffle.api.dsl.Idempotent; import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.dsl.ReportPolymorphism; @@ -309,8 +307,6 @@ import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.nodes.ExplodeLoop; -import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind; import com.oracle.truffle.api.nodes.LoopNode; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; @@ -1279,207 +1275,27 @@ Object doObject(Object value, } } - /** - * Base class for {@code isinstance} and {@code issubclass} that implements the recursive - * iteration of tuples passed as the second argument. The inheriting classes need to just - * provide the base for the recursion. - */ - public abstract static class RecursiveBinaryCheckBaseNode extends PythonBinaryBuiltinNode { - static final int MAX_EXPLODE_LOOP = 16; // is also shifted to the left by recursion depth - static final byte NON_RECURSIVE = Byte.MAX_VALUE; - - protected final byte depth; - - protected RecursiveBinaryCheckBaseNode(byte depth) { - this.depth = depth; - } - - public abstract boolean executeWith(VirtualFrame frame, Object instance, Object cls); - - @NeverDefault - protected final RecursiveBinaryCheckBaseNode createRecursive() { - return createRecursive((byte) (depth + 1)); - } - - @NeverDefault - protected final RecursiveBinaryCheckBaseNode createNonRecursive() { - return createRecursive(NON_RECURSIVE); - } - - protected RecursiveBinaryCheckBaseNode createRecursive(@SuppressWarnings("unused") byte newDepth) { - throw new AbstractMethodError(); // Cannot be really abstract b/c Truffle DSL... - } - - @Idempotent - protected int getMaxExplodeLoop() { - return MAX_EXPLODE_LOOP >> depth; - } - - @Specialization(guards = {"depth < getNodeRecursionLimit()", "getLength(clsTuple) == cachedLen", "cachedLen < getMaxExplodeLoop()"}, // - limit = "getVariableArgumentInlineCacheLimit()") - @ExplodeLoop(kind = LoopExplosionKind.FULL_UNROLL_UNTIL_RETURN) - static boolean doTupleConstantLen(VirtualFrame frame, Object instance, PTuple clsTuple, - @Bind Node inliningTarget, - @Cached("getLength(clsTuple)") int cachedLen, - @Shared @Cached GetObjectArrayNode getObjectArrayNode, - @Shared @Cached("createRecursive()") RecursiveBinaryCheckBaseNode recursiveNode) { - Object[] array = getObjectArrayNode.execute(inliningTarget, clsTuple); - for (int i = 0; i < cachedLen; i++) { - Object cls = array[i]; - if (recursiveNode.executeWith(frame, instance, cls)) { - return true; - } - } - return false; - } - - @Specialization(guards = "depth < getNodeRecursionLimit()", replaces = "doTupleConstantLen") - static boolean doRecursiveWithNode(VirtualFrame frame, Object instance, PTuple clsTuple, - @Bind Node inliningTarget, - @Shared @Cached GetObjectArrayNode getObjectArrayNode, - @Shared @Cached("createRecursive()") RecursiveBinaryCheckBaseNode recursiveNode) { - for (Object cls : getObjectArrayNode.execute(inliningTarget, clsTuple)) { - if (recursiveNode.executeWith(frame, instance, cls)) { - return true; - } - } - return false; - } - - @Specialization(guards = {"depth != NON_RECURSIVE", "depth >= getNodeRecursionLimit()"}) - static boolean doRecursiveWithLoop(VirtualFrame frame, Object instance, PTuple clsTuple, - @Bind Node inliningTarget, - @Cached("createFor($node)") BoundaryCallData boundaryCallData, - @Shared @Cached GetObjectArrayNode getObjectArrayNode, - @Cached("createNonRecursive()") RecursiveBinaryCheckBaseNode node) { - PythonContext context = PythonContext.get(inliningTarget); - PythonLanguage language = context.getLanguage(inliningTarget); - Object state = BoundaryCallContext.enter(frame, language, context, boundaryCallData); - try { - // Note: we need actual recursion to trigger the stack overflow error like CPython - // Note: we need fresh RecursiveBinaryCheckBaseNode and cannot use "this", because - // other children of this executed by other specializations may assume they'll - // always get a non-null frame - return callRecursiveWithNodeTruffleBoundary(inliningTarget, instance, clsTuple, getObjectArrayNode, node); - } finally { - BoundaryCallContext.exit(frame, language, context, state); - } - } - - @Specialization(guards = "depth == NON_RECURSIVE") - boolean doRecursiveWithLoopReuseThis(VirtualFrame frame, Object instance, PTuple clsTuple, - @Bind Node inliningTarget, - @Shared @Cached GetObjectArrayNode getObjectArrayNode) { - // This should be only called by doRecursiveWithLoop, now we have to reuse this to stop - // recursive node creation. It is OK, because now all specializations should always get - // null frame - assert frame == null; - return callRecursiveWithNodeTruffleBoundary(inliningTarget, instance, clsTuple, getObjectArrayNode, this); - } - - @TruffleBoundary - private static boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object instance, PTuple clsTuple, GetObjectArrayNode getObjectArrayNode, RecursiveBinaryCheckBaseNode node) { - return doRecursiveWithNode(null, instance, clsTuple, inliningTarget, getObjectArrayNode, node); - } - - protected static int getLength(PTuple t) { - return t.getSequenceStorage().length(); - } - } - // isinstance(object, classinfo) @Builtin(name = J_ISINSTANCE, minNumOfPositionalArgs = 2) @GenerateNodeFactory - public abstract static class IsInstanceNode extends RecursiveBinaryCheckBaseNode { - - protected IsInstanceNode(byte depth) { - super(depth); - } - - protected IsInstanceNode() { - this((byte) 0); - } + abstract static class IsInstanceNode extends PythonBinaryBuiltinNode { - @Override - public IsInstanceNode createRecursive(byte newDepth) { - return BuiltinFunctionsFactory.IsInstanceNodeFactory.create(newDepth); - } - - @Specialization(guards = "!isPTuple(cls)") - static boolean isInstance(VirtualFrame frame, Object instance, Object cls, - @Bind Node inliningTarget, - @Cached GetClassNode getClsClassNode, - @Cached IsBuiltinClassExactProfile classProfile, - @Cached GetClassNode getInstanceClassNode, - @Cached TypeNodes.IsSameTypeNode isSameTypeNode, - @Cached("create(T___INSTANCECHECK__)") LookupAndCallBinaryNode instanceCheckNode, - @Cached PyObjectIsTrueNode isTrueNode, - @Cached TypeNodes.GenericInstanceCheckNode genericInstanceCheckNode, - @Cached InlinedBranchProfile noInstanceCheckProfile) { - if (isSameTypeNode.execute(inliningTarget, getInstanceClassNode.execute(inliningTarget, instance), cls)) { - // Exact match, don't call __instancecheck__ - return true; - } - if (classProfile.profileClass(inliningTarget, getClsClassNode.execute(inliningTarget, cls), PythonBuiltinClassType.PythonClass)) { - // Avoid the lookup and call overhead when we know we're calling - // type.__instancecheck__ - return genericInstanceCheckNode.execute(frame, inliningTarget, instance, cls); - } - try { - Object result = instanceCheckNode.executeObject(frame, cls, instance); - return isTrueNode.execute(frame, result); - } catch (SpecialMethodNotFound ignore) { - noInstanceCheckProfile.enter(inliningTarget); - return genericInstanceCheckNode.execute(frame, inliningTarget, instance, cls); - } + @Specialization + static Object isInstance(VirtualFrame frame, Object instance, Object cls, + @Cached PyObjectIsInstanceNode isInstanceNode) { + return isInstanceNode.execute(frame, instance, cls); } } // issubclass(class, classinfo) @Builtin(name = J_ISSUBCLASS, minNumOfPositionalArgs = 2) @GenerateNodeFactory - public abstract static class IsSubClassNode extends RecursiveBinaryCheckBaseNode { - public abstract boolean executeBoolean(VirtualFrame frame, Object derived, Object cls); - - protected IsSubClassNode(byte depth) { - super(depth); - } - - protected IsSubClassNode() { - this((byte) 0); - } + abstract static class IsSubClassNode extends PythonBinaryBuiltinNode { - @Override - public IsSubClassNode createRecursive(byte newDepth) { - return BuiltinFunctionsFactory.IsSubClassNodeFactory.create(newDepth); - } - - @Specialization(guards = "!isPTuple(cls)") - static boolean isSubclass(VirtualFrame frame, Object derived, Object cls, - @Bind Node inliningTarget, - @Cached GetClassNode getClsClassNode, - @Cached IsBuiltinClassExactProfile classProfile, - @Cached("create(T___SUBCLASSCHECK__)") LookupAndCallBinaryNode subclassCheckNode, - @Cached PyObjectIsTrueNode isTrueNode, - @Cached TypeNodes.GenericSubclassCheckNode genericSubclassCheckNode, - @Cached InlinedBranchProfile noInstanceCheckProfile) { - if (classProfile.profileClass(inliningTarget, getClsClassNode.execute(inliningTarget, cls), PythonBuiltinClassType.PythonClass)) { - // Avoid the lookup and call overhead when we know we're calling - // type.__subclasscheck__ - return genericSubclassCheckNode.execute(frame, inliningTarget, derived, cls); - } - try { - Object result = subclassCheckNode.executeObject(frame, cls, derived); - return isTrueNode.execute(frame, result); - } catch (SpecialMethodNotFound ignore) { - noInstanceCheckProfile.enter(inliningTarget); - return genericSubclassCheckNode.execute(frame, inliningTarget, derived, cls); - } - } - - @NeverDefault - public static IsSubClassNode create() { - return BuiltinFunctionsFactory.IsSubClassNodeFactory.create(); + @Specialization + static Object isSubclass(VirtualFrame frame, Object derived, Object cls, + @Cached PyObjectIsSubclassNode isSubclassNode) { + return isSubclassNode.execute(frame, derived, cls); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java index 8adfefcedc..8e3b59e649 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java @@ -208,6 +208,7 @@ import com.oracle.graal.python.lib.PyNumberAsSizeNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectGetAttr; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectReprAsObjectNode; import com.oracle.graal.python.lib.PyObjectSetAttr; @@ -1925,7 +1926,7 @@ Object doHook(VirtualFrame frame, Object[] args, PKeyword[] keywords, @Cached PyObjectGetAttr getAttr, @Cached PyImportImport importNode, @Cached IsBuiltinObjectProfile attrErrorProfile, - @Cached BuiltinFunctions.IsInstanceNode isInstanceNode, + @Cached PyObjectIsInstanceNode isInstanceNode, @Cached WarningsModuleBuiltins.WarnNode warnNode, @Cached TruffleString.CodePointLengthNode codePointLengthNode, @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, @@ -1962,7 +1963,7 @@ Object doHook(VirtualFrame frame, Object[] args, PKeyword[] keywords, try { module = importNode.execute(frame, inliningTarget, modPath); } catch (PException pe) { - if (isInstanceNode.executeWith(frame, pe.getUnreifiedException(), ImportError)) { + if (isInstanceNode.execute(frame, pe.getUnreifiedException(), ImportError)) { warnNode.warnFormat(frame, RuntimeWarning, WARN_IGNORE_UNIMPORTABLE_BREAKPOINT_S, hookName); } return PNone.NONE; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WarningsModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WarningsModuleBuiltins.java index 68bf00ea9a..a303144e2f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WarningsModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WarningsModuleBuiltins.java @@ -76,7 +76,6 @@ import com.oracle.graal.python.builtins.Python3Core; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.IsSubClassNode; import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltinsClinicProviders.WarnBuiltinNodeClinicProviderGen; import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltinsFactory.WarnBuiltinNodeFactory; import com.oracle.graal.python.builtins.objects.PNone; @@ -95,6 +94,7 @@ import com.oracle.graal.python.lib.PyNumberAsSizeNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectIsTrueNode; +import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectReprAsTruffleStringNode; import com.oracle.graal.python.lib.PyObjectRichCompareBool.CachedPyObjectRichCompareBool; @@ -210,7 +210,7 @@ static final class WarningsModuleNode extends Node { @Child GetClassNode getClassNode; @Child PyNumberAsSizeNode asSizeNode; @Child PyObjectIsTrueNode isTrueNode; - @Child IsSubClassNode isSubClassNode; + @Child PyObjectIsSubclassNode isSubClassNode; @Child GetOrCreateDictNode getDictNode; @Child ReadFrameNode readFrameNode; @Child PyObjectLookupAttr lookupAttrNode; @@ -376,11 +376,11 @@ private TruffleString.SubstringNode getSubstringNode() { return substringNode; } - private IsSubClassNode getIsSubClass() { + private PyObjectIsSubclassNode getIsSubClass() { if (isSubClassNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); reportPolymorphicSpecialize(); - isSubClassNode = insert(IsSubClassNode.create()); + isSubClassNode = insert(PyObjectIsSubclassNode.create()); } return isSubClassNode; } @@ -595,7 +595,7 @@ private TruffleString getFilter(VirtualFrame frame, BoundaryCallData boundaryCal boolean goodMsg = checkMatched(frame, msg, text); boolean goodMod = checkMatched(frame, mod, module); - boolean isSubclass = getIsSubClass().executeBoolean(frame, category, cat); + boolean isSubclass = getIsSubClass().execute(frame, category, cat); int ln = getAsSizeNode().executeExactCached(frame, lnObj); if (goodMsg && isSubclass && goodMod && (ln == 0 || lineno == ln)) { // if we're ignoring warnings, the first action will match all and the loop @@ -778,7 +778,7 @@ private void warnExplicit(VirtualFrame frame, PythonModule warnings, // Python code uses PyObject_IsInstance but on the built-in Warning class, so we know // what __instancecheck__ does Object text; - if (getIsSubClass().executeBoolean(frame, getPythonClass(message), PythonBuiltinClassType.Warning)) { + if (getIsSubClass().execute(frame, getPythonClass(message), PythonBuiltinClassType.Warning)) { text = getStrNode().executeCached(frame, message); category = getPythonClass(message); } else { @@ -907,11 +907,11 @@ private void setupContext(VirtualFrame frame, int stackLevel, TruffleString[] sk */ private Object getCategory(VirtualFrame frame, Object message, Object category) { Object messageType = getPythonClass(message); - if (getIsSubClass().executeBoolean(frame, messageType, PythonBuiltinClassType.Warning)) { + if (getIsSubClass().execute(frame, messageType, PythonBuiltinClassType.Warning)) { return messageType; } else if (category == null || category == PNone.NONE) { return PythonBuiltinClassType.UserWarning; - } else if (!getIsTypeNode().executeCached(category) || !getIsSubClass().executeBoolean(frame, category, PythonBuiltinClassType.Warning)) { + } else if (!getIsTypeNode().executeCached(category) || !getIsSubClass().execute(frame, category, PythonBuiltinClassType.Warning)) { throw PRaiseNode.raiseStatic(this, PythonBuiltinClassType.TypeError, ErrorMessages.CATEGORY_MUST_BE_WARN_SUBCLS, category); } else { return category; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextErrBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextErrBuiltins.java index 9e62747c76..0c1a4fcf11 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextErrBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextErrBuiltins.java @@ -66,7 +66,6 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.IsSubClassNode; import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins.ExitNode; import com.oracle.graal.python.builtins.modules.SysModuleBuiltins; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; @@ -95,6 +94,7 @@ import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectGetAttr; +import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectSetAttr; import com.oracle.graal.python.lib.PyObjectStrAsObjectNode; @@ -198,7 +198,7 @@ abstract static class GraalPyPrivate_Err_CreateAndSetException extends CApiBinar @Specialization(guards = "!isExceptionClass(inliningTarget, type, isTypeNode, isSubClassNode)") static Object create(Object type, @SuppressWarnings("unused") Object value, @SuppressWarnings("unused") @Shared @Cached IsTypeNode isTypeNode, - @SuppressWarnings("unused") @Shared @Cached IsSubClassNode isSubClassNode, + @SuppressWarnings("unused") @Shared @Cached PyObjectIsSubclassNode isSubClassNode, @Bind Node inliningTarget) { throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.SystemError, EXCEPTION_NOT_BASEEXCEPTION, new Object[]{type}); } @@ -207,14 +207,14 @@ static Object create(Object type, @SuppressWarnings("unused") Object value, static Object create(Object type, Object value, @Bind Node inliningTarget, @SuppressWarnings("unused") @Shared @Cached IsTypeNode isTypeNode, - @SuppressWarnings("unused") @Shared @Cached IsSubClassNode isSubClassNode, + @SuppressWarnings("unused") @Shared @Cached PyObjectIsSubclassNode isSubClassNode, @Cached PrepareExceptionNode prepareExceptionNode) { Object exception = prepareExceptionNode.execute(null, type, value); throw PRaiseNode.raiseExceptionObjectStatic(inliningTarget, exception); } - protected static boolean isExceptionClass(Node inliningTarget, Object obj, IsTypeNode isTypeNode, IsSubClassNode isSubClassNode) { - return isTypeNode.execute(inliningTarget, obj) && isSubClassNode.executeWith(null, obj, PythonBuiltinClassType.PBaseException); + protected static boolean isExceptionClass(Node inliningTarget, Object obj, IsTypeNode isTypeNode, PyObjectIsSubclassNode isSubClassNode) { + return isTypeNode.execute(inliningTarget, obj) && isSubClassNode.execute(null, obj, PythonBuiltinClassType.PBaseException); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java index e478b59271..3b91c662f4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java @@ -70,8 +70,6 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.modules.BuiltinFunctions.FormatNode; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.IsInstanceNode; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.IsSubClassNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApi5BuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; @@ -115,6 +113,8 @@ import com.oracle.graal.python.lib.PyObjectGetAttrO; import com.oracle.graal.python.lib.PyObjectGetIter; import com.oracle.graal.python.lib.PyObjectHashNode; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; +import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectIsTrueNode; import com.oracle.graal.python.lib.PyObjectLookupAttrO; import com.oracle.graal.python.lib.PyObjectReprAsObjectNode; @@ -403,8 +403,8 @@ static Object doGeneric(Object obj, Object k, Object v, abstract static class PyObject_IsInstance extends CApiBinaryBuiltinNode { @Specialization static int doGeneric(Object obj, Object typ, - @Cached IsInstanceNode isInstanceNode) { - return intValue((boolean) isInstanceNode.execute(null, obj, typ)); + @Cached PyObjectIsInstanceNode isInstanceNode) { + return intValue(isInstanceNode.execute(null, obj, typ)); } } @@ -412,8 +412,8 @@ static int doGeneric(Object obj, Object typ, abstract static class PyObject_IsSubclass extends CApiBinaryBuiltinNode { @Specialization static int doGeneric(Object obj, Object typ, - @Cached IsSubClassNode isSubclassNode) { - return intValue((boolean) isSubclassNode.execute(null, obj, typ)); + @Cached PyObjectIsSubclassNode isSubclassNode) { + return intValue(isSubclassNode.execute(null, obj, typ)); } } 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 64891dd61f..6a481a4fca 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -44,13 +44,13 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions; 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.type.TypeNodes.IsTypeNode; import com.oracle.graal.python.lib.PyExceptionInstanceCheckNode; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; @@ -101,13 +101,13 @@ static Object doExceptionOrCreate(VirtualFrame frame, Object type, Object value, @Bind Node inliningTarget, @SuppressWarnings("unused") @Exclusive @Cached IsTypeNode isTypeNode, @Exclusive @Cached PyExceptionInstanceCheckNode check, - @Cached BuiltinFunctions.IsInstanceNode isInstanceNode, + @Cached PyObjectIsInstanceNode isInstanceNode, @Cached InlinedConditionProfile isInstanceProfile, @Shared @Cached IsSubtypeNode isSubtypeNode, @Exclusive @Cached PRaiseNode raiseNode, @Shared("callCtor") @Cached CallNode callConstructor) { checkExceptionClass(inliningTarget, type, isSubtypeNode, raiseNode); - if (isInstanceProfile.profile(inliningTarget, isInstanceNode.executeWith(frame, value, type))) { + if (isInstanceProfile.profile(inliningTarget, isInstanceNode.execute(frame, value, type))) { return value; } else { Object instance = callConstructor.execute(frame, type, value); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/ObjectNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/ObjectNodes.java index 329e0b7af6..11bff8568b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/ObjectNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/ObjectNodes.java @@ -78,7 +78,6 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.PythonAbstractObject; @@ -119,6 +118,7 @@ import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyObjectGetIter; +import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectLookupAttrO; import com.oracle.graal.python.lib.PyObjectSizeNode; @@ -675,7 +675,7 @@ static Object reduceNewObj(VirtualFrame frame, Node inliningTarget, Object obj, @Cached InlinedConditionProfile hasArgsProfile, @Cached(inline = false) GetNewArgsNode getNewArgsNode, @Cached ObjectGetStateNode getStateNode, - @Cached(inline = false) BuiltinFunctions.IsSubClassNode isSubClassNode, + @Cached(inline = false) PyObjectIsSubclassNode isSubClassNode, @Cached SequenceNodes.GetSequenceStorageNode getSequenceStorageNode, @Cached SequenceStorageNodes.ToArrayNode toArrayNode, @Cached PyObjectSizeNode sizeNode, @@ -718,8 +718,8 @@ static Object reduceNewObj(VirtualFrame frame, Node inliningTarget, Object obj, throw raiseNode.raiseBadInternalCall(inliningTarget); } - boolean objIsList = isSubClassNode.executeWith(frame, cls, PythonBuiltinClassType.PList); - boolean objIsDict = isSubClassNode.executeWith(frame, cls, PythonBuiltinClassType.PDict); + boolean objIsList = isSubClassNode.execute(frame, cls, PythonBuiltinClassType.PList); + boolean objIsDict = isSubClassNode.execute(frame, cls, PythonBuiltinClassType.PDict); boolean required = !hasargs && !objIsDict && !objIsList; Object state = getStateNode.execute(frame, inliningTarget, obj, required); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java index 050f9a87e0..ff923f403a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java @@ -2798,6 +2798,7 @@ static boolean check(Node inliningTarget, Object type, /** Equivalent of CPython's {@code recursive_isinstance} */ @GenerateInline @GenerateCached(false) + @GenerateUncached public abstract static class GenericInstanceCheckNode extends Node { public abstract boolean execute(VirtualFrame frame, Node inliningTarget, Object instance, Object cls); @@ -2848,6 +2849,7 @@ static boolean isInstance(VirtualFrame frame, Node inliningTarget, Object instan /** Equivalent of CPython's {@code recursive_issubclass} */ @GenerateInline @GenerateCached(false) + @GenerateUncached public abstract static class GenericSubclassCheckNode extends Node { public abstract boolean execute(VirtualFrame frame, Node inliningTarget, Object derived, Object cls); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/types/UnionTypeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/types/UnionTypeBuiltins.java index 3e53b1f8a3..625397545d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/types/UnionTypeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/types/UnionTypeBuiltins.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 @@ -58,7 +58,6 @@ import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.common.HashingCollectionNodes; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; @@ -76,6 +75,8 @@ import com.oracle.graal.python.lib.PyNumberOrNode; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyObjectHashNode; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; +import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectRichCompareBool; import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.nodes.ErrorMessages; @@ -225,7 +226,7 @@ abstract static class InstanceCheckNode extends PythonBinaryBuiltinNode { static boolean check(VirtualFrame frame, PUnionType self, Object other, @Bind Node inliningTarget, @Cached SequenceStorageNodes.GetItemScalarNode getItem, - @Cached BuiltinFunctions.IsInstanceNode isInstanceNode, + @Cached PyObjectIsInstanceNode isInstanceNode, @Cached PRaiseNode raiseNode) { SequenceStorage argsStorage = self.getArgs().getSequenceStorage(); boolean result = false; @@ -235,7 +236,7 @@ static boolean check(VirtualFrame frame, PUnionType self, Object other, throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.ISINSTANCE_ARG_2_CANNOT_CONTAIN_A_PARAMETERIZED_GENERIC); } if (!result) { - result = isInstanceNode.executeWith(frame, other, arg); + result = isInstanceNode.execute(frame, other, arg); // Cannot break here, the check for GenericAlias needs to check all args } } @@ -251,7 +252,7 @@ static boolean check(VirtualFrame frame, PUnionType self, Object other, @Bind Node inliningTarget, @Cached TypeNodes.IsTypeNode isTypeNode, @Cached SequenceStorageNodes.GetItemScalarNode getItem, - @Cached BuiltinFunctions.IsSubClassNode isSubClassNode, + @Cached PyObjectIsSubclassNode isSubClassNode, @Cached PRaiseNode raiseNode) { if (!isTypeNode.execute(inliningTarget, other)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.ISSUBCLASS_ARG_1_MUST_BE_A_CLASS); @@ -264,7 +265,7 @@ static boolean check(VirtualFrame frame, PUnionType self, Object other, throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.ISSUBCLASS_ARG_2_CANNOT_CONTAIN_A_PARAMETERIZED_GENERIC); } if (!result) { - result = isSubClassNode.executeWith(frame, other, arg); + result = isSubClassNode.execute(frame, other, arg); // Cannot break here, the check for GenericAlias needs to check all args } } 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 new file mode 100644 index 0000000000..df804279a6 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java @@ -0,0 +1,118 @@ +/* + * 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.lib; + +import static com.oracle.graal.python.nodes.SpecialMethodNames.T___INSTANCECHECK__; + +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.nodes.PGuards; +import com.oracle.graal.python.nodes.call.special.CallBinaryMethodNode; +import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodNode; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassExactProfile; +import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; +import com.oracle.truffle.api.dsl.NeverDefault; +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; + +/** + * Equivalent of CPython's {@code PyObject_IsInstance}. Implements tuple recursion for + * {@code classinfo} and the {@code __instancecheck__} fallback protocol. + */ +@GenerateInline(false) +@GenerateUncached +@ImportStatic(PGuards.class) +public abstract class PyObjectIsInstanceNode extends PyObjectRecursiveBinaryCheckNode { + @Override + PyObjectRecursiveBinaryCheckNode createRecursive() { + return create(); + } + + @Override + PyObjectRecursiveBinaryCheckNode getUncachedRecursive() { + return getUncached(); + } + + @NeverDefault + public static PyObjectIsInstanceNode create() { + return PyObjectIsInstanceNodeGen.create(); + } + + public static PyObjectIsInstanceNode getUncached() { + return PyObjectIsInstanceNodeGen.getUncached(); + } + + @Specialization(guards = "!isPTuple(cls)", insertBefore = "doTupleConstantLen") + static boolean isInstance(VirtualFrame frame, Object instance, Object cls, @SuppressWarnings("unused") int depth, + @Bind Node inliningTarget, + @Cached GetClassNode getClsClassNode, + @Cached IsBuiltinClassExactProfile classProfile, + @Cached GetClassNode getInstanceClassNode, + @Cached TypeNodes.IsSameTypeNode isSameTypeNode, + @Cached LookupSpecialMethodNode.Dynamic instanceCheckLookup, + @Cached CallBinaryMethodNode callInstanceCheck, + @Cached PyObjectIsTrueNode isTrueNode, + @Cached TypeNodes.GenericInstanceCheckNode genericInstanceCheckNode, + @Cached InlinedBranchProfile noInstanceCheckProfile) { + if (isSameTypeNode.execute(inliningTarget, getInstanceClassNode.execute(inliningTarget, instance), cls)) { + // Exact match, don't call __instancecheck__ + return true; + } + if (classProfile.profileClass(inliningTarget, getClsClassNode.execute(inliningTarget, cls), PythonBuiltinClassType.PythonClass)) { + // Avoid the lookup and call overhead when we know we're calling type.__instancecheck__. + return genericInstanceCheckNode.execute(frame, inliningTarget, instance, cls); + } + Object method = instanceCheckLookup.execute(frame, inliningTarget, getClsClassNode.execute(inliningTarget, cls), T___INSTANCECHECK__, cls); + if (PGuards.isNoValue(method)) { + noInstanceCheckProfile.enter(inliningTarget); + return genericInstanceCheckNode.execute(frame, inliningTarget, instance, cls); + } + Object result = callInstanceCheck.executeObject(frame, method, cls, instance); + return isTrueNode.execute(frame, result); + } +} 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 new file mode 100644 index 0000000000..fb9b43c1e2 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java @@ -0,0 +1,112 @@ +/* + * 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.lib; + +import static com.oracle.graal.python.nodes.SpecialMethodNames.T___SUBCLASSCHECK__; + +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.nodes.PGuards; +import com.oracle.graal.python.nodes.call.special.CallBinaryMethodNode; +import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodNode; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassExactProfile; +import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; +import com.oracle.truffle.api.dsl.NeverDefault; +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; + +/** + * Equivalent of CPython's {@code PyObject_IsSubclass}. Implements tuple recursion for + * {@code classinfo} and the {@code __subclasscheck__} fallback protocol. + */ +@GenerateInline(false) +@GenerateUncached +@ImportStatic(PGuards.class) +public abstract class PyObjectIsSubclassNode extends PyObjectRecursiveBinaryCheckNode { + @Override + PyObjectRecursiveBinaryCheckNode createRecursive() { + return create(); + } + + @Override + PyObjectRecursiveBinaryCheckNode getUncachedRecursive() { + return getUncached(); + } + + @NeverDefault + public static PyObjectIsSubclassNode create() { + return PyObjectIsSubclassNodeGen.create(); + } + + public static PyObjectIsSubclassNode getUncached() { + return PyObjectIsSubclassNodeGen.getUncached(); + } + + @Specialization(guards = "!isPTuple(cls)", insertBefore = "doTupleConstantLen") + static boolean isSubclass(VirtualFrame frame, Object derived, Object cls, @SuppressWarnings("unused") int depth, + @Bind Node inliningTarget, + @Cached GetClassNode getClsClassNode, + @Cached IsBuiltinClassExactProfile classProfile, + @Cached LookupSpecialMethodNode.Dynamic subclassCheckLookup, + @Cached CallBinaryMethodNode callSubclassCheck, + @Cached PyObjectIsTrueNode isTrueNode, + @Cached TypeNodes.GenericSubclassCheckNode genericSubclassCheckNode, + @Cached InlinedBranchProfile noInstanceCheckProfile) { + if (classProfile.profileClass(inliningTarget, getClsClassNode.execute(inliningTarget, cls), PythonBuiltinClassType.PythonClass)) { + // Avoid the lookup and call overhead when we know we're calling type.__subclasscheck__. + return genericSubclassCheckNode.execute(frame, inliningTarget, derived, cls); + } + Object method = subclassCheckLookup.execute(frame, inliningTarget, getClsClassNode.execute(inliningTarget, cls), T___SUBCLASSCHECK__, cls); + if (PGuards.isNoValue(method)) { + noInstanceCheckProfile.enter(inliningTarget); + return genericSubclassCheckNode.execute(frame, inliningTarget, derived, cls); + } + Object result = callSubclassCheck.executeObject(frame, method, cls, derived); + return isTrueNode.execute(frame, result); + } +} 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 new file mode 100644 index 0000000000..0731ba6ab8 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java @@ -0,0 +1,168 @@ +/* + * 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.lib; + +import com.oracle.graal.python.PythonLanguage; +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.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; +import com.oracle.truffle.api.dsl.ImportStatic; +import com.oracle.truffle.api.dsl.NeverDefault; +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; + +/** + * Base for recursive binary checks like {@code isinstance} and {@code issubclass}. It implements + * the recursive iteration of tuple {@code classinfo}; subclasses provide the scalar check. + */ +@ImportStatic({PythonOptions.class, PGuards.class}) +@GenerateInline(false) +@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); + } + + protected abstract boolean executeInternal(Frame frame, Object arg, Object classinfo, int depth); + + @NeverDefault + abstract PyObjectRecursiveBinaryCheckNode createRecursive(); + + 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)"}, 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); + } + } + + @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); + } + + @TruffleBoundary + private boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object arg, PTuple clsTuple, GetObjectArrayNode getObjectArrayNode) { + return loopRecursive(null, arg, clsTuple, inliningTarget, getObjectArrayNode, 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)) { + if (node.executeInternal(frame, arg, cls, depth)) { + return true; + } + } + return false; + } + + protected static int getLength(PTuple t) { + return t.getSequenceStorage().length(); + } + + @Idempotent + protected static int getNodeRecursionLimit(PythonLanguage language) { + return language.getEngineOption(PythonOptions.NodeRecursionLimit); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/MatchClassNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/MatchClassNode.java index adb0911187..46b8bf492e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/MatchClassNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/MatchClassNode.java @@ -43,27 +43,28 @@ import static com.oracle.graal.python.builtins.objects.type.TypeFlags.MATCH_SELF; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___MATCH_ARGS; import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import com.oracle.graal.python.PythonLanguage; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions; -import com.oracle.graal.python.builtins.objects.str.StringBuiltins; -import com.oracle.graal.python.builtins.objects.tuple.TupleBuiltins; import com.oracle.graal.python.builtins.objects.type.TypeNodes; -import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.lib.PyObjectGetAttrO; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; import com.oracle.graal.python.lib.PyTupleCheckExactNode; +import com.oracle.graal.python.lib.PyTupleGetItem; import com.oracle.graal.python.lib.PyTupleSizeNode; import com.oracle.graal.python.lib.PyUnicodeCheckNode; 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.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; +import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.Frame; @@ -73,23 +74,25 @@ import com.oracle.truffle.api.strings.TruffleString; @GenerateInline(false) // used in BCI root node +@GenerateUncached public abstract class MatchClassNode extends PNodeWithContext { public abstract Object execute(Frame frame, Object subject, Object type, int nargs, TruffleString[] kwArgs); @Specialization - Object match(VirtualFrame frame, Object subject, Object type, int nargs, @NeverDefault @SuppressWarnings("unused") TruffleString[] kwArgsArg, + static Object match(VirtualFrame frame, Object subject, Object type, int nargs, @NeverDefault @SuppressWarnings("unused") TruffleString[] kwArgsArg, @Bind Node inliningTarget, @Cached(value = "kwArgsArg", dimensions = 1) TruffleString[] kwArgs, @Cached TypeNodes.IsTypeNode isTypeNode, - @Cached BuiltinFunctions.IsInstanceNode isInstanceNode, + @Cached PyObjectIsInstanceNode isInstanceNode, @Cached PyObjectGetAttrO getAttr, @Cached TypeNodes.GetTypeFlagsNode getTypeFlagsNode, @Cached IsBuiltinObjectProfile isClassProfile, - @Cached StringBuiltins.StringRichCmpNode eqStrNode, + @Cached CastToTruffleStringNode castStringNode, + @Cached TruffleString.EqualNode equalNode, @Cached PyTupleCheckExactNode tupleCheckExactNode, @Bind PythonLanguage language, @Cached PyTupleSizeNode tupleSizeNode, - @Cached TupleBuiltins.GetItemNode getItemNode, + @Cached PyTupleGetItem getItemNode, @Cached PyUnicodeCheckNode unicodeCheckNode, @Cached PRaiseNode raise) { @@ -97,7 +100,7 @@ Object match(VirtualFrame frame, Object subject, Object type, int nargs, @NeverD throw raise.raise(inliningTarget, TypeError, ErrorMessages.CALLED_MATCH_PAT_MUST_BE_TYPE); } - if (!isInstanceNode.executeWith(frame, subject, type)) { + if (!isInstanceNode.execute(frame, subject, type)) { return null; } @@ -133,14 +136,14 @@ Object match(VirtualFrame frame, Object subject, Object type, int nargs, @NeverD attrs[attrsLength[0]++] = subject; } else { attrs = new Object[nargs + kwArgs.length]; - getArgs(frame, inliningTarget, subject, type, nargs, seen, seenLength, attrs, attrsLength, matchArgs, getAttr, eqStrNode, getItemNode, unicodeCheckNode, raise); + getArgs(frame, inliningTarget, subject, type, nargs, seen, seenLength, attrs, attrsLength, matchArgs, getAttr, castStringNode, equalNode, getItemNode, unicodeCheckNode, raise); } } else { attrs = new Object[kwArgs.length]; } // Finally, the keyword subpatterns: try { - getKwArgs(frame, inliningTarget, subject, type, kwArgs, seen, seenLength, attrs, attrsLength, getAttr, eqStrNode, raise); + getKwArgs(frame, inliningTarget, subject, type, kwArgs, seen, seenLength, attrs, attrsLength, getAttr, castStringNode, equalNode, raise); } catch (PException pe) { // missing keyword argument will throw AttributeError, but in pattern matching, that // should be ignored @@ -153,42 +156,43 @@ Object match(VirtualFrame frame, Object subject, Object type, int nargs, @NeverD @ExplodeLoop private static void getArgs(VirtualFrame frame, Node inliningTarget, Object subject, Object type, int nargs, Object[] seen, int[] seenLength, Object[] attrs, int[] attrsLength, Object matchArgs, PyObjectGetAttrO getAttr, - StringBuiltins.StringRichCmpNode eqStrNode, TupleBuiltins.GetItemNode getItemNode, PyUnicodeCheckNode unicodeCheckNode, PRaiseNode raise) { + CastToTruffleStringNode castStringNode, TruffleString.EqualNode equalNode, PyTupleGetItem getItemNode, PyUnicodeCheckNode unicodeCheckNode, PRaiseNode raise) { CompilerAsserts.partialEvaluationConstant(nargs); for (int i = 0; i < nargs; i++) { - Object name = getItemNode.execute(frame, matchArgs, i); + Object name = getItemNode.execute(inliningTarget, matchArgs, i); if (!unicodeCheckNode.execute(inliningTarget, name)) { throw raise.raise(inliningTarget, TypeError, ErrorMessages.MATCH_ARGS_ELEMENTS_MUST_BE_STRINGS_GOT_P, name); } - setName(frame, inliningTarget, type, name, seen, seenLength, eqStrNode, raise); + setName(inliningTarget, type, name, seen, seenLength, castStringNode, equalNode, raise); attrs[attrsLength[0]++] = getAttr.execute(frame, inliningTarget, subject, name); } } @ExplodeLoop private static void getKwArgs(VirtualFrame frame, Node inliningTarget, Object subject, Object type, TruffleString[] kwArgs, Object[] seen, int[] seenLength, Object[] attrs, int[] attrsLength, - PyObjectGetAttrO getAttr, - StringBuiltins.StringRichCmpNode eqStrNode, PRaiseNode raise) { + PyObjectGetAttrO getAttr, CastToTruffleStringNode castStringNode, TruffleString.EqualNode equalNode, PRaiseNode raise) { CompilerAsserts.partialEvaluationConstant(kwArgs); for (int i = 0; i < kwArgs.length; i++) { TruffleString name = kwArgs[i]; CompilerAsserts.partialEvaluationConstant(name); - setName(frame, inliningTarget, type, name, seen, seenLength, eqStrNode, raise); + setName(inliningTarget, type, name, seen, seenLength, castStringNode, equalNode, raise); attrs[attrsLength[0]++] = getAttr.execute(frame, inliningTarget, subject, name); } } - private static void setName(VirtualFrame frame, Node inliningTarget, Object type, Object name, Object[] seen, int[] seenLength, StringBuiltins.StringRichCmpNode eqNode, PRaiseNode raise) { - if (seenLength[0] > 0 && contains(frame, seen, name, eqNode)) { + private static void setName(Node inliningTarget, Object type, Object name, Object[] seen, int[] seenLength, CastToTruffleStringNode castStringNode, TruffleString.EqualNode equalNode, + PRaiseNode raise) { + if (seenLength[0] > 0 && contains(inliningTarget, seen, name, castStringNode, equalNode)) { throw raise.raise(inliningTarget, TypeError, ErrorMessages.S_GOT_MULTIPLE_SUBPATTERNS_FOR_ATTR_S, type, name); } seen[seenLength[0]++] = name; } @ExplodeLoop - private static boolean contains(VirtualFrame frame, Object[] seen, Object name, StringBuiltins.StringRichCmpNode eqNode) { + private static boolean contains(Node inliningTarget, Object[] seen, Object name, CastToTruffleStringNode castStringNode, TruffleString.EqualNode equalNode) { + TruffleString nameString = castStringNode.castKnownString(inliningTarget, name); for (int i = 0; i < seen.length; i++) { - if (seen[i] != null && (boolean) eqNode.execute(frame, seen[i], name, RichCmpOp.Py_EQ)) { + if (seen[i] != null && equalNode.execute(castStringNode.castKnownString(inliningTarget, seen[i]), nameString, TS_ENCODING)) { return true; } } 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 2c5c3271c9..49d39e2916 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 @@ -1501,7 +1501,7 @@ public static boolean perform(VirtualFrame frame, LocalAccessor values, Object m } } - @Operation(storeBytecodeIndex = true, forceCached = true) + @Operation(storeBytecodeIndex = true) @ConstantOperand(type = LocalAccessor.class) public static final class MatchClass { @Specialization diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java index 912b41aa90..5fffa6fc26 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java @@ -580,15 +580,6 @@ public static int getVariableArgumentInlineCacheLimit() { return PythonLanguage.get(null).getEngineOption(VariableArgumentInlineCacheLimit); } - @Idempotent - public static int getNodeRecursionLimit() { - CompilerAsserts.neverPartOfCompilation(); - int result = PythonLanguage.get(null).getEngineOption(NodeRecursionLimit); - // So that we can use byte counters and also Byte.MAX_VALUE as special placeholder - assert result < Byte.MAX_VALUE; - return result; - } - public static boolean isPExceptionWithJavaStacktrace(PythonLanguage language) { return language.getEngineOption(WithJavaStacktrace) >= 2; } From a1231b8d25668643e7fe7318740ebe617abbb4f6 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 14 May 2026 15:04:30 +0200 Subject: [PATCH 4/6] Use PyObjectFormat instead of the builtin function node --- .../builtins/modules/BuiltinFunctions.java | 32 +++++------- .../cext/PythonCextObjectBuiltins.java | 4 +- .../builtins/objects/str/StringBuiltins.java | 6 +-- .../objects/str/TemplateFormatter.java | 10 ++-- .../graal/python/lib/PyObjectFormat.java | 49 +++++++++---------- .../nodes/bytecode/PBytecodeRootNode.java | 9 ++-- .../bytecode_dsl/PBytecodeDSLRootNode.java | 4 +- 7 files changed, 52 insertions(+), 62 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java index d5960c4a7a..ff2d357fca 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java @@ -89,10 +89,8 @@ import static com.oracle.graal.python.nodes.PGuards.isNoValue; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___DICT__; import static com.oracle.graal.python.nodes.SpecialMethodNames.T_FILENO; -import static com.oracle.graal.python.nodes.SpecialMethodNames.T___FORMAT__; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___MRO_ENTRIES__; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___ROUND__; -import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING; import static com.oracle.graal.python.nodes.StringLiterals.T_FALSE; import static com.oracle.graal.python.nodes.StringLiterals.T_MINUS; import static com.oracle.graal.python.nodes.StringLiterals.T_NEWLINE; @@ -190,6 +188,7 @@ import com.oracle.graal.python.lib.PyObjectAsciiAsObjectNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectDir; +import com.oracle.graal.python.lib.PyObjectFormat; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyObjectGetAttrO; import com.oracle.graal.python.lib.PyObjectGetIter; @@ -238,6 +237,7 @@ import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonTernaryClinicBuiltinNode; @@ -285,7 +285,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.RootCallTarget; -import com.oracle.truffle.api.bytecode.OperationProxy; import com.oracle.truffle.api.debug.Debugger; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; @@ -1695,27 +1694,18 @@ static Object repr(VirtualFrame frame, Object obj, // format(object, [format_spec]) @Builtin(name = J_FORMAT, minNumOfPositionalArgs = 1, parameterNames = {"object", "format_spec"}) + @ArgumentClinic(name = "format_spec", conversion = ArgumentClinic.ClinicConversion.TString, defaultValue = "T_EMPTY_STRING") @GenerateNodeFactory - @OperationProxy.Proxyable(storeBytecodeIndex = true) - @ImportStatic(PGuards.class) - public abstract static class FormatNode extends PythonBinaryBuiltinNode { + abstract static class FormatNode extends PythonBinaryClinicBuiltinNode { + @Override + protected ArgumentClinicProvider getArgumentClinic() { + return BuiltinFunctionsClinicProviders.FormatNodeClinicProviderGen.INSTANCE; + } @Specialization - public static Object format(VirtualFrame frame, Object obj, Object formatSpec, - @Bind Node inliningTarget, - @Cached("create(T___FORMAT__)") LookupAndCallBinaryNode callFormat, - @Cached InlinedConditionProfile formatIsNoValueProfile, - @Cached PRaiseNode raiseNode) { - Object format = formatIsNoValueProfile.profile(inliningTarget, isNoValue(formatSpec)) ? T_EMPTY_STRING : formatSpec; - try { - Object res = callFormat.executeObject(frame, obj, format); - if (!PGuards.isString(res)) { - throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.S_MUST_RETURN_S_NOT_P, T___FORMAT__, "str", res); - } - return res; - } catch (SpecialMethodNotFound ignore) { - throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.TYPE_DOESNT_DEFINE_FORMAT, obj); - } + public static Object format(VirtualFrame frame, Object obj, TruffleString formatSpec, + @Cached PyObjectFormat formatNode) { + return formatNode.execute(frame, obj, formatSpec); } @NeverDefault diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java index 3b91c662f4..cae2956447 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java @@ -69,7 +69,6 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.FormatNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApi5BuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; @@ -110,6 +109,7 @@ import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectDelItem; import com.oracle.graal.python.lib.PyObjectDir; +import com.oracle.graal.python.lib.PyObjectFormat; import com.oracle.graal.python.lib.PyObjectGetAttrO; import com.oracle.graal.python.lib.PyObjectGetIter; import com.oracle.graal.python.lib.PyObjectHashNode; @@ -675,7 +675,7 @@ static TruffleString asciiNone(@SuppressWarnings("unused") PNone obj) { abstract static class PyObject_Format extends CApiBinaryBuiltinNode { @Specialization static Object ascii(Object obj, Object spec, - @Cached FormatNode format) { + @Cached PyObjectFormat format) { return format.execute(null, obj, spec); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringBuiltins.java index 34d2c45953..fe5b1e1df2 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringBuiltins.java @@ -74,7 +74,6 @@ import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions; import com.oracle.graal.python.builtins.modules.CodecsModuleBuiltins; import com.oracle.graal.python.builtins.modules.TRegexUtil; import com.oracle.graal.python.builtins.objects.PNone; @@ -134,6 +133,7 @@ import com.oracle.graal.python.builtins.objects.type.slots.TpSlotSqContains.SqContainsBuiltinNode; import com.oracle.graal.python.lib.PyIndexCheckNode; import com.oracle.graal.python.lib.PyNumberAsSizeNode; +import com.oracle.graal.python.lib.PyObjectFormat; import com.oracle.graal.python.lib.PyObjectGetItem; import com.oracle.graal.python.lib.PyObjectHashNode; import com.oracle.graal.python.lib.PyObjectStrAsObjectNode; @@ -459,7 +459,7 @@ abstract static class StrFormatNode extends PythonBuiltinNode { static TruffleString format(VirtualFrame frame, Object self, Object[] args, PKeyword[] kwargs, @Bind Node inliningTarget, @Cached("createFor($node)") BoundaryCallData boundaryCallData, - @Cached BuiltinFunctions.FormatNode format, + @Cached PyObjectFormat format, @Cached CastToTruffleStringNode castToStringNode, @Cached PRaiseNode raiseNode) { TruffleString string; @@ -493,7 +493,7 @@ protected ArgumentClinicProvider getArgumentClinic() { @Specialization TruffleString format(VirtualFrame frame, TruffleString self, Object mapping, @Cached("createFor($node)") BoundaryCallData boundaryCallData, - @Cached BuiltinFunctions.FormatNode format) { + @Cached PyObjectFormat format) { TemplateFormatter template = new TemplateFormatter(self); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/TemplateFormatter.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/TemplateFormatter.java index b67bb43d77..04cc82eebc 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/TemplateFormatter.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/TemplateFormatter.java @@ -64,11 +64,11 @@ import java.math.BigInteger; import java.util.ArrayList; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.FormatNode; import com.oracle.graal.python.builtins.modules.SysModuleBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.lib.PyObjectAsciiAsObjectNode; +import com.oracle.graal.python.lib.PyObjectFormat; import com.oracle.graal.python.lib.PyObjectGetItem; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectReprAsObjectNode; @@ -102,7 +102,7 @@ public TemplateFormatter(TruffleString template) { } @TruffleBoundary - public TruffleString build(Node node, Object[] argsArg, Object kwArgs, FormatNode formatNode) { + public TruffleString build(Node node, Object[] argsArg, Object kwArgs, PyObjectFormat formatNode) { this.args = argsArg; this.keywords = kwArgs; this.autoNumbering = 0; @@ -110,14 +110,14 @@ public TruffleString build(Node node, Object[] argsArg, Object kwArgs, FormatNod return buildString(node, 0, template.length(), 2, formatNode); } - private TruffleString buildString(Node node, int start, int end, int level, FormatNode formatNode) { + private TruffleString buildString(Node node, int start, int end, int level, PyObjectFormat formatNode) { if (level == 0) { throw PRaiseNode.raiseStatic(node, ValueError, RECURSION_DEPTH_EXCEEDED); } return doBuildString(node, start, end, level - 1, this.template, formatNode); } - private TruffleString doBuildString(Node node, int start, int end, int level, String s, FormatNode formatNode) { + private TruffleString doBuildString(Node node, int start, int end, int level, String s, PyObjectFormat formatNode) { StringBuilder out = new StringBuilder(); int lastLiteral = start; int i = start; @@ -375,7 +375,7 @@ private static int toInt(Node node, String s) { } } - private Object renderField(Node node, int start, int end, boolean recursive, int level, FormatNode formatNode) { + private Object renderField(Node node, int start, int end, boolean recursive, int level, PyObjectFormat formatNode) { Field filed = parseField(node, start, end); String name = filed.name; Character conversion = filed.conversion; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectFormat.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectFormat.java index 5a398d5249..fcdfaec902 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectFormat.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -53,14 +53,15 @@ import com.oracle.graal.python.nodes.call.special.CallBinaryMethodNode; import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodNode; import com.oracle.graal.python.nodes.object.GetClassNode; +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.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.VirtualFrame; @@ -73,23 +74,32 @@ * not accept native {@code NULL} as {@code formatSpec}. Convert it to an empty string to get * equivalent behavior. */ -@GenerateInline +@GenerateInline(false) @GenerateUncached +@OperationProxy.Proxyable(allowUncached = true, storeBytecodeIndex = true) public abstract class PyObjectFormat extends PNodeWithContext { - public final Object executeNonInlined(VirtualFrame frame, Object obj, Object formatSpec) { - return execute(frame, null, obj, formatSpec); + + public abstract Object execute(Frame frame, Object obj, Object formatSpec); + + @NeverDefault + public static PyObjectFormat create() { + return PyObjectFormatNodeGen.create(); } - public abstract Object execute(VirtualFrame frame, Node node, Object obj, Object formatSpec); + public static PyObjectFormat getUncached() { + return PyObjectFormatNodeGen.getUncached(); + } - @Specialization - static Object doNone(VirtualFrame frame, Node inliningTarget, Object obj, PNone formatSpec, + @Specialization(guards = "isNoValue(formatSpec)") + public static Object doNone(VirtualFrame frame, Object obj, PNone formatSpec, + @Bind Node inliningTarget, @Shared("impl") @Cached PyObjectFormatStr formatStr) { return formatStr.execute(frame, inliningTarget, obj, T_EMPTY_STRING); } @Fallback - static Object doOthers(VirtualFrame frame, Node inliningTarget, Object obj, Object formatSpec, + public static Object doOthers(VirtualFrame frame, Object obj, Object formatSpec, + @Bind Node inliningTarget, @Shared("impl") @Cached PyObjectFormatStr formatStr) { return formatStr.execute(frame, inliningTarget, obj, formatSpec); } @@ -100,31 +110,21 @@ static Object doOthers(VirtualFrame frame, Node inliningTarget, Object obj, Obje public abstract static class PyObjectFormatStr extends PNodeWithContext { public abstract Object execute(Frame frame, Node inliningTarget, Object obj, Object formatSpec); - static boolean isEmptyString(Object formatSpec) { - // to keep the fast-path optimization guard simple, we ignore empty PStrings - return (formatSpec instanceof TruffleString && ((TruffleString) formatSpec).isEmpty()); - } - - @Specialization(guards = {"isString(obj)", "isEmptyString(formatSpec)"}) - static Object doString(Object obj, Object formatSpec) { - return obj; - } - - @Specialization(guards = {"isEmptyString(formatSpec)"}) - static Object doLong(long obj, Object formatSpec) { + // to keep the fast-path optimization guard simple, we ignore PStrings + @Specialization(guards = {"formatSpec.isEmpty()"}) + static Object doString(TruffleString obj, TruffleString formatSpec) { return obj; } - // Note: PRaiseNode is @Exclusive to workaround a bug in DSL @Specialization(guards = "isString(formatSpec)") static Object doGeneric(VirtualFrame frame, Node inliningTarget, Object obj, Object formatSpec, @Cached GetClassNode getClassNode, @Cached LookupSpecialMethodNode.Dynamic lookupFormat, @Cached CallBinaryMethodNode callFormat, - @Exclusive @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode) { Object formatMethod = lookupFormat.execute(frame, inliningTarget, getClassNode.execute(inliningTarget, obj), T___FORMAT__, obj); if (formatMethod != PNone.NO_VALUE) { - Object res = callFormat.executeObject(frame, obj, formatSpec); + Object res = callFormat.executeObject(frame, formatMethod, obj, formatSpec); if (!PGuards.isString(res)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.S_MUST_RETURN_S_NOT_P, T___FORMAT__, "str", res); } @@ -134,7 +134,6 @@ static Object doGeneric(VirtualFrame frame, Node inliningTarget, Object obj, Obj } } - // Note: PRaiseNode is @Exclusive to workaround a bug in DSL @Fallback static Object doNonStringFormat(Object obj, Object formatSpec, @Bind Node inliningTarget) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/PBytecodeRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/PBytecodeRootNode.java index fdf55dd4d2..4930eae2f5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/PBytecodeRootNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/PBytecodeRootNode.java @@ -61,8 +61,6 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.FormatNode; -import com.oracle.graal.python.builtins.modules.BuiltinFunctionsFactory.FormatNodeFactory.FormatNodeGen; import com.oracle.graal.python.builtins.modules.MarshalModuleBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.asyncio.GetAwaitableNode; @@ -147,6 +145,8 @@ import com.oracle.graal.python.lib.PyObjectAsciiAsObjectNodeGen; import com.oracle.graal.python.lib.PyObjectDelItem; import com.oracle.graal.python.lib.PyObjectDelItemNodeGen; +import com.oracle.graal.python.lib.PyObjectFormat; +import com.oracle.graal.python.lib.PyObjectFormatNodeGen; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyObjectGetAttrNodeGen; import com.oracle.graal.python.lib.PyObjectGetItem; @@ -403,7 +403,8 @@ public final class PBytecodeRootNode extends PRootNode implements BytecodeOSRNod private static final NodeSupplier NODE_REPR = PyObjectReprAsObjectNode::create; private static final PyObjectAsciiAsObjectNode UNCACHED_ASCII = PyObjectAsciiAsObjectNode.getUncached(); private static final NodeSupplier NODE_ASCII = PyObjectAsciiAsObjectNode::create; - private static final NodeSupplier NODE_FORMAT = FormatNode::create; + private static final PyObjectFormat UNCACHED_FORMAT = PyObjectFormat.getUncached(); + private static final NodeSupplier NODE_FORMAT = PyObjectFormat::create; private static final NodeSupplier NODE_SEND = SendNode::create; private static final NodeSupplier NODE_THROW = ThrowNode::create; private static final WriteGlobalNode UNCACHED_WRITE_GLOBAL = WriteGlobalNode.getUncached(); @@ -5009,7 +5010,7 @@ private int bytecodeFormatValue(VirtualFrame virtualFrame, int initialStackTop, default: assert type == FormatOptions.FVC_NONE; } - FormatNode formatNode = insertChildNode(localNodes, bci + 1, FormatNodeGen.class, NODE_FORMAT); + PyObjectFormat formatNode = insertChildNode(localNodes, bci + 1, UNCACHED_FORMAT, PyObjectFormatNodeGen.class, NODE_FORMAT, useCachedNodes); value = formatNode.execute(virtualFrame, value, spec); virtualFrame.setObject(stackTop, value); return stackTop; 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 49d39e2916..85c160d9e7 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 @@ -63,7 +63,6 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.BuiltinFunctions.FormatNode; import com.oracle.graal.python.builtins.modules.MarshalModuleBuiltins; import com.oracle.graal.python.builtins.modules.TypingModuleBuiltins.CallTypingFuncObjectNode; import com.oracle.graal.python.builtins.modules.TypingModuleBuiltins.UnpackTypeVarTuplesNode; @@ -156,6 +155,7 @@ import com.oracle.graal.python.lib.PyObjectAsciiAsObjectNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectDelItem; +import com.oracle.graal.python.lib.PyObjectFormat; import com.oracle.graal.python.lib.PyObjectFunctionStr; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyObjectGetItem; @@ -358,7 +358,7 @@ @OperationProxy(PyNumberInPlaceLshiftNode.class) @OperationProxy(PyNumberInPlaceRshiftNode.class) @OperationProxy(IsNode.class) -@OperationProxy(value = FormatNode.class, forceCached = true) +@OperationProxy(value = PyObjectFormat.class, name = "Format") @OperationProxy(ExceptMatchNode.class) @OperationProxy(value = HandleExceptionsInHandlerNode.class, forceCached = true) @OperationProxy(value = EncapsulateExceptionGroupNode.class, forceCached = true) From 7d5ae8c74139b316ead843a16c07ed26eea36d25 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 14 May 2026 15:45:58 +0200 Subject: [PATCH 5/6] Enable uncached for CopyDictWithoutKeys --- .../python/nodes/bytecode/CopyDictWithoutKeysNode.java | 10 ++++++---- .../nodes/bytecode_dsl/PBytecodeDSLRootNode.java | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/CopyDictWithoutKeysNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/CopyDictWithoutKeysNode.java index 4d1e27f40c..d0bd140a9b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/CopyDictWithoutKeysNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/CopyDictWithoutKeysNode.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 @@ -52,6 +52,7 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.Frame; @@ -60,11 +61,12 @@ import com.oracle.truffle.api.nodes.Node; @GenerateInline(false) // used in BCI root node -@OperationProxy.Proxyable(storeBytecodeIndex = true) +@OperationProxy.Proxyable(storeBytecodeIndex = true, allowUncached = true) +@GenerateUncached public abstract class CopyDictWithoutKeysNode extends PNodeWithContext { public abstract PDict execute(Frame frame, Object subject, Object[] keys); - @Specialization(guards = {"keys.length == keysLength", "keysLength <= 32"}, limit = "1") + @Specialization(guards = {"keys.length == keysLength", "keysLength <= 32"}, limit = "1", excludeForUncached = true) public static PDict copy(VirtualFrame frame, Object subject, @NeverDefault @SuppressWarnings("unused") Object[] keys, @Bind Node inliningTarget, @Cached("keys.length") int keysLength, @@ -85,7 +87,7 @@ private static void deleteKeys(VirtualFrame frame, Node inliningTarget, Object[] } } - @Specialization(guards = "keys.length > 32") + @Specialization public static PDict copy(VirtualFrame frame, Object subject, Object[] keys, @Bind Node inliningTarget, @Bind PythonLanguage language, 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 85c160d9e7..bca9133a6c 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 @@ -368,7 +368,7 @@ @OperationProxy(GetAIterNode.class) @OperationProxy(GetANextNode.class) @OperationProxy(value = ReadGlobalOrBuiltinNode.class, name = "ReadGlobal") -@OperationProxy(value = CopyDictWithoutKeysNode.class, name = "CopyDictWithoutKeys", forceCached = true) +@OperationProxy(value = CopyDictWithoutKeysNode.class, name = "CopyDictWithoutKeys") @OperationProxy(value = PyObjectIsTrueNode.class, name = "Yes") @OperationProxy(value = PyObjectIsNotTrueNode.class, name = "Not") @OperationProxy(value = ListNodes.AppendNode.class, name = "ListAppend") From dcf9bafd43b65be3bdf1ef18438e21f8f2910e89 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 15 May 2026 10:22:00 +0200 Subject: [PATCH 6/6] Fix specialization inconsistency in dict view xor/sub --- .../python/builtins/objects/dict/DictViewBuiltins.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/dict/DictViewBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/dict/DictViewBuiltins.java index 387c1796e2..74557d6a7d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/dict/DictViewBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/dict/DictViewBuiltins.java @@ -62,6 +62,7 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageCopy; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageDiff; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageForEach; +import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageForEachCallback; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItem; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItemWithHash; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetIterator; @@ -75,7 +76,6 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageSetItem; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageTransferItem; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageXor; -import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageForEachCallback; import com.oracle.graal.python.builtins.objects.common.ObjectHashMap; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.dict.DictViewBuiltinsFactory.ContainedInNodeGen; @@ -458,7 +458,7 @@ static PBaseSet doItemsView(VirtualFrame frame, PDictItemsView self, PBaseSet ot return PFactory.createSet(language, storage); } - @Specialization + @Fallback static PBaseSet doGeneric(VirtualFrame frame, Object self, Object other, @Bind Node inliningTarget, @Shared("constrSet") @Cached SetNodes.ConstructSetNode constructSetNode, @@ -670,7 +670,7 @@ private static HashingStorage getKeysViewOrSetStorage(Object self) { return ((PBaseSet) self).getDictStorage(); } - @Specialization(guards = {"!isDictKeysView(self)", "!isAnySet(self)"}) + @Fallback static PBaseSet doGeneric(VirtualFrame frame, Object self, Object other, @Bind Node inliningTarget, @Cached SetNodes.ConstructSetNode constructSetNode,