Skip to content

[mypyc] feat: stararg fastpath when calling fn(*args) with tuple #19623

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

Merged
merged 8 commits into from
Aug 13, 2025
Merged
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
16 changes: 15 additions & 1 deletion mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,18 @@ 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 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)
star_result = value
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 +897,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:
# 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 Down
57 changes: 22 additions & 35 deletions mypyc/test-data/irbuild-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -166,25 +166,18 @@ execute(f, 1)
def execute(func, args, kwargs):
func :: object
args :: tuple
kwargs :: dict
r0 :: list
r1 :: object
r2 :: dict
r3 :: i32
r4 :: bit
r5 :: tuple
r6 :: object
r7 :: int
kwargs, r0 :: dict
r1 :: i32
r2 :: bit
r3 :: object
r4 :: int
L0:
r0 = PyList_New(0)
r1 = CPyList_Extend(r0, args)
r2 = PyDict_New()
r3 = CPyDict_UpdateInDisplay(r2, kwargs)
r4 = r3 >= 0 :: signed
r5 = PyList_AsTuple(r0)
r6 = PyObject_Call(func, r5, r2)
r7 = unbox(int, r6)
return r7
r0 = PyDict_New()
r1 = CPyDict_UpdateInDisplay(r0, kwargs)
r2 = r1 >= 0 :: signed
r3 = PyObject_Call(func, args, r0)
r4 = unbox(int, r3)
return r4
def f(x):
x :: int
L0:
Expand Down Expand Up @@ -709,14 +702,11 @@ def inner_deco_obj.__call__(__mypyc_self__, args, kwargs):
can_dictcomp :: dict
r22, can_iter, r23, can_use_keys, r24, can_use_values :: list
r25 :: object
r26 :: list
r27 :: object
r28 :: dict
r29 :: i32
r30 :: bit
r31 :: tuple
r32 :: object
r33 :: int
r26 :: dict
r27 :: i32
r28 :: bit
r29 :: object
r30 :: int
L0:
r0 = __mypyc_self__.__mypyc_env__
r1 = var_object_size args
Expand Down Expand Up @@ -768,15 +758,12 @@ L9:
r24 = CPyDict_Values(kwargs)
can_use_values = r24
r25 = r0.func
r26 = PyList_New(0)
r27 = CPyList_Extend(r26, args)
r28 = PyDict_New()
r29 = CPyDict_UpdateInDisplay(r28, kwargs)
r30 = r29 >= 0 :: signed
r31 = PyList_AsTuple(r26)
r32 = PyObject_Call(r25, r31, r28)
r33 = unbox(int, r32)
return r33
r26 = PyDict_New()
r27 = CPyDict_UpdateInDisplay(r26, kwargs)
r28 = r27 >= 0 :: signed
r29 = PyObject_Call(r25, args, r26)
r30 = unbox(int, r29)
return r30
def deco(func):
func :: object
r0 :: __main__.deco_env
Expand Down
Loading