mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2024-11-21 06:15:56 -07:00
c1c1b8c58d
Use the node's min and max for both sides. Constants are just a special case where min = max = value.
2023 lines
86 KiB
Python
2023 lines
86 KiB
Python
# (C) Copyright 2015-2024 Sei Lisa. All rights reserved.
|
|
#
|
|
# This file is part of LSL PyOptimizer.
|
|
#
|
|
# LSL PyOptimizer is free software: you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# LSL PyOptimizer is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with LSL PyOptimizer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Constant folding and simplification of expressions and statements.
|
|
|
|
from lslopt import lslcommon
|
|
from lslopt.lslcommon import Vector, Quaternion, warning, nr
|
|
from lslopt import lslfuncs
|
|
from lslopt.lslfuncs import ZERO_VECTOR, ZERO_ROTATION
|
|
import math
|
|
from lslopt.lslfuncopt import OptimizeFunc, OptimizeArgs, FuncOptSetup
|
|
from strutil import xrange, unicode
|
|
|
|
# TODO: Remove special handling of @ within IF,WHILE,FOR,DO
|
|
|
|
class foldconst(object):
|
|
|
|
def getMin(self, node):
|
|
assert node.t in ('integer', 'float')
|
|
if node.nt == 'CONST':
|
|
return node.value
|
|
return getattr(node, 'min', None)
|
|
|
|
def getMax(self, node):
|
|
assert node.t in ('integer', 'float')
|
|
if node.nt == 'CONST':
|
|
return node.value
|
|
return getattr(node, 'max', None)
|
|
|
|
def isLocalVar(self, node):
|
|
name = node.name
|
|
scope = node.scope
|
|
return (self.symtab[scope][name]['Kind'] == 'v'
|
|
and 'Loc' not in self.symtab[scope][name])
|
|
|
|
def GetListNodeLength(self, node):
|
|
"""Get the length of a list that is expressed as a CONST, LIST or CAST
|
|
node, or False if it can't be determined.
|
|
"""
|
|
assert node.t == 'list'
|
|
nt = node.nt
|
|
if nt == 'CAST':
|
|
if node.ch[0].t == 'list':
|
|
return self.GetListNodeLength(node.ch[0])
|
|
return 1
|
|
if nt == 'CONST': # constant list
|
|
return len(node.value)
|
|
if nt == 'LIST': # list constructor
|
|
return len(node.ch)
|
|
return False
|
|
|
|
def GetListNodeElement(self, node, index):
|
|
"""Get an element of a list expressed as a CONST, LIST or CAST node.
|
|
If the index is out of range, return False; otherwise the result can be
|
|
either a node or a constant.
|
|
"""
|
|
assert node.t == 'list'
|
|
nt = node.nt
|
|
if nt == 'CAST':
|
|
# (list)list_expr should have been handled in CAST
|
|
assert node.ch[0].t != 'list'
|
|
|
|
if index == 0 or index == -1:
|
|
return node.ch[0]
|
|
return False
|
|
if nt == 'CONST':
|
|
try:
|
|
return node.value[index]
|
|
except IndexError:
|
|
pass
|
|
return False
|
|
if nt == 'LIST':
|
|
try:
|
|
return node.ch[index]
|
|
except IndexError:
|
|
return False
|
|
return False
|
|
|
|
def ConstFromNodeOrConst(self, nodeOrConst):
|
|
"""Return the constant if the value is a node and represents a constant,
|
|
or if the value is directly a constant, and False otherwise.
|
|
"""
|
|
if type(nodeOrConst) == nr:
|
|
if nodeOrConst.nt == 'CONST':
|
|
return nodeOrConst.value
|
|
return False
|
|
return nodeOrConst
|
|
|
|
def TypeFromNodeOrConst(self, nodeOrConst):
|
|
"""Return the LSL type of a node or constant."""
|
|
if nodeOrConst is False:
|
|
return False
|
|
if type(nodeOrConst) == nr:
|
|
return nodeOrConst.t
|
|
return lslcommon.PythonType2LSL[type(nodeOrConst)]
|
|
|
|
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 CompareTrees(self, node1, node2):
|
|
"""Try to compare two subtrees to see if they are equivalent.
|
|
|
|
Returns True if they are."""
|
|
# They MUST be SEF and stable.
|
|
if not node1.SEF or not node2.SEF:
|
|
return False
|
|
if node1.t != node2.t:
|
|
return False
|
|
# It's not complete yet.
|
|
nt1 = node1.nt
|
|
if nt1 == node2.nt:
|
|
if (nt1 == 'IDENT'
|
|
and node1.name == node2.name
|
|
and node1.scope == node2.scope
|
|
):
|
|
return True
|
|
if (nt1 == 'FNCALL'
|
|
and node1.name == node2.name
|
|
and 'uns' not in self.symtab[0][node1.name]
|
|
and all(self.CompareTrees(node1.ch[i],
|
|
node2.ch[i])
|
|
for i in xrange(len(node1.ch)))
|
|
):
|
|
return True
|
|
if (nt1 == 'CAST'
|
|
and self.CompareTrees(node1.ch[0], node2.ch[0])
|
|
):
|
|
return True
|
|
if nt1 == 'CONST' and node1.value == node2.value:
|
|
return True
|
|
if (nt1 in ('!', '~', 'NEG')
|
|
and self.CompareTrees(node1.ch[0], node2.ch[0])
|
|
):
|
|
return True
|
|
if (nt1 in self.binary_ops
|
|
and self.CompareTrees(node1.ch[0], node2.ch[0])
|
|
and self.CompareTrees(node1.ch[1], node2.ch[1])
|
|
):
|
|
return True
|
|
if ((nt1 in ('*', '^', '&', '|', '==') # commutative
|
|
or nt1 == '+'
|
|
and node1.ch[0].t not in ('list', 'string')
|
|
and node2.ch[0].t not in ('list', 'string')
|
|
)
|
|
and self.CompareTrees(node1.ch[0], node2.ch[1])
|
|
and self.CompareTrees(node1.ch[1], node2.ch[0])
|
|
):
|
|
return True
|
|
return False
|
|
|
|
def FnSEF(self, node):
|
|
'''Applied to function call nodes, return whether the node corresponds
|
|
to a SEF function.
|
|
'''
|
|
assert node.nt == 'FNCALL'
|
|
sym = self.symtab[0][node.name]
|
|
return 'SEF' in sym and sym['SEF'] is True
|
|
|
|
def FoldStmt(self, parent, index):
|
|
"""Simplify a statement."""
|
|
node = parent[index]
|
|
# If the statement is side-effect-free, remove it as it does nothing.
|
|
if node.SEF:
|
|
# When a statement is side-effect free, it 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.
|
|
# Other unary and binary operators are side effect-free.
|
|
parent[index] = nr(nt=';', t=None, SEF=True)
|
|
return
|
|
if node.nt == 'EXPR':
|
|
node = node.ch[0]
|
|
# Post-increments take more space than pre-increments.
|
|
if node.nt in ('V++', 'V--'):
|
|
node.nt = '++V' if node.nt == 'V++' else '--V';
|
|
|
|
# Function calls are SEF if both the function and the args are SEF.
|
|
# If the statement is a function call and the function is marked as SEF
|
|
# at this point, it means the arguments are not SEF. Replace the node
|
|
# in that case with a block of expressions.
|
|
if (node.nt == 'FNCALL' and 'Loc' in self.symtab[0][node.name]
|
|
and self.FnSEF(node)
|
|
):
|
|
scope = len(self.symtab)
|
|
self.symtab.append({})
|
|
parent[index] = nr(nt='{}', t=None, scope=scope, ch=[
|
|
nr(nt='EXPR', t=x.t, ch=[x]) for x in node.ch])
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
def ExpandCondition(self, parent, index):
|
|
"""IF, FOR, WHILE and DO...WHILE conditions accept several types, not
|
|
just integer. However, leaving them as-is generates longer code than if
|
|
we expand them and let the optimizer optimize, for float, vector and
|
|
rotation, and no matter the optimization in the case of list.
|
|
"""
|
|
ctyp = parent[index].t
|
|
# Under LSO, this would break the fact that 1-element lists count as
|
|
# false, so we don't do it for LSO lists.
|
|
if (ctyp in ('float', 'vector', 'rotation', 'string')
|
|
or ctyp == 'list' and not lslcommon.LSO
|
|
):
|
|
parent[index] = nr(nt='!=', t='integer', ch=[parent[index],
|
|
nr(nt='CONST', t=ctyp, value=0.0 if ctyp == 'float'
|
|
else ZERO_VECTOR if ctyp == 'vector'
|
|
else ZERO_ROTATION if ctyp == 'rotation'
|
|
else u"" if ctyp == 'string'
|
|
else [])])
|
|
parent[index].SEF = parent[index].ch[0].SEF
|
|
|
|
def IsBool(self, node):
|
|
"""Some operators return 0 or 1, and that allows simplification of
|
|
boolean expressions. This function returns whether we know for sure
|
|
that the result is boolean.
|
|
"""
|
|
nt = node.nt
|
|
if (nt in ('<', '!', '>', '<=', '>=', '==', '||', '&&')
|
|
or nt == '!=' and node.ch[0].t != 'list'
|
|
or nt == '&' and any(self.IsBool(node.ch[i]) for i in (0, 1))
|
|
or nt in ('|', '^', '*')
|
|
and all(self.IsBool(node.ch[i]) for i in (0, 1))
|
|
or nt == 'CONST' and node.t == 'integer' and node.value in (0, 1)
|
|
):
|
|
return True
|
|
|
|
if nt == 'FNCALL':
|
|
sym = self.symtab[0][node.name]
|
|
if (sym['Type'] == 'integer' and 'min' in sym and 'max' in sym
|
|
and sym['min'] >= 0 and sym['max'] <= 1
|
|
):
|
|
return True
|
|
|
|
return False
|
|
|
|
def IsAndBool(self, node):
|
|
"""For bitwise AND, in some cases we can relax the condition to this:
|
|
when bit 0 is 0, all other bits are guaranteed to be 0 as well. That's
|
|
the case of -bool, which is the only case we deal with here, but an
|
|
important one because we generate it as an intermediate result in some
|
|
operations.
|
|
"""
|
|
return (node.nt == 'NEG' and self.IsBool(node.ch[0])
|
|
or self.IsBool(node))
|
|
|
|
def GetTruth(self, node):
|
|
"""Decode truth value of node when possible.
|
|
Returns True if it's always true, False if it's always false, and None
|
|
if it can't be determined.
|
|
"""
|
|
if node.nt == 'CONST':
|
|
return lslfuncs.cond(node.value)
|
|
min = getattr(node, 'min', None)
|
|
max = getattr(node, 'max', None)
|
|
if min is None or max is None:
|
|
if node.nt == 'FNCALL':
|
|
min = self.symtab[0][node.name].get('min', min)
|
|
max = self.symtab[0][node.name].get('max', max)
|
|
if min is not None and min > 0:
|
|
return True
|
|
if max is not None and max < 0:
|
|
return True
|
|
if min == max == 0:
|
|
return False
|
|
return None
|
|
|
|
def FoldAsBool(self, parent, index, ParentIsNegation = False):
|
|
"""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]
|
|
nt = node.nt
|
|
truth = self.GetTruth(node)
|
|
if truth is not None and node.SEF:
|
|
parent[index] = nr(nt='CONST',t='integer',value=1 if truth else 0,
|
|
SEF=True)
|
|
return
|
|
if nt in ('IDENT', 'FLD'):
|
|
return # Nothing to do if it's already simplified.
|
|
child = node.ch
|
|
|
|
if nt == 'FNCALL' and 'strlen' in self.symtab[0][node.name]:
|
|
# llStringLength(expr) -> !(expr == "")
|
|
# new node is SEF if the argument to llStringLength is
|
|
node = nr(nt='==', t='integer', SEF=child[0].SEF,
|
|
ch=[child[0],
|
|
nr(nt='CONST', t='string', value=u'', SEF=True)
|
|
])
|
|
node = nr(nt='!', t='integer', ch=[node], SEF=child[0].SEF)
|
|
parent[index] = node
|
|
nt = '!'
|
|
child = node.ch
|
|
# fall through to keep optimizing if necessary
|
|
|
|
if nt == '!':
|
|
self.FoldAsBool(child, 0, True)
|
|
|
|
if child[0].nt == '!':
|
|
# bool(!!a) equals bool(a)
|
|
parent[index] = child[0].ch[0]
|
|
return
|
|
|
|
if (child[0].nt == '==' and child[0].ch[0].t == 'integer'
|
|
and child[0].ch[1].t == 'integer'
|
|
):
|
|
# We have !(int == int). Replace with int ^ int or with int - 1
|
|
node = parent[index] = child[0] # remove the negation
|
|
child = child[0].ch
|
|
if (child[0].nt == 'CONST' and child[0].value == 1
|
|
or child[1].nt == 'CONST' and child[1].value == 1
|
|
):
|
|
# a != 1 -> a - 1 (which FoldTree will transform to ~-a)
|
|
node.nt = '-'
|
|
else:
|
|
# This converts != to ^; FoldTree will simplify ^-1 to ~
|
|
# and optimize out ^0.
|
|
node.nt = '^'
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
if (child[0].nt == '&'
|
|
and any(child[0].ch[i].nt == '!'
|
|
and self.IsAndBool(child[0].ch[1-i]) for i in (0, 1))
|
|
):
|
|
# We can remove at least one !
|
|
child[0].nt = '|'
|
|
for i in (0, 1):
|
|
child[0].ch[i] = nr(nt='!', t='integer',
|
|
ch=[child[0].ch[i]], SEF=child[0].ch[i].SEF)
|
|
parent[index] = child[0]
|
|
self.FoldTree(parent, index)
|
|
self.FoldAsBool(parent, index)
|
|
return
|
|
|
|
if nt == 'NEG':
|
|
# bool(-a) equals bool(a)
|
|
parent[index] = child[0]
|
|
self.FoldAsBool(parent, index, ParentIsNegation)
|
|
return
|
|
|
|
if nt in self.binary_ops and child[0].t == child[1].t == 'integer':
|
|
if nt == '==':
|
|
if (child[0].nt == 'CONST' and -1 <= child[0].value <= 1
|
|
or child[1].nt == 'CONST' and -1 <= child[1].value <= 1
|
|
):
|
|
# Transform a==b into !(a-b) if either a or b are in [-1,1]
|
|
parent[index] = nr(nt='!', t='integer', ch=[node])
|
|
node.nt = '-'
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
if nt == '|':
|
|
# In a boolean context, the operands count as booleans.
|
|
self.FoldAsBool(child, 0)
|
|
self.FoldAsBool(child, 1)
|
|
|
|
# Deal with operands in any order
|
|
a, b = 0, 1
|
|
# Put constant in child[b] if present
|
|
if child[b].nt != 'CONST':
|
|
a, b = 1, 0
|
|
if child[b].nt == 'CONST' and child[b].value and child[a].SEF:
|
|
node = parent[index] = child[b]
|
|
node.value = -1
|
|
return
|
|
del a, b
|
|
|
|
# Specific optimization to catch a frequent bitwise test.
|
|
# If b and c are constant powers of two:
|
|
# !(a & b) | !(a & c) -> ~(a|~(b|c))
|
|
# e.g. if (a & 4 && a & 8) -> if (!~(a|-13))
|
|
if (child[0].nt == '!' and child[0].ch[0].nt == '&'
|
|
and child[1].nt == '!' and child[1].ch[0].nt == '&'
|
|
):
|
|
and1 = child[0].ch[0].ch
|
|
and2 = child[1].ch[0].ch
|
|
a, b, c, d = 0, 1, 0, 1
|
|
if and1[b].nt != 'CONST':
|
|
a, b = b, a
|
|
if and2[d].nt != 'CONST':
|
|
c, d = d, c
|
|
if and1[b].nt == and2[d].nt == 'CONST':
|
|
val1 = and1[b].value
|
|
val2 = and2[d].value
|
|
if (val1 and val2
|
|
# power of 2
|
|
and (val1 & (val1 - 1) & 0xFFFFFFFF) == 0
|
|
and (val2 & (val2 - 1) & 0xFFFFFFFF) == 0
|
|
and self.CompareTrees(and1[a], and2[c])
|
|
):
|
|
# Check passed
|
|
child[0] = and1[a]
|
|
child[1] = and1[b]
|
|
child[1].value = ~(val1 | val2)
|
|
parent[index] = nr(nt='~', t='integer', ch=[node],
|
|
SEF=node.SEF)
|
|
self.FoldAsBool(parent, index, ParentIsNegation)
|
|
return
|
|
del val1, val2
|
|
del a, b, c, d, and1, and2
|
|
|
|
# Absorb further flags, to allow chaining of &&
|
|
# If ~r and s are constants, and s is a power of two:
|
|
# (!~(x|~r) && x&s) -> !~(x|(~r&~s))
|
|
# This is implemented as:
|
|
# ~(x|~r) | !(x&s) -> ~(x|~(r|s))
|
|
# since that's the intermediate result after conversion of &&.
|
|
# a and b are going to be the children of the main |
|
|
# a is going to be child that has the ~
|
|
# b is the other child (with the !)
|
|
# c is the child of ~ which has x
|
|
# d is the child of ~ with the constant ~r
|
|
# e is the child of ! which has x
|
|
# f is the child of ! with the constant s
|
|
a, b = 0, 1
|
|
if child[a].nt != '~':
|
|
a, b = b, a
|
|
c, d = 0, 1
|
|
if child[a].nt == '~' and child[a].ch[0].nt == '|':
|
|
if child[a].ch[0].ch[d].nt != 'CONST':
|
|
c, d = d, c
|
|
e, f = 0, 1
|
|
if child[b].nt == '!' and child[b].ch[0].nt == '&':
|
|
if child[b].ch[0].ch[f].nt != 'CONST':
|
|
e, f = f, e
|
|
# All pointers are ready to check applicability.
|
|
if (child[a].nt == '~' and child[a].ch[0].nt == '|'
|
|
and child[b].nt == '!' and child[b].ch[0].nt == '&'
|
|
):
|
|
ch1 = child[a].ch[0].ch
|
|
ch2 = child[b].ch[0].ch
|
|
if (ch1[d].nt == 'CONST' and ch2[f].nt == 'CONST'
|
|
and (ch2[f].value & (ch2[f].value - 1)
|
|
& 0xFFFFFFFF) == 0
|
|
):
|
|
if self.CompareTrees(ch1[c], ch2[e]):
|
|
# We're in that case. Apply optimization.
|
|
parent[index] = child[a]
|
|
ch1[d].value &= ~ch2[f].value
|
|
return
|
|
del ch1, ch2
|
|
|
|
del a, b, c, d, e, f
|
|
|
|
|
|
# Check if the operands are a negation ('!') or can be inverted
|
|
# without adding more than 1 byte and are boolean.
|
|
# We only support '<' and some cases of '&' (are there more?)
|
|
Invertible = [False, False]
|
|
for a in (0, 1):
|
|
Invertible[a] = child[a].nt == '!'
|
|
if (child[a].nt == '<'
|
|
and child[a].ch[0].t == child[a].ch[1].t == 'integer'
|
|
):
|
|
if (child[a].ch[0].nt == 'CONST'
|
|
and child[a].ch[0].value != 2147483647
|
|
or child[a].ch[1].nt == 'CONST'
|
|
and child[a].ch[1].value != int(-2147483648)
|
|
):
|
|
Invertible[a] = True
|
|
|
|
# Deal with our optimization of a<0 -> a&0x80000000
|
|
# (see below)
|
|
if child[a].nt == '&' and (
|
|
child[a].ch[0].nt == 'CONST'
|
|
and child[a].ch[0].value == int(-2147483648)
|
|
or child[a].ch[1].nt == 'CONST'
|
|
and child[a].ch[1].value == int(-2147483648)
|
|
):
|
|
Invertible[a] |= ParentIsNegation
|
|
|
|
if (Invertible[0] or Invertible[1]) and ParentIsNegation:
|
|
# !(!a|b) -> a&-!b or a&!b
|
|
# This deals with the part after the first !, transforming
|
|
# it into (!a|!!b) so that the outer node can optimize the
|
|
# negated version to a simple &.
|
|
for a in (0, 1):
|
|
if not Invertible[a]:
|
|
child[a] = nr(nt='!', t='integer',
|
|
ch=[nr(nt='!', t='integer', ch=[child[a]])]
|
|
)
|
|
Invertible[a] = True
|
|
|
|
if Invertible[0] and Invertible[1]:
|
|
# Both operands are negated, or negable.
|
|
# Make them a negation if they aren't already.
|
|
for a in (0, 1):
|
|
if child[a].nt == '<':
|
|
if child[a].ch[0].nt == 'CONST':
|
|
child[a].ch[0].value += 1
|
|
else:
|
|
child[a].ch[1].value -= 1
|
|
child[a].ch[0], child[a].ch[1] = \
|
|
child[a].ch[1], child[a].ch[0]
|
|
child[a] = nr(nt='!', t='integer', ch=[child[a]])
|
|
elif child[a].nt == '&':
|
|
child[a] = nr(nt='!', t='integer',
|
|
ch=[nr(nt='!', t='integer', ch=[child[a]])]
|
|
)
|
|
self.FoldTree(child[a].ch, 0)
|
|
# If they are boolean, the expression can be turned into
|
|
# !(a&b) which hopefully will have a ! uptree if it came
|
|
# from a '&&' and cancel out (if not, we still remove one
|
|
# ! so it's good). If one is bool, another transformation
|
|
# can be performed: !nonbool|!bool -> !(nonbool&-bool)
|
|
# which is still a gain.
|
|
|
|
# Deal with operands in any order
|
|
a, b = 0, 1
|
|
# Put the bool in child[b].ch[0].
|
|
if not self.IsBool(child[b].ch[0]):
|
|
a, b = 1, 0
|
|
if self.IsBool(child[b].ch[0]):
|
|
if not self.IsAndBool(child[a].ch[0]):
|
|
child[b].ch[0] = nr(nt='NEG', t='integer',
|
|
ch=[child[b].ch[0]])
|
|
|
|
node = parent[index] = nr(nt='!', t='integer',
|
|
ch=[nr(nt='&', t='integer',
|
|
ch=[child[0].ch[0], child[1].ch[0]])
|
|
], SEF=child[0].ch[0].SEF and child[1].ch[0].SEF)
|
|
# Fold the node we've just synthesized
|
|
self.FoldTree(parent, index)
|
|
|
|
return
|
|
|
|
if nt == '<' and child[0].t == child[1].t == 'integer':
|
|
sym = None
|
|
for a in (0, 1):
|
|
if child[a].nt == 'FNCALL':
|
|
sym = self.symtab[0][child[a].name]
|
|
break
|
|
|
|
# cond(FNCALL < 0) -> cond(!~FNCALL) if min == -1
|
|
if (child[1].nt == 'CONST' and child[1].value == 0
|
|
and child[0].nt == 'FNCALL'
|
|
and 'min' in sym and sym['min'] == -1
|
|
):
|
|
node = parent[index] = nr(nt='!', t='integer',
|
|
ch=[nr(nt='~', t='integer', ch=[child[0]])])
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
# cond(FNCALL > -1) -> cond(~FNCALL) if min == -1
|
|
if (child[0].nt == 'CONST' and child[0].value == -1
|
|
and child[1].nt == 'FNCALL'
|
|
and 'min' in sym and sym['min'] == -1
|
|
):
|
|
node = parent[index] = nr(nt='~', t='integer',
|
|
ch=[child[1]])
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
# cond(FNCALL < 1) -> cond(!FNCALL) if min == 0
|
|
if (child[1].nt == 'CONST' and child[1].value == 1
|
|
and child[0].nt == 'FNCALL'
|
|
and 'min' in sym and sym['min'] == 0
|
|
):
|
|
node = parent[index] = nr(nt='!', t='integer',
|
|
ch=[child[0]])
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
# cond(FNCALL > 0) -> cond(FNCALL) if min == 0
|
|
if (child[0].nt == 'CONST' and child[0].value == 0
|
|
and child[1].nt == 'FNCALL'
|
|
and 'min' in sym and sym['min'] == 0
|
|
):
|
|
node = parent[index] = child[1]
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
if nt == '&':
|
|
|
|
# Deal with operands in any order
|
|
a, b = 0, 1
|
|
# Put constant in child[b], if present
|
|
if child[b].nt != 'CONST':
|
|
a, b = 1, 0
|
|
if (child[b].nt == 'CONST'
|
|
and child[b].value == int(-2147483648)
|
|
and child[a].nt == 'FNCALL'
|
|
):
|
|
sym = self.symtab[0][child[a].name]
|
|
if 'min' in sym and sym['min'] == -1:
|
|
node = parent[index] = nr(nt='~', t='integer',
|
|
ch=[child[a]])
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
def CopyNode(self, node):
|
|
'''Deep copy of a node'''
|
|
ret = node.copy()
|
|
if ret.ch:
|
|
ret.ch = [self.CopyNode(subnode) for subnode in ret.ch]
|
|
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 nt == 'CONST':
|
|
# Job already done. But mark as side-effect free.
|
|
node.SEF = True
|
|
return
|
|
|
|
if nt == 'CAST':
|
|
self.FoldTree(child, 0)
|
|
node.SEF = child[0].SEF
|
|
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] = nr(nt='CONST', t=node.t, SEF=True,
|
|
value=lslfuncs.typecast(
|
|
child[0].value, lslcommon.LSLType2Python[node.t]))
|
|
|
|
# Remove casts of a type to the same type (NOP in Mono)
|
|
# This is not an optimization by itself, but it simplifies the job,
|
|
# by not needing to look into nested casts like (key)((key)...)
|
|
while node.nt == 'CAST' and child[0].t == node.t:
|
|
parent[index] = node = child[0]
|
|
if node.ch is None:
|
|
break
|
|
child = node.ch
|
|
|
|
return
|
|
|
|
if nt == 'NEG':
|
|
self.FoldTree(child, 0)
|
|
node.SEF = child[0].SEF
|
|
|
|
if child[0].nt == '+' and any(child[0].ch[i].nt == 'NEG'
|
|
for i in (0, 1)):
|
|
node = parent[index] = child[0]
|
|
child = node.ch
|
|
for a in (0, 1):
|
|
if child[a].nt == 'NEG':
|
|
child[a] = child[a].ch[0]
|
|
else:
|
|
child[a] = nr(nt='NEG', t=child[a].t, ch=[child[a]],
|
|
SEF=child[a].SEF)
|
|
self.FoldTree(child, a)
|
|
return
|
|
|
|
if child[0].nt == 'NEG':
|
|
# Double negation: - - expr -> expr
|
|
node = parent[index] = child[0].ch[0]
|
|
child = node.ch
|
|
elif child[0].nt == 'CONST':
|
|
node = parent[index] = child[0]
|
|
node.value = lslfuncs.neg(node.value)
|
|
child = None
|
|
|
|
if child and node.nt == 'NEG' and child[0].nt == '~':
|
|
track = child[0].ch[0]
|
|
const = 1
|
|
while track.nt == 'NEG' and track.ch[0].nt == '~':
|
|
const += 1
|
|
track = track.ch[0].ch[0]
|
|
if const > 2:
|
|
# -~-~-~expr -> expr+3
|
|
node = nr(nt='CONST', t='integer', SEF=True, value=const)
|
|
node = nr(nt='+', t='integer', ch=[node, track],
|
|
SEF=track.SEF)
|
|
parent[index] = node
|
|
|
|
return
|
|
|
|
if nt == '!':
|
|
self.FoldTree(child, 0)
|
|
self.FoldAsBool(child, 0, True)
|
|
# !! does *not* cancel out (unless in cond)
|
|
subexpr = child[0]
|
|
snt = subexpr.nt
|
|
|
|
node.SEF = subexpr.SEF
|
|
if snt == 'CONST':
|
|
node = parent[index] = subexpr
|
|
node.value = int(not node.value)
|
|
return
|
|
if snt == '<':
|
|
lop = subexpr.ch[0]
|
|
rop = subexpr.ch[1]
|
|
if (lop.nt == 'CONST' and lop.t == rop.t == 'integer'
|
|
and lop.value < 2147483647
|
|
):
|
|
lop.value += 1
|
|
subexpr.ch[0], subexpr.ch[1] = subexpr.ch[1], subexpr.ch[0]
|
|
parent[index] = subexpr # remove the !
|
|
return
|
|
if (rop.nt == 'CONST' and lop.t == rop.t == 'integer'
|
|
and rop.value > int(-2147483648)
|
|
):
|
|
rop.value -= 1
|
|
subexpr.ch[0], subexpr.ch[1] = subexpr.ch[1], subexpr.ch[0]
|
|
parent[index] = subexpr # remove the !
|
|
return
|
|
if snt == '&':
|
|
a, b = 0, 1
|
|
if subexpr.ch[b].nt != 'CONST':
|
|
a, b = 1, 0
|
|
if (subexpr.ch[b].nt == 'CONST'
|
|
and subexpr.ch[b].value == int(-2147483648)
|
|
):
|
|
# !(i & 0x80000000) -> -1 < i (because one of our
|
|
# optimizations can be counter-productive, see FoldAsBool)
|
|
subexpr.nt = '<'
|
|
subexpr.ch[b].value = -1
|
|
subexpr.ch = [subexpr.ch[b], subexpr.ch[a]]
|
|
parent[index] = subexpr
|
|
return
|
|
if snt == '!=' or snt == '^' or snt == '-' or snt == '+':
|
|
if snt == '+':
|
|
# Change !(x + y) -> -x == y, and make another pass
|
|
# to get rid of the signs where possible
|
|
subexpr.ch[0] = nr(nt='NEG', t='integer',
|
|
ch=[subexpr.ch[0]], SEF=subexpr.ch[0].SEF)
|
|
|
|
subexpr.nt = '=='
|
|
parent[index] = subexpr
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
return
|
|
|
|
if nt == '~':
|
|
self.FoldTree(child, 0)
|
|
subexpr = child[0]
|
|
node.SEF = subexpr.SEF
|
|
|
|
if child[0].nt == 'NEG':
|
|
track = child[0].ch[0]
|
|
const = -1
|
|
while track.nt == '~' and track.ch[0].nt == 'NEG':
|
|
const -= 1
|
|
track = track.ch[0].ch[0]
|
|
if const < -2:
|
|
# ~-~-~-expr -> expr + (-3)
|
|
node = nr(nt='CONST', t='integer', SEF=True, value=const)
|
|
node = nr(nt='+', t='integer', ch=[node, track],
|
|
SEF=track.SEF)
|
|
parent[index] = node
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
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 in self.binary_ops:
|
|
# RTL evaluation
|
|
self.FoldTree(child, 1)
|
|
self.FoldTree(child, 0)
|
|
# Node is SEF if both sides are side-effect free.
|
|
node.SEF = child[0].SEF and child[1].SEF
|
|
|
|
optype = node.t
|
|
lval = child[0]
|
|
ltype = lval.t
|
|
lnt = lval.nt
|
|
rval = child[1]
|
|
rtype = rval.t
|
|
rnt = rval.nt
|
|
|
|
if lnt == rnt == 'CONST':
|
|
op1 = lval.value
|
|
op2 = rval.value
|
|
if nt == '+':
|
|
if ltype == rtype == 'string' and not self.addstrings:
|
|
return
|
|
result = lslfuncs.add(op1, op2)
|
|
elif nt == '-':
|
|
result = lslfuncs.sub(op1, op2)
|
|
elif nt == '*':
|
|
result = lslfuncs.mul(op1, op2)
|
|
elif nt == '/':
|
|
try:
|
|
result = lslfuncs.div(op1, op2)
|
|
except lslfuncs.ELSLMathError:
|
|
return
|
|
elif nt == '%':
|
|
try:
|
|
result = lslfuncs.mod(op1, op2)
|
|
except lslfuncs.ELSLMathError:
|
|
return
|
|
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] = nr(nt='CONST', t=node.t, SEF=True, value=result)
|
|
return
|
|
|
|
# Simplifications for particular operands
|
|
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] = nr(nt='NEG', t=node.t, ch=[rval],
|
|
SEF=rval.SEF)
|
|
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'
|
|
rval = child[1] = nr(nt=rnt, t=rval.t, ch=[rval],
|
|
SEF=rval.SEF)
|
|
self.FoldTree(child, 1)
|
|
# rtype unchanged
|
|
|
|
# Fall through to simplify it as '+'
|
|
|
|
if nt == '+':
|
|
# Tough one. Remove neutral elements for the various types,
|
|
# and more.
|
|
|
|
# expr + -expr -> 0
|
|
# -expr + expr -> 0
|
|
if (child[0].nt == 'NEG'
|
|
and self.CompareTrees(child[0].ch[0], child[1])
|
|
or child[1].nt == 'NEG'
|
|
and self.CompareTrees(child[1].ch[0], child[0])
|
|
):
|
|
parent[index] = nr(nt='CONST', t='integer', value=0,
|
|
SEF=True)
|
|
return
|
|
|
|
# Addition of integers, strings, and lists is associative.
|
|
# Addition of floats, vectors and rotations would be, except
|
|
# for FP precision.
|
|
# TODO: associative addition of lists
|
|
# Associative lists are trickier, because unlike the others,
|
|
# the types of the operands may not be lists
|
|
# so e.g. list+(integer+integer) != (list+integer)+integer.
|
|
if optype == 'integer' or optype == 'string' and self.addstrings:
|
|
if lnt == '+' and rnt == 'CONST' and lval.ch[1].nt == 'CONST':
|
|
# (var + ct1) + ct2 -> var + (ct1 + ct2)
|
|
child[1] = nr(nt='+', t=optype, ch=[lval.ch[1], rval],
|
|
SEF=True)
|
|
lval = child[0] = lval.ch[0]
|
|
lnt = lval.nt
|
|
ltype = lval.t
|
|
rtype = optype
|
|
# Fold the RHS again now that we have it constant
|
|
self.FoldTree(child, 1)
|
|
rval = child[1]
|
|
rnt = rval.nt
|
|
|
|
if optype == 'list' and not (ltype == rtype == 'list'):
|
|
if lnt == 'CONST' and ltype == 'list' and not lval.value:
|
|
# [] + nonlist -> (list)nonlist
|
|
parent[index] = self.Cast(rval, optype)
|
|
# node is SEF if rval is
|
|
parent[index].SEF = rval.SEF
|
|
return
|
|
|
|
if optype in ('vector', 'rotation'):
|
|
# not much to do with vectors or quaternions either
|
|
if lnt == 'CONST' and all(x == 0 for x in lval.value):
|
|
# Change <0,0,0[,0]>+expr -> expr
|
|
parent[index] = rval
|
|
elif rnt == 'CONST' and all(x == 0 for x 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
|
|
assert optype != 'key'
|
|
|
|
if optype in ('string', 'float', 'list'):
|
|
# All these types evaluate to boolean False when they are
|
|
# the neutral addition element.
|
|
if lnt == 'CONST' and not lval.value and (ltype == rtype
|
|
or ltype == 'integer' and rtype == 'float'
|
|
or ltype == 'float' and rtype == 'integer'):
|
|
# 0 + fval -> fval
|
|
# 0. + fval -> fval
|
|
# 0. + ival -> fval
|
|
# "" + sval -> sval
|
|
# [] + lval -> lval
|
|
parent[index] = self.Cast(rval, optype)
|
|
# node is SEF if rval is
|
|
parent[index].SEF = rval.SEF
|
|
return
|
|
if rnt == 'CONST' and not rval.value and (rtype == ltype
|
|
or rtype == 'integer' and ltype == 'float'
|
|
or rtype == 'float' and ltype == 'integer'):
|
|
# fval + 0 -> fval
|
|
# fval + 0. -> fval
|
|
# ival + 0. -> fval
|
|
# sval + "" -> sval
|
|
# lval + [] -> lval
|
|
parent[index] = self.Cast(lval, optype)
|
|
# node is SEF if lval is
|
|
parent[index].SEF = lval.SEF
|
|
return
|
|
|
|
if ltype == rtype == 'list':
|
|
|
|
if (rnt == 'LIST' and len(rval.ch) == 1
|
|
or rnt == 'CONST' and len(rval.value) == 1
|
|
or rnt == 'CAST'
|
|
):
|
|
# list + (list)element -> list + element
|
|
# list + [element] -> list + element
|
|
while rnt == 'CAST' and rval.t == 'list':
|
|
# Remove nested typecasts
|
|
# e.g. list + (list)((list)x) -> list + x
|
|
rval = parent[index].ch[1] = rval.ch[0]
|
|
rnt = rval.nt
|
|
if (rnt == 'LIST' and len(rval.ch) == 1
|
|
and rval.ch[0].t != 'list'):
|
|
# Finally, remove [] wrapper if it's not
|
|
# list within list
|
|
rval = child[1] = rval.ch[0]
|
|
rnt = rval.nt
|
|
if rnt == 'CONST' and len(rval.value) == 1:
|
|
# list + [constant] -> list + constant
|
|
rval.value = rval.value[0]
|
|
rtype = rval.t = lslcommon.PythonType2LSL[
|
|
type(rval.value)]
|
|
return
|
|
|
|
if (lnt == 'LIST' and len(lval.ch) == 1
|
|
or lnt == 'CONST' and len(lval.value) == 1
|
|
or lnt == 'CAST'
|
|
):
|
|
# (list)element + list -> element + list
|
|
# [element] + list -> element + list
|
|
# (list)[element] + list -> element + list
|
|
while lnt == 'CAST' and lval.t == 'list':
|
|
# Remove nested typecasts
|
|
# e.g. (list)((list)x) + list -> x + list
|
|
lval = parent[index].ch[0] = lval.ch[0]
|
|
lnt = lval.nt
|
|
if (lnt == 'LIST' and len(lval.ch) == 1
|
|
and lval.ch[0].t != 'list'):
|
|
# Finally, remove [] wrapper if it's not
|
|
# list within list
|
|
lval = child[0] = lval.ch[0]
|
|
lnt = lval.nt
|
|
if lnt == 'CONST' and len(lval.value) == 1:
|
|
# [constant] + list -> constant + list
|
|
lval.value = lval.value[0]
|
|
ltype = lval.t = lslcommon.PythonType2LSL[
|
|
type(lval.value)]
|
|
return
|
|
|
|
if optype == 'float' and rnt == 'CONST':
|
|
# Addition of floats is commutative.
|
|
# Put the constant first. May reduce stack.
|
|
lval, rval = child[0], child[1] = child[1], child[0]
|
|
lnt, rnt = rnt, lnt
|
|
ltype, rtype = rtype, ltype
|
|
|
|
if (self.addstrings and optype == 'string' and rnt == '+'
|
|
and rval.ch[0].nt == 'CONST' and lnt == 'CONST'
|
|
):
|
|
# We have CONST + (CONST + expr) of strings.
|
|
# Apply associativity to merge both constants.
|
|
|
|
# Add the constants
|
|
child[0].value = lslfuncs.add(child[0].value,
|
|
rval.ch[0].value)
|
|
# Prune the expr and graft it as RHS
|
|
child[1] = rval.ch[1]
|
|
# Re-optimize this node to apply it recursively
|
|
return self.FoldTree(parent, index)
|
|
|
|
# Nothing else to do with addition of float, string or list
|
|
return
|
|
|
|
# Must be two integers. This allows for a number of
|
|
# optimizations. First the most obvious ones.
|
|
assert optype == 'integer' # just to make sure
|
|
|
|
# Commutativity: place the constant first; may save stack and
|
|
# it helps simplifying
|
|
if rnt == 'CONST':
|
|
lval, rval = child[0], child[1] = child[1], child[0]
|
|
lnt, rnt = rnt, lnt
|
|
ltype, rtype = rtype, ltype
|
|
|
|
if lnt == 'CONST' and lval.value == 0:
|
|
# 0 + x = x
|
|
parent[index] = rval
|
|
return
|
|
|
|
if lnt == 'CONST' and rnt == '+' and rval.ch[0].nt == 'CONST':
|
|
# We have CONST + (CONST + expr)
|
|
# Apply associativity to merge both constants.
|
|
|
|
# Add the constants
|
|
lval.value = lslfuncs.add(lval.value, rval.ch[0].value)
|
|
# Prune the expr and graft it as RHS
|
|
child[1] = rval.ch[1]
|
|
|
|
# Re-optimize the result, to possibly apply -~ or ~- if
|
|
# appropriate.
|
|
return self.FoldTree(parent, index)
|
|
|
|
while lnt == 'CONST' and rnt == 'NEG' and rval.ch[0].nt == '~':
|
|
lval.value += 1
|
|
child[1] = rval.ch[0].ch[0]
|
|
# rtype doesn't change
|
|
assert child[1].t == 'integer'
|
|
self.FoldTree(parent, index)
|
|
node = parent[index]
|
|
nt, child = node.nt, node.ch
|
|
if nt != '+':
|
|
return
|
|
lval, rval = child[0], child[1]
|
|
lnt, rnt = lval.nt, rval.nt
|
|
ltype, rtype = lval.t, rval.t
|
|
|
|
while lnt == 'CONST' and rnt == '~' and rval.ch[0].nt == 'NEG':
|
|
lval.value -= 1
|
|
child[1] = rval.ch[0].ch[0]
|
|
# rtype doesn't change
|
|
assert child[1].t == 'integer'
|
|
self.FoldTree(parent, index)
|
|
node = parent[index]
|
|
nt, child = node.nt, node.ch
|
|
if nt != '+':
|
|
return
|
|
lval, rval = child[0], child[1]
|
|
lnt, rnt = lval.nt, rval.nt
|
|
ltype, rtype = lval.t, rval.t
|
|
|
|
if lnt != 'CONST':
|
|
# Neither is const.
|
|
# The case expr - expr -> 0 has been handled earlier
|
|
# because it's more general and applies to floats as well.
|
|
|
|
# -expr + -expr -> -(expr + expr) (saves 1 byte)
|
|
if lnt == rnt == 'NEG':
|
|
node = nr(nt='+', t=optype, ch=[lval.ch[0], rval.ch[0]],
|
|
SEF=lval.ch[0].SEF and rval.ch[0].SEF)
|
|
node = nr(nt='NEG', t=optype, ch=[node], SEF=node.SEF)
|
|
parent[index] = node
|
|
return
|
|
|
|
return
|
|
|
|
RSEF = rval.SEF
|
|
|
|
if lval.value == -1 or lval.value == -2:
|
|
if rnt == 'NEG': # Cancel the NEG
|
|
node = nr(nt='~', t=optype, ch=rval.ch, SEF=RSEF)
|
|
else: # Add the NEG
|
|
node = nr(nt='NEG', t=optype, ch=[rval], SEF=RSEF)
|
|
node = nr(nt='~', t=optype, ch=[node], SEF=RSEF)
|
|
if lval.value == -2:
|
|
node = nr(nt='NEG', t=optype, ch=[node], SEF=RSEF)
|
|
node = nr(nt='~', t=optype, ch=[node], SEF=RSEF)
|
|
parent[index] = node
|
|
return
|
|
|
|
if lval.value == 1 or lval.value == 2:
|
|
if rnt == '~': # Cancel the ~
|
|
node = nr(nt='NEG', t=optype, ch=rval.ch, SEF=RSEF)
|
|
else:
|
|
node = nr(nt='~', t=optype, ch=[rval], SEF=RSEF)
|
|
node = nr(nt='NEG', t=optype, ch=[node], SEF=RSEF)
|
|
if lval.value == 2:
|
|
node = nr(nt='~', t=optype, ch=[node], SEF=RSEF)
|
|
node = nr(nt='NEG', t=optype, ch=[node], SEF=RSEF)
|
|
parent[index] = node
|
|
return
|
|
|
|
# More than 2 becomes counter-productive.
|
|
|
|
return
|
|
|
|
if nt == '<<' and child[1].nt == 'CONST':
|
|
# Transforming << into multiply saves some bytes.
|
|
if child[1].value & 31:
|
|
# x << 3 --> x * 8
|
|
|
|
# we have {<<, something, {CONST n}}
|
|
# we transform it into {*, something, {CONST n}}
|
|
nt = node.nt = '*'
|
|
child[1].value = lslfuncs.S32(1 << (child[1].value & 31))
|
|
|
|
# Fall through to optimize product
|
|
|
|
else: # x << 0 --> x
|
|
parent[index] = child[0]
|
|
return
|
|
|
|
if (nt == '%' and child[1].nt == 'CONST'
|
|
and child[1].t == 'integer'
|
|
and abs(child[1].value) == 1):
|
|
# a%1 -> a&0
|
|
# a%-1 -> a&0
|
|
# (SEF analysis performed below)
|
|
nt = node.nt = '&'
|
|
child[1].value = 0
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
if nt in ('*', '/'):
|
|
# TODO: <0,0,0,1>*rot, rot*<0,0,0,1>, rot/<0,0,0,1> -> rot
|
|
# <0,0,0,1>/<x,y,z,s> -> <-x,-y,-z, s>
|
|
# <0,0,0>*vec -> 0 if SEF
|
|
# <0,0,0>*rot -> <0,0,0> if SEF
|
|
# Extract signs outside
|
|
if child[0].nt == 'NEG' or child[1].nt == 'NEG':
|
|
a, b = 0, 1
|
|
if child[b].nt == 'NEG':
|
|
a, b = 1, 0
|
|
child[a] = child[a].ch[0]
|
|
parent[index] = node = nr(nt='NEG', t=node.t, ch=[node],
|
|
SEF = node.SEF)
|
|
# Fold the new expression
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
# Deal with operands in any order
|
|
a, b = 0, 1
|
|
if child[a].nt == 'CONST' and child[a].t in ('float', 'integer'):
|
|
a, b = 1, 0
|
|
|
|
if child[b].nt == 'CONST':
|
|
val = child[b].value
|
|
|
|
# Optimize out signs if possible.
|
|
# Note that (-intvar)*floatconst needs cornermath because
|
|
# -intvar could equal intvar if intvar = -2147483648,
|
|
# so the sign is a no-op and pushing it to floatconst would
|
|
# make the result be different.
|
|
if (child[a].nt == 'NEG'
|
|
and (self.cornermath
|
|
or child[a].t != 'integer'
|
|
or child[b].t != 'float')
|
|
):
|
|
# Expression is of the form (-float)*const or (-float)/const or const/(-float)
|
|
if val != int(-2147483648) or child[a].t == 'integer': # can't be optimized otherwise
|
|
child[a] = child[a].ch[0] # remove NEG
|
|
child[b].value = val = -val
|
|
|
|
# Five optimizations corresponding to -2, -1, 0, 1, 2
|
|
# for product, and two for division:
|
|
# expr * 1 -> expr
|
|
# expr * 0 -> 0 if side-effect free
|
|
# expr * -1 -> -expr
|
|
# ident * 2 -> ident + ident (only if ident is local)
|
|
# ident * -2 -> -(ident + ident) (only if ident is local)
|
|
# expr/1 -> expr
|
|
# expr/-1 -> -expr
|
|
if (nt == '*' and child[b].t in ('float', 'integer')
|
|
and val in (-2, -1, 0, 1, 2)
|
|
or nt == '/'
|
|
and b == 1 and val in (-1, 1)
|
|
):
|
|
if val == 1:
|
|
parent[index] = self.Cast(child[a], optype)
|
|
self.FoldTree(parent, index)
|
|
return
|
|
if val == 0:
|
|
if child[a].SEF:
|
|
parent[index] = self.Cast(child[b], optype)
|
|
self.FoldTree(parent, index)
|
|
return
|
|
if val == -1:
|
|
# Note 0.0*-1 equals -0.0 in LSL, so this is safe
|
|
node = parent[index] = nr(nt='NEG', t=node.t,
|
|
ch=[self.Cast(child[a], optype)],
|
|
SEF=child[a].SEF)
|
|
self.FoldTree(parent, index)
|
|
return
|
|
# only -2, 2 remain
|
|
if child[a].nt == 'IDENT' and self.isLocalVar(child[a]):
|
|
child[b] = self.Cast(child[a].copy(), optype)
|
|
node.nt = '+'
|
|
if val == -2:
|
|
parent[index] = nr(nt='NEG', t=optype,
|
|
ch=[node], SEF=node.SEF)
|
|
self.FoldTree(parent, index)
|
|
return
|
|
return
|
|
|
|
if nt == '==':
|
|
if child[0].t == child[1].t == 'integer':
|
|
# Deal with operands in any order
|
|
a, b = 0, 1
|
|
if child[b].nt != 'CONST':
|
|
a, b = 1, 0
|
|
|
|
# a == -1 (in any order) -> !~a,
|
|
# a == 0 -> !a
|
|
# a == 1 -> !~-a
|
|
if child[b].nt == 'CONST':
|
|
if child[b].value in (-1, 0, 1):
|
|
node = child[a]
|
|
if child[b].value == -1:
|
|
node = nr(nt='~', t='integer', ch=[node],
|
|
SEF=node.SEF)
|
|
elif child[b].value == 1:
|
|
node = nr(nt='NEG', t='integer', ch=[node],
|
|
SEF=node.SEF)
|
|
node = nr(nt='~', t='integer', ch=[node],
|
|
SEF=node.SEF)
|
|
node = parent[index] = nr(nt='!', t='integer',
|
|
ch=[node], SEF=node.SEF)
|
|
# Can't delete
|
|
# See https://docs.python.org/2/reference/simple_stmts.html#del
|
|
child = None
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
# -a == -b -> a == b with const variations.
|
|
# Note this changes the sign of two CONSTs but that case
|
|
# should not reach here, as those are resolved earlier.
|
|
if ((child[0].nt == 'NEG' or child[0].nt == 'CONST')
|
|
and
|
|
(child[1].nt == 'NEG' or child[1].nt == 'CONST')
|
|
):
|
|
for a in (0, 1):
|
|
if child[a].nt == 'NEG':
|
|
child[a] = child[a].ch[0] # remove sign
|
|
else:
|
|
child[a].value = lslfuncs.neg(
|
|
child[a].value)
|
|
|
|
|
|
if self.CompareTrees(child[0], child[1]):
|
|
# expr == expr -> 1
|
|
# FIXME: not true if NaN
|
|
parent[index] = nr(nt='CONST', t='integer', value=1,
|
|
SEF=True)
|
|
return
|
|
# TODO: Simplify if ((x & y) == y) for constant y to if (!(~x & y))
|
|
return
|
|
|
|
if nt in ('<=', '>=') or nt == '!=' and child[0].t != 'list':
|
|
# Except for list != list, all these comparisons are compiled
|
|
# as !(a>b) etc. so we transform them here in order to reduce
|
|
# the number of cases to check.
|
|
# a<=b --> !(a>b); a>=b --> !(a<b); a!=b --> !(a==b)
|
|
node.nt = {'<=':'>', '>=':'<', '!=':'=='}[nt]
|
|
parent[index] = nr(nt='!', t=node.t, ch=[node])
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
if nt == '>' and (child[0].SEF and child[1].SEF
|
|
or child[0].nt == 'CONST'
|
|
or child[1].nt == 'CONST'
|
|
):
|
|
# Invert the inequalities to avoid doubling the cases to check.
|
|
# a>b -> b<a
|
|
nt = node.nt = '<'
|
|
child[1], child[0] = child[0], child[1]
|
|
# fall through to check for '<'
|
|
|
|
if nt == '<':
|
|
# expr < expr -> 0
|
|
if self.CompareTrees(child[0], child[1]):
|
|
parent[index] = nr(nt='CONST', t='integer', value=0,
|
|
SEF=True)
|
|
return
|
|
|
|
if child[0].SEF and child[1].SEF:
|
|
lmin = self.getMin(child[0])
|
|
lmax = self.getMax(child[0])
|
|
rmin = self.getMin(child[1])
|
|
rmax = self.getMax(child[1])
|
|
# when lmax < rmin: always true
|
|
if (rmin is not None and lmax is not None
|
|
and lslfuncs.less(lmax, rmin)
|
|
):
|
|
parent[index] = nr(nt='CONST', t='integer',
|
|
value=1, SEF=True)
|
|
return
|
|
# when lmin >= rmax: always false
|
|
if (rmax is not None and lmin is not None
|
|
and not lslfuncs.less(lmin, rmax)
|
|
):
|
|
parent[index] = nr(nt='CONST', t='integer',
|
|
value=0, SEF=True)
|
|
return
|
|
del lmin, lmax, rmin, rmax
|
|
|
|
# Convert 2147483647<i and i<-2147483648 to i&0
|
|
if (child[0].t == child[1].t == 'integer'
|
|
and (child[0].nt == 'CONST'
|
|
and child[0].value == 2147483647
|
|
or child[1].nt == 'CONST'
|
|
and child[1].value == int(-2147483648))
|
|
):
|
|
a, b = 0, 1
|
|
# Put the constant in child[b]
|
|
if child[a].nt == 'CONST':
|
|
a, b = b, a
|
|
nt = node.nt = '&'
|
|
child[b].value = 0
|
|
# fall through to check for '&'
|
|
else:
|
|
return
|
|
|
|
if nt in ('&', '|'):
|
|
# expr & expr -> expr
|
|
# expr | expr -> expr
|
|
if self.CompareTrees(child[0], child[1]):
|
|
parent[index] = child[0]
|
|
return
|
|
|
|
# Deal with operands in any order
|
|
a, b = 0, 1
|
|
# Put constant in child[b]
|
|
if child[b].nt != 'CONST':
|
|
a, b = 1, 0
|
|
|
|
if child[b].nt == 'CONST':
|
|
val = child[b].value
|
|
if (nt == '|' and val == 0
|
|
or nt == '&'
|
|
and (val == -1
|
|
or val == 1 and self.IsBool(child[a]))
|
|
):
|
|
# a|0 -> a
|
|
# a&-1 -> a
|
|
# a&1 -> a if a is boolean
|
|
parent[index] = child[a]
|
|
return
|
|
if (nt == '|'
|
|
and (val == -1
|
|
or (val & 1) == 1 and self.IsBool(child[a]))
|
|
or nt == '&' and val == 0
|
|
):
|
|
# a|-1 -> -1 if a is SEF
|
|
# a|C -> C if bit 0 of C is 1 and a is bool and SEF
|
|
# a&0 -> 0 if a is SEF
|
|
if child[a].SEF:
|
|
parent[index] = child[b]
|
|
|
|
# Apply boolean distributivity
|
|
applied = False
|
|
opposite = '&' if nt == '|' else '|'
|
|
if child[0].nt == child[1].nt == opposite:
|
|
left = child[0].ch
|
|
right = child[1].ch
|
|
# Can't loop individually because we must break out of both
|
|
for c, d in ((0, 0), (0, 1), (1, 0), (1, 1)):
|
|
if self.CompareTrees(left[c], right[d]):
|
|
child[1].nt = nt
|
|
nt = node.nt = opposite
|
|
opposite = child[1].nt
|
|
right[d] = left[1 - c]
|
|
child[0] = left[c]
|
|
applied = True
|
|
break
|
|
|
|
# Apply absorption, possibly after distributivity
|
|
if child[0].nt == opposite or child[1].nt == opposite:
|
|
c = 0 if child[1].nt == opposite else 1
|
|
for d in (0, 1):
|
|
if (self.CompareTrees(child[c], child[1 - c].ch[d])
|
|
and child[1 - c].ch[1 - d].SEF
|
|
):
|
|
node = parent[index] = child[c]
|
|
nt = node.nt
|
|
child = node.ch
|
|
applied = True
|
|
break
|
|
|
|
if applied:
|
|
# Re-fold
|
|
self.FoldTree(parent, index)
|
|
|
|
return
|
|
|
|
if nt == '^':
|
|
# expr ^ expr -> 0
|
|
if self.CompareTrees(child[0], child[1]):
|
|
parent[index] = nr(nt='CONST', t='integer', value=0,
|
|
SEF=True)
|
|
return
|
|
a, b = 0, 1
|
|
if child[a].nt == 'CONST':
|
|
a, b = 1, 0
|
|
if child[b].nt == 'CONST' and child[b].value in (0, -1):
|
|
if child[b].value == 0:
|
|
parent[index] = child[a]
|
|
else:
|
|
node.nt = '~'
|
|
node.ch = [child[a]]
|
|
return
|
|
|
|
if nt == '||':
|
|
# Expand to its equivalent a || b -> !!(a | b)
|
|
node = nr(nt='|', t='integer', ch=[child[0], child[1]],
|
|
SEF=child[0].SEF and child[1].SEF)
|
|
node = nr(nt='!', t='integer', ch=[node], SEF=node.SEF)
|
|
node = nr(nt='!', t='integer', ch=[node], SEF=node.SEF)
|
|
parent[index] = node
|
|
# Make another pass with the substitution
|
|
self.FoldTree(parent, index)
|
|
elif nt == '&&':
|
|
# Expand to its equivalent a && b -> !(!a | !b)
|
|
orchildren = [
|
|
nr(nt='!', t='integer', ch=[child[0]], SEF=child[0].SEF),
|
|
nr(nt='!', t='integer', ch=[child[1]], SEF=child[1].SEF)
|
|
]
|
|
node = nr(nt='|', t='integer', ch=orchildren,
|
|
SEF=child[0].SEF and child[1].SEF)
|
|
node = nr(nt='!', t='integer', ch=[node], SEF=node.SEF)
|
|
parent[index] = node
|
|
# Make another pass with the substitution
|
|
self.FoldTree(parent, index)
|
|
|
|
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...
|
|
# e.g. a += b -> a + b
|
|
node.nt = nt[:-1]
|
|
|
|
# Linden Craziness: int *= float; is valid (but no other
|
|
# int op= float is). It's actually performed as
|
|
# i = (integer)(i + (f));
|
|
# This breaks equivalence of x op= y as x = x op (y) so we add
|
|
# the explicit 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] = nr(nt='=', t=child[0].t, ch=child)
|
|
|
|
# We have a regular assignment either way now. Simplify the RHS.
|
|
self.FoldTree(node.ch, 1)
|
|
chkequal = child[1].ch[0] if child[1].nt == '=' else child[1]
|
|
if (child[0].nt == chkequal.nt == 'IDENT'
|
|
and chkequal.name == child[0].name
|
|
and chkequal.scope == child[0].scope
|
|
or child[0].nt == chkequal.nt == 'FLD'
|
|
and chkequal.ch[0].name == child[0].ch[0].name
|
|
and chkequal.ch[0].scope == child[0].ch[0].scope
|
|
and chkequal.fld == child[0].fld
|
|
):
|
|
parent[index] = child[1]
|
|
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 defn.ch:
|
|
val = defn.ch[0]
|
|
if val.nt != 'CONST' or ident.t == 'key':
|
|
return
|
|
val = val.copy()
|
|
else:
|
|
val = nr(nt='CONST', t=defn.t,
|
|
value=self.DefaultValues[defn.t], SEF=True)
|
|
if nt == 'FLD':
|
|
val = nr(nt='CONST', t='float',
|
|
value=val.value['xyzs'.index(node.fld)], SEF=True)
|
|
parent[index] = val
|
|
return
|
|
|
|
if nt == 'FNCALL':
|
|
name = node.name
|
|
|
|
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
|
|
SEFargs = SEFargs and child[idx].SEF
|
|
# Function is not a constant if any argument is not a constant
|
|
CONSTargs = CONSTargs and child[idx].nt == 'CONST'
|
|
|
|
sym = self.symtab[0][name]
|
|
if 'min' in sym:
|
|
node.min = sym['min']
|
|
if 'max' in sym:
|
|
node.max = sym['max']
|
|
OptimizeArgs(node, sym)
|
|
try:
|
|
if 'Fn' in sym and (self.FnSEF(node) or lslcommon.IsCalc):
|
|
# It's side-effect free if the children are and the function
|
|
# is marked as SEF.
|
|
if SEFargs:
|
|
node.SEF = True
|
|
if CONSTargs:
|
|
# Call it
|
|
fn = sym['Fn']
|
|
args = [arg.value for arg in child]
|
|
assert len(args) == len(sym['ParamTypes'])
|
|
|
|
try:
|
|
# May raise ELSLCantCompute
|
|
if 'detect' in self.symtab[0][name]:
|
|
value = fn(*args,
|
|
evsym=None if self.CurEvent is None
|
|
else self.events[self.CurEvent])
|
|
else:
|
|
value = fn(*args)
|
|
finally:
|
|
del args
|
|
|
|
if not self.foldtabs:
|
|
generatesTabs = (
|
|
isinstance(value, unicode) and u'\t' in value
|
|
or type(value) == list
|
|
and any(isinstance(x, unicode)
|
|
and u'\t' in x for x in value)
|
|
)
|
|
if generatesTabs:
|
|
if self.warntabs:
|
|
warning(u"Can't optimize call to %s"
|
|
u" because it would generate a tab"
|
|
u" character (you can force the "
|
|
u" optimization with the 'foldtabs'"
|
|
u" option, or disable this warning by"
|
|
u" disabling the 'warntabs' option)."
|
|
% name.decode('utf8'))
|
|
raise lslfuncs.ELSLCantCompute()
|
|
# Replace with a constant
|
|
parent[index] = nr(nt='CONST', t=node.t, value=value,
|
|
SEF=True)
|
|
return
|
|
|
|
elif SEFargs and 'SEF' in self.symtab[0][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
|
|
|
|
except lslfuncs.ELSLCantCompute:
|
|
# Don't transform the tree if function is not computable
|
|
pass
|
|
|
|
# At this point, we have resolved whether the function is SEF,
|
|
# or whether the function resolves to a constant.
|
|
OptimizeFunc(self, parent, index)
|
|
|
|
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)
|
|
node.SEF = child[0].SEF
|
|
return
|
|
|
|
if nt == 'FNDEF':
|
|
# FIXME: Fix SEFness of UDFs
|
|
# A return statement does have side effects for the current
|
|
# function, as removing it would change its behaviour drastically.
|
|
# However, when seen from the outside, that does not make the
|
|
# function as a whole have side effects: if all nodes except
|
|
# return statements are SEF, the function is SEF.
|
|
|
|
# CurEvent is needed when folding llDetected* function calls
|
|
if hasattr(node, 'scope'):
|
|
# function definition
|
|
self.CurEvent = None
|
|
else:
|
|
# event definition
|
|
self.CurEvent = node.name
|
|
self.FoldTree(child, 0)
|
|
|
|
# Test if the event is SEF and does nothing, and remove it if so.
|
|
if (not hasattr(node, 'scope') and child[0].SEF
|
|
and 'SEF' in self.events[node.name]
|
|
):
|
|
# Delete ourselves.
|
|
del parent[index]
|
|
return
|
|
|
|
# Delete trailing bare RETURNs.
|
|
# TODO: This works, but analysis of code paths is DCR's thing
|
|
# and this is incomplete, e.g. x(){{return;}} is not detected.
|
|
while child[0].ch:
|
|
last = child[0].ch[-1]
|
|
if last.nt != 'RETURN' or last.ch:
|
|
break
|
|
del child[0].ch[-1]
|
|
if child[0].SEF:
|
|
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)):
|
|
self.FoldTree(child, idx)
|
|
isconst = isconst and child[idx].nt == 'CONST'
|
|
issef = issef and child[idx].SEF
|
|
|
|
if isconst:
|
|
value = [x.value for x in child]
|
|
if nt == 'VECTOR':
|
|
value = Vector([lslfuncs.ff(x) for x in value])
|
|
elif nt == 'ROTATION':
|
|
value = Quaternion([lslfuncs.ff(x) for x in value])
|
|
parent[index] = nr(nt='CONST', t=node.t, value=value, SEF=True)
|
|
return
|
|
node.SEF = issef
|
|
return
|
|
|
|
if nt == 'STDEF':
|
|
for idx in xrange(len(child) - 1, -1, -1):
|
|
self.FoldTree(child, idx)
|
|
if not child:
|
|
# All events removed - add a dummy timer()
|
|
scope = len(self.symtab)
|
|
self.symtab.append({})
|
|
child.append(nr(nt='FNDEF', t=None, name='timer',
|
|
pscope=scope, ptypes=[], pnames=[],
|
|
ch=[nr(nt='{}', t=None, scope=scope, ch=[])]
|
|
))
|
|
return
|
|
|
|
if nt == '{}':
|
|
# Remove SEF statements, and mark as SEF if it ends up empty
|
|
idx = 0
|
|
nchild = len(child)
|
|
while idx < nchild:
|
|
self.FoldTree(child, idx)
|
|
self.FoldStmt(child, idx)
|
|
if child[idx].SEF:
|
|
# SEF statements can be removed
|
|
del child[idx]
|
|
nchild -= 1
|
|
else:
|
|
idx += 1
|
|
# Make another pass to remove JUMPs to the next statement
|
|
changed = True # Allow entering the loop
|
|
while changed:
|
|
changed = False
|
|
idx = 0
|
|
while idx < nchild:
|
|
advance = 1
|
|
if child[idx].nt == 'JUMP':
|
|
idx2 = idx + 1
|
|
while idx2 < nchild:
|
|
# Search for a label that is the destination of
|
|
# this JUMP, skipping other labels
|
|
if child[idx2].nt != '@':
|
|
break
|
|
if (child[idx].scope == child[idx2].scope
|
|
and child[idx].name == child[idx2].name
|
|
):
|
|
sym = self.symtab[child[idx].scope]
|
|
sym = sym[child[idx].name]
|
|
# remove the JUMP
|
|
del child[idx]
|
|
advance = 0
|
|
changed = True
|
|
idx2 -= 1 # it has scrolled
|
|
nchild -= 1
|
|
# remove reference to label
|
|
assert(sym['ref'])
|
|
sym['ref'] -= 1
|
|
if sym['ref'] == 0:
|
|
# No longer referenced - delete label too
|
|
del child[idx2]
|
|
nchild -= 1
|
|
break
|
|
idx2 += 1
|
|
del idx2
|
|
idx += advance
|
|
|
|
# We're SEF if we're empty, as we've removed all SEF statements
|
|
node.SEF = nchild == 0
|
|
return
|
|
|
|
if nt == 'IF':
|
|
self.ExpandCondition(child, 0)
|
|
self.FoldTree(child, 0)
|
|
self.FoldAsBool(child, 0)
|
|
if child[0].nt == 'CONST':
|
|
# We might be able to remove one of the branches.
|
|
if lslfuncs.cond(child[0].value):
|
|
self.FoldTree(child, 1)
|
|
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] = nr(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)
|
|
# Check if it makes sense to swap if and else branches
|
|
if self.ifelseswap and not child[2].SEF:
|
|
# Check if we can gain something by negating the
|
|
# expression.
|
|
# Swap 'if' and 'else' branch when the condition has
|
|
# a '!' prefix
|
|
if child[0].nt == '!':
|
|
child[0] = child[0].ch[0]
|
|
child[1], child[2] = child[2], child[1]
|
|
# Swap them if condition is '==' with integer operands
|
|
if (child[0].nt == '=='
|
|
and child[0].ch[0].t
|
|
== child[0].ch[1].t == 'integer'
|
|
):
|
|
child[0].nt = '^'
|
|
child[1], child[2] = child[2], child[1]
|
|
# Re-test just in case we swapped in the previous check.
|
|
if child[2].SEF:
|
|
# no point in "... else ;" - remove else branch
|
|
del child[2]
|
|
if child[1].SEF:
|
|
# if (X) ; -> X;
|
|
if len(child) == 2:
|
|
parent[index] = nr(nt='EXPR', t=child[0].t,
|
|
ch=[child[0]], SEF=child[0].SEF)
|
|
# It has been promoted to statement. Fold it as such.
|
|
# (Will remove it if SEF)
|
|
self.FoldStmt(parent, index)
|
|
return
|
|
|
|
# If type(X) != Key, then:
|
|
# if (X) ; else {stuff} -> if (!X) {stuff}
|
|
if child[0].t != 'key':
|
|
# We've already converted all other types to equivalent
|
|
# comparisons
|
|
assert child[0].t == 'integer'
|
|
child[0] = nr(nt='!', t='integer', ch=[child[0]])
|
|
del child[1]
|
|
self.FoldTree(child, 0)
|
|
self.FoldAsBool(child, 0)
|
|
|
|
if all(subnode.SEF 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
|
|
# out anyway. Otherwise we just don't know if it may be infinite,
|
|
# even if every component is SEF.
|
|
|
|
if not child[1].SEF:
|
|
|
|
self.ExpandCondition(child, 0)
|
|
self.FoldTree(child, 0)
|
|
self.FoldAsBool(child, 0)
|
|
if child[0].nt == 'CONST':
|
|
# See if the whole WHILE can be eliminated.
|
|
if not lslfuncs.cond(child[0].value):
|
|
# Whole statement can be removed.
|
|
parent[index] = nr(nt=';', t=None, SEF=True)
|
|
return
|
|
self.FoldTree(child, 1)
|
|
self.FoldStmt(child, 1)
|
|
return
|
|
|
|
# It does nothing - Turn it into a do..while
|
|
nt = node.nt = 'DO'
|
|
child[0], child[1] = child[1], child[0]
|
|
|
|
# Fall through to optimize as DO..WHILE
|
|
|
|
if nt == 'DO':
|
|
self.FoldTree(child, 0) # This one is always executed.
|
|
self.FoldStmt(child, 0)
|
|
self.ExpandCondition(child, 1)
|
|
self.FoldTree(child, 1)
|
|
self.FoldAsBool(child, 1)
|
|
# See if the latest part is a constant.
|
|
if child[1].nt == 'CONST':
|
|
if not lslfuncs.cond(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.ExpandCondition(child, 1) # Condition.
|
|
self.FoldTree(child, 1)
|
|
self.FoldAsBool(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. They are expressions, no
|
|
# declarations or labels allowed, thus no new identifiers may
|
|
# be created in the new scope.
|
|
if lslfuncs.cond(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:
|
|
# Loop never executes.
|
|
# Convert expression list to code block.
|
|
exprlist = []
|
|
for expr in child[0].ch:
|
|
# Fold into expression statements.
|
|
exprlist.append(nr(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.
|
|
scope = len(self.symtab)
|
|
self.symtab.append({})
|
|
parent[index] = nr(nt='{}', t=None, scope=scope,
|
|
ch=exprlist)
|
|
else:
|
|
parent[index] = nr(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 getattr(child[0], 'Simple', False):
|
|
orig = self.CopyNode(child[0])
|
|
del orig.Simple # presence of orig in child will be enough
|
|
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
|
|
):
|
|
node.ch = None
|
|
return
|
|
else:
|
|
# Add assignment if vector, rotation or float.
|
|
if node.t in ('float', 'vector', 'rotation'):
|
|
typ = node.t
|
|
node.ch = [nr(nt='CONST', t=typ, SEF=True,
|
|
value=0.0 if typ == 'float'
|
|
else ZERO_VECTOR if typ == 'vector'
|
|
else ZERO_ROTATION)]
|
|
# Declarations always have side effects.
|
|
return
|
|
|
|
if nt == 'STSW':
|
|
# State switch always has side effects.
|
|
return
|
|
|
|
if nt == 'SUBIDX':
|
|
# Recurse to every child. It's SEF if all children are.
|
|
idx = 0
|
|
issef = True
|
|
while idx < len(child):
|
|
self.FoldTree(child, idx)
|
|
issef = issef and child[idx].SEF
|
|
idx += 1
|
|
node.SEF = issef
|
|
return
|
|
|
|
if nt == ';':
|
|
node.SEF = True
|
|
return
|
|
|
|
if nt == '@':
|
|
# SEF if there are no JUMPs jumping to it
|
|
node.SEF = not self.symtab[node.scope][node.name]['ref']
|
|
return
|
|
|
|
if nt in ('JUMP', 'V++', 'V--', '--V', '++V', 'LAMBDA'):
|
|
# Except LAMBDA, these all have side effects, as in, can't be
|
|
# eliminated as statements.
|
|
# LAMBDA can't be eliminated without scrolling Loc's.
|
|
return
|
|
|
|
assert False, 'Internal error: This should not happen, node type = ' \
|
|
+ nt # pragma: no cover
|
|
|
|
def IsValidGlobalIdOrConst(self, node):
|
|
# nan can't be represented as a simple constant; all others are valid
|
|
return not (node.nt == 'CONST' and node.t == 'float'
|
|
and math.isnan(node.value))
|
|
|
|
def IsValidGlobalConstant(self, decl):
|
|
if decl.ch is None:
|
|
return True
|
|
expr = decl.ch[0]
|
|
if expr.nt in ('CONST', 'IDENT'):
|
|
return self.IsValidGlobalIdOrConst(expr)
|
|
if expr.nt not in ('VECTOR', 'ROTATION', 'LIST'):
|
|
return False
|
|
return all(elem.nt in ('CONST', 'IDENT')
|
|
and self.IsValidGlobalIdOrConst(elem)
|
|
for elem in expr.ch)
|
|
|
|
def FoldScript(self, warningpass = True):
|
|
"""Optimize the symbolic table symtab in place. Requires a table of
|
|
predefined functions for folding constants.
|
|
"""
|
|
self.globalmode = False
|
|
|
|
tree = self.tree
|
|
self.CurEvent = None
|
|
|
|
FuncOptSetup()
|
|
|
|
# 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 warningpass and not self.IsValidGlobalConstant(tree[idx]):
|
|
warning(u"Expression in globals doesn't resolve to a simple constant.")
|
|
else:
|
|
self.FoldTree(tree, idx)
|