diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f5334f70768dc4..ebf2e446ef056b 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3262,8 +3262,6 @@ def test_vectorcall_on_init(self): class Foo "FooObject *" "Foo_Type" @vectorcall Foo.__init__ - iterable: object = NULL - / """ func = self.parse_function(block, signatures_in_block=3, function_index=2) @@ -3294,14 +3292,16 @@ class Foo "FooObject *" "Foo_Type" self.expect_failure(block, err, lineno=2) def test_vectorcall_without_type_object(self): - err = "@vectorcall requires the type object of 'Foo'" + # Heap types have no C pointer to name, so the type object is optional. block = """ module m class Foo "FooObject *" "" @vectorcall Foo.__init__ """ - self.expect_failure(block, err, lineno=3) + func = self.parse_function(block, signatures_in_block=3, + function_index=2) + self.assertTrue(func.vectorcall) def test_vectorcall_unsupported_converter(self): # str(encoding=...) has no parse_arg() implementation. diff --git a/Misc/NEWS.d/next/Library/2026-09-26-05-18-21.gh-issue-144650.zqHLOW.rst b/Misc/NEWS.d/next/Library/2026-09-26-05-18-21.gh-issue-144650.zqHLOW.rst new file mode 100644 index 00000000000000..34a0c0246d3ee8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-26-05-18-21.gh-issue-144650.zqHLOW.rst @@ -0,0 +1,2 @@ +Use AC vectorcall support for the :class:`decimal.Decimal`. This offers +~1.15x speedup on creation of small instances. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index ada9b02d690717..cbcb3bf55463f0 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -57,6 +57,8 @@ #define _PY_DEC_ROUND_GUARD (MPD_ROUND_GUARD-1) #endif +static PyType_Spec dec_spec; + #include "clinic/_decimal.c.h" #define MPD_SPEC_VERSION "1.70" // Highest version of the spec this complies with @@ -132,7 +134,6 @@ get_module_state(PyObject *mod) } static struct PyModuleDef _decimal_module; -static PyType_Spec dec_spec; static PyType_Spec context_spec; static inline decimal_state * @@ -3309,6 +3310,7 @@ PyDec_FromObject(PyObject *v, PyObject *context) } /*[clinic input] +@vectorcall @classmethod _decimal.Decimal.__new__ as dec_new @@ -3325,7 +3327,7 @@ trap is active. static PyObject * dec_new_impl(PyTypeObject *type, PyObject *value, PyObject *context) -/*[clinic end generated code: output=35f48a40c65625ba input=5f8a0892d3fcef80]*/ +/*[clinic end generated code: output=35f48a40c65625ba input=0e5ca99183562cd9]*/ { decimal_state *state = get_module_state_by_def(type); CONTEXT_CHECK_VA(state, context); @@ -6186,6 +6188,7 @@ static PyType_Slot dec_slots[] = { {Py_tp_methods, dec_methods}, {Py_tp_getset, dec_getsets}, {Py_tp_new, dec_new}, + {Py_tp_vectorcall, dec_vectorcall}, // Number protocol {Py_nb_add, nm_mpd_qadd}, diff --git a/Modules/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index 8ad883d8a2d14d..c188e9e61664b8 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -878,7 +878,8 @@ static PyObject * dec_new_impl(PyTypeObject *type, PyObject *value, PyObject *context); static PyObject * -dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +dec_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) @@ -910,12 +911,11 @@ dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) #undef KWTUPLE PyObject *argsbuf[2]; 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 *value = NULL; PyObject *context = Py_None; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, /*minpos*/ 0, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!fastargs) { goto exit; @@ -937,6 +937,47 @@ dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) return return_value; } +static PyObject * +dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return dec_new_helper(type, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +dec_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *value = NULL; + PyObject *context = Py_None; + + assert(_PyType_CAST(type)->tp_new == dec_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 (kwnames != NULL || nargs > 2) { + return dec_new_helper(_PyType_CAST(type), args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + } + if (nargs < 1) { + goto skip_optional; + } + value = args[0]; + if (nargs < 2) { + goto skip_optional; + } + context = args[1]; +skip_optional: + return_value = dec_new_impl(_PyType_CAST(type), value, context); + + return return_value; +} + PyDoc_STRVAR(_decimal_Context_create_decimal__doc__, "create_decimal($self, num=\'0\', /)\n" "--\n" @@ -7100,4 +7141,4 @@ _decimal_Context_same_quantum(PyObject *context, PyTypeObject *cls, PyObject *co #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF #define _DECIMAL_CONTEXT_APPLY_METHODDEF #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */ -/*[clinic end generated code: output=718b1f6c20412350 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=65782a3bf40cc49f input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index c3bf217a9e7b7b..4b9e5d2791d536 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -5030,7 +5030,7 @@ vc_plain_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *a = Py_None; - assert(Py_Is(_PyType_CAST(type), &VcNew_Type)); + assert(_PyType_CAST(type)->tp_new == vc_plain_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)); @@ -5129,7 +5129,7 @@ vc_posorkw_vectorcall(PyObject *type, PyObject *const *args, PyObject *a; PyObject *b = Py_None; - assert(Py_Is(_PyType_CAST(type), &VcInit_Type)); + assert(_PyType_CAST(type)->tp_init == vc_posorkw_init); /* 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)); @@ -5248,7 +5248,7 @@ vc_base_vectorcall(PyObject *type, PyObject *const *args, PyObject *a; PyObject *b = Py_None; - assert(Py_Is(_PyType_CAST(type), &VcNewBase_Type)); + assert(_PyType_CAST(type)->tp_new == vc_base_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)); @@ -5341,7 +5341,7 @@ vc_kwonly_vectorcall(PyObject *type, PyObject *const *args, { Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - assert(Py_Is(_PyType_CAST(type), &VcKwOnly_Type)); + assert(_PyType_CAST(type)->tp_new == vc_kwonly_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)); @@ -5349,4 +5349,4 @@ vc_kwonly_vectorcall(PyObject *type, PyObject *const *args, kwnames ? PyTuple_GET_SIZE(kwnames) : 0, NULL, kwnames); } -/*[clinic end generated code: output=8a219f606f1296ac input=a9049054013a1b77]*/ +/*[clinic end generated code: output=31a229ecc8e80427 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/enumobject.c.h b/Objects/clinic/enumobject.c.h index 26b1801cb7312e..9b8a25a60b571e 100644 --- a/Objects/clinic/enumobject.c.h +++ b/Objects/clinic/enumobject.c.h @@ -99,7 +99,7 @@ enum_vectorcall(PyObject *type, PyObject *const *args, PyObject *iterable; PyObject *start = 0; - assert(Py_Is(_PyType_CAST(type), &PyEnum_Type)); + assert(_PyType_CAST(type)->tp_new == enum_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)); @@ -157,7 +157,7 @@ reversed_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *seq; - assert(Py_Is(_PyType_CAST(type), &PyReversed_Type)); + assert(_PyType_CAST(type)->tp_new == reversed_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)); @@ -173,4 +173,4 @@ reversed_vectorcall(PyObject *type, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=d0c066334eeb3b17 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b97e8b89ca97ba64 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/tupleobject.c.h b/Objects/clinic/tupleobject.c.h index 5e136b2d1cdfdf..9d6a3edf60bb24 100644 --- a/Objects/clinic/tupleobject.c.h +++ b/Objects/clinic/tupleobject.c.h @@ -119,7 +119,7 @@ tuple_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *iterable = NULL; - assert(Py_Is(_PyType_CAST(type), &PyTuple_Type)); + assert(_PyType_CAST(type)->tp_new == tuple_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)); @@ -156,4 +156,4 @@ tuple___getnewargs__(PyObject *self, PyObject *Py_UNUSED(ignored)) { return tuple___getnewargs___impl((PyTupleObject *)self); } -/*[clinic end generated code: output=69cab12f1ecb03e9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1bc2bfc233992933 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index a798fac4f3fd09..ba3345dadecc97 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -639,11 +639,6 @@ def normalize_function_kind(self, fullname: str) -> None: if not self.kind.new_or_init: fail("@vectorcall can only be used with __init__ and __new__ " "methods currently") - # Guaranteed by the __new__ / __init__ checks above. - assert cls is not None - if not cls.type_object: - fail(f"@vectorcall requires the type object of {cls.name!r}, " - f"which was declared without one") def resolve_return_converter( self, full_name: str, forced_converter: str diff --git a/Tools/clinic/libclinic/parse_args.py b/Tools/clinic/libclinic/parse_args.py index 4caab253fe32cc..a73e8a3d1fe060 100644 --- a/Tools/clinic/libclinic/parse_args.py +++ b/Tools/clinic/libclinic/parse_args.py @@ -6,7 +6,7 @@ from libclinic.function import ( Function, Parameter, ParamTuple, count_required, group_to_variable_name, permute_optional_groups, - GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, + GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, METHOD_NEW, ACCESSORS, SETTERS) from libclinic.converter import CConverter from libclinic.converters import ( @@ -1537,19 +1537,22 @@ def create_template_dict(self) -> dict[str, str]: return d2 def _vectorcall_type_check(self) -> list[str]: - """Assert `type` is the one type this vectorcall was generated for. + """Check dispatch function hasn't changed. - The generated code is only correct for that type: __init__ calls - tp_new with no arguments, then the impl. tp_vectorcall is not - inherited, so subclasses never reach it; the assert catches C code - installing the function on a second type. + The generated code is only correct for a type whose tp_new / tp_init is + the parser this vectorcall shadows. """ func = self.func - # The DSL parser rejects @vectorcall without a class and type object. - assert func.cls is not None - assert func.cls.type_object + if func.kind is METHOD_INIT: + check = f"_PyType_CAST(type)->tp_init == {func.c_basename}" + elif func.kind is METHOD_NEW: + check = f"_PyType_CAST(type)->tp_new == {func.c_basename}" + else: + raise AssertionError( + f"Unhandled function kind for vectorcall: {func.kind!r}" + ) return [libclinic.normalize_snippet(f""" - assert(Py_Is(_PyType_CAST(type), {func.cls.type_object})); + assert({check}); /* 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)); @@ -1689,6 +1692,23 @@ def parse_vectorcall_pos_or_kw(self) -> None: parser_code.extend(self._vectorcall_positional(arity_checked=True)) self.vectorcall_body(*parser_code) + def parse_vectorcall_no_args(self) -> None: + """No keyword or positional arguments.""" + parser_code = self._vectorcall_type_check() + self.codegen.add_include('pycore_modsupport.h', + '_PyArg_NoKwnames()') + parser_code.append(libclinic.normalize_snippet(""" + if (nargs) {{ + PyErr_SetString(PyExc_TypeError, + "{name}() takes no positional arguments"); + goto exit; + }} + if (!_PyArg_NoKwnames("{name}", kwnames)) {{ + goto exit; + }} + """, indent=4)) + self.vectorcall_body(*parser_code) + def parse_vectorcall(self) -> None: """Generate the vectorcall entry point for __new__ / __init__. @@ -1696,11 +1716,11 @@ def parse_vectorcall(self) -> None: """ # Branches ordered to mirror parse_args(). The DSL parser rejects # @vectorcall with optional groups, and METH_O never applies to - # __new__/__init__. They always have arguments. + # __new__/__init__. assert not self.has_option_groups() assert not self.use_meth_o() if not self.parameters and not self.varpos and not self.var_keyword: - raise NotImplementedError("No argument vectorcall") + self.parse_vectorcall_no_args() elif self.var_keyword is not None: self.parse_vectorcall_kw_required() elif self.pos_only == len(self.parameters):