From 5e41a26b79a349a5135b6df68da8d032c2120967 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Tue, 27 Oct 2026 16:59:02 -0700 Subject: [PATCH] gh-87613: Use AC vectorcall for bool Move bool.__new__ to Argument Clinic with @vectorcall, dropping the hand-written bool_new and bool_vectorcall. The generated vectorcall name matches the hand-written one so the tp_vectorcall slot is unchanged, and argument error messages are unchanged on both paths. pyperf microbenchmark (PGO+LTO, run serially), no significant change on the vectorcall path: | Benchmark | base | bool | |----------------|:-------:|:---------------------:| | bool.__new__ | 23.0 ns | 21.7 ns: 1.06x faster | | bool() | 10.1 ns | 10.4 ns: 1.03x slower | | bool(int) | 12.2 ns | 12.6 ns: 1.03x slower | | bool(True) | 11.1 ns | 11.4 ns: 1.03x slower | | Geometric mean | (ref) | 1.00x slower | Benchmark hidden because not significant (2): bool(__bool__), bool(list) Instructions per call (callgrind, inclusive of bool_vectorcall or bool_new, PGO+LTO): | Case | base | bool | delta | |-----------------------|-----:|-----:|------:| | bool() | 17 | 18 | +1 | | bool(True) | 20 | 23 | +3 | | bool(12345) | 53 | 49 | -4 | | bool([1]) | 50 | 47 | -3 | | bool.__new__(bool, 1) | 127 | 69 | -58 | The compiler folds PyObject_IsTrue(Py_False) for bool(), so the default costs nothing; the small deltas are register allocation. bool.__new__ drops PyArg_UnpackTuple for inline clinic parsing. Co-Authored-By: Claude Opus 5.5 --- Objects/boolobject.c | 67 +++++++++++--------------------- Objects/clinic/boolobject.c.h | 73 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 44 deletions(-) create mode 100644 Objects/clinic/boolobject.c.h diff --git a/Objects/boolobject.c b/Objects/boolobject.c index b694691ae4d0d1..b709e0934b7872 100644 --- a/Objects/boolobject.c +++ b/Objects/boolobject.c @@ -2,12 +2,18 @@ #include "Python.h" #include "pycore_long.h" // FALSE_TAG TRUE_TAG -#include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _Py_FatalRefcountError() #include "pycore_runtime.h" // _Py_ID() #include +/*[clinic input] +class bool "PyObject *" "&PyBool_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ee492546880a882f]*/ + +#include "clinic/boolobject.c.h" + /* We define bool_repr to return "False" or "True" */ static PyObject * @@ -23,45 +29,28 @@ PyObject *PyBool_FromLong(long ok) return ok ? Py_True : Py_False; } -/* We define bool_new to always return either Py_True or Py_False */ +/*[clinic input] +@vectorcall +@classmethod +bool.__new__ as bool_new + object as x: object = False + / -static PyObject * -bool_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyObject *x = Py_False; - long ok; +Returns True when the argument is true, False otherwise. - if (!_PyArg_NoKeywords("bool", kwds)) - return NULL; - if (!PyArg_UnpackTuple(args, "bool", 0, 1, &x)) - return NULL; - ok = PyObject_IsTrue(x); - if (ok < 0) - return NULL; - return PyBool_FromLong(ok); -} +The builtins True and False are the only two instances of the class +bool. The class bool is a subclass of the class int, and cannot be +subclassed. +[clinic start generated code]*/ static PyObject * -bool_vectorcall(PyObject *type, PyObject * const*args, - size_t nargsf, PyObject *kwnames) +bool_new_impl(PyTypeObject *type, PyObject *x) +/*[clinic end generated code: output=ec434408d43a3773 input=d96e25fce5a04f73]*/ { - long ok = 0; - if (!_PyArg_NoKwnames("bool", kwnames)) { - return NULL; - } - - Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - if (!_PyArg_CheckPositional("bool", nargs, 0, 1)) { + int ok = PyObject_IsTrue(x); + if (ok < 0) { return NULL; } - - assert(PyType_Check(type)); - if (nargs) { - ok = PyObject_IsTrue(args[0]); - if (ok < 0) { - return NULL; - } - } return PyBool_FromLong(ok); } @@ -107,16 +96,6 @@ bool_xor(PyObject *a, PyObject *b) return PyBool_FromLong((a == Py_True) ^ (b == Py_True)); } -/* Doc string */ - -PyDoc_STRVAR(bool_doc, -"bool(object=False, /)\n\ ---\n\ -\n\ -Returns True when the argument is true, False otherwise.\n\ -The builtins True and False are the only two instances of the class bool.\n\ -The class bool is a subclass of the class int, and cannot be subclassed."); - /* Arithmetic methods -- only so we can override &, |, ^. */ static PyNumberMethods bool_as_number = { @@ -189,7 +168,7 @@ PyTypeObject PyBool_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ - bool_doc, /* tp_doc */ + bool_new__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ diff --git a/Objects/clinic/boolobject.c.h b/Objects/clinic/boolobject.c.h new file mode 100644 index 00000000000000..610b87e420d626 --- /dev/null +++ b/Objects/clinic/boolobject.c.h @@ -0,0 +1,73 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(bool_new__doc__, +"bool(object=False, /)\n" +"--\n" +"\n" +"Returns True when the argument is true, False otherwise.\n" +"\n" +"The builtins True and False are the only two instances of the class\n" +"bool. The class bool is a subclass of the class int, and cannot be\n" +"subclassed."); + +static PyObject * +bool_new_impl(PyTypeObject *type, PyObject *x); + +static PyObject * +bool_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = &PyBool_Type; + PyObject *x = Py_False; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("bool", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("bool", 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 = bool_new_impl(type, x); + +exit: + return return_value; +} + +static PyObject * +bool_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *x = Py_False; + + assert(_PyType_CAST(type)->tp_new == bool_new); + /* 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 (!_PyArg_NoKwnames("bool", kwnames)) { + goto exit; + } + if (!_PyArg_CheckPositional("bool", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + x = args[0]; +skip_optional: + return_value = bool_new_impl(_PyType_CAST(type), x); + +exit: + return return_value; +} +/*[clinic end generated code: output=c7be32f9d1f3e07a input=a9049054013a1b77]*/