diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-10-22-16-18.gh-issue-117709.-_1YL0.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-10-22-16-18.gh-issue-117709.-_1YL0.rst new file mode 100644 index 00000000000000..1877ac5818cb69 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-10-22-16-18.gh-issue-117709.-_1YL0.rst @@ -0,0 +1,2 @@ +Speed up calls to :func:`str` by using the :pep:`590` ``vectorcall`` calling +convention. Patch by Erlend Aasland. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 5f15071d7d54ef..2a6f05d59baecd 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -14617,6 +14617,115 @@ unicode_new_impl(PyTypeObject *type, PyObject *x, const char *encoding, return unicode; } +static const char * +as_const_char(PyObject *obj, const char *name) +{ + if (!PyUnicode_Check(obj)) { + PyErr_Format(PyExc_TypeError, + "str() argument '%s' must be str, not %T", + name, obj); + return NULL; + } + const char *str = _PyUnicode_AsUTF8NoNUL(obj); + if (str == NULL) { + return NULL; + } + return str; +} + +static PyObject * +fallback_to_tp_call(PyObject *type, Py_ssize_t nargs, Py_ssize_t nkwargs, + PyObject *const *args, PyObject *kwnames) +{ + PyObject *tuple = _PyTuple_FromArray(args, nargs); + if (tuple == NULL) { + return NULL; + } + PyObject *dict = _PyStack_AsDict(args + nargs, kwnames); + if (dict == NULL) { + Py_DECREF(tuple); + return NULL; + } + return unicode_new(_PyType_CAST(type), tuple, dict); +} + +static PyObject * +unicode_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + assert(Py_Is(_PyType_CAST(type), &PyUnicode_Type)); + + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + Py_ssize_t nkwargs = (kwnames) ? PyTuple_GET_SIZE(kwnames) : 0; + if (nargs == 0 && nkwargs == 0) { + return unicode_get_empty(); + } + PyObject *object = args[0]; + if (nargs == 1 && nkwargs == 0) { + return PyObject_Str(object); + } + if (nargs + nkwargs == 2) { + const char *encoding = NULL; + const char *errors = NULL; + if (nkwargs == 1) { + PyObject *key = PyTuple_GET_ITEM(kwnames, 0); + if (_PyUnicode_EqualToASCIIString(key, "encoding")) { + encoding = as_const_char(args[1], "encoding"); + if (encoding == NULL) { + return NULL; + } + } + else if (_PyUnicode_EqualToASCIIString(key, "errors")) { + errors = as_const_char(args[1], "errors"); + if (errors == NULL) { + return NULL; + } + } + else { + PyErr_Format(PyExc_TypeError, + "str() got an unexpected keyword argument %R", key); + return NULL; + } + } + else if (nkwargs == 0) { + encoding = as_const_char(args[1], "encoding"); + } + else { + return fallback_to_tp_call(type, nargs, nkwargs, args, kwnames); + } + return PyUnicode_FromEncodedObject(object, encoding, errors); + } + if (nargs + nkwargs == 3) { + if (nkwargs == 1) { + PyObject *key = PyTuple_GET_ITEM(kwnames, 0); + if (!_PyUnicode_EqualToASCIIString(key, "errors")) { + PyErr_Format(PyExc_TypeError, + "str() got an unexpected keyword argument %R", key); + return NULL; + } + } + else if (nkwargs != 0) { + return fallback_to_tp_call(type, nargs, nkwargs, args, kwnames); + } + const char *encoding = as_const_char(args[1], "encoding"); + if (encoding == NULL) { + return NULL; + } + const char *errors = as_const_char(args[2], "errors"); + if (errors == NULL) { + return NULL; + } + return PyUnicode_FromEncodedObject(object, encoding, errors); + } + if (nargs > 3) { + PyErr_Format(PyExc_TypeError, + "str() takes at most 3 arguments (%d given)", nargs + nkwargs); + return NULL; + } + + return fallback_to_tp_call(type, nargs, nkwargs, args, kwnames); +} + static PyObject * unicode_subtype_new(PyTypeObject *type, PyObject *unicode) { @@ -14758,6 +14867,7 @@ PyTypeObject PyUnicode_Type = { 0, /* tp_alloc */ unicode_new, /* tp_new */ PyObject_Del, /* tp_free */ + .tp_vectorcall = unicode_vectorcall, }; /* Initialize the Unicode implementation */