def translate(self, func, sig): t = TranslationContext() t.buildannotator().build_types(func, sig) t.buildrtyper().specialize() if option.view: t.view() return t, RaiseAnalyzer(t)
def simple_inline_function(translator, inline_func, graph): inliner = Inliner(translator, graph, inline_func, translator.rtyper.lltype_to_classdef_mapping(), raise_analyzer=RaiseAnalyzer(translator)) return inliner.inline_all()
def __init__(self, cpu=None, jitdrivers_sd=[]): assert isinstance(jitdrivers_sd, list) # debugging self.cpu = cpu self.jitdrivers_sd = jitdrivers_sd self.jitcodes = {} # map {graph: jitcode} self.unfinished_graphs = [] # list of graphs with pending jitcodes self.callinfocollection = CallInfoCollection() if hasattr(cpu, 'rtyper'): # for tests self.rtyper = cpu.rtyper translator = self.rtyper.annotator.translator self.raise_analyzer = RaiseAnalyzer(translator) self.readwrite_analyzer = ReadWriteAnalyzer(translator) self.virtualizable_analyzer = VirtualizableAnalyzer(translator) self.quasiimmut_analyzer = QuasiImmutAnalyzer(translator) self.randomeffects_analyzer = RandomEffectsAnalyzer(translator) self.seen = DependencyTracker(self.readwrite_analyzer) else: self.seen = None # for index, jd in enumerate(jitdrivers_sd): jd.index = index
def __init__(self, cpu=None, jitdrivers_sd=[]): assert isinstance(jitdrivers_sd, list) # debugging self.cpu = cpu self.jitdrivers_sd = jitdrivers_sd self.jitcodes = {} # map {graph: jitcode} self.unfinished_graphs = [] # list of graphs with pending jitcodes self.callinfocollection = CallInfoCollection() if hasattr(cpu, 'rtyper'): # for tests self.rtyper = cpu.rtyper translator = self.rtyper.annotator.translator self.raise_analyzer = RaiseAnalyzer(translator) self.raise_analyzer_ignore_memoryerror = RaiseAnalyzer(translator) self.raise_analyzer_ignore_memoryerror.do_ignore_memory_error() self.readwrite_analyzer = ReadWriteAnalyzer(translator) self.virtualizable_analyzer = VirtualizableAnalyzer(translator) self.quasiimmut_analyzer = QuasiImmutAnalyzer(translator) self.randomeffects_analyzer = RandomEffectsAnalyzer(translator) self.seen = DependencyTracker(self.readwrite_analyzer) else: self.seen = None # for index, jd in enumerate(jitdrivers_sd): jd.index = index
def __init__(self, translator, inline=False): self.translator = translator self.seen_graphs = set() self.prepared = False self.minimal_transform = set() if translator: self.mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) else: self.mixlevelannotator = None self.inline = inline if translator and inline: self.lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping() self.raise_analyzer = RaiseAnalyzer(translator) self.graphs_to_inline = {} self.graph_dependencies = {} self.ll_finalizers_ptrs = [] if self.MinimalGCTransformer: self.minimalgctransformer = self.MinimalGCTransformer(self) else: self.minimalgctransformer = None
def inline_helpers(self, graph): if not self.prepared: raise Exception("Need to call prepare_inline_helpers first") if self.inline: raise_analyzer = RaiseAnalyzer(self.translator) to_enum = self.graph_dependencies.get(graph, self.graphs_to_inline) must_constfold = False for inline_graph in to_enum: try: inline.inline_function(self.translator, inline_graph, graph, self.lltype_to_classdef, raise_analyzer, cleanup=False) must_constfold = True except inline.CannotInline, e: print 'CANNOT INLINE:', e print '\t%s into %s' % (inline_graph, graph) cleanup_graph(graph) if must_constfold: constant_fold_graph(graph)
def inline_helpers(self, graphs): from rpython.translator.backendopt.inline import iter_callsites raise_analyzer = RaiseAnalyzer(self.translator) for graph in graphs: to_enum = [] for called, block, i in iter_callsites(graph, None): if called in self.graphs_to_inline: to_enum.append(called) must_constfold = False for inline_graph in to_enum: try: inline.inline_function(self.translator, inline_graph, graph, self.lltype_to_classdef, raise_analyzer, cleanup=False) must_constfold = True except inline.CannotInline, e: print 'CANNOT INLINE:', e print '\t%s into %s' % (inline_graph, graph) cleanup_graph(graph) if must_constfold: constant_fold_graph(graph)
def auto_inlining(translator, threshold=None, callgraph=None, call_count_pred=None, heuristic=inlining_heuristic): assert threshold is not None and threshold != 1 to_cleanup = {} from heapq import heappush, heappop, heapreplace, heapify callers = {} # {graph: {graphs-that-call-it}} callees = {} # {graph: {graphs-that-it-calls}} if callgraph is None: callgraph = inlinable_static_callers(translator.graphs) for graph1, graph2 in callgraph: callers.setdefault(graph2, {})[graph1] = True callees.setdefault(graph1, {})[graph2] = True # the -len(callers) change is OK heap = [(0.0, -len(callers[graph]), graph) for graph in callers] valid_weight = {} try_again = {} lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping() raise_analyzer = RaiseAnalyzer(translator) count = 0 while heap: weight, _, graph = heap[0] if not valid_weight.get(graph): if always_inline(graph): weight, fixed = 0.0, True else: weight, fixed = heuristic(graph) # Don't let 'weight' be NaN past this point. If we do, # then heapify() might (sometimes, rarely) not do its job # correctly. I suspect it's because the algorithm gets # confused by the fact that both 'a < b' and 'b < a' are # false. A concrete example: [39.0, 0.0, 33.0, nan, nan] # heapifies to [33.0, nan, 39.0, nan, 0.0], but 33.0 is # not the smallest item. if not (weight < 1e9): weight = 1e9 #print ' + cost %7.2f %50s' % (weight, graph.name) heapreplace(heap, (weight, -len(callers[graph]), graph)) valid_weight[graph] = True if not fixed: try_again[graph] = 'initial' continue if weight >= threshold: # finished... unless some graphs not in valid_weight would now # have a weight below the threshold. Re-insert such graphs # at the start of the heap finished = True for i in range(len(heap)): graph = heap[i][2] if not valid_weight.get(graph): heap[i] = (0.0, heap[i][1], graph) finished = False if finished: break else: heapify(heap) continue heappop(heap) if callers[graph]: if translator.config.translation.verbose: log.inlining('%7.2f %50s' % (weight, graph.name)) else: log.dot() for parentgraph in callers[graph]: if parentgraph == graph: continue subcount = 0 try: subcount = inline_function(translator, graph, parentgraph, lltype_to_classdef, raise_analyzer, call_count_pred, cleanup=False) to_cleanup[parentgraph] = True res = bool(subcount) except CannotInline as e: try_again[graph] = str(e) res = CannotInline if res is True: count += subcount # the parentgraph should now contain all calls that were # done by 'graph' for graph2 in callees.get(graph, {}): callees[parentgraph][graph2] = True callers[graph2][parentgraph] = True if parentgraph in try_again: # the parentgraph was previously uninlinable, but it has # been modified. Maybe now we can inline it into further # parents? del try_again[parentgraph] heappush(heap, (0.0, -len(callers[parentgraph]), parentgraph)) valid_weight[parentgraph] = False invalid = [(graph, msg) for graph, msg in try_again.items() if always_inline(graph) is True] if invalid: message = '\n'.join([ "%s has _always_inline_=True but inlining failed:\n\t%s" % (graph, msg) for (graph, msg) in invalid]) raise CannotInline(message) for graph in to_cleanup: cleanup_graph(graph) return count
class CallControl(object): virtualref_info = None # optionally set from outside has_libffi_call = False # default value def __init__(self, cpu=None, jitdrivers_sd=[]): assert isinstance(jitdrivers_sd, list) # debugging self.cpu = cpu self.jitdrivers_sd = jitdrivers_sd self.jitcodes = {} # map {graph: jitcode} self.unfinished_graphs = [] # list of graphs with pending jitcodes self.callinfocollection = CallInfoCollection() if hasattr(cpu, 'rtyper'): # for tests self.rtyper = cpu.rtyper translator = self.rtyper.annotator.translator self.raise_analyzer = RaiseAnalyzer(translator) self.readwrite_analyzer = ReadWriteAnalyzer(translator) self.virtualizable_analyzer = VirtualizableAnalyzer(translator) self.quasiimmut_analyzer = QuasiImmutAnalyzer(translator) self.randomeffects_analyzer = RandomEffectsAnalyzer(translator) self.seen = DependencyTracker(self.readwrite_analyzer) else: self.seen = None # for index, jd in enumerate(jitdrivers_sd): jd.index = index def find_all_graphs(self, policy): try: return self.candidate_graphs except AttributeError: pass is_candidate = policy.look_inside_graph assert len(self.jitdrivers_sd) > 0 todo = [jd.portal_graph for jd in self.jitdrivers_sd] if hasattr(self, 'rtyper'): for oopspec_name, ll_args, ll_res in support.inline_calls_to: c_func, _ = support.builtin_func_for_spec(self.rtyper, oopspec_name, ll_args, ll_res) todo.append(c_func.value._obj.graph) candidate_graphs = set(todo) def callers(): graph = top_graph print graph while graph in coming_from: graph = coming_from[graph] print '<-', graph coming_from = {} while todo: top_graph = todo.pop() for _, op in top_graph.iterblockops(): if op.opname not in ("direct_call", "indirect_call"): continue kind = self.guess_call_kind(op, is_candidate) # use callers() to view the calling chain in pdb if kind != "regular": continue for graph in self.graphs_from(op, is_candidate): if graph in candidate_graphs: continue assert is_candidate(graph) todo.append(graph) candidate_graphs.add(graph) coming_from[graph] = top_graph self.candidate_graphs = candidate_graphs return candidate_graphs def graphs_from(self, op, is_candidate=None): if is_candidate is None: is_candidate = self.is_candidate if op.opname == 'direct_call': funcobj = op.args[0].value._obj graph = funcobj.graph if is_candidate(graph): return [graph] # common case: look inside this graph else: assert op.opname == 'indirect_call' graphs = op.args[-1].value if graphs is not None: result = [] for graph in graphs: if is_candidate(graph): result.append(graph) if result: return result # common case: look inside these graphs, # and ignore the others if there are any # residual call case: we don't need to look into any graph return None def guess_call_kind(self, op, is_candidate=None): if op.opname == 'direct_call': funcptr = op.args[0].value if self.jitdriver_sd_from_portal_runner_ptr(funcptr) is not None: return 'recursive' funcobj = funcptr._obj if getattr(funcobj, 'graph', None) is None: return 'residual' targetgraph = funcobj.graph if hasattr(targetgraph, 'func'): # must never produce JitCode for a function with # _gctransformer_hint_close_stack_ set! if getattr(targetgraph.func, '_gctransformer_hint_close_stack_', False): return 'residual' if hasattr(targetgraph.func, 'oopspec'): return 'builtin' if self.graphs_from(op, is_candidate) is None: return 'residual' return 'regular' def is_candidate(self, graph): # used only after find_all_graphs() return graph in self.candidate_graphs def grab_initial_jitcodes(self): for jd in self.jitdrivers_sd: jd.mainjitcode = self.get_jitcode(jd.portal_graph) jd.mainjitcode.is_portal = True def enum_pending_graphs(self): while self.unfinished_graphs: graph = self.unfinished_graphs.pop() yield graph, self.jitcodes[graph] def get_jitcode(self, graph, called_from=None): # 'called_from' is only one of the callers, used for debugging. try: return self.jitcodes[graph] except KeyError: # must never produce JitCode for a function with # _gctransformer_hint_close_stack_ set! if hasattr(graph, 'func') and getattr(graph.func, '_gctransformer_hint_close_stack_', False): raise AssertionError( '%s has _gctransformer_hint_close_stack_' % (graph,)) # fnaddr, calldescr = self.get_jitcode_calldescr(graph) jitcode = JitCode(graph.name, fnaddr, calldescr, called_from=called_from) self.jitcodes[graph] = jitcode self.unfinished_graphs.append(graph) return jitcode def get_jitcode_calldescr(self, graph): """Return the calldescr that describes calls to the 'graph'. This returns a calldescr that is appropriate to attach to the jitcode corresponding to 'graph'. It has no extra effectinfo, because it is not needed there; it is only used by the blackhole interp to really do the call corresponding to 'inline_call' ops. """ fnptr = getfunctionptr(graph) FUNC = lltype.typeOf(fnptr).TO assert self.rtyper.type_system.name == "lltypesystem" fnaddr = llmemory.cast_ptr_to_adr(fnptr) NON_VOID_ARGS = [ARG for ARG in FUNC.ARGS if ARG is not lltype.Void] calldescr = self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS), FUNC.RESULT, EffectInfo.MOST_GENERAL) return (fnaddr, calldescr) def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE, extraeffect=None, extradescr=None): """Return the calldescr that describes all calls done by 'op'. This returns a calldescr that we can put in the corresponding call operation in the calling jitcode. It gets an effectinfo describing the effect of the call: which field types it may change, whether it can force virtualizables, whether it can raise, etc. """ NON_VOID_ARGS = [x.concretetype for x in op.args[1:] if x.concretetype is not lltype.Void] RESULT = op.result.concretetype # check the number and type of arguments FUNC = op.args[0].concretetype.TO ARGS = FUNC.ARGS if NON_VOID_ARGS != [T for T in ARGS if T is not lltype.Void]: raise Exception( "in operation %r: caling a function with signature %r, " "but passing actual arguments (ignoring voids) of types %r" % (op, FUNC, NON_VOID_ARGS)) if RESULT != FUNC.RESULT: raise Exception( "in operation %r: caling a function with signature %r, " "but the actual return type is %r" % (op, FUNC, RESULT)) # ok # get the 'elidable' and 'loopinvariant' flags from the function object elidable = False loopinvariant = False call_release_gil_target = llmemory.NULL if op.opname == "direct_call": funcobj = op.args[0].value._obj assert getattr(funcobj, 'calling_conv', 'c') == 'c', ( "%r: getcalldescr() with a non-default call ABI" % (op,)) func = getattr(funcobj, '_callable', None) elidable = getattr(func, "_elidable_function_", False) loopinvariant = getattr(func, "_jit_loop_invariant_", False) if loopinvariant: assert not NON_VOID_ARGS, ("arguments not supported for " "loop-invariant function!") if getattr(func, "_call_aroundstate_target_", None): call_release_gil_target = func._call_aroundstate_target_ call_release_gil_target = llmemory.cast_ptr_to_adr( call_release_gil_target) elif op.opname == 'indirect_call': # check that we're not trying to call indirectly some # function with the special flags graphs = op.args[-1].value for graph in (graphs or ()): if not hasattr(graph, 'func'): continue error = None if hasattr(graph.func, '_elidable_function_'): error = '@jit.elidable' if hasattr(graph.func, '_jit_loop_invariant_'): error = '@jit.loop_invariant' if hasattr(graph.func, '_call_aroundstate_target_'): error = '_call_aroundstate_target_' if not error: continue raise Exception( "%r is an indirect call to a family of functions " "(or methods) that includes %r. However, the latter " "is marked %r. You need to use an indirection: replace " "it with a non-marked function/method which calls the " "marked function." % (op, graph, error)) # build the extraeffect random_effects = self.randomeffects_analyzer.analyze(op) if random_effects: extraeffect = EffectInfo.EF_RANDOM_EFFECTS # random_effects implies can_invalidate can_invalidate = random_effects or self.quasiimmut_analyzer.analyze(op) if extraeffect is None: if self.virtualizable_analyzer.analyze(op): extraeffect = EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE elif loopinvariant: extraeffect = EffectInfo.EF_LOOPINVARIANT elif elidable: if self._canraise(op): extraeffect = EffectInfo.EF_ELIDABLE_CAN_RAISE else: extraeffect = EffectInfo.EF_ELIDABLE_CANNOT_RAISE elif self._canraise(op): extraeffect = EffectInfo.EF_CAN_RAISE else: extraeffect = EffectInfo.EF_CANNOT_RAISE # # check that the result is really as expected if loopinvariant: if extraeffect != EffectInfo.EF_LOOPINVARIANT: raise Exception( "in operation %r: this calls a _jit_loop_invariant_ function," " but this contradicts other sources (e.g. it can have random" " effects): EF=%s" % (op, extraeffect)) if elidable: if extraeffect not in (EffectInfo.EF_ELIDABLE_CANNOT_RAISE, EffectInfo.EF_ELIDABLE_CAN_RAISE): raise Exception( "in operation %r: this calls an _elidable_function_," " but this contradicts other sources (e.g. it can have random" " effects): EF=%s" % (op, extraeffect)) # effectinfo = effectinfo_from_writeanalyze( self.readwrite_analyzer.analyze(op, self.seen), self.cpu, extraeffect, oopspecindex, can_invalidate, call_release_gil_target, extradescr, ) # assert effectinfo is not None if elidable or loopinvariant: assert extraeffect != EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE # XXX this should also say assert not can_invalidate, but # it can't because our analyzer is not good enough for now # (and getexecutioncontext() can't really invalidate) # return self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS), RESULT, effectinfo) def _canraise(self, op): if op.opname == 'pseudo_call_cannot_raise': return False try: return self.raise_analyzer.can_raise(op) except lltype.DelayedPointer: return True # if we need to look into the delayed ptr that is # the portal, then it's certainly going to raise def calldescr_canraise(self, calldescr): effectinfo = calldescr.get_extra_info() return effectinfo.check_can_raise() def jitdriver_sd_from_portal_graph(self, graph): for jd in self.jitdrivers_sd: if jd.portal_graph is graph: return jd return None def jitdriver_sd_from_portal_runner_ptr(self, funcptr): for jd in self.jitdrivers_sd: if funcptr is jd.portal_runner_ptr: return jd return None def jitdriver_sd_from_jitdriver(self, jitdriver): for jd in self.jitdrivers_sd: if jd.jitdriver is jitdriver: return jd return None def get_vinfo(self, VTYPEPTR): seen = set() for jd in self.jitdrivers_sd: if jd.virtualizable_info is not None: if jd.virtualizable_info.is_vtypeptr(VTYPEPTR): seen.add(jd.virtualizable_info) if seen: assert len(seen) == 1 return seen.pop() else: return None def could_be_green_field(self, GTYPE, fieldname): GTYPE_fieldname = (GTYPE, fieldname) for jd in self.jitdrivers_sd: if jd.greenfield_info is not None: if GTYPE_fieldname in jd.greenfield_info.green_fields: return True return False
class CallControl(object): virtualref_info = None # optionally set from outside has_libffi_call = False # default value def __init__(self, cpu=None, jitdrivers_sd=[]): assert isinstance(jitdrivers_sd, list) # debugging self.cpu = cpu self.jitdrivers_sd = jitdrivers_sd self.jitcodes = {} # map {graph: jitcode} self.unfinished_graphs = [] # list of graphs with pending jitcodes self.callinfocollection = CallInfoCollection() if hasattr(cpu, 'rtyper'): # for tests self.rtyper = cpu.rtyper translator = self.rtyper.annotator.translator self.raise_analyzer = RaiseAnalyzer(translator) self.raise_analyzer_ignore_memoryerror = RaiseAnalyzer(translator) self.raise_analyzer_ignore_memoryerror.do_ignore_memory_error() self.readwrite_analyzer = ReadWriteAnalyzer(translator) self.virtualizable_analyzer = VirtualizableAnalyzer(translator) self.quasiimmut_analyzer = QuasiImmutAnalyzer(translator) self.randomeffects_analyzer = RandomEffectsAnalyzer(translator) self.collect_analyzer = CollectAnalyzer(translator) self.seen_rw = DependencyTracker(self.readwrite_analyzer) self.seen_gc = DependencyTracker(self.collect_analyzer) # for index, jd in enumerate(jitdrivers_sd): jd.index = index def find_all_graphs(self, policy): try: return self.candidate_graphs except AttributeError: pass is_candidate = policy.look_inside_graph assert len(self.jitdrivers_sd) > 0 todo = [jd.portal_graph for jd in self.jitdrivers_sd] if hasattr(self, 'rtyper'): for oopspec_name, ll_args, ll_res in support.inline_calls_to: c_func, _ = support.builtin_func_for_spec( self.rtyper, oopspec_name, ll_args, ll_res) todo.append(c_func.value._obj.graph) candidate_graphs = set(todo) def callers(): graph = top_graph print graph while graph in coming_from: graph = coming_from[graph] print '<-', graph coming_from = {} while todo: top_graph = todo.pop() for _, op in top_graph.iterblockops(): if op.opname not in ("direct_call", "indirect_call"): continue kind = self.guess_call_kind(op, is_candidate) # use callers() to view the calling chain in pdb if kind != "regular": continue for graph in self.graphs_from(op, is_candidate): if graph in candidate_graphs: continue assert is_candidate(graph) todo.append(graph) candidate_graphs.add(graph) coming_from[graph] = top_graph self.candidate_graphs = candidate_graphs return candidate_graphs def graphs_from(self, op, is_candidate=None): if is_candidate is None: is_candidate = self.is_candidate if op.opname == 'direct_call': funcobj = op.args[0].value._obj graph = funcobj.graph if is_candidate(graph): return [graph] # common case: look inside this graph else: assert op.opname == 'indirect_call' graphs = op.args[-1].value if graphs is not None: result = [] for graph in graphs: if is_candidate(graph): result.append(graph) if result: return result # common case: look inside these graphs, # and ignore the others if there are any # residual call case: we don't need to look into any graph return None def guess_call_kind(self, op, is_candidate=None): if op.opname == 'direct_call': funcptr = op.args[0].value if self.jitdriver_sd_from_portal_runner_ptr(funcptr) is not None: return 'recursive' funcobj = funcptr._obj assert ( funcobj is not rposix._get_errno and funcobj is not rposix._set_errno ), ("the JIT must never come close to _get_errno() or _set_errno();" " it should all be done at a lower level") if getattr(funcobj, 'graph', None) is None: return 'residual' targetgraph = funcobj.graph if hasattr(targetgraph, 'func'): # must never produce JitCode for a function with # _gctransformer_hint_close_stack_ set! if getattr(targetgraph.func, '_gctransformer_hint_close_stack_', False): return 'residual' if hasattr(targetgraph.func, 'oopspec'): return 'builtin' if self.graphs_from(op, is_candidate) is None: return 'residual' return 'regular' def is_candidate(self, graph): # used only after find_all_graphs() return graph in self.candidate_graphs def grab_initial_jitcodes(self): for jd in self.jitdrivers_sd: jd.mainjitcode = self.get_jitcode(jd.portal_graph) jd.mainjitcode.jitdriver_sd = jd def enum_pending_graphs(self): while self.unfinished_graphs: graph = self.unfinished_graphs.pop() yield graph, self.jitcodes[graph] def get_jitcode(self, graph, called_from=None): # 'called_from' is only one of the callers, used for debugging. try: return self.jitcodes[graph] except KeyError: # must never produce JitCode for a function with # _gctransformer_hint_close_stack_ set! if hasattr(graph, 'func') and getattr( graph.func, '_gctransformer_hint_close_stack_', False): raise AssertionError( '%s has _gctransformer_hint_close_stack_' % (graph, )) # fnaddr, calldescr = self.get_jitcode_calldescr(graph) jitcode = JitCode(graph.name, fnaddr, calldescr, called_from=called_from) self.jitcodes[graph] = jitcode self.unfinished_graphs.append(graph) return jitcode def get_jitcode_calldescr(self, graph): """Return the calldescr that describes calls to the 'graph'. This returns a calldescr that is appropriate to attach to the jitcode corresponding to 'graph'. It has no extra effectinfo, because it is not needed there; it is only used by the blackhole interp to really do the call corresponding to 'inline_call' ops. """ fnptr = getfunctionptr(graph) FUNC = lltype.typeOf(fnptr).TO fnaddr = llmemory.cast_ptr_to_adr(fnptr) NON_VOID_ARGS = [ARG for ARG in FUNC.ARGS if ARG is not lltype.Void] calldescr = self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS), FUNC.RESULT, EffectInfo.MOST_GENERAL) return (fnaddr, calldescr) def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE, extraeffect=None, extradescr=None): """Return the calldescr that describes all calls done by 'op'. This returns a calldescr that we can put in the corresponding call operation in the calling jitcode. It gets an effectinfo describing the effect of the call: which field types it may change, whether it can force virtualizables, whether it can raise, etc. """ NON_VOID_ARGS = [ x.concretetype for x in op.args[1:] if x.concretetype is not lltype.Void ] RESULT = op.result.concretetype # check the number and type of arguments FUNC = op.args[0].concretetype.TO ARGS = FUNC.ARGS if NON_VOID_ARGS != [T for T in ARGS if T is not lltype.Void]: raise Exception( "in operation %r: caling a function with signature %r, " "but passing actual arguments (ignoring voids) of types %r" % (op, FUNC, NON_VOID_ARGS)) if RESULT != FUNC.RESULT: raise Exception( "in operation %r: caling a function with signature %r, " "but the actual return type is %r" % (op, FUNC, RESULT)) # ok # get the 'elidable' and 'loopinvariant' flags from the function object elidable = False loopinvariant = False call_release_gil_target = EffectInfo._NO_CALL_RELEASE_GIL_TARGET if op.opname == "direct_call": funcobj = op.args[0].value._obj assert getattr(funcobj, 'calling_conv', 'c') == 'c', ( "%r: getcalldescr() with a non-default call ABI" % (op, )) func = getattr(funcobj, '_callable', None) elidable = getattr(func, "_elidable_function_", False) loopinvariant = getattr(func, "_jit_loop_invariant_", False) if loopinvariant: assert not NON_VOID_ARGS, ("arguments not supported for " "loop-invariant function!") if getattr(func, "_call_aroundstate_target_", None): tgt_func, tgt_saveerr = func._call_aroundstate_target_ tgt_func = llmemory.cast_ptr_to_adr(tgt_func) call_release_gil_target = (tgt_func, tgt_saveerr) elif op.opname == 'indirect_call': # check that we're not trying to call indirectly some # function with the special flags graphs = op.args[-1].value for graph in (graphs or ()): if not hasattr(graph, 'func'): continue error = None if hasattr(graph.func, '_elidable_function_'): error = '@jit.elidable' if hasattr(graph.func, '_jit_loop_invariant_'): error = '@jit.loop_invariant' if hasattr(graph.func, '_call_aroundstate_target_'): error = '_call_aroundstate_target_' if not error: continue raise Exception( "%r is an indirect call to a family of functions " "(or methods) that includes %r. However, the latter " "is marked %r. You need to use an indirection: replace " "it with a non-marked function/method which calls the " "marked function." % (op, graph, error)) # build the extraeffect random_effects = self.randomeffects_analyzer.analyze(op) if random_effects: extraeffect = EffectInfo.EF_RANDOM_EFFECTS # random_effects implies can_invalidate can_invalidate = random_effects or self.quasiimmut_analyzer.analyze(op) if extraeffect is None: if self.virtualizable_analyzer.analyze(op): extraeffect = EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE elif loopinvariant: extraeffect = EffectInfo.EF_LOOPINVARIANT elif elidable: cr = self._canraise(op) if cr == "mem": extraeffect = EffectInfo.EF_ELIDABLE_OR_MEMORYERROR elif cr: extraeffect = EffectInfo.EF_ELIDABLE_CAN_RAISE else: extraeffect = EffectInfo.EF_ELIDABLE_CANNOT_RAISE elif self._canraise(op): # True or "mem" extraeffect = EffectInfo.EF_CAN_RAISE else: extraeffect = EffectInfo.EF_CANNOT_RAISE # # check that the result is really as expected if loopinvariant: if extraeffect != EffectInfo.EF_LOOPINVARIANT: raise Exception( "in operation %r: this calls a _jit_loop_invariant_ function," " but this contradicts other sources (e.g. it can have random" " effects): EF=%s" % (op, extraeffect)) if elidable: if extraeffect not in (EffectInfo.EF_ELIDABLE_CANNOT_RAISE, EffectInfo.EF_ELIDABLE_OR_MEMORYERROR, EffectInfo.EF_ELIDABLE_CAN_RAISE): raise Exception( "in operation %r: this calls an elidable function," " but this contradicts other sources (e.g. it can have random" " effects): EF=%s" % (op, extraeffect)) elif RESULT is lltype.Void: raise Exception( "in operation %r: this calls an elidable function " "but the function has no result" % (op, )) # effectinfo = effectinfo_from_writeanalyze( self.readwrite_analyzer.analyze(op, self.seen_rw), self.cpu, extraeffect, oopspecindex, can_invalidate, call_release_gil_target, extradescr, self.collect_analyzer.analyze(op, self.seen_gc), ) # assert effectinfo is not None if elidable or loopinvariant: assert (effectinfo.extraeffect < EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE) # XXX this should also say assert not can_invalidate, but # it can't because our analyzer is not good enough for now # (and getexecutioncontext() can't really invalidate) # return self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS), RESULT, effectinfo) def _canraise(self, op): """Returns True, False, or "mem" to mean 'only MemoryError'.""" if op.opname == 'pseudo_call_cannot_raise': return False try: if self.raise_analyzer.can_raise(op): if self.raise_analyzer_ignore_memoryerror.can_raise(op): return True else: return "mem" else: return False except lltype.DelayedPointer: return True # if we need to look into the delayed ptr that is # the portal, then it's certainly going to raise def calldescr_canraise(self, calldescr): effectinfo = calldescr.get_extra_info() return effectinfo.check_can_raise() def jitdriver_sd_from_portal_graph(self, graph): for jd in self.jitdrivers_sd: if jd.portal_graph is graph: return jd return None def jitdriver_sd_from_portal_runner_ptr(self, funcptr): for jd in self.jitdrivers_sd: if funcptr is jd.portal_runner_ptr: return jd return None def jitdriver_sd_from_jitdriver(self, jitdriver): for jd in self.jitdrivers_sd: if jd.jitdriver is jitdriver: return jd return None def get_vinfo(self, VTYPEPTR): seen = set() for jd in self.jitdrivers_sd: if jd.virtualizable_info is not None: if jd.virtualizable_info.is_vtypeptr(VTYPEPTR): seen.add(jd.virtualizable_info) if seen: assert len(seen) == 1 return seen.pop() else: return None def could_be_green_field(self, GTYPE, fieldname): GTYPE_fieldname = (GTYPE, fieldname) for jd in self.jitdrivers_sd: if jd.greenfield_info is not None: if GTYPE_fieldname in jd.greenfield_info.green_fields: return True return False
def test_memoryerror(self): def f(x): return [x, 42] t, ra = self.translate(f, [int]) result = ra.analyze_direct_call(graphof(t, f)) assert result # ra = RaiseAnalyzer(t) ra.do_ignore_memory_error() result = ra.analyze_direct_call(graphof(t, f)) assert not result # def g(x): try: return f(x) except: raise t, ra = self.translate(g, [int]) ra.do_ignore_memory_error() result = ra.analyze_direct_call(graphof(t, g)) assert not result # def h(x): return {5:6}[x] t, ra = self.translate(h, [int]) ra.do_ignore_memory_error() # but it's potentially a KeyError result = ra.analyze_direct_call(graphof(t, h)) assert result
def auto_inlining(translator, threshold=None, callgraph=None, call_count_pred=None, heuristic=inlining_heuristic): assert threshold is not None and threshold != 1 to_cleanup = {} from heapq import heappush, heappop, heapreplace, heapify callers = {} # {graph: {graphs-that-call-it}} callees = {} # {graph: {graphs-that-it-calls}} if callgraph is None: callgraph = inlinable_static_callers(translator.graphs) for graph1, graph2 in callgraph: callers.setdefault(graph2, {})[graph1] = True callees.setdefault(graph1, {})[graph2] = True # the -len(callers) change is OK heap = [(0.0, -len(callers[graph]), graph) for graph in callers] valid_weight = {} try_again = {} lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping() raise_analyzer = RaiseAnalyzer(translator) count = 0 while heap: weight, _, graph = heap[0] if not valid_weight.get(graph): if always_inline(graph): weight, fixed = 0.0, True else: weight, fixed = heuristic(graph) #print ' + cost %7.2f %50s' % (weight, graph.name) heapreplace(heap, (weight, -len(callers[graph]), graph)) valid_weight[graph] = True if not fixed: try_again[graph] = 'initial' continue if weight >= threshold: # finished... unless some graphs not in valid_weight would now # have a weight below the threshold. Re-insert such graphs # at the start of the heap finished = True for i in range(len(heap)): graph = heap[i][2] if not valid_weight.get(graph): heap[i] = (0.0, heap[i][1], graph) finished = False if finished: break else: heapify(heap) continue heappop(heap) if callers[graph]: if translator.config.translation.verbose: log.inlining('%7.2f %50s' % (weight, graph.name)) else: log.dot() for parentgraph in callers[graph]: if parentgraph == graph: continue subcount = 0 try: subcount = inline_function(translator, graph, parentgraph, lltype_to_classdef, raise_analyzer, call_count_pred, cleanup=False) to_cleanup[parentgraph] = True res = bool(subcount) except CannotInline, e: try_again[graph] = str(e) res = CannotInline if res is True: count += subcount # the parentgraph should now contain all calls that were # done by 'graph' for graph2 in callees.get(graph, {}): callees[parentgraph][graph2] = True callers[graph2][parentgraph] = True if parentgraph in try_again: # the parentgraph was previously uninlinable, but it has # been modified. Maybe now we can inline it into further # parents? del try_again[parentgraph] heappush(heap, (0.0, -len(callers[parentgraph]), parentgraph)) valid_weight[parentgraph] = False