diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-05-13-19-49.gh-issue-87613.JLTGBy.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-05-13-19-49.gh-issue-87613.JLTGBy.rst new file mode 100644 index 000000000000000..830e8ef1dc23b28 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-05-13-19-49.gh-issue-87613.JLTGBy.rst @@ -0,0 +1,2 @@ +Speed up :class:`bytes` and :class:`bytearray` construction by adding +:ref:`vectorcall` support. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 496d04a1704d46d..429b8d993867f0c 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -977,6 +977,7 @@ bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) } /*[clinic input] +@vectorcall bytearray.__init__ source as arg: object = NULL @@ -988,7 +989,7 @@ bytearray.__init__ static int bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, const char *encoding, const char *errors) -/*[clinic end generated code: output=4ce1304649c2f8b3 input=1141a7122eefd7b9]*/ +/*[clinic end generated code: output=4ce1304649c2f8b3 input=24ddb84055f432dd]*/ { Py_ssize_t count; PyObject *it; @@ -3027,6 +3028,7 @@ PyTypeObject PyByteArray_Type = { PyType_GenericAlloc, /* tp_alloc */ bytearray_new, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_vectorcall = bytearray_vectorcall, .tp_version_tag = _Py_TYPE_VERSION_BYTEARRAY, }; diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 1917055d25198f1..ada81db66427020 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2918,6 +2918,7 @@ static PyObject * bytes_subtype_new(PyTypeObject *, PyObject *); /*[clinic input] +@vectorcall @classmethod bytes.__new__ as bytes_new @@ -2930,7 +2931,7 @@ bytes.__new__ as bytes_new static PyObject * bytes_new_impl(PyTypeObject *type, PyObject *x, const char *encoding, const char *errors) -/*[clinic end generated code: output=1e0c471be311a425 input=f0a966d19b7262b4]*/ +/*[clinic end generated code: output=1e0c471be311a425 input=b0248d22e221a095]*/ { PyObject *bytes; PyObject *func; @@ -3316,6 +3317,7 @@ PyTypeObject PyBytes_Type = { bytes_type_alloc, /* tp_alloc */ bytes_new, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_vectorcall = bytes_vectorcall, .tp_version_tag = _Py_TYPE_VERSION_BYTES, ._tp_iteritem = bytes_iteritem, }; diff --git a/Objects/clinic/bytearrayobject.c.h b/Objects/clinic/bytearrayobject.c.h index b074851f1fbf78f..9c8ba841200f306 100644 --- a/Objects/clinic/bytearrayobject.c.h +++ b/Objects/clinic/bytearrayobject.c.h @@ -4,11 +4,11 @@ preserve #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) # include "pycore_gc.h" // PyGC_Head -# include "pycore_runtime.h" // _Py_ID() #endif #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +#include "pycore_runtime.h" // _Py_SINGLETON() #include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8NoNUL() static int @@ -16,7 +16,8 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, const char *encoding, const char *errors); static int -bytearray___init__(PyObject *self, PyObject *args, PyObject *kwargs) +bytearray___init___helper(PyObject *self, PyObject *const *args, + Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) { int return_value = -1; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -48,13 +49,12 @@ bytearray___init__(PyObject *self, PyObject *args, PyObject *kwargs) #undef KWTUPLE PyObject *argsbuf[3]; PyObject * const *fastargs; - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + Py_ssize_t noptargs = nargs + nkw - 0; PyObject *arg = NULL; const char *encoding = NULL; const char *errors = NULL; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, /*minpos*/ 0, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!fastargs) { goto exit; @@ -96,6 +96,89 @@ bytearray___init__(PyObject *self, PyObject *args, PyObject *kwargs) return return_value; } +static int +bytearray___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return bytearray___init___helper(self, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +bytearray_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *self; + int _result; + PyObject *arg = NULL; + const char *encoding = NULL; + const char *errors = NULL; + + assert(Py_Is(_PyType_CAST(type), &PyByteArray_Type)); + /* Make sure the type object is immutable: the generated + * vectorcall doesn't deal e.g. with users reassigning __init__. */ + assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); + if (kwnames != NULL || nargs > 3) { + self = _PyType_CAST(type)->tp_new(_PyType_CAST(type), + (PyObject *)&_Py_SINGLETON(tuple_empty), NULL); + if (self == NULL) { + return NULL; + } + _result = bytearray___init___helper(self, args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + if (_result != 0) { + Py_DECREF(self); + return NULL; + } + return self; + } + if (nargs < 1) { + goto skip_optional; + } + arg = args[0]; + if (nargs < 2) { + goto skip_optional; + } + if (!PyUnicode_Check(args[1])) { + _PyArg_BadArgument("bytearray", "argument 'encoding'", "str", args[1]); + goto exit; + } + encoding = _PyUnicode_AsUTF8NoNUL(args[1]); + if (encoding == NULL) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + if (!PyUnicode_Check(args[2])) { + _PyArg_BadArgument("bytearray", "argument 'errors'", "str", args[2]); + goto exit; + } + errors = _PyUnicode_AsUTF8NoNUL(args[2]); + if (errors == NULL) { + goto exit; + } +skip_optional: + self = _PyType_CAST(type)->tp_new(_PyType_CAST(type), + (PyObject *)&_Py_SINGLETON(tuple_empty), NULL); + if (self == NULL) { + goto exit; + } + _result = bytearray___init___impl((PyByteArrayObject *)self, arg, encoding, errors); + if (_result != 0) { + Py_DECREF(self); + goto exit; + } + return_value = self; + +exit: + return return_value; +} + PyDoc_STRVAR(bytearray_find__doc__, "find($self, sub[, start[, end]], /)\n" "--\n" @@ -1863,4 +1946,4 @@ bytearray_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) { return bytearray_sizeof_impl((PyByteArrayObject *)self); } -/*[clinic end generated code: output=16fb40de6aefa5f8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b67d1f1e84c7ed0f input=a9049054013a1b77]*/ diff --git a/Objects/clinic/bytesobject.c.h b/Objects/clinic/bytesobject.c.h index 56e8d089334a7a7..2b2850b5933cf9b 100644 --- a/Objects/clinic/bytesobject.c.h +++ b/Objects/clinic/bytesobject.c.h @@ -1357,7 +1357,8 @@ bytes_new_impl(PyTypeObject *type, PyObject *x, const char *encoding, const char *errors); static PyObject * -bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +bytes_new_helper(PyTypeObject *type, PyObject *const *args, + Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1389,13 +1390,12 @@ bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) #undef KWTUPLE PyObject *argsbuf[3]; PyObject * const *fastargs; - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + Py_ssize_t noptargs = nargs + nkw - 0; PyObject *x = NULL; const char *encoding = NULL; const char *errors = NULL; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, /*minpos*/ 0, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!fastargs) { goto exit; @@ -1436,4 +1436,65 @@ bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=2de57d656cc2f041 input=a9049054013a1b77]*/ + +static PyObject * +bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return bytes_new_helper(type, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +bytes_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *x = NULL; + const char *encoding = NULL; + const char *errors = NULL; + + assert(Py_Is(_PyType_CAST(type), &PyBytes_Type)); + /* Make sure the type object is immutable: the generated + * vectorcall doesn't deal e.g. with users reassigning __init__. */ + assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); + if (kwnames != NULL || nargs > 3) { + return bytes_new_helper(_PyType_CAST(type), args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + } + if (nargs < 1) { + goto skip_optional; + } + x = args[0]; + if (nargs < 2) { + goto skip_optional; + } + if (!PyUnicode_Check(args[1])) { + _PyArg_BadArgument("bytes", "argument 'encoding'", "str", args[1]); + goto exit; + } + encoding = _PyUnicode_AsUTF8NoNUL(args[1]); + if (encoding == NULL) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + if (!PyUnicode_Check(args[2])) { + _PyArg_BadArgument("bytes", "argument 'errors'", "str", args[2]); + goto exit; + } + errors = _PyUnicode_AsUTF8NoNUL(args[2]); + if (errors == NULL) { + goto exit; + } +skip_optional: + return_value = bytes_new_impl(_PyType_CAST(type), x, encoding, errors); + +exit: + return return_value; +} +/*[clinic end generated code: output=9ea978e8e97e33ae input=a9049054013a1b77]*/