https://github.com/python/cpython/commit/a7c03835e4d6110eeef5aef63fc429b732b877e4
commit: a7c03835e4d6110eeef5aef63fc429b732b877e4
branch: main
author: Cody Maloney <[email protected]>
committer: eendebakpt <[email protected]>
date: 2026-11-01T21:41:05+01:00
summary:

gh-87613: Use AC and AC vectorcall for bool (#160791)

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 <[email protected]>

files:
A Objects/clinic/boolobject.c.h
M Objects/boolobject.c

diff --git a/Objects/boolobject.c b/Objects/boolobject.c
index b694691ae4d0d1e..b709e0934b7872c 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 <stddef.h>
 
+/*[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 000000000000000..610b87e420d6269
--- /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]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to