|
1 |
| -"""Helpers for generating for loops. |
| 1 | +"""Helpers for generating for loops and comprehensions. |
2 | 2 |
|
3 | 3 | We special case certain kinds for loops such as "for x in range(...)"
|
4 | 4 | for better efficiency. Each for loop generator class below deals one
|
5 | 5 | such special case.
|
6 | 6 | """
|
7 | 7 |
|
8 |
| -from typing import Union, List |
9 |
| -from typing_extensions import TYPE_CHECKING |
| 8 | +from typing import Union, List, Optional, Tuple, Callable |
10 | 9 |
|
11 |
| -from mypy.nodes import Lvalue, Expression |
| 10 | +from mypy.nodes import Lvalue, Expression, TupleExpr, CallExpr, RefExpr, GeneratorExpr, ARG_POS |
12 | 11 | from mypyc.ir.ops import (
|
13 | 12 | Value, BasicBlock, LoadInt, Branch, Register, AssignmentTarget
|
14 | 13 | )
|
15 |
| -from mypyc.ir.rtypes import RType, is_short_int_rprimitive, is_list_rprimitive |
| 14 | +from mypyc.ir.rtypes import ( |
| 15 | + RType, is_short_int_rprimitive, is_list_rprimitive, is_sequence_rprimitive |
| 16 | +) |
16 | 17 | from mypyc.primitives.int_ops import unsafe_short_add
|
17 |
| -from mypyc.primitives.list_ops import list_get_item_unsafe_op |
| 18 | +from mypyc.primitives.list_ops import new_list_op, list_append_op, list_get_item_unsafe_op |
18 | 19 | from mypyc.primitives.misc_ops import iter_op, next_op
|
19 | 20 | from mypyc.primitives.exc_ops import no_err_occurred_op
|
20 |
| - |
21 |
| -if TYPE_CHECKING: |
22 |
| - import mypyc.irbuild.builder |
| 21 | +from mypyc.irbuild.builder import IRBuilder |
| 22 | + |
| 23 | + |
| 24 | +GenFunc = Callable[[], None] |
| 25 | + |
| 26 | + |
| 27 | +def for_loop_helper(builder: IRBuilder, index: Lvalue, expr: Expression, |
| 28 | + body_insts: GenFunc, else_insts: Optional[GenFunc], |
| 29 | + line: int) -> None: |
| 30 | + """Generate IR for a loop. |
| 31 | +
|
| 32 | + Args: |
| 33 | + index: the loop index Lvalue |
| 34 | + expr: the expression to iterate over |
| 35 | + body_insts: a function that generates the body of the loop |
| 36 | + else_insts: a function that generates the else block instructions |
| 37 | + """ |
| 38 | + # Body of the loop |
| 39 | + body_block = BasicBlock() |
| 40 | + # Block that steps to the next item |
| 41 | + step_block = BasicBlock() |
| 42 | + # Block for the else clause, if we need it |
| 43 | + else_block = BasicBlock() |
| 44 | + # Block executed after the loop |
| 45 | + exit_block = BasicBlock() |
| 46 | + |
| 47 | + # Determine where we want to exit, if our condition check fails. |
| 48 | + normal_loop_exit = else_block if else_insts is not None else exit_block |
| 49 | + |
| 50 | + for_gen = make_for_loop_generator(builder, index, expr, body_block, normal_loop_exit, line) |
| 51 | + |
| 52 | + builder.push_loop_stack(step_block, exit_block) |
| 53 | + condition_block = BasicBlock() |
| 54 | + builder.goto_and_activate(condition_block) |
| 55 | + |
| 56 | + # Add loop condition check. |
| 57 | + for_gen.gen_condition() |
| 58 | + |
| 59 | + # Generate loop body. |
| 60 | + builder.activate_block(body_block) |
| 61 | + for_gen.begin_body() |
| 62 | + body_insts() |
| 63 | + |
| 64 | + # We generate a separate step block (which might be empty). |
| 65 | + builder.goto_and_activate(step_block) |
| 66 | + for_gen.gen_step() |
| 67 | + # Go back to loop condition. |
| 68 | + builder.goto(condition_block) |
| 69 | + |
| 70 | + for_gen.add_cleanup(normal_loop_exit) |
| 71 | + builder.pop_loop_stack() |
| 72 | + |
| 73 | + if else_insts is not None: |
| 74 | + builder.activate_block(else_block) |
| 75 | + else_insts() |
| 76 | + builder.goto(exit_block) |
| 77 | + |
| 78 | + builder.activate_block(exit_block) |
| 79 | + |
| 80 | + |
| 81 | +def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value: |
| 82 | + list_ops = builder.primitive_op(new_list_op, [], gen.line) |
| 83 | + loop_params = list(zip(gen.indices, gen.sequences, gen.condlists)) |
| 84 | + |
| 85 | + def gen_inner_stmts() -> None: |
| 86 | + e = builder.accept(gen.left_expr) |
| 87 | + builder.primitive_op(list_append_op, [list_ops, e], gen.line) |
| 88 | + |
| 89 | + comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line) |
| 90 | + return list_ops |
| 91 | + |
| 92 | + |
| 93 | +def comprehension_helper(builder: IRBuilder, |
| 94 | + loop_params: List[Tuple[Lvalue, Expression, List[Expression]]], |
| 95 | + gen_inner_stmts: Callable[[], None], |
| 96 | + line: int) -> None: |
| 97 | + """Helper function for list comprehensions. |
| 98 | +
|
| 99 | + "loop_params" is a list of (index, expr, [conditions]) tuples defining nested loops: |
| 100 | + - "index" is the Lvalue indexing that loop; |
| 101 | + - "expr" is the expression for the object to be iterated over; |
| 102 | + - "conditions" is a list of conditions, evaluated in order with short-circuiting, |
| 103 | + that must all be true for the loop body to be executed |
| 104 | + "gen_inner_stmts" is a function to generate the IR for the body of the innermost loop |
| 105 | + """ |
| 106 | + def handle_loop(loop_params: List[Tuple[Lvalue, Expression, List[Expression]]]) -> None: |
| 107 | + """Generate IR for a loop. |
| 108 | +
|
| 109 | + Given a list of (index, expression, [conditions]) tuples, generate IR |
| 110 | + for the nested loops the list defines. |
| 111 | + """ |
| 112 | + index, expr, conds = loop_params[0] |
| 113 | + for_loop_helper(builder, index, expr, |
| 114 | + lambda: loop_contents(conds, loop_params[1:]), |
| 115 | + None, line) |
| 116 | + |
| 117 | + def loop_contents( |
| 118 | + conds: List[Expression], |
| 119 | + remaining_loop_params: List[Tuple[Lvalue, Expression, List[Expression]]], |
| 120 | + ) -> None: |
| 121 | + """Generate the body of the loop. |
| 122 | +
|
| 123 | + "conds" is a list of conditions to be evaluated (in order, with short circuiting) |
| 124 | + to gate the body of the loop. |
| 125 | + "remaining_loop_params" is the parameters for any further nested loops; if it's empty |
| 126 | + we'll instead evaluate the "gen_inner_stmts" function. |
| 127 | + """ |
| 128 | + # Check conditions, in order, short circuiting them. |
| 129 | + for cond in conds: |
| 130 | + cond_val = builder.accept(cond) |
| 131 | + cont_block, rest_block = BasicBlock(), BasicBlock() |
| 132 | + # If the condition is true we'll skip the continue. |
| 133 | + builder.add_bool_branch(cond_val, rest_block, cont_block) |
| 134 | + builder.activate_block(cont_block) |
| 135 | + builder.nonlocal_control[-1].gen_continue(builder, cond.line) |
| 136 | + builder.goto_and_activate(rest_block) |
| 137 | + |
| 138 | + if remaining_loop_params: |
| 139 | + # There's another nested level, so the body of this loop is another loop. |
| 140 | + return handle_loop(remaining_loop_params) |
| 141 | + else: |
| 142 | + # We finally reached the actual body of the generator. |
| 143 | + # Generate the IR for the inner loop body. |
| 144 | + gen_inner_stmts() |
| 145 | + |
| 146 | + handle_loop(loop_params) |
| 147 | + |
| 148 | + |
| 149 | +def make_for_loop_generator(builder: IRBuilder, |
| 150 | + index: Lvalue, |
| 151 | + expr: Expression, |
| 152 | + body_block: BasicBlock, |
| 153 | + loop_exit: BasicBlock, |
| 154 | + line: int, |
| 155 | + nested: bool = False) -> 'ForGenerator': |
| 156 | + """Return helper object for generating a for loop over an iterable. |
| 157 | +
|
| 158 | + If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)". |
| 159 | + """ |
| 160 | + |
| 161 | + rtyp = builder.node_type(expr) |
| 162 | + if is_sequence_rprimitive(rtyp): |
| 163 | + # Special case "for x in <list>". |
| 164 | + expr_reg = builder.accept(expr) |
| 165 | + target_type = builder.get_sequence_type(expr) |
| 166 | + |
| 167 | + for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) |
| 168 | + for_list.init(expr_reg, target_type, reverse=False) |
| 169 | + return for_list |
| 170 | + |
| 171 | + if (isinstance(expr, CallExpr) |
| 172 | + and isinstance(expr.callee, RefExpr)): |
| 173 | + if (expr.callee.fullname == 'builtins.range' |
| 174 | + and (len(expr.args) <= 2 |
| 175 | + or (len(expr.args) == 3 |
| 176 | + and builder.extract_int(expr.args[2]) is not None)) |
| 177 | + and set(expr.arg_kinds) == {ARG_POS}): |
| 178 | + # Special case "for x in range(...)". |
| 179 | + # We support the 3 arg form but only for int literals, since it doesn't |
| 180 | + # seem worth the hassle of supporting dynamically determining which |
| 181 | + # direction of comparison to do. |
| 182 | + if len(expr.args) == 1: |
| 183 | + start_reg = builder.add(LoadInt(0)) |
| 184 | + end_reg = builder.accept(expr.args[0]) |
| 185 | + else: |
| 186 | + start_reg = builder.accept(expr.args[0]) |
| 187 | + end_reg = builder.accept(expr.args[1]) |
| 188 | + if len(expr.args) == 3: |
| 189 | + step = builder.extract_int(expr.args[2]) |
| 190 | + assert step is not None |
| 191 | + if step == 0: |
| 192 | + builder.error("range() step can't be zero", expr.args[2].line) |
| 193 | + else: |
| 194 | + step = 1 |
| 195 | + |
| 196 | + for_range = ForRange(builder, index, body_block, loop_exit, line, nested) |
| 197 | + for_range.init(start_reg, end_reg, step) |
| 198 | + return for_range |
| 199 | + |
| 200 | + elif (expr.callee.fullname == 'builtins.enumerate' |
| 201 | + and len(expr.args) == 1 |
| 202 | + and expr.arg_kinds == [ARG_POS] |
| 203 | + and isinstance(index, TupleExpr) |
| 204 | + and len(index.items) == 2): |
| 205 | + # Special case "for i, x in enumerate(y)". |
| 206 | + lvalue1 = index.items[0] |
| 207 | + lvalue2 = index.items[1] |
| 208 | + for_enumerate = ForEnumerate(builder, index, body_block, loop_exit, line, |
| 209 | + nested) |
| 210 | + for_enumerate.init(lvalue1, lvalue2, expr.args[0]) |
| 211 | + return for_enumerate |
| 212 | + |
| 213 | + elif (expr.callee.fullname == 'builtins.zip' |
| 214 | + and len(expr.args) >= 2 |
| 215 | + and set(expr.arg_kinds) == {ARG_POS} |
| 216 | + and isinstance(index, TupleExpr) |
| 217 | + and len(index.items) == len(expr.args)): |
| 218 | + # Special case "for x, y in zip(a, b)". |
| 219 | + for_zip = ForZip(builder, index, body_block, loop_exit, line, nested) |
| 220 | + for_zip.init(index.items, expr.args) |
| 221 | + return for_zip |
| 222 | + |
| 223 | + if (expr.callee.fullname == 'builtins.reversed' |
| 224 | + and len(expr.args) == 1 |
| 225 | + and expr.arg_kinds == [ARG_POS] |
| 226 | + and is_sequence_rprimitive(rtyp)): |
| 227 | + # Special case "for x in reversed(<list>)". |
| 228 | + expr_reg = builder.accept(expr.args[0]) |
| 229 | + target_type = builder.get_sequence_type(expr) |
| 230 | + |
| 231 | + for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) |
| 232 | + for_list.init(expr_reg, target_type, reverse=True) |
| 233 | + return for_list |
| 234 | + |
| 235 | + # Default to a generic for loop. |
| 236 | + expr_reg = builder.accept(expr) |
| 237 | + for_obj = ForIterable(builder, index, body_block, loop_exit, line, nested) |
| 238 | + item_type = builder._analyze_iterable_item_type(expr) |
| 239 | + item_rtype = builder.type_to_rtype(item_type) |
| 240 | + for_obj.init(expr_reg, item_rtype) |
| 241 | + return for_obj |
23 | 242 |
|
24 | 243 |
|
25 | 244 | class ForGenerator:
|
26 | 245 | """Abstract base class for generating for loops."""
|
27 | 246 |
|
28 | 247 | def __init__(self,
|
29 |
| - builder: 'mypyc.irbuild.builder.IRBuilder', |
| 248 | + builder: IRBuilder, |
30 | 249 | index: Lvalue,
|
31 | 250 | body_block: BasicBlock,
|
32 | 251 | loop_exit: BasicBlock,
|
@@ -122,7 +341,7 @@ def gen_cleanup(self) -> None:
|
122 | 341 |
|
123 | 342 |
|
124 | 343 | def unsafe_index(
|
125 |
| - builder: 'mypyc.irbuild.builder.IRBuilder', target: Value, index: Value, line: int |
| 344 | + builder: IRBuilder, target: Value, index: Value, line: int |
126 | 345 | ) -> Value:
|
127 | 346 | """Emit a potentially unsafe index into a target."""
|
128 | 347 | # This doesn't really fit nicely into any of our data-driven frameworks
|
@@ -297,7 +516,8 @@ def init(self, index1: Lvalue, index2: Lvalue, expr: Expression) -> None:
|
297 | 516 | self.line, nested=True)
|
298 | 517 | self.index_gen.init()
|
299 | 518 | # Iterate over the actual iterable.
|
300 |
| - self.main_gen = self.builder.make_for_loop_generator( |
| 519 | + self.main_gen = make_for_loop_generator( |
| 520 | + self.builder, |
301 | 521 | index2,
|
302 | 522 | expr,
|
303 | 523 | self.body_block,
|
@@ -336,7 +556,8 @@ def init(self, indexes: List[Lvalue], exprs: List[Expression]) -> None:
|
336 | 556 | self.cond_blocks = [BasicBlock() for _ in range(len(indexes) - 1)] + [self.body_block]
|
337 | 557 | self.gens = [] # type: List[ForGenerator]
|
338 | 558 | for index, expr, next_block in zip(indexes, exprs, self.cond_blocks):
|
339 |
| - gen = self.builder.make_for_loop_generator( |
| 559 | + gen = make_for_loop_generator( |
| 560 | + self.builder, |
340 | 561 | index,
|
341 | 562 | expr,
|
342 | 563 | next_block,
|
|
0 commit comments