classification
Title: Generate vectorcall code to parse arguments using Argument Clinic
Type: Stage:
Components: C API Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: corona10 Nosy List: corona10, erlendaasland, serhiy.storchaka, vstinner
Priority: normal Keywords:

Created on 2021-03-09 11:34 by vstinner, last changed 2021-04-06 13:21 by corona10.

Messages (2)
msg388354 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021-03-09 11:34
To optimize the creation of objects, a lot of "tp_new" methods are defined twice: once in the legacy way (tp_new slot), once with the new VECTORCALL calling convention (tp_vectorcall slot). My concern is that the VECTORCALL implementation copy/paste most of the code just to parse arguments, whereas the specific code is just a few lines.

Example with the float type constructor:
--------------
static PyObject *
float_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
    PyObject *return_value = NULL;
    PyObject *x = NULL;

    if ((type == &PyFloat_Type) &&
        !_PyArg_NoKeywords("float", kwargs)) {
        goto exit;
    }
    if (!_PyArg_CheckPositional("float", PyTuple_GET_SIZE(args), 0, 1)) {
        goto exit;
    }
    if (PyTuple_GET_SIZE(args) < 1) {
        goto skip_optional;
    }
    x = PyTuple_GET_ITEM(args, 0);
skip_optional:
    return_value = float_new_impl(type, x);

exit:
    return return_value;
}

/*[clinic input]
@classmethod
float.__new__ as float_new
    x: object(c_default="NULL") = 0
    /

Convert a string or number to a floating point number, if possible.
[clinic start generated code]*/

static PyObject *
float_new_impl(PyTypeObject *type, PyObject *x)
/*[clinic end generated code: output=ccf1e8dc460ba6ba input=f43661b7de03e9d8]*/
{
    if (type != &PyFloat_Type) {
        if (x == NULL) {
            x = _PyLong_GetZero();
        }
        return float_subtype_new(type, x); /* Wimp out */
    }

    if (x == NULL) {
        return PyFloat_FromDouble(0.0);
    }
    /* If it's a string, but not a string subclass, use
       PyFloat_FromString. */
    if (PyUnicode_CheckExact(x))
        return PyFloat_FromString(x);
    return PyNumber_Float(x);
}

static PyObject *
float_vectorcall(PyObject *type, PyObject * const*args,
                 size_t nargsf, PyObject *kwnames)
{
    if (!_PyArg_NoKwnames("float", kwnames)) {
        return NULL;
    }

    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
    if (!_PyArg_CheckPositional("float", nargs, 0, 1)) {
        return NULL;
    }

    PyObject *x = nargs >= 1 ? args[0] : NULL;
    return float_new_impl((PyTypeObject *)type, x);
}
--------------

Here the float_new() function (tp_new slot) is implemented with Argument Clinic: float_new() C code is generated from the [clinic input] DSL: good!

My concern is that float_vectorcall() code is hand written, it's boring to write and boring to maintain.

Would it be possible to add a new [clinic input] DSL for vectorcall? I expect something like that:
--------------
static PyObject *
float_vectorcall(PyObject *type, PyObject * const*args,
                 size_t nargsf, PyObject *kwnames)
{
    if (!_PyArg_NoKwnames("float", kwnames)) {
        return NULL;
    }

    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
    if (!_PyArg_CheckPositional("float", nargs, 0, 1)) {
        return NULL;
    }

    PyObject *x = nargs >= 1 ? args[0] : NULL;
    return float_vectorcall_impl(type, x);
}

static PyObject *
float_vectorcall_impl(PyObject *type, PyObject *x)
{
    return float_new_impl((PyTypeObject *)type, x);
}
--------------
 
where float_vectorcall() C code would be generated, and float_vectorcall_impl() would be the only part written manually. float_vectorcall_impl() gets a clean API and its body is way simpler to write and to maintain!
msg389214 - (view) Author: Dong-hee Na (corona10) * (Python committer) Date: 2021-03-21 03:46
I agree with we should update AC to generate vectorcall.
I am going to investigate what we can :)
History
Date User Action Args
2021-04-06 13:21:23corona10setassignee: corona10
2021-03-22 09:38:40erlendaaslandsetnosy: + erlendaasland
2021-03-21 03:46:26corona10setmessages: + msg389214
2021-03-09 11:34:23vstinnercreate