diff --git a/lslopt/lsloptimizer.py b/lslopt/lsloptimizer.py index 1d1e420..934c90b 100644 --- a/lslopt/lsloptimizer.py +++ b/lslopt/lsloptimizer.py @@ -11,93 +11,279 @@ class optimizer(object): 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): - """Recursively traverse the tree to fold constants, changing the tree - in place. + """Recursively traverse the tree to fold constants, changing it in + place. + + Also optimizes away IF, WHILE, etc. """ while code[0] == 'EXPR': code[:] = code[2] - if code[0] == 'CAST': + code0 = code[0] + + if code0 == 'CAST': self.FoldTree(code[2]) 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])] return - if code[0] == '-': - self.FoldTree(code[3]) - if code[3][1] in ('integer', 'float'): # no gain otherwise - if code[3][0] == CONSTANT: - code[3][2] = -code[3][2] - 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 code0 == 'NEG': + self.FoldTree(code[2]) + if code[2][0] == CONSTANT: + code[:] = [CONSTANT, code[1], lslfuncs.neg(code[2][2])] + return - 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 self.FoldTree(code[3]) self.FoldTree(code[2]) 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 if self.globalmode: - if code[0] == 'IDENT': - if code[1] != 'key' and self.symtab[code[2]][2] is not None: + if code0 == 'IDENT': + if code[1] != 'key' and self.symtab[code[3]][code[2]][2] is not None: code[:] = [CONSTANT, code[1], self.symtab[code[2]][2]] return - if code[0] == 'FUNCTION': + if code0 == 'FUNCTION': for x in code[3][::-1]: 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]: if x[0] != CONSTANT: break else: - if code[2] in self.functions: - # Call it - val = self.functions[code[2]](tuple(x[2] for x in code[3])) - code[:] = [CONSTANT, code[1], val] + # Call it + val = self.functions[code[2]][2](*tuple(x[2] for x in code[3])) + code[:] = [CONSTANT, code[1], val] return - if code[0] == 'PRINT': + if code0 == 'PRINT': # useless but who knows self.FoldTree(code[2]) - - if code[0] == '{}': - for x in code[2:]: - self.FoldTree(x) return - if code[0] in ('VECTOR', 'ROTATION', 'LIST'): + if code0 in ('VECTOR', 'ROTATION', 'LIST'): for x in code[:1:-1]: self.FoldTree(x) - - 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]] + # TODO: Fold into constant if possible. 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 tree = list(code[2]) - self.globalmode = IsGlobal + self.globalmode = IsGlobal and len(code) == 3 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. if not IsGlobal and tree[0] == 'CONSTANT': - if tree[1] == 'integer' and tree[2] < 0: - tree[:] = [S['CAST'], 'integer', tree] - elif tree[1] == 'float' and tree[2] < 0.0 and not math.isinf(tree[2]): + # Disabled because we print integer constants in hex anyway. + #if tree[1] == 'integer' and tree[2] < 0: + # 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] if type(code) == tuple: @@ -108,6 +294,8 @@ class optimizer(object): assert False code[2] = tuple(tree) + del self.globalmode + def optimize(self, symtab, functions): """Optimize the symbolic table symtab in place. Uses a table of predefined functions for folding constants. @@ -122,6 +310,6 @@ class optimizer(object): entry = symtab[0][name] if entry[1] == 'State': for event in entry[2]: - self.Fold(entry[2][event]) + self.Fold(entry[2][event], False) elif type(entry[2]) == tuple: - self.Fold(entry, IsGlobal = True) # global + self.Fold(entry) # global