# (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 . # 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)