Skip to content

Commit b2edab2

Browse files
authored
[mypyc] Refactor: extract code from IRBuilder (#8509)
Extract methods that seemed out of place in IRBuilder, including file and import handling, and for loop and comprehension helpers. This also removes a cyclic import. Closes mypyc/mypyc#714.
1 parent 0a05e61 commit b2edab2

File tree

7 files changed

+413
-389
lines changed

7 files changed

+413
-389
lines changed

mypyc/irbuild/builder.py

Lines changed: 46 additions & 358 deletions
Large diffs are not rendered by default.

mypyc/irbuild/expression.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from mypyc.primitives.set_ops import new_set_op, set_add_op, set_update_op
2828
from mypyc.irbuild.specialize import specializers
2929
from mypyc.irbuild.builder import IRBuilder
30+
from mypyc.irbuild.for_helpers import translate_list_comprehension, comprehension_helper
3031

3132

3233
# Name and attribute references
@@ -495,7 +496,7 @@ def _visit_display(builder: IRBuilder,
495496

496497

497498
def transform_list_comprehension(builder: IRBuilder, o: ListComprehension) -> Value:
498-
return builder.translate_list_comprehension(o.generator)
499+
return translate_list_comprehension(builder, o.generator)
499500

500501

501502
def transform_set_comprehension(builder: IRBuilder, o: SetComprehension) -> Value:
@@ -507,7 +508,7 @@ def gen_inner_stmts() -> None:
507508
e = builder.accept(gen.left_expr)
508509
builder.primitive_op(set_add_op, [set_ops, e], o.line)
509510

510-
builder.comprehension_helper(loop_params, gen_inner_stmts, o.line)
511+
comprehension_helper(builder, loop_params, gen_inner_stmts, o.line)
511512
return set_ops
512513

513514

@@ -520,7 +521,7 @@ def gen_inner_stmts() -> None:
520521
v = builder.accept(o.value)
521522
builder.primitive_op(dict_set_item_op, [d, k, v], o.line)
522523

523-
builder.comprehension_helper(loop_params, gen_inner_stmts, o.line)
524+
comprehension_helper(builder, loop_params, gen_inner_stmts, o.line)
524525
return d
525526

526527

@@ -543,5 +544,5 @@ def get_arg(arg: Optional[Expression]) -> Value:
543544
def transform_generator_expr(builder: IRBuilder, o: GeneratorExpr) -> Value:
544545
builder.warning('Treating generator comprehension as list', o.line)
545546
return builder.primitive_op(
546-
iter_op, [builder.translate_list_comprehension(o)], o.line
547+
iter_op, [translate_list_comprehension(builder, o)], o.line
547548
)

mypyc/irbuild/for_helpers.py

Lines changed: 234 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,251 @@
1-
"""Helpers for generating for loops.
1+
"""Helpers for generating for loops and comprehensions.
22
33
We special case certain kinds for loops such as "for x in range(...)"
44
for better efficiency. Each for loop generator class below deals one
55
such special case.
66
"""
77

8-
from typing import Union, List
9-
from typing_extensions import TYPE_CHECKING
8+
from typing import Union, List, Optional, Tuple, Callable
109

11-
from mypy.nodes import Lvalue, Expression
10+
from mypy.nodes import Lvalue, Expression, TupleExpr, CallExpr, RefExpr, GeneratorExpr, ARG_POS
1211
from mypyc.ir.ops import (
1312
Value, BasicBlock, LoadInt, Branch, Register, AssignmentTarget
1413
)
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+
)
1617
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
1819
from mypyc.primitives.misc_ops import iter_op, next_op
1920
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
23242

24243

25244
class ForGenerator:
26245
"""Abstract base class for generating for loops."""
27246

28247
def __init__(self,
29-
builder: 'mypyc.irbuild.builder.IRBuilder',
248+
builder: IRBuilder,
30249
index: Lvalue,
31250
body_block: BasicBlock,
32251
loop_exit: BasicBlock,
@@ -122,7 +341,7 @@ def gen_cleanup(self) -> None:
122341

123342

124343
def unsafe_index(
125-
builder: 'mypyc.irbuild.builder.IRBuilder', target: Value, index: Value, line: int
344+
builder: IRBuilder, target: Value, index: Value, line: int
126345
) -> Value:
127346
"""Emit a potentially unsafe index into a target."""
128347
# 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:
297516
self.line, nested=True)
298517
self.index_gen.init()
299518
# 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,
301521
index2,
302522
expr,
303523
self.body_block,
@@ -336,7 +556,8 @@ def init(self, indexes: List[Lvalue], exprs: List[Expression]) -> None:
336556
self.cond_blocks = [BasicBlock() for _ in range(len(indexes) - 1)] + [self.body_block]
337557
self.gens = [] # type: List[ForGenerator]
338558
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,
340561
index,
341562
expr,
342563
next_block,

mypyc/irbuild/main.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ def f(x: int) -> int:
2222
from collections import OrderedDict
2323
from typing import List, Dict, Callable, Any, TypeVar, cast
2424

25-
from mypy.nodes import MypyFile, Expression
25+
from mypy.nodes import MypyFile, Expression, ClassDef
2626
from mypy.types import Type
2727
from mypy.state import strict_optional_set
2828
from mypy.build import Graph
2929

30+
from mypyc.common import TOP_LEVEL_NAME
3031
from mypyc.errors import Errors
3132
from mypyc.options import CompilerOptions
33+
from mypyc.ir.rtypes import none_rprimitive
3234
from mypyc.ir.module_ir import ModuleIR, ModuleIRs
35+
from mypyc.ir.func_ir import FuncIR, FuncDecl, FuncSignature
3336
from mypyc.irbuild.prebuildvisitor import PreBuildVisitor
3437
from mypyc.irbuild.vtable import compute_vtable
3538
from mypyc.irbuild.prepare import build_type_map
@@ -72,7 +75,7 @@ def build_ir(modules: List[MypyFile],
7275
visitor.builder = builder
7376

7477
# Second pass does the bulk of the work.
75-
builder.visit_mypy_file(module)
78+
transform_mypy_file(builder, module)
7679
module_ir = ModuleIR(
7780
module.fullname,
7881
list(builder.imports),
@@ -89,3 +92,36 @@ def build_ir(modules: List[MypyFile],
8992
compute_vtable(cir)
9093

9194
return result
95+
96+
97+
def transform_mypy_file(builder: IRBuilder, mypyfile: MypyFile) -> None:
98+
if mypyfile.fullname in ('typing', 'abc'):
99+
# These module are special; their contents are currently all
100+
# built-in primitives.
101+
return
102+
103+
builder.set_module(mypyfile.fullname, mypyfile.path)
104+
105+
classes = [node for node in mypyfile.defs if isinstance(node, ClassDef)]
106+
107+
# Collect all classes.
108+
for cls in classes:
109+
ir = builder.mapper.type_to_ir[cls.info]
110+
builder.classes.append(ir)
111+
112+
builder.enter('<top level>')
113+
114+
# Make sure we have a builtins import
115+
builder.gen_import('builtins', -1)
116+
117+
# Generate ops.
118+
for node in mypyfile.defs:
119+
builder.accept(node)
120+
builder.maybe_add_implicit_return()
121+
122+
# Generate special function representing module top level.
123+
blocks, env, ret_type, _ = builder.leave()
124+
sig = FuncSignature([], none_rprimitive)
125+
func_ir = FuncIR(FuncDecl(TOP_LEVEL_NAME, None, builder.module_name, sig), blocks, env,
126+
traceback_name="<module>")
127+
builder.functions.append(func_ir)

0 commit comments

Comments
 (0)