class HintBookkeeper(object): def __init__(self, hannotator): self.pending_specializations = [] self.originflags = {} self.virtual_containers = {} self.descs = {} self.tsgraph_maximal_call_families = UnionFind(TsGraphCallFamily) self.annotator = hannotator self.tsgraphsigs = {} self.nonstuboriggraphcount = 0 self.stuboriggraphcount = 0 if hannotator is not None: # for tests t = hannotator.base_translator self.impurity_analyzer = ImpurityAnalyzer(t) # circular imports hack global hintmodel from pypy.jit.hintannotator import model as hintmodel def getdesc(self, graph): try: return self.descs[graph] except KeyError: self.descs[graph] = desc = GraphDesc(self, graph) return desc def enter(self, position_key): """Start of an operation. The operation is uniquely identified by the given key.""" res = getattr(self, 'position_key', None) self.position_key = position_key TLS.bookkeeper = self return res def leave(self, old=None): """End of an operation.""" if old is None: del TLS.bookkeeper del self.position_key else: self.position_key = old def myinputargorigin(self, graph, i): try: origin = self.originflags[graph, i] except KeyError: origin = hintmodel.InputArgOriginFlags(self, graph, i) self.originflags[graph, i] = origin return origin def myorigin(self): try: origin = self.originflags[self.position_key] except KeyError: assert len(self.position_key) == 3 graph, block, i = self.position_key spaceop = block.operations[i] spaceop = SpaceOperation(spaceop.opname, list(spaceop.args), spaceop.result) origin = hintmodel.OriginFlags(self, spaceop) self.originflags[self.position_key] = origin return origin def compute_at_fixpoint(self): binding = self.annotator.binding # for the entry point, we need to remove the 'myorigin' of # the input arguments (otherwise they will always be green, # as there is no call to the entry point to make them red) tsgraph = self.annotator.translator.graphs[0] for v in tsgraph.getargs(): hs_arg = binding(v) if isinstance(hs_arg, hintmodel.SomeLLAbstractConstant): hs_arg.myorigin = None # for convenience, force the return var to be red too, as # the timeshifter doesn't support anything else if self.annotator.policy.entrypoint_returns_red: v = tsgraph.getreturnvar() hs_red = hintmodel.variableoftype(v.concretetype) self.annotator.setbinding(v, hs_red) # propagate the green/red constraints log.event("Computing maximal green set...") greenorigindependencies = {} callreturndependencies = {} for origin in self.originflags.values(): origin.greenargs = True origin.record_dependencies(greenorigindependencies, callreturndependencies) while True: progress = False # check all calls to see if they are green calls or not for origin, graphs in callreturndependencies.items(): if self.is_green_call(origin.spaceop): pass # green call => don't force spaceop.result to red else: # non-green calls: replace the dependency with a regular # dependency from graph.getreturnvar() to spaceop.result del callreturndependencies[origin] retdeps = greenorigindependencies.setdefault(origin, []) for graph in graphs: retdeps.append(graph.getreturnvar()) # propagate normal dependencies for origin, deps in greenorigindependencies.items(): for v in deps: if not binding(v).is_green(): # not green => force the origin to be red too origin.greenargs = False del greenorigindependencies[origin] progress = True break if not progress: break for callfamily in self.tsgraph_maximal_call_families.infos(): if len(callfamily.tsgraphs) > 1: # if at least one graph in the family returns a red, # we force a red as the return of all of them returns_red = False for graph in callfamily.tsgraphs: if not binding(graph.getreturnvar()).is_green(): returns_red = True if returns_red: for graph in callfamily.tsgraphs: v = graph.getreturnvar() hs_red = hintmodel.variableoftype(v.concretetype) self.annotator.setbinding(v, hs_red) # compute and cache the signature of the graphs before they are # modified by further code ha = self.annotator for tsgraph in ha.translator.graphs: sig_hs = ([ha.binding(v) for v in tsgraph.getargs()], ha.binding(tsgraph.getreturnvar())) self.tsgraphsigs[tsgraph] = sig_hs def is_pure_graph(self, graph): impure = self.impurity_analyzer.analyze_direct_call(graph) return not impure def is_green_call(self, callop): "Is the given call operation completely computable at compile-time?" for v in callop.args: hs_arg = self.annotator.binding(v) if not hs_arg.is_green(): return False # all-green arguments. Note that we can return True even if the # result appears to be red; it's not a real red result then. impure = self.impurity_analyzer.analyze(callop) return not impure def immutableconstant(self, const): res = hintmodel.SomeLLAbstractConstant(const.concretetype, {}) res.const = const.value # we want null pointers to be deepfrozen! if isinstance(const.concretetype, (lltype.Ptr, ootype.Instance, ootype.BuiltinType, ootype.StaticMethod)): if not const.value: res.deepfrozen = True return res def immutablevalue(self, value): return self.immutableconstant(Constant(value, lltype.typeOf(value))) def current_op_concretetype(self): _, block, i = self.position_key op = block.operations[i] return op.result.concretetype def current_op_binding(self): _, block, i = self.position_key op = block.operations[i] hs_res = self.annotator.binding(op.result, annmodel.s_ImpossibleValue) return hs_res def getvirtualcontainerdef(self, TYPE, constructor=None): try: res = self.virtual_containers[self.position_key] assert res.T == TYPE except KeyError: if constructor is None: from pypy.jit.hintannotator.container import virtualcontainerdef constructor = virtualcontainerdef res = constructor(self, TYPE) self.virtual_containers[self.position_key] = res return res def warning(self, msg): return self.annotator.warning(msg) def specialization_key(self, fixed, args_hs): if fixed: return 'fixed' else: key = [] specialize = False for i, arg_hs in enumerate(args_hs): if isinstance(arg_hs, hintmodel.SomeLLAbstractVariable): key.append('v') specialize = True continue if (isinstance(arg_hs, hintmodel.SomeLLAbstractConstant) and arg_hs.eager_concrete): key.append('E') specialize = True else: key.append('x') if (isinstance(arg_hs, hintmodel.SomeLLAbstractConstant) and arg_hs.deepfrozen): key.append('D') specialize = True else: key.append('x') if specialize: return ''.join(key) else: return None def get_graph_by_key(self, graph, specialization_key): desc = self.getdesc(graph) return desc._cache[specialization_key] def get_graph_for_call(self, graph, fixed, args_hs): # this can modify args_hs in-place! key = self.specialization_key(fixed, args_hs) if key is None: alt_name = None else: alt_name = graph.name + '_H'+key desc = self.getdesc(graph) graph = desc.specialize(args_hs, key=key, alt_name=alt_name) return graph def graph_call(self, graph, fixed, args_hs, tsgraph_accum=None, hs_callable=None): input_args_hs = list(args_hs) graph = self.get_graph_for_call(graph, fixed, input_args_hs) if tsgraph_accum is not None: tsgraph_accum.append(graph) # save this if the caller cares # propagate fixing of arguments in the function to the caller for inp_arg_hs, arg_hs in zip(input_args_hs, args_hs): if isinstance(arg_hs, hintmodel.SomeLLAbstractConstant): assert len(inp_arg_hs.origins) == 1 [o] = inp_arg_hs.origins.keys() if o.read_fixed(): for o in arg_hs.origins: o.set_fixed() hs_res = self.annotator.recursivecall(graph, self.position_key, input_args_hs) # look on which input args the hs_res result depends on if isinstance(hs_res, hintmodel.SomeLLAbstractConstant): if (hs_callable is not None and not isinstance(hs_callable, hintmodel.SomeLLAbstractConstant)): hs_res = hintmodel.variableoftype(hs_res.concretetype, hs_res.deepfrozen) else: deps_hs = [] for hs_inputarg, hs_arg in zip(input_args_hs, args_hs): if isinstance(hs_inputarg, hintmodel.SomeLLAbstractConstant): assert len(hs_inputarg.origins) == 1 [o] = hs_inputarg.origins.keys() if o in hs_res.origins: deps_hs.append(hs_arg) if fixed: deps_hs.append(hs_res) hs_res = hintmodel.reorigin(hs_res, hs_callable, *deps_hs) return hs_res def graph_family_call(self, graph_list, fixed, args_hs, tsgraphs_accum=None, hs_callable=None): if tsgraphs_accum is None: tsgraphs = [] else: tsgraphs = tsgraphs_accum results_hs = [] for graph in graph_list: results_hs.append(self.graph_call(graph, fixed, args_hs, tsgraphs, hs_callable)) # put the tsgraphs in the same call family call_families = self.tsgraph_maximal_call_families _, rep, callfamily = call_families.find(tsgraphs[0]) for tsgraph in tsgraphs[1:]: _, rep, callfamily = call_families.union(rep, tsgraph) return annmodel.unionof(*results_hs)
def compute_lifetimes(self, graph): """Compute the static data flow of the graph: returns a list of LifeTime instances, each of which corresponds to a set of Variables from the graph. The variables are grouped in the same LifeTime if a value can pass from one to the other by following the links. Each LifeTime also records all places where a Variable in the set is used (read) or build (created). """ lifetimes = UnionFind(LifeTime) def set_creation_point(block, var, *cp): _, _, info = lifetimes.find((block, var)) info.creationpoints[cp] = True def set_use_point(block, var, *up): _, _, info = lifetimes.find((block, var)) info.usepoints[up] = True def union(block1, var1, block2, var2): if isinstance(var1, Variable): lifetimes.union((block1, var1), (block2, var2)) elif isinstance(var1, Constant): set_creation_point(block2, var2, "constant", var1) else: raise TypeError(var1) for var in graph.startblock.inputargs: set_creation_point(graph.startblock, var, "inputargs") set_use_point(graph.returnblock, graph.returnblock.inputargs[0], "return") set_use_point(graph.exceptblock, graph.exceptblock.inputargs[0], "except") set_use_point(graph.exceptblock, graph.exceptblock.inputargs[1], "except") def visit(node): if isinstance(node, Block): for op in node.operations: if op.opname in self.IDENTITY_OPS: # special-case these operations to identify their input # and output variables union(node, op.args[0], node, op.result) continue if op.opname in self.SUBSTRUCT_OPS: if self.visit_substruct_op(node, union, op): continue for i in range(len(op.args)): if isinstance(op.args[i], Variable): set_use_point(node, op.args[i], "op", node, op, i) set_creation_point(node, op.result, "op", node, op) if isinstance(node.exitswitch, Variable): set_use_point(node, node.exitswitch, "exitswitch", node) if isinstance(node, Link): if isinstance(node.last_exception, Variable): set_creation_point(node.prevblock, node.last_exception, "last_exception") if isinstance(node.last_exc_value, Variable): set_creation_point(node.prevblock, node.last_exc_value, "last_exc_value") d = {} for i, arg in enumerate(node.args): union(node.prevblock, arg, node.target, node.target.inputargs[i]) if isinstance(arg, Variable): if arg in d: # same variable present several times in link.args # consider it as a 'use' of the variable, which # will disable malloc optimization (aliasing problems) set_use_point(node.prevblock, arg, "dup", node, i) else: d[arg] = True traverse(visit, graph) return lifetimes.infos()
def compute_lifetimes(self, graph): """Compute the static data flow of the graph: returns a list of LifeTime instances, each of which corresponds to a set of Variables from the graph. The variables are grouped in the same LifeTime if a value can pass from one to the other by following the links. Each LifeTime also records all places where a Variable in the set is used (read) or build (created). """ lifetimes = UnionFind(LifeTime) def set_creation_point(block, var, *cp): _, _, info = lifetimes.find((block, var)) info.creationpoints[cp] = True def set_use_point(block, var, *up): _, _, info = lifetimes.find((block, var)) info.usepoints[up] = True def union(block1, var1, block2, var2): if isinstance(var1, Variable): lifetimes.union((block1, var1), (block2, var2)) elif isinstance(var1, Constant): set_creation_point(block2, var2, "constant", var1) else: raise TypeError(var1) for var in graph.startblock.inputargs: set_creation_point(graph.startblock, var, "inputargs") set_use_point(graph.returnblock, graph.returnblock.inputargs[0], "return") set_use_point(graph.exceptblock, graph.exceptblock.inputargs[0], "except") set_use_point(graph.exceptblock, graph.exceptblock.inputargs[1], "except") def visit(node): if isinstance(node, Block): for op in node.operations: if op.opname in self.IDENTITY_OPS: # special-case these operations to identify their input # and output variables union(node, op.args[0], node, op.result) continue if op.opname in self.SUBSTRUCT_OPS: if self.visit_substruct_op(node, union, op): continue for i in range(len(op.args)): if isinstance(op.args[i], Variable): set_use_point(node, op.args[i], "op", node, op, i) set_creation_point(node, op.result, "op", node, op) if isinstance(node.exitswitch, Variable): set_use_point(node, node.exitswitch, "exitswitch", node) if isinstance(node, Link): if isinstance(node.last_exception, Variable): set_creation_point(node.prevblock, node.last_exception, "last_exception") if isinstance(node.last_exc_value, Variable): set_creation_point(node.prevblock, node.last_exc_value, "last_exc_value") d = {} for i, arg in enumerate(node.args): union(node.prevblock, arg, node.target, node.target.inputargs[i]) if isinstance(arg, Variable): if arg in d: # same variable present several times in link.args # consider it as a 'use' of the variable, which # will disable malloc optimization (aliasing problems) set_use_point(node.prevblock, arg, "dup", node, i) else: d[arg] = True traverse(visit, graph) return lifetimes.infos()