Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Use AC vectorcall support for the :class:`decimal.Decimal`. This offers
~1.15x speedup on creation of small instances.
7 changes: 5 additions & 2 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 *
Expand Down Expand Up @@ -3309,6 +3310,7 @@ PyDec_FromObject(PyObject *v, PyObject *context)
}

/*[clinic input]
@vectorcall
@classmethod
_decimal.Decimal.__new__ as dec_new

Expand All @@ -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);
Expand Down Expand Up @@ -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},
Expand Down
51 changes: 46 additions & 5 deletions Modules/_decimal/clinic/_decimal.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Modules/clinic/_testclinic.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Objects/clinic/enumobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Objects/clinic/tupleobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 0 additions & 5 deletions Tools/clinic/libclinic/dsl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 32 additions & 12 deletions Tools/clinic/libclinic/parse_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1689,18 +1692,35 @@ 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__.

Dispatch to specific parser-code builders based on parameter shape.
"""
# 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):
Expand Down
Loading