diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/AbstractCallMethodNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/AbstractCallMethodNode.java index e85f993f7a..6f05ec45a5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/AbstractCallMethodNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/AbstractCallMethodNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -40,7 +40,6 @@ */ package com.oracle.graal.python.nodes.call.special; -import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Slot.SlotSignature; import com.oracle.graal.python.annotations.Builtin; import com.oracle.graal.python.builtins.objects.PNone; @@ -50,7 +49,6 @@ import com.oracle.graal.python.builtins.objects.type.slots.TpSlot.TpSlotBuiltin; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; -import com.oracle.graal.python.nodes.PRootNode; import com.oracle.graal.python.nodes.builtins.FunctionNodes.GetCallTargetNode; import com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; @@ -59,7 +57,6 @@ import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.runtime.PythonOptions; -import com.oracle.graal.python.util.PythonUtils.NodeCounterWithLimit; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.RootCallTarget; @@ -67,7 +64,6 @@ import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.NodeField; import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; @ImportStatic({PythonOptions.class, PGuards.class}) @@ -142,53 +138,12 @@ private static int getBuiltinNodeArity(Class no } private boolean callerExceedsMaxSize(T builtinNode) { - CompilerAsserts.neverPartOfCompilation(); - if (isAdoptable() && !isMaxSizeExceeded()) { - // to avoid building up AST of recursive builtin calls we check that the same builtin - // isn't already our parent. - Class builtinClass = builtinNode.getClass(); - Node parent = getParent(); - int recursiveCalls = 0; - PythonLanguage language = PythonLanguage.get(this); - while (parent != null && !(parent instanceof RootNode)) { - if (parent.getClass() == builtinClass) { - int recursionLimit = language.getEngineOption(PythonOptions.NodeRecursionLimit); - if (recursiveCalls == recursionLimit) { - return true; - } - recursiveCalls++; - } - parent = parent.getParent(); - } - - RootNode root = getRootNode(); - // nb: option 'BuiltinsInliningMaxCallerSize' is defined as a compatible option, i.e., - // ASTs will only be shared between contexts that have the same value for this option. - int maxSize = language.getEngineOption(PythonOptions.BuiltinsInliningMaxCallerSize); - if (root instanceof PRootNode) { - PRootNode pRoot = (PRootNode) root; - int rootNodeCount = pRoot.getNodeCountForInlining(); - if (rootNodeCount < maxSize) { - NodeCounterWithLimit counter = new NodeCounterWithLimit(rootNodeCount, maxSize); - builtinNode.accept(counter); - if (counter.isOverLimit()) { - setMaxSizeExceeded(true); - return true; - } - pRoot.setNodeCountForInlining(counter.getCount()); - } - } else { - NodeCounterWithLimit counter = new NodeCounterWithLimit(maxSize); - root.accept(counter); - if (!counter.isOverLimit()) { - builtinNode.accept(counter); - } - if (counter.isOverLimit()) { - setMaxSizeExceeded(true); - return true; - } + if (!isMaxSizeExceeded()) { + BuiltinInliningPolicy.CallerSizeCheck result = BuiltinInliningPolicy.checkCallerSize(this, builtinNode); + if (result == BuiltinInliningPolicy.CallerSizeCheck.EXCEEDS_MAX_SIZE) { + setMaxSizeExceeded(true); } - return false; + return BuiltinInliningPolicy.exceedsCallerSize(result); } return true; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/BuiltinInliningPolicy.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/BuiltinInliningPolicy.java new file mode 100644 index 0000000000..13ca02a8eb --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/BuiltinInliningPolicy.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.nodes.call.special; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.nodes.PRootNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.runtime.PythonOptions; +import com.oracle.graal.python.util.PythonUtils.NodeCounterWithLimit; +import com.oracle.truffle.api.CompilerAsserts; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; + +final class BuiltinInliningPolicy { + + enum CallerSizeCheck { + OK, + EXCEEDS_MAX_SIZE, + DISABLED + } + + private BuiltinInliningPolicy() { + } + + static CallerSizeCheck checkCallerSize(Node caller, T builtinNode) { + CompilerAsserts.neverPartOfCompilation(); + if (caller.isAdoptable()) { + // To avoid building up ASTs of recursive builtin calls, check that the same builtin + // isn't already the call node's parent. + Class builtinClass = builtinNode.getClass(); + Node parent = caller.getParent(); + int recursiveCalls = 0; + PythonLanguage language = PythonLanguage.get(caller); + while (parent != null && !(parent instanceof RootNode)) { + if (parent.getClass() == builtinClass) { + int recursionLimit = language.getEngineOption(PythonOptions.NodeRecursionLimit); + if (recursiveCalls == recursionLimit) { + return CallerSizeCheck.DISABLED; + } + recursiveCalls++; + } + parent = parent.getParent(); + } + + RootNode root = caller.getRootNode(); + // nb: option 'BuiltinsInliningMaxCallerSize' is defined as a compatible option, i.e., + // ASTs will only be shared between contexts that have the same value for this option. + int maxSize = language.getEngineOption(PythonOptions.BuiltinsInliningMaxCallerSize); + if (root instanceof PRootNode) { + PRootNode pRoot = (PRootNode) root; + int rootNodeCount = pRoot.getNodeCountForInlining(); + if (rootNodeCount < maxSize) { + NodeCounterWithLimit counter = new NodeCounterWithLimit(rootNodeCount, maxSize); + builtinNode.accept(counter); + if (counter.isOverLimit()) { + return CallerSizeCheck.EXCEEDS_MAX_SIZE; + } + pRoot.setNodeCountForInlining(counter.getCount()); + } + } else { + NodeCounterWithLimit counter = new NodeCounterWithLimit(maxSize); + root.accept(counter); + if (!counter.isOverLimit()) { + builtinNode.accept(counter); + } + if (counter.isOverLimit()) { + return CallerSizeCheck.EXCEEDS_MAX_SIZE; + } + } + return CallerSizeCheck.OK; + } + return CallerSizeCheck.DISABLED; + } + + static boolean exceedsCallerSize(CallerSizeCheck result) { + return result != CallerSizeCheck.OK; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallBinaryNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallBinaryNode.java index 06e3b6a38e..58d02f3bcd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallBinaryNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallBinaryNode.java @@ -44,16 +44,17 @@ import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.runtime.PythonOptions; -import com.oracle.truffle.api.CompilerDirectives; +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.Cached.Exclusive; import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NeverDefault; -import com.oracle.truffle.api.dsl.ReportPolymorphism.Megamorphic; +import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; @@ -67,7 +68,6 @@ */ @ImportStatic(PythonOptions.class) public abstract class LookupAndCallBinaryNode extends Node { - @Child private CallBinaryMethodNode dispatchNode; protected final TruffleString name; LookupAndCallBinaryNode(TruffleString name) { @@ -81,26 +81,26 @@ public static LookupAndCallBinaryNode create(TruffleString name) { return LookupAndCallBinaryNodeGen.create(name); } - protected final CallBinaryMethodNode ensureDispatch() { - // this also serves as a branch profile - if (dispatchNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - dispatchNode = insert(CallBinaryMethodNode.create()); - } - return dispatchNode; - } - protected final PythonBinaryBuiltinNode getBinaryBuiltin(PythonBuiltinClassType clazz) { + CompilerAsserts.neverPartOfCompilation(); Object attribute = LookupAttributeInMRONode.Dynamic.getUncached().execute(clazz, name); if (attribute instanceof PBuiltinFunction) { PBuiltinFunction builtinFunction = (PBuiltinFunction) attribute; - if (PythonBinaryBuiltinNode.class.isAssignableFrom(builtinFunction.getBuiltinNodeFactory().getNodeClass())) { - return (PythonBinaryBuiltinNode) builtinFunction.getBuiltinNodeFactory().createNode(); + NodeFactory builtinNodeFactory = builtinFunction.getBuiltinNodeFactory(); + if (builtinNodeFactory != null && PythonBinaryBuiltinNode.class.isAssignableFrom(builtinNodeFactory.getNodeClass())) { + PythonBinaryBuiltinNode builtinNode = (PythonBinaryBuiltinNode) builtinNodeFactory.createNode(); + if (!callerExceedsMaxSize(builtinNode)) { + return builtinNode; + } } } return null; } + private boolean callerExceedsMaxSize(T builtinNode) { + return BuiltinInliningPolicy.exceedsCallerSize(BuiltinInliningPolicy.checkCallerSize(this, builtinNode)); + } + protected static PythonBuiltinClassType getBuiltinClass(Node inliningTarget, Object receiver, GetClassNode getClassNode) { Object clazz = getClassNode.execute(inliningTarget, receiver); return clazz instanceof PythonBuiltinClassType ? (PythonBuiltinClassType) clazz : null; @@ -112,7 +112,7 @@ protected static boolean isClazz(Node inliningTarget, PythonBuiltinClassType cla // Object, Object - @Specialization(guards = {"clazz != null", "function != null", "isClazz(inliningTarget, clazz, left, getClassNode)"}, limit = "getCallSiteInlineCacheMaxDepth()") + @Specialization(guards = {"clazz != null", "function != null", "isClazz(inliningTarget, clazz, left, getClassNode)"}, limit = "1") static Object callObjectBuiltin(VirtualFrame frame, Object left, Object right, @SuppressWarnings("unused") @Bind Node inliningTarget, @SuppressWarnings("unused") @Exclusive @Cached GetClassNode getClassNode, @@ -121,37 +121,19 @@ static Object callObjectBuiltin(VirtualFrame frame, Object left, Object right, return function.execute(frame, left, right); } - @Specialization(guards = {"left.getClass() == cachedLeftClass", "right.getClass() == cachedRightClass"}, limit = "5") - @SuppressWarnings("truffle-static-method") - Object callObjectGeneric(VirtualFrame frame, Object left, Object right, - @Bind Node inliningTarget, - @SuppressWarnings("unused") @Cached("left.getClass()") Class cachedLeftClass, - @SuppressWarnings("unused") @Cached("right.getClass()") Class cachedRightClass, - @Exclusive @Cached InlinedBranchProfile notFoundProfile, - @Exclusive @Cached GetClassNode getClassNode, - @Exclusive @Cached("create(name)") LookupSpecialMethodNode getattr) { - return doCallObject(frame, inliningTarget, notFoundProfile, left, right, getClassNode, getattr); - } - - @Specialization(replaces = "callObjectGeneric") - @Megamorphic - @SuppressWarnings("truffle-static-method") - Object callObjectMegamorphic(VirtualFrame frame, Object left, Object right, + @Specialization(replaces = "callObjectBuiltin") + static Object callObject(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, @Exclusive @Cached InlinedBranchProfile notFoundProfile, @Exclusive @Cached GetClassNode getClassNode, - @Exclusive @Cached("create(name)") LookupSpecialMethodNode getattr) { - return doCallObject(frame, inliningTarget, notFoundProfile, left, right, getClassNode, getattr); - } - - private Object doCallObject(VirtualFrame frame, Node inliningTarget, InlinedBranchProfile notFoundProfile, Object left, Object right, GetClassNode getClassNode, - LookupSpecialMethodNode getattr) { + @Exclusive @Cached("create(name)") LookupSpecialMethodNode getattr, + @Cached CallBinaryMethodNode dispatchNode) { Object leftClass = getClassNode.execute(inliningTarget, left); Object leftCallable = getattr.execute(frame, leftClass, left); if (PGuards.isNoValue(leftCallable)) { notFoundProfile.enter(inliningTarget); throw SpecialMethodNotFound.INSTANCE; } - return ensureDispatch().executeObject(frame, leftCallable, left, right); + return dispatchNode.executeObject(frame, leftCallable, left, right); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallUnaryNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallUnaryNode.java index 7b34b7e4a3..b6c16f5c27 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallUnaryNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/LookupAndCallUnaryNode.java @@ -46,18 +46,21 @@ import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode; import com.oracle.graal.python.nodes.expression.UnaryOpNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.runtime.PythonOptions; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Exclusive; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NeverDefault; -import com.oracle.truffle.api.dsl.ReportPolymorphism.Megamorphic; +import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; @@ -90,16 +93,25 @@ public TruffleString getMethodName() { } protected final PythonUnaryBuiltinNode getUnaryBuiltin(PythonBuiltinClassType clazz) { + CompilerAsserts.neverPartOfCompilation(); Object attribute = LookupAttributeInMRONode.Dynamic.getUncached().execute(clazz, name); if (attribute instanceof PBuiltinFunction) { PBuiltinFunction builtinFunction = (PBuiltinFunction) attribute; - if (PythonUnaryBuiltinNode.class.isAssignableFrom(builtinFunction.getBuiltinNodeFactory().getNodeClass())) { - return (PythonUnaryBuiltinNode) builtinFunction.getBuiltinNodeFactory().createNode(); + NodeFactory builtinNodeFactory = builtinFunction.getBuiltinNodeFactory(); + if (builtinNodeFactory != null && PythonUnaryBuiltinNode.class.isAssignableFrom(builtinNodeFactory.getNodeClass())) { + PythonUnaryBuiltinNode builtinNode = (PythonUnaryBuiltinNode) builtinNodeFactory.createNode(); + if (!callerExceedsMaxSize(builtinNode)) { + return builtinNode; + } } } return null; } + private boolean callerExceedsMaxSize(T builtinNode) { + return BuiltinInliningPolicy.exceedsCallerSize(BuiltinInliningPolicy.checkCallerSize(this, builtinNode)); + } + protected static PythonBuiltinClassType getBuiltinClass(Node inliningTarget, Object receiver, GetClassNode getClassNode) { Object clazz = getClassNode.execute(inliningTarget, receiver); return clazz instanceof PythonBuiltinClassType ? (PythonBuiltinClassType) clazz : null; @@ -111,7 +123,7 @@ protected static boolean isClazz(Node inliningTarget, PythonBuiltinClassType cla // Object - @Specialization(guards = {"clazz != null", "function != null", "isClazz(inliningTarget, clazz, receiver, getClassNode)"}, limit = "getCallSiteInlineCacheMaxDepth()") + @Specialization(guards = {"clazz != null", "function != null", "isClazz(inliningTarget, clazz, receiver, getClassNode)"}, limit = "1") static Object callObjectBuiltin(VirtualFrame frame, Object receiver, @SuppressWarnings("unused") @Bind Node inliningTarget, @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, @@ -120,37 +132,17 @@ static Object callObjectBuiltin(VirtualFrame frame, Object receiver, return function.execute(frame, receiver); } - @Specialization(guards = "getObjectClass(receiver) == cachedClass", limit = "3") - Object callObjectGeneric(VirtualFrame frame, Object receiver, - @Bind Node inliningTarget, - @SuppressWarnings("unused") @Cached("receiver.getClass()") Class cachedClass, - @Shared @Cached InlinedBranchProfile notFoundProfile, - @Shared @Cached GetClassNode getClassNode, - @Shared @Cached("create(name)") LookupSpecialMethodNode getattr, - @Shared @Cached CallUnaryMethodNode dispatchNode) { - return doCallObject(frame, inliningTarget, notFoundProfile, receiver, getClassNode, getattr, dispatchNode); - } - - @Specialization(replaces = "callObjectGeneric") - @Megamorphic + @Specialization(replaces = "callObjectBuiltin") @InliningCutoff @SuppressWarnings("truffle-static-method") - Object callObjectMegamorphic(VirtualFrame frame, Object receiver, + Object callObject(VirtualFrame frame, Object receiver, @Bind Node inliningTarget, - @Shared @Cached InlinedBranchProfile notFoundProfile, - @Shared @Cached GetClassNode getClassNode, - @Shared @Cached("create(name)") LookupSpecialMethodNode getattr, - @Shared @Cached CallUnaryMethodNode dispatchNode) { - return doCallObject(frame, inliningTarget, notFoundProfile, receiver, getClassNode, getattr, dispatchNode); - } - - protected Class getObjectClass(Object object) { - return object.getClass(); - } - - private Object doCallObject(VirtualFrame frame, Node inliningTarget, InlinedBranchProfile notFoundProfile, - Object receiver, GetClassNode getClassNode, LookupSpecialMethodNode getattr, CallUnaryMethodNode dispatchNode) { - Object attr = getattr.execute(frame, getClassNode.execute(inliningTarget, receiver), receiver); + @Cached InlinedBranchProfile notFoundProfile, + @Exclusive @Cached GetClassNode getClassNode, + @Cached("create(name)") LookupSpecialMethodNode getattr, + @Cached CallUnaryMethodNode dispatchNode) { + Object receiverClass = getClassNode.execute(inliningTarget, receiver); + Object attr = getattr.execute(frame, receiverClass, receiver); if (attr == PNone.NO_VALUE) { notFoundProfile.enter(inliningTarget); throw SpecialMethodNotFound.INSTANCE;