Implement side-effect-free (SEF) analysis. Also optimize x++ to ++x.

As an additional bonus, the condition folding function is now the one that converts a condition to -1, relieving that burden from the rest of the code and simplifying tests.
This commit is contained in:
Sei Lisa 2014-08-06 04:30:26 +02:00
parent 0a7d409a4e
commit 9c4d81db08
2 changed files with 172 additions and 43 deletions

View file

@ -20,8 +20,6 @@ class optimizer(renamer, deadcode):
LSL2PythonType = {'integer':int, 'float':float, 'string':unicode, 'key':lslfuncs.Key, LSL2PythonType = {'integer':int, 'float':float, 'string':unicode, 'key':lslfuncs.Key,
'vector':lslfuncs.Vector, 'rotation':lslfuncs.Quaternion, 'list':list} 'vector':lslfuncs.Vector, 'rotation':lslfuncs.Quaternion, 'list':list}
ignored_stmts = frozenset(('V++','V--','--V','++V',';','STSW','JUMP','@'))
def FoldAndRemoveEmptyStmts(self, lst): def FoldAndRemoveEmptyStmts(self, lst):
"""Utility function for elimination of useless expressions in FOR""" """Utility function for elimination of useless expressions in FOR"""
idx = 0 idx = 0
@ -35,22 +33,41 @@ class optimizer(renamer, deadcode):
idx += 1 idx += 1
def FoldStmt(self, parent, index): def FoldStmt(self, parent, index):
"""If the statement is a constant or an identifier, remove it as it does """Simplify a statement."""
nothing.
"""
# Ideally this should consider side effect analysis of the whole thing.
node = parent[index] node = parent[index]
if node['nt'] == 'EXPR': if node['nt'] == 'EXPR':
node = node['ch'][0] node = node['ch'][0]
if node['nt'] in ('CONST', 'IDENT', 'FLD'): # If the statement is side-effect-free, remove it as it does nothing.
parent[index] = {'nt':';','t':None} if 'SEF' in node:
# Side-effect free means that a statement does nothing except
# wasting CPU, and can thus be removed without affecting the
# program. But side effect freedom is propagated from the
# constituents of the statement, e.g. function calls in expressions
# or substatements in FOR, or even individual variables.
#
# Many library functions like llSameGroup or llGetVel() are
# side-effect free. Many other functions like llSleep() or
# llSetScale() are not. User functions may or may not be.
#
# Assignments do have side effects, except those of the form x = x.
# Pre- and post-increment and decrement also have side effects.
# Type casts do not add side effects. Neither do binary operators.
parent[index] = {'nt':';', 't':None, 'SEF': True}
return
# Post-increments take more space than pre-increments.
if node['nt'] in ('V++', 'V--'):
node['nt'] = '++V' if node['nt'] == 'V++' else '--V';
def FoldCond(self, parent, index): def FoldCond(self, parent, index):
"""When we know that the parent is interested only in the truth value """When we know that the parent is interested only in the truth value
of the node, we can perform further optimizations. This function deals of the node, we can perform further optimizations. This function deals
with them. with them.
""" """
if parent[index]['nt'] in ('CONST', 'IDENT', 'FIELD'): node = parent[index]
if node['nt'] in ('CONST', 'IDENT', 'FLD'):
if node['nt'] == 'CONST':
node['t'] = 'integer'
node['value'] = -1 if lslfuncs.cond(node['value']) else 0
return # Nothing to do if it's already simplified. return # Nothing to do if it's already simplified.
# TODO: Implement FoldCond # TODO: Implement FoldCond
@ -83,11 +100,14 @@ class optimizer(renamer, deadcode):
child = node['ch'] if 'ch' in node else None child = node['ch'] if 'ch' in node else None
if nt == 'CONST': if nt == 'CONST':
# Job already done # Job already done. But mark as side-effect free.
node['SEF'] = True
return return
if nt == 'CAST': if nt == 'CAST':
self.FoldTree(child, 0) self.FoldTree(child, 0)
if 'SEF' in child[0]:
node['SEF'] = True
if child[0]['nt'] == 'CONST': if child[0]['nt'] == 'CONST':
# Enable key constants. We'll typecast them back on output, but # Enable key constants. We'll typecast them back on output, but
# this enables some optimizations. # this enables some optimizations.
@ -101,13 +121,18 @@ class optimizer(renamer, deadcode):
if nt == 'NEG': if nt == 'NEG':
self.FoldTree(child, 0) self.FoldTree(child, 0)
while child[0]['nt'] == '()' and child[0]['ch'][0]['nt'] == 'NEG': while child[0]['nt'] == '()' and child[0]['ch'][0]['nt'] == 'NEG':
child[0] = child[0]['ch'][0] # Remove parentheses # Remove parentheses: - ( - expr ) --> - - expr
child[0] = child[0]['ch'][0]
if child[0]['nt'] == 'NEG': if child[0]['nt'] == 'NEG':
# Double negation: - - expr # Double negation: - - expr --> expr
# NOTE: Not 100% sure this doesn't need parentheses around expr.
parent[index] = child[0]['ch'][0] parent[index] = child[0]['ch'][0]
elif child[0]['nt'] == 'CONST': elif child[0]['nt'] == 'CONST':
node = parent[index] = child[0] node = parent[index] = child[0]
node['value'] = lslfuncs.neg(node['value']) node['value'] = lslfuncs.neg(node['value'])
elif 'SEF' in child[0]:
# propagate Side Effect Free flag
node['SEF'] = True
return return
if nt == '!': if nt == '!':
@ -115,6 +140,8 @@ class optimizer(renamer, deadcode):
self.FoldCond(child, 0) self.FoldCond(child, 0)
# !! does *not* cancel out, but !!! can be simplified to ! # !! does *not* cancel out, but !!! can be simplified to !
subexpr = child[0] subexpr = child[0]
if 'SEF' in subexpr:
node['SEF'] = True
while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()', '~', '!', '++V', '--V'): while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()', '~', '!', '++V', '--V'):
subexpr = child[0] = subexpr['ch'][0] # Remove parentheses subexpr = child[0] = subexpr['ch'][0] # Remove parentheses
if subexpr['nt'] == '!' and subexpr['ch'][0]['nt'] == '!': if subexpr['nt'] == '!' and subexpr['ch'][0]['nt'] == '!':
@ -128,7 +155,10 @@ class optimizer(renamer, deadcode):
if nt == '~': if nt == '~':
self.FoldTree(child, 0) self.FoldTree(child, 0)
subexpr = child[0] subexpr = child[0]
while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()', '~', '!', '++V', '--V'): if 'SEF' in subexpr:
node['SEF'] = True
while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()',
'~', '!', '++V', '--V'):
subexpr = child[0] = subexpr['ch'][0] # Remove parentheses subexpr = child[0] = subexpr['ch'][0] # Remove parentheses
if subexpr['nt'] == '~': if subexpr['nt'] == '~':
# Double negation: ~~expr # Double negation: ~~expr
@ -140,6 +170,8 @@ class optimizer(renamer, deadcode):
if nt == '()': if nt == '()':
self.FoldTree(child, 0) self.FoldTree(child, 0)
if 'SEF' in child[0]:
node['SEF'] = True
if child[0]['nt'] in ('()', 'CONST', 'VECTOR', 'ROTATION', 'LIST', if child[0]['nt'] in ('()', 'CONST', 'VECTOR', 'ROTATION', 'LIST',
'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'): 'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'):
# Child is an unary postfix expression (highest priority); # Child is an unary postfix expression (highest priority);
@ -155,6 +187,9 @@ class optimizer(renamer, deadcode):
# RTL evaluation # RTL evaluation
self.FoldTree(child, 1) self.FoldTree(child, 1)
self.FoldTree(child, 0) self.FoldTree(child, 0)
if 'SEF' in child[0] and 'SEF' in child[1]:
# Propagate SEF flag if both sides are side-effect free.
node['SEF'] = True
if child[0]['nt'] == child[1]['nt'] == 'CONST': if child[0]['nt'] == child[1]['nt'] == 'CONST':
op1 = child[0]['value'] op1 = child[0]['value']
op2 = child[1]['value'] op2 = child[1]['value']
@ -377,6 +412,8 @@ class optimizer(renamer, deadcode):
# Transform the whole thing into a regular assignment, as there are # Transform the whole thing into a regular assignment, as there are
# no gains and it simplifies the optimization. # no gains and it simplifies the optimization.
# An assignment has no side effects only if it's of the form x = x.
if nt != '=': if nt != '=':
# Replace the node with the expression alone # Replace the node with the expression alone
child[1] = {'nt':'()', 't':child[1]['t'], 'ch':[child[1]]} child[1] = {'nt':'()', 't':child[1]['t'], 'ch':[child[1]]}
@ -391,13 +428,19 @@ class optimizer(renamer, deadcode):
node = self.Cast(node, 'integer') node = self.Cast(node, 'integer')
# And wrap it in an assignment. # And wrap it in an assignment.
node = parent[index] = {'nt':'=', 't':child[0]['t'], 'ch':[child[0].copy(), node]} child = [child[0].copy(), node]
node = parent[index] = {'nt':'=', 't':child[0]['t'], 'ch':child}
# We have a regular assignment either way now. Simplify the RHS. # We have a regular assignment either way now. Simplify the RHS.
self.FoldTree(node['ch'], 1) self.FoldTree(node['ch'], 1)
if child[1]['nt'] == 'IDENT' and child[1]['name'] == child[0]['name'] \
and child[1]['scope'] == child[0]['scope']:
node['SEF'] = True
self.FoldStmt(parent, index)
return return
if nt == 'IDENT' or nt == 'FLD': if nt == 'IDENT' or nt == 'FLD':
node['SEF'] = True
if self.globalmode: if self.globalmode:
ident = child[0] if nt == 'FLD' else node ident = child[0] if nt == 'FLD' else node
# Resolve constant values so they can be optimized # Resolve constant values so they can be optimized
@ -422,10 +465,22 @@ class optimizer(renamer, deadcode):
return return
if nt == 'FNCALL': if nt == 'FNCALL':
SEFargs = True
CONSTargs = True
for idx in xrange(len(child)-1, -1, -1): for idx in xrange(len(child)-1, -1, -1):
self.FoldTree(child, idx) self.FoldTree(child, idx)
# Function is not SEF if any argument is not SEF
if 'SEF' not in child[idx]:
SEFargs = False
# Function is not a constant if any argument is not a constant
if child[idx]['nt'] != 'CONST':
CONSTargs = False
if 'Fn' in self.symtab[0][node['name']]: if 'Fn' in self.symtab[0][node['name']]:
if all(arg['nt'] == 'CONST' for arg in child): # Guaranteed to be side-effect free if the children are.
if SEFargs:
node['SEF'] = True
if CONSTargs:
# Call it # Call it
fn = self.symtab[0][node['name']]['Fn'] fn = self.symtab[0][node['name']]['Fn']
value = fn(*tuple(arg['value'] for arg in child)) value = fn(*tuple(arg['value'] for arg in child))
@ -438,18 +493,42 @@ class optimizer(renamer, deadcode):
node = {'nt':'CONST', 't':'list', 'value':[]} node = {'nt':'CONST', 't':'list', 'value':[]}
node = {'nt':'!=', 't':'list', 'ch':[child[0], node]} node = {'nt':'!=', 't':'list', 'ch':[child[0], node]}
parent[index] = {'nt':'()', 't':'list', 'ch':[node]} parent[index] = {'nt':'()', 't':'list', 'ch':[node]}
elif SEFargs and 'SEF' in self.symtab[0][node['name']]:
# The function is marked as SEF in the symbol table, and the
# arguments are all side-effect-free. The result is SEF.
node['SEF'] = True
return return
if nt in ('PRINT', 'EXPR'): if nt == 'PRINT':
self.FoldTree(child, 0) self.FoldTree(child, 0)
# PRINT is considered to have side effects. If it's there, assume
# there's a reason.
return
if nt == 'EXPR':
self.FoldTree(child, 0)
if 'SEF' in child[0]:
node['SEF'] = True
return
if nt == 'FNDEF':
self.FoldTree(child, 0)
if 'SEF' in child[0]:
node['SEF'] = True
if node['name'] in self.symtab[0]:
# Mark the symbol table entry if it's not an event.
self.symtab[0][node['name']]['SEF'] = True
return return
if nt in ('VECTOR', 'ROTATION', 'LIST'): if nt in ('VECTOR', 'ROTATION', 'LIST'):
isconst = True isconst = True
issef = True
for idx in xrange(len(child)-1, -1, -1): for idx in xrange(len(child)-1, -1, -1):
self.FoldTree(child, idx) self.FoldTree(child, idx)
if child[idx]['nt'] != 'CONST': if child[idx]['nt'] != 'CONST':
isconst = False isconst = False
if 'SEF' not in child[idx]:
issef = False
if isconst: if isconst:
value = [elem['value'] for elem in child] value = [elem['value'] for elem in child]
if nt == 'VECTOR': if nt == 'VECTOR':
@ -457,6 +536,8 @@ class optimizer(renamer, deadcode):
elif nt == 'ROTATION': elif nt == 'ROTATION':
value = lslfuncs.Quaternion([lslfuncs.ff(x) for x in value]) value = lslfuncs.Quaternion([lslfuncs.ff(x) for x in value])
parent[index] = {'nt':'CONST', 't':node['t'], 'value':value} parent[index] = {'nt':'CONST', 't':node['t'], 'value':value}
if issef:
node['SEF'] = True
return return
if nt == 'STDEF': if nt == 'STDEF':
@ -464,11 +545,14 @@ class optimizer(renamer, deadcode):
self.FoldTree(child, idx) self.FoldTree(child, idx)
return return
if nt in ('{}', 'FNDEF'): if nt == '{}':
idx = 0 idx = 0
issef = True
while idx < len(child): while idx < len(child):
self.FoldTree(child, idx) self.FoldTree(child, idx)
self.FoldStmt(child, idx) self.FoldStmt(child, idx)
if 'SEF' not in child[idx]:
issef = False
if child[idx]['nt'] == ';' \ if child[idx]['nt'] == ';' \
or nt == '{}' and child[idx]['nt'] == '{}' and not child[idx]['ch']: or nt == '{}' and child[idx]['nt'] == '{}' and not child[idx]['ch']:
del child[idx] del child[idx]
@ -476,6 +560,8 @@ class optimizer(renamer, deadcode):
if 'StSw' in child[idx]: if 'StSw' in child[idx]:
node['StSw'] = True node['StSw'] = True
idx += 1 idx += 1
if issef:
node['SEF'] = True
return return
if nt == 'IF': if nt == 'IF':
@ -483,7 +569,7 @@ class optimizer(renamer, deadcode):
self.FoldCond(child, 0) self.FoldCond(child, 0)
if child[0]['nt'] == 'CONST': if child[0]['nt'] == 'CONST':
# We might be able to remove one of the branches. # We might be able to remove one of the branches.
if lslfuncs.cond(child[0]['value']): if child[0]['value']:
self.FoldTree(child, 1) self.FoldTree(child, 1)
# If it has a state switch, the if() must be preserved # If it has a state switch, the if() must be preserved
# (but the else branch may be removed). # (but the else branch may be removed).
@ -494,19 +580,22 @@ class optimizer(renamer, deadcode):
# result of optimization so they must be wrapped in an # result of optimization so they must be wrapped in an
# IF statement). The current approach leaves unnecessary # IF statement). The current approach leaves unnecessary
# IFs behind. # IFs behind.
if len(child) > 2: if len(child) == 3:
del child[2] # Delete ELSE if present del child[2] # Delete ELSE if present
child[0].update({'t':'integer', 'value':-1}) return
else: else:
self.FoldStmt(child, 1) self.FoldStmt(child, 1)
parent[index] = child[1] parent[index] = child[1]
elif len(child) > 2: return
elif len(child) == 3:
self.FoldTree(child, 2) self.FoldTree(child, 2)
self.FoldStmt(child, 2) self.FoldStmt(child, 2)
parent[index] = child[2] parent[index] = child[2]
return
else: else:
# No ELSE branch, replace the statement with an empty one. # No ELSE branch, replace the statement with an empty one.
parent[index] = {'nt':';', 't':None} parent[index] = {'nt':';', 't':None, 'SEF':True}
return
else: else:
self.FoldTree(child, 1) self.FoldTree(child, 1)
self.FoldStmt(child, 1) self.FoldStmt(child, 1)
@ -517,26 +606,32 @@ class optimizer(renamer, deadcode):
or child[2]['nt'] == '{}' and not child[2]['ch']: or child[2]['nt'] == '{}' and not child[2]['ch']:
# no point in "... else ;" - remove else branch # no point in "... else ;" - remove else branch
del child[2] del child[2]
if all('SEF' in subnode for subnode in child):
node['SEF'] = True
return return
if nt == 'WHILE': if nt == 'WHILE':
self.FoldTree(child, 0) self.FoldTree(child, 0)
self.FoldCond(child, 0) self.FoldCond(child, 0)
allSEF = 'SEF' in child[0]
if child[0]['nt'] == 'CONST': if child[0]['nt'] == 'CONST':
# See if the whole WHILE can be eliminated. # See if the whole WHILE can be eliminated.
if lslfuncs.cond(child[0]['value']): if child[0]['value']:
# Endless loop which must be kept. # Endless loop which must be kept.
# First, replace the constant.
child[0].update({'t':'integer', 'value':-1})
# Recurse on the statement. # Recurse on the statement.
self.FoldTree(child, 1) self.FoldTree(child, 1)
self.FoldStmt(child, 1) self.FoldStmt(child, 1)
allSEF &= 'SEF' in child[1]
else: else:
# Can be removed. # Can be removed.
parent[index] = {'nt':';', 't':None} parent[index] = {'nt':';', 't':None, 'SEF':True}
return
else: else:
self.FoldTree(child, 1) self.FoldTree(child, 1)
self.FoldStmt(child, 1) self.FoldStmt(child, 1)
allSEF &= 'SEF' in child[1]
if allSEF:
node['SEF'] = True
return return
if nt == 'DO': if nt == 'DO':
@ -544,12 +639,11 @@ class optimizer(renamer, deadcode):
self.FoldStmt(child, 0) self.FoldStmt(child, 0)
self.FoldTree(child, 1) self.FoldTree(child, 1)
self.FoldCond(child, 1) self.FoldCond(child, 1)
if 'SEF' in child[0] and 'SEF' in child[1]:
node['SEF'] = True
# See if the latest part is a constant. # See if the latest part is a constant.
if child[1]['nt'] == 'CONST': if child[1]['nt'] == 'CONST':
if lslfuncs.cond(child[1]['value']): if not child[1]['value']:
# Endless loop. Replace the constant.
child[1].update({'t':'integer', 'value':-1})
else:
# Only one go. Replace with the statement(s). # Only one go. Replace with the statement(s).
parent[index] = child[0] parent[index] = child[0]
return return
@ -559,45 +653,59 @@ class optimizer(renamer, deadcode):
assert child[2]['nt'] == 'EXPRLIST' assert child[2]['nt'] == 'EXPRLIST'
self.FoldAndRemoveEmptyStmts(child[0]['ch']) self.FoldAndRemoveEmptyStmts(child[0]['ch'])
# If there were side-effect-free elements, they're already removed.
# So if there are remaining element, they're not SEF.
allSEF = bool(child[0]['ch'])
self.FoldTree(child, 1) # Condition. self.FoldTree(child, 1) # Condition.
self.FoldCond(child, 1) self.FoldCond(child, 1)
if child[1]['nt'] == 'CONST': if child[1]['nt'] == 'CONST':
# FOR is delicate. It can have multiple expressions at start. # FOR is delicate. It can have multiple expressions at start.
# And if there is more than one, these expressions will need a # And if there is more than one, these expressions will need a
# new block, which means new scope, which is dangerous. # new block, which means new scope, which is dangerous.
# They are expressions, no declarations or labels allowed, but # They are expressions, no declarations or labels allowed, thus
# it feels creepy. # no new identifiers, but it still feels uneasy.
if lslfuncs.cond(child[1]['value']): if child[1]['value']:
# Endless loop. Just replace the constant and traverse the rest. # Endless loop. Traverse the loop and the iterator.
child[1].update({'t':'integer', 'value':-1})
self.FoldTree(child, 3) self.FoldTree(child, 3)
self.FoldStmt(child, 3) self.FoldStmt(child, 3)
self.FoldAndRemoveEmptyStmts(child[2]['ch']) self.FoldAndRemoveEmptyStmts(child[2]['ch'])
elif child[0]['ch']: allSEF &= bool(child[2]['ch']) and 'SEF' in child[3]
else:
# Convert expression list to code block. # Convert expression list to code block.
exprlist = [] exprlist = []
for expr in child[0]['ch']: for expr in child[0]['ch']:
# Fold into expression statements. # Fold into expression statements.
exprlist.append({'nt':'EXPR', 't':expr['t'], 'ch':[expr]}) exprlist.append({'nt':'EXPR', 't':expr['t'], 'ch':[expr]})
# returns type None, as FOR does # returns type None, as FOR does
parent[index] = {'nt':'{}', 't':None, 'ch':exprlist} # We're in the case where there are expressions. If any
else: # remain, they are not SEF (or they would have been
parent[index] = {'nt':';', 't':None} # removed earlier) so don't mark this node as SEF.
if exprlist:
parent[index] = {'nt':'{}', 't':None, 'ch':exprlist}
else:
parent[index] = {'nt':';', 't':None, 'SEF': True}
return
else: else:
self.FoldTree(child, 3) self.FoldTree(child, 3)
self.FoldStmt(child, 3) self.FoldStmt(child, 3)
self.FoldAndRemoveEmptyStmts(child[2]['ch']) self.FoldAndRemoveEmptyStmts(child[2]['ch'])
allSEF &= bool(child[2]['ch']) and 'SEF' in child[3]
if allSEF:
node['SEF'] = True
return return
if nt == 'RETURN': if nt == 'RETURN':
if child: if child:
self.FoldTree(child, 0) self.FoldTree(child, 0)
if 'SEF' in child[0]:
node['SEF'] = True
return return
if nt == 'DECL': if nt == 'DECL':
if child: if child:
# Check if child is a simple_expr. If it is, then we keep the # Check if child is a simple_expr. If it is, then we keep the
# original attached to the folded node and use it in the output. # original attached to the folded node to use it in the output.
if child[0].pop('Simple', False): if child[0].pop('Simple', False):
orig = self.CopyNode(child[0]) orig = self.CopyNode(child[0])
self.FoldTree(child, 0) self.FoldTree(child, 0)
@ -609,21 +717,30 @@ class optimizer(renamer, deadcode):
and not child[0]['value']: and not child[0]['value']:
del node['ch'] del node['ch']
child = None child = None
return
else: else:
# Add assignment if vector, rotation or float. # Add assignment if vector, rotation or float.
if node['t'] in ('float', 'vector', 'rotation'): if node['t'] in ('float', 'vector', 'rotation'):
typ = node['t'] typ = node['t']
node['ch'] = [{'nt':'CONST', 't':typ, 'value': node['ch'] = [{'nt':'CONST', 't':typ, 'SEF': True, 'value':
0.0 if typ == 'float' else 0.0 if typ == 'float' else
lslfuncs.ZERO_VECTOR if typ == 'vector' else lslfuncs.ZERO_VECTOR if typ == 'vector' else
lslfuncs.ZERO_ROTATION}] lslfuncs.ZERO_ROTATION}]
# Declarations always have side effects.
return return
if nt == 'STSW': if nt == 'STSW':
# State switch always has side effects.
node['StSw'] = True node['StSw'] = True
return return
if nt in self.ignored_stmts: if nt == ';':
node['SEF'] = True
return
if nt in ('JUMP', '@', 'V++', 'V--', '--V', '++V'):
# These all have side effects, as in, can't be eliminated as
# statements.
return return
assert False, 'Internal error: This should not happen,' \ assert False, 'Internal error: This should not happen,' \
@ -643,7 +760,6 @@ class optimizer(renamer, deadcode):
"""Optimize the symbolic table symtab in place. Requires a table of """Optimize the symbolic table symtab in place. Requires a table of
predefined functions for folding constants. predefined functions for folding constants.
""" """
if 'optimize' not in options: if 'optimize' not in options:
return treesymtab return treesymtab

View file

@ -2050,3 +2050,16 @@ class parser(object):
finally: finally:
f.close() f.close()
# Load the side-effect-free table as well.
f = open('seftable.txt', 'rb')
try:
while True:
line = f.readline()
if line == '':
break
line = line.strip()
if line and line[0] != '#' and line in self.functions:
self.functions[line]['SEF'] = True
finally:
f.close()