mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-01 15:48:21 +00:00
Move the constant folding code to lslfoldconst.py.
lsloptimizer remains as the "conductor" and option handler. As a result, new options have been added to enable DCR and constant folding.
This commit is contained in:
parent
beea757a0a
commit
8e5166bb2e
3 changed files with 22 additions and 852 deletions
|
@ -1,30 +1,8 @@
|
|||
|
||||
import lslfuncs
|
||||
from lslfuncs import Key, Vector, Quaternion
|
||||
from lslparse import warning
|
||||
|
||||
from lslrenamer import renamer
|
||||
from lsldeadcode import deadcode
|
||||
|
||||
class optimizer(renamer, deadcode):
|
||||
|
||||
# Default values per type when declaring variables
|
||||
DefaultValues = {'integer': 0, 'float': 0.0, 'string': u'',
|
||||
'key': lslfuncs.Key(u''), 'vector': lslfuncs.ZERO_VECTOR,
|
||||
'rotation': lslfuncs.ZERO_ROTATION, 'list': []
|
||||
}
|
||||
|
||||
# explicitly exclude assignments
|
||||
binary_ops = frozenset(('+','-','*','/','%','<<','>>','<','<=','>','>=',
|
||||
'==','!=','|','^','&','||','&&'))
|
||||
assign_ops = frozenset(('=','+=','-=','*=','/=','%=','&=','|=','^=','<<=','>>='))
|
||||
|
||||
LSL2PythonType = {'integer':int, 'float':float, 'string':unicode, 'key':lslfuncs.Key,
|
||||
'vector':lslfuncs.Vector, 'rotation':lslfuncs.Quaternion, 'list':list}
|
||||
|
||||
PythonType2LSL = {int: 'integer', float: 'float',
|
||||
unicode: 'string', Key: 'key', Vector: 'vector',
|
||||
Quaternion: 'rotation', list: 'list'}
|
||||
class foldconst(object):
|
||||
|
||||
def FoldAndRemoveEmptyStmts(self, lst):
|
||||
"""Utility function for elimination of useless expressions in FOR"""
|
||||
|
@ -77,27 +55,8 @@ class optimizer(renamer, deadcode):
|
|||
return # Nothing to do if it's already simplified.
|
||||
# TODO: Implement FoldCond
|
||||
|
||||
def Cast(self, value, newtype):
|
||||
# Return a CAST node if the types are not equal, otherwise the
|
||||
# value unchanged
|
||||
if value['t'] == newtype:
|
||||
return value
|
||||
if value not in ('CONST','()','FLD','IDENT','FNCALL','V++','V--',
|
||||
'VECTOR','ROTATION','LIST'):
|
||||
value = {'nt':'()', 't':newtype, 'ch':[value]}
|
||||
if 'SEF' in value['ch'][0]:
|
||||
value['SEF'] = True
|
||||
if 'X' in value['ch'][0]:
|
||||
value['X'] = value['ch'][0]['X']
|
||||
ret = {'nt':'CAST', 't':newtype, 'ch':[value]}
|
||||
if 'SEF' in value:
|
||||
ret['SEF'] = True
|
||||
if 'X' in value:
|
||||
ret['X'] = value['X']
|
||||
return ret
|
||||
|
||||
def CopyNode(self, node):
|
||||
# This is mainly for simple_expr so not a big deal.
|
||||
# This is mainly for simple_expr so no need to go deeper than 1 level.
|
||||
ret = node.copy()
|
||||
if 'ch' in ret:
|
||||
new = []
|
||||
|
@ -818,21 +777,14 @@ class optimizer(renamer, deadcode):
|
|||
return False
|
||||
return all(elem['nt'] in ('CONST', 'IDENT') for elem in expr['ch'])
|
||||
|
||||
def optimize(self, treesymtab, options = ('optimize',)):
|
||||
def FoldScript(self):
|
||||
"""Optimize the symbolic table symtab in place. Requires a table of
|
||||
predefined functions for folding constants.
|
||||
"""
|
||||
if 'optimize' not in options:
|
||||
return treesymtab
|
||||
|
||||
self.foldtabs = 'foldtabs' in options
|
||||
|
||||
self.shrinknames = 'shrinknames' in options
|
||||
|
||||
tree, symtab = self.tree, self.symtab = treesymtab
|
||||
|
||||
self.globalmode = False
|
||||
|
||||
tree = self.tree
|
||||
|
||||
# Constant folding pass. It does some other optimizations along the way.
|
||||
for idx in xrange(len(tree)):
|
||||
if tree[idx]['nt'] == 'DECL':
|
||||
|
@ -843,13 +795,3 @@ class optimizer(renamer, deadcode):
|
|||
warning('Expression does not resolve to a single constant.')
|
||||
else:
|
||||
self.FoldTree(tree, idx)
|
||||
|
||||
if self.shrinknames:
|
||||
self.ShrinkNames()
|
||||
|
||||
self.RemoveDeadCode()
|
||||
|
||||
treesymtab = (self.tree, self.symtab)
|
||||
del self.tree
|
||||
del self.symtab
|
||||
return treesymtab
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
|
||||
import lslfuncs
|
||||
from lslfuncs import Key, Vector, Quaternion
|
||||
from lslparse import warning
|
||||
|
||||
from lslfoldconst import foldconst
|
||||
from lslrenamer import renamer
|
||||
from lsldeadcode import deadcode
|
||||
|
||||
class optimizer(renamer, deadcode):
|
||||
class optimizer(foldconst, renamer, deadcode):
|
||||
|
||||
# Default values per type when declaring variables
|
||||
DefaultValues = {'integer': 0, 'float': 0.0, 'string': u'',
|
||||
|
@ -26,57 +26,6 @@ class optimizer(renamer, deadcode):
|
|||
unicode: 'string', Key: 'key', Vector: 'vector',
|
||||
Quaternion: 'rotation', list: 'list'}
|
||||
|
||||
def FoldAndRemoveEmptyStmts(self, lst):
|
||||
"""Utility function for elimination of useless expressions in FOR"""
|
||||
idx = 0
|
||||
while idx < len(lst):
|
||||
self.FoldTree(lst, idx)
|
||||
self.FoldStmt(lst, idx)
|
||||
# If eliminated, it must be totally removed. A ';' won't do.
|
||||
if lst[idx]['nt'] == ';':
|
||||
del lst[idx]
|
||||
else:
|
||||
idx += 1
|
||||
|
||||
def FoldStmt(self, parent, index):
|
||||
"""Simplify a statement."""
|
||||
node = parent[index]
|
||||
if node['nt'] == 'EXPR':
|
||||
node = node['ch'][0]
|
||||
# If the statement is side-effect-free, remove it as it does nothing.
|
||||
if 'SEF' in node:
|
||||
# Side-effect free means that a statement does nothing except
|
||||
# wasting CPU, and can thus be removed without affecting the
|
||||
# program. But side effect freedom is propagated from the
|
||||
# constituents of the statement, e.g. function calls in expressions
|
||||
# or substatements in FOR, or even individual variables.
|
||||
#
|
||||
# Many library functions like llSameGroup or llGetVel() are
|
||||
# side-effect free. Many other functions like llSleep() or
|
||||
# llSetScale() are not. User functions may or may not be.
|
||||
#
|
||||
# Assignments do have side effects, except those of the form x = x.
|
||||
# Pre- and post-increment and decrement also have side effects.
|
||||
# Type casts do not add side effects. Neither do binary operators.
|
||||
parent[index] = {'nt':';', 't':None, 'SEF': True}
|
||||
return
|
||||
# Post-increments take more space than pre-increments.
|
||||
if node['nt'] in ('V++', 'V--'):
|
||||
node['nt'] = '++V' if node['nt'] == 'V++' else '--V';
|
||||
|
||||
def FoldCond(self, parent, index):
|
||||
"""When we know that the parent is interested only in the truth value
|
||||
of the node, we can perform further optimizations. This function deals
|
||||
with them.
|
||||
"""
|
||||
node = parent[index]
|
||||
if node['nt'] in ('CONST', 'IDENT', 'FLD'):
|
||||
if node['nt'] == 'CONST':
|
||||
node['t'] = 'integer'
|
||||
node['value'] = -1 if lslfuncs.cond(node['value']) else 0
|
||||
return # Nothing to do if it's already simplified.
|
||||
# TODO: Implement FoldCond
|
||||
|
||||
def Cast(self, value, newtype):
|
||||
# Return a CAST node if the types are not equal, otherwise the
|
||||
# value unchanged
|
||||
|
@ -96,729 +45,7 @@ class optimizer(renamer, deadcode):
|
|||
ret['X'] = value['X']
|
||||
return ret
|
||||
|
||||
def CopyNode(self, node):
|
||||
# This is mainly for simple_expr so not a big deal.
|
||||
ret = node.copy()
|
||||
if 'ch' in ret:
|
||||
new = []
|
||||
for subnode in ret['ch']:
|
||||
new.append(self.CopyNode(subnode))
|
||||
ret['ch'] = new
|
||||
return ret
|
||||
|
||||
def FoldTree(self, parent, index):
|
||||
"""Recursively traverse the tree to fold constants, changing it in
|
||||
place.
|
||||
|
||||
Also optimizes away IF, WHILE, etc.
|
||||
"""
|
||||
node = parent[index]
|
||||
nt = node['nt']
|
||||
child = node['ch'] if 'ch' in node else None
|
||||
|
||||
if nt == 'CONST':
|
||||
# Job already done. But mark as side-effect free.
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == 'CAST':
|
||||
self.FoldTree(child, 0)
|
||||
if 'SEF' in child[0]:
|
||||
node['SEF'] = True
|
||||
if child[0]['nt'] == 'CONST':
|
||||
# Enable key constants. We'll typecast them back on output, but
|
||||
# this enables some optimizations.
|
||||
#if node['t'] != 'key': # key constants not possible
|
||||
|
||||
parent[index] = {'nt':'CONST', 't':node['t'], 'SEF':True,
|
||||
'value':lslfuncs.typecast(
|
||||
child[0]['value'], self.LSL2PythonType[node['t']])}
|
||||
return
|
||||
|
||||
if nt == 'NEG':
|
||||
self.FoldTree(child, 0)
|
||||
while child[0]['nt'] == '()' and child[0]['ch'][0]['nt'] == 'NEG':
|
||||
# Remove parentheses: - ( - expr ) --> - - expr
|
||||
child[0] = child[0]['ch'][0]
|
||||
if child[0]['nt'] == 'NEG':
|
||||
# Double negation: - - expr --> expr
|
||||
# NOTE: Not 100% sure this doesn't need parentheses around expr.
|
||||
parent[index] = child[0]['ch'][0]
|
||||
elif child[0]['nt'] == 'CONST':
|
||||
node = parent[index] = child[0]
|
||||
node['value'] = lslfuncs.neg(node['value'])
|
||||
elif 'SEF' in child[0]:
|
||||
# propagate Side Effect Free flag
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == '!':
|
||||
self.FoldTree(child, 0)
|
||||
self.FoldCond(child, 0)
|
||||
# !! does *not* cancel out, but !!! can be simplified to !
|
||||
subexpr = child[0]
|
||||
if 'SEF' in subexpr:
|
||||
node['SEF'] = True
|
||||
while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()', '~', '!', '++V', '--V'):
|
||||
subexpr = child[0] = subexpr['ch'][0] # Remove parentheses
|
||||
if subexpr['nt'] == '!' and subexpr['ch'][0]['nt'] == '!':
|
||||
# Simplify !!! to !
|
||||
subexpr = child[0] = subexpr['ch'][0]['ch'][0]
|
||||
if subexpr['nt'] == 'CONST':
|
||||
node = parent[index] = subexpr
|
||||
node['value'] = int(not node['value'])
|
||||
return
|
||||
|
||||
if nt == '~':
|
||||
self.FoldTree(child, 0)
|
||||
subexpr = child[0]
|
||||
if 'SEF' in subexpr:
|
||||
node['SEF'] = True
|
||||
while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()',
|
||||
'~', '!', '++V', '--V'):
|
||||
subexpr = child[0] = subexpr['ch'][0] # Remove parentheses
|
||||
if subexpr['nt'] == '~':
|
||||
# Double negation: ~~expr
|
||||
parent[index] = subexpr['ch'][0]
|
||||
elif subexpr['nt'] == 'CONST':
|
||||
node = parent[index] = child[0]
|
||||
node['value'] = ~node['value']
|
||||
return
|
||||
|
||||
if nt == '()':
|
||||
self.FoldTree(child, 0)
|
||||
if 'SEF' in child[0]:
|
||||
node['SEF'] = True
|
||||
if child[0]['nt'] in ('()', 'CONST', 'VECTOR', 'ROTATION', 'LIST',
|
||||
'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'):
|
||||
# Child is an unary postfix expression (highest priority);
|
||||
# parentheses are redundant and can be removed safely. Not
|
||||
# strictly an optimization but it helps keeping the output
|
||||
# tidy-ish a bit. It's not done in general (e.g. (a * b) + c
|
||||
# does not need parentheses but these are not eliminated). Only
|
||||
# cases like (3) or (myvar++) are simplified.
|
||||
parent[index] = child[0]
|
||||
return
|
||||
|
||||
if nt in self.binary_ops:
|
||||
# RTL evaluation
|
||||
self.FoldTree(child, 1)
|
||||
self.FoldTree(child, 0)
|
||||
if 'SEF' in child[0] and 'SEF' in child[1]:
|
||||
# Propagate SEF flag if both sides are side-effect free.
|
||||
node['SEF'] = True
|
||||
if child[0]['nt'] == child[1]['nt'] == 'CONST':
|
||||
op1 = child[0]['value']
|
||||
op2 = child[1]['value']
|
||||
if nt == '+':
|
||||
result = lslfuncs.add(op1, op2)
|
||||
elif nt == '-':
|
||||
result = lslfuncs.sub(op1, op2)
|
||||
elif nt == '*':
|
||||
result = lslfuncs.mul(op1, op2)
|
||||
elif nt == '/':
|
||||
result = lslfuncs.div(op1, op2)
|
||||
elif nt == '%':
|
||||
result = lslfuncs.mod(op1, op2)
|
||||
elif nt == '<<':
|
||||
result = lslfuncs.S32(op1 << (op2 & 31))
|
||||
elif nt == '>>':
|
||||
result = lslfuncs.S32(op1 >> (op2 & 31))
|
||||
elif nt == '==' or nt == '!=':
|
||||
result = lslfuncs.compare(op1, op2, Eq = (nt == '=='))
|
||||
elif nt in ('<', '<=', '>', '>='):
|
||||
if nt in ('>', '<='):
|
||||
result = lslfuncs.less(op2, op1)
|
||||
else:
|
||||
result = lslfuncs.less(op1, op2)
|
||||
if nt in ('>=', '<='):
|
||||
result = 1-result
|
||||
elif nt == '|':
|
||||
result = op1 | op2
|
||||
elif nt == '^':
|
||||
result = op1 ^ op2
|
||||
elif nt == '&':
|
||||
result = op1 & op2
|
||||
elif nt == '||':
|
||||
result = int(bool(op1) or bool(op2))
|
||||
elif nt == '&&':
|
||||
result = int(bool(op1) and bool(op2))
|
||||
else:
|
||||
assert False, 'Internal error: Operator not found: ' + nt # pragma: no cover
|
||||
parent[index] = {'nt':'CONST', 't':node['t'], 'SEF':True, 'value':result}
|
||||
return
|
||||
|
||||
# Simplifications for particular operands
|
||||
optype = node['t']
|
||||
lval = child[0]
|
||||
ltype = lval['t']
|
||||
lnt = lval['nt']
|
||||
rval = child[1]
|
||||
rtype = rval['t']
|
||||
rnt = rval['nt']
|
||||
if nt == '-':
|
||||
if optype in ('vector', 'rotation'):
|
||||
if lnt == 'CONST' and all(component == 0 for component in lval['value']):
|
||||
# Change <0,0,0[,0]>-expr -> -expr
|
||||
parent[index] = {'nt':'NEG', 't':node['t'], 'ch':[rval]}
|
||||
if 'SEF' in rval:
|
||||
parent[index]['SEF'] = True
|
||||
elif rnt == 'CONST' and all(component == 0 for component in rval['value']):
|
||||
# Change expr-<0,0,0[,0]> -> expr
|
||||
parent[index] = lval
|
||||
return
|
||||
|
||||
# Change - to + - for int/float
|
||||
nt = node['nt'] = '+'
|
||||
if child[1]['nt'] == 'CONST':
|
||||
rval['value'] = lslfuncs.neg(rval['value'])
|
||||
else:
|
||||
rnt = 'NEG'
|
||||
RSEF = 'SEF' in rval
|
||||
rval = child[1] = {'nt':rnt, 't':rval['t'], 'ch':[rval]}
|
||||
if RSEF:
|
||||
rval['SEF'] = True
|
||||
# rtype unchanged
|
||||
|
||||
# Fall through to simplify it as '+'
|
||||
|
||||
if nt == '+':
|
||||
# Tough one. Remove neutral elements for the diverse types,
|
||||
# and more.
|
||||
if optype == 'list' and not (ltype == rtype == 'list'):
|
||||
# Nothing to do with list + nonlist or nonlist + list.
|
||||
# FIXME: Not true. (list)"string" is a 5 byte saving vs.
|
||||
# [] + "string". Activating explicitcast forces the
|
||||
# conversion [] + (list)"string" -> (list)"string" which
|
||||
# is what we want here, but it is a loss for other types.
|
||||
# Further analysis needed.
|
||||
return
|
||||
|
||||
if optype in ('vector', 'rotation'):
|
||||
# not much to do with vectors or quaternions either
|
||||
if lnt == 'CONST' and all(component == 0 for component in lval['value']):
|
||||
# Change <0,0,0[,0]>+expr -> expr
|
||||
parent[index] = rval
|
||||
elif rnt == 'CONST' and all(component == 0 for component in rval['value']):
|
||||
# Change expr+<0,0,0[,0]> -> expr
|
||||
parent[index] = lval
|
||||
return
|
||||
|
||||
# Can't be key, as no combo of addition operands returns key
|
||||
# All these types evaluate as boolean False when they are
|
||||
# the neutral addition element.
|
||||
if optype in ('string', 'float', 'list'):
|
||||
if lnt == 'CONST' and not lval['value']:
|
||||
# 0 + expr -> expr
|
||||
# "" + expr -> expr
|
||||
# [] + expr -> expr
|
||||
parent[index] = self.Cast(rval, optype)
|
||||
elif rnt == 'CONST' and not rval['value']:
|
||||
# expr + 0 -> expr
|
||||
# expr + "" -> expr
|
||||
# expr + [] -> expr
|
||||
parent[index] = self.Cast(lval, optype)
|
||||
return
|
||||
|
||||
# Must be two integers. This allows for a number of
|
||||
# optimizations. First the most obvious ones.
|
||||
|
||||
if lnt == 'CONST' and lval['value'] == 0:
|
||||
parent[index] = rval
|
||||
return
|
||||
|
||||
if rnt == 'CONST' and rval['value'] == 0:
|
||||
parent[index] = lval
|
||||
return
|
||||
|
||||
# Remove parentheses if they enclose a NEG, to unhide their
|
||||
# operators. Precedence rules allow us.
|
||||
if lnt == '()' and lval['ch'][0]['nt'] == 'NEG':
|
||||
# (-expr) + expr -> -expr + expr
|
||||
lval = child[0] = lval['ch'][0]
|
||||
if rnt == '()' and rval['ch'][0]['nt'] == 'NEG':
|
||||
# expr + (-expr) -> expr + -expr
|
||||
rval = child[1] = rval['ch'][0]
|
||||
|
||||
if lnt != 'CONST' != rnt:
|
||||
# Neither is const. Two chances to optimize.
|
||||
# 1. -expr + -expr -> -(expr + expr) (saves 1 byte)
|
||||
# 2. lvalue + -lvalue -> 0
|
||||
# There may be other possibilities for optimization,
|
||||
# e.g. (type)ident + -(type)ident but we only do lvalues
|
||||
# here. Note these are integers, no NaN involved.
|
||||
# TODO: Compare the subtrees if they are SEF. If they are
|
||||
# the same subtree, they can cancel out.
|
||||
if lnt == rnt == 'NEG':
|
||||
node = {'nt':'+', 't':optype, 'ch':[lval['ch'][0], rval['ch'][0]]}
|
||||
SEF = 'SEF' in lval['ch'][0] and 'SEF' in rval['ch'][0]
|
||||
if SEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'()', 't':optype, 'ch':[node]}
|
||||
if SEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'NEG', 't':optype, 'ch':[node]}
|
||||
if SEF:
|
||||
node['SEF'] = True
|
||||
parent[index] = node
|
||||
return
|
||||
|
||||
if lnt == 'NEG':
|
||||
# Swap to treat always as expr + -expr for simplicity.
|
||||
lnt, lval, rnt, rval = rnt, rval, lnt, lval
|
||||
if lnt == 'IDENT' and rnt == 'NEG' and rval['ch'][0]['nt'] == 'IDENT' \
|
||||
and lval['name'] == rval['ch'][0]['name']:
|
||||
# Replace with 0
|
||||
parent[index] = {'nt':'CONST', 'SEF': True, 't':optype, 'value':0}
|
||||
|
||||
return
|
||||
|
||||
if rnt == 'CONST':
|
||||
# Swap the vars to deal with const in lval always
|
||||
lval, lnt, rval, rnt = rval, rnt, lval, lnt
|
||||
RSEF = 'SEF' in rval
|
||||
if lval['value'] == -1:
|
||||
if rnt == 'NEG':
|
||||
node = {'nt':'~', 't':optype, 'ch':rval['ch']}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
else:
|
||||
node = {'nt':'NEG', 't':optype, 'ch':[rval]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'~', 't':optype, 'ch':[node]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
parent[index] = node
|
||||
return
|
||||
|
||||
if lval['value'] == -2:
|
||||
if rnt == 'NEG': # Cancel the NEG
|
||||
node = {'nt':'~', 't':optype, 'ch':rval['ch']}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'NEG', 't':optype, 'ch':[node]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'~', 't':optype, 'ch':[node]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
else: # Add the NEG
|
||||
node = {'nt':'NEG', 't':optype, 'ch':[rval]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'~', 't':optype, 'ch':[node]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'NEG', 't':optype, 'ch':[node]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
node = {'nt':'~', 't':optype, 'ch':[node]}
|
||||
if RSEF:
|
||||
node['SEF'] = True
|
||||
parent[index] = node
|
||||
return
|
||||
|
||||
if lval['value'] == 1:
|
||||
parent[index] = node = {'nt':'NEG', 't':optype,
|
||||
'ch':[{'nt':'~', 't':optype, 'ch':[rval]}]}
|
||||
if RSEF:
|
||||
node['ch'][0]['SEF'] = True
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if lval['value'] == 2:
|
||||
node = {'nt':'NEG', 't':optype,
|
||||
'ch':[{'nt':'~', 't':optype, 'ch':[rval]}]}
|
||||
if RSEF:
|
||||
node['ch'][0]['SEF'] = True
|
||||
node['SEF'] = True
|
||||
parent[index] = node = {'nt':'NEG', 't':optype,
|
||||
'ch':[{'nt':'~', 't':optype, 'ch':[node]}]}
|
||||
if RSEF:
|
||||
node['ch'][0]['SEF'] = True
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
# More than 2 becomes counter-productive.
|
||||
|
||||
return
|
||||
|
||||
elif nt == '<<' and child[1]['nt'] == 'CONST':
|
||||
# Transforming << into multiply saves some bytes.
|
||||
if child[1]['value'] & 31:
|
||||
# x << 3 --> x * 8
|
||||
# Do we need parentheses for *? It depends on x
|
||||
# e.g. x+3<<3 needs parentheses when converted to (x+3)*8
|
||||
# We can have {<< {<< x y} 3} -> (x << y) * 8 but we can't
|
||||
# have e.g. {<< {& x y} 3}; there will be explicit
|
||||
# parentheses here always, so we don't need to worry.
|
||||
|
||||
# Operands with priority between * (not included) and <<
|
||||
# (included).
|
||||
if child[0]['nt'] in ('+', '-', 'NEG', '<<', '>>'):
|
||||
SEF = 'SEF' in child[0]
|
||||
child[0] = {'nt':'()', 't':child[0]['t'], 'ch':[child[0]]}
|
||||
if SEF:
|
||||
child[0]['SEF'] = True
|
||||
# we have {<<, something, {CONST n}}, transform into {*, something, {CONST n}}
|
||||
node['nt'] = '*'
|
||||
child[1]['value'] = 1 << (child[1]['value'] & 31)
|
||||
else: # x << 0 --> x
|
||||
parent[index] = child[0]
|
||||
else:
|
||||
pass # TODO: Eliminate redundancy (x*1, x*-1, x|0, x&-1, etc.)
|
||||
# Include != to ^ and || to | and maybe && to &
|
||||
# Note some cases e.g. x*0 can't be optimized away without side-effect analysis.
|
||||
# But some cases like %1 can be turned into *0 to save bytes.
|
||||
# Turn also % (power of 2) into & mask (oops, nope, negative doesn't work)
|
||||
# Maybe turn != -1 into ~ in if()'s.
|
||||
return
|
||||
|
||||
if nt in self.assign_ops:
|
||||
# Transform the whole thing into a regular assignment, as there are
|
||||
# no gains and it simplifies the optimization.
|
||||
|
||||
# An assignment has no side effects only if it's of the form x = x.
|
||||
|
||||
if nt != '=':
|
||||
# Replace the node with the expression alone
|
||||
child[1] = {'nt':'()', 't':child[1]['t'], 'ch':[child[1]]}
|
||||
node['nt'] = nt[:-1]
|
||||
|
||||
# Linden Craziness: i *= f; is valid (but no other i op= f is).
|
||||
# It's actually performed as i = (integer)(i + (f)). This breaks
|
||||
# regular equivalence of x op= y as x = x op (y) so we add
|
||||
# the type cast here.
|
||||
if nt == '*=' and child[0]['t'] == 'integer' and child[1]['t'] == 'float':
|
||||
node['t'] = 'float' # Addition shall return float.
|
||||
node = self.Cast(node, 'integer')
|
||||
|
||||
# And wrap it in an assignment.
|
||||
child = [child[0].copy(), node]
|
||||
node = parent[index] = {'nt':'=', 't':child[0]['t'], 'ch':child}
|
||||
|
||||
# We have a regular assignment either way now. Simplify the RHS.
|
||||
self.FoldTree(node['ch'], 1)
|
||||
if child[0]['nt'] == child[1]['nt'] == 'IDENT' \
|
||||
and child[1]['name'] == child[0]['name'] \
|
||||
and child[1]['scope'] == child[0]['scope'] \
|
||||
or child[0]['nt'] == child[1]['nt'] == 'FLD' \
|
||||
and child[1]['ch'][0]['name'] == child[0]['ch'][0]['name'] \
|
||||
and child[1]['ch'][0]['scope'] == child[0]['ch'][0]['scope'] \
|
||||
and child[1]['fld'] == child[0]['fld']:
|
||||
node['SEF'] = True
|
||||
self.FoldStmt(parent, index)
|
||||
return
|
||||
|
||||
if nt == 'IDENT' or nt == 'FLD':
|
||||
node['SEF'] = True
|
||||
if self.globalmode:
|
||||
ident = child[0] if nt == 'FLD' else node
|
||||
# Resolve constant values so they can be optimized
|
||||
sym = self.symtab[ident['scope']][ident['name']]
|
||||
|
||||
defn = self.tree[sym['Loc']]
|
||||
assert defn['name'] == ident['name']
|
||||
|
||||
# Assume we already were there
|
||||
if 'ch' in defn:
|
||||
val = defn['ch'][0]
|
||||
if val['nt'] != 'CONST' or ident['t'] == 'key':
|
||||
return
|
||||
val = val.copy()
|
||||
else:
|
||||
val = {'nt':'CONST', 't':defn['t'],
|
||||
'value':self.DefaultValues[defn['t']]}
|
||||
if nt == 'FLD':
|
||||
val = {'nt':'CONST', 't':'float',
|
||||
'value':val['value']['xyzs'.index(node['fld'])]}
|
||||
parent[index] = val
|
||||
return
|
||||
|
||||
if nt == 'FNCALL':
|
||||
SEFargs = True
|
||||
CONSTargs = True
|
||||
for idx in xrange(len(child)-1, -1, -1):
|
||||
self.FoldTree(child, idx)
|
||||
# Function is not SEF if any argument is not SEF
|
||||
if 'SEF' not in child[idx]:
|
||||
SEFargs = False
|
||||
# Function is not a constant if any argument is not a constant
|
||||
if child[idx]['nt'] != 'CONST':
|
||||
CONSTargs = False
|
||||
|
||||
if 'Fn' in self.symtab[0][node['name']]:
|
||||
# Guaranteed to be side-effect free if the children are.
|
||||
if SEFargs:
|
||||
node['SEF'] = True
|
||||
if CONSTargs:
|
||||
# Call it
|
||||
fn = self.symtab[0][node['name']]['Fn']
|
||||
value = fn(*tuple(arg['value'] for arg in child))
|
||||
if not self.foldtabs and isinstance(value, unicode) and '\t' in value:
|
||||
warning('Tab in function result and foldtabs option not used.')
|
||||
return
|
||||
parent[index] = {'nt':'CONST', 't':node['t'], 'value':value}
|
||||
elif node['name'] == 'llGetListLength' and child[0]['nt'] == 'IDENT':
|
||||
# Convert llGetListLength(ident) to (ident != [])
|
||||
node = {'nt':'CONST', 't':'list', 'value':[]}
|
||||
node = {'nt':'!=', 't':'list', 'ch':[child[0], node]}
|
||||
parent[index] = {'nt':'()', 't':'list', 'ch':[node]}
|
||||
elif SEFargs and 'SEF' in self.symtab[0][node['name']]:
|
||||
# The function is marked as SEF in the symbol table, and the
|
||||
# arguments are all side-effect-free. The result is SEF.
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == 'PRINT':
|
||||
self.FoldTree(child, 0)
|
||||
# PRINT is considered to have side effects. If it's there, assume
|
||||
# there's a reason.
|
||||
return
|
||||
|
||||
if nt == 'EXPR':
|
||||
self.FoldTree(child, 0)
|
||||
if 'SEF' in child[0]:
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == 'FNDEF':
|
||||
self.FoldTree(child, 0)
|
||||
if 'SEF' in child[0]:
|
||||
node['SEF'] = True
|
||||
if node['name'] in self.symtab[0]:
|
||||
# Mark the symbol table entry if it's not an event.
|
||||
self.symtab[0][node['name']]['SEF'] = True
|
||||
return
|
||||
|
||||
if nt in ('VECTOR', 'ROTATION', 'LIST'):
|
||||
isconst = True
|
||||
issef = True
|
||||
for idx in xrange(len(child)-1, -1, -1):
|
||||
self.FoldTree(child, idx)
|
||||
if child[idx]['nt'] != 'CONST':
|
||||
isconst = False
|
||||
if 'SEF' not in child[idx]:
|
||||
issef = False
|
||||
if isconst:
|
||||
value = [elem['value'] for elem in child]
|
||||
if nt == 'VECTOR':
|
||||
value = lslfuncs.Vector([lslfuncs.ff(x) for x in value])
|
||||
elif nt == 'ROTATION':
|
||||
value = lslfuncs.Quaternion([lslfuncs.ff(x) for x in value])
|
||||
parent[index] = {'nt':'CONST', 'SEF':True, 't':node['t'],
|
||||
'value':value}
|
||||
return
|
||||
if issef:
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == 'STDEF':
|
||||
for idx in xrange(len(child)):
|
||||
self.FoldTree(child, idx)
|
||||
return
|
||||
|
||||
if nt == '{}':
|
||||
idx = 0
|
||||
issef = True
|
||||
while idx < len(child):
|
||||
self.FoldTree(child, idx)
|
||||
self.FoldStmt(child, idx)
|
||||
if 'SEF' not in child[idx]:
|
||||
issef = False
|
||||
if child[idx]['nt'] == ';' \
|
||||
or nt == '{}' and child[idx]['nt'] == '{}' and not child[idx]['ch']:
|
||||
del child[idx]
|
||||
else:
|
||||
if 'StSw' in child[idx]:
|
||||
node['StSw'] = True
|
||||
idx += 1
|
||||
if issef:
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == 'IF':
|
||||
self.FoldTree(child, 0)
|
||||
self.FoldCond(child, 0)
|
||||
if child[0]['nt'] == 'CONST':
|
||||
# We might be able to remove one of the branches.
|
||||
if child[0]['value']:
|
||||
self.FoldTree(child, 1)
|
||||
# If it has a state switch, the if() must be preserved
|
||||
# (but the else branch may be removed).
|
||||
if 'StSw' in child[1]:
|
||||
# TODO: Get rid of StSw craziness and make another pass
|
||||
# to put them under conditionals if present (if bald
|
||||
# state switches are present, it means they are the
|
||||
# result of optimization so they must be wrapped in an
|
||||
# IF statement). The current approach leaves unnecessary
|
||||
# IFs behind.
|
||||
if len(child) == 3:
|
||||
del child[2] # Delete ELSE if present
|
||||
return
|
||||
else:
|
||||
self.FoldStmt(child, 1)
|
||||
parent[index] = child[1]
|
||||
return
|
||||
elif len(child) == 3:
|
||||
self.FoldTree(child, 2)
|
||||
self.FoldStmt(child, 2)
|
||||
parent[index] = child[2]
|
||||
return
|
||||
else:
|
||||
# No ELSE branch, replace the statement with an empty one.
|
||||
parent[index] = {'nt':';', 't':None, 'SEF':True}
|
||||
return
|
||||
else:
|
||||
self.FoldTree(child, 1)
|
||||
self.FoldStmt(child, 1)
|
||||
if len(child) > 2:
|
||||
self.FoldTree(child, 2)
|
||||
self.FoldStmt(child, 2)
|
||||
if child[2]['nt'] == ';' \
|
||||
or child[2]['nt'] == '{}' and not child[2]['ch']:
|
||||
# no point in "... else ;" - remove else branch
|
||||
del child[2]
|
||||
if all('SEF' in subnode for subnode in child):
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt == 'WHILE':
|
||||
# Loops are not considered side-effect free. If the expression is
|
||||
# TRUE, it's definitely not SEF. If it's FALSE, it will be optimized
|
||||
# anyway. Otherwise we just don't know if it may be infinite, even
|
||||
# if every component is SEF.
|
||||
|
||||
self.FoldTree(child, 0)
|
||||
self.FoldCond(child, 0)
|
||||
if child[0]['nt'] == 'CONST':
|
||||
# See if the whole WHILE can be eliminated.
|
||||
if child[0]['value']:
|
||||
# Endless loop which must be kept.
|
||||
# Recurse on the statement.
|
||||
self.FoldTree(child, 1)
|
||||
self.FoldStmt(child, 1)
|
||||
else:
|
||||
# Can be removed.
|
||||
parent[index] = {'nt':';', 't':None, 'SEF':True}
|
||||
return
|
||||
else:
|
||||
self.FoldTree(child, 1)
|
||||
self.FoldStmt(child, 1)
|
||||
return
|
||||
|
||||
if nt == 'DO':
|
||||
self.FoldTree(child, 0) # This one is always executed.
|
||||
self.FoldStmt(child, 0)
|
||||
self.FoldTree(child, 1)
|
||||
self.FoldCond(child, 1)
|
||||
# See if the latest part is a constant.
|
||||
if child[1]['nt'] == 'CONST':
|
||||
if not child[1]['value']:
|
||||
# Only one go. Replace with the statement(s).
|
||||
parent[index] = child[0]
|
||||
return
|
||||
|
||||
if nt == 'FOR':
|
||||
assert child[0]['nt'] == 'EXPRLIST'
|
||||
assert child[2]['nt'] == 'EXPRLIST'
|
||||
self.FoldAndRemoveEmptyStmts(child[0]['ch'])
|
||||
|
||||
self.FoldTree(child, 1) # Condition.
|
||||
self.FoldCond(child, 1)
|
||||
if child[1]['nt'] == 'CONST':
|
||||
# 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, thus
|
||||
# no new identifiers, but it still feels uneasy.
|
||||
if child[1]['value']:
|
||||
# Endless loop. Traverse the loop and the iterator.
|
||||
self.FoldTree(child, 3)
|
||||
self.FoldStmt(child, 3)
|
||||
self.FoldAndRemoveEmptyStmts(child[2]['ch'])
|
||||
else:
|
||||
# Convert expression list to code block.
|
||||
exprlist = []
|
||||
for expr in child[0]['ch']:
|
||||
# Fold into expression statements.
|
||||
exprlist.append({'nt':'EXPR', 't':expr['t'], 'ch':[expr]})
|
||||
# returns type None, as FOR does
|
||||
if exprlist:
|
||||
# We're in the case where there are expressions. If any
|
||||
# remain, they are not SEF (or they would have been
|
||||
# removed earlier) so don't mark this node as SEF.
|
||||
parent[index] = {'nt':'{}', 't':None, 'ch':exprlist}
|
||||
else:
|
||||
parent[index] = {'nt':';', 't':None, 'SEF': True}
|
||||
return
|
||||
else:
|
||||
self.FoldTree(child, 3)
|
||||
self.FoldStmt(child, 3)
|
||||
self.FoldAndRemoveEmptyStmts(child[2]['ch'])
|
||||
return
|
||||
|
||||
if nt == 'RETURN':
|
||||
if child:
|
||||
self.FoldTree(child, 0)
|
||||
return
|
||||
|
||||
if nt == 'DECL':
|
||||
if child:
|
||||
# Check if child is a simple_expr. If it is, then we keep the
|
||||
# original attached to the folded node to use it in the output.
|
||||
if child[0].pop('Simple', False):
|
||||
orig = self.CopyNode(child[0])
|
||||
self.FoldTree(child, 0)
|
||||
child[0]['orig'] = orig
|
||||
else:
|
||||
self.FoldTree(child, 0)
|
||||
# Remove assignment if integer zero.
|
||||
if node['t'] == 'integer' and child[0]['nt'] == 'CONST' \
|
||||
and not child[0]['value']:
|
||||
del node['ch']
|
||||
child = None
|
||||
return
|
||||
else:
|
||||
# Add assignment if vector, rotation or float.
|
||||
if node['t'] in ('float', 'vector', 'rotation'):
|
||||
typ = node['t']
|
||||
node['ch'] = [{'nt':'CONST', 't':typ, 'SEF': True,
|
||||
'value': 0.0 if typ == 'float' else
|
||||
lslfuncs.ZERO_VECTOR if typ == 'vector' else
|
||||
lslfuncs.ZERO_ROTATION}]
|
||||
# Declarations always have side effects.
|
||||
return
|
||||
|
||||
if nt == 'STSW':
|
||||
# State switch always has side effects.
|
||||
node['StSw'] = True
|
||||
return
|
||||
|
||||
if nt == ';':
|
||||
node['SEF'] = True
|
||||
return
|
||||
|
||||
if nt in ('JUMP', '@', 'V++', 'V--', '--V', '++V'):
|
||||
# These all have side effects, as in, can't be eliminated as
|
||||
# statements.
|
||||
return
|
||||
|
||||
assert False, 'Internal error: This should not happen, node type = ' \
|
||||
+ nt # pragma: no cover
|
||||
|
||||
def IsValidGlobalConstant(self, decl):
|
||||
if 'ch' not in decl:
|
||||
return True
|
||||
expr = decl['ch'][0]
|
||||
if expr['nt'] in ('CONST', 'IDENT'):
|
||||
return True
|
||||
if expr['nt'] not in ('VECTOR', 'ROTATION', 'LIST'):
|
||||
return False
|
||||
return all(elem['nt'] in ('CONST', 'IDENT') for elem in expr['ch'])
|
||||
|
||||
def optimize(self, treesymtab, options = ('optimize',)):
|
||||
def optimize(self, treesymtab, options = ('optimize','constfold','dcr')):
|
||||
"""Optimize the symbolic table symtab in place. Requires a table of
|
||||
predefined functions for folding constants.
|
||||
"""
|
||||
|
@ -829,26 +56,27 @@ class optimizer(renamer, deadcode):
|
|||
|
||||
self.shrinknames = 'shrinknames' in options
|
||||
|
||||
self.constfold = 'constfold' in options
|
||||
self.dcr = 'dcr' in options
|
||||
|
||||
tree, symtab = self.tree, self.symtab = treesymtab
|
||||
|
||||
self.globalmode = False
|
||||
|
||||
# Constant folding pass. It does some other optimizations along the way.
|
||||
for idx in xrange(len(tree)):
|
||||
if tree[idx]['nt'] == 'DECL':
|
||||
self.globalmode = True
|
||||
self.FoldTree(tree, idx)
|
||||
self.globalmode = False
|
||||
if not self.IsValidGlobalConstant(tree[idx]):
|
||||
warning('Expression does not resolve to a single constant.')
|
||||
else:
|
||||
self.FoldTree(tree, idx)
|
||||
if self.constfold:
|
||||
self.FoldScript()
|
||||
|
||||
if self.dcr:
|
||||
self.RemoveDeadCode()
|
||||
|
||||
# Make another fold pass, since RemoveDeadCode can embed expressions
|
||||
# into other expressions and generate unoptimized code.
|
||||
if self.constfold:
|
||||
self.FoldScript()
|
||||
|
||||
if self.shrinknames:
|
||||
self.ShrinkNames()
|
||||
|
||||
self.RemoveDeadCode()
|
||||
|
||||
treesymtab = (self.tree, self.symtab)
|
||||
del self.tree
|
||||
del self.symtab
|
||||
|
|
|
@ -382,7 +382,7 @@ class Test03_Optimizer(UnitTestCase):
|
|||
f(integer a, integer b, integer c, integer d, integer e){}
|
||||
default{timer(){}}
|
||||
''')
|
||||
self.opt.optimize(p, ['optimize','shrinknames'])
|
||||
self.opt.optimize(p, ['optimize','shrinknames','dcr','constfold'])
|
||||
out = self.outscript.output(p)
|
||||
self.assertEqual(out, 'default\n{\n timer()\n {\n }\n}\n')
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue