Skip to content

Commit dc08ab6

Browse files
authored
[mypyc] Simplify generated C by omitting gotos to the next block (#10230)
This reduces the number of lines in the generated C when compiling the richards benchmark by about 5%. The main benefit is making the generated C easier to read by humans. This may also marginally improve the speed of compilation.
1 parent 20e8e68 commit dc08ab6

File tree

2 files changed

+108
-22
lines changed

2 files changed

+108
-22
lines changed

mypyc/codegen/emitfunc.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Code generation for native function bodies."""
22

3-
from typing import Union
3+
from typing import Union, Optional
44
from typing_extensions import Final
55

66
from mypyc.common import (
@@ -73,11 +73,17 @@ def generate_native_function(fn: FuncIR,
7373
init=init))
7474

7575
# Before we emit the blocks, give them all labels
76-
for i, block in enumerate(fn.blocks):
76+
blocks = fn.blocks
77+
for i, block in enumerate(blocks):
7778
block.label = i
7879

79-
for block in fn.blocks:
80+
for i in range(len(blocks)):
81+
block = blocks[i]
82+
next_block = None
83+
if i + 1 < len(blocks):
84+
next_block = blocks[i + 1]
8085
body.emit_label(block)
86+
visitor.next_block = next_block
8187
for op in block.ops:
8288
op.accept(visitor)
8389

@@ -99,23 +105,31 @@ def __init__(self,
99105
self.source_path = source_path
100106
self.module_name = module_name
101107
self.literals = emitter.context.literals
108+
self.next_block = None # type: Optional[BasicBlock]
102109

103110
def temp_name(self) -> str:
104111
return self.emitter.temp_name()
105112

106113
def visit_goto(self, op: Goto) -> None:
107-
self.emit_line('goto %s;' % self.label(op.label))
114+
if op.label is not self.next_block:
115+
self.emit_line('goto %s;' % self.label(op.label))
108116

109117
def visit_branch(self, op: Branch) -> None:
110-
neg = '!' if op.negated else ''
111-
118+
true, false = op.true, op.false
119+
negated = op.negated
120+
if true is self.next_block and op.traceback_entry is None:
121+
# Switch true/false since it avoids an else block.
122+
true, false = false, true
123+
negated = not negated
124+
125+
neg = '!' if negated else ''
112126
cond = ''
113127
if op.op == Branch.BOOL:
114128
expr_result = self.reg(op.value)
115129
cond = '{}{}'.format(neg, expr_result)
116130
elif op.op == Branch.IS_ERROR:
117131
typ = op.value.type
118-
compare = '!=' if op.negated else '=='
132+
compare = '!=' if negated else '=='
119133
if isinstance(typ, RTuple):
120134
# TODO: What about empty tuple?
121135
cond = self.emitter.tuple_undefined_check_cond(typ,
@@ -133,15 +147,24 @@ def visit_branch(self, op: Branch) -> None:
133147
if op.traceback_entry is not None or op.rare:
134148
cond = 'unlikely({})'.format(cond)
135149

136-
self.emit_line('if ({}) {{'.format(cond))
137-
138-
self.emit_traceback(op)
139-
140-
self.emit_lines(
141-
'goto %s;' % self.label(op.true),
142-
'} else',
143-
' goto %s;' % self.label(op.false)
144-
)
150+
if false is self.next_block:
151+
if op.traceback_entry is None:
152+
self.emit_line('if ({}) goto {};'.format(cond, self.label(true)))
153+
else:
154+
self.emit_line('if ({}) {{'.format(cond))
155+
self.emit_traceback(op)
156+
self.emit_lines(
157+
'goto %s;' % self.label(true),
158+
'}'
159+
)
160+
else:
161+
self.emit_line('if ({}) {{'.format(cond))
162+
self.emit_traceback(op)
163+
self.emit_lines(
164+
'goto %s;' % self.label(true),
165+
'} else',
166+
' goto %s;' % self.label(false)
167+
)
145168

146169
def visit_return(self, op: Return) -> None:
147170
value_str = self.reg(op.value)

mypyc/test/test_emitfunc.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import unittest
22

3-
from typing import List
3+
from typing import List, Optional
44

55
from mypy.ordered_dict import OrderedDict
66

@@ -24,9 +24,7 @@
2424
from mypyc.codegen.emitfunc import generate_native_function, FunctionEmitterVisitor
2525
from mypyc.primitives.registry import binary_ops
2626
from mypyc.primitives.misc_ops import none_object_op
27-
from mypyc.primitives.list_ops import (
28-
list_get_item_op, list_set_item_op, list_append_op
29-
)
27+
from mypyc.primitives.list_ops import list_get_item_op, list_set_item_op, list_append_op
3028
from mypyc.primitives.dict_ops import (
3129
dict_new_op, dict_update_op, dict_get_item_op, dict_set_item_op
3230
)
@@ -36,6 +34,8 @@
3634

3735

3836
class TestFunctionEmitterVisitor(unittest.TestCase):
37+
"""Test generation of fragments of C from individual IR ops."""
38+
3939
def setUp(self) -> None:
4040
self.registers = [] # type: List[Register]
4141

@@ -75,6 +75,10 @@ def test_goto(self) -> None:
7575
self.assert_emit(Goto(BasicBlock(2)),
7676
"goto CPyL2;")
7777

78+
def test_goto_next_block(self) -> None:
79+
next_block = BasicBlock(2)
80+
self.assert_emit(Goto(next_block), "", next_block=next_block)
81+
7882
def test_return(self) -> None:
7983
self.assert_emit(Return(self.m),
8084
"return cpy_r_m;")
@@ -128,6 +132,61 @@ def test_branch(self) -> None:
128132
goto CPyL9;
129133
""")
130134

135+
def test_branch_no_else(self) -> None:
136+
next_block = BasicBlock(9)
137+
b = Branch(self.b, BasicBlock(8), next_block, Branch.BOOL)
138+
self.assert_emit(b,
139+
"""if (cpy_r_b) goto CPyL8;""",
140+
next_block=next_block)
141+
next_block = BasicBlock(9)
142+
b = Branch(self.b, BasicBlock(8), next_block, Branch.BOOL)
143+
b.negated = True
144+
self.assert_emit(b,
145+
"""if (!cpy_r_b) goto CPyL8;""",
146+
next_block=next_block)
147+
148+
def test_branch_no_else_negated(self) -> None:
149+
next_block = BasicBlock(1)
150+
b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL)
151+
self.assert_emit(b,
152+
"""if (!cpy_r_b) goto CPyL2;""",
153+
next_block=next_block)
154+
next_block = BasicBlock(1)
155+
b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL)
156+
b.negated = True
157+
self.assert_emit(b,
158+
"""if (cpy_r_b) goto CPyL2;""",
159+
next_block=next_block)
160+
161+
def test_branch_is_error(self) -> None:
162+
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
163+
self.assert_emit(b,
164+
"""if (cpy_r_b == 2) {
165+
goto CPyL8;
166+
} else
167+
goto CPyL9;
168+
""")
169+
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
170+
b.negated = True
171+
self.assert_emit(b,
172+
"""if (cpy_r_b != 2) {
173+
goto CPyL8;
174+
} else
175+
goto CPyL9;
176+
""")
177+
178+
def test_branch_is_error_next_block(self) -> None:
179+
next_block = BasicBlock(8)
180+
b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR)
181+
self.assert_emit(b,
182+
"""if (cpy_r_b != 2) goto CPyL9;""",
183+
next_block=next_block)
184+
b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR)
185+
b.negated = True
186+
self.assert_emit(b,
187+
"""if (cpy_r_b == 2) goto CPyL9;""",
188+
next_block=next_block)
189+
131190
def test_call(self) -> None:
132191
decl = FuncDecl('myfn', None, 'mod',
133192
FuncSignature([RuntimeArg('m', int_rprimitive)], int_rprimitive))
@@ -318,7 +377,7 @@ def test_long_unsigned(self) -> None:
318377
self.assert_emit(Assign(a, Integer((1 << 31) - 1, int64_rprimitive)),
319378
"""cpy_r_a = 2147483647;""")
320379

321-
def assert_emit(self, op: Op, expected: str) -> None:
380+
def assert_emit(self, op: Op, expected: str, next_block: Optional[BasicBlock] = None) -> None:
322381
block = BasicBlock(0)
323382
block.ops.append(op)
324383
value_names = generate_names_for_ir(self.registers, [block])
@@ -328,13 +387,17 @@ def assert_emit(self, op: Op, expected: str) -> None:
328387
declarations.fragments = []
329388

330389
visitor = FunctionEmitterVisitor(emitter, declarations, 'prog.py', 'prog')
390+
visitor.next_block = next_block
331391

332392
op.accept(visitor)
333393
frags = declarations.fragments + emitter.fragments
334394
actual_lines = [line.strip(' ') for line in frags]
335395
assert all(line.endswith('\n') for line in actual_lines)
336396
actual_lines = [line.rstrip('\n') for line in actual_lines]
337-
expected_lines = expected.rstrip().split('\n')
397+
if not expected.strip():
398+
expected_lines = []
399+
else:
400+
expected_lines = expected.rstrip().split('\n')
338401
expected_lines = [line.strip(' ') for line in expected_lines]
339402
assert_string_arrays_equal(expected_lines, actual_lines,
340403
msg='Generated code unexpected')

0 commit comments

Comments
 (0)