Files
LSL-PyOptimizer/lslopt/lslinliner.py
Sei Lisa 0ffe823c18 Move symbol existence check and creation to newId
ENameAlreadyExists has also been expanded to accept an inliner object, even though it's not used yet. Plans are the same as in EParse.
2019-01-06 01:33:49 +01:00

434 lines
15 KiB
Python

# (C) Copyright 2015-2019 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/>.
# Expand inlined functions. This could perhaps be made at parse time, but that
# would obfuscate the source too much.
from lslcommon import nr
# Statement-level nodes that have at most 1 child and is of type expression
SINGLE_OPT_EXPR_CHILD_NODES = frozenset({'DECL', 'EXPR', 'RETURN',
'@', 'STSW', 'JUMP', ';', 'LAMBDA'})
# TODO: We can do a bit better with evaluation order.
class ENameAlreadyExists(Exception):
def __init__(self, obj, text):
super(ENameAlreadyExists, self).__init__(text)
class EExpansionLoop(Exception):
def __init__(self):
super(EExpansionLoop, self).__init__(u"Loop found in expansion of"
u" inline functions")
class inliner(object):
def newId(self, namespace, scope, symdata):
"""Create a new identifier based on a namespace."""
self.symCtr[namespace] = self.symCtr.get(namespace, 0) + 1
name = '___%s__%05d' % (namespace, self.symCtr[namespace])
if name in self.symtab[scope]:
kinds = {'l':u"Label", 'f':u"Function", 'v':u"Variable",
's':u"State"}
raise ENameAlreadyExists(self, u"%s already exists: %s"
% (kinds[symdata['Kind']], name.decode('utf8')))
self.symtab[scope][name] = symdata
return name
def newSymtab(self):
self.symtab.append({})
return len(self.symtab) - 1
def FixJumps(self, node):
"""Change name and scope of JUMPs to point to the correct symtab entry
"""
nt = node.nt
if nt == 'JUMP':
orig = self.symtab[node.scope][node.name]
if 'NewSymbolName' in orig:
node.name = orig['NewSymbolName']
node.scope = orig['NewSymbolScope']
self.symtab[node.scope][node.name]['ref'] += 1
return
if nt in SINGLE_OPT_EXPR_CHILD_NODES:
return
if nt == '{}':
for i in node.ch:
self.FixJumps(i)
return
if nt == 'IF' or nt == 'WHILE':
self.FixJumps(node.ch[1])
if len(node.ch) > 2:
self.FixJumps(node.ch[2])
return
if nt == 'DO':
self.FixJumps(node.ch[0])
return
if nt == 'FOR':
self.FixJumps(node.ch[3])
return
assert False, u"Unexpected node type: %s" % nt.decode('utf8')
def GetFuncCopy(self, node, scope=0):
"""Get a copy of the function's body
Replaces 'return expr' with assignment+jump, 'return' with jump, and
existing labels with fresh labels. Also creates new symtabs for locals
and adjusts scopes of symbols.
"""
nt = node.nt
if nt == 'FNDEF':
# We're at the top level. Check return type and create a label,
# then recurse into the block.
assert node.ch[0].nt == '{}'
copy = self.GetFuncCopy(node.ch[0], node.ch[0].scope)
assert copy.nt == '{}'
self.FixJumps(copy)
return copy
if nt == '{}':
copy = node.copy()
copy.scope = self.newSymtab()
copy.ch = []
for i in node.ch:
copy.ch.append(self.GetFuncCopy(i, node.scope))
if i.nt == 'DECL':
self.symtab[copy.scope][i.name] = \
self.symtab[i.scope][i.name].copy()
self.symtab[node.scope][i.name]['NewSymbolScope'] = \
copy.scope
copy.ch[-1].scope = copy.scope
return copy
if nt == '@':
copy = node.copy()
oldscope = node.scope
oldname = node.name
copy.name = self.newId('lbl', scope, {'Type':'l', 'Scope':scope,
'ref':0})
copy.scope = scope
self.symtab[oldscope][oldname]['NewSymbolName'] = copy.name
self.symtab[oldscope][oldname]['NewSymbolScope'] = scope
return copy
if nt == 'RETURN':
newnode = nr(nt='JUMP', t=None, name=self.retlabel,
scope=self.retlscope)
self.symtab[self.retlscope][self.retlabel]['ref'] += 1
if node.ch:
# Returns a value. Wrap in {} and add an assignment.
# BUG: We don't honour ExplicitCast here.
newnode = nr(nt='{}', t=None, scope=self.newSymtab(), ch=[
nr(nt='EXPR', t=self.rettype, ch=[
nr(nt='=', t=self.rettype, ch=[
nr(nt='IDENT', t=node.ch[0].t,
name=self.retvar, scope=self.retscope)
, self.GetFuncCopy(node.ch[0])
])
]), newnode
])
return newnode
if nt == 'IDENT':
copy = node.copy()
if 'NewSymbolScope' in self.symtab[node.scope][node.name]:
copy.scope = \
self.symtab[node.scope][node.name]['NewSymbolScope']
return copy
if not node.ch:
return node.copy()
copy = node.copy()
copy.ch = []
for i in node.ch:
copy.ch.append(self.GetFuncCopy(i, scope))
return copy
def ConvertFunction(self, parent, index, scope):
node = parent[index]
fns = []
for i in range(len(node.ch)):
fns.extend(self.RecurseExpression(node.ch, i, scope))
fnsym = self.symtab[0][node.name]
rettype = fnsym['Type']
self.rettype = rettype
retvar = None
if rettype is not None:
# Returns a value. Create a local variable at the starting level.
retvar = self.newId('ret', scope, {'Kind':'v', 'Scope':scope,
'Type':rettype})
# Add the declaration to the list of statements
fns.append(nr(nt='DECL', t=rettype, name=retvar, scope=scope))
# Begin expansion
if node.name in self.expanding:
raise EExpansionLoop()
outer = None
if fnsym['ParamNames']:
# Add a new block + symbols + assignments for parameter values
pscope = self.newSymtab()
outer = nr(nt='{}', t=None, scope=pscope, ch=[])
origpscope = self.tree[fnsym['Loc']].pscope
for i in range(len(fnsym['ParamNames'])):
# Add parameter assignments and symbol table entries
pname = fnsym['ParamNames'][i]
ptype = fnsym['ParamTypes'][i]
value = node.ch[i]
self.symtab[pscope][pname] = {'Kind':'v','Type':ptype,
'Scope':pscope}
self.symtab[origpscope][pname]['NewSymbolScope'] = pscope
# BUG: We don't honour ExplicitCast here.
outer.ch.append(nr(nt='DECL', t=ptype, name=pname, scope=pscope,
ch=[value]))
self.expanding.append(node.name)
self.retvar = retvar
self.retscope = scope
self.retlscope = scope
retlabel = self.newId('rtl', scope, {'Type':'l', 'Scope':scope,
'ref':0})
self.retlabel = retlabel
# Get a copy of the function
blk = [self.GetFuncCopy(self.tree[fnsym['Loc']])]
if outer:
outer.ch.extend(blk)
blk = [outer]
retused = self.symtab[scope][retlabel]['ref'] != 0
self.RecurseStatement(blk, 0, scope) # recursively expand functions
# Add return label if used, otherwise remove it from the symbol table
if retused:
blk.append(nr(nt='@', name=retlabel, scope=scope))
else:
del self.symtab[scope][retlabel]
self.expanding.pop()
# End expansion
fns.extend(blk)
if rettype is None:
del parent[index]
else:
parent[index] = nr(nt='IDENT', t=rettype, name=retvar, scope=scope)
return fns
def RecurseExpression(self, parent, index, scope):
node = parent[index]
nt = node.nt
fns = []
if nt == 'FNCALL' and self.symtab[0][node.name].get('Inline', False):
fns.extend(self.ConvertFunction(parent, index, scope))
elif node.ch:
for i in range(len(node.ch)):
fns.extend(self.RecurseExpression(node.ch, i, scope))
return fns
def RecurseSingleStatement(self, parent, index, scope):
# Synthesize a block node whose child is the statement.
newscope = self.newSymtab()
node = nr(nt='{}', t=None, scope=newscope, ch=[parent[index]],
SEF=parent[index].SEF)
# Recurse into that node, so that any additions are made right there.
self.RecurseStatement(node.ch, 0, newscope)
# If it's no longer a single statement, promote it to a block.
if len(node.ch) != 1:
parent[index] = node
else:
# The new level won't be necessary. We can't delete the symbol
# table, though, because that shifts any new symbol tables.
assert not self.symtab[newscope]
parent[index] = node.ch[0]
def RecurseStatement(self, parent, index, scope):
node = parent[index]
nt = node.nt
child = node.ch
fns = None
if nt in SINGLE_OPT_EXPR_CHILD_NODES:
if child:
fns = self.RecurseExpression(child, 0, scope)
if nt == 'EXPR' and not node.ch:
del parent[index]
else:
return
elif nt == '{}':
i = -len(child)
while i:
self.RecurseStatement(child, len(child)+i, node.scope)
i += 1
elif nt == 'IF':
fns = self.RecurseExpression(child, 0, scope)
self.RecurseSingleStatement(child, 1, scope)
if len(child) > 2:
self.RecurseSingleStatement(child, 2, scope)
# TODO: Handle loops properly
# Consider this:
#
# integer f()
# {
# llOwnerSay("body of f");
# return 1;
# }
#
# while (f())
# llOwnerSay("doing stuff");
#
# In order to execute it every loop iteration, the while() loop must be
# converted to an if+jump:
#
# integer ___ret__00001;
# @___lbl__00001;
# {
# llOwnerSay("body_of_f");
# __ret__00001 = 1;
# }
# if (___ret__00001)
# {
# llOwnerSay("doing stuff");
# jump ___lbl__00001;
# }
#
# The for loop is similar, but the initializer and iterator must be
# expanded as well, to convert it to a while loop. When expanding the
# iterator, care must be taken to avoid name clashes. For example:
#
# for (i = 0; f(i); i++)
# {
# integer i;
# }
#
# should be expanded to:
#
# i = 0;
# @loop_label;
# integer ___ret__00001;
# {
# llOwnerSay("body of f");
# ___ret__00001 = 1,
# }
# if (___ret__00001)
# {
# {
# integer i;
# }
# i++;
# }
#
# The extra {} inside the 'if' are needed to protect the iterator from
# being affected by the variables defined in the inner block.
#
# Do loops are different:
#
# do
# llOwnerSay("doing stuff");
# while (f());
#
# is converted to:
#
# integer ___ret__00001;
# do
# {
# llOwnerSay("doing stuff");
# {
# llOwnerSay("body_of_f");
# ___ret__00001 = 1;
# }
# }
# while (___ret__00001);
#
elif nt == 'DO':
self.RecurseSingleStatement(child, 0, scope)
fns = self.RecurseExpression(child, 1, scope)
if fns:
# Need to do some plumbing to move the bodies into the loop
i = 0;
while i < len(fns):
if fns[i].nt != 'DECL':
assert fns[i].nt in ('{}', '@')
# All bodies must be moved inside the loop
if child[0].nt != '{}':
# Needs wrapping now
child[0] = nr(nt='{}', t=None, ch=[child[0]],
scope=self.newSymtab())
child[0].ch.append(fns.pop(i))
else:
i += 1
elif nt == 'WHILE':
fns = self.RecurseExpression(child, 0, scope)
self.RecurseSingleStatement(child, 1, scope)
elif nt == 'FOR':
assert child[0].nt == 'EXPRLIST'
assert child[2].nt == 'EXPRLIST'
fns = []
for i in range(len(child[0].ch)):
fns.extend(self.RecurseExpression(child[0].ch, i, scope))
fns.extend(self.RecurseExpression(child, 1, scope))
for i in range(len(child[2].ch)):
fns.extend(self.RecurseExpression(child[2].ch, i, scope))
self.RecurseSingleStatement(child, 3, scope)
else:
assert False, u"Unexpected node type: %s" % nt.decode('utf8')
if fns:
parent[index:index] = fns
def inline(self, tree, symtab):
self.tree = tree
self.symtab = symtab
self.symCtr = {}
self.expanding = []
for i in range(len(tree)):
if tree[i].nt == 'STDEF':
for j in range(len(tree[i].ch)): # for each event in the state
self.RecurseStatement(tree[i].ch[j].ch, 0,
tree[i].ch[j].ch[0].scope)
elif (tree[i].nt == 'FNDEF'
and not symtab[tree[i].scope][tree[i].name].get('Inline',
False)
):
# Must be an UDF
self.RecurseStatement(tree[i].ch, 0, tree[i].ch[0].scope)
# Remove all inline function definitions
for i in range(len(tree)):
if (tree[i].nt == 'FNDEF'
and symtab[tree[i].scope][tree[i].name].get('Inline', False)
):
tree[i] = nr(nt='LAMBDA', t=None)