Skip to content

[mypyc] feat: PyObject_CallObject op for fn(*args) fastpath #19631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9fc66f8
[mypyc] feat: reuse existing tuple when calling fn(*args)
BobTheBuidler Aug 9, 2025
4db8e93
chore: cleanup
BobTheBuidler Aug 9, 2025
d709425
chore: update IR
BobTheBuidler Aug 9, 2025
301080a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 9, 2025
e028a72
fix: mypy errs
BobTheBuidler Aug 9, 2025
3315a97
Merge branch 'tupe' of https://github.com/BobTheBuidler/mypy into tupe
BobTheBuidler Aug 9, 2025
9057461
fix: mypy errs
BobTheBuidler Aug 9, 2025
d3b3f0d
chore: refactor
BobTheBuidler Aug 9, 2025
27a13a7
feat: extend stararg fastpath logic to handle lists and generic seque…
BobTheBuidler Aug 9, 2025
3e72105
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 9, 2025
3da4bf2
update IR
BobTheBuidler Aug 9, 2025
567b8a4
Merge branch 'stararg-fastpath' of https://github.com/BobTheBuidler/m…
BobTheBuidler Aug 9, 2025
2f0e0fe
fix mypy errs
BobTheBuidler Aug 9, 2025
65e431b
[mypyc] feat: PyObject_CallObject op for fn(*args)
BobTheBuidler Aug 9, 2025
daab9af
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 9, 2025
f3927d4
Update ll_builder.py
BobTheBuidler Aug 9, 2025
5cd545d
Update ll_builder.py
BobTheBuidler Aug 9, 2025
0b06aef
fix: assertion error
BobTheBuidler Aug 9, 2025
353bd24
chore: update IR
BobTheBuidler Aug 9, 2025
b9ff9f8
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 13, 2025
2eb99b8
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 14, 2025
c063a3b
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 18, 2025
c1a6b44
Update irbuild-basic.test
BobTheBuidler Aug 19, 2025
feaabbe
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 19, 2025
16e391f
add run tests
BobTheBuidler Aug 20, 2025
2c2725e
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 20, 2025
145307f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 20, 2025
505c3ed
Update run-functions.test
BobTheBuidler Aug 20, 2025
440e0e2
Update run-functions.test
BobTheBuidler Aug 21, 2025
55fb87f
Update run-functions.test
BobTheBuidler Aug 21, 2025
3fbf62e
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 21, 2025
8df87ae
Merge branch 'master' into PyObject_CallObject
BobTheBuidler Aug 23, 2025
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
1 change: 1 addition & 0 deletions mypyc/annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def __init__(self, message: str, priority: int = 1) -> None:
"PyNumber_Rshift": Annotation('Generic ">>" operation.'),
"PyNumber_Invert": Annotation('Generic "~" operation.'),
"PyObject_Call": Annotation("Generic call operation."),
"PyObject_CallObject": Annotation("Generic call operation."),
"PyObject_RichCompare": Annotation("Generic comparison operation."),
"PyObject_GetItem": Annotation("Generic indexing operation."),
"PyObject_SetItem": Annotation("Generic indexed assignment."),
Expand Down
15 changes: 9 additions & 6 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
generic_ssize_t_len_op,
py_call_op,
py_call_with_kwargs_op,
py_call_with_posargs_op,
py_getattr_op,
py_method_call_op,
py_vectorcall_method_op,
Expand Down Expand Up @@ -805,7 +806,7 @@ def _construct_varargs(
value.type, RTuple
):
value = self.primitive_op(sequence_tuple_op, [value], line)
return value, self._create_dict([], [], line)
return value, None
elif len(args) == 2 and args[1][1] == ARG_STAR2:
# fn(*args, **kwargs)
# TODO: extend to cover(*args, **k, **w, **a, **r, **g, **s)
Expand Down Expand Up @@ -938,7 +939,7 @@ def _construct_varargs(
elif not is_tuple_rprimitive(star_result.type):
# if star_result is a tuple we took the fast path
star_result = self.primitive_op(list_tuple_op, [star_result], line)
if has_star2 and star2_result is None:
if has_star2 and star2_result is None and len(star2_keys) > 0:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might be able to get rid of has_star2 entirely with this PR

# TODO: use dict_copy_op for simple cases of **kwargs
star2_result = self._create_dict(star2_keys, star2_values, line)

Expand All @@ -964,13 +965,16 @@ def py_call(
if arg_kinds is None or all(kind == ARG_POS for kind in arg_kinds):
return self.call_c(py_call_op, [function] + arg_values, line)

# Otherwise fallback to py_call_with_kwargs_op.
# Otherwise fallback to py_call_with_posargs_op or py_call_with_kwargs_op.
assert arg_names is not None

pos_args_tuple, kw_args_dict = self._construct_varargs(
list(zip(arg_values, arg_kinds, arg_names)), line, has_star=True, has_star2=True
)
assert pos_args_tuple and kw_args_dict
assert pos_args_tuple

if kw_args_dict is None:
return self.call_c(py_call_with_posargs_op, [function, pos_args_tuple], line)

return self.call_c(py_call_with_kwargs_op, [function, pos_args_tuple, kw_args_dict], line)

Expand Down Expand Up @@ -1169,8 +1173,7 @@ def native_args_to_positional(
assert star_arg
output_arg = star_arg
elif arg.kind == ARG_STAR2:
assert star2_arg
output_arg = star2_arg
output_arg = star2_arg or self._create_dict([], [], line)
elif not lst:
if is_fixed_width_rtype(arg.type):
output_arg = Integer(0, arg.type)
Expand Down
9 changes: 9 additions & 0 deletions mypyc/primitives/generic_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,15 @@
error_kind=ERR_MAGIC,
)

# Call callable object with positional args only: func(*args)
# Arguments are (func, *args tuple).
py_call_with_posargs_op = custom_op(
arg_types=[object_rprimitive, object_rprimitive],
return_type=object_rprimitive,
c_function_name="PyObject_CallObject",
error_kind=ERR_MAGIC,
)

# Call method with positional arguments: obj.method(arg1, ...)
# Arguments are (object, attribute name, arg1, ...).
py_method_call_op = custom_op(
Expand Down
52 changes: 20 additions & 32 deletions mypyc/test-data/irbuild-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -1674,20 +1674,17 @@ def g():
r0 :: tuple[int, int, int]
r1 :: dict
r2 :: str
r3 :: object
r4 :: dict
r5, r6 :: object
r7 :: tuple[int, int, int]
r3, r4, r5 :: object
r6 :: tuple[int, int, int]
L0:
r0 = (2, 4, 6)
r1 = __main__.globals :: static
r2 = 'f'
r3 = CPyDict_GetItem(r1, r2)
r4 = PyDict_New()
r5 = box(tuple[int, int, int], r0)
r6 = PyObject_Call(r3, r5, r4)
r7 = unbox(tuple[int, int, int], r6)
return r7
r4 = box(tuple[int, int, int], r0)
r5 = PyObject_CallObject(r3, r4)
r6 = unbox(tuple[int, int, int], r5)
return r6
def h():
r0 :: tuple[int, int]
r1 :: dict
Expand All @@ -1698,9 +1695,8 @@ def h():
r6 :: ptr
r7, r8 :: object
r9 :: tuple
r10 :: dict
r11 :: object
r12 :: tuple[int, int, int]
r10 :: object
r11 :: tuple[int, int, int]
L0:
r0 = (4, 6)
r1 = __main__.globals :: static
Expand All @@ -1714,10 +1710,9 @@ L0:
r7 = box(tuple[int, int], r0)
r8 = CPyList_Extend(r4, r7)
r9 = PyList_AsTuple(r4)
r10 = PyDict_New()
r11 = PyObject_Call(r3, r9, r10)
r12 = unbox(tuple[int, int, int], r11)
return r12
r10 = PyObject_CallObject(r3, r9)
r11 = unbox(tuple[int, int, int], r10)
return r11

[case testStar2Args]
from typing import Tuple
Expand Down Expand Up @@ -3562,15 +3557,12 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args):
__mypyc_self__ :: __main__.wrapper_deco_obj
args :: tuple
r0 :: __main__.deco_env
r1 :: object
r2 :: dict
r3 :: object
r1, r2 :: object
L0:
r0 = __mypyc_self__.__mypyc_env__
r1 = r0.fn
r2 = PyDict_New()
r3 = PyObject_Call(r1, args, r2)
return r3
r2 = PyObject_CallObject(r1, args)
return r2
def deco(fn):
fn :: object
r0 :: __main__.deco_env
Expand Down Expand Up @@ -3613,15 +3605,13 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args):
r0 :: __main__.deco_env
r1 :: object
r2 :: tuple
r3 :: dict
r4 :: object
r3 :: object
L0:
r0 = __mypyc_self__.__mypyc_env__
r1 = r0.fn
r2 = PyList_AsTuple(args)
r3 = PyDict_New()
r4 = PyObject_Call(r1, r2, r3)
return r4
r3 = PyObject_CallObject(r1, r2)
return r3
def deco(fn):
fn :: object
r0 :: __main__.deco_env
Expand Down Expand Up @@ -3716,15 +3706,13 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args):
r0 :: __main__.deco_env
r1 :: object
r2 :: tuple
r3 :: dict
r4 :: object
r3 :: object
L0:
r0 = __mypyc_self__.__mypyc_env__
r1 = r0.fn
r2 = PySequence_Tuple(args)
r3 = PyDict_New()
r4 = PyObject_Call(r1, r2, r3)
return r4
r3 = PyObject_CallObject(r1, r2)
return r3
def deco(fn):
fn :: object
r0 :: __main__.deco_env
Expand Down
Loading