forked from M-Labs/artiq
1
0
Fork 0

compiler: fixed dead code eliminator

Instead of removing basic blocks with no predecessor, we will now mark
and remove all blocks that are unreachable from the entry block. This
can handle loops that are dead code. This is needed as we will now
generate more complicated code for exception handling which the old dead
code eliminator failed to handle.
This commit is contained in:
pca006132 2022-01-23 21:16:19 +08:00 committed by Sébastien Bourdeauducq
parent da4ff44377
commit 6ec003c1c9
2 changed files with 27 additions and 17 deletions

View File

@ -135,6 +135,7 @@ class NamedValue(Value):
def __init__(self, typ, name): def __init__(self, typ, name):
super().__init__(typ) super().__init__(typ)
self.name, self.function = name, None self.name, self.function = name, None
self.is_removed = False
def set_name(self, new_name): def set_name(self, new_name):
if self.function is not None: if self.function is not None:
@ -235,7 +236,7 @@ class Instruction(User):
self.drop_references() self.drop_references()
# Check this after drop_references in case this # Check this after drop_references in case this
# is a self-referencing phi. # is a self-referencing phi.
assert not any(self.uses) assert all(use.is_removed for use in self.uses)
def replace_with(self, value): def replace_with(self, value):
self.replace_all_uses_with(value) self.replace_all_uses_with(value)
@ -370,7 +371,7 @@ class BasicBlock(NamedValue):
self.remove_from_parent() self.remove_from_parent()
# Check this after erasing instructions in case the block # Check this after erasing instructions in case the block
# loops into itself. # loops into itself.
assert not any(self.uses) assert all(use.is_removed for use in self.uses)
def prepend(self, insn): def prepend(self, insn):
assert isinstance(insn, Instruction) assert isinstance(insn, Instruction)
@ -1360,14 +1361,6 @@ class LandingPad(Terminator):
def cleanup(self): def cleanup(self):
return self.operands[0] return self.operands[0]
def erase(self):
self.remove_from_parent()
# we should erase all clauses as well
for block in set(self.operands):
block.uses.remove(self)
block.erase()
assert not any(self.uses)
def clauses(self): def clauses(self):
return zip(self.operands[1:], self.types) return zip(self.operands[1:], self.types)

View File

@ -15,13 +15,26 @@ class DeadCodeEliminator:
self.process_function(func) self.process_function(func)
def process_function(self, func): def process_function(self, func):
modified = True # defer removing those blocks, so our use checks will ignore deleted blocks
while modified: preserve = [func.entry()]
modified = False work_list = [func.entry()]
for block in list(func.basic_blocks): while any(work_list):
if not any(block.predecessors()) and block != func.entry(): block = work_list.pop()
self.remove_block(block) for succ in block.successors():
modified = True if succ not in preserve:
preserve.append(succ)
work_list.append(succ)
to_be_removed = []
for block in func.basic_blocks:
if block not in preserve:
block.is_removed = True
to_be_removed.append(block)
for insn in block.instructions:
insn.is_removed = True
for block in to_be_removed:
self.remove_block(block)
modified = True modified = True
while modified: while modified:
@ -42,6 +55,8 @@ class DeadCodeEliminator:
def remove_block(self, block): def remove_block(self, block):
# block.uses are updated while iterating # block.uses are updated while iterating
for use in set(block.uses): for use in set(block.uses):
if use.is_removed:
continue
if isinstance(use, ir.Phi): if isinstance(use, ir.Phi):
use.remove_incoming_block(block) use.remove_incoming_block(block)
if not any(use.operands): if not any(use.operands):
@ -56,6 +71,8 @@ class DeadCodeEliminator:
def remove_instruction(self, insn): def remove_instruction(self, insn):
for use in set(insn.uses): for use in set(insn.uses):
if use.is_removed:
continue
if isinstance(use, ir.Phi): if isinstance(use, ir.Phi):
use.remove_incoming_value(insn) use.remove_incoming_value(insn)
if not any(use.operands): if not any(use.operands):