diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py index 90b1542327..5a0681b559 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py @@ -1764,3 +1764,70 @@ def test_pickle_native(self): pass else: assert False, "Expected TypeError" + + def test_pickle_native_getnewargs(self): + import pickle + + TestPicklableWithNewArgs = CPyExtType( + "TestPicklableWithNewArgs", + """ + static PyObject* test_pickle_new(PyTypeObject* cls, PyObject* args, PyObject* kwargs) { + return PyType_GenericNew(cls, NULL, NULL); + } + + static PyObject* test_getnewargs(PyObject* self) { + PyObject* value = PyLong_FromLong(42); + if (value == NULL) + return NULL; + PyObject* result = PyTuple_Pack(1, value); + Py_DECREF(value); + return result; + } + """, + tp_new="test_pickle_new", + tp_methods='{"__getnewargs__", (PyCFunction)test_getnewargs, METH_NOARGS, ""}', + ) + + obj = TestPicklableWithNewArgs() + data = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL) + assert type(pickle.loads(data)) == TestPicklableWithNewArgs + + def test_pickle_native_getnewargs_ex(self): + import pickle + + TestPicklableWithNewArgsEx = CPyExtType( + "TestPicklableWithNewArgsEx", + """ + static PyObject* test_pickle_new(PyTypeObject* cls, PyObject* args, PyObject* kwargs) { + return PyType_GenericNew(cls, NULL, NULL); + } + + static PyObject* test_getnewargs_ex(PyObject* self) { + PyObject* value = PyLong_FromLong(42); + if (value == NULL) + return NULL; + PyObject* args = PyTuple_Pack(1, value); + if (args == NULL) { + Py_DECREF(value); + return NULL; + } + PyObject* kwargs = PyDict_New(); + if (kwargs == NULL) { + Py_DECREF(value); + Py_DECREF(args); + return NULL; + } + PyObject* result = PyTuple_Pack(2, args, kwargs); + Py_DECREF(value); + Py_DECREF(args); + Py_DECREF(kwargs); + return result; + } + """, + tp_new="test_pickle_new", + tp_methods='{"__getnewargs_ex__", (PyCFunction)test_getnewargs_ex, METH_NOARGS, ""}', + ) + + obj = TestPicklableWithNewArgsEx() + data = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL) + assert type(pickle.loads(data)) == TestPicklableWithNewArgsEx diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_tuple.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_tuple.py index c91d5015bc..7e7902b682 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_tuple.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_tuple.py @@ -1,4 +1,4 @@ -# 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 @@ -147,7 +147,7 @@ class TestPyTuple(CPyExtTestCase): arguments=["PyObject* tuple", "Py_ssize_t index"], resulttype="PyObject*", ) - + test_PyTuple_SetItem = CPyExtFunction( _reference_setitem, lambda: ( @@ -294,6 +294,36 @@ class TestPyTuple(CPyExtTestCase): ) +class TestNativeTupleStorage(unittest.TestCase): + def test_marshal_native_tuple(self): + import marshal + + TestNativeTupleFactory = CPyExtType( + "TestNativeTupleFactory", + """ + static PyObject* get_tuple(PyObject* cls) { + PyObject* value = PyLong_FromLong(42); + if (value == NULL) + return NULL; + PyObject* text = PyUnicode_FromString("native"); + if (text == NULL) { + Py_DECREF(value); + return NULL; + } + PyObject* result = PyTuple_Pack(2, value, text); + Py_DECREF(value); + Py_DECREF(text); + return result; + } + """, + tp_methods='{"get_tuple", (PyCFunction)get_tuple, METH_NOARGS | METH_CLASS, ""}', + ) + + native_tuple = TestNativeTupleFactory.get_tuple() + assert is_native_object(native_tuple) + assert marshal.loads(marshal.dumps(native_tuple)) == (42, "native") + + class TestNativeSubclass(unittest.TestCase): def _verify(self, t): assert is_native_object(t) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py b/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py index d6fa3e9599..8107b4ffd0 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py @@ -312,6 +312,8 @@ def target_inner(): assert worker_ident in frames frame = frames[worker_ident] + if frame is None: + return # we hit the timeout while frame is not None and frame.f_code.co_name != "target_inner": frame = frame.f_back diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceNodes.java index fa9a34f047..31610f632e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceNodes.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 @@ -44,6 +44,7 @@ import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError; import com.oracle.graal.python.builtins.objects.bytes.PBytesLike; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.common.SequenceNodesFactory.CachedGetObjectArrayNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceNodesFactory.GetObjectArrayNodeGen; import com.oracle.graal.python.builtins.objects.common.SequenceNodesFactory.SetSequenceStorageNodeGen; @@ -51,9 +52,11 @@ import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.lib.PySequenceCheckNode; +import com.oracle.graal.python.lib.PyTupleCheckNode; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.builtins.TupleNodes; import com.oracle.graal.python.nodes.object.IsForeignObjectNode; import com.oracle.graal.python.runtime.sequence.PSequence; import com.oracle.graal.python.runtime.sequence.storage.ForeignSequenceStorage; @@ -137,6 +140,13 @@ static SequenceStorage doSequence(Node inliningTarget, PSequence seq, return getPSequenceStorageNode.execute(inliningTarget, seq); } + @Specialization(guards = "tupleCheck.execute(inliningTarget, seq)", limit = "1") + static SequenceStorage doNativeTuple(Node inliningTarget, PythonAbstractNativeObject seq, + @SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck, + @Cached TupleNodes.GetNativeTupleStorage getNativeTupleStorage) { + return getNativeTupleStorage.execute(seq); + } + // Note: this does not seem currently used but is good to accept foreign lists in more // places @Specialization(guards = {"isForeignObjectNode.execute(inliningTarget, seq)", "interop.hasArrayElements(seq)"}, limit = "1") 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 11bff8568b..5152e8e0ef 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 @@ -92,7 +92,6 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorage; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageDelItem; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageSetItem; -import com.oracle.graal.python.builtins.objects.common.SequenceNodes; 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.PEllipsis; @@ -117,6 +116,7 @@ import com.oracle.graal.python.lib.PyImportImport; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectGetAttr; +import com.oracle.graal.python.lib.PyObjectGetItem; import com.oracle.graal.python.lib.PyObjectGetIter; import com.oracle.graal.python.lib.PyObjectIsSubclassNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; @@ -144,7 +144,6 @@ import com.oracle.graal.python.runtime.PythonOptions; import com.oracle.graal.python.runtime.object.IDUtils; import com.oracle.graal.python.runtime.object.PFactory; -import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.dsl.Bind; @@ -455,8 +454,7 @@ static Pair doNewArgsEx(VirtualFrame frame, Object getNewArgsExA @Exclusive @Cached CallNode callNode, @Exclusive @Cached PyTupleCheckNode tupleCheckNode, @Cached PyDictCheckNode isDictSubClassNode, - @Cached SequenceStorageNodes.GetItemNode getItemNode, - @Cached SequenceNodes.GetSequenceStorageNode getSequenceStorageNode, + @Cached PyObjectGetItem getItemNode, @Cached PyObjectSizeNode sizeNode, @Exclusive @Cached PRaiseNode raiseNode) { Object newargs = callNode.execute(frame, getNewArgsExAttr); @@ -468,9 +466,8 @@ static Pair doNewArgsEx(VirtualFrame frame, Object getNewArgsExA throw raiseNode.raise(inliningTarget, ValueError, SHOULD_RETURN_A_NOT_B, T___GETNEWARGS_EX__, "tuple of length 2", length); } - SequenceStorage sequenceStorage = getSequenceStorageNode.execute(inliningTarget, newargs); - Object args = getItemNode.execute(sequenceStorage, 0); - Object kwargs = getItemNode.execute(sequenceStorage, 1); + Object args = getItemNode.execute(frame, inliningTarget, newargs, 0); + Object kwargs = getItemNode.execute(frame, inliningTarget, newargs, 1); if (!tupleCheckNode.execute(inliningTarget, args)) { throw raiseNode.raise(inliningTarget, TypeError, MUST_BE_TYPE_A_NOT_TYPE_B, "first item of the tuple returned by __getnewargs_ex__", "tuple", args); @@ -676,9 +673,8 @@ static Object reduceNewObj(VirtualFrame frame, Node inliningTarget, Object obj, @Cached(inline = false) GetNewArgsNode getNewArgsNode, @Cached ObjectGetStateNode getStateNode, @Cached(inline = false) PyObjectIsSubclassNode isSubClassNode, - @Cached SequenceNodes.GetSequenceStorageNode getSequenceStorageNode, - @Cached SequenceStorageNodes.ToArrayNode toArrayNode, @Cached PyObjectSizeNode sizeNode, + @Cached PyObjectGetItem getItemNode, @Exclusive @Cached PyObjectCallMethodObjArgs callMethod, @Cached PyObjectGetIter getIter, @Bind PythonLanguage language, @@ -702,11 +698,12 @@ static Object reduceNewObj(VirtualFrame frame, Node inliningTarget, Object obj, newobj = lookupAttr.execute(frame, inliningTarget, copyReg, T___NEWOBJ__); Object[] newargsVals; if (hasArgsProfile.profile(inliningTarget, hasargs)) { - SequenceStorage sequenceStorage = getSequenceStorageNode.execute(inliningTarget, args); - Object[] vals = toArrayNode.execute(inliningTarget, sequenceStorage); - newargsVals = new Object[vals.length + 1]; + int argsLen = sizeNode.execute(frame, inliningTarget, args); + newargsVals = new Object[argsLen + 1]; newargsVals[0] = cls; - System.arraycopy(vals, 0, newargsVals, 1, vals.length); + for (int i = 0; i < argsLen; i++) { + newargsVals[i + 1] = getItemNode.execute(frame, inliningTarget, args, i); + } } else { newargsVals = new Object[]{cls}; }