Skip to content

Commit bcef9f4

Browse files
[mypyc] feat: support RTuple in sequence_from_generator_preallocate_helper (#19931)
This PR extends `sequence_from_generator_preallocate_helper` to work with RTuple types. This is ready for review.
1 parent a12565c commit bcef9f4

File tree

2 files changed

+120
-74
lines changed

2 files changed

+120
-74
lines changed

mypyc/irbuild/for_helpers.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
TypeAlias,
2929
Var,
3030
)
31+
from mypy.types import LiteralType, TupleType, get_proper_type, get_proper_types
3132
from mypyc.ir.ops import (
3233
ERR_NEVER,
3334
BasicBlock,
@@ -36,6 +37,7 @@
3637
IntOp,
3738
LoadAddress,
3839
LoadErrorValue,
40+
LoadLiteral,
3941
LoadMem,
4042
MethodCall,
4143
RaiseStandardError,
@@ -170,7 +172,7 @@ def for_loop_helper_with_index(
170172
body_insts: a function that generates the body of the loop.
171173
It needs a index as parameter.
172174
"""
173-
assert is_sequence_rprimitive(expr_reg.type)
175+
assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type)
174176
target_type = builder.get_sequence_type(expr)
175177

176178
body_block = BasicBlock()
@@ -217,10 +219,9 @@ def sequence_from_generator_preallocate_helper(
217219
there is no condition list in the generator and only one original sequence with
218220
one index is allowed.
219221
220-
e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes)
221-
(2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes)
222-
(3) [f(x) for x in a_list/a_tuple/a_str/a_bytes]
223-
RTuple as an original sequence is not supported yet.
222+
e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes/an_rtuple)
223+
(2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes/an_rtuple)
224+
(3) [f(x) for x in a_list/a_tuple/a_str/a_bytes/an_rtuple]
224225
225226
Args:
226227
empty_op_llbuilder: A function that can generate an empty sequence op when
@@ -235,23 +236,41 @@ def sequence_from_generator_preallocate_helper(
235236
implementation.
236237
"""
237238
if len(gen.sequences) == 1 and len(gen.indices) == 1 and len(gen.condlists[0]) == 0:
238-
rtype = builder.node_type(gen.sequences[0])
239-
if is_sequence_rprimitive(rtype):
240-
sequence = builder.accept(gen.sequences[0])
241-
length = get_expr_length_value(
242-
builder, gen.sequences[0], sequence, gen.line, use_pyssize_t=True
243-
)
244-
target_op = empty_op_llbuilder(length, gen.line)
245-
246-
def set_item(item_index: Value) -> None:
247-
e = builder.accept(gen.left_expr)
248-
builder.call_c(set_item_op, [target_op, item_index, e], gen.line)
249-
250-
for_loop_helper_with_index(
251-
builder, gen.indices[0], gen.sequences[0], sequence, set_item, gen.line, length
252-
)
239+
line = gen.line
240+
sequence_expr = gen.sequences[0]
241+
rtype = builder.node_type(sequence_expr)
242+
if not (is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple)):
243+
return None
244+
sequence = builder.accept(sequence_expr)
245+
length = get_expr_length_value(builder, sequence_expr, sequence, line, use_pyssize_t=True)
246+
if isinstance(rtype, RTuple):
247+
# If input is RTuple, box it to tuple_rprimitive for generic iteration
248+
# TODO: this can be optimized a bit better with an unrolled ForRTuple helper
249+
proper_type = get_proper_type(builder.types[sequence_expr])
250+
assert isinstance(proper_type, TupleType), proper_type
251+
252+
get_item_ops = [
253+
(
254+
LoadLiteral(typ.value, object_rprimitive)
255+
if isinstance(typ, LiteralType)
256+
else TupleGet(sequence, i, line)
257+
)
258+
for i, typ in enumerate(get_proper_types(proper_type.items))
259+
]
260+
items = list(map(builder.add, get_item_ops))
261+
sequence = builder.new_tuple(items, line)
262+
263+
target_op = empty_op_llbuilder(length, line)
264+
265+
def set_item(item_index: Value) -> None:
266+
e = builder.accept(gen.left_expr)
267+
builder.call_c(set_item_op, [target_op, item_index, e], line)
268+
269+
for_loop_helper_with_index(
270+
builder, gen.indices[0], sequence_expr, sequence, set_item, line, length
271+
)
253272

254-
return target_op
273+
return target_op
255274
return None
256275

257276

@@ -804,7 +823,7 @@ class ForSequence(ForGenerator):
804823
def init(
805824
self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None
806825
) -> None:
807-
assert is_sequence_rprimitive(expr_reg.type), expr_reg
826+
assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type)
808827
builder = self.builder
809828
# Record a Value indicating the length of the sequence, if known at compile time.
810829
self.length = length
@@ -834,7 +853,6 @@ def init(
834853
def gen_condition(self) -> None:
835854
builder = self.builder
836855
line = self.line
837-
# TODO: Don't reload the length each time when iterating an immutable sequence?
838856
if self.reverse:
839857
# If we are iterating in reverse order, we obviously need
840858
# to check that the index is still positive. Somewhat less
@@ -1216,7 +1234,7 @@ def get_expr_length_value(
12161234
builder: IRBuilder, expr: Expression, expr_reg: Value, line: int, use_pyssize_t: bool
12171235
) -> Value:
12181236
rtype = builder.node_type(expr)
1219-
assert is_sequence_rprimitive(rtype), rtype
1237+
assert is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple), rtype
12201238
length = get_expr_length(expr)
12211239
if length is None:
12221240
# We cannot compute the length at compile time, so we will fetch it.

mypyc/test-data/irbuild-tuple.test

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -694,36 +694,50 @@ L0:
694694
return r1
695695
def test():
696696
r0, source :: tuple[int, int, int]
697-
r1 :: list
698-
r2, r3, r4 :: object
699-
r5, x :: int
700-
r6 :: bool
701-
r7 :: object
702-
r8 :: i32
703-
r9, r10 :: bit
704-
r11, a :: tuple
697+
r1 :: object
698+
r2 :: native_int
699+
r3 :: bit
700+
r4, r5, r6 :: int
701+
r7, r8, r9 :: object
702+
r10, r11 :: tuple
703+
r12 :: native_int
704+
r13 :: bit
705+
r14 :: object
706+
r15, x :: int
707+
r16 :: bool
708+
r17 :: object
709+
r18 :: native_int
710+
a :: tuple
705711
L0:
706712
r0 = (2, 4, 6)
707713
source = r0
708-
r1 = PyList_New(0)
709-
r2 = box(tuple[int, int, int], source)
710-
r3 = PyObject_GetIter(r2)
714+
r1 = box(tuple[int, int, int], source)
715+
r2 = PyObject_Size(r1)
716+
r3 = r2 >= 0 :: signed
717+
r4 = source[0]
718+
r5 = source[1]
719+
r6 = source[2]
720+
r7 = box(int, r4)
721+
r8 = box(int, r5)
722+
r9 = box(int, r6)
723+
r10 = PyTuple_Pack(3, r7, r8, r9)
724+
r11 = PyTuple_New(r2)
725+
r12 = 0
711726
L1:
712-
r4 = PyIter_Next(r3)
713-
if is_error(r4) goto L4 else goto L2
727+
r13 = r12 < r2 :: signed
728+
if r13 goto L2 else goto L4 :: bool
714729
L2:
715-
r5 = unbox(int, r4)
716-
x = r5
717-
r6 = f(x)
718-
r7 = box(bool, r6)
719-
r8 = PyList_Append(r1, r7)
720-
r9 = r8 >= 0 :: signed
730+
r14 = CPySequenceTuple_GetItemUnsafe(r10, r12)
731+
r15 = unbox(int, r14)
732+
x = r15
733+
r16 = f(x)
734+
r17 = box(bool, r16)
735+
CPySequenceTuple_SetItemUnsafe(r11, r12, r17)
721736
L3:
737+
r18 = r12 + 1
738+
r12 = r18
722739
goto L1
723740
L4:
724-
r10 = CPy_NoErrOccurred()
725-
L5:
726-
r11 = PyList_AsTuple(r1)
727741
a = r11
728742
return 1
729743

@@ -746,42 +760,56 @@ L0:
746760
r1 = int_eq r0, 0
747761
return r1
748762
def test():
749-
r0 :: list
750-
r1 :: tuple[int, int, int]
751-
r2 :: bool
752-
r3, r4, r5 :: object
753-
r6, x :: int
754-
r7 :: bool
755-
r8 :: object
756-
r9 :: i32
757-
r10, r11 :: bit
758-
r12, a :: tuple
759-
L0:
760-
r0 = PyList_New(0)
761-
r1 = __main__.source :: static
762-
if is_error(r1) goto L1 else goto L2
763+
r0 :: tuple[int, int, int]
764+
r1 :: bool
765+
r2 :: object
766+
r3 :: native_int
767+
r4 :: bit
768+
r5, r6, r7 :: int
769+
r8, r9, r10 :: object
770+
r11, r12 :: tuple
771+
r13 :: native_int
772+
r14 :: bit
773+
r15 :: object
774+
r16, x :: int
775+
r17 :: bool
776+
r18 :: object
777+
r19 :: native_int
778+
a :: tuple
779+
L0:
780+
r0 = __main__.source :: static
781+
if is_error(r0) goto L1 else goto L2
763782
L1:
764-
r2 = raise NameError('value for final name "source" was not set')
783+
r1 = raise NameError('value for final name "source" was not set')
765784
unreachable
766785
L2:
767-
r3 = box(tuple[int, int, int], r1)
768-
r4 = PyObject_GetIter(r3)
786+
r2 = box(tuple[int, int, int], r0)
787+
r3 = PyObject_Size(r2)
788+
r4 = r3 >= 0 :: signed
789+
r5 = r0[0]
790+
r6 = r0[1]
791+
r7 = r0[2]
792+
r8 = box(int, r5)
793+
r9 = box(int, r6)
794+
r10 = box(int, r7)
795+
r11 = PyTuple_Pack(3, r8, r9, r10)
796+
r12 = PyTuple_New(r3)
797+
r13 = 0
769798
L3:
770-
r5 = PyIter_Next(r4)
771-
if is_error(r5) goto L6 else goto L4
799+
r14 = r13 < r3 :: signed
800+
if r14 goto L4 else goto L6 :: bool
772801
L4:
773-
r6 = unbox(int, r5)
774-
x = r6
775-
r7 = f(x)
776-
r8 = box(bool, r7)
777-
r9 = PyList_Append(r0, r8)
778-
r10 = r9 >= 0 :: signed
802+
r15 = CPySequenceTuple_GetItemUnsafe(r11, r13)
803+
r16 = unbox(int, r15)
804+
x = r16
805+
r17 = f(x)
806+
r18 = box(bool, r17)
807+
CPySequenceTuple_SetItemUnsafe(r12, r13, r18)
779808
L5:
809+
r19 = r13 + 1
810+
r13 = r19
780811
goto L3
781812
L6:
782-
r11 = CPy_NoErrOccurred()
783-
L7:
784-
r12 = PyList_AsTuple(r0)
785813
a = r12
786814
return 1
787815

0 commit comments

Comments
 (0)