def instrument_inline_candidates(graphs, threshold): cache = {None: False} def candidate(graph): try: return cache[graph] except KeyError: res = static_instruction_count(graph) <= threshold cache[graph] = res return res n = 0 for parentgraph in graphs: for block in parentgraph.iterblocks(): ops = block.operations i = len(ops)-1 while i >= 0: op = ops[i] i -= 1 if op.opname == "direct_call": funcobj = get_funcobj(op.args[0].value) graph = getattr(funcobj, 'graph', None) if graph is not None: if getattr(getattr(funcobj, '_callable', None), '_dont_inline_', False): continue if candidate(graph): tag = Constant('inline', Void) label = Constant(n, Signed) dummy = Variable() dummy.concretetype = Void count = SpaceOperation('instrument_count', [tag, label], dummy) ops.insert(i+1, count) n += 1 log.inlining("%d call sites instrumented" % n)
def test_immutable_list_out_of_instance(self): from pypy.translator.simplify import get_funcobj for immutable_fields in (["a", "b"], ["a", "b", "y[*]"]): class A(object): _immutable_fields_ = immutable_fields class B(A): pass def f(i): b = B() lst = [i] lst[0] += 1 b.y = lst ll_assert(b.y is lst, "copying when reading out the attr?") return b.y[0] res = self.interpret(f, [10]) assert res == 11 t, rtyper, graph = self.gengraph(f, [int]) block = graph.startblock op = block.operations[-1] assert op.opname == 'direct_call' func = get_funcobj(op.args[0].value)._callable assert ('foldable' in func.func_name) == \ ("y[*]" in immutable_fields)
def test_immutable_list_out_of_instance(self): from pypy.translator.simplify import get_funcobj for immutable_fields in (["a", "b"], ["a", "b", "y[*]"]): class A(object): _immutable_fields_ = immutable_fields class B(A): pass def f(i): b = B() lst = [i] lst[0] += 1 b.y = lst ll_assert(b.y is lst, "copying when reading out the attr?") return b.y[0] res = self.interpret(f, [10]) assert res == 11 t, rtyper, graph = self.gengraph(f, [int]) block = graph.startblock op = block.operations[-1] assert op.opname == "direct_call" func = get_funcobj(op.args[0].value)._callable assert ("foldable" in func.func_name) == ("y[*]" in immutable_fields)
def test_read_really(self): class A(object): def __init__(self, y): self.y = y def f(self): self.x = 1 return self.y def h(flag): obj = A(flag) return obj.f() t, wa = self.translate(h, [int]) hgraph = graphof(t, h) op_call_f = hgraph.startblock.operations[-1] # check that we fished the expected ops assert op_call_f.opname == "direct_call" assert get_funcobj(op_call_f.args[0].value)._name == 'A.f' result = wa.analyze(op_call_f) assert len(result) == 2 result = list(result) result.sort() [(struct1, T1, name1), (struct2, T2, name2)] = result assert struct1 == "readstruct" assert name1.endswith("y") assert struct2 == "struct" assert name2.endswith("x") assert T1 == T2
def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE, extraeffect=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 = get_functype(op.args[0].concretetype) ARGS = FUNC.ARGS assert NON_VOID_ARGS == [T for T in ARGS if T is not lltype.Void] assert RESULT == FUNC.RESULT # ok # get the 'pure' and 'loopinvariant' flags from the function object pure = False loopinvariant = False if op.opname == "direct_call": func = getattr(get_funcobj(op.args[0].value), '_callable', None) pure = getattr(func, "_pure_function_", False) loopinvariant = getattr(func, "_jit_loop_invariant_", False) if loopinvariant: assert not NON_VOID_ARGS, ("arguments not supported for " "loop-invariant function!") # build the extraeffect can_invalidate = 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 pure: # XXX check what to do about exceptions (also MemoryError?) extraeffect = EffectInfo.EF_PURE elif self._canraise(op): extraeffect = EffectInfo.EF_CAN_RAISE else: extraeffect = EffectInfo.EF_CANNOT_RAISE # effectinfo = effectinfo_from_writeanalyze( self.readwrite_analyzer.analyze(op), self.cpu, extraeffect, oopspecindex, can_invalidate) # if oopspecindex != EffectInfo.OS_NONE: assert effectinfo is not None if pure or loopinvariant: assert effectinfo is not None 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 analyze_external_call(self, op, seen=None): funcobj = get_funcobj(op.args[0].value) result = self.bottom_result() if hasattr(funcobj, '_callbacks'): bk = self.translator.annotator.bookkeeper for function in funcobj._callbacks.callbacks: desc = bk.getdesc(function) for graph in desc.getgraphs(): result = self.join_two_results( result, self.analyze_direct_call(graph, seen)) return result
def maybe_on_top_of_llinterp(rtyper, fnptr): # Run a generated graph on top of the llinterp for testing. # When translated, this just returns the fnptr. funcobj = get_funcobj(fnptr) if hasattr(funcobj, 'graph'): llinterp = LLInterpreter(rtyper) #, exc_data_ptr=exc_data_ptr) def on_top_of_llinterp(*args): return llinterp.eval_graph(funcobj.graph, list(args)) else: assert hasattr(funcobj, '_callable') def on_top_of_llinterp(*args): return funcobj._callable(*args) return on_top_of_llinterp
def decode_builtin_call(op): if op.opname == 'oosend': SELFTYPE, name, opargs = decompose_oosend(op) return get_send_oopspec(SELFTYPE, name), opargs elif op.opname == 'direct_call': fnobj = get_funcobj(op.args[0].value) opargs = op.args[1:] return get_call_oopspec_opargs(fnobj, opargs) elif op.opname in ('oostring', 'oounicode'): return get_oostring_oopspec(op) elif op.opname == 'gc_identityhash': return get_identityhash_oopspec(op) else: raise ValueError(op.opname)
def iter_callsites(graph, calling_what): for block in graph.iterblocks(): for i, op in enumerate(block.operations): if op.opname == "direct_call": funcobj = get_funcobj(op.args[0].value) elif op.opname == "oosend": funcobj = get_meth_from_oosend(op) if funcobj is None: continue # cannot inline virtual methods else: continue graph = getattr(funcobj, 'graph', None) # accept a function or a graph as 'inline_func' if (graph is calling_what or getattr(funcobj, '_callable', None) is calling_what): yield graph, block, i
def iter_callsites(graph, calling_what): for block in graph.iterblocks(): for i, op in enumerate(block.operations): if op.opname == "direct_call": funcobj = get_funcobj(op.args[0].value) elif op.opname == "oosend": funcobj = get_meth_from_oosend(op) if funcobj is None: continue # cannot inline virtual methods else: continue graph = getattr(funcobj, 'graph', None) # accept a function or a graph as 'inline_func' if (calling_what is None or graph is calling_what or getattr(funcobj, '_callable', None) is calling_what): yield graph, block, i
def direct_call(hs_f1, *args_hs): bookkeeper = getbookkeeper() fnobj = get_funcobj(hs_f1.const) if (bookkeeper.annotator.policy.oopspec and hasattr(fnobj._callable, 'oopspec')): # try to handle the call as a high-level operation try: return handle_highlevel_operation(bookkeeper, fnobj._callable, *args_hs) except NotImplementedError: pass # normal call if not hasattr(fnobj, 'graph'): raise NotImplementedError("XXX call to externals or primitives") return hs_f1._call_single_graph(fnobj.graph, lltype.typeOf(fnobj).RESULT, *args_hs)
def maybe_on_top_of_llinterp(rtyper, fnptr): # Run a generated graph on top of the llinterp for testing. # When translated, this just returns the fnptr. def process_args(args): real_args = [] ARGS = lltype.typeOf(funcobj).ARGS i = 0 for ARG in ARGS: if ARG is lltype.Void: real_args.append(None) else: if ARG is lltype.Float: real_args.append(args[i]) elif isinstance(ARG, lltype.Primitive): real_args.append(lltype.cast_primitive(ARG, args[i])) elif isinstance(ARG, lltype.Ptr): if ARG.TO._gckind == 'gc': real_args.append(lltype.cast_opaque_ptr(ARG, args[i])) else: real_args.append(rffi.cast(ARG, args[i])) else: raise Exception("Unexpected arg: %s" % ARG) i += 1 return real_args funcobj = get_funcobj(fnptr) if hasattr(funcobj, 'graph'): # cache the llinterp; otherwise the remember_malloc/remember_free # done on the LLInterpreter don't match try: llinterp = rtyper._on_top_of_llinterp_llinterp except AttributeError: llinterp = LLInterpreter(rtyper) #, exc_data_ptr=exc_data_ptr) rtyper._on_top_of_llinterp_llinterp = llinterp def on_top_of_llinterp(*args): real_args = process_args(args) return llinterp.eval_graph(funcobj.graph, real_args) else: assert hasattr(funcobj, '_callable') def on_top_of_llinterp(*args): args = process_args(args) return funcobj._callable(*args) return on_top_of_llinterp
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 = get_funcobj(funcptr) if getattr(funcobj, 'graph', None) is None: return 'residual' targetgraph = funcobj.graph if (hasattr(targetgraph, 'func') and hasattr(targetgraph.func, 'oopspec')): return 'builtin' elif op.opname == 'oosend': SELFTYPE, methname, opargs = support.decompose_oosend(op) if SELFTYPE.oopspec_name is not None: return 'builtin' if self.graphs_from(op, is_candidate) is None: return 'residual' return 'regular'
def inlinable_static_callers(graphs): ok_to_call = set(graphs) result = [] for parentgraph in graphs: for block in parentgraph.iterblocks(): for op in block.operations: if op.opname == "direct_call": funcobj = get_funcobj(op.args[0].value) graph = getattr(funcobj, 'graph', None) if graph is not None and graph in ok_to_call: if getattr(getattr(funcobj, '_callable', None), '_dont_inline_', False): continue result.append((parentgraph, graph)) if op.opname == "oosend": meth = get_meth_from_oosend(op) graph = getattr(meth, 'graph', None) if graph is not None and graph in ok_to_call: result.append((parentgraph, graph)) return result
def search_for_calls(self, block): d = {} for i, op in enumerate(block.operations): if op.opname == "direct_call": funcobj = get_funcobj(op.args[0].value) elif op.opname == "oosend": funcobj = get_meth_from_oosend(op) if funcobj is None: continue else: continue graph = getattr(funcobj, 'graph', None) # accept a function or a graph as 'inline_func' if (graph is self.inline_func or getattr(funcobj, '_callable', None) is self.inline_func): d[i] = graph if d: self.block_to_index[block] = d else: try: del self.block_to_index[block] except KeyError: pass
def graphs_from(self, op, is_candidate=None): if is_candidate is None: is_candidate = self.is_candidate if op.opname == 'direct_call': funcobj = get_funcobj(op.args[0].value) graph = funcobj.graph if is_candidate(graph): return [graph] # common case: look inside this graph else: assert op.opname in ('indirect_call', 'oosend') if op.opname == 'indirect_call': graphs = op.args[-1].value else: v_obj = op.args[1].concretetype graphs = v_obj._lookup_graphs(op.args[0].value) # if graphs is None: # special case: handle the indirect call that goes to # the 'instantiate' methods. This check is a bit imprecise # but it's not too bad if we mistake a random indirect call # for the one to 'instantiate'. from pypy.rpython.lltypesystem import rclass CALLTYPE = op.args[0].concretetype if (op.opname == 'indirect_call' and len(op.args) == 2 and CALLTYPE == rclass.OBJECT_VTABLE.instantiate): graphs = list(self._graphs_of_all_instantiate()) # 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 check_call(op, fname): assert op.opname == "direct_call" assert get_funcobj(op.args[0].value)._name == fname
def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE, extraeffect=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 = get_functype(op.args[0].concretetype) ARGS = FUNC.ARGS assert NON_VOID_ARGS == [T for T in ARGS if T is not lltype.Void] assert RESULT == FUNC.RESULT # ok # get the 'elidable' and 'loopinvariant' flags from the function object elidable = False loopinvariant = False if op.opname == "direct_call": funcobj = get_funcobj(op.args[0].value) 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!") # 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 # effectinfo = effectinfo_from_writeanalyze( self.readwrite_analyzer.analyze(op), self.cpu, extraeffect, oopspecindex, can_invalidate) # 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 analyze_external_call(self, op, seen=None): fnobj = get_funcobj(op.args[0].value) return getattr(fnobj, 'canraise', True)
def get_graph_from_op(self, op): assert op.opname in ('direct_call', 'oosend') if op.opname == 'direct_call': return get_funcobj(self.op.args[0].value).graph else: return get_meth_from_oosend(op).graph
def consider_op_ts_metacall(self, hs_f1, hs_metadesccls, *args_hs): bookkeeper = self.bookkeeper fnobj = get_funcobj(hs_f1.const) return hintmodel.cannot_follow_call(bookkeeper, fnobj.graph, args_hs, lltype.typeOf(fnobj).RESULT)