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:
Sei Lisa 2014-08-13 14:19:58 +02:00
parent beea757a0a
commit 8e5166bb2e
3 changed files with 22 additions and 852 deletions

View file

@ -1,30 +1,8 @@
import lslfuncs import lslfuncs
from lslfuncs import Key, Vector, Quaternion
from lslparse import warning from lslparse import warning
from lslrenamer import renamer class foldconst(object):
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'}
def FoldAndRemoveEmptyStmts(self, lst): def FoldAndRemoveEmptyStmts(self, lst):
"""Utility function for elimination of useless expressions in FOR""" """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. return # Nothing to do if it's already simplified.
# TODO: Implement FoldCond # 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): 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() ret = node.copy()
if 'ch' in ret: if 'ch' in ret:
new = [] new = []
@ -818,21 +777,14 @@ class optimizer(renamer, deadcode):
return False return False
return all(elem['nt'] in ('CONST', 'IDENT') for elem in expr['ch']) 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 """Optimize the symbolic table symtab in place. Requires a table of
predefined functions for folding constants. 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 self.globalmode = False
tree = self.tree
# Constant folding pass. It does some other optimizations along the way. # Constant folding pass. It does some other optimizations along the way.
for idx in xrange(len(tree)): for idx in xrange(len(tree)):
if tree[idx]['nt'] == 'DECL': if tree[idx]['nt'] == 'DECL':
@ -843,13 +795,3 @@ class optimizer(renamer, deadcode):
warning('Expression does not resolve to a single constant.') warning('Expression does not resolve to a single constant.')
else: else:
self.FoldTree(tree, idx) self.FoldTree(tree, idx)
if self.shrinknames:
self.ShrinkNames()
self.RemoveDeadCode()
treesymtab = (self.tree, self.symtab)
del self.tree
del self.symtab
return treesymtab

View file

@ -1,12 +1,12 @@
import lslfuncs import lslfuncs
from lslfuncs import Key, Vector, Quaternion from lslfuncs import Key, Vector, Quaternion
from lslparse import warning
from lslfoldconst import foldconst
from lslrenamer import renamer from lslrenamer import renamer
from lsldeadcode import deadcode from lsldeadcode import deadcode
class optimizer(renamer, deadcode): class optimizer(foldconst, renamer, deadcode):
# Default values per type when declaring variables # Default values per type when declaring variables
DefaultValues = {'integer': 0, 'float': 0.0, 'string': u'', DefaultValues = {'integer': 0, 'float': 0.0, 'string': u'',
@ -26,57 +26,6 @@ class optimizer(renamer, deadcode):
unicode: 'string', Key: 'key', Vector: 'vector', unicode: 'string', Key: 'key', Vector: 'vector',
Quaternion: 'rotation', list: 'list'} 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): def Cast(self, value, newtype):
# Return a CAST node if the types are not equal, otherwise the # Return a CAST node if the types are not equal, otherwise the
# value unchanged # value unchanged
@ -96,729 +45,7 @@ class optimizer(renamer, deadcode):
ret['X'] = value['X'] ret['X'] = value['X']
return ret return ret
def CopyNode(self, node): def optimize(self, treesymtab, options = ('optimize','constfold','dcr')):
# 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',)):
"""Optimize the symbolic table symtab in place. Requires a table of """Optimize the symbolic table symtab in place. Requires a table of
predefined functions for folding constants. predefined functions for folding constants.
""" """
@ -829,26 +56,27 @@ class optimizer(renamer, deadcode):
self.shrinknames = 'shrinknames' in options self.shrinknames = 'shrinknames' in options
self.constfold = 'constfold' in options
self.dcr = 'dcr' in options
tree, symtab = self.tree, self.symtab = treesymtab tree, symtab = self.tree, self.symtab = treesymtab
self.globalmode = False self.globalmode = False
# Constant folding pass. It does some other optimizations along the way. if self.constfold:
for idx in xrange(len(tree)): self.FoldScript()
if tree[idx]['nt'] == 'DECL':
self.globalmode = True if self.dcr:
self.FoldTree(tree, idx) self.RemoveDeadCode()
self.globalmode = False
if not self.IsValidGlobalConstant(tree[idx]): # Make another fold pass, since RemoveDeadCode can embed expressions
warning('Expression does not resolve to a single constant.') # into other expressions and generate unoptimized code.
else: if self.constfold:
self.FoldTree(tree, idx) self.FoldScript()
if self.shrinknames: if self.shrinknames:
self.ShrinkNames() self.ShrinkNames()
self.RemoveDeadCode()
treesymtab = (self.tree, self.symtab) treesymtab = (self.tree, self.symtab)
del self.tree del self.tree
del self.symtab del self.symtab

View file

@ -382,7 +382,7 @@ class Test03_Optimizer(UnitTestCase):
f(integer a, integer b, integer c, integer d, integer e){} f(integer a, integer b, integer c, integer d, integer e){}
default{timer(){}} default{timer(){}}
''') ''')
self.opt.optimize(p, ['optimize','shrinknames']) self.opt.optimize(p, ['optimize','shrinknames','dcr','constfold'])
out = self.outscript.output(p) out = self.outscript.output(p)
self.assertEqual(out, 'default\n{\n timer()\n {\n }\n}\n') self.assertEqual(out, 'default\n{\n timer()\n {\n }\n}\n')