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
18 class MultiLineMatcher():
19 def __init__(self
, regexes
, matchfn
, nomatchfn
):
20 self
.matchfn
= matchfn
21 self
.nomatchfn
= nomatchfn
22 self
.regexes
= regexes
24 self
.saved_lines
= None
28 line
= line
.rstrip('\n')
29 m
= self
.regexes
[self
.line_matches
].match(line
)
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
)
39 self
.saved_lines
= None
44 for ln
in self
.saved_lines
:
45 self
.nomatchfn(self
, ln
)
46 self
.saved_lines
= None
47 self
.nomatchfn(self
, line
)
52 def push_pop_matchfn(matcher
, matches
, lines
):
53 ws
, reg1
= matches
[0].groups(0)
54 ws2
, reg2
= matches
[1].groups(0)
56 if reg1
== reg2
: removed
+= 1
57 else: print "%smr %s, %s"%(ws
, reg2
, reg1
)
60 def output_fn(matcher
, line
):
63 def sourceline_matchfn(matcher
, matches
, lines
):
67 def cmp_mr_matchfn(matcher
, matches
, lines
):
69 ws
, op
= matches
[0].groups(0)
70 print "%s%s ax, bx"%(ws
, op
)
73 def cmp2_matchfn(matcher
, matches
, lines
):
75 if op
== 'gt': return 'lte'
76 elif op
== 'gte': return 'lt'
77 elif op
== 'lt': return 'gte'
78 elif op
== 'lte': return 'gt'
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
86 def load_negative_literal_matchfn(matcher
, matches
, lines
):
88 ws
, val
= matches
[1].groups(0)
89 print "%smr bx, ax"%(ws)
90 print "%sli ax, -%s"%(ws
, val
)
93 def load_literal_matchfn(matcher
, matches
, lines
):
95 ws
, val
= matches
[1].groups(0)
96 print "%smr bx, ax"%(ws)
97 print "%sli ax, %s"%(ws
, val
)
100 def axmar_matchfn(matcher
, matches
, lines
):
102 ws
, val
= matches
[0].groups(0)
103 print "%sli bx, %s"%(ws
, val
)
108 def mr_swap_matchfn(matcher
, matches
, lines
):
114 def eprint(text
): sys
.stderr
.write("%s\n"%text
)
117 eprint("usage: %s -cmp -pushpop -sourceline -lnl -ll -cmp2 -axmar -mrswap"%sys
.argv
[0])
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")
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
)
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
190 s
= sys
.stdin
.readline()
194 sys
.stderr
.write( "removed %d lines\n"%removed
)
196 if __name__
== "__main__": main()