From 9fc66f8b78a34d8839d6c1bd1a89dc10878b117e Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 04:01:02 +0000 Subject: [PATCH 01/17] [mypyc] feat: reuse existing tuple when calling fn(*args) --- mypyc/irbuild/ll_builder.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index a5e28268efed..74e0ce4e0818 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -788,7 +788,17 @@ def _construct_varargs( for value, kind, name in args: if kind == ARG_STAR: if star_result is None: - star_result = self.new_list_op(star_values, line) + # 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) and ( + len(args) == 1 or (len(args) == 2 and args[1][1] == ARG_STAR2) + ): # only matches fn(*args) and fn(*args, **kwargs) + # TODO extend this to optimize fn(*args, k=1, **kwargs) case + star_result = value + continue + else: + # 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: if star2_result is None: @@ -885,9 +895,13 @@ def _construct_varargs( # tuple. Otherwise create the tuple from the list. if star_result is None: star_result = self.new_tuple(star_values, line) + elif is_tuple_rprimitive(star_result.type): + # We're just passing in the tuple + pass else: 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 From 4db8e938331e78f671fe7f12247f35b8fe3e44e1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 04:19:38 +0000 Subject: [PATCH 02/17] chore: cleanup --- mypyc/irbuild/ll_builder.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 74e0ce4e0818..49fc82d28fc8 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -790,12 +790,15 @@ 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) and ( - len(args) == 1 or (len(args) == 2 and args[1][1] == ARG_STAR2) - ): # only matches fn(*args) and fn(*args, **kwargs) + if is_tuple_rprimitive(value.type): + if len(args) == 1: + # fn(*args) + return value, None + elif len(args) == 2 and args[1][1] == ARG_STAR2: + # fn(*args, **kwargs) + star_result = value + continue # TODO extend this to optimize fn(*args, k=1, **kwargs) case - star_result = value - continue else: # TODO optimize this case using the length utils - currently in review star_result = self.new_list_op(star_values, line) From d7094257dae29948ffe8983f09d51acac72af5d7 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 04:22:22 +0000 Subject: [PATCH 03/17] chore: update IR --- mypyc/test-data/irbuild-generics.test | 58 +++++++++++---------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index d39d47e397a1..a4f427bf83c0 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -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: @@ -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 @@ -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 @@ -795,3 +782,4 @@ def f(x): x :: int L0: return x + From 301080ac6f0190d3193df10d2185c57293d1f81e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 04:23:45 +0000 Subject: [PATCH 04/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/test-data/irbuild-generics.test | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index a4f427bf83c0..03032a7746c0 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -782,4 +782,3 @@ def f(x): x :: int L0: return x - From e028a7283107db3908a75be5b034941d1e7d22d5 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 04:29:51 +0000 Subject: [PATCH 05/17] fix: mypy errs --- mypyc/irbuild/ll_builder.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 49fc82d28fc8..070ae6825cd8 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -798,10 +798,9 @@ def _construct_varargs( # fn(*args, **kwargs) star_result = value continue - # TODO extend this to optimize fn(*args, k=1, **kwargs) case - else: - # TODO optimize this case using the length utils - currently in review - star_result = self.new_list_op(star_values, line) + # 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: if star2_result is None: From 9057461ed631388f3f07215e54b1698e59cc1ee1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 04:38:54 +0000 Subject: [PATCH 06/17] fix: mypy errs --- mypyc/irbuild/ll_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 070ae6825cd8..3204bedbc9eb 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -793,7 +793,7 @@ def _construct_varargs( if is_tuple_rprimitive(value.type): if len(args) == 1: # fn(*args) - return value, None + return value, self._create_dict([], [], line) elif len(args) == 2 and args[1][1] == ARG_STAR2: # fn(*args, **kwargs) star_result = value From d3b3f0d74826f55eb088255ec46bc1b6c499d68b Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 04:59:30 +0000 Subject: [PATCH 07/17] chore: refactor --- mypyc/irbuild/ll_builder.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 3204bedbc9eb..a4483c5c4ce9 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -897,10 +897,8 @@ def _construct_varargs( # tuple. Otherwise create the tuple from the list. if star_result is None: star_result = self.new_tuple(star_values, line) - elif is_tuple_rprimitive(star_result.type): - # We're just passing in the tuple - pass - 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 From 27a13a72d94fa9a59ca3c57bba4eca7123e11376 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 14:47:57 +0000 Subject: [PATCH 08/17] feat: extend stararg fastpath logic to handle lists and generic sequences --- mypyc/irbuild/ll_builder.py | 24 ++- mypyc/primitives/tuple_ops.py | 2 +- mypyc/test-data/irbuild-basic.test | 264 +++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+), 9 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index a4483c5c4ce9..14978e7738e5 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -183,7 +183,7 @@ 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 @@ -790,14 +790,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.call_c(list_tuple_op, [value], line) + elif not is_tuple_rprimitive(value.type): + value = self.call_c(sequence_tuple_op, [value], line) + return value, self._create_dict([], [], line) + 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.call_c(list_tuple_op, [value], line) + else: + star_result = self.call_c(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) diff --git a/mypyc/primitives/tuple_ops.py b/mypyc/primitives/tuple_ops.py index d95161acf853..f262dec8b05a 100644 --- a/mypyc/primitives/tuple_ops.py +++ b/mypyc/primitives/tuple_ops.py @@ -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, diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 4a7d315ec836..77ad9d7ef1d3 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3546,3 +3546,267 @@ L0: r2 = PyObject_Vectorcall(r1, 0, 0, 0) r3 = box(None, 1) return r3 + +[case testStarArgFastPathTuple] +from typing import Any, Callable +def deco(fn: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(*args: Any) -> Any: + return fn(*args) + return wrapper + +[out] +def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +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 +L0: + r0 = __mypyc_self__.__mypyc_env__ + r1 = r0.fn + r2 = PyDict_New() + r3 = PyObject_Call(r1, args, r2) + return r3 +def deco(fn): + fn :: object + r0 :: __main__.deco_env + r1 :: bool + r2 :: __main__.wrapper_deco_obj + r3 :: bool + wrapper :: object +L0: + r0 = deco_env() + r0.fn = fn; r1 = is_error + r2 = wrapper_deco_obj() + r2.__mypyc_env__ = r0; r3 = is_error + wrapper = r2 + return wrapper + +[case testStarArgFastPathList] +from typing import Any, Callable, List +def deco(fn: Callable[..., Any]) -> Callable[[List[Any]], Any]: + def wrapper(args: List[Any]) -> Any: + return fn(*args) + return wrapper + +[out] +def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +def wrapper_deco_obj.__call__(__mypyc_self__, args): + __mypyc_self__ :: __main__.wrapper_deco_obj + args :: list + r0 :: __main__.deco_env + r1 :: object + r2 :: tuple + r3 :: dict + r4 :: 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 +def deco(fn): + fn :: object + r0 :: __main__.deco_env + r1 :: bool + r2 :: __main__.wrapper_deco_obj + r3 :: bool + wrapper :: object +L0: + r0 = deco_env() + r0.fn = fn; r1 = is_error + r2 = wrapper_deco_obj() + r2.__mypyc_env__ = r0; r3 = is_error + wrapper = r2 + return wrapper + +[case testStarArgFastPathListWithKwargs] +from typing import Any, Callable, Dict, List +def deco(fn: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(lst: List[Any], kwargs: Dict[str, Any]) -> Any: + return fn(*lst, **kwargs) + return wrapper + +[out] +def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +def wrapper_deco_obj.__call__(__mypyc_self__, lst, kwargs): + __mypyc_self__ :: __main__.wrapper_deco_obj + lst :: list + kwargs :: dict + r0 :: __main__.deco_env + r1 :: object + r2 :: tuple + r3 :: dict + r4 :: i32 + r5 :: bit + r6 :: object +L0: + r0 = __mypyc_self__.__mypyc_env__ + r1 = r0.fn + r2 = PyList_AsTuple(lst) + r3 = PyDict_New() + r4 = CPyDict_UpdateInDisplay(r3, kwargs) + r5 = r4 >= 0 :: signed + r6 = PyObject_Call(r1, r2, r3) + return r6 +def deco(fn): + fn :: object + r0 :: __main__.deco_env + r1 :: bool + r2 :: __main__.wrapper_deco_obj + r3 :: bool + wrapper :: object +L0: + r0 = deco_env() + r0.fn = fn; r1 = is_error + r2 = wrapper_deco_obj() + r2.__mypyc_env__ = r0; r3 = is_error + wrapper = r2 + return wrapper + +[case testStarArgFastPathSequence] +from typing import Any, Callable +def deco(fn: Callable[[Any], Any]) -> Callable[[Any], Any]: + def wrapper(args: Any) -> Any: + return fn(*args) + return wrapper + +[out] +def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +def wrapper_deco_obj.__call__(__mypyc_self__, args): + __mypyc_self__ :: __main__.wrapper_deco_obj + args :: object + r0 :: __main__.deco_env + r1 :: object + r2 :: tuple + r3 :: dict + r4 :: 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 +def deco(fn): + fn :: object + r0 :: __main__.deco_env + r1 :: bool + r2 :: __main__.wrapper_deco_obj + r3 :: bool + wrapper :: object +L0: + r0 = deco_env() + r0.fn = fn; r1 = is_error + r2 = wrapper_deco_obj() + r2.__mypyc_env__ = r0; r3 = is_error + wrapper = r2 + return wrapper + +[case testStarArgFastPathSequenceWithKwargs] +from typing import Any, Callable +def deco(fn: Callable[[Any], Any]) -> Callable[[Any], Any]: + def wrapper(args: Any, **kwargs: Any) -> Any: + return fn(*args, **kwargs) + return wrapper + +[out] +def wrapper_deco_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +def wrapper_deco_obj.__call__(__mypyc_self__, args, kwargs): + __mypyc_self__ :: __main__.wrapper_deco_obj + args :: object + kwargs :: dict + r0 :: __main__.deco_env + r1 :: object + r2 :: tuple + r3 :: dict + r4 :: i32 + r5 :: bit + r6 :: object +L0: + r0 = __mypyc_self__.__mypyc_env__ + r1 = r0.fn + r2 = PySequence_Tuple(args) + r3 = PyDict_New() + r4 = CPyDict_UpdateInDisplay(r3, kwargs) + r5 = r4 >= 0 :: signed + r6 = PyObject_Call(r1, r2, r3) + return r6 +def deco(fn): + fn :: object + r0 :: __main__.deco_env + r1 :: bool + r2 :: __main__.wrapper_deco_obj + r3 :: bool + wrapper :: object +L0: + r0 = deco_env() + r0.fn = fn; r1 = is_error + r2 = wrapper_deco_obj() + r2.__mypyc_env__ = r0; r3 = is_error + wrapper = r2 + return wrapper + From 3e72105e0b6a27d4d83b29ee8e8485f9a231614e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 14:52:28 +0000 Subject: [PATCH 09/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 7 ++++++- mypyc/test-data/irbuild-basic.test | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 14978e7738e5..933712c2ddbf 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -183,7 +183,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, sequence_tuple_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 diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 77ad9d7ef1d3..9f043ccb208e 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3809,4 +3809,3 @@ L0: r2.__mypyc_env__ = r0; r3 = is_error wrapper = r2 return wrapper - From 3da4bf2164aead85d0f5605810692c9dfe02d44f Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 15:04:05 +0000 Subject: [PATCH 10/17] update IR --- mypyc/test-data/irbuild-basic.test | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 77ad9d7ef1d3..b2ff16a406b7 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1674,26 +1674,22 @@ def g(): r0 :: tuple[int, int, int] r1 :: dict r2 :: str - r3 :: object - r4 :: list - r5, r6 :: object - r7 :: tuple - r8 :: dict - r9 :: object - r10 :: tuple[int, int, int] + r3, r4 :: object + r5 :: tuple + r6 :: dict + r7 :: object + r8 :: tuple[int, int, int] L0: r0 = (2, 4, 6) r1 = __main__.globals :: static r2 = 'f' r3 = CPyDict_GetItem(r1, r2) - r4 = PyList_New(0) - r5 = box(tuple[int, int, int], r0) - r6 = CPyList_Extend(r4, r5) - r7 = PyList_AsTuple(r4) - r8 = PyDict_New() - r9 = PyObject_Call(r3, r7, r8) - r10 = unbox(tuple[int, int, int], r9) - return r10 + r4 = box(tuple[int, int, int], r0) + r5 = PySequence_Tuple(r4) + r6 = PyDict_New() + r7 = PyObject_Call(r3, r5, r6) + r8 = unbox(tuple[int, int, int], r7) + return r8 def h(): r0 :: tuple[int, int] r1 :: dict From 2f0e0fe6e2371152fc1f7a4ab619874c876c397e Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 15:09:12 +0000 Subject: [PATCH 11/17] fix mypy errs --- mypyc/irbuild/ll_builder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 933712c2ddbf..2177349058cd 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -798,18 +798,18 @@ def _construct_varargs( if len(args) == 1: # fn(*args) if is_list_rprimitive(value.type): - value = self.call_c(list_tuple_op, [value], line) + value = self.primitive_op(list_tuple_op, [value], line) elif not is_tuple_rprimitive(value.type): - value = self.call_c(sequence_tuple_op, [value], line) + value = self.primitive_op(sequence_tuple_op, [value], line) return value, self._create_dict([], [], line) 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.call_c(list_tuple_op, [value], line) + star_result = self.primitive_op(list_tuple_op, [value], line) else: - star_result = self.call_c(sequence_tuple_op, [value], line) + 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 From 82e39c4157ba50681c341edd6228b0e7d25c3b17 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 15:15:30 +0000 Subject: [PATCH 12/17] [mypyc] feat: extend stararg fastpath from #19629 with star2 fastpath --- mypyc/irbuild/ll_builder.py | 9 ++++++++ mypyc/primitives/dict_ops.py | 2 +- mypyc/test-data/irbuild-generics.test | 32 ++++++++++----------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 2177349058cd..6c3055651632 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -127,6 +127,7 @@ from mypyc.primitives.bytes_ops import bytes_compare from mypyc.primitives.dict_ops import ( dict_build_op, + dict_copy_op, dict_new_op, dict_ssize_t_size_op, dict_update_in_display_op, @@ -810,6 +811,9 @@ def _construct_varargs( star_result = self.primitive_op(list_tuple_op, [value], line) else: star_result = self.primitive_op(sequence_tuple_op, [value], line) + if is_dict_rprimitive(args[1][0].type): + # even faster fastpath for decorators + return star_result, self.primitive_op(dict_copy_op, [args[1][0]], 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 @@ -817,6 +821,11 @@ def _construct_varargs( self.primitive_op(list_extend_op, [star_result, value], line) elif kind == ARG_STAR2: if star2_result is None: + if len(args) == 1: + # early exit with fastpath if the only arg is ARG_STAR2 + # TODO: can we maintain an empty tuple in memory and just reuse it again and again? + return self.new_tuple([], line), self.primitive_op(dict_copy_op, [args[0][0]], line) + star2_result = self._create_dict(star2_keys, star2_values, line) self.call_c(dict_update_in_display_op, [star2_result, value], line=line) diff --git a/mypyc/primitives/dict_ops.py b/mypyc/primitives/dict_ops.py index ac928bb0eb50..b74e9de57623 100644 --- a/mypyc/primitives/dict_ops.py +++ b/mypyc/primitives/dict_ops.py @@ -53,7 +53,7 @@ ) # Construct a dictionary from another dictionary. -function_op( +dict_copy_op = function_op( name="builtins.dict", arg_types=[dict_rprimitive], return_type=dict_rprimitive, diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 03032a7746c0..791180d0664f 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -167,17 +167,13 @@ def execute(func, args, kwargs): func :: object args :: tuple kwargs, r0 :: dict - r1 :: i32 - r2 :: bit - r3 :: object - r4 :: int + r1 :: object + r2 :: int L0: - r0 = PyDict_New() - r1 = CPyDict_UpdateInDisplay(r0, kwargs) - r2 = r1 >= 0 :: signed - r3 = PyObject_Call(func, args, r0) - r4 = unbox(int, r3) - return r4 + r0 = PyDict_Copy(kwargs) + r1 = PyObject_Call(func, args, r0) + r2 = unbox(int, r1) + return r2 def f(x): x :: int L0: @@ -703,10 +699,8 @@ def inner_deco_obj.__call__(__mypyc_self__, args, kwargs): r22, can_iter, r23, can_use_keys, r24, can_use_values :: list r25 :: object r26 :: dict - r27 :: i32 - r28 :: bit - r29 :: object - r30 :: int + r27 :: object + r28 :: int L0: r0 = __mypyc_self__.__mypyc_env__ r1 = var_object_size args @@ -758,12 +752,10 @@ L9: r24 = CPyDict_Values(kwargs) can_use_values = r24 r25 = r0.func - r26 = PyDict_New() - r27 = CPyDict_UpdateInDisplay(r26, kwargs) - r28 = r27 >= 0 :: signed - r29 = PyObject_Call(r25, args, r26) - r30 = unbox(int, r29) - return r30 + r26 = PyDict_Copy(kwargs) + r27 = PyObject_Call(r25, args, r26) + r28 = unbox(int, r27) + return r28 def deco(func): func :: object r0 :: __main__.deco_env From aed4a8b175f4f3c6e51d269583b187497a361c62 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 15:19:16 +0000 Subject: [PATCH 13/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 6c3055651632..db006f32ff67 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -824,7 +824,9 @@ def _construct_varargs( if len(args) == 1: # early exit with fastpath if the only arg is ARG_STAR2 # TODO: can we maintain an empty tuple in memory and just reuse it again and again? - return self.new_tuple([], line), self.primitive_op(dict_copy_op, [args[0][0]], line) + return self.new_tuple([], line), self.primitive_op( + dict_copy_op, [args[0][0]], line + ) star2_result = self._create_dict(star2_keys, star2_values, line) From 2be03267eab395d9aaabba4ff4d48815e8f2a162 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sat, 9 Aug 2025 15:35:25 +0000 Subject: [PATCH 14/17] fix: separate fastpath for dict vs nondict --- mypyc/irbuild/ll_builder.py | 20 +++++++++---- mypyc/test-data/irbuild-basic.test | 47 ++++++++++++------------------ 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 6c3055651632..ce216a264e0b 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -127,6 +127,7 @@ from mypyc.primitives.bytes_ops import bytes_compare from mypyc.primitives.dict_ops import ( dict_build_op, + dict_copy, dict_copy_op, dict_new_op, dict_ssize_t_size_op, @@ -805,16 +806,21 @@ def _construct_varargs( return value, self._create_dict([], [], line) elif len(args) == 2 and args[1][1] == ARG_STAR2: # fn(*args, **kwargs) + # TODO: extend to cover(*args, **k, **w, **a, **r, **g, **s) 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) - if is_dict_rprimitive(args[1][0].type): - # even faster fastpath for decorators - return star_result, self.primitive_op(dict_copy_op, [args[1][0]], line) - continue + + star2_arg = args[1] + star2_value = star2_arg[0] + if is_dict_rprimitive(star2_value.type): + star2_fastpath_op = dict_copy_op + else: + star2_fastpath_op = dict_copy + return star_result, self.primitive_op(star2_fastpath_op, [star2_value], line) # 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) @@ -824,7 +830,11 @@ def _construct_varargs( if len(args) == 1: # early exit with fastpath if the only arg is ARG_STAR2 # TODO: can we maintain an empty tuple in memory and just reuse it again and again? - return self.new_tuple([], line), self.primitive_op(dict_copy_op, [args[0][0]], line) + if is_dict_rprimitive(value.type): + star2_fastpath_op = dict_copy_op + else: + star2_fastpath_op = dict_copy + return self.new_tuple([], line), self.primitive_op(star2_fastpath_op, [args[0][0]], line) star2_result = self._create_dict(star2_keys, star2_values, line) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 365ec8ce9e7d..f54eeb967a4e 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1742,12 +1742,10 @@ def g(): r6, r7 :: dict r8 :: str r9 :: object - r10 :: dict - r11 :: i32 - r12 :: bit - r13 :: tuple - r14 :: object - r15 :: tuple[int, int, int] + r10 :: tuple + r11 :: dict + r12 :: object + r13 :: tuple[int, int, int] L0: r0 = 'a' r1 = 'b' @@ -1759,13 +1757,11 @@ L0: r7 = __main__.globals :: static r8 = 'f' r9 = CPyDict_GetItem(r7, r8) - r10 = PyDict_New() - r11 = CPyDict_UpdateInDisplay(r10, r6) - r12 = r11 >= 0 :: signed - r13 = PyTuple_Pack(0) - r14 = PyObject_Call(r9, r13, r10) - r15 = unbox(tuple[int, int, int], r14) - return r15 + r10 = PyTuple_Pack(0) + r11 = PyDict_Copy(r6) + r12 = PyObject_Call(r9, r10, r11) + r13 = unbox(tuple[int, int, int], r12) + return r13 def h(): r0, r1 :: str r2, r3 :: object @@ -3672,18 +3668,14 @@ def wrapper_deco_obj.__call__(__mypyc_self__, lst, kwargs): r1 :: object r2 :: tuple r3 :: dict - r4 :: i32 - r5 :: bit - r6 :: object + r4 :: object L0: r0 = __mypyc_self__.__mypyc_env__ r1 = r0.fn r2 = PyList_AsTuple(lst) - r3 = PyDict_New() - r4 = CPyDict_UpdateInDisplay(r3, kwargs) - r5 = r4 >= 0 :: signed - r6 = PyObject_Call(r1, r2, r3) - return r6 + r3 = PyDict_Copy(kwargs) + r4 = PyObject_Call(r1, r2, r3) + return r4 def deco(fn): fn :: object r0 :: __main__.deco_env @@ -3779,18 +3771,14 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args, kwargs): r1 :: object r2 :: tuple r3 :: dict - r4 :: i32 - r5 :: bit - r6 :: object + r4 :: object L0: r0 = __mypyc_self__.__mypyc_env__ r1 = r0.fn r2 = PySequence_Tuple(args) - r3 = PyDict_New() - r4 = CPyDict_UpdateInDisplay(r3, kwargs) - r5 = r4 >= 0 :: signed - r6 = PyObject_Call(r1, r2, r3) - return r6 + r3 = PyDict_Copy(kwargs) + r4 = PyObject_Call(r1, r2, r3) + return r4 def deco(fn): fn :: object r0 :: __main__.deco_env @@ -3805,3 +3793,4 @@ L0: r2.__mypyc_env__ = r0; r3 = is_error wrapper = r2 return wrapper + From 847ea629ac4d0a3e34f454451388573f619ae3d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 15:37:35 +0000 Subject: [PATCH 15/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 10 +++++++--- mypyc/test-data/irbuild-basic.test | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 39cf4d6b8105..510fa6820fb3 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -813,14 +813,16 @@ def _construct_varargs( star_result = self.primitive_op(list_tuple_op, [value], line) else: star_result = self.primitive_op(sequence_tuple_op, [value], line) - + star2_arg = args[1] star2_value = star2_arg[0] if is_dict_rprimitive(star2_value.type): star2_fastpath_op = dict_copy_op else: star2_fastpath_op = dict_copy - return star_result, self.primitive_op(star2_fastpath_op, [star2_value], line) + return star_result, self.primitive_op( + star2_fastpath_op, [star2_value], line + ) # 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) @@ -834,7 +836,9 @@ def _construct_varargs( star2_fastpath_op = dict_copy_op else: star2_fastpath_op = dict_copy - return self.new_tuple([], line), self.primitive_op(star2_fastpath_op, [value], line) + return self.new_tuple([], line), self.primitive_op( + star2_fastpath_op, [value], line + ) star2_result = self._create_dict(star2_keys, star2_values, line) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index f54eeb967a4e..8f9034dbe89c 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3793,4 +3793,3 @@ L0: r2.__mypyc_env__ = r0; r3 = is_error wrapper = r2 return wrapper - From f260adaf2c3bcad6460001e6fedcd7b49948d03b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 9 Aug 2025 11:39:10 -0400 Subject: [PATCH 16/17] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 510fa6820fb3..d9ed91efb0b0 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -937,7 +937,6 @@ def _construct_varargs( # 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 From f5f96df86b8936b254bb40aabe01b7085bfee69e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 9 Aug 2025 11:40:19 -0400 Subject: [PATCH 17/17] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index d9ed91efb0b0..914d1d0c2b37 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -933,8 +933,7 @@ def _construct_varargs( # tuple. Otherwise create the tuple from the list. if star_result is None: star_result = self.new_tuple(star_values, line) - elif not is_tuple_rprimitive(star_result.type): - # if star_result is a tuple we took the fast path + else: star_result = self.primitive_op(list_tuple_op, [star_result], line) if has_star2 and star2_result is None: star2_result = self._create_dict(star2_keys, star2_values, line)