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 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 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
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
44 changes: 37 additions & 7 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,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 @@ -183,7 +184,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 @@ -788,6 +794,26 @@ def _construct_varargs(
for value, kind, name in args:
if kind == ARG_STAR:
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 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
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)
self.primitive_op(list_extend_op, [star_result, value], line)
elif kind == ARG_STAR2:
Expand Down Expand Up @@ -885,9 +911,11 @@ def _construct_varargs(
# tuple. Otherwise create the tuple from the list.
if star_result is None:
star_result = self.new_tuple(star_values, line)
else:
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)

return star_result, star2_result
Expand All @@ -912,13 +940,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 @@ -1117,8 +1148,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