disassembler: further optimize fixup lookup
[rofl0r-agsutils.git] / optimizer.py
blob7736d06898f3f26bbaa3ae61d4a1305be763f5a4
1 # the AGS script compiler basically doesn't optimize at all,
2 # and the code it emits is super-naive and redundant.
3 # the register bx is most often used only as a temporary
4 # storage and discarded immediately after doing one basic op.
5 # therefore, doing these transformations should be safe.
6 # running these transformations on .s files reduces the
7 # amount of code by about 15-25%, but it also removes debugging
8 # information (sourceline directives).
9 # this speeds up game execution and makes the game smaller.
10 # currently, the optimizer takes input only from stdin and
11 # apply a single transformation at a time.
12 # a future optimization could be to cache code-chunks between
13 # jump labels and apply multiple transformation on the in-memory
14 # code.
16 import sys, re
18 class MultiLineMatcher():
19 def __init__(self, regexes, matchfn, nomatchfn):
20 self.matchfn = matchfn
21 self.nomatchfn = nomatchfn
22 self.regexes = regexes
23 self.line_matches = 0
24 self.saved_lines = None
25 self.matches = None
27 def feed(self, line):
28 line = line.rstrip('\n')
29 m = self.regexes[self.line_matches].match(line)
30 if m:
31 if self.matches is None: self.matches = []
32 if self.saved_lines is None: self.saved_lines = []
33 self.matches.append(m)
34 self.saved_lines.append(line)
35 self.line_matches += 1
36 if self.line_matches == len(self.regexes):
37 self.matchfn(self, self.matches, self.saved_lines)
38 self.line_matches = 0
39 self.saved_lines = None
40 else:
41 self.line_matches = 0
42 self.matches = None
43 if self.saved_lines:
44 for ln in self.saved_lines:
45 self.nomatchfn(self, ln)
46 self.saved_lines = None
47 self.nomatchfn(self, line)
49 removed = 0
50 lineno = 0
52 def push_pop_matchfn(matcher, matches, lines):
53 ws, reg1 = matches[0].groups(0)
54 ws2, reg2 = matches[1].groups(0)
55 global removed
56 if reg1 == reg2: removed += 1
57 else: print "%smr %s, %s"%(ws, reg2, reg1)
58 removed += 1
60 def output_fn(matcher, line):
61 print line
63 def sourceline_matchfn(matcher, matches, lines):
64 global removed
65 removed += 1
67 def cmp_mr_matchfn(matcher, matches, lines):
68 global removed
69 ws, op = matches[0].groups(0)
70 print "%s%s ax, bx"%(ws, op)
71 removed += 1
73 def cmp2_matchfn(matcher, matches, lines):
74 def reverse_cmp(op):
75 if op == 'gt': return 'lte'
76 elif op == 'gte': return 'lt'
77 elif op == 'lt': return 'gte'
78 elif op == 'lte': return 'gt'
79 global removed
80 ws, val = matches[1].groups(0)
81 ws2, op = matches[2].groups(0)
82 print "%sli bx, %s"%(ws, val)
83 print "%s%s ax, bx"%(ws, op) # since we already switched registers, we don't need to switch the op too
84 removed += 2
86 def load_negative_literal_matchfn(matcher, matches, lines):
87 global removed
88 ws, val = matches[1].groups(0)
89 print "%smr bx, ax"%(ws)
90 print "%sli ax, -%s"%(ws, val)
91 removed += 4
93 def load_literal_matchfn(matcher, matches, lines):
94 global removed
95 ws, val = matches[1].groups(0)
96 print "%smr bx, ax"%(ws)
97 print "%sli ax, %s"%(ws, val)
98 removed += 1
100 def axmar_matchfn(matcher, matches, lines):
101 global removed
102 ws, val = matches[0].groups(0)
103 print "%sli bx, %s"%(ws, val)
104 print lines[2]
105 print lines[3]
106 removed += 1
108 def mr_swap_matchfn(matcher, matches, lines):
109 global removed
110 print lines[0]
111 removed += 1
114 def eprint(text): sys.stderr.write("%s\n"%text)
116 def usage():
117 eprint("usage: %s -cmp -pushpop -sourceline -lnl -ll -cmp2 -axmar -mrswap"%sys.argv[0])
118 eprint("")
119 eprint("cmp: optimize cmp/mr")
120 eprint("cmp2: optimize gt/gte/lt/lte (requires prev -ll pass)")
121 eprint("pushpop: optimize push/pop")
122 eprint("sourceline: remove sourceline statements")
123 eprint("lnl: optimize negative literal loads")
124 eprint("ll: optimize literal loads")
125 eprint("only one option can be used at a time")
126 eprint("input is taken from stdin")
128 def main():
129 pushpop_matcher = MultiLineMatcher([
130 re.compile('(\s+)push ([a-z]+)'),
131 re.compile('(\s+)pop ([a-z]+)'),
132 ], push_pop_matchfn, output_fn)
134 sourceline_matcher = MultiLineMatcher([
135 re.compile('(\s+)sourceline ([0-9]+)'),
136 ], sourceline_matchfn, output_fn)
138 cmp_mr_matcher = MultiLineMatcher([
139 re.compile('(\s+)(cmpeq|cmpne|lor|land) bx, ax'),
140 re.compile('(\s+)mr ax, bx'),
141 ], cmp_mr_matchfn, output_fn)
143 load_negative_literal_matcher = MultiLineMatcher([
144 re.compile('(\s+)push ax$'),
145 re.compile('(\s+)li ax, ([0-9]+)$'),
146 re.compile('(\s+)li bx, 0$'),
147 re.compile('(\s+)sub bx, ax$'),
148 re.compile('(\s+)mr ax, bx$'),
149 re.compile('(\s+)pop bx$'),
150 ], load_negative_literal_matchfn, output_fn)
152 load_literal_matcher = MultiLineMatcher([
153 re.compile('(\s+)push ax$'),
154 re.compile('(\s+)li ax, ([0-9]+)$'),
155 re.compile('(\s+)pop bx$'),
156 ], load_literal_matchfn, output_fn)
158 cmp2_matcher = MultiLineMatcher([
159 re.compile('(\s+)mr bx, ax'),
160 re.compile('(\s+)li ax, ([0-9]+)'),
161 re.compile('(\s+)(gt|gte|lt|lte) bx, ax'),
162 re.compile('(\s+)mr ax, bx'),
163 ], cmp2_matchfn, output_fn)
165 axmar_matcher = MultiLineMatcher([
166 re.compile('(\s+)li ax, ([0-9]+)$'),
167 re.compile('(\s+)mr bx, ax$'),
168 re.compile('(\s+)li mar, (.+)$'),
169 re.compile('(\s+)mr ax, mar$'),
170 ], axmar_matchfn, output_fn)
172 mr_swap_matcher = MultiLineMatcher([
173 re.compile('(\s+)mr ax, bx'),
174 re.compile('(\s+)mr bx, ax'),
175 ], mr_swap_matchfn, output_fn)
177 global lineno
178 if len(sys.argv) != 2: return usage()
179 if sys.argv[1] == "-cmp": matcher = cmp_mr_matcher
180 elif sys.argv[1] == "-cmp2": matcher = cmp2_matcher
181 elif sys.argv[1] == "-pushpop": matcher = pushpop_matcher
182 elif sys.argv[1] == "-sourceline": matcher = sourceline_matcher
183 elif sys.argv[1] == "-lnl": matcher = load_negative_literal_matcher
184 elif sys.argv[1] == "-ll": matcher = load_literal_matcher
185 elif sys.argv[1] == "-axmar": matcher = axmar_matcher
186 elif sys.argv[1] == "-mrswap": matcher = mr_swap_matcher
187 else: return usage()
188 while True:
189 lineno = lineno + 1
190 s = sys.stdin.readline()
191 if s == '': break
192 matcher.feed(s)
194 sys.stderr.write( "removed %d lines\n"%removed)
196 if __name__ == "__main__": main()