diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index 21769c4c5c869c8..6583e06a5939e63 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -227,3 +227,118 @@ List Objects Return a new tuple object containing the contents of *list*; equivalent to ``tuple(list)``. + + +.. _pylistwriter: + +PyListWriter +------------ + +The :c:type:`PyListWriter` API can be used to create a Python :class:`list` +object. + +On Free Threading, it's more efficient than the ``PyList`` C API to create a +:class:`list`, because it can avoid the critical section on the created list. + +.. versionadded:: next + + +.. c:type:: PyListWriter + + A list writer object. + + The API is **not thread safe**. To share a writer with multiple threads, a + critical section or a lock is needed. + + The instance must be destroyed by :c:func:`PyListWriter_Finish` on + success, or :c:func:`PyListWriter_Discard` on error. + + +.. c:function:: PyListWriter* PyListWriter_Create(Py_ssize_t size) + + Create a :c:type:`PyListWriter` and preallocate *size* items. + + On error, set an exception and return ``NULL``. + + *size* must be positive or zero. + +.. c:function:: PyObject* PyListWriter_Finish(PyListWriter *writer) + + Finish a :c:type:`PyListWriter` created by + :c:func:`PyListWriter_Create`. + + On success, return a Python :class:`list` object. + On error, set an exception and return ``NULL``. + + The writer instance is invalid after the call in any case. + No API can be called on the writer after :c:func:`PyListWriter_Finish`. + +.. c:function:: void PyListWriter_Discard(PyListWriter *writer) + + Discard a :c:type:`PyListWriter` created by :c:func:`PyListWriter_Create`. + + Do nothing if *writer* is ``NULL``. + + The writer instance is invalid after the call. + No API can be called on the writer after :c:func:`PyListWriter_Discard`. + +.. c:function:: int PyListWriter_Append(PyListWriter *writer, PyObject *item) + + Append an item to the list writer. + Similar to :c:func:`PyList_Append`. + + On success, return ``0``. + On error, set an exception, leave the writer unchanged, and return ``-1``. + +.. c:function:: int PyListWriter_AppendSteal(PyListWriter *writer, PyObject *item) + + Similar to :c:func:`PyListWriter_Append`, but :term:`steals ` the + reference to *item*, instead of creating a new :term:`strong reference`. + +.. c:function:: int PyListWriter_Extend(PyListWriter *writer, PyObject *iterable) + + Extend the list writer with the contents of *iterable*. + Similar to :c:func:`PyList_Extend`. + + On success, return ``0``. + On error, set an exception, leave the writer unchanged, and return ``-1``. + + If consuming *iterable* raises an exception, the writer is restored to its + previous state: discard added items. + +.. c:function:: int PyListWriter_ExtendArray(PyListWriter *writer, PyObject *const *items, Py_ssize_t nitem) + + Extend the list writer with the contents of *items*: copy references from + *items* to the list writer. + + On success, return ``0``. + On error, set an exception, leave the writer unchanged, and return ``-1``. + +.. c:function:: int PyListWriter_ExtendArraySteal(PyListWriter *writer, PyObject *const *items, Py_ssize_t nitem) + + Similar to :c:func:`PyListWriter_ExtendArray`, but :term:`steal` references + instead of copying references. + + On error, all *items* references are also cleared. + +.. c:function:: int PyListWriter_FromFormat(PyListWriter *writer, const char *format, ...) + + Add values using :ref:`building values format `. + + Example adding 3 integers: ``PyListWriter_FromFormat(writer, "iii", 1, 2, 3)``. + + On success, return ``0``. + On error, set an exception, leave the writer unchanged, and return ``-1``. + + On error, the writer is restored to its previous state: discard added items. + +.. c:function:: int PyListWriter_FromBuffer(PyListWriter *writer, Py_buffer *view) + + Add values from a buffer. + + *view* must have 1 dimension. + + On success, return ``0``. + On error, set an exception, leave the writer unchanged, and return ``-1``. + + On error, the writer is restored to its previous state: discard added items. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 09477b22d95413f..76f773d49770432 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -1310,6 +1310,24 @@ New features * Add :c:func:`Py_HashBuffer` to the limited C API. (Contributed by Victor Stinner in :gh:`148233`.) +* Add a new :ref:`PyListWriter ` C API to create a :class:`list` + object. On Free Threading, it's more efficient than the ``PyList`` C API to + create a :class:`list`, because it can avoid the critical section on the + created list. Add functions: + + * :c:func:`PyListWriter_Create` + * :c:func:`PyListWriter_Finish` + * :c:func:`PyListWriter_Discard` + * :c:func:`PyListWriter_Append` + * :c:func:`PyListWriter_AppendSteal` + * :c:func:`PyListWriter_Extend` + * :c:func:`PyListWriter_ExtendArray` + * :c:func:`PyListWriter_ExtendArraySteal` + * :c:func:`PyListWriter_FromFormat` + * :c:func:`PyListWriter_FromBuffer` + + (Contributed by Victor Stinner in :gh:`158660`.) + * Add :c:macro:`Py_SETREF` and :c:macro:`Py_XSETREF` macros to the limited C API. (Contributed by Nathan Goldbaum and Victor Stinner in :gh:`158842`.) diff --git a/Include/cpython/listobject.h b/Include/cpython/listobject.h index 49f5e8d6d1a0d6c..30ad5d13b4c4117 100644 --- a/Include/cpython/listobject.h +++ b/Include/cpython/listobject.h @@ -51,3 +51,36 @@ PyList_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyAPI_FUNC(int) PyList_Extend(PyObject *self, PyObject *iterable); PyAPI_FUNC(int) PyList_Clear(PyObject *self); + + +/* --- PyListWriter API --------------------------------------------------- */ + +typedef struct PyListWriter PyListWriter; + +PyAPI_FUNC(PyListWriter*) PyListWriter_Create(Py_ssize_t size); +PyAPI_FUNC(PyObject*) PyListWriter_Finish(PyListWriter *writer); +PyAPI_FUNC(void) PyListWriter_Discard(PyListWriter *writer); +PyAPI_FUNC(int) PyListWriter_Append( + PyListWriter *writer, + PyObject *item); +PyAPI_FUNC(int) PyListWriter_AppendSteal( + PyListWriter *writer, + PyObject *item); +PyAPI_FUNC(int) PyListWriter_Extend( + PyListWriter *writer, + PyObject *iterable); +PyAPI_FUNC(int) PyListWriter_ExtendArray( + PyListWriter *writer, + PyObject *const *items, + Py_ssize_t nitem); +PyAPI_FUNC(int) PyListWriter_ExtendArraySteal( + PyListWriter *writer, + PyObject *const *items, + Py_ssize_t nitem); +PyAPI_FUNC(int) PyListWriter_FromFormat( + PyListWriter *writer, + const char *format, + ...); +PyAPI_FUNC(int) PyListWriter_FromBuffer( + PyListWriter *writer, + const Py_buffer *buf); diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index df0d00f752573b5..9b51ecb9f510113 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -12,6 +12,8 @@ extern "C" { #include "pycore_stackref.h" #endif +#define _Py_LIST_SMALL_ALLOCATED 32 + PyAPI_FUNC(PyObject*) _PyList_Extend(PyListObject *, PyObject *); PyAPI_FUNC(PyObject) *_PyList_SliceSubscript(PyObject*, PyObject*); PyAPI_FUNC(PyObject *) _PyList_BinarySlice(PyObject *, PyObject *, PyObject *); @@ -77,6 +79,37 @@ union _PyStackRef; PyAPI_FUNC(PyObject *)_PyList_FromStackRefStealOnSuccess(const union _PyStackRef *src, Py_ssize_t n); PyAPI_FUNC(PyObject *)_PyList_AsTupleAndClear(PyListObject *v); +#ifdef Py_DEBUG +# define _PyListWriter_INIT_ITEMS 1 +#else +# define _PyListWriter_INIT_ITEMS 0 +#endif + +extern int _PyListWriter_Resize( + PyListWriter *writer, + Py_ssize_t new_size); +extern void _PyListWriter_ResetSize( + PyListWriter *writer, + Py_ssize_t size); + + +// The caller must resize the list so it's big enough to set this item. +// A writer list item must be set exactly once. +static inline void +_PyListWriter_SET_ITEM_STEAL(PyListWriter *writer, Py_ssize_t index, + PyObject *item) +{ + assert(item != NULL); + + PyListObject *list = (PyListObject*)writer; + assert(0 <= index && index < Py_SIZE(list)); +#if _PyListWriter_INIT_ITEMS + assert(list->ob_item[index] == NULL); +#endif + + list->ob_item[index] = item; +} + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_modsupport.h b/Include/internal/pycore_modsupport.h index a72a557059727a7..4127fb64d84d194 100644 --- a/Include/internal/pycore_modsupport.h +++ b/Include/internal/pycore_modsupport.h @@ -32,12 +32,13 @@ PyAPI_FUNC(int) _PyArg_CheckPositional(const char *, Py_ssize_t, (((min) <= (nargs) && (nargs) <= (max)) \ || _PyArg_CheckPositional((funcname), (nargs), (min), (max))) -extern PyObject ** _Py_VaBuildStack( +extern int _Py_VaBuildArray( + PyObject ***array, + Py_ssize_t *size, PyObject **small_stack, Py_ssize_t small_stack_len, const char *format, - va_list va, - Py_ssize_t *p_nargs); + va_list va); extern PyObject* _PyModule_CreateInitialized(PyModuleDef*, int apiver); diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index b95b8ba960bd8b2..dbabc7289f8c99f 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -1,12 +1,23 @@ +import array +import threading import unittest -from test.support import import_helper from collections import UserList +from test import support +from test.support import import_helper +from test.support import threading_helper +try: + import _testbuffer +except ImportError: + _testbuffer = None + _testcapi = import_helper.import_module('_testcapi') _testlimitedcapi = import_helper.import_module('_testlimitedcapi') + NULL = None PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN PY_SSIZE_T_MAX = _testcapi.PY_SSIZE_T_MAX +LIST_SMALL_ALLOCATED = _testcapi.LIST_SMALL_ALLOCATED class ListSubclass(list): pass @@ -355,5 +366,258 @@ def test_uninitialized_list_repr(self): self.assertEqual(repr(lst), '[, , ]') +class ListWriterTests(unittest.TestCase): + def create_writer(self, size): + # Test PyListWriter_Create() + return _testcapi.PyListWriter(size) + + def list_equal(self, result, expected): + if not isinstance(expected, list): + expected = list(expected) + self.assertEqual(result, expected) + self.assertEqual(type(result), list) + + def create_items(self, nitem): + # Call object() to create unique objects. Don't use small integers + # which are immortal (and so would not check for reference leaks). + return tuple(object() for i in range(nitem)) + + def check_append(self, method): + items = self.create_items(3) + for size in (0, len(items), len(items) * 10): + with self.subTest(size=size): + writer = self.create_writer(size) + append = getattr(writer, method) + for item in items: + append(item) + self.list_equal(writer.finish(), items) + + # test MemoryError + writer = self.create_writer(0) + append = getattr(writer, method) + with self.assertRaises(MemoryError): + with support.inject_memory_error_cm(): + append("alloc failed") + for item in items: + append(item) + self.list_equal(writer.finish(), items) + + def test_append(self): + # Test PyListWriter_Append() + self.check_append('append') + + def test_append_steal(self): + # Test PyListWriter_AppendSteal() + self.check_append('append_steal') + + def check_extend(self, method): + items = self.create_items(5) + for size in (0, len(items), len(items) * 10): + with self.subTest(size=size): + writer = self.create_writer(size) + extend = getattr(writer, method) + extend(items) + self.list_equal(writer.finish(), items) + + # test MemoryError + writer = self.create_writer(0) + extend = getattr(writer, method) + items_error = ("alloc failed",) + with self.assertRaises(MemoryError): + with support.inject_memory_error_cm(): + extend(items_error) + extend(items) + self.list_equal(writer.finish(), items) + + def test_extend(self): + # Test PyListWriter_Extend() + self.check_extend('extend') + + def test_extend_iter_error(self): + class IterError: + def __init__(self): + self.index = 1 + + def __iter__(self): + return self + + def __next__(self): + value = self.index + self.index += 1 + if value >= 3: + raise ValueError("iter error") + return value + + # On error, restore the writer to its previous state: + # discard added items + iterable = IterError() + writer = self.create_writer(0) + writer.append("before") + with self.assertRaisesRegex(ValueError, "iter error"): + writer.extend(iterable) + self.assertEqual(iterable.index, 4) + writer.append("after") + self.list_equal(writer.finish(), ["before", "after"]) + + def test_extend_array(self): + # Test PyListWriter_ExtendArray() + self.check_extend('extend_array') + + def test_extend_array_steal(self): + # Test PyListWriter_ExtendArraySteal() + self.check_extend('extend_array_steal') + + def test_from_format(self): + # Test PyListWriter_FromFormat() + + # Allocate a small array on the stack (1 item) + writer = self.create_writer(0) + writer.from_format_i(123) + self.list_equal(writer.finish(), [123]) + + # Allocate an array on the heap (16 items) + items = tuple(range(100, 100+16)) + writer = self.create_writer(0) + writer.from_format_i16(items) + self.list_equal(writer.finish(), items) + + # Test error: call PyListWriter_FromFormat(writer, "iiO", 1, 2, NULL). + # First items before the error must not be added. + writer = self.create_writer(0) + writer.append("before") + with self.assertRaises(SystemError): + writer.from_format_error() + writer.append("after") + self.list_equal(writer.finish(), ["before", "after"]) + + def test_from_buffer(self): + # Test PyListWriter_FromBuffer() + writer = self.create_writer(0) + mview = memoryview(b'abc') + writer.from_buffer(mview) + self.list_equal(writer.finish(), mview.tolist()) + + numbers = array.array('d', [1.5, 3.0, 5.0]) + writer = self.create_writer(0) + writer.from_buffer(memoryview(numbers)) + self.list_equal(writer.finish(), numbers.tolist()) + + def test_from_buffer_memoryerror(self): + # Inject MemoryError in the middle of the array: reset the writer + # to its previous state, discard added float objects + numbers = array.array('d', [2.0] * 128) + mview = memoryview(numbers) + writer = self.create_writer(0) + with self.assertRaises(MemoryError): + with support.inject_memory_error_cm(start=len(numbers) // 2): + writer.from_buffer(mview) + self.list_equal(writer.finish(), []) + + @unittest.skipIf(_testbuffer is None, 'need _testbuffer') + def test_from_buffer_ndarray(self): + # PyListWriter_FromBuffer() requires 1 dimension + array_0d = _testbuffer.ndarray(ord(b"a"), format='B', + shape=(), strides=()) + array_2d = _testbuffer.ndarray(list(b"abcdefgh"), format='B', + shape=(4, 2), strides=(2, 1)) + + for array in (array_0d, array_2d): + with self.subTest(array=array): + writer = self.create_writer(0) + errmsg = 'require a buffer of 1 dimension, got 2 dimentions' + with self.assertRaisesRegex(ValueError, errmsg): + writer.from_buffer(array_2d) + self.list_equal(writer.finish(), []) + + def test_empty(self): + list_get_allocated = _testcapi._list_get_allocated + empty_tuple = () + + for size in (0, LIST_SMALL_ALLOCATED, 1024): + with self.subTest(size=size): + writer = self.create_writer(size) + writer.extend(empty_tuple) + writer.extend_array(empty_tuple) + writer.extend_array_steal(empty_tuple) + result = writer.finish() + self.list_equal(result, []) + + if size == 0 or size > LIST_SMALL_ALLOCATED: + expected = 0 + else: + expected = size + self.assertEqual(list_get_allocated(result), expected) + + def test_finish_shrink(self): + # PyListWriter_Finish() shrinks the list if its allocated size + # is way too big + list_get_allocated = _testcapi._list_get_allocated + + def compute_allocated(size): + return (size + (size >> 3) + 6) & ~3 + + def check_allocated(items, alloc, expected): + writer = self.create_writer(alloc) + # Don't use PyListWriter_Extend() since it can shrink the list + for item in items: + writer.append(item) + result = writer.finish() + self.list_equal(result, list(items)) + self.assertEqual(list_get_allocated(result), expected) + + # small list + items = [1, 2, 3] + check_allocated(items, + 1024, + compute_allocated(len(items))) + + alloc = LIST_SMALL_ALLOCATED + check_allocated(items, alloc, alloc) + + # larger list + items = tuple(range(100)) + check_allocated(items, + (len(items) + 1) * 2, + compute_allocated(len(items))) + + alloc = len(items) * 2 + check_allocated(items, alloc, alloc) + + # do not shrink on MemoryError + alloc = 1024 + writer = self.create_writer(alloc) + writer.append(1) + with support.inject_memory_error_cm(): + # list_resize() never fails when shrinking + result = writer.finish() + self.list_equal(result, [1]) + self.assertEqual(list_get_allocated(result), alloc) + + def test_discard(self): + # Test PyListWriter_Discard(): must not leak memory + writer = self.create_writer(0) + writer.extend([2, 5, 7]) + writer.discard() + + @threading_helper.requires_working_threading() + def test_thread(self): + # PyListWriter can be used by multiple threads: it's up to the caller + # to implement a lock to prevent concurrent accesses. + writer = self.create_writer(0) + + def thread_func(writer): + writer.append('a') + writer.append_steal('b') + writer.extend(('c',)) + writer.extend_array(('d',)) + writer.extend_array_steal(('e',)) + + thread = threading.Thread(target=thread_func, args=(writer,)) + thread.start() + threading_helper.join_thread(thread) + + self.list_equal(writer.finish(), list('abcde')) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C_API/2026-10-21-16-35-42.gh-issue-158660.KvixNp.rst b/Misc/NEWS.d/next/C_API/2026-10-21-16-35-42.gh-issue-158660.KvixNp.rst new file mode 100644 index 000000000000000..2c993d9be70fc44 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-10-21-16-35-42.gh-issue-158660.KvixNp.rst @@ -0,0 +1,17 @@ +Add a new :ref:`PyListWriter ` C API to create a :class:`list` +object. On Free Threading, it's more efficient than the ``PyList`` C API to +create a :class:`list`, because it can avoid the critical section on the +created list. Add functions: + +* :c:func:`PyListWriter_Create` +* :c:func:`PyListWriter_Finish` +* :c:func:`PyListWriter_Discard` +* :c:func:`PyListWriter_Append` +* :c:func:`PyListWriter_AppendSteal` +* :c:func:`PyListWriter_Extend` +* :c:func:`PyListWriter_ExtendArray` +* :c:func:`PyListWriter_ExtendArraySteal` +* :c:func:`PyListWriter_FromFormat` +* :c:func:`PyListWriter_FromBuffer` + +Patch by Victor Stinner. diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c index 530b47780ac94e2..531a8156cdcfdf0 100644 --- a/Modules/_testcapi/list.c +++ b/Modules/_testcapi/list.c @@ -1,6 +1,11 @@ +// Use pycore_list.h +#define PYTESTCAPI_NEED_INTERNAL_API + #include "parts.h" #include "util.h" +#include "pycore_list.h" // _Py_LIST_SMALL_ALLOCATED + static PyObject * list_get_size(PyObject *Py_UNUSED(module), PyObject *obj) @@ -103,6 +108,348 @@ test_list_api(PyObject *self, PyObject *Py_UNUSED(ignored)) } +static PyObject * +_list_get_allocated(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + PyListObject *list = (PyListObject*)obj; + return PyLong_FromSsize_t(list->allocated); +} + + +typedef struct { + PyObject_HEAD + PyListWriter *writer; +} WriterObject; + + +static inline int +writer_check(WriterObject *self) +{ + if (self->writer == NULL) { + PyErr_SetString(PyExc_ValueError, + "operation on finished/discarded writer"); + return -1; + } + return 0; +} + + +static PyObject * +writer_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + WriterObject *self = (WriterObject *)type->tp_alloc(type, 0); + if (!self) { + return NULL; + } + self->writer = NULL; + return (PyObject*)self; +} + + +// Test PyListWriter_Create() +static int +writer_init(PyObject *self_raw, PyObject *args, PyObject *kwargs) +{ + if (kwargs && PyDict_GET_SIZE(kwargs)) { + PyErr_Format(PyExc_TypeError, + "PyListWriter() takes no keyword arguments"); + return -1; + } + + Py_ssize_t size = 0; + if (!PyArg_ParseTuple(args, "|n", &size)) { + return -1; + } + + WriterObject *self = (WriterObject *)self_raw; + PyListWriter_Discard(self->writer); + + self->writer = PyListWriter_Create(size); + if (self->writer == NULL) { + return -1; + } + + return 0; +} + + +// Test PyListWriter_Discard() +static void +writer_dealloc(PyObject *self_raw) +{ + WriterObject *self = (WriterObject *)self_raw; + PyTypeObject *tp = Py_TYPE(self); + if (self->writer) { + PyListWriter_Discard(self->writer); + } + tp->tp_free(self); + Py_DECREF(tp); +} + + +// Test PyListWriter_Append() +static PyObject* +writer_append(PyObject *self_raw, PyObject *item) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (PyListWriter_Append(self->writer, item) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_AppendSteal() +static PyObject* +writer_append_steal(PyObject *self_raw, PyObject *item) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (PyListWriter_AppendSteal(self->writer, Py_NewRef(item)) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_Extend() +static PyObject* +writer_extend(PyObject *self_raw, PyObject *iterable) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (PyListWriter_Extend(self->writer, iterable) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_ExtendArray() +static PyObject* +writer_extend_array(PyObject *self_raw, PyObject *tuple) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (!PyTuple_Check(tuple)) { + PyErr_Format(PyExc_TypeError, + "expect tuple, got %T", tuple); + return NULL; + } + PyObject **items = &PyTuple_GET_ITEM(tuple, 0); + Py_ssize_t len = PyTuple_GET_SIZE(tuple); + + if (PyListWriter_ExtendArray(self->writer, items, len) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_ExtendArraySteal() +static PyObject* +writer_extend_array_steal(PyObject *self_raw, PyObject *tuple) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (!PyTuple_Check(tuple)) { + PyErr_Format(PyExc_TypeError, + "expect tuple, got %T", tuple); + return NULL; + } + PyObject **items = &PyTuple_GET_ITEM(tuple, 0); + Py_ssize_t len = PyTuple_GET_SIZE(tuple); + + for (Py_ssize_t i = 0; i < len; i++) { + Py_INCREF(items[i]); + } + + if (PyListWriter_ExtendArraySteal(self->writer, items, len) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_FromFormat("i") +static PyObject* +writer_from_format_i(PyObject *self_raw, PyObject *arg) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + int value; + if (!PyArg_Parse(arg, "i", &value)) { + return NULL; + } + + if (PyListWriter_FromFormat(self->writer, "i", value) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_FromFormat("i") +static PyObject* +writer_from_format_i16(PyObject *self_raw, PyObject *tuple) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + // Test size larger than small_array size in PyListWriter_FromFormat() + int values[16]; + Py_ssize_t len = Py_ARRAY_LENGTH(values); + if (!PyTuple_Check(tuple) || PyTuple_GET_SIZE(tuple) != len) { + PyErr_Format(PyExc_ValueError, + "expect a tuple of %zd integers", len); + return NULL; + } + for (Py_ssize_t i = 0; i < len; i++) { + int value = PyLong_AsInt(PyTuple_GET_ITEM(tuple, i)); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + values[i] = value; + } + + int res = PyListWriter_FromFormat(self->writer, + "iiiiiiiiiiiiiiii", + values[0], values[1], values[2], values[3], + values[4], values[5], values[6], values[7], + values[8], values[9], values[10], values[11], + values[12], values[13], values[14], values[15] + ); + if (res < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_FromFormat() error +static PyObject* +writer_from_format_error(PyObject *self_raw, PyObject *Py_UNUSED(args)) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + int res = PyListWriter_FromFormat(self->writer, "iiO", 1, 2, NULL); + if (res < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_FromBuffer() error +static PyObject* +writer_from_buffer(PyObject *self_raw, PyObject *obj) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + Py_buffer view; + int flags = PyBUF_FORMAT | PyBUF_ND | PyBUF_STRIDES; + if (PyObject_GetBuffer(obj, &view, flags) < 0) { + return NULL; + } + + int res = PyListWriter_FromBuffer(self->writer, &view); + PyBuffer_Release(&view); + if (res < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyListWriter_Finish() +static PyObject* +writer_finish(PyObject *self_raw, PyObject *Py_UNUSED(args)) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + PyObject *list = PyListWriter_Finish(self->writer); + self->writer = NULL; + return list; +} + + +// Test PyListWriter_Discard() +static PyObject* +writer_discard(PyObject *self_raw, PyObject *Py_UNUSED(args)) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + PyListWriter_Discard(self->writer); + self->writer = NULL; + Py_RETURN_NONE; +} + + +static PyMethodDef writer_methods[] = { + {"append", _PyCFunction_CAST(writer_append), METH_O}, + {"append_steal", _PyCFunction_CAST(writer_append_steal), METH_O}, + {"extend", _PyCFunction_CAST(writer_extend), METH_O}, + {"extend_array", _PyCFunction_CAST(writer_extend_array), METH_O}, + {"extend_array_steal", _PyCFunction_CAST(writer_extend_array_steal), METH_O}, + {"from_format_i", _PyCFunction_CAST(writer_from_format_i), METH_O}, + {"from_format_i16", _PyCFunction_CAST(writer_from_format_i16), METH_O}, + {"from_format_error", _PyCFunction_CAST(writer_from_format_error), METH_NOARGS}, + {"from_buffer", _PyCFunction_CAST(writer_from_buffer), METH_O}, + {"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS}, + {"discard", _PyCFunction_CAST(writer_discard), METH_NOARGS}, + {NULL, NULL} /* sentinel */ +}; + +static PyType_Slot Writer_Type_slots[] = { + {Py_tp_new, writer_new}, + {Py_tp_init, writer_init}, + {Py_tp_dealloc, writer_dealloc}, + {Py_tp_methods, writer_methods}, + {0, 0}, /* sentinel */ +}; + +static PyType_Spec Writer_spec = { + .name = "_testcapi.PyListWriter", + .basicsize = sizeof(WriterObject), + .flags = Py_TPFLAGS_DEFAULT, + .slots = Writer_Type_slots, +}; + + static PyMethodDef test_methods[] = { {"list_get_size", list_get_size, METH_O}, {"list_get_item", list_get_item, METH_VARARGS}, @@ -110,11 +457,30 @@ static PyMethodDef test_methods[] = { {"list_clear", list_clear, METH_O}, {"list_extend", list_extend, METH_VARARGS}, {"test_list_api", test_list_api, METH_NOARGS}, + {"_list_get_allocated", _list_get_allocated, METH_O}, {NULL}, }; int _PyTestCapi_Init_List(PyObject *m) { - return PyModule_AddFunctions(m, test_methods); + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + + PyTypeObject *writer_type = (PyTypeObject *)PyType_FromSpec(&Writer_spec); + if (writer_type == NULL) { + return -1; + } + if (PyModule_AddType(m, writer_type) < 0) { + Py_DECREF(writer_type); + return -1; + } + Py_DECREF(writer_type); + + if (PyModule_AddIntConstant(m, "LIST_SMALL_ALLOCATED", _Py_LIST_SMALL_ALLOCATED) < 0) { + return -1; + } + + return 0; } diff --git a/Objects/call.c b/Objects/call.c index a79f167aacc8c06..8008a919e9459b3 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -517,12 +517,6 @@ static PyObject * _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable, const char *format, va_list va) { - PyObject* small_stack[_PY_FASTCALL_SMALL_STACK]; - const Py_ssize_t small_stack_len = Py_ARRAY_LENGTH(small_stack); - PyObject **stack; - Py_ssize_t nargs, i; - PyObject *result; - if (callable == NULL) { return null_error(tstate); } @@ -531,12 +525,17 @@ _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable, return _PyObject_CallNoArgsTstate(tstate, callable); } - stack = _Py_VaBuildStack(small_stack, small_stack_len, - format, va, &nargs); - if (stack == NULL) { + PyObject* small_stack[_PY_FASTCALL_SMALL_STACK]; + PyObject **stack; + Py_ssize_t nargs; + if (_Py_VaBuildArray(&stack, &nargs, + small_stack, Py_ARRAY_LENGTH(small_stack), + format, va) < 0) { return NULL; } EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable); + + PyObject *result; if (nargs == 1 && PyTuple_Check(stack[0])) { /* Special cases for backward compatibility: - PyObject_CallFunction(func, "O", tuple) calls func(*tuple) @@ -553,7 +552,7 @@ _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable, stack, nargs, NULL); } - for (i = 0; i < nargs; ++i) { + for (Py_ssize_t i = 0; i < nargs; ++i) { Py_DECREF(stack[i]); } if (stack != small_stack) { @@ -640,15 +639,19 @@ callmethod(PyThreadState *tstate, PyObject *obj, PyObject *name, /* Build the positional arguments from the format string. */ PyObject *small_stack[_PY_FASTCALL_SMALL_STACK]; - Py_ssize_t nargs = 0; - PyObject **built = NULL; + PyObject **built; + Py_ssize_t nargs; if (format != NULL && *format != '\0') { - built = _Py_VaBuildStack(small_stack, _PY_FASTCALL_SMALL_STACK, - format, va, &nargs); - if (built == NULL) { + if (_Py_VaBuildArray(&built, &nargs, + small_stack, _PY_FASTCALL_SMALL_STACK, + format, va) < 0) { goto exit; } } + else { + built = NULL; + nargs = 0; + } /* Backward compat: a single tuple from "O" is unpacked. */ PyObject *const *args = built; diff --git a/Objects/listobject.c b/Objects/listobject.c index 5ca8cdf09d3160b..bbc5b08952cfd37 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -91,7 +91,7 @@ ensure_shared_on_resize(PyListObject *self) #endif } -#define LIST_SMALL_ALLOCATED 32 +#define LIST_SMALL_ALLOCATED _Py_LIST_SMALL_ALLOCATED static int py_list_resize(PyListObject *self, Py_ssize_t newsize); @@ -299,30 +299,48 @@ PyList_New(Py_ssize_t size) return (PyObject *) op; } +// Similar to PyList_New() but don't initialize memory and do not track +// the newly created list in the garbage collector. +// +// The list is initialized with a size of 0. +// +// size must be >= 0. static PyObject * list_new_prealloc(Py_ssize_t size) { - assert(size > 0); - PyListObject *op = (PyListObject *) PyList_New(0); + // The caller is responsible to check the size + assert(size >= 0); + + PyListObject *op = _Py_FREELIST_POP(PyListObject, lists); if (op == NULL) { - return NULL; + op = PyObject_GC_New(PyListObject, &PyList_Type); + if (op == NULL) { + return NULL; + } } - assert(op->ob_item == NULL); -#ifdef Py_GIL_DISABLED - _PyListArray *array = list_allocate_array(size); - if (array == NULL) { - Py_DECREF(op); - return PyErr_NoMemory(); + + if (size <= 0) { + op->ob_item = NULL; } - op->ob_item = array->ob_item; + else { +#ifdef Py_GIL_DISABLED + _PyListArray *array = list_allocate_array(size); + if (array == NULL) { + Py_DECREF(op); + return PyErr_NoMemory(); + } + op->ob_item = array->ob_item; #else - op->ob_item = PyMem_New(PyObject *, size); - if (op->ob_item == NULL) { - Py_DECREF(op); - return PyErr_NoMemory(); - } + op->ob_item = (PyObject **) PyMem_Malloc(size * sizeof(PyObject *)); #endif + if (op->ob_item == NULL) { + Py_DECREF(op); + return PyErr_NoMemory(); + } + } + Py_SET_SIZE(op, 0); op->allocated = size; + return (PyObject *) op; } @@ -766,6 +784,7 @@ list_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) dest[i] = Py_NewRef(v); } Py_SET_SIZE(np, len); + _PyObject_GC_TRACK(np); return (PyObject *)np; } @@ -848,6 +867,7 @@ list_concat_lock_held(PyListObject *a, PyListObject *b) dest[i] = Py_NewRef(v); } Py_SET_SIZE(np, size); + _PyObject_GC_TRACK(np); return (PyObject *)np; } @@ -918,6 +938,7 @@ list_repeat_impl(PyListObject *a, Py_ssize_t n) } Py_SET_SIZE(np, output_size); + _PyObject_GC_TRACK(np); return (PyObject *) np; } @@ -1507,6 +1528,7 @@ list_extend_dictitems(PyListObject *self, PyDictObject *dict) return 0; } +// If you update this function, update also PyListWriter_Extend(). static int _list_extend(PyListObject *self, PyObject *iterable) { @@ -3722,6 +3744,7 @@ list_slice_step_lock_held(PyListObject *a, Py_ssize_t start, Py_ssize_t step, Py dest[i] = Py_NewRef(v); } Py_SET_SIZE(np, len); + _PyObject_GC_TRACK(np); return (PyObject *)np; } @@ -4387,3 +4410,321 @@ listiter_reduce_general(void *_it, int forward) } return Py_BuildValue("N(N)", iter, list); } + + +/* --- PyListWriter API --------------------------------------------------- */ + +#ifndef NDEBUG +static int +listwriter_check_consistency(PyListWriter *writer) +{ + assert(writer != NULL); + assert(PyList_CheckExact(_PyObject_CAST(writer))); + PyListObject *list = (PyListObject*)writer; + assert(Py_SIZE(list) >= 0); + assert(Py_SIZE(list) <= list->allocated); + // Don't test _PyObject_IsUniquelyReferenced() since a list writer can + // be shared between multiple threads with external locking. + assert(Py_REFCNT(list) == 1); + assert(!_PyObject_GC_IS_TRACKED(list)); + return 1; +} +#endif + + +PyListWriter* +PyListWriter_Create(Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must not be negative"); + return NULL; + } + + PyObject *list = list_new_prealloc(size); + if (list == NULL) { + return NULL; + } + assert(Py_SIZE(list) == 0); + // In release mode, list items are uninitialized pointers which is safe, + // since the list object is not tracked by the GC, and the writer caller + // must initialize all items. + +#if _PyListWriter_INIT_ITEMS + // In debug mode, set all items to NULL to be able to check that they are + // non-NULL in PyListWriter_Finish(). + if (1 <= size) { + memset(_PyList_CAST(list)->ob_item, 0, size * sizeof(PyObject*)); + } +#endif + + PyListWriter *writer = (PyListWriter*)list; + assert(listwriter_check_consistency(writer)); + return writer; +} + + +void +PyListWriter_Discard(PyListWriter *writer) +{ + if (writer == NULL) { + return; + } + + assert(listwriter_check_consistency(writer)); + PyObject *list = _PyObject_CAST(writer); + Py_DECREF(list); +} + + +PyObject* +PyListWriter_Finish(PyListWriter *writer) +{ + assert(listwriter_check_consistency(writer)); + PyListObject *list = _PyList_CAST(writer); + + // Shrink the list if the allocated size is greater than the double size. + // For example, if PyListWriter_Create() argument overestimated the final + // size. + if (Py_SIZE(list) < (list->allocated >> 1) + && list->allocated > LIST_SMALL_ALLOCATED) + { + if (list_resize(list, Py_SIZE(list)) < 0) { + // This case cannot happen: list_resize() never fails when + // shrinking + PyListWriter_Discard(writer); + return NULL; + } + // Note: cannot test that list->allocated changed in debug mode, since + // it's left unchanged on memory allocation failure. + } + +#if _PyListWriter_INIT_ITEMS + // Make sure that all items have been initialized + Py_ssize_t size = Py_SIZE(list); + PyObject **list_items = list->ob_item; + for (Py_ssize_t i = 0; i < size; i++) { + assert(list_items[i] != NULL); + } +#endif + + // Now that the list is fully initialized, it's safe to track it in the GC + _PyObject_GC_TRACK(list); + return _PyObject_CAST(list); +} + + +int +_PyListWriter_Resize(PyListWriter *writer, Py_ssize_t new_size) +{ + PyListObject *list = _PyList_CAST(writer); + Py_ssize_t old_size = Py_SIZE(list); + + // On Free Threading, it can call _PyObject_GC_SET_SHARED() on the list + int res = list_resize(list, new_size); +#if _PyListWriter_INIT_ITEMS + if (res == 0 && new_size > old_size) { + memset(&list->ob_item[old_size], 0, + (new_size - old_size) * sizeof(PyObject*)); + } +#endif + return res; +} + + +int +PyListWriter_AppendSteal(PyListWriter *writer, PyObject *item) +{ + assert(listwriter_check_consistency(writer)); + PyListObject *list = _PyList_CAST(writer); + + Py_ssize_t index = Py_SIZE(list); + Py_ssize_t allocated = list->allocated; + if (index < allocated) { + Py_SET_SIZE(list, index + 1); + list->ob_item[index] = item; + return 0; + } + + if (_PyListWriter_Resize(writer, index + 1) < 0) { + Py_DECREF(item); + return -1; + } + _PyListWriter_SET_ITEM_STEAL(writer, index, item); + return 0; +} + + +int +PyListWriter_Append(PyListWriter *writer, PyObject *item) +{ + return PyListWriter_AppendSteal(writer, Py_NewRef(item)); +} + + +void +_PyListWriter_ResetSize(PyListWriter *writer, Py_ssize_t size) +{ + assert(listwriter_check_consistency(writer)); + PyListObject *list = _PyList_CAST(writer); + + Py_ssize_t new_size = Py_SIZE(list); + assert(0 <= size && size <= new_size); + + Py_SET_SIZE(list, size); + + // Discard added items + PyObject **items = list->ob_item; + for (Py_ssize_t i = size; i < new_size; i++) { + Py_DECREF(items[i]); +#if _PyListWriter_INIT_ITEMS + items[i] = NULL; +#endif + } +} + + +// Similar to _list_extend() but don't lock the writer list. +// If you update this function, update also _list_extend(). +int +PyListWriter_Extend(PyListWriter *writer, PyObject *iterable) +{ + assert(listwriter_check_consistency(writer)); + PyListObject *self = _PyList_CAST(writer); + assert(_PyObject_CAST(self) != iterable); + + Py_ssize_t old_size = Py_SIZE(self); + int res; + if (PyList_CheckExact(iterable)) { + Py_BEGIN_CRITICAL_SECTION(iterable); + res = list_extend_lock_held(self, iterable); + Py_END_CRITICAL_SECTION(); + } + else if (PyTuple_CheckExact(iterable)) { + res = list_extend_lock_held(self, iterable); + } + else if (PyAnySet_CheckExact(iterable)) { + Py_BEGIN_CRITICAL_SECTION(iterable); + res = list_extend_set(self, (PySetObject *)iterable); + Py_END_CRITICAL_SECTION(); + } + else if (PyDict_CheckExact(iterable)) { + Py_BEGIN_CRITICAL_SECTION(iterable); + res = list_extend_dict(self, (PyDictObject *)iterable, 0 /*keys*/); + Py_END_CRITICAL_SECTION(); + } + else if (Py_IS_TYPE(iterable, &PyDictKeys_Type)) { + PyDictObject *dict = ((_PyDictViewObject *)iterable)->dv_dict; + Py_BEGIN_CRITICAL_SECTION(dict); + res = list_extend_dict(self, dict, 0 /*keys*/); + Py_END_CRITICAL_SECTION(); + } + else if (Py_IS_TYPE(iterable, &PyDictValues_Type)) { + PyDictObject *dict = ((_PyDictViewObject *)iterable)->dv_dict; + Py_BEGIN_CRITICAL_SECTION(dict); + res = list_extend_dict(self, dict, 1 /*values*/); + Py_END_CRITICAL_SECTION(); + } + else if (Py_IS_TYPE(iterable, &PyDictItems_Type)) { + PyDictObject *dict = ((_PyDictViewObject *)iterable)->dv_dict; + Py_BEGIN_CRITICAL_SECTION(dict); + res = list_extend_dictitems(self, dict); + Py_END_CRITICAL_SECTION(); + } + else { + res = list_extend_iter_lock_held(self, iterable); + } + + if (res < 0 && Py_SIZE(self) != old_size) { + // On error, reset the writer to its previous state: + // discard added items + _PyListWriter_ResetSize(writer, old_size); + } + + return res; +} + + +// Force inlining to optimize "if (steal)" at build time +static inline Py_ALWAYS_INLINE int +listwriter_extend_array(PyListWriter *writer, + PyObject *const *items, Py_ssize_t nitem, + int steal) +{ + assert(listwriter_check_consistency(writer)); + assert(nitem >= 0); + + if (nitem == 0) { + // Fast path. Avoid also memcpy(dst, src, 0) undefined behavior below. + return 0; + } + + PyListObject *list = _PyList_CAST(writer); + Py_ssize_t size = Py_SIZE(list); + Py_ssize_t new_size = size + nitem; + if (new_size > list->allocated) { + if (_PyListWriter_Resize(writer, new_size) < 0) { + if (steal) { + for (Py_ssize_t i = 0; i < nitem; i++) { + Py_DECREF(items[i]); + } + } + return -1; + } + } + else { + Py_SET_SIZE(list, new_size); + } + + PyObject **list_items = &list->ob_item[size]; + if (steal) { + memcpy(list_items, items, nitem * sizeof(PyObject*)); + } + else { + for (Py_ssize_t i = 0; i < nitem; i++) { + list_items[i] = Py_NewRef(items[i]); + } + } + return 0; +} + + +int +PyListWriter_ExtendArray(PyListWriter *writer, + PyObject *const *items, Py_ssize_t nitem) +{ + return listwriter_extend_array(writer, items, nitem, 0); +} + + +int +PyListWriter_ExtendArraySteal(PyListWriter *writer, + PyObject *const *items, Py_ssize_t nitem) +{ + return listwriter_extend_array(writer, items, nitem, 1); +} + + +int +PyListWriter_FromFormat(PyListWriter *writer, const char *format, ...) +{ + PyObject* small_array[8]; + Py_ssize_t size; + PyObject **array; + + va_list vargs; + va_start(vargs, format); + int res = _Py_VaBuildArray( + &array, &size, + small_array, Py_ARRAY_LENGTH(small_array), + format, vargs); + va_end(vargs); + if (res < 0) { + return -1; + } + + res = PyListWriter_ExtendArraySteal(writer, array, size); + if (array != small_array) { + PyMem_Free(array); + } + return res; +} diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index e6d49b6c29a3e95..a5b5414fa187ca5 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -12,6 +12,7 @@ #include "Python.h" #include "pycore_abstract.h" // _PyIndex_Check() +#include "pycore_list.h" // _PyListWriter_ResetSize() #include "pycore_memoryobject.h" // _PyManagedBuffer_Type #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_strhex.h" // _Py_strhex_with_sep() @@ -1842,7 +1843,9 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt) unsigned char uc; void *p; - CHECK_RELEASED_AGAIN(self); + if (self != NULL) { + CHECK_RELEASED_AGAIN(self); + } #if PY_LITTLE_ENDIAN int endian = 1; @@ -2374,6 +2377,70 @@ memoryview_tolist_impl(PyMemoryViewObject *self) } } + +int +PyListWriter_FromBuffer(PyListWriter *writer, const Py_buffer *view) +{ + if (view->ndim != 1) { + PyErr_Format(PyExc_ValueError, + "require a buffer of 1 dimension, got %i dimentions", + view->ndim); + return -1; + } + + const char *fmt; + if (view->format != NULL) { + fmt = adjust_fmt(view); + if (fmt == NULL) { + return -1; + } + } + else { + // Same default value than init_shared_values() + fmt = "B"; + } + + PyListObject *list = _PyList_CAST(writer); + Py_ssize_t old_size = Py_SIZE(list); + + const char *ptr = view->buf; + const Py_ssize_t shape = view->shape ? view->shape[0] : view->len; + const Py_ssize_t strides = view->strides ? view->strides[0] : view->itemsize; + const Py_ssize_t *suboffsets = view->suboffsets; + + Py_ssize_t new_size = old_size + shape; + if (new_size > list->allocated) { + if (_PyListWriter_Resize(writer, new_size) < 0) { + return -1; + } + } + else { + Py_SET_SIZE(list, new_size); + } + + for (Py_ssize_t i = 0; i < shape; ptr += strides, i++) { + const char *xptr = ADJUST_PTR(ptr, suboffsets, 0); + + PyObject *item = unpack_single(NULL, xptr, fmt); + if (item == NULL) { + Py_SET_SIZE(list, old_size + i); + goto error; + } + + _PyListWriter_SET_ITEM_STEAL(writer, old_size + i, item); + } + return 0; + +error: + if (Py_SIZE(list) != old_size) { + // On error, reset the writer to its previous state: + // discard added items + _PyListWriter_ResetSize(writer, old_size); + } + return -1; +} + + /*[clinic input] memoryview.tobytes diff --git a/Python/modsupport.c b/Python/modsupport.c index bab21d1b2be5b51..c7ed598c908262d 100644 --- a/Python/modsupport.c +++ b/Python/modsupport.c @@ -549,52 +549,55 @@ va_build_value(const char *format, va_list va) return retval; } -PyObject ** -_Py_VaBuildStack(PyObject **small_stack, Py_ssize_t small_stack_len, - const char *format, va_list va, Py_ssize_t *p_nargs) +int +_Py_VaBuildArray(PyObject ***array, Py_ssize_t *array_size, + PyObject **small_array, Py_ssize_t small_array_len, + const char *format, va_list va) { - const char *f; - Py_ssize_t n; - va_list lva; - PyObject **stack; - int res; - - n = countformat(format, '\0'); + Py_ssize_t n = countformat(format, '\0'); if (n < 0) { - *p_nargs = 0; - return NULL; + goto error; } if (n == 0) { - *p_nargs = 0; - return small_stack; + *array = small_array; + *array_size = 0; + return 0; } - if (n <= small_stack_len) { - stack = small_stack; + PyObject **stack; + if (n <= small_array_len) { + stack = small_array; } else { stack = PyMem_Malloc(n * sizeof(stack[0])); if (stack == NULL) { PyErr_NoMemory(); - return NULL; + goto error; } } + va_list lva; va_copy(lva, va); - f = format; - res = do_mkstack(stack, &f, &lva, '\0', n); + const char *f = format; + int res = do_mkstack(stack, &f, &lva, '\0', n); va_end(lva); if (res < 0) { - if (stack != small_stack) { + if (stack != small_array) { PyMem_Free(stack); } - return NULL; + goto error; } - *p_nargs = n; - return stack; + *array = stack; + *array_size = n; + return 0; + +error: + *array = NULL; + *array_size = -1; + return -1; }