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 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 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
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
42 changes: 29 additions & 13 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,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 @@ -184,7 +185,12 @@
str_ssize_t_size_op,
unicode_compare,
)
from mypyc.primitives.tuple_ops import list_tuple_op, new_tuple_op, new_tuple_with_length_op
from mypyc.primitives.tuple_ops import (
list_tuple_op,
new_tuple_op,
new_tuple_with_length_op,
sequence_tuple_op,
)
from mypyc.rt_subtype import is_runtime_subtype
from mypyc.sametype import is_same_type
from mypyc.subtype import is_subtype
Expand Down Expand Up @@ -791,14 +797,22 @@ def _construct_varargs(
if star_result is None:
# fast path if star expr is a tuple:
# we can pass the immutable tuple straight into the function call.
if is_tuple_rprimitive(value.type):
if len(args) == 1:
# fn(*args)
return value, self._create_dict([], [], line)
elif len(args) == 2 and args[1][1] == ARG_STAR2:
# fn(*args, **kwargs)
if len(args) == 1:
# fn(*args)
if is_list_rprimitive(value.type):
value = self.primitive_op(list_tuple_op, [value], line)
elif not is_tuple_rprimitive(value.type):
value = self.primitive_op(sequence_tuple_op, [value], line)
return value, None
elif len(args) == 2 and args[1][1] == ARG_STAR2:
# fn(*args, **kwargs)
if is_tuple_rprimitive(value.type):
star_result = value
continue
elif is_list_rprimitive(value.type):
star_result = self.primitive_op(list_tuple_op, [value], line)
else:
star_result = self.primitive_op(sequence_tuple_op, [value], line)
continue
# elif ...: TODO extend this to optimize fn(*args, k=1, **kwargs) case
# TODO optimize this case using the length utils - currently in review
star_result = self.new_list_op(star_values, line)
Expand Down Expand Up @@ -901,7 +915,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 @@ -927,13 +941,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 @@ -1132,8 +1149,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 @@ -307,6 +307,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
2 changes: 1 addition & 1 deletion mypyc/primitives/tuple_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
)

# Construct tuple from an arbitrary (iterable) object.
function_op(
sequence_tuple_op = function_op(
name="builtins.tuple",
arg_types=[object_rprimitive],
return_type=tuple_rprimitive,
Expand Down
Loading
Loading