%PDF- %PDF-
Direktori : /lib/python3/dist-packages/pythran/analyses/ |
Current File : //lib/python3/dist-packages/pythran/analyses/lazyness_analysis.py |
""" LazynessAnalysis returns number of time a name is use. """ from pythran.analyses.aliases import Aliases from pythran.analyses.argument_effects import ArgumentEffects from pythran.analyses.identifiers import Identifiers from pythran.analyses.pure_expressions import PureExpressions from pythran.passmanager import FunctionAnalysis from pythran.syntax import PythranSyntaxError from pythran.utils import get_variable, isattr import pythran.metadata as md import pythran.openmp as openmp import gast as ast import sys class LazynessAnalysis(FunctionAnalysis): """ Returns number of time a name is used. +inf if it is use in a loop, if a variable used to compute it is modify before its last use or if it is use in a function call (as it is not an interprocedural analysis) >>> import gast as ast, sys >>> from pythran import passmanager, backend >>> code = "def foo(): c = 1; a = c + 2; c = 2; b = c + c + a; return b" >>> node = ast.parse(code) >>> pm = passmanager.PassManager("test") >>> res = pm.gather(LazynessAnalysis, node) >>> res['a'], res['b'], res['c'] (inf, 1, 2) >>> code = ''' ... def foo(): ... k = 2 ... for i in [1, 2]: ... builtins.print(k) ... k = i ... builtins.print(k)''' >>> node = ast.parse(code) >>> res = pm.gather(LazynessAnalysis, node) >>> (res['i'], res['k']) == (sys.maxsize, 1) True >>> code = ''' ... def foo(): ... k = 2 ... for i in [1, 2]: ... builtins.print(k) ... k = i ... builtins.print(k)''' >>> node = ast.parse(code) >>> res = pm.gather(LazynessAnalysis, node) >>> (res['i'], res['k']) == (sys.maxsize, 2) True >>> code = ''' ... def foo(): ... d = 0 ... for i in [0, 1]: ... for j in [0, 1]: ... k = 1 ... d += k * 2 ... return d''' >>> node = ast.parse(code) >>> res = pm.gather(LazynessAnalysis, node) >>> res['k'] 1 >>> code = ''' ... def foo(): ... k = 2 ... for i in [1, 2]: ... builtins.print(k)''' >>> node = ast.parse(code) >>> res = pm.gather(LazynessAnalysis, node) >>> res['k'] == sys.maxsize True >>> code = ''' ... def foo(): ... k = builtins.sum ... builtins.print(k([1, 2]))''' >>> node = ast.parse(code) >>> res = pm.gather(LazynessAnalysis, node) >>> res['k'] 1 """ INF = float('inf') MANY = sys.maxsize def __init__(self): # map variable with maximum count of use in the programm self.result = dict() # map variable with current count of use self.name_count = dict() # map variable to variables needed to compute it self.use = dict() # gather variables which can't be compute later. (variables used # to compute it have changed self.dead = set() # count use of variable before first assignation in the loop # {variable: (count, is_assigned)} self.pre_loop_count = dict() # prevent any form of Forward Substitution at omp frontier self.in_omp = set() self.name_to_nodes = dict() super(LazynessAnalysis, self).__init__(ArgumentEffects, Aliases, PureExpressions) def modify(self, name): # if we modify a variable, all variables that needed it # to be compute are dead and its aliases too dead_vars = [var for var, deps in self.use.items() if name in deps] self.dead.update(dead_vars) for var in dead_vars: dead_aliases = [alias.id for alias in self.name_to_nodes[var] if isinstance(alias, ast.Name)] self.dead.update(dead_aliases) def assign_to(self, node, from_): if isinstance(node, ast.Name): self.name_to_nodes.setdefault(node.id, set()).add(node) # a reassigned variable is not dead anymore if node.id in self.dead: self.dead.remove(node.id) # we keep the bigger possible number of use self.result[node.id] = max(self.result.get(node.id, 0), self.name_count.get(node.id, 0)) # assign variable don't come from before omp pragma anymore self.in_omp.discard(node.id) # count number of use in the loop before first reassign pre_loop = self.pre_loop_count.setdefault(node.id, (0, True)) if not pre_loop[1]: self.pre_loop_count[node.id] = (pre_loop[0], True) # note this variable as modified self.modify(node.id) # prepare a new variable count self.name_count[node.id] = 0 self.use[node.id] = set(from_) def visit(self, node): old_omp = self.in_omp omp_nodes = md.get(node, openmp.OMPDirective) if omp_nodes: self.in_omp = set(self.name_count.keys()) super(LazynessAnalysis, self).visit(node) if omp_nodes: new_nodes = set(self.name_count).difference(self.in_omp) for omp_node in omp_nodes: for n in omp_node.deps: if isinstance(n, ast.Name): self.result[n.id] = LazynessAnalysis.INF self.dead.update(new_nodes) self.in_omp = old_omp def visit_FunctionDef(self, node): self.ids = self.gather(Identifiers, node) self.generic_visit(node) def visit_Assign(self, node): md.visit(self, node) self.visit(node.value) ids = self.gather(Identifiers, node.value) for target in node.targets: if isinstance(target, ast.Name): self.assign_to(target, ids) if node.value not in self.pure_expressions: self.result[target.id] = LazynessAnalysis.INF elif isinstance(target, (ast.Subscript)) or isattr(target): # if we modify just a part of a variable, it can't be lazy var_name = get_variable(target) if isinstance(var_name, ast.Name): # variable is modified so other variables that use it dies self.modify(var_name.id) # and this variable can't be lazy self.result[var_name.id] = LazynessAnalysis.INF else: raise PythranSyntaxError("Assign to unknown node", node) def visit_AugAssign(self, node): md.visit(self, node) # augassigned variable can't be lazy self.visit(node.value) if isinstance(node.target, ast.Name): # variable is modified so other variables that use it dies self.modify(node.target.id) # and this variable can't be lazy self.result[node.target.id] = LazynessAnalysis.INF elif isinstance(node.target, ast.Subscript) or isattr(node.target): var_name = get_variable(node.target) # variable is modified so other variables that use it dies self.modify(var_name.id) # and this variable can't be lazy self.result[var_name.id] = LazynessAnalysis.INF else: raise PythranSyntaxError("AugAssign to unknown node", node) def visit_Name(self, node): if isinstance(node.ctx, ast.Load) and node.id in self.use: # we only care about variable local to the function def is_loc_var(x): return isinstance(x, ast.Name) and x.id in self.ids alias_names = [var for var in self.aliases[node] if is_loc_var(var)] alias_names = {x.id for x in alias_names} alias_names.add(node.id) for alias in alias_names: if (node.id in self.dead or node.id in self.in_omp): self.result[alias] = LazynessAnalysis.INF elif alias in self.name_count: self.name_count[alias] += 1 # init value as pre_use variable and count it pre_loop = self.pre_loop_count.setdefault(alias, (0, False)) if not pre_loop[1]: self.pre_loop_count[alias] = (pre_loop[0] + 1, False) else: # a variable may alias to assigned value (with a = b, 'b' # alias on 'a' as modifying 'a' will modify 'b' too) pass elif isinstance(node.ctx, ast.Param): self.name_count[node.id] = 0 self.use[node.id] = set() elif isinstance(node.ctx, ast.Store): # Store is only for exception self.name_count[node.id] = LazynessAnalysis.INF self.use[node.id] = set() else: # we ignore globals pass def visit_If(self, node): md.visit(self, node) self.visit(node.test) old_count = dict(self.name_count) old_dead = set(self.dead) old_deps = {a: set(b) for a, b in self.use.items()} # wrap body in a list if we come from an ifExp body = node.body if isinstance(node.body, list) else [node.body] for stmt in body: self.visit(stmt) mid_count = self.name_count mid_dead = self.dead mid_deps = self.use self.name_count = old_count self.dead = old_dead self.use = old_deps # wrap orelse in a list if we come from an ifExp orelse = (node.orelse if isinstance(node.orelse, list) else [node.orelse]) for stmt in orelse: self.visit(stmt) # merge use variable for key in self.use: if key in mid_deps: self.use[key].update(mid_deps[key]) for key in mid_deps: if key not in self.use: self.use[key] = set(mid_deps[key]) # value is the worse case of both branches names = set(self.name_count.keys()).union(mid_count.keys()) for name in names: val_body = mid_count.get(name, 0) val_else = self.name_count.get(name, 0) self.name_count[name] = max(val_body, val_else) # dead var are still dead self.dead.update(mid_dead) visit_IfExp = visit_If def visit_loop(self, body): # we start a new loop so we init the "at start of loop use" counter old_pre_count = self.pre_loop_count self.pre_loop_count = dict() # do visit body for stmt in body: self.visit(stmt) # variable use in loop but not assigned are no lazy no_assign = [n for n, (_, a) in self.pre_loop_count.items() if not a] self.result.update(zip(no_assign, [LazynessAnalysis.MANY] * len(no_assign))) # lazyness value is the max of previous lazyness and lazyness for one # iteration in the loop for k, v in self.pre_loop_count.items(): loop_value = v[0] + self.name_count[k] self.result[k] = max(self.result.get(k, 0), loop_value) # variable dead at the end of the loop but use at the beginning of it # can't be lazy dead = self.dead.intersection(self.pre_loop_count) self.result.update(zip(dead, [LazynessAnalysis.INF] * len(dead))) # merge previous count of "use at start of loop" and current state. for k, v in old_pre_count.items(): if v[1] or k not in self.pre_loop_count: self.pre_loop_count[k] = v else: self.pre_loop_count[k] = (v[0] + self.pre_loop_count[k][0], self.pre_loop_count[k][1]) def visit_For(self, node): md.visit(self, node) ids = self.gather(Identifiers, node.iter) if isinstance(node.target, ast.Name): self.assign_to(node.target, ids) self.result[node.target.id] = LazynessAnalysis.INF else: err = "Assignation in for loop not to a Name" raise PythranSyntaxError(err, node) self.visit_loop(node.body) for stmt in node.orelse: self.visit(stmt) def visit_While(self, node): md.visit(self, node) self.visit(node.test) self.visit_loop(node.body) for stmt in node.orelse: self.visit(stmt) def func_args_lazyness(self, func_name, args, node): for fun in self.aliases[func_name]: if isinstance(fun, ast.Call): # call to partial functions self.func_args_lazyness(fun.args[0], fun.args[1:] + args, node) elif fun in self.argument_effects: # when there is an argument effect, apply "modify" to the arg for i, arg in enumerate(self.argument_effects[fun]): # check len of args as default is 11 args if arg and len(args) > i: if isinstance(args[i], ast.Name): self.modify(args[i].id) elif isinstance(fun, ast.Name): # it may be a variable to a function. Lazyness will be compute # correctly thanks to aliasing continue else: # conservative choice for arg in args: self.modify(arg) def visit_Call(self, node): """ Compute use of variables in a function call. Each arg is use once and function name too. Information about modified arguments is forwarded to func_args_lazyness. """ md.visit(self, node) for arg in node.args: self.visit(arg) self.func_args_lazyness(node.func, node.args, node) self.visit(node.func) def run(self, node): result = super(LazynessAnalysis, self).run(node) # update result with last name_count values for name, val in self.name_count.items(): old_val = result.get(name, 0) result[name] = max(old_val, val) self.result = result return self.result