1""" GlobalEffects computes function effect on global state. """ 2 3from pythran.analyses.aliases import Aliases 4from pythran.analyses.global_declarations import GlobalDeclarations 5from pythran.passmanager import ModuleAnalysis 6from pythran.tables import MODULES 7from pythran.graph import DiGraph 8import pythran.intrinsic as intrinsic 9 10import gast as ast 11from functools import reduce 12 13 14class GlobalEffects(ModuleAnalysis): 15 16 """Add a flag on each function that updates a global variable.""" 17 18 class FunctionEffect(object): 19 def __init__(self, node): 20 self.func = node 21 if isinstance(node, ast.FunctionDef): 22 self.global_effect = False 23 elif isinstance(node, intrinsic.Intrinsic): 24 self.global_effect = node.global_effects 25 elif isinstance(node, ast.alias): 26 self.global_effect = False 27 elif isinstance(node, str): 28 self.global_effect = False 29 elif isinstance(node, intrinsic.Class): 30 self.global_effect = False 31 elif isinstance(node, intrinsic.UnboundValueType): 32 self.global_effect = True # conservative choice 33 else: 34 print(type(node), node) 35 raise NotImplementedError 36 37 def __init__(self): 38 self.result = DiGraph() 39 self.node_to_functioneffect = dict() 40 super(GlobalEffects, self).__init__(Aliases, GlobalDeclarations) 41 42 def prepare(self, node): 43 """ 44 Initialise globals effects as this analyse is inter-procedural. 45 46 Initialisation done for Pythonic functions and default value set for 47 user defined functions. 48 """ 49 super(GlobalEffects, self).prepare(node) 50 51 def register_node(module): 52 """ Recursively save globals effect for all functions. """ 53 for v in module.values(): 54 if isinstance(v, dict): # Submodule case 55 register_node(v) 56 else: 57 fe = GlobalEffects.FunctionEffect(v) 58 self.node_to_functioneffect[v] = fe 59 self.result.add_node(fe) 60 if isinstance(v, intrinsic.Class): 61 register_node(v.fields) 62 63 register_node(self.global_declarations) 64 for module in MODULES.values(): 65 register_node(module) 66 self.node_to_functioneffect[intrinsic.UnboundValue] = \ 67 GlobalEffects.FunctionEffect(intrinsic.UnboundValue) 68 69 def run(self, node): 70 result = super(GlobalEffects, self).run(node) 71 keep_going = True 72 while keep_going: 73 keep_going = False 74 for function in result: 75 if function.global_effect: 76 for pred in self.result.predecessors(function): 77 if not pred.global_effect: 78 keep_going = pred.global_effect = True 79 self.result = {f.func for f in result if f.global_effect} 80 return self.result 81 82 def visit_FunctionDef(self, node): 83 self.current_function = self.node_to_functioneffect[node] 84 assert self.current_function in self.result 85 self.generic_visit(node) 86 87 def visit_Print(self, _): 88 self.current_function.global_effect = True 89 90 def visit_Call(self, node): 91 # try to get all aliases of the function, if possible 92 # else use [] as a fallback 93 func_aliases = self.aliases[node.func] 94 # expand argument if any 95 func_aliases = reduce( 96 # all funcs 97 lambda x, y: x + (list(self.node_to_functioneffect.keys()) 98 if isinstance(y, ast.Name) else [y]), 99 func_aliases, 100 list()) 101 for func_alias in func_aliases: 102 # special hook for bound functions 103 if isinstance(func_alias, ast.Call): 104 fake_call = ast.Call(func_alias.args[0], 105 func_alias.args[1:], []) 106 self.visit(fake_call) 107 continue 108 109 # conservative choice 110 if func_alias not in self.node_to_functioneffect: 111 func_alias = intrinsic.UnboundValue 112 113 func_alias = self.node_to_functioneffect[func_alias] 114 self.result.add_edge(self.current_function, func_alias) 115 self.generic_visit(node) 116