Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mypyc] Remove unused labels and gotos #15244

Merged
merged 3 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mypyc/codegen/emit.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ def emit_label(self, label: BasicBlock | str) -> None:
if isinstance(label, str):
text = label
else:
if label.label == 0 or not label.referenced:
return

text = self.label(label)
# Extra semicolon prevents an error when the next line declares a tempvar
self.fragments.append(f"{text}: ;\n")
Expand Down
34 changes: 30 additions & 4 deletions mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
CallC,
Cast,
ComparisonOp,
ControlOp,
DecRef,
Extend,
Float,
Expand Down Expand Up @@ -123,6 +124,28 @@ def generate_native_function(
for i, block in enumerate(blocks):
block.label = i

dosisod marked this conversation as resolved.
Show resolved Hide resolved
# Find blocks that are never jumped to or are only jumped to from the
# block directly above it. This allows for more labels and gotos to be
# eliminated during code generation.
Comment on lines +127 to +129
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only labels are eliminated here, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code only marks the basic blocks as "referenced" if they meet the conditions above, nothing actually gets eliminated here. And yes, during codegen, labels won't be emitted for these "unreferenced" blocks, same for gotos (assuming they are jumping to the next block).

for block in fn.blocks:
terminator = block.terminator
assert isinstance(terminator, ControlOp)

for target in terminator.targets():
is_next_block = target.label == block.label + 1

# Always emit labels for GetAttr error checks since the emit code that
# generates them will add instructions between the branch and the
# next label, causing the label to be wrongly removed. A better
# solution would be to change the IR so that it adds a basic block
# inbetween the calls.
is_problematic_op = isinstance(terminator, Branch) and any(
isinstance(s, GetAttr) for s in terminator.sources()
)

if not is_next_block or is_problematic_op:
fn.blocks[target.label].referenced = True

common = frequently_executed_blocks(fn.blocks[0])

for i in range(len(blocks)):
Expand Down Expand Up @@ -216,17 +239,20 @@ def visit_branch(self, op: Branch) -> None:

if false is self.next_block:
if op.traceback_entry is None:
self.emit_line(f"if ({cond}) goto {self.label(true)};")
if true is not self.next_block:
self.emit_line(f"if ({cond}) goto {self.label(true)};")
dosisod marked this conversation as resolved.
Show resolved Hide resolved
else:
self.emit_line(f"if ({cond}) {{")
self.emit_traceback(op)
self.emit_lines("goto %s;" % self.label(true), "}")
else:
self.emit_line(f"if ({cond}) {{")
self.emit_traceback(op)
self.emit_lines(
"goto %s;" % self.label(true), "} else", " goto %s;" % self.label(false)
)

if true is not self.next_block:
self.emit_line("goto %s;" % self.label(true))

self.emit_lines("} else", " goto %s;" % self.label(false))

def visit_return(self, op: Return) -> None:
value_str = self.reg(op.value)
Expand Down
1 change: 1 addition & 0 deletions mypyc/ir/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(self, label: int = -1) -> None:
self.label = label
self.ops: list[Op] = []
self.error_handler: BasicBlock | None = None
self.referenced = False

@property
def terminated(self) -> bool:
Expand Down
8 changes: 1 addition & 7 deletions mypyc/test/test_emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,12 +894,7 @@ def test_simple(self) -> None:
generate_native_function(fn, emitter, "prog.py", "prog")
result = emitter.fragments
assert_string_arrays_equal(
[
"CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n",
"CPyL0: ;\n",
" return cpy_r_arg;\n",
"}\n",
],
["CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n", " return cpy_r_arg;\n", "}\n"],
result,
msg="Generated code invalid",
)
Expand All @@ -922,7 +917,6 @@ def test_register(self) -> None:
[
"PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n",
" CPyTagged cpy_r_r0;\n",
"CPyL0: ;\n",
" cpy_r_r0 = 10;\n",
" CPy_Unreachable();\n",
"}\n",
Expand Down