Skip to content

Commit 91487cb

Browse files
[mypyc] feat: extend stararg fastpath from #19623 to handle lists and generic sequences (#19629)
This PR extends #19623 with additional logic for handling non-tuple star inputs Now, we can use the fast path for any arbitrary sequence, in addition to tuples. I opted to separate this PR from 19623 to keep them smaller and easier to review, and to declutter the changes in the IR.
1 parent 38eeff8 commit 91487cb

File tree

3 files changed

+294
-23
lines changed

3 files changed

+294
-23
lines changed

mypyc/irbuild/ll_builder.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,12 @@
184184
str_ssize_t_size_op,
185185
unicode_compare,
186186
)
187-
from mypyc.primitives.tuple_ops import list_tuple_op, new_tuple_op, new_tuple_with_length_op
187+
from mypyc.primitives.tuple_ops import (
188+
list_tuple_op,
189+
new_tuple_op,
190+
new_tuple_with_length_op,
191+
sequence_tuple_op,
192+
)
188193
from mypyc.rt_subtype import is_runtime_subtype
189194
from mypyc.sametype import is_same_type
190195
from mypyc.subtype import is_subtype
@@ -789,16 +794,25 @@ def _construct_varargs(
789794
for value, kind, name in args:
790795
if kind == ARG_STAR:
791796
if star_result is None:
792-
# fast path if star expr is a tuple:
793-
# we can pass the immutable tuple straight into the function call.
794-
if is_tuple_rprimitive(value.type):
795-
if len(args) == 1:
796-
# fn(*args)
797-
return value, self._create_dict([], [], line)
798-
elif len(args) == 2 and args[1][1] == ARG_STAR2:
799-
# fn(*args, **kwargs)
797+
# star args fastpath
798+
if len(args) == 1:
799+
# fn(*args)
800+
if is_list_rprimitive(value.type):
801+
value = self.primitive_op(list_tuple_op, [value], line)
802+
elif not is_tuple_rprimitive(value.type) and not isinstance(
803+
value.type, RTuple
804+
):
805+
value = self.primitive_op(sequence_tuple_op, [value], line)
806+
return value, self._create_dict([], [], line)
807+
elif len(args) == 2 and args[1][1] == ARG_STAR2:
808+
# fn(*args, **kwargs)
809+
if is_tuple_rprimitive(value.type) or isinstance(value.type, RTuple):
800810
star_result = value
801-
continue
811+
elif is_list_rprimitive(value.type):
812+
star_result = self.primitive_op(list_tuple_op, [value], line)
813+
else:
814+
star_result = self.primitive_op(sequence_tuple_op, [value], line)
815+
continue
802816
# elif ...: TODO extend this to optimize fn(*args, k=1, **kwargs) case
803817
# TODO optimize this case using the length utils - currently in review
804818
star_result = self.new_list_op(star_values, line)

mypyc/primitives/tuple_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
)
7777

7878
# Construct tuple from an arbitrary (iterable) object.
79-
function_op(
79+
sequence_tuple_op = function_op(
8080
name="builtins.tuple",
8181
arg_types=[object_rprimitive],
8282
return_type=tuple_rprimitive,

mypyc/test-data/irbuild-basic.test

Lines changed: 269 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,25 +1675,19 @@ def g():
16751675
r1 :: dict
16761676
r2 :: str
16771677
r3 :: object
1678-
r4 :: list
1678+
r4 :: dict
16791679
r5, r6 :: object
1680-
r7 :: tuple
1681-
r8 :: dict
1682-
r9 :: object
1683-
r10 :: tuple[int, int, int]
1680+
r7 :: tuple[int, int, int]
16841681
L0:
16851682
r0 = (2, 4, 6)
16861683
r1 = __main__.globals :: static
16871684
r2 = 'f'
16881685
r3 = CPyDict_GetItem(r1, r2)
1689-
r4 = PyList_New(0)
1686+
r4 = PyDict_New()
16901687
r5 = box(tuple[int, int, int], r0)
1691-
r6 = CPyList_Extend(r4, r5)
1692-
r7 = PyList_AsTuple(r4)
1693-
r8 = PyDict_New()
1694-
r9 = PyObject_Call(r3, r7, r8)
1695-
r10 = unbox(tuple[int, int, int], r9)
1696-
return r10
1688+
r6 = PyObject_Call(r3, r5, r4)
1689+
r7 = unbox(tuple[int, int, int], r6)
1690+
return r7
16971691
def h():
16981692
r0 :: tuple[int, int]
16991693
r1 :: dict
@@ -3546,3 +3540,266 @@ L0:
35463540
r2 = PyObject_Vectorcall(r1, 0, 0, 0)
35473541
r3 = box(None, 1)
35483542
return r3
3543+
3544+
[case testStarArgFastPathTuple]
3545+
from typing import Any, Callable
3546+
def deco(fn: Callable[..., Any]) -> Callable[..., Any]:
3547+
def wrapper(*args: Any) -> Any:
3548+
return fn(*args)
3549+
return wrapper
3550+
3551+
[out]
3552+
def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner):
3553+
__mypyc_self__, instance, owner, r0 :: object
3554+
r1 :: bit
3555+
r2 :: object
3556+
L0:
3557+
r0 = load_address _Py_NoneStruct
3558+
r1 = instance == r0
3559+
if r1 goto L1 else goto L2 :: bool
3560+
L1:
3561+
return __mypyc_self__
3562+
L2:
3563+
r2 = PyMethod_New(__mypyc_self__, instance)
3564+
return r2
3565+
def wrapper_deco_obj.__call__(__mypyc_self__, args):
3566+
__mypyc_self__ :: __main__.wrapper_deco_obj
3567+
args :: tuple
3568+
r0 :: __main__.deco_env
3569+
r1 :: object
3570+
r2 :: dict
3571+
r3 :: object
3572+
L0:
3573+
r0 = __mypyc_self__.__mypyc_env__
3574+
r1 = r0.fn
3575+
r2 = PyDict_New()
3576+
r3 = PyObject_Call(r1, args, r2)
3577+
return r3
3578+
def deco(fn):
3579+
fn :: object
3580+
r0 :: __main__.deco_env
3581+
r1 :: bool
3582+
r2 :: __main__.wrapper_deco_obj
3583+
r3 :: bool
3584+
wrapper :: object
3585+
L0:
3586+
r0 = deco_env()
3587+
r0.fn = fn; r1 = is_error
3588+
r2 = wrapper_deco_obj()
3589+
r2.__mypyc_env__ = r0; r3 = is_error
3590+
wrapper = r2
3591+
return wrapper
3592+
3593+
[case testStarArgFastPathList]
3594+
from typing import Any, Callable
3595+
def deco(fn: Callable[..., Any]) -> Callable[[list[Any]], Any]:
3596+
def wrapper(args: list[Any]) -> Any:
3597+
return fn(*args)
3598+
return wrapper
3599+
3600+
[out]
3601+
def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner):
3602+
__mypyc_self__, instance, owner, r0 :: object
3603+
r1 :: bit
3604+
r2 :: object
3605+
L0:
3606+
r0 = load_address _Py_NoneStruct
3607+
r1 = instance == r0
3608+
if r1 goto L1 else goto L2 :: bool
3609+
L1:
3610+
return __mypyc_self__
3611+
L2:
3612+
r2 = PyMethod_New(__mypyc_self__, instance)
3613+
return r2
3614+
def wrapper_deco_obj.__call__(__mypyc_self__, args):
3615+
__mypyc_self__ :: __main__.wrapper_deco_obj
3616+
args :: list
3617+
r0 :: __main__.deco_env
3618+
r1 :: object
3619+
r2 :: tuple
3620+
r3 :: dict
3621+
r4 :: object
3622+
L0:
3623+
r0 = __mypyc_self__.__mypyc_env__
3624+
r1 = r0.fn
3625+
r2 = PyList_AsTuple(args)
3626+
r3 = PyDict_New()
3627+
r4 = PyObject_Call(r1, r2, r3)
3628+
return r4
3629+
def deco(fn):
3630+
fn :: object
3631+
r0 :: __main__.deco_env
3632+
r1 :: bool
3633+
r2 :: __main__.wrapper_deco_obj
3634+
r3 :: bool
3635+
wrapper :: object
3636+
L0:
3637+
r0 = deco_env()
3638+
r0.fn = fn; r1 = is_error
3639+
r2 = wrapper_deco_obj()
3640+
r2.__mypyc_env__ = r0; r3 = is_error
3641+
wrapper = r2
3642+
return wrapper
3643+
3644+
[case testStarArgFastPathListWithKwargs]
3645+
from typing import Any, Callable
3646+
def deco(fn: Callable[..., Any]) -> Callable[..., Any]:
3647+
def wrapper(lst: list[Any], kwargs: dict[str, Any]) -> Any:
3648+
return fn(*lst, **kwargs)
3649+
return wrapper
3650+
3651+
[out]
3652+
def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner):
3653+
__mypyc_self__, instance, owner, r0 :: object
3654+
r1 :: bit
3655+
r2 :: object
3656+
L0:
3657+
r0 = load_address _Py_NoneStruct
3658+
r1 = instance == r0
3659+
if r1 goto L1 else goto L2 :: bool
3660+
L1:
3661+
return __mypyc_self__
3662+
L2:
3663+
r2 = PyMethod_New(__mypyc_self__, instance)
3664+
return r2
3665+
def wrapper_deco_obj.__call__(__mypyc_self__, lst, kwargs):
3666+
__mypyc_self__ :: __main__.wrapper_deco_obj
3667+
lst :: list
3668+
kwargs :: dict
3669+
r0 :: __main__.deco_env
3670+
r1 :: object
3671+
r2 :: tuple
3672+
r3 :: dict
3673+
r4 :: i32
3674+
r5 :: bit
3675+
r6 :: object
3676+
L0:
3677+
r0 = __mypyc_self__.__mypyc_env__
3678+
r1 = r0.fn
3679+
r2 = PyList_AsTuple(lst)
3680+
r3 = PyDict_New()
3681+
r4 = CPyDict_UpdateInDisplay(r3, kwargs)
3682+
r5 = r4 >= 0 :: signed
3683+
r6 = PyObject_Call(r1, r2, r3)
3684+
return r6
3685+
def deco(fn):
3686+
fn :: object
3687+
r0 :: __main__.deco_env
3688+
r1 :: bool
3689+
r2 :: __main__.wrapper_deco_obj
3690+
r3 :: bool
3691+
wrapper :: object
3692+
L0:
3693+
r0 = deco_env()
3694+
r0.fn = fn; r1 = is_error
3695+
r2 = wrapper_deco_obj()
3696+
r2.__mypyc_env__ = r0; r3 = is_error
3697+
wrapper = r2
3698+
return wrapper
3699+
3700+
[case testStarArgFastPathSequence]
3701+
from typing import Any, Callable
3702+
def deco(fn: Callable[[Any], Any]) -> Callable[[Any], Any]:
3703+
def wrapper(args: Any) -> Any:
3704+
return fn(*args)
3705+
return wrapper
3706+
3707+
[out]
3708+
def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner):
3709+
__mypyc_self__, instance, owner, r0 :: object
3710+
r1 :: bit
3711+
r2 :: object
3712+
L0:
3713+
r0 = load_address _Py_NoneStruct
3714+
r1 = instance == r0
3715+
if r1 goto L1 else goto L2 :: bool
3716+
L1:
3717+
return __mypyc_self__
3718+
L2:
3719+
r2 = PyMethod_New(__mypyc_self__, instance)
3720+
return r2
3721+
def wrapper_deco_obj.__call__(__mypyc_self__, args):
3722+
__mypyc_self__ :: __main__.wrapper_deco_obj
3723+
args :: object
3724+
r0 :: __main__.deco_env
3725+
r1 :: object
3726+
r2 :: tuple
3727+
r3 :: dict
3728+
r4 :: object
3729+
L0:
3730+
r0 = __mypyc_self__.__mypyc_env__
3731+
r1 = r0.fn
3732+
r2 = PySequence_Tuple(args)
3733+
r3 = PyDict_New()
3734+
r4 = PyObject_Call(r1, r2, r3)
3735+
return r4
3736+
def deco(fn):
3737+
fn :: object
3738+
r0 :: __main__.deco_env
3739+
r1 :: bool
3740+
r2 :: __main__.wrapper_deco_obj
3741+
r3 :: bool
3742+
wrapper :: object
3743+
L0:
3744+
r0 = deco_env()
3745+
r0.fn = fn; r1 = is_error
3746+
r2 = wrapper_deco_obj()
3747+
r2.__mypyc_env__ = r0; r3 = is_error
3748+
wrapper = r2
3749+
return wrapper
3750+
3751+
[case testStarArgFastPathSequenceWithKwargs]
3752+
from typing import Any, Callable
3753+
def deco(fn: Callable[[Any], Any]) -> Callable[[Any], Any]:
3754+
def wrapper(args: Any, **kwargs: Any) -> Any:
3755+
return fn(*args, **kwargs)
3756+
return wrapper
3757+
3758+
[out]
3759+
def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner):
3760+
__mypyc_self__, instance, owner, r0 :: object
3761+
r1 :: bit
3762+
r2 :: object
3763+
L0:
3764+
r0 = load_address _Py_NoneStruct
3765+
r1 = instance == r0
3766+
if r1 goto L1 else goto L2 :: bool
3767+
L1:
3768+
return __mypyc_self__
3769+
L2:
3770+
r2 = PyMethod_New(__mypyc_self__, instance)
3771+
return r2
3772+
def wrapper_deco_obj.__call__(__mypyc_self__, args, kwargs):
3773+
__mypyc_self__ :: __main__.wrapper_deco_obj
3774+
args :: object
3775+
kwargs :: dict
3776+
r0 :: __main__.deco_env
3777+
r1 :: object
3778+
r2 :: tuple
3779+
r3 :: dict
3780+
r4 :: i32
3781+
r5 :: bit
3782+
r6 :: object
3783+
L0:
3784+
r0 = __mypyc_self__.__mypyc_env__
3785+
r1 = r0.fn
3786+
r2 = PySequence_Tuple(args)
3787+
r3 = PyDict_New()
3788+
r4 = CPyDict_UpdateInDisplay(r3, kwargs)
3789+
r5 = r4 >= 0 :: signed
3790+
r6 = PyObject_Call(r1, r2, r3)
3791+
return r6
3792+
def deco(fn):
3793+
fn :: object
3794+
r0 :: __main__.deco_env
3795+
r1 :: bool
3796+
r2 :: __main__.wrapper_deco_obj
3797+
r3 :: bool
3798+
wrapper :: object
3799+
L0:
3800+
r0 = deco_env()
3801+
r0.fn = fn; r1 = is_error
3802+
r2 = wrapper_deco_obj()
3803+
r2.__mypyc_env__ = r0; r3 = is_error
3804+
wrapper = r2
3805+
return wrapper

0 commit comments

Comments
 (0)