From fa9e5c5b9a1d119e0f8cf962a2446f7fd400ed21 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 18 Sep 2026 12:29:31 +0200 Subject: [PATCH 1/2] Convert PyList_(New|Append|Size|GetItemRef) to static upcalls --- .../modules/cext/PythonCextListBuiltins.java | 144 +++++++++--------- .../objects/common/SequenceStorageNodes.java | 11 ++ 2 files changed, 83 insertions(+), 72 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java index 7b25500031..86efd1aa10 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java @@ -41,6 +41,7 @@ package com.oracle.graal.python.builtins.modules.cext; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.IndexError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; @@ -68,9 +69,14 @@ import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.EnsurePythonObjectNode; import com.oracle.graal.python.builtins.objects.cext.capi.PySequenceArrayWrapper; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemScalarNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.ListGeneralizationNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.SetItemScalarNode; +import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.ListBuiltins; import com.oracle.graal.python.builtins.objects.list.ListBuiltins.ListExtendNode; import com.oracle.graal.python.builtins.objects.list.ListBuiltins.ListInsertNode; @@ -95,31 +101,31 @@ public final class PythonCextListBuiltins { - @CApiBuiltin(ret = PyObjectTransfer, args = {Py_ssize_t}, call = Direct) - abstract static class PyList_New extends CApiUnaryBuiltinNode { - @Specialization(guards = "size < 0") - static Object newListError(long size, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, SystemError, BAD_ARG_TO_INTERNAL_FUNC_S, size); - } - - @SuppressWarnings("unused") - @Specialization(guards = "size == 0") - static Object newEmptyList(long size, - @Bind PythonLanguage language) { - return PFactory.createList(language); - } + private static final CApiTiming TIMING_PYLIST_NEW = CApiTiming.create(false, "PyList_New"); - @Specialization(guards = "size > 0") - static Object newList(long size, - @Bind PythonLanguage language) { - return PFactory.createList(language, array(size)); - } + @CApiBuiltin(ret = PyObjectTransfer, args = {Py_ssize_t}, call = Direct, acquireGil = false) + static long PyList_New(long size) { + CApiTiming.enter(); + try { + if (size < 0) { + throw PRaiseNode.raiseStatic(null, SystemError, BAD_ARG_TO_INTERNAL_FUNC_S, size); + } - private static Object[] array(long size) { - Object[] a = new Object[(int) size]; - Arrays.fill(a, PNone.NO_VALUE); - return a; + PythonLanguage language = PythonLanguage.get(null); + PList result; + if (size == 0) { + result = PFactory.createList(language); + } else { + if (!PInt.fitsInInt(size)) { + throw PRaiseNode.raiseStatic(null, MemoryError); + } + Object[] a = new Object[(int) size]; + Arrays.fill(a, PNone.NO_VALUE); + result = PFactory.createList(language, a); + } + return PythonToNativeInternalNode.executeNewRefUncached(result); + } finally { + CApiTiming.exit(TIMING_PYLIST_NEW); } } @@ -157,58 +163,50 @@ Object fallback(Object list, @SuppressWarnings("unused") Object pos) { } } - @CApiBuiltin(ret = PyObjectTransfer, args = {PyObject, Py_ssize_t}, call = Direct) - abstract static class PyList_GetItemRef extends CApiBinaryBuiltinNode { + private static final CApiTiming TIMING_PYLIST_GETITEMREF = CApiTiming.create(false, "PyList_GetItemRef"); - @Specialization - static Object doPList(PList list, long key, - @Bind Node inliningTarget, - @Bind PythonContext context, - @Cached EnsurePythonObjectNode ensureNode, - @Cached ListGeneralizationNode generalizationNode, - @Cached SetItemScalarNode setItemNode, - @Cached GetItemScalarNode getItemNode, - @Cached PRaiseNode raiseNode) { + @CApiBuiltin(ret = PyObjectTransfer, args = {PyObject, Py_ssize_t}, call = Direct) + static long PyList_GetItemRef(long opPtr, long key) { + CApiTiming.enter(); + try { + Object op = NativeToPythonInternalNode.executeUncached(opPtr, false); + if (!(op instanceof PList list)) { + throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_A_LIST); + } SequenceStorage sequenceStorage = list.getSequenceStorage(); // we must do a bounds-check but we must not normalize the index if (key < 0 || key >= sequenceStorage.length()) { - throw raiseNode.raise(inliningTarget, IndexError, ErrorMessages.LIST_INDEX_OUT_OF_RANGE); + throw PRaiseNode.raiseStatic(null, IndexError, ErrorMessages.LIST_INDEX_OUT_OF_RANGE); } - Object result = getItemNode.execute(inliningTarget, sequenceStorage, (int) key); + Object result = GetItemScalarNode.executeUncached(sequenceStorage, (int) key); // See the note in PyDict_GetItemRef - Object promotedValue = ensureNode.execute(context, result, false); + Object promotedValue = EnsurePythonObjectNode.executeUncached(PythonContext.get(null), result, false); if (promotedValue != result) { - sequenceStorage = generalizationNode.execute(inliningTarget, sequenceStorage, promotedValue); + sequenceStorage = ListGeneralizationNode.executeUncached(sequenceStorage, promotedValue); list.setSequenceStorage(sequenceStorage); - setItemNode.execute(inliningTarget, sequenceStorage, (int) key, promotedValue); - return promotedValue; + SetItemScalarNode.executeUncached(sequenceStorage, (int) key, promotedValue); } - return result; - } - - @Fallback - static Object fallback(@SuppressWarnings("unused") Object list, @SuppressWarnings("unused") Object pos, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.EXPECTED_A_LIST); + return PythonToNativeInternalNode.executeNewRefUncached(promotedValue); + } finally { + CApiTiming.exit(TIMING_PYLIST_GETITEMREF); } } - @CApiBuiltin(ret = Int, args = {PyObject, PyObject}, call = Direct) - abstract static class PyList_Append extends CApiBinaryBuiltinNode { + private static final CApiTiming TIMING_PYLIST_APPEND = CApiTiming.create(false, "PyList_Append"); - @Specialization - int append(PList list, Object newItem, - @Cached AppendNode appendNode) { - if (newItem == PNone.NO_VALUE) { - throw badInternalCall("newitem"); + @CApiBuiltin(ret = Int, args = {PyObject, PyObject}, call = Direct) + static int PyList_Append(long opPtr, long itemPtr) { + CApiTiming.enter(); + try { + Object op = NativeToPythonInternalNode.executeUncached(opPtr, false); + Object item = NativeToPythonInternalNode.executeUncached(itemPtr, false); + if (op instanceof PList list && item != PNone.NO_VALUE) { + AppendNode.appendObjectGeneric(list, item, null, SequenceStorageNodes.AppendNode.getUncached(), AppendNode.getUpdateStoreProfileUncached()); + return 0; } - appendNode.execute(list, newItem); - return 0; - } - - @Fallback - int fallback(Object list, @SuppressWarnings("unused") Object newItem) { - throw raiseFallback(list, PythonBuiltinClassType.PList); + throw PythonCextBuiltins.badInternalCall("PyList_Append", "op"); + } finally { + CApiTiming.exit(TIMING_PYLIST_APPEND); } } @@ -293,21 +291,23 @@ Object fallback(Object list, @SuppressWarnings("unused") Object iterable) { } } + private static final CApiTiming TIMING_PYLIST_SIZE = CApiTiming.create(false, "PyList_Size"); + /* * A pure-C Py_SIZE implementation regressed mixed managed/native/list-subclass workload by * about 1.26x. */ - @CApiBuiltin(ret = Py_ssize_t, args = {PyObject}, call = Direct) - abstract static class PyList_Size extends CApiUnaryBuiltinNode { - - @Specialization - static long size(PList list) { - return list.getSequenceStorage().length(); - } - - @Fallback - long fallback(Object list) { - throw raiseFallback(list, PythonBuiltinClassType.PList); + @CApiBuiltin(ret = Py_ssize_t, args = {PyObject}, call = Direct, acquireGil = false) + static long PyList_Size(long opPtr) { + CApiTiming.enter(); + try { + Object op = NativeToPythonInternalNode.executeUncached(opPtr, false); + if (op instanceof PList list) { + return list.getSequenceStorage().length(); + } + throw PythonCextBuiltins.badInternalCall("PyList_Size", "op"); + } finally { + CApiTiming.exit(TIMING_PYLIST_SIZE); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceStorageNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceStorageNodes.java index 0851c13bdf..bc65651a20 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceStorageNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceStorageNodes.java @@ -80,6 +80,7 @@ import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.RepeatNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.SetItemDynamicNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.SetItemNodeGen; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.SetItemScalarNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.StorageToNativeNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.ToArrayNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodesFactory.ToByteArrayNodeGen; @@ -1324,6 +1325,11 @@ static void doError(@SuppressWarnings("unused") Node inliningTarget, SequenceSto @GenerateCached(false) public abstract static class SetItemScalarNode extends AbstractSetItemScalarNode { + @TruffleBoundary + public static void executeUncached(SequenceStorage storage, int idx, Object value) { + SetItemScalarNodeGen.getUncached().execute(null, storage, idx, value); + } + @InliningCutoff @Specialization protected static void doNative(NativeSequenceStorage storage, int idx, Object value, @@ -2830,6 +2836,11 @@ public GeneralizationNode create() { } }; + @TruffleBoundary + public static SequenceStorage executeUncached(SequenceStorage toGeneralize, Object indicationValue) { + return ListGeneralizationNodeGen.getUncached().execute(null, toGeneralize, indicationValue); + } + @Override public final SequenceStorage executeCached(SequenceStorage toGeneralize, Object indicationValue) { return execute(this, toGeneralize, indicationValue); From 08289946f26d43a53800c4118792d47628b6bad8 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 2 Oct 2026 15:44:26 +0200 Subject: [PATCH 2/2] Split ListNodes.AppendNode --- .../modules/cext/PythonCextListBuiltins.java | 5 +- .../python/nodes/builtins/ListNodes.java | 70 ++++++++++++------- .../bytecode_dsl/PBytecodeDSLRootNode.java | 2 +- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java index 86efd1aa10..6ba585bbd5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextListBuiltins.java @@ -72,7 +72,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; -import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemScalarNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.ListGeneralizationNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.SetItemScalarNode; @@ -85,7 +84,7 @@ import com.oracle.graal.python.lib.PySliceNew; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.builtins.ListNodes.AppendNode; +import com.oracle.graal.python.nodes.builtins.ListNodes.PListAppendNode; import com.oracle.graal.python.nodes.builtins.TupleNodes.ConstructTupleNode; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.object.PFactory; @@ -201,7 +200,7 @@ static int PyList_Append(long opPtr, long itemPtr) { Object op = NativeToPythonInternalNode.executeUncached(opPtr, false); Object item = NativeToPythonInternalNode.executeUncached(itemPtr, false); if (op instanceof PList list && item != PNone.NO_VALUE) { - AppendNode.appendObjectGeneric(list, item, null, SequenceStorageNodes.AppendNode.getUncached(), AppendNode.getUpdateStoreProfileUncached()); + PListAppendNode.executeUncached(list, item); return 0; } throw PythonCextBuiltins.badInternalCall("PyList_Append", "op"); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java index 132725a9d4..ec4558b1e0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java @@ -61,6 +61,7 @@ import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.builtins.ListNodesFactory.AppendNodeGen; import com.oracle.graal.python.nodes.builtins.ListNodesFactory.ConstructListNodeGen; +import com.oracle.graal.python.nodes.builtins.ListNodesFactory.PListAppendNodeGen; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.object.IsForeignObjectNode; @@ -74,7 +75,6 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.bytecode.OperationProxy; -import com.oracle.truffle.api.bytecode.StoreBytecodeIndex; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Exclusive; @@ -260,6 +260,39 @@ protected PList doGeneric(VirtualFrame frame, Object value, } + @GenerateUncached + @GenerateInline(false) // footprint reduction 36 -> 17 + public abstract static class AppendNode extends PNodeWithContext { + + public abstract void execute(Object list, Object value); + + @Specialization + static void appendObjectGeneric(PList list, Object value, + @Bind Node inliningTarget, + @Cached PListAppendNode appendNode) { + appendNode.execute(list, value); + } + + @Fallback + static void appendObjectForeign(Object list, Object value, + @Bind Node inliningTarget, + @Cached GetListStorageNode getStorageNode, + @Cached SequenceStorageNodes.AppendNode appendNode) { + var storage = getStorageNode.execute(inliningTarget, list); + SequenceStorage newStore = appendNode.execute(inliningTarget, storage, value, ListGeneralizationNode.SUPPLIER); + assert newStore == storage; + } + + @NeverDefault + public static AppendNode create() { + return AppendNodeGen.create(); + } + + public static AppendNode getUncached() { + return AppendNodeGen.getUncached(); + } + } + /** * This node takes a bit of care to avoid compiling code for switching storages. In the * interpreter, it will use a different {@link AppendNode} than in the compiled code, so the @@ -269,12 +302,16 @@ protected PList doGeneric(VirtualFrame frame, Object value, * code will only see lists of the correct size and storage type. */ @GenerateUncached - @GenerateInline(false) // footprint reduction 36 -> 17 + @GenerateInline(false) @OperationProxy.Proxyable(allowUncached = true, storeBytecodeIndex = false) - public abstract static class AppendNode extends PNodeWithContext { + public abstract static class PListAppendNode extends PNodeWithContext { private static final BranchProfile[] DISABLED = new BranchProfile[]{BranchProfile.getUncached()}; - public abstract void execute(Object list, Object value); + public static void executeUncached(PList list, Object value) { + PListAppendNodeGen.getUncached().execute(list, value); + } + + public abstract void execute(PList list, Object value); @NeverDefault public static BranchProfile[] getUpdateStoreProfile() { @@ -286,10 +323,9 @@ public static BranchProfile[] getUpdateStoreProfileUncached() { } @Specialization - public static void appendObjectGeneric(PList list, Object value, + public static void doPList(PList list, Object value, @Bind Node inliningTarget, - // @Exclusive for truffle-interpreted-performance - @Exclusive @Cached SequenceStorageNodes.AppendNode appendNode, + @Cached SequenceStorageNodes.AppendNode appendNode, @Cached(value = "getUpdateStoreProfile()", uncached = "getUpdateStoreProfileUncached()", dimensions = 1) BranchProfile[] updateStoreProfile) { if (updateStoreProfile[0] == null) { // Executed for the first time. We don't pollute the AppendNode specializations, @@ -313,26 +349,6 @@ public static void appendObjectGeneric(PList list, Object value, } } } - - @Fallback - @StoreBytecodeIndex - public static void appendObjectForeign(Object list, Object value, - @Bind Node inliningTarget, - @Cached GetListStorageNode getStorageNode, - @Exclusive @Cached SequenceStorageNodes.AppendNode appendNode) { - var storage = getStorageNode.execute(inliningTarget, list); - SequenceStorage newStore = appendNode.execute(inliningTarget, storage, value, ListGeneralizationNode.SUPPLIER); - assert newStore == storage; - } - - @NeverDefault - public static AppendNode create() { - return AppendNodeGen.create(); - } - - public static AppendNode getUncached() { - return AppendNodeGen.getUncached(); - } } @GenerateInline(false) 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 305c13bb32..c2dfed6a62 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 @@ -391,7 +391,7 @@ @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") +@OperationProxy(value = ListNodes.PListAppendNode.class, name = "ListAppend") @OperationProxy(value = SetNodes.AddNode.class, name = "SetAdd") @ShortCircuitOperation(name = "BoolAnd", booleanConverter = PyObjectIsTrueNode.class, operator = Operator.AND_RETURN_VALUE) @ShortCircuitOperation(name = "BoolOr", booleanConverter = PyObjectIsTrueNode.class, operator = Operator.OR_RETURN_VALUE)