First prototype version of the optimizer.

This commit is contained in:
Sei Lisa 2014-07-27 02:19:54 +02:00
parent 603b85afc8
commit 59de1e1f4f

View file

@ -11,93 +11,279 @@ class optimizer(object):
binary_ops = frozenset(('+','-','*','/','%','<<','>>','<','<=','>','>=', binary_ops = frozenset(('+','-','*','/','%','<<','>>','<','<=','>','>=',
'==','!=','|','^','&','||','&&')) '==','!=','|','^','&','||','&&'))
def FoldStmt(self, code):
"""If the statement is a constant or an identifier, remove it as it does
nothing.
"""
if code[0] in (CONSTANT, 'IDENT', 'FIELD'):
code[:] = [S[';'], None]
else:
code[:] = code
def FoldTree(self, code): def FoldTree(self, code):
"""Recursively traverse the tree to fold constants, changing the tree """Recursively traverse the tree to fold constants, changing it in
in place. place.
Also optimizes away IF, WHILE, etc.
""" """
while code[0] == 'EXPR': while code[0] == 'EXPR':
code[:] = code[2] code[:] = code[2]
if code[0] == 'CAST': code0 = code[0]
if code0 == 'CAST':
self.FoldTree(code[2]) self.FoldTree(code[2])
if code[2][0] == CONSTANT: if code[2][0] == CONSTANT:
if code[1] != 'key': # key constants not possible # Enable key constants. We'll typecast them back on output, but
# this enables some optimizations.
#if code[1] != 'key': # key constants not possible
code[:] = [CONSTANT, code[1], lslfuncs.typecast(code[2][2])] code[:] = [CONSTANT, code[1], lslfuncs.typecast(code[2][2])]
return return
if code[0] == '-': if code0 == 'NEG':
self.FoldTree(code[3]) self.FoldTree(code[2])
if code[3][1] in ('integer', 'float'): # no gain otherwise if code[2][0] == CONSTANT:
if code[3][0] == CONSTANT: code[:] = [CONSTANT, code[1], lslfuncs.neg(code[2][2])]
code[3][2] = -code[3][2] return
else:
code[:] = ['+', code[1], code[2], [S['NEG'], code[3][1], code[3]]]
self.FoldTree(code[2])
else:
self.FoldTree(code[2])
if code[2][0] == code[3][0] == CONSTANT:
code[:] = ['-', code[1], lslfuncs.sub(code[2][2], code[3][2])]
# Fall through to optimize it right away as addition
if code[0] in self.binary_ops: if code0 == '!':
self.FoldTree(code[2])
if code[2][0] == CONSTANT:
code[:] = [CONSTANT, code[1], int(not code[2][2])]
return
if code0 == '~':
self.FoldTree(code[2])
if code[2][0] == CONSTANT:
code[:] = [CONSTANT, code[1], ~code[2][2]]
return
if code0 == '()':
self.FoldTree(code[2])
if code[2][0] == CONSTANT:
code[:] = code[2]
if code0 in self.binary_ops:
# RTL evaluation # RTL evaluation
self.FoldTree(code[3]) self.FoldTree(code[3])
self.FoldTree(code[2]) self.FoldTree(code[2])
if code[2][0] == code[3][0] == CONSTANT: if code[2][0] == code[3][0] == CONSTANT:
code[:] = [CONSTANT, code[1], lslfuncs.add(code[2][2], code[3][2])] op = code0
op1 = code[2][2]
op2 = code[3][2]
if op == '+':
result = lslfuncs.add(op1, op2)
elif op == '-':
result = lslfuncs.sub(op1, op2)
elif op == '*':
result = lslfuncs.mul(op1, op2)
elif op == '/':
result = lslfuncs.div(op1, op2)
elif op == '%':
result = lslfuncs.mod(op1, op2)
elif op == '<<':
result = lslfuncs.S32(op1 << (op2 & 31))
elif op == '>>':
result = lslfuncs.S32(op1 >> (op2 & 31))
elif op == '==' or op == '!=':
result = lslfuncs.compare(op1, op2, op == '==')
elif op in ('<', '<=', '>', '>='):
if op in ('>', '<='):
result = lslfuncs.less(op2, op1)
else:
result = lslfuncs.less(op1, op2)
if op in ('>=', '<='):
result = not result
elif op == '|':
result = op1 | op2
elif op == '^':
result = op1 ^ op2
elif op == '&':
result = op1 & op2
elif op == '||':
result = int(op1 or op2)
elif op == '&&':
result = int(op1 and op2)
else:
raise Exception(u'Internal error: Operator not found: ' + op.decode('utf8'))
code[:] = [CONSTANT, code[1], result]
elif code[0] == '-' and code[2][1] in ('integer', 'float') and code[3][1] in ('integer', 'float'):
# Change - to + - for int/float
if code[3][0] == CONSTANT:
code[3][2] = lslfuncs.neg(code[3][2])
else:
code[:] = [S['+'], code[1], code[2], [S['NEG'], code[3][1], code[3]]]
elif code[0] == '<<' and code[3][0] == CONSTANT:
# Transforming << into multiply saves some bytes.
if code[2][0] in ('+', '-', 'NEG'): # operands with priority between * and <<
code[2] = [S['()'], code[2][1], code[2]]
code[:] = [S['*'], code[1], code[2], 1<<(code[3][2] & 31)]
return return
if self.globalmode: if self.globalmode:
if code[0] == 'IDENT': if code0 == 'IDENT':
if code[1] != 'key' and self.symtab[code[2]][2] is not None: if code[1] != 'key' and self.symtab[code[3]][code[2]][2] is not None:
code[:] = [CONSTANT, code[1], self.symtab[code[2]][2]] code[:] = [CONSTANT, code[1], self.symtab[code[2]][2]]
return return
if code[0] == 'FUNCTION': if code0 == 'FUNCTION':
for x in code[3][::-1]: for x in code[3][::-1]:
self.FoldTree(x) self.FoldTree(x)
if code[2] in self.functions: if code[2] in self.functions and self.functions[code[2]][2] is not None:
for x in code[3]: for x in code[3]:
if x[0] != CONSTANT: if x[0] != CONSTANT:
break break
else: else:
if code[2] in self.functions: # Call it
# Call it val = self.functions[code[2]][2](*tuple(x[2] for x in code[3]))
val = self.functions[code[2]](tuple(x[2] for x in code[3])) code[:] = [CONSTANT, code[1], val]
code[:] = [CONSTANT, code[1], val]
return return
if code[0] == 'PRINT': if code0 == 'PRINT':
# useless but who knows # useless but who knows
self.FoldTree(code[2]) self.FoldTree(code[2])
if code[0] == '{}':
for x in code[2:]:
self.FoldTree(x)
return return
if code[0] in ('VECTOR', 'ROTATION', 'LIST'): if code0 in ('VECTOR', 'ROTATION', 'LIST'):
for x in code[:1:-1]: for x in code[:1:-1]:
self.FoldTree(x) self.FoldTree(x)
# TODO: Fold into constant if possible.
if code[0] == 'FIELD':
self.FoldTree(code[2])
assert code[2][0] in ('VECTOR', 'ROTATION')
idx = '--xyzs'.index(code[3])
if code[2][idx][0] == CONSTANT:
code[:] = [CONSTANT, 'float', code[2][idx][0]]
return return
def Fold(self, code, IsGlobal = False): if code0 == 'FIELD':
# FIXME: I was drunk when I wrote this.
#self.FoldTree(code[2])
#assert code[2][1] in ('vector', 'rotation')
#idx = '--xyzs'.index(code[3])
#if code[2][idx][0] == CONSTANT:
# code[:] = [CONSTANT, 'float', code[2][idx][0]]
if self.globalmode:
# We can fold a vector or rotation field as they are constant.
assert code[2][0] == 'IDENT'
value = self.symtab[code[2][3]][code[2][2]][2]
assert type(value) in (lslfuncs.Vector, lslfuncs.Quaternion)
code[:] = [CONSTANT, 'float', lslfuncs.ff(value['xyzs'].index(code[3]))]
return
if code0 == '{}':
for x in code[2:]:
self.FoldTree(x)
self.FoldStmt(x)
return
if code0 == 'IF':
self.FoldTree(code[2])
if code[2][0] == CONSTANT:
# We can remove one of the branches safely.
if lslfuncs.cond(code[2][2]):
self.FoldTree(code[3])
code[:] = code[3]
self.FoldStmt(code)
elif len(code) > 4:
self.FoldTree(code[4])
code[:] = code[4]
self.FoldStmt(code)
else:
# No ELSE branch, replace the statement with an empty one.
code[:] = [S[';'], None]
else:
self.FoldTree(code[3])
self.FoldStmt(code[3])
if len(code) > 4:
self.FoldTree(code[4])
self.FoldStmt(code[4])
return
if code0 == 'WHILE':
self.FoldTree(code[2])
if code[2][0] == CONSTANT:
# See if the whole WHILE can be eliminated.
if lslfuncs.cond(code[2][2]):
# Endless loop which must be kept.
# First, replace the constant.
code[2][1:2] = [S['integer'], 1]
# Recurse on the statement.
self.FoldTree(code[3])
self.FoldStmt(code[3])
else:
# Can be removed.
code[:] = [S[';'], None]
else:
self.FoldTree(code[3])
self.FoldStmt(code[3])
return
if code0 == 'DO':
self.FoldTree(code[2]) # This one is always executed.
self.FoldStmt(code[2])
self.FoldTree(code[3])
# See if the latest part is a constant.
if code[3][0] == CONSTANT:
if lslfuncs.cond(code[3][2]):
# Endless loop. Replace the constant.
code[3][1:2] = [S['integer'], 1]
else:
# Only one go. Replace with the statement(s).
code[:] = code[2]
return
if code0 == 'FOR':
for x in code[2]:
self.FoldTree(x) # Initializer expresion list, always executed.
self.FoldTree(code[3]) # Condition.
if code[3][0] == CONSTANT:
# FOR is delicate. It can have multiple expressions at start.
# And if there is more than one, these expressions will need a
# new block, which means new scope, which is dangerous.
# They are expressions, no declarations or labels allowed, but
# it feels creepy.
if lslfuncs.cond(code[3][2]):
# Endless loop. Just replace the constant and traverse the rest.
code[3][1:2] = [S['integer'], 1]
for x in code[4]:
self.FoldTree(x)
self.FoldTree(5)
self.FoldStmt(5)
elif len(code[2]) > 1:
code[:] = [S['{}'], None, code[2]]
elif code[2]:
code[:] = code[2][0]
else:
code[:] = [S[';'], None]
else:
for x in code[4]:
self.FoldTree(x)
self.FoldTree(code[5])
self.FoldStmt(code[5])
return
if code0 == 'RETURN':
if code[2] is not None:
self.FoldTree(code[2])
if code0 == 'DECL':
# The expression code is elsewhere.
expr = self.symtab[code[3]][code[2]][2]
if expr is not None:
self.FoldTree(expr)
def Fold(self, code, IsGlobal = True):
assert type(code[2]) == tuple assert type(code[2]) == tuple
tree = list(code[2]) tree = list(code[2])
self.globalmode = IsGlobal self.globalmode = IsGlobal and len(code) == 3
self.FoldTree(tree) self.FoldTree(tree)
# As a special case, we fold the constants that are keys,
# because the folder
# TODO: Move this to a post-folding optimization.
# Reasons: (1) it doesn't optimize deep constants and
# (2) it disturbs normal folding if done on the fly.
# Mono optimization: (integer)-5 and (float)-3.0 is cheaper. # Mono optimization: (integer)-5 and (float)-3.0 is cheaper.
if not IsGlobal and tree[0] == 'CONSTANT': if not IsGlobal and tree[0] == 'CONSTANT':
if tree[1] == 'integer' and tree[2] < 0: # Disabled because we print integer constants in hex anyway.
tree[:] = [S['CAST'], 'integer', tree] #if tree[1] == 'integer' and tree[2] < 0:
elif tree[1] == 'float' and tree[2] < 0.0 and not math.isinf(tree[2]): # tree[:] = [S['CAST'], 'integer', tree]
if tree[1] == 'float' and tree[2] < 0.0 and not math.isinf(tree[2]):
tree[:] = [S['CAST'], 'float', tree] tree[:] = [S['CAST'], 'float', tree]
if type(code) == tuple: if type(code) == tuple:
@ -108,6 +294,8 @@ class optimizer(object):
assert False assert False
code[2] = tuple(tree) code[2] = tuple(tree)
del self.globalmode
def optimize(self, symtab, functions): def optimize(self, symtab, functions):
"""Optimize the symbolic table symtab in place. Uses a table of """Optimize the symbolic table symtab in place. Uses a table of
predefined functions for folding constants. predefined functions for folding constants.
@ -122,6 +310,6 @@ class optimizer(object):
entry = symtab[0][name] entry = symtab[0][name]
if entry[1] == 'State': if entry[1] == 'State':
for event in entry[2]: for event in entry[2]:
self.Fold(entry[2][event]) self.Fold(entry[2][event], False)
elif type(entry[2]) == tuple: elif type(entry[2]) == tuple:
self.Fold(entry, IsGlobal = True) # global self.Fold(entry) # global