mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-01 07:38:21 +00:00
Add 'inline' directive to forcibly inline function calls
This commit is contained in:
parent
7bb07ecf38
commit
a4986f21df
3 changed files with 398 additions and 6 deletions
374
lslopt/lslinliner.py
Normal file
374
lslopt/lslinliner.py
Normal file
|
@ -0,0 +1,374 @@
|
|||
# (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'})
|
||||
|
||||
class ENameAlreadyExists(Exception): pass
|
||||
|
||||
class EExpansionLoop(Exception):
|
||||
def __init__(self):
|
||||
super(EExpansionLoop, self).__init__(u"Loop found in expansion of"
|
||||
u" inline functions")
|
||||
|
||||
class inliner(object):
|
||||
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']
|
||||
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.
|
||||
"""
|
||||
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 = len(self.symtab)
|
||||
copy.ch = []
|
||||
self.symtab.append({})
|
||||
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()
|
||||
copy.ch[-1].scope = copy.scope
|
||||
return copy
|
||||
|
||||
if nt == '@':
|
||||
copy = node.copy()
|
||||
oldscope = node.scope
|
||||
oldname = node.name
|
||||
self.lblCount += 1
|
||||
copy.name = '___lbl__%05d' % self.lblCount
|
||||
copy.scope = scope
|
||||
if copy.name in self.symtab[scope]:
|
||||
raise ENameAlreadyExists(
|
||||
u"Label already exists: %s" % copy.name.decode('utf8'))
|
||||
self.symtab[scope][copy.name] = {'Type':'l', '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)
|
||||
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=len(self.symtab), 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
|
||||
])
|
||||
self.symtab.append({})
|
||||
self.retused = True
|
||||
return newnode
|
||||
|
||||
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.
|
||||
self.retCount += 1
|
||||
retvar = '___ret__%05d' % self.retCount
|
||||
if retvar in self.symtab[scope]:
|
||||
raise ENameAlreadyExists(u"Symbol %s already exists"
|
||||
% retvar.decode('utf8'))
|
||||
# Add the symbol to the symbol table
|
||||
self.symtab[scope][retvar] = {'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()
|
||||
|
||||
self.expanding.append(node.name)
|
||||
self.retvar = retvar
|
||||
self.retscope = scope
|
||||
self.retlscope = scope
|
||||
self.lblCount += 1
|
||||
retlabel = '___rtl__%05d' % self.lblCount
|
||||
self.retlabel = retlabel
|
||||
self.symtab[scope][retlabel] = {'Type':'l', 'Scope':scope}
|
||||
|
||||
# Get a copy of the function
|
||||
self.retused = False
|
||||
blk = [self.GetFuncCopy(self.tree[fnsym['Loc']])]
|
||||
retused = self.retused
|
||||
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
|
||||
|
||||
if fnsym['ParamNames']:
|
||||
# Add a new block + symbols + assignments for parameter values
|
||||
pscope = len(self.symtab)
|
||||
self.symtab.append({})
|
||||
outer = nr(nt='{}', t=None, scope=pscope, ch=[])
|
||||
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}
|
||||
# BUG: We don't honour ExplicitCast here.
|
||||
outer.ch.append(nr(nt='DECL', t=ptype, name=pname, scope=pscope,
|
||||
ch=[value]))
|
||||
outer.ch.extend(blk)
|
||||
blk = [outer]
|
||||
|
||||
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 = len(self.symtab)
|
||||
self.symtab.append({})
|
||||
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.
|
||||
#
|
||||
# Do loops are different:
|
||||
#
|
||||
# do
|
||||
# llOwnerSay("doing stuff");
|
||||
# while (f());
|
||||
#
|
||||
# should be 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)
|
||||
|
||||
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.retCount = 0
|
||||
self.lblCount = 0
|
||||
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)
|
|
@ -1011,7 +1011,8 @@ class parser(object):
|
|||
params = (['list', 'integer', 'list'],
|
||||
['L', 'i', 'v'])
|
||||
self.AddSymbol('f', 0, 'lazy_list_set', Loc=self.usedspots,
|
||||
Type='list', ParamTypes=params[0], ParamNames=params[1])
|
||||
Type='list', ParamTypes=params[0], ParamNames=params[1],
|
||||
Inline=False)
|
||||
self.AddSymbol('v', paramscope, 'L', Type='list')
|
||||
self.AddSymbol('v', paramscope, 'i', Type='integer')
|
||||
self.AddSymbol('v', paramscope, 'v', Type='list')
|
||||
|
@ -2507,16 +2508,22 @@ list lazy_list_set(list L, integer i, list v)
|
|||
self.NextToken()
|
||||
self.localevents = None
|
||||
self.locallabels = set()
|
||||
force_inline = False
|
||||
if (self.enable_inline and self.tok[0] == 'IDENT'
|
||||
and self.tok[1] == 'inline'):
|
||||
self.NextToken()
|
||||
force_inline = True
|
||||
body = self.Parse_code_block(typ)
|
||||
del self.locallabels
|
||||
if typ and not getattr(body, 'LIR', False): # is LastIsReturn flag set?
|
||||
raise EParseCodePathWithoutRet(self)
|
||||
paramscope = self.scopeindex
|
||||
self.AddSymbol('f', 0, name, Loc=len(self.tree), Type=typ,
|
||||
Inline=force_inline,
|
||||
ParamTypes=params[0], ParamNames=params[1])
|
||||
self.tree.append(nr(nt='FNDEF', t=typ, name=name, scope=0,
|
||||
pscope=paramscope,
|
||||
ptypes=params[0], pnames=params[1], ch=[body]))
|
||||
pscope=paramscope, ptypes=params[0], pnames=params[1],
|
||||
ch=[body]))
|
||||
self.PopScope()
|
||||
assert self.scopeindex == 0
|
||||
else:
|
||||
|
@ -2667,6 +2674,8 @@ list lazy_list_set(list L, integer i, list v)
|
|||
break
|
||||
self.NextToken()
|
||||
self.NextToken()
|
||||
if self.tok[0] == 'IDENT' and self.tok[1] == 'inline':
|
||||
self.NextToken()
|
||||
if self.tok[0] != '{':
|
||||
return ret
|
||||
self.NextToken() # Enter the first brace
|
||||
|
@ -2845,6 +2854,9 @@ list lazy_list_set(list L, integer i, list v)
|
|||
# coding pattern is normally easy to work around anyway.
|
||||
self.optenabled = 'optimize' in options
|
||||
|
||||
# Inline keyword
|
||||
self.enable_inline = 'inline' in options
|
||||
|
||||
# Symbol table:
|
||||
# This is a list of all local and global symbol tables.
|
||||
# The first element (0) is the global scope. Each symbol table is a
|
||||
|
@ -2948,6 +2960,10 @@ list lazy_list_set(list L, integer i, list v)
|
|||
del self.globals
|
||||
del self.scopestack
|
||||
|
||||
if self.enable_inline:
|
||||
import lslinliner
|
||||
lslinliner.inliner().inline(self.tree, self.symtab)
|
||||
|
||||
treesymtab = self.tree, self.symtab
|
||||
del self.tree
|
||||
del self.symtab
|
||||
|
|
8
main.py
8
main.py
|
@ -27,7 +27,7 @@ import lslopt.lslcommon
|
|||
import lslopt.lslloadlib
|
||||
|
||||
|
||||
VERSION = '0.2.2beta'
|
||||
VERSION = '0.3.0beta'
|
||||
|
||||
|
||||
def ReportError(script, e):
|
||||
|
@ -274,6 +274,8 @@ Case insensitive.
|
|||
with that name). This flag works around that limitation
|
||||
by replacing the names of the labels in the output with
|
||||
unique ones.
|
||||
Inline + Enable 'inline' keyword to force functions to be inlined
|
||||
(EXPERIMENTAL)
|
||||
|
||||
Deprecated / compatibility syntax extensions options:
|
||||
|
||||
|
@ -356,7 +358,7 @@ validoptions = frozenset(('extendedglobalexpr','breakcont','extendedtypecast',
|
|||
'lazylists','enableswitch','errmissingdefault','funcoverride','optimize',
|
||||
'optsigns','optfloats','constfold','dcr','shrinknames','addstrings',
|
||||
'foldtabs','warntabs','processpre','explicitcast','listlength','listadd',
|
||||
'help',
|
||||
'inline', 'help',
|
||||
# undocumented
|
||||
'lso','expr','rsrclimit',
|
||||
# 'clear' is handled as a special case
|
||||
|
@ -374,7 +376,7 @@ def main(argv):
|
|||
options = set(('extendedglobalexpr','extendedtypecast','extendedassignment',
|
||||
'allowkeyconcat','allowmultistrings','processpre','warntabs','optimize',
|
||||
'optsigns','optfloats','constfold','dcr','errmissingdefault',
|
||||
'listlength','listadd',
|
||||
'listlength','listadd','inline',
|
||||
))
|
||||
|
||||
assert not (options - validoptions), (u"Default options not present in"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue