forked from sampsyo/bril
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tdce.py
137 lines (110 loc) · 3.99 KB
/
tdce.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Trivial dead code elimination for Bril programs---a demonstration of
local optimization.
"""
import sys
import json
from form_blocks import form_blocks
from util import flatten
def trivial_dce_pass(func):
"""Remove instructions from `func` that are never used as arguments
to any other instruction. Return a bool indicating whether we deleted
anything.
"""
blocks = list(form_blocks(func['instrs']))
# Find all the variables used as an argument to any instruction,
# even once.
used = set()
for block in blocks:
for instr in block:
# Mark all the variable arguments as used.
used.update(instr.get('args', []))
# Delete the instructions that write to unused variables.
changed = False
for block in blocks:
# Avoid deleting *effect instructions* that do not produce a
# result. The `'dest' in i` predicate is true for all the *value
# functions*, which are pure and can be eliminated if their
# results are never used.
new_block = [i for i in block
if 'dest' not in i or i['dest'] in used]
# Record whether we deleted anything.
changed |= len(new_block) != len(block)
# Replace the block with the filtered one.
block[:] = new_block
# Reassemble the function.
func['instrs'] = flatten(blocks)
return changed
def trivial_dce(func):
"""Iteratively remove dead instructions, stopping when nothing
remains to remove.
"""
# An exercise for the reader: prove that this loop terminates.
while trivial_dce_pass(func):
pass
def drop_killed_local(block):
"""Delete instructions in a single block whose result is unused
before the next assignment. Return a bool indicating whether
anything changed.
"""
# A map from variable names to the last place they were assigned
# since the last use. These are candidates for deletion---if a
# variable is assigned while in this map, we'll delete what the maps
# point to.
last_def = {}
# Find the indices of droppable instructions.
to_drop = set()
for i, instr in enumerate(block):
# Check for uses. Anything we use is no longer a candidate for
# deletion.
for var in instr.get('args', []):
if var in last_def:
del last_def[var]
# Check for definitions. This *has* to happen after the use
# check, so we don't count "a = a + 1" as killing a before using
# it.
if 'dest' in instr:
dest = instr['dest']
if dest in last_def:
# Another definition since the most recent use. Drop the
# last definition.
to_drop.add(last_def[dest])
last_def[dest] = i
# Remove the instructions marked for deletion.
new_block = [instr for i, instr in enumerate(block)
if i not in to_drop]
changed = len(new_block) != len(block)
block[:] = new_block
return changed
def drop_killed_pass(func):
"""Drop killed functions from *all* blocks. Return a bool indicating
whether anything changed.
"""
blocks = list(form_blocks(func['instrs']))
changed = False
for block in blocks:
changed |= drop_killed_local(block)
func['instrs'] = flatten(blocks)
return changed
def trivial_dce_plus(func):
"""Like `trivial_dce`, but also deletes locally killed instructions.
"""
while trivial_dce_pass(func) or drop_killed_pass(func):
pass
MODES = {
'tdce': trivial_dce,
'tdcep': trivial_dce_pass,
'dkp': drop_killed_pass,
'tdce+': trivial_dce_plus,
}
def localopt():
if len(sys.argv) > 1:
modify_func = MODES[sys.argv[1]]
else:
modify_func = trivial_dce
# Apply the change to all the functions in the input program.
bril = json.load(sys.stdin)
for func in bril['functions']:
modify_func(func)
json.dump(bril, sys.stdout, indent=2, sort_keys=True)
if __name__ == '__main__':
localopt()