def call_final_function(translator, final_func, annhelper=None): """When the program finishes normally, call 'final_func()'.""" from rpython.annotator import model as annmodel from rpython.rtyper.lltypesystem import lltype from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator own_annhelper = (annhelper is None) if own_annhelper: annhelper = MixLevelHelperAnnotator(translator.rtyper) c_final_func = annhelper.constfunc(final_func, [], annmodel.s_None) if own_annhelper: annhelper.finish() entry_point = translator.entry_point_graph v = copyvar(translator.annotator, entry_point.getreturnvar()) extrablock = Block([v]) v_none = varoftype(lltype.Void) newop = SpaceOperation('direct_call', [c_final_func], v_none) extrablock.operations = [newop] extrablock.closeblock(Link([v], entry_point.returnblock)) for block in entry_point.iterblocks(): if block is not extrablock: for link in block.exits: if link.target is entry_point.returnblock: link.target = extrablock checkgraph(entry_point)
def make_driverhook_graphs(self): from rpython.rlib.jit import BaseJitCell bk = self.rtyper.annotator.bookkeeper classdef = bk.getuniqueclassdef(BaseJitCell) s_BaseJitCell_or_None = annmodel.SomeInstance(classdef, can_be_None=True) s_BaseJitCell_not_None = annmodel.SomeInstance(classdef) s_Str = annmodel.SomeString() # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._set_jitcell_at_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.set_jitcell_at, annmodel.s_None, s_BaseJitCell_not_None) jd._get_jitcell_at_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_jitcell_at, s_BaseJitCell_or_None) jd._get_printable_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_printable_location, s_Str) jd._confirm_enter_jit_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) annhelper.finish()
def build_cpu(self, CPUClass, translate_support_code=False, no_stats=False, supports_floats=True, supports_longlong=True, supports_singlefloats=True, **kwds): assert CPUClass is not None self.opt = history.Options(**kwds) if no_stats: stats = history.NoStats() else: stats = history.Stats(None) self.stats = stats if translate_support_code: self.annhelper = MixLevelHelperAnnotator(self.translator.rtyper) cpu = CPUClass(self.translator.rtyper, self.stats, self.opt, translate_support_code, gcdescr=self.gcdescr) if not supports_floats: cpu.supports_floats = False if not supports_longlong: cpu.supports_longlong = False if not supports_singlefloats: cpu.supports_singlefloats = False self.cpu = cpu
def builtin_func_for_spec(rtyper, oopspec_name, ll_args, ll_res, extra=None, extrakey=None): assert (extra is None) == (extrakey is None) key = (oopspec_name, tuple(ll_args), ll_res, extrakey) try: return rtyper._builtin_func_for_spec_cache[key] except (KeyError, AttributeError): pass args_s = [lltype_to_annotation(v) for v in ll_args] if '.' not in oopspec_name: # 'newxxx' operations LIST_OR_DICT = ll_res else: LIST_OR_DICT = ll_args[0] s_result = lltype_to_annotation(ll_res) impl = setup_extra_builtin(rtyper, oopspec_name, len(args_s), extra) if getattr(impl, 'need_result_type', False): bk = rtyper.annotator.bookkeeper args_s.insert(0, annmodel.SomePBC([bk.getdesc(deref(ll_res))])) # if hasattr(rtyper, 'annotator'): # regular case mixlevelann = MixLevelHelperAnnotator(rtyper) c_func = mixlevelann.constfunc(impl, args_s, s_result) mixlevelann.finish() else: # for testing only c_func = Constant(oopspec_name, lltype.Ptr(lltype.FuncType(ll_args, ll_res))) # if not hasattr(rtyper, '_builtin_func_for_spec_cache'): rtyper._builtin_func_for_spec_cache = {} rtyper._builtin_func_for_spec_cache[key] = (c_func, LIST_OR_DICT) # return c_func, LIST_OR_DICT
def call_final_function(translator, final_func, annhelper=None): """When the program finishes normally, call 'final_func()'.""" from rpython.annotator import model as annmodel from rpython.rtyper.lltypesystem import lltype from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator own_annhelper = (annhelper is None) if own_annhelper: annhelper = MixLevelHelperAnnotator(translator.rtyper) c_final_func = annhelper.constfunc(final_func, [], annmodel.s_None) if own_annhelper: annhelper.finish() entry_point = translator.entry_point_graph v = entry_point.getreturnvar().copy() extrablock = Block([v]) v_none = varoftype(lltype.Void) newop = SpaceOperation('direct_call', [c_final_func], v_none) extrablock.operations = [newop] extrablock.closeblock(Link([v], entry_point.returnblock)) for block in entry_point.iterblocks(): if block is not extrablock: for link in block.exits: if link.target is entry_point.returnblock: link.target = extrablock checkgraph(entry_point)
def complete_destrptr(gctransformer): translator = gctransformer.translator mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) args_s = [lltype_to_annotation(STACKLET_PTR)] s_result = annmodel.s_None destrptr = mixlevelannotator.delayedfunction(stacklet_destructor, args_s, s_result) mixlevelannotator.finish() lltype.attachRuntimeTypeInfo(STACKLET, destrptr=destrptr)
def _setup_frame_realloc(self, translate_support_code): FUNC_TP = lltype.Ptr(lltype.FuncType([llmemory.GCREF, lltype.Signed], llmemory.GCREF)) base_ofs = self.get_baseofs_of_frame_field() def realloc_frame(frame, size): try: if not we_are_translated(): assert not self._exception_emulator[0] frame = lltype.cast_opaque_ptr(jitframe.JITFRAMEPTR, frame) if size > frame.jf_frame_info.jfi_frame_depth: # update the frame_info size, which is for whatever reason # not up to date frame.jf_frame_info.update_frame_depth(base_ofs, size) new_frame = jitframe.JITFRAME.allocate(frame.jf_frame_info) frame.jf_forward = new_frame i = 0 while i < len(frame.jf_frame): new_frame.jf_frame[i] = frame.jf_frame[i] frame.jf_frame[i] = 0 i += 1 new_frame.jf_savedata = frame.jf_savedata new_frame.jf_guard_exc = frame.jf_guard_exc # all other fields are empty llop.gc_writebarrier(lltype.Void, new_frame) return lltype.cast_opaque_ptr(llmemory.GCREF, new_frame) except Exception as e: print "Unhandled exception", e, "in realloc_frame" return lltype.nullptr(llmemory.GCREF.TO) def realloc_frame_crash(frame, size): print "frame", frame, "size", size return lltype.nullptr(llmemory.GCREF.TO) if not translate_support_code: fptr = llhelper(FUNC_TP, realloc_frame) else: FUNC = FUNC_TP.TO args_s = [lltype_to_annotation(ARG) for ARG in FUNC.ARGS] s_result = lltype_to_annotation(FUNC.RESULT) mixlevelann = MixLevelHelperAnnotator(self.rtyper) graph = mixlevelann.getgraph(realloc_frame, args_s, s_result) fptr = mixlevelann.graph2delayed(graph, FUNC) mixlevelann.finish() self.realloc_frame = heaptracker.adr2int(llmemory.cast_ptr_to_adr(fptr)) if not translate_support_code: fptr = llhelper(FUNC_TP, realloc_frame_crash) else: FUNC = FUNC_TP.TO args_s = [lltype_to_annotation(ARG) for ARG in FUNC.ARGS] s_result = lltype_to_annotation(FUNC.RESULT) mixlevelann = MixLevelHelperAnnotator(self.rtyper) graph = mixlevelann.getgraph(realloc_frame_crash, args_s, s_result) fptr = mixlevelann.graph2delayed(graph, FUNC) mixlevelann.finish() self.realloc_frame_crash = heaptracker.adr2int(llmemory.cast_ptr_to_adr(fptr))
def complete_destrptr(gctransformer): translator = gctransformer.translator mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) args_s = [lltype_to_annotation(STACKLET_PTR)] s_result = annmodel.s_None destrptr = mixlevelannotator.delayedfunction(stacklet_destructor, args_s, s_result) mixlevelannotator.finish() lltype.attachRuntimeTypeInfo(STACKLET, destrptr=destrptr)
def test_conditional_call(self): def g(): pass def f(n): conditional_call(n >= 0, g) def later(m): conditional_call(m, g) t = TranslationContext() t.buildannotator().build_types(f, [int]) t.buildrtyper().specialize() mix = MixLevelHelperAnnotator(t.rtyper) mix.getgraph(later, [annmodel.s_Bool], annmodel.s_None) mix.finish()
def test_enforced_args(): from rpython.annotator.model import s_None from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator from rpython.translator.interactive import Translation def f1(): str2charp("hello") def f2(): str2charp("world") t = Translation(f1, []) t.rtype() mixann = MixLevelHelperAnnotator(t.context.rtyper) mixann.getgraph(f2, [], s_None) mixann.finish()
def test_delayed_fnptr(): from rpython.flowspace.model import SpaceOperation from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator from rpython.translator.translator import TranslationContext t = TranslationContext() t.buildannotator() t.buildrtyper() annhelper = MixLevelHelperAnnotator(t.rtyper) def f(): pass c_f = annhelper.constfunc(f, [], None) op = SpaceOperation('direct_call', [c_f], None) analyzer = BoolGraphAnalyzer(t) assert analyzer.analyze(op)
def make_driverhook_graphs(self): # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._get_printable_location_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.get_printable_location, annmodel.SomeString()) jd._get_unique_id_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.get_unique_id, annmodel.SomeInteger()) jd._confirm_enter_jit_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) # items = [] types = () pos = () if jd.jitdriver.get_location: assert hasattr(jd.jitdriver.get_location, '_loc_types'), """ You must decorate your get_location function: from rpython.rlib.rjitlog import rjitlog as jl @jl.returns(jl.MP_FILENAME, jl.MP_XXX, ...) def get_loc(your, green, keys): name = "x.txt" # extract it from your green keys return (name, ...) """ types = jd.jitdriver.get_location._loc_types del jd.jitdriver.get_location._loc_types # for _, type in types: if type == 's': items.append(annmodel.SomeString()) elif type == 'i': items.append(annmodel.SomeInteger()) else: raise NotImplementedError s_Tuple = annmodel.SomeTuple(items) jd._get_location_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.get_location, s_Tuple) jd._get_loc_types = types annhelper.finish()
def builtin_func_for_spec(rtyper, oopspec_name, ll_args, ll_res, extra=None, extrakey=None): assert (extra is None) == (extrakey is None) key = (oopspec_name, tuple(ll_args), ll_res, extrakey) try: return rtyper._builtin_func_for_spec_cache[key] except (KeyError, AttributeError): pass args_s = [lltype_to_annotation(v) for v in ll_args] if '.' not in oopspec_name: # 'newxxx' operations LIST_OR_DICT = ll_res else: LIST_OR_DICT = ll_args[0] s_result = lltype_to_annotation(ll_res) impl = setup_extra_builtin(rtyper, oopspec_name, len(args_s), extra) if getattr(impl, 'need_result_type', False): if hasattr(rtyper, 'annotator'): bk = rtyper.annotator.bookkeeper ll_restype = ll_res if impl.need_result_type != 'exact': ll_restype = ll_restype.TO desc = bk.getdesc(ll_restype) else: class TestingDesc(object): knowntype = int pyobj = None desc = TestingDesc() args_s.insert(0, annmodel.SomePBC([desc])) # if hasattr(rtyper, 'annotator'): # regular case mixlevelann = MixLevelHelperAnnotator(rtyper) c_func = mixlevelann.constfunc(impl, args_s, s_result) mixlevelann.finish() else: # for testing only c_func = Constant(oopspec_name, lltype.Ptr(lltype.FuncType(ll_args, ll_res))) # if not hasattr(rtyper, '_builtin_func_for_spec_cache'): rtyper._builtin_func_for_spec_cache = {} rtyper._builtin_func_for_spec_cache[key] = (c_func, LIST_OR_DICT) # return c_func, LIST_OR_DICT
def make_driverhook_graphs(self): s_Str = annmodel.SomeString() # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._get_printable_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_printable_location, s_Str) jd._confirm_enter_jit_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) annhelper.finish()
def test_delayed_fnptr(): from rpython.flowspace.model import SpaceOperation from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator from rpython.translator.translator import TranslationContext t = TranslationContext() t.buildannotator() t.buildrtyper() annhelper = MixLevelHelperAnnotator(t.rtyper) def f(): pass c_f = annhelper.constfunc(f, [], None) op = SpaceOperation('direct_call', [c_f], None) analyzer = BoolGraphAnalyzer(t) assert analyzer.analyze(op)
def make_driverhook_graphs(self): s_Str = annmodel.SomeString() # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._get_printable_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_printable_location, s_Str) jd._confirm_enter_jit_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) annhelper.finish()
def make_driverhook_graphs(self): # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._get_printable_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_printable_location, annmodel.SomeString()) jd._get_unique_id_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_unique_id, annmodel.SomeInteger()) jd._confirm_enter_jit_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) # items = [] types = () pos = () if jd.jitdriver.get_location: assert hasattr(jd.jitdriver.get_location, '_loc_types'), """ You must decorate your get_location function: from rpython.rlib.rjitlog import rjitlog as jl @jl.returns(jl.MP_FILENAME, jl.MP_XXX, ...) def get_loc(your, green, keys): name = "x.txt" # extract it from your green keys return (name, ...) """ types = jd.jitdriver.get_location._loc_types del jd.jitdriver.get_location._loc_types # for _,type in types: if type == 's': items.append(annmodel.SomeString()) elif type == 'i': items.append(annmodel.SomeInteger()) else: raise NotImplementedError s_Tuple = annmodel.SomeTuple(items) jd._get_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_location, s_Tuple) jd._get_loc_types = types annhelper.finish()
def test_pseudohighlevelcallable(): t = TranslationContext() t.buildannotator() rtyper = t.buildrtyper() rtyper.specialize() a = MixLevelHelperAnnotator(rtyper) class A: value = 5 def double(self): return self.value * 2 def fn1(a): a2 = A() a2.value = a.double() return a2 s_A, r_A = a.s_r_instanceof(A) fn1ptr = a.delayedfunction(fn1, [s_A], s_A) pseudo = PseudoHighLevelCallable(fn1ptr, [s_A], s_A) def fn2(n): a = A() a.value = n a2 = pseudo(a) return a2.value graph = a.getgraph(fn2, [annmodel.SomeInteger()], annmodel.SomeInteger()) a.finish() llinterp = LLInterpreter(rtyper) res = llinterp.eval_graph(graph, [21]) assert res == 42
def replace_force_virtualizable(self, rtyper, graphs): from rpython.annotator import model as annmodel from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator graph = graphs[0] for block, op in graph.iterblockops(): if op.opname == 'jit_force_virtualizable': v_inst_ll_type = op.args[0].concretetype break def mycall(vinst_ll): if vinst_ll.vable_token: raise ValueError annhelper = MixLevelHelperAnnotator(rtyper) s_vinst = SomePtr(v_inst_ll_type) funcptr = annhelper.delayedfunction(mycall, [s_vinst], annmodel.s_None) annhelper.finish() replace_force_virtualizable_with_call(graphs, v_inst_ll_type, funcptr) return funcptr
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 replace_force_virtualizable(self, rtyper, graphs): from rpython.annotator import model as annmodel from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator graph = graphs[0] for block, op in graph.iterblockops(): if op.opname == 'jit_force_virtualizable': v_inst_ll_type = op.args[0].concretetype break def mycall(vinst_ll): if vinst_ll.vable_token: raise ValueError annhelper = MixLevelHelperAnnotator(rtyper) s_vinst = SomePtr(v_inst_ll_type) funcptr = annhelper.delayedfunction(mycall, [s_vinst], annmodel.s_None) annhelper.finish() replace_force_virtualizable_with_call(graphs, v_inst_ll_type, funcptr) return funcptr
def test_pseudohighlevelcallable(): t = TranslationContext() t.buildannotator() rtyper = t.buildrtyper() rtyper.specialize() a = MixLevelHelperAnnotator(rtyper) class A: value = 5 def double(self): return self.value * 2 def fn1(a): a2 = A() a2.value = a.double() return a2 s_A, r_A = a.s_r_instanceof(A) fn1ptr = a.delayedfunction(fn1, [s_A], s_A) pseudo = PseudoHighLevelCallable(fn1ptr, [s_A], s_A) def fn2(n): a = A() a.value = n a2 = pseudo(a) return a2.value graph = a.getgraph(fn2, [annmodel.SomeInteger()], annmodel.SomeInteger()) a.finish() llinterp = LLInterpreter(rtyper) res = llinterp.eval_graph(graph, [21]) assert res == 42
def call_initial_function(translator, initial_func, annhelper=None): """Before the program starts, call 'initial_func()'.""" from rpython.annotator import model as annmodel from rpython.rtyper.lltypesystem import lltype from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator own_annhelper = (annhelper is None) if own_annhelper: annhelper = MixLevelHelperAnnotator(translator.rtyper) c_initial_func = annhelper.constfunc(initial_func, [], annmodel.s_None) if own_annhelper: annhelper.finish() entry_point = translator.entry_point_graph args = [copyvar(translator.annotator, v) for v in entry_point.getargs()] extrablock = Block(args) v_none = varoftype(lltype.Void) newop = SpaceOperation('direct_call', [c_initial_func], v_none) extrablock.operations = [newop] extrablock.closeblock(Link(args, entry_point.startblock)) entry_point.startblock = extrablock checkgraph(entry_point)
def call_initial_function(translator, initial_func, annhelper=None): """Before the program starts, call 'initial_func()'.""" from rpython.annotator import model as annmodel from rpython.rtyper.lltypesystem import lltype from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator own_annhelper = (annhelper is None) if own_annhelper: annhelper = MixLevelHelperAnnotator(translator.rtyper) c_initial_func = annhelper.constfunc(initial_func, [], annmodel.s_None) if own_annhelper: annhelper.finish() entry_point = translator.entry_point_graph args = [v.copy() for v in entry_point.getargs()] extrablock = Block(args) v_none = varoftype(lltype.Void) newop = SpaceOperation('direct_call', [c_initial_func], v_none) extrablock.operations = [newop] extrablock.closeblock(Link(args, entry_point.startblock)) entry_point.startblock = extrablock checkgraph(entry_point)
def getentrypointptr(self): # XXX check that the entrypoint has the correct # signature: list-of-strings -> int if not self.make_entrypoint_wrapper: bk = self.translator.annotator.bookkeeper return getfunctionptr(bk.getdesc(self.entrypoint).getuniquegraph()) if self._entrypoint_wrapper is not None: return self._entrypoint_wrapper # from rpython.annotator import model as annmodel from rpython.rtyper.lltypesystem import rffi from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator from rpython.rtyper.llannotation import lltype_to_annotation entrypoint = self.entrypoint # def entrypoint_wrapper(argc, argv): """This is a wrapper that takes "Signed argc" and "char **argv" like the C main function, and puts them inside an RPython list of strings before invoking the real entrypoint() function. """ list = [""] * argc i = 0 while i < argc: list[i] = rffi.charp2str(argv[i]) i += 1 return entrypoint(list) # mix = MixLevelHelperAnnotator(self.translator.rtyper) args_s = [annmodel.SomeInteger(), lltype_to_annotation(rffi.CCHARPP)] s_result = annmodel.SomeInteger() graph = mix.getgraph(entrypoint_wrapper, args_s, s_result) mix.finish() res = getfunctionptr(graph) self._entrypoint_wrapper = res return res
def getentrypointptr(self): # XXX check that the entrypoint has the correct # signature: list-of-strings -> int if not self.make_entrypoint_wrapper: bk = self.translator.annotator.bookkeeper return getfunctionptr(bk.getdesc(self.entrypoint).getuniquegraph()) if self._entrypoint_wrapper is not None: return self._entrypoint_wrapper # from rpython.annotator import model as annmodel from rpython.rtyper.lltypesystem import rffi from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator from rpython.rtyper.llannotation import lltype_to_annotation entrypoint = self.entrypoint # def entrypoint_wrapper(argc, argv): """This is a wrapper that takes "Signed argc" and "char **argv" like the C main function, and puts them inside an RPython list of strings before invoking the real entrypoint() function. """ list = [""] * argc i = 0 while i < argc: list[i] = rffi.charp2str(argv[i]) i += 1 return entrypoint(list) # mix = MixLevelHelperAnnotator(self.translator.rtyper) args_s = [annmodel.SomeInteger(), lltype_to_annotation(rffi.CCHARPP)] s_result = annmodel.SomeInteger() graph = mix.getgraph(entrypoint_wrapper, args_s, s_result) mix.finish() res = getfunctionptr(graph) self._entrypoint_wrapper = res return res
def test_secondary_backendopt(self): # checks an issue with a newly added graph that calls an # already-exception-transformed graph. This can occur e.g. # from a late-seen destructor added by the GC transformer # which ends up calling existing code. def common(n): if n > 5: raise ValueError def main(n): common(n) def later(n): try: common(n) return 0 except ValueError: return 1 t = TranslationContext() t.buildannotator().build_types(main, [int]) t.buildrtyper().specialize() exctransformer = t.getexceptiontransformer() exctransformer.create_exception_handling(graphof(t, common)) from rpython.annotator import model as annmodel from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator annhelper = MixLevelHelperAnnotator(t.rtyper) later_graph = annhelper.getgraph(later, [annmodel.SomeInteger()], annmodel.SomeInteger()) annhelper.finish() annhelper.backend_optimize() # ^^^ as the inliner can't handle exception-transformed graphs, # this should *not* inline common() into later(). if option.view: later_graph.show() common_graph = graphof(t, common) found = False for block in later_graph.iterblocks(): for op in block.operations: if (op.opname == 'direct_call' and op.args[0].value._obj.graph is common_graph): found = True assert found, "cannot find the call (buggily inlined?)" from rpython.rtyper.llinterp import LLInterpreter llinterp = LLInterpreter(t.rtyper) res = llinterp.eval_graph(later_graph, [10]) assert res == 1
def test_enforced_args(): from rpython.annotator.model import s_None from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator from rpython.translator.interactive import Translation def f1(): str2charp("hello") def f2(): str2charp("world") t = Translation(f1, []) t.rtype() mixann = MixLevelHelperAnnotator(t.context.rtyper) mixann.getgraph(f2, [], s_None) mixann.finish()
def test_conditional_call(self): def g(): pass def f(n): conditional_call(n >= 0, g) def later(m): conditional_call(m, g) t = TranslationContext() t.buildannotator().build_types(f, [int]) t.buildrtyper().specialize() mix = MixLevelHelperAnnotator(t.rtyper) mix.getgraph(later, [annmodel.s_Bool], annmodel.s_None) mix.finish()
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.graphs_to_inline = {} self.graph_dependencies = {} self.ll_finalizers_ptrs = [] if self.MinimalGCTransformer: self.minimalgctransformer = self.MinimalGCTransformer(self) else: self.minimalgctransformer = None
def test_secondary_backendopt(self): # checks an issue with a newly added graph that calls an # already-exception-transformed graph. This can occur e.g. # from a late-seen destructor added by the GC transformer # which ends up calling existing code. def common(n): if n > 5: raise ValueError def main(n): common(n) def later(n): try: common(n) return 0 except ValueError: return 1 t = TranslationContext() t.buildannotator().build_types(main, [int]) t.buildrtyper().specialize() exctransformer = t.getexceptiontransformer() exctransformer.create_exception_handling(graphof(t, common)) from rpython.annotator import model as annmodel from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator annhelper = MixLevelHelperAnnotator(t.rtyper) later_graph = annhelper.getgraph(later, [annmodel.SomeInteger()], annmodel.SomeInteger()) annhelper.finish() annhelper.backend_optimize() # ^^^ as the inliner can't handle exception-transformed graphs, # this should *not* inline common() into later(). if option.view: later_graph.show() common_graph = graphof(t, common) found = False for block in later_graph.iterblocks(): for op in block.operations: if op.opname == "direct_call" and op.args[0].value._obj.graph is common_graph: found = True assert found, "cannot find the call (buggily inlined?)" from rpython.rtyper.llinterp import LLInterpreter llinterp = LLInterpreter(t.rtyper) res = llinterp.eval_graph(later_graph, [10]) assert res == 1
def build_cpu(self, CPUClass, translate_support_code=False, no_stats=False, supports_floats=True, supports_longlong=True, supports_singlefloats=True, **kwds): assert CPUClass is not None self.opt = history.Options(**kwds) if no_stats: stats = history.NoStats() else: stats = history.Stats(None) self.stats = stats if translate_support_code: self.annhelper = MixLevelHelperAnnotator(self.translator.rtyper) cpu = CPUClass(self.translator.rtyper, self.stats, self.opt, translate_support_code, gcdescr=self.gcdescr) if not supports_floats: cpu.supports_floats = False if not supports_longlong: cpu.supports_longlong = False if not supports_singlefloats: cpu.supports_singlefloats = False self.cpu = cpu
dump_arguments = rmarshal.get_marshaller(tuple(args_s)) load_result = rmarshal.get_loader(s_result) except (NotImplementedError, rmarshal.CannotMarshal, rmarshal.CannotUnmarshall), e: msg = 'Not Implemented: %s' % (e,) log.WARNING(msg) def execute(*args): not_implemented_stub(msg) else: def execute(*args): # marshal the function name and input arguments buf = [] dump_string(buf, fnname) dump_arguments(buf, args) # send the buffer and wait for the answer loader = sandboxed_io(buf) # decode the answer result = load_result(loader) loader.check_finished() return result execute = func_with_new_name(execute, 'sandboxed_' + fnname) ann = MixLevelHelperAnnotator(db.translator.rtyper) graph = ann.getgraph(execute, args_s, s_result) ann.finish() return graph
load_result = rmarshal.get_loader(s_result) except (NotImplementedError, rmarshal.CannotMarshal, rmarshal.CannotUnmarshall), e: msg = 'Not Implemented: %s' % (e, ) log.WARNING(msg) def execute(*args): not_implemented_stub(msg) else: def execute(*args): # marshal the function name and input arguments buf = [] dump_string(buf, fnname) dump_arguments(buf, args) # send the buffer and wait for the answer loader = sandboxed_io(buf) # decode the answer result = load_result(loader) loader.check_finished() return result execute = func_with_new_name(execute, 'sandboxed_' + fnname) ann = MixLevelHelperAnnotator(db.translator.rtyper) graph = ann.getgraph(execute, args_s, s_result) ann.finish() return graph
def getannmixlevel(self): if self.annmixlevel is not None: return self.annmixlevel from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator self.annmixlevel = MixLevelHelperAnnotator(self) return self.annmixlevel
class WarmRunnerDesc(object): def __init__(self, translator, policy=None, backendopt=True, CPUClass=None, ProfilerClass=EmptyProfiler, **kwds): pyjitpl._warmrunnerdesc = self # this is a global for debugging only! self.set_translator(translator) self.memory_manager = memmgr.MemoryManager() self.build_cpu(CPUClass, **kwds) self.inline_inlineable_portals() self.find_portals() self.codewriter = codewriter.CodeWriter(self.cpu, self.jitdrivers_sd) if policy is None: policy = JitPolicy() policy.set_supports_floats(self.cpu.supports_floats) policy.set_supports_longlong(self.cpu.supports_longlong) policy.set_supports_singlefloats(self.cpu.supports_singlefloats) graphs = self.codewriter.find_all_graphs(policy) policy.dump_unsafe_loops() self.check_access_directly_sanity(graphs) if backendopt: self.prejit_optimizations(policy, graphs) elif self.opt.listops: self.prejit_optimizations_minimal_inline(policy, graphs) self.build_meta_interp( ProfilerClass, translator.config.translation.jit_opencoder_model) self.make_args_specifications() # from rpython.jit.metainterp.virtualref import VirtualRefInfo vrefinfo = VirtualRefInfo(self) self.codewriter.setup_vrefinfo(vrefinfo) # from rpython.jit.metainterp import counter if self.cpu.translate_support_code: self.jitcounter = counter.JitCounter(translator=translator) else: self.jitcounter = counter.DeterministicJitCounter() # self.hooks = policy.jithookiface self.make_virtualizable_infos() self.make_driverhook_graphs() self.make_enter_functions() self.rewrite_jit_merge_points(policy) verbose = False # not self.cpu.translate_support_code self.rewrite_access_helpers() self.create_jit_entry_points() jitcodes = self.codewriter.make_jitcodes(verbose=verbose) self.metainterp_sd.jitcodes = jitcodes self.rewrite_can_enter_jits() self.rewrite_set_param_and_get_stats() self.rewrite_force_virtual(vrefinfo) self.rewrite_jitcell_accesses() self.rewrite_force_quasi_immutable() self.add_finish() self.metainterp_sd.finish_setup(self.codewriter) def finish(self): vinfos = set([jd.virtualizable_info for jd in self.jitdrivers_sd]) for vinfo in vinfos: if vinfo is not None: vinfo.finish() self.metainterp_sd.finish_setup_descrs() if self.cpu.translate_support_code: self.annhelper.finish() def _freeze_(self): return True def set_translator(self, translator): self.translator = translator self.rtyper = translator.rtyper self.gcdescr = gc.get_description(translator.config) def inline_inlineable_portals(self): """ Find all the graphs which have been decorated with @jitdriver.inline and inline them in the callers, making them JIT portals. Then, create a fresh copy of the jitdriver for each of those new portals, because they cannot share the same one. See test_ajit::test_inline_jit_merge_point """ from rpython.translator.backendopt.inline import ( inlinable_static_callers, auto_inlining) jmp_calls = {} def get_jmp_call(graph, _inline_jit_merge_point_): # there might be multiple calls to the @inlined function: the # first time we see it, we remove the call to the jit_merge_point # and we remember the corresponding op. Then, we create a new call # to it every time we need a new one (i.e., for each callsite # which becomes a new portal) try: op, jmp_graph = jmp_calls[graph] except KeyError: op, jmp_graph = fish_jmp_call(graph, _inline_jit_merge_point_) jmp_calls[graph] = op, jmp_graph # # clone the op newargs = op.args[:] newresult = Variable() newresult.concretetype = op.result.concretetype op = SpaceOperation(op.opname, newargs, newresult) return op, jmp_graph def fish_jmp_call(graph, _inline_jit_merge_point_): # graph is function which has been decorated with # @jitdriver.inline, so its very first op is a call to the # function which contains the actual jit_merge_point: fish it! jmp_block, op_jmp_call = next(callee.iterblockops()) msg = ( "The first operation of an _inline_jit_merge_point_ graph must be " "a direct_call to the function passed to @jitdriver.inline()") assert op_jmp_call.opname == 'direct_call', msg jmp_funcobj = op_jmp_call.args[0].value._obj assert jmp_funcobj._callable is _inline_jit_merge_point_, msg jmp_block.operations.remove(op_jmp_call) return op_jmp_call, jmp_funcobj.graph # find all the graphs which call an @inline_in_portal function callgraph = inlinable_static_callers(self.translator.graphs, store_calls=True) new_callgraph = [] new_portals = set() inlined_jit_merge_points = set() for caller, block, op_call, callee in callgraph: func = getattr(callee, 'func', None) _inline_jit_merge_point_ = getattr(func, '_inline_jit_merge_point_', None) if _inline_jit_merge_point_: _inline_jit_merge_point_._always_inline_ = True inlined_jit_merge_points.add(_inline_jit_merge_point_) op_jmp_call, jmp_graph = get_jmp_call( callee, _inline_jit_merge_point_) # # now we move the op_jmp_call from callee to caller, just # before op_call. We assume that the args passed to # op_jmp_call are the very same which are received by callee # (i.e., the one passed to op_call) assert len(op_call.args) == len(op_jmp_call.args) op_jmp_call.args[1:] = op_call.args[1:] idx = block.operations.index(op_call) block.operations.insert(idx, op_jmp_call) # # finally, we signal that we want to inline op_jmp_call into # caller, so that finally the actuall call to # driver.jit_merge_point will be seen there new_callgraph.append((caller, jmp_graph)) new_portals.add(caller) # inline them! inline_threshold = 0.1 # we rely on the _always_inline_ set above auto_inlining(self.translator, inline_threshold, new_callgraph) # clean up _always_inline_ = True, it can explode later for item in inlined_jit_merge_points: del item._always_inline_ # make a fresh copy of the JitDriver in all newly created # jit_merge_points self.clone_inlined_jit_merge_points(new_portals) def clone_inlined_jit_merge_points(self, graphs): """ Find all the jit_merge_points in the given graphs, and replace the original JitDriver with a fresh clone. """ if not graphs: return for graph, block, pos in find_jit_merge_points(graphs): op = block.operations[pos] v_driver = op.args[1] driver = v_driver.value if not driver.inline_jit_merge_point: continue new_driver = driver.clone() c_new_driver = Constant(new_driver, v_driver.concretetype) op.args[1] = c_new_driver def find_portals(self): self.jitdrivers_sd = [] graphs = self.translator.graphs for graph, block, pos in find_jit_merge_points(graphs): support.autodetect_jit_markers_redvars(graph) self.split_graph_and_record_jitdriver(graph, block, pos) # assert (len(set([jd.jitdriver for jd in self.jitdrivers_sd])) == len(self.jitdrivers_sd)), \ "there are multiple jit_merge_points with the same jitdriver" def split_graph_and_record_jitdriver(self, graph, block, pos): op = block.operations[pos] jd = JitDriverStaticData() jd._jit_merge_point_in = graph args = op.args[2:] s_binding = self.translator.annotator.binding jd._portal_args_s = [s_binding(v) for v in args] graph = copygraph(graph) [jmpp] = find_jit_merge_points([graph]) graph.startblock = support.split_before_jit_merge_point(*jmpp) # XXX this is incredibly obscure, but this is sometiems necessary # so we don't explode in checkgraph. for reasons unknown this # is not contanied within simplify_graph removenoops.remove_same_as(graph) # a crash in the following checkgraph() means that you forgot # to list some variable in greens=[] or reds=[] in JitDriver, # or that a jit_merge_point() takes a constant as an argument. checkgraph(graph) for v in graph.getargs(): assert isinstance(v, Variable) assert len(dict.fromkeys(graph.getargs())) == len(graph.getargs()) self.translator.graphs.append(graph) jd.portal_graph = graph # it's a bit unbelievable to have a portal without func assert hasattr(graph, "func") graph.func._dont_inline_ = True graph.func._jit_unroll_safe_ = True jd.jitdriver = block.operations[pos].args[1].value jd.vec = jd.jitdriver.vec jd.portal_runner_ptr = "<not set so far>" jd.result_type = history.getkind( jd.portal_graph.getreturnvar().concretetype)[0] self.jitdrivers_sd.append(jd) def check_access_directly_sanity(self, graphs): from rpython.translator.backendopt.inline import collect_called_graphs jit_graphs = set(graphs) for graph in collect_called_graphs(self.translator.entry_point_graph, self.translator): if graph in jit_graphs: continue assert not getattr(graph, 'access_directly', False) def prejit_optimizations(self, policy, graphs): from rpython.translator.backendopt.all import backend_optimizations backend_optimizations(self.translator, graphs=graphs, merge_if_blocks=True, constfold=True, remove_asserts=True, really_remove_asserts=True, replace_we_are_jitted=False) def prejit_optimizations_minimal_inline(self, policy, graphs): from rpython.translator.backendopt.inline import auto_inline_graphs auto_inline_graphs(self.translator, graphs, 0.01) def build_cpu(self, CPUClass, translate_support_code=False, no_stats=False, supports_floats=True, supports_longlong=True, supports_singlefloats=True, **kwds): assert CPUClass is not None self.opt = history.Options(**kwds) if no_stats: stats = history.NoStats() else: stats = history.Stats(None) self.stats = stats if translate_support_code: self.annhelper = MixLevelHelperAnnotator(self.translator.rtyper) cpu = CPUClass(self.translator.rtyper, self.stats, self.opt, translate_support_code, gcdescr=self.gcdescr) if not supports_floats: cpu.supports_floats = False if not supports_longlong: cpu.supports_longlong = False if not supports_singlefloats: cpu.supports_singlefloats = False self.cpu = cpu def build_meta_interp(self, ProfilerClass, opencoder_model): from rpython.jit.metainterp.opencoder import Model, BigModel self.metainterp_sd = MetaInterpStaticData(self.cpu, self.opt, ProfilerClass=ProfilerClass, warmrunnerdesc=self) if opencoder_model == 'big': self.metainterp_sd.opencoder_model = BigModel else: self.metainterp_sd.opencoder_model = Model self.stats.metainterp_sd = self.metainterp_sd def make_virtualizable_infos(self): vinfos = {} for jd in self.jitdrivers_sd: # jd.greenfield_info = None for name in jd.jitdriver.greens: if '.' in name: from rpython.jit.metainterp.greenfield import GreenFieldInfo jd.greenfield_info = GreenFieldInfo(self.cpu, jd) break # if not jd.jitdriver.virtualizables: jd.virtualizable_info = None jd.index_of_virtualizable = -1 continue else: assert jd.greenfield_info is None, "XXX not supported yet" # jitdriver = jd.jitdriver assert len(jitdriver.virtualizables) == 1 # for now [vname] = jitdriver.virtualizables # XXX skip the Voids here too jd.index_of_virtualizable = jitdriver.reds.index(vname) # index = jd.num_green_args + jd.index_of_virtualizable VTYPEPTR = jd._JIT_ENTER_FUNCTYPE.ARGS[index] if VTYPEPTR not in vinfos: from rpython.jit.metainterp.virtualizable import VirtualizableInfo vinfos[VTYPEPTR] = VirtualizableInfo(self, VTYPEPTR) jd.virtualizable_info = vinfos[VTYPEPTR] def make_enter_functions(self): for jd in self.jitdrivers_sd: self.make_enter_function(jd) def make_enter_function(self, jd): from rpython.jit.metainterp.warmstate import WarmEnterState state = WarmEnterState(self, jd) maybe_compile_and_run, EnterJitAssembler = state.make_entry_point() jd.warmstate = state def crash_in_jit(e): tb = not we_are_translated() and sys.exc_info()[2] try: raise e except jitexc.JitException: raise # go through except MemoryError: raise # go through except StackOverflow: raise # go through except Exception as e: if not we_are_translated(): print "~~~ Crash in JIT!" print '~~~ %s: %s' % (e.__class__, e) if sys.stdout == sys.__stdout__: import pdb pdb.post_mortem(tb) raise e.__class__, e, tb fatalerror('~~~ Crash in JIT! %s' % (e, )) crash_in_jit._dont_inline_ = True def maybe_enter_jit(*args): try: maybe_compile_and_run(state.increment_threshold, *args) except Exception as e: crash_in_jit(e) maybe_enter_jit._always_inline_ = True jd._maybe_enter_jit_fn = maybe_enter_jit jd._maybe_compile_and_run_fn = maybe_compile_and_run jd._EnterJitAssembler = EnterJitAssembler def make_driverhook_graphs(self): # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._get_printable_location_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.get_printable_location, annmodel.SomeString()) jd._get_unique_id_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.get_unique_id, annmodel.SomeInteger()) jd._confirm_enter_jit_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) # items = [] types = () pos = () if jd.jitdriver.get_location: assert hasattr(jd.jitdriver.get_location, '_loc_types'), """ You must decorate your get_location function: from rpython.rlib.rjitlog import rjitlog as jl @jl.returns(jl.MP_FILENAME, jl.MP_XXX, ...) def get_loc(your, green, keys): name = "x.txt" # extract it from your green keys return (name, ...) """ types = jd.jitdriver.get_location._loc_types del jd.jitdriver.get_location._loc_types # for _, type in types: if type == 's': items.append(annmodel.SomeString()) elif type == 'i': items.append(annmodel.SomeInteger()) else: raise NotImplementedError s_Tuple = annmodel.SomeTuple(items) jd._get_location_ptr = self._make_hook_graph( jd, annhelper, jd.jitdriver.get_location, s_Tuple) jd._get_loc_types = types annhelper.finish() def _make_hook_graph(self, jitdriver_sd, annhelper, func, s_result, s_first_arg=None, onlygreens=True): if func is None: return None # if not onlygreens: assert not jitdriver_sd.jitdriver.autoreds, ( "reds='auto' is not compatible with JitDriver hooks such as " "confirm_enter_jit") extra_args_s = [] if s_first_arg is not None: extra_args_s.append(s_first_arg) # args_s = jitdriver_sd._portal_args_s if onlygreens: args_s = args_s[:len(jitdriver_sd._green_args_spec)] graph = annhelper.getgraph(func, extra_args_s + args_s, s_result) funcptr = annhelper.graph2delayed(graph) return funcptr def make_args_specifications(self): for jd in self.jitdrivers_sd: self.make_args_specification(jd) def make_args_specification(self, jd): graph = jd._jit_merge_point_in _, _, op = locate_jit_merge_point(graph) greens_v, reds_v = support.decode_hp_hint_args(op) ALLARGS = [v.concretetype for v in (greens_v + reds_v)] jd._green_args_spec = [v.concretetype for v in greens_v] jd.red_args_types = [history.getkind(v.concretetype) for v in reds_v] jd.num_green_args = len(jd._green_args_spec) jd.num_red_args = len(jd.red_args_types) RESTYPE = graph.getreturnvar().concretetype (jd._JIT_ENTER_FUNCTYPE, jd._PTR_JIT_ENTER_FUNCTYPE) = self.cpu.ts.get_FuncType( ALLARGS, lltype.Void) (jd._PORTAL_FUNCTYPE, jd._PTR_PORTAL_FUNCTYPE) = self.cpu.ts.get_FuncType(ALLARGS, RESTYPE) # if jd.result_type == 'v': ASMRESTYPE = lltype.Void elif jd.result_type == history.INT: ASMRESTYPE = lltype.Signed elif jd.result_type == history.REF: ASMRESTYPE = llmemory.GCREF elif jd.result_type == history.FLOAT: ASMRESTYPE = lltype.Float else: assert False (_, jd._PTR_ASSEMBLER_HELPER_FUNCTYPE) = self.cpu.ts.get_FuncType( [llmemory.GCREF, llmemory.GCREF], ASMRESTYPE) def rewrite_jitcell_accesses(self): jitdrivers_by_name = {} for jd in self.jitdrivers_sd: name = jd.jitdriver.name if name != 'jitdriver': jitdrivers_by_name[name] = jd m = _find_jit_markers(self.translator.graphs, ('get_jitcell_at_key', 'trace_next_iteration', 'dont_trace_here', 'trace_next_iteration_hash')) accessors = {} def get_accessor(name, jitdriver_name, function, ARGS, green_arg_spec): a = accessors.get((name, jitdriver_name)) if a: return a d = { 'function': function, 'cast_instance_to_gcref': cast_instance_to_gcref, 'lltype': lltype } arg_spec = ", ".join([("arg%d" % i) for i in range(len(ARGS))]) arg_converters = [] for i, spec in enumerate(green_arg_spec): if isinstance(spec, lltype.Ptr): arg_converters.append( "arg%d = lltype.cast_opaque_ptr(type%d, arg%d)" % (i, i, i)) d['type%d' % i] = spec convert = ";".join(arg_converters) if name == 'get_jitcell_at_key': exec py.code.Source(""" def accessor(%s): %s return cast_instance_to_gcref(function(%s)) """ % (arg_spec, convert, arg_spec)).compile() in d FUNC = lltype.Ptr(lltype.FuncType(ARGS, llmemory.GCREF)) elif name == "trace_next_iteration_hash": exec py.code.Source(""" def accessor(arg0): function(arg0) """).compile() in d FUNC = lltype.Ptr( lltype.FuncType([lltype.Unsigned], lltype.Void)) else: exec py.code.Source(""" def accessor(%s): %s function(%s) """ % (arg_spec, convert, arg_spec)).compile() in d FUNC = lltype.Ptr(lltype.FuncType(ARGS, lltype.Void)) func = d['accessor'] ll_ptr = self.helper_func(FUNC, func) accessors[(name, jitdriver_name)] = ll_ptr return ll_ptr for graph, block, index in m: op = block.operations[index] jitdriver_name = op.args[1].value JitCell = jitdrivers_by_name[jitdriver_name].warmstate.JitCell ARGS = [x.concretetype for x in op.args[2:]] if op.args[0].value == 'get_jitcell_at_key': func = JitCell.get_jitcell elif op.args[0].value == 'dont_trace_here': func = JitCell.dont_trace_here elif op.args[0].value == 'trace_next_iteration_hash': func = JitCell.trace_next_iteration_hash else: func = JitCell._trace_next_iteration argspec = jitdrivers_by_name[jitdriver_name]._green_args_spec accessor = get_accessor(op.args[0].value, jitdriver_name, func, ARGS, argspec) v_result = op.result c_accessor = Constant(accessor, concretetype=lltype.Void) newop = SpaceOperation('direct_call', [c_accessor] + op.args[2:], v_result) block.operations[index] = newop def rewrite_can_enter_jits(self): sublists = {} for jd in self.jitdrivers_sd: sublists[jd.jitdriver] = jd, [] jd.no_loop_header = True # loop_headers = find_loop_headers(self.translator.graphs) for graph, block, index in loop_headers: op = block.operations[index] jitdriver = op.args[1].value assert jitdriver in sublists, \ "loop_header with no matching jit_merge_point" jd, sublist = sublists[jitdriver] jd.no_loop_header = False # can_enter_jits = find_can_enter_jit(self.translator.graphs) for graph, block, index in can_enter_jits: op = block.operations[index] jitdriver = op.args[1].value assert jitdriver in sublists, \ "can_enter_jit with no matching jit_merge_point" assert not jitdriver.autoreds, ( "can_enter_jit not supported with a jitdriver that " "has reds='auto'") jd, sublist = sublists[jitdriver] origportalgraph = jd._jit_merge_point_in if graph is not origportalgraph: sublist.append((graph, block, index)) jd.no_loop_header = False else: pass # a 'can_enter_jit' before the 'jit-merge_point', but # originally in the same function: we ignore it here # see e.g. test_jitdriver.test_simple for jd in self.jitdrivers_sd: _, sublist = sublists[jd.jitdriver] self.rewrite_can_enter_jit(jd, sublist) def rewrite_can_enter_jit(self, jd, can_enter_jits): FUNCPTR = jd._PTR_JIT_ENTER_FUNCTYPE jit_enter_fnptr = self.helper_func(FUNCPTR, jd._maybe_enter_jit_fn) if len(can_enter_jits) == 0: # see test_warmspot.test_no_loop_at_all operations = jd.portal_graph.startblock.operations op1 = operations[0] assert (op1.opname == 'jit_marker' and op1.args[0].value == 'jit_merge_point') op0 = SpaceOperation('jit_marker', [Constant('can_enter_jit', lltype.Void)] + op1.args[1:], None) operations.insert(0, op0) can_enter_jits = [(jd.portal_graph, jd.portal_graph.startblock, 0)] for graph, block, index in can_enter_jits: if graph is jd._jit_merge_point_in: continue op = block.operations[index] greens_v, reds_v = support.decode_hp_hint_args(op) args_v = greens_v + reds_v vlist = [Constant(jit_enter_fnptr, FUNCPTR)] + args_v v_result = Variable() v_result.concretetype = lltype.Void newop = SpaceOperation('direct_call', vlist, v_result) block.operations[index] = newop def helper_func(self, FUNCPTR, func): if not self.cpu.translate_support_code: return llhelper(FUNCPTR, func) FUNC = FUNCPTR.TO args_s = [lltype_to_annotation(ARG) for ARG in FUNC.ARGS] s_result = lltype_to_annotation(FUNC.RESULT) graph = self.annhelper.getgraph(func, args_s, s_result) return self.annhelper.graph2delayed(graph, FUNC) def rewrite_access_helpers(self): ah = find_access_helpers(self.translator.graphs) for graph, block, index in ah: op = block.operations[index] self.rewrite_access_helper(op) def create_jit_entry_points(self): for func, args, result in all_jit_entrypoints: self.helper_func(lltype.Ptr(lltype.FuncType(args, result)), func) annotated_jit_entrypoints.append((func, None)) def rewrite_access_helper(self, op): # make sure we make a copy of function so it no longer belongs # to extregistry func = op.args[1].value if func.func_name.startswith('stats_'): # get special treatment since we rewrite it to a call that accepts # jit driver assert len(op.args) >= 3, ("%r must have a first argument " "(which is None)" % (func, )) func = func_with_new_name(func, func.func_name + '_compiled') def new_func(ignored, *args): return func(self, *args) ARGS = [lltype.Void] + [arg.concretetype for arg in op.args[3:]] else: ARGS = [arg.concretetype for arg in op.args[2:]] new_func = func_with_new_name(func, func.func_name + '_compiled') RESULT = op.result.concretetype FUNCPTR = lltype.Ptr(lltype.FuncType(ARGS, RESULT)) ptr = self.helper_func(FUNCPTR, new_func) op.opname = 'direct_call' op.args = [Constant(ptr, FUNCPTR)] + op.args[2:] def rewrite_jit_merge_points(self, policy): for jd in self.jitdrivers_sd: self.rewrite_jit_merge_point(jd, policy) def rewrite_jit_merge_point(self, jd, policy): # # Mutate the original portal graph from this: # # def original_portal(..): # stuff # while 1: # jit_merge_point(*args) # more stuff # # to that: # # def original_portal(..): # stuff # return portal_runner(*args) # # def portal_runner(*args): # while 1: # try: # return portal(*args) # except JitException, e: # return handle_jitexception(e) # # def portal(*args): # while 1: # more stuff # origportalgraph = jd._jit_merge_point_in portalgraph = jd.portal_graph PORTALFUNC = jd._PORTAL_FUNCTYPE # ____________________________________________________________ # Prepare the portal_runner() helper # from rpython.jit.metainterp.warmstate import specialize_value from rpython.jit.metainterp.warmstate import unspecialize_value portal_ptr = self.cpu.ts.functionptr(PORTALFUNC, 'portal', graph=portalgraph) jd._portal_ptr = portal_ptr # portalfunc_ARGS = [] nums = {} for i, ARG in enumerate(PORTALFUNC.ARGS): kind = history.getkind(ARG) assert kind != 'void' if i < len(jd.jitdriver.greens): color = 'green' else: color = 'red' attrname = '%s_%s' % (color, kind) count = nums.get(attrname, 0) nums[attrname] = count + 1 portalfunc_ARGS.append((ARG, attrname, count)) portalfunc_ARGS = unrolling_iterable(portalfunc_ARGS) # rtyper = self.translator.rtyper RESULT = PORTALFUNC.RESULT result_kind = history.getkind(RESULT) assert result_kind.startswith(jd.result_type) ts = self.cpu.ts state = jd.warmstate maybe_compile_and_run = jd._maybe_compile_and_run_fn EnterJitAssembler = jd._EnterJitAssembler def ll_portal_runner(*args): try: # maybe enter from the function's start. maybe_compile_and_run(state.increment_function_threshold, *args) # # then run the normal portal function, i.e. the # interpreter's main loop. It might enter the jit # via maybe_enter_jit(), which typically ends with # handle_fail() being called, which raises on the # following exceptions --- catched here, because we # want to interrupt the whole interpreter loop. return support.maybe_on_top_of_llinterp(rtyper, portal_ptr)(*args) except jitexc.JitException as e: result = handle_jitexception(e) if result_kind != 'void': result = specialize_value(RESULT, result) return result def handle_jitexception(e): # XXX there are too many exceptions all around... while True: if isinstance(e, EnterJitAssembler): try: return e.execute() except jitexc.JitException as e: continue # if isinstance(e, jitexc.ContinueRunningNormally): args = () for ARGTYPE, attrname, count in portalfunc_ARGS: x = getattr(e, attrname)[count] x = specialize_value(ARGTYPE, x) args = args + (x, ) try: result = support.maybe_on_top_of_llinterp( rtyper, portal_ptr)(*args) except jitexc.JitException as e: continue if result_kind != 'void': result = unspecialize_value(result) return result # if result_kind == 'void': if isinstance(e, jitexc.DoneWithThisFrameVoid): return None if result_kind == 'int': if isinstance(e, jitexc.DoneWithThisFrameInt): return e.result if result_kind == 'ref': if isinstance(e, jitexc.DoneWithThisFrameRef): return e.result if result_kind == 'float': if isinstance(e, jitexc.DoneWithThisFrameFloat): return e.result # if isinstance(e, jitexc.ExitFrameWithExceptionRef): value = ts.cast_to_baseclass(e.value) if not we_are_translated(): raise LLException(ts.get_typeptr(value), value) else: value = cast_base_ptr_to_instance(Exception, value) assert value is not None raise value # raise AssertionError("all cases should have been handled") jd._ll_portal_runner = ll_portal_runner # for debugging jd.portal_runner_ptr = self.helper_func(jd._PTR_PORTAL_FUNCTYPE, ll_portal_runner) jd.portal_runner_adr = llmemory.cast_ptr_to_adr(jd.portal_runner_ptr) jd.portal_calldescr = self.cpu.calldescrof( jd._PTR_PORTAL_FUNCTYPE.TO, jd._PTR_PORTAL_FUNCTYPE.TO.ARGS, jd._PTR_PORTAL_FUNCTYPE.TO.RESULT, EffectInfo.MOST_GENERAL) vinfo = jd.virtualizable_info def assembler_call_helper(deadframe, virtualizableref): fail_descr = self.cpu.get_latest_descr(deadframe) try: fail_descr.handle_fail(deadframe, self.metainterp_sd, jd) except jitexc.JitException as e: return handle_jitexception(e) else: assert 0, "should have raised" jd._assembler_call_helper = assembler_call_helper # for debugging jd._assembler_helper_ptr = self.helper_func( jd._PTR_ASSEMBLER_HELPER_FUNCTYPE, assembler_call_helper) jd.assembler_helper_adr = llmemory.cast_ptr_to_adr( jd._assembler_helper_ptr) if vinfo is not None: jd.vable_token_descr = vinfo.vable_token_descr def handle_jitexception_from_blackhole(bhcaller, e): result = handle_jitexception(e) if result_kind == 'void': pass elif result_kind == 'int': bhcaller._setup_return_value_i(result) elif result_kind == 'ref': bhcaller._setup_return_value_r(result) elif result_kind == 'float': bhcaller._setup_return_value_f(result) else: assert False jd.handle_jitexc_from_bh = handle_jitexception_from_blackhole # ____________________________________________________________ # Now mutate origportalgraph to end with a call to portal_runner_ptr # origblock, origindex, op = locate_jit_merge_point(origportalgraph) assert op.opname == 'jit_marker' assert op.args[0].value == 'jit_merge_point' greens_v, reds_v = support.decode_hp_hint_args(op) vlist = [Constant(jd.portal_runner_ptr, jd._PTR_PORTAL_FUNCTYPE)] vlist += greens_v vlist += reds_v v_result = Variable() v_result.concretetype = PORTALFUNC.RESULT newop = SpaceOperation('direct_call', vlist, v_result) del origblock.operations[origindex:] origblock.operations.append(newop) origblock.exitswitch = None origblock.recloseblock(Link([v_result], origportalgraph.returnblock)) # the origportal now can raise (even if it did not raise before), # which means that we cannot inline it anywhere any more, but that's # fine since any forced inlining has been done before # checkgraph(origportalgraph) def add_finish(self): def finish(): if self.metainterp_sd.profiler.initialized: self.metainterp_sd.profiler.finish() self.metainterp_sd.cpu.finish_once() if self.cpu.translate_support_code: call_final_function(self.translator, finish, annhelper=self.annhelper) def rewrite_set_param_and_get_stats(self): from rpython.rtyper.lltypesystem.rstr import STR closures = {} graphs = self.translator.graphs _, PTR_SET_PARAM_FUNCTYPE = self.cpu.ts.get_FuncType([lltype.Signed], lltype.Void) _, PTR_SET_PARAM_STR_FUNCTYPE = self.cpu.ts.get_FuncType( [lltype.Ptr(STR)], lltype.Void) def make_closure(jd, fullfuncname, is_string): if jd is None: def closure(i): if is_string: i = hlstr(i) for jd in self.jitdrivers_sd: getattr(jd.warmstate, fullfuncname)(i) else: state = jd.warmstate def closure(i): if is_string: i = hlstr(i) getattr(state, fullfuncname)(i) if is_string: TP = PTR_SET_PARAM_STR_FUNCTYPE else: TP = PTR_SET_PARAM_FUNCTYPE funcptr = self.helper_func(TP, closure) return Constant(funcptr, TP) # for graph, block, i in find_set_param(graphs): op = block.operations[i] if op.args[1].value is not None: for jd in self.jitdrivers_sd: if jd.jitdriver is op.args[1].value: break else: assert 0, "jitdriver of set_param() not found" else: jd = None funcname = op.args[2].value key = jd, funcname if key not in closures: closures[key] = make_closure(jd, 'set_param_' + funcname, funcname == 'enable_opts') op.opname = 'direct_call' op.args[:3] = [closures[key]] def rewrite_force_virtual(self, vrefinfo): all_graphs = self.translator.graphs vrefinfo.replace_force_virtual_with_call(all_graphs) def replace_force_quasiimmut_with_direct_call(self, op): ARG = op.args[0].concretetype mutatefieldname = op.args[1].value key = (ARG, mutatefieldname) if key in self._cache_force_quasiimmed_funcs: cptr = self._cache_force_quasiimmed_funcs[key] else: from rpython.jit.metainterp import quasiimmut func = quasiimmut.make_invalidation_function(ARG, mutatefieldname) FUNC = lltype.Ptr(lltype.FuncType([ARG], lltype.Void)) llptr = self.helper_func(FUNC, func) cptr = Constant(llptr, FUNC) self._cache_force_quasiimmed_funcs[key] = cptr op.opname = 'direct_call' op.args = [cptr, op.args[0]] def rewrite_force_quasi_immutable(self): self._cache_force_quasiimmed_funcs = {} graphs = self.translator.graphs for graph, block, i in find_force_quasi_immutable(graphs): self.replace_force_quasiimmut_with_direct_call(block.operations[i])
class ExceptionTransformer(object): def __init__(self, translator): self.translator = translator self.raise_analyzer = canraise.RaiseAnalyzer(translator) edata = translator.rtyper.exceptiondata self.lltype_of_exception_value = edata.lltype_of_exception_value self.lltype_of_exception_type = edata.lltype_of_exception_type self.mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) exc_data, null_type, null_value = self.setup_excdata() (assertion_error_ll_exc_type, assertion_error_ll_exc) = self.get_builtin_exception(AssertionError) (n_i_error_ll_exc_type, n_i_error_ll_exc) = self.get_builtin_exception(NotImplementedError) self.c_assertion_error_ll_exc_type = constant_value( assertion_error_ll_exc_type) self.c_n_i_error_ll_exc_type = constant_value(n_i_error_ll_exc_type) def rpyexc_occured(): exc_type = exc_data.exc_type return bool(exc_type) def rpyexc_fetch_type(): return exc_data.exc_type def rpyexc_fetch_value(): return exc_data.exc_value def rpyexc_clear(): exc_data.exc_type = null_type exc_data.exc_value = null_value def rpyexc_raise(etype, evalue): # When compiling in debug mode, the following ll_asserts will # crash the program as soon as it raises AssertionError or # NotImplementedError. Useful when you are in a debugger. # When compiling in release mode, AssertionErrors and # NotImplementedErrors are raised normally, and only later # caught by debug_catch_exception and printed, which allows # us to see at least part of the traceback for them. ll_assert(etype != assertion_error_ll_exc_type, "AssertionError") ll_assert(etype != n_i_error_ll_exc_type, "NotImplementedError") exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_start_traceback(lltype.Void, etype) def rpyexc_reraise(etype, evalue): exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_reraise_traceback(lltype.Void, etype) def rpyexc_fetch_exception(): evalue = rpyexc_fetch_value() rpyexc_clear() return evalue def rpyexc_restore_exception(evalue): if evalue: exc_data.exc_type = ll_inst_type(evalue) exc_data.exc_value = evalue self.rpyexc_occured_ptr = self.build_func("RPyExceptionOccurred", rpyexc_occured, [], lltype.Bool) self.rpyexc_fetch_type_ptr = self.build_func( "RPyFetchExceptionType", rpyexc_fetch_type, [], self.lltype_of_exception_type) self.rpyexc_fetch_value_ptr = self.build_func( "RPyFetchExceptionValue", rpyexc_fetch_value, [], self.lltype_of_exception_value) self.rpyexc_clear_ptr = self.build_func("RPyClearException", rpyexc_clear, [], lltype.Void) self.rpyexc_raise_ptr = self.build_func( "RPyRaiseException", self.noinline(rpyexc_raise), [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_reraise_ptr = self.build_func( "RPyReRaiseException", rpyexc_reraise, [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_fetch_exception_ptr = self.build_func( "RPyFetchException", rpyexc_fetch_exception, [], self.lltype_of_exception_value) self.rpyexc_restore_exception_ptr = self.build_func( "RPyRestoreException", self.noinline(rpyexc_restore_exception), [self.lltype_of_exception_value], lltype.Void) self.build_extra_funcs() self.mixlevelannotator.finish() self.lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping( ) def noinline(self, fn): fn = func_with_new_name(fn, fn.__name__) fn._dont_inline_ = True return fn def build_func(self, name, fn, inputtypes, rettype, **kwds): l2a = lltype_to_annotation graph = self.mixlevelannotator.getgraph(fn, map(l2a, inputtypes), l2a(rettype)) return self.constant_func(name, inputtypes, rettype, graph, exception_policy="exc_helper", **kwds) def get_builtin_exception(self, Class): edata = self.translator.rtyper.exceptiondata bk = self.translator.annotator.bookkeeper error_def = bk.getuniqueclassdef(Class) error_ll_exc = edata.get_standard_ll_exc_instance( self.translator.rtyper, error_def) error_ll_exc_type = ll_inst_type(error_ll_exc) return error_ll_exc_type, error_ll_exc def transform_completely(self): for graph in self.translator.graphs: self.create_exception_handling(graph) def create_exception_handling(self, graph): """After an exception in a direct_call (or indirect_call), that is not caught by an explicit except statement, we need to reraise the exception. So after this direct_call we need to test if an exception had occurred. If so, we return from the current graph with a special value (False/-1/-1.0/null). Because of the added exitswitch we need an additional block. """ if hasattr(graph, 'exceptiontransformed'): assert self.same_obj(self.exc_data_ptr, graph.exceptiontransformed) return else: self.raise_analyzer.analyze_direct_call(graph) graph.exceptiontransformed = self.exc_data_ptr join_blocks(graph) # collect the blocks before changing them n_need_exc_matching_blocks = 0 n_gen_exc_checks = 0 # entrymap = mkentrymap(graph) if graph.exceptblock in entrymap: for link in entrymap[graph.exceptblock]: self.transform_jump_to_except_block(graph, entrymap, link) # for block in list(graph.iterblocks()): self.replace_fetch_restore_operations(block) need_exc_matching, gen_exc_checks = self.transform_block( graph, block) n_need_exc_matching_blocks += need_exc_matching n_gen_exc_checks += gen_exc_checks cleanup_graph(graph) return n_need_exc_matching_blocks, n_gen_exc_checks def replace_fetch_restore_operations(self, block): # the gctransformer will create these operations. It looks as if the # order of transformations is important - but the gctransformer will # put them in a new graph, so all transformations will run again. for i in range(len(block.operations)): opname = block.operations[i].opname if opname == 'gc_fetch_exception': block.operations[i].opname = "direct_call" block.operations[i].args = [self.rpyexc_fetch_exception_ptr] elif opname == 'gc_restore_exception': block.operations[i].opname = "direct_call" block.operations[i].args.insert( 0, self.rpyexc_restore_exception_ptr) elif opname == 'get_exception_addr': # only for lltype block.operations[i].opname = "direct_call" block.operations[i].args.insert( 0, self.rpyexc_get_exception_addr_ptr) elif opname == 'get_exc_value_addr': # only for lltype block.operations[i].opname = "direct_call" block.operations[i].args.insert( 0, self.rpyexc_get_exc_value_addr_ptr) def transform_block(self, graph, block): need_exc_matching = False n_gen_exc_checks = 0 if block is graph.exceptblock: return need_exc_matching, n_gen_exc_checks elif block is graph.returnblock: return need_exc_matching, n_gen_exc_checks last_operation = len(block.operations) - 1 if block.exitswitch == c_last_exception: need_exc_matching = True last_operation -= 1 elif (len(block.exits) == 1 and block.exits[0].target is graph.returnblock and len(block.operations) and (block.exits[0].args[0].concretetype is lltype.Void or block.exits[0].args[0] is block.operations[-1].result) and block.operations[-1].opname not in ('malloc', 'malloc_varsize')): # special cases last_operation -= 1 lastblock = block for i in range(last_operation, -1, -1): op = block.operations[i] if not self.raise_analyzer.can_raise(op): continue splitlink = split_block(None, block, i + 1) afterblock = splitlink.target if lastblock is block: lastblock = afterblock self.gen_exc_check(block, graph.returnblock, afterblock) n_gen_exc_checks += 1 if need_exc_matching: assert lastblock.exitswitch == c_last_exception if not self.raise_analyzer.can_raise(lastblock.operations[-1]): lastblock.exitswitch = None lastblock.recloseblock(lastblock.exits[0]) lastblock.exits[0].exitcase = None else: self.insert_matching(lastblock, graph) return need_exc_matching, n_gen_exc_checks def comes_from_last_exception(self, entrymap, link): seen = set() pending = [(link, link.args[1])] while pending: link, v = pending.pop() if (link, v) in seen: continue seen.add((link, v)) if link.last_exc_value is not None and v is link.last_exc_value: return True block = link.prevblock if block is None: continue for op in block.operations[::-1]: if v is op.result: if op.opname == 'cast_pointer': v = op.args[0] else: break for link in entrymap.get(block, ()): for v1, v2 in zip(link.args, block.inputargs): if v2 is v: pending.append((link, v1)) return False def transform_jump_to_except_block(self, graph, entrymap, link): reraise = self.comes_from_last_exception(entrymap, link) result = Variable() result.concretetype = lltype.Void block = Block([v.copy() for v in graph.exceptblock.inputargs]) if reraise: block.operations = [ SpaceOperation("direct_call", [self.rpyexc_reraise_ptr] + block.inputargs, result), ] else: block.operations = [ SpaceOperation("direct_call", [self.rpyexc_raise_ptr] + block.inputargs, result), SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void)), ] link.target = block RETTYPE = graph.returnblock.inputargs[0].concretetype l = Link([error_constant(RETTYPE)], graph.returnblock) block.recloseblock(l) def insert_matching(self, block, graph): proxygraph, op = self.create_proxy_graph(block.operations[-1]) block.operations[-1] = op #non-exception case block.exits[0].exitcase = block.exits[0].llexitcase = None # use the dangerous second True flag :-) inliner = inline.OneShotInliner( self.translator, graph, self.lltype_to_classdef, inline_guarded_calls=True, inline_guarded_calls_no_matter_what=True, raise_analyzer=self.raise_analyzer) inliner.inline_once(block, len(block.operations) - 1) #block.exits[0].exitcase = block.exits[0].llexitcase = False def create_proxy_graph(self, op): """ creates a graph which calls the original function, checks for raised exceptions, fetches and then raises them again. If this graph is inlined, the correct exception matching blocks are produced.""" # XXX slightly annoying: construct a graph by hand # but better than the alternative result = op.result.copy() opargs = [] inputargs = [] callargs = [] ARGTYPES = [] for var in op.args: if isinstance(var, Variable): v = Variable() v.concretetype = var.concretetype inputargs.append(v) opargs.append(v) callargs.append(var) ARGTYPES.append(var.concretetype) else: opargs.append(var) newop = SpaceOperation(op.opname, opargs, result) startblock = Block(inputargs) startblock.operations.append(newop) newgraph = FunctionGraph("dummy_exc1", startblock) startblock.closeblock(Link([result], newgraph.returnblock)) newgraph.returnblock.inputargs[0].concretetype = op.result.concretetype self.gen_exc_check(startblock, newgraph.returnblock) excblock = Block([]) llops = rtyper.LowLevelOpList(None) var_value = self.gen_getfield('exc_value', llops) var_type = self.gen_getfield('exc_type', llops) # c_check1 = self.c_assertion_error_ll_exc_type c_check2 = self.c_n_i_error_ll_exc_type llops.genop('debug_catch_exception', [var_type, c_check1, c_check2]) # self.gen_setfield('exc_value', self.c_null_evalue, llops) self.gen_setfield('exc_type', self.c_null_etype, llops) excblock.operations[:] = llops newgraph.exceptblock.inputargs[ 0].concretetype = self.lltype_of_exception_type newgraph.exceptblock.inputargs[ 1].concretetype = self.lltype_of_exception_value excblock.closeblock(Link([var_type, var_value], newgraph.exceptblock)) startblock.exits[True].target = excblock startblock.exits[True].args = [] fptr = self.constant_func("dummy_exc1", ARGTYPES, op.result.concretetype, newgraph) return newgraph, SpaceOperation("direct_call", [fptr] + callargs, op.result) def gen_exc_check(self, block, returnblock, normalafterblock=None): llops = rtyper.LowLevelOpList(None) spaceop = block.operations[-1] alloc_shortcut = self.check_for_alloc_shortcut(spaceop) if alloc_shortcut: var_no_exc = self.gen_nonnull(spaceop.result, llops) else: v_exc_type = self.gen_getfield('exc_type', llops) var_no_exc = self.gen_isnull(v_exc_type, llops) # # We could add a "var_no_exc is likely true" hint, but it seems # not to help, so it was commented out again. #var_no_exc = llops.genop('likely', [var_no_exc], lltype.Bool) block.operations.extend(llops) block.exitswitch = var_no_exc #exception occurred case b = Block([]) b.operations = [ SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void)) ] l = Link([error_constant(returnblock.inputargs[0].concretetype)], returnblock) b.closeblock(l) l = Link([], b) l.exitcase = l.llexitcase = False #non-exception case l0 = block.exits[0] l0.exitcase = l0.llexitcase = True block.recloseblock(l0, l) insert_zeroing_op = False if spaceop.opname in ['malloc', 'malloc_varsize']: flavor = spaceop.args[1].value['flavor'] if flavor == 'gc': insert_zeroing_op = True true_zero = spaceop.args[1].value.get('zero', False) # NB. when inserting more special-cases here, keep in mind that # you also need to list the opnames in transform_block() # (see "special cases") if insert_zeroing_op: if normalafterblock is None: normalafterblock = insert_empty_block(None, l0) v_result = spaceop.result if v_result in l0.args: result_i = l0.args.index(v_result) v_result_after = normalafterblock.inputargs[result_i] else: v_result_after = v_result.copy() l0.args.append(v_result) normalafterblock.inputargs.append(v_result_after) if true_zero: opname = "zero_everything_inside" else: opname = "zero_gc_pointers_inside" normalafterblock.operations.insert( 0, SpaceOperation(opname, [v_result_after], varoftype(lltype.Void))) def setup_excdata(self): EXCDATA = lltype.Struct('ExcData', ('exc_type', self.lltype_of_exception_type), ('exc_value', self.lltype_of_exception_value)) self.EXCDATA = EXCDATA exc_data = lltype.malloc(EXCDATA, immortal=True) null_type = lltype.nullptr(self.lltype_of_exception_type.TO) null_value = lltype.nullptr(self.lltype_of_exception_value.TO) self.exc_data_ptr = exc_data self.cexcdata = Constant(exc_data, lltype.Ptr(self.EXCDATA)) self.c_null_etype = Constant(null_type, self.lltype_of_exception_type) self.c_null_evalue = Constant(null_value, self.lltype_of_exception_value) return exc_data, null_type, null_value def constant_func(self, name, inputtypes, rettype, graph, **kwds): FUNC_TYPE = lltype.FuncType(inputtypes, rettype) fn_ptr = lltype.functionptr(FUNC_TYPE, name, graph=graph, **kwds) return Constant(fn_ptr, lltype.Ptr(FUNC_TYPE)) def gen_getfield(self, name, llops): c_name = inputconst(lltype.Void, name) return llops.genop('getfield', [self.cexcdata, c_name], resulttype=getattr(self.EXCDATA, name)) def gen_setfield(self, name, v_value, llops): c_name = inputconst(lltype.Void, name) llops.genop('setfield', [self.cexcdata, c_name, v_value]) def gen_isnull(self, v, llops): return llops.genop('ptr_iszero', [v], lltype.Bool) def gen_nonnull(self, v, llops): return llops.genop('ptr_nonzero', [v], lltype.Bool) def same_obj(self, ptr1, ptr2): return ptr1._same_obj(ptr2) def check_for_alloc_shortcut(self, spaceop): if spaceop.opname in ('malloc', 'malloc_varsize'): return True elif spaceop.opname == 'direct_call': fnobj = spaceop.args[0].value._obj if hasattr(fnobj, '_callable'): oopspec = getattr(fnobj._callable, 'oopspec', None) if oopspec and oopspec == 'newlist(length)': return True return False def build_extra_funcs(self): EXCDATA = self.EXCDATA exc_data = self.exc_data_ptr def rpyexc_get_exception_addr(): return (llmemory.cast_ptr_to_adr(exc_data) + llmemory.offsetof(EXCDATA, 'exc_type')) def rpyexc_get_exc_value_addr(): return (llmemory.cast_ptr_to_adr(exc_data) + llmemory.offsetof(EXCDATA, 'exc_value')) self.rpyexc_get_exception_addr_ptr = self.build_func( "RPyGetExceptionAddr", rpyexc_get_exception_addr, [], llmemory.Address) self.rpyexc_get_exc_value_addr_ptr = self.build_func( "RPyGetExcValueAddr", rpyexc_get_exc_value_addr, [], llmemory.Address)
def _annotate(rtyper, f, args_s, s_result): ann = MixLevelHelperAnnotator(rtyper) graph = ann.getgraph(f, args_s, s_result) ann.finish() return graph
def _setup_frame_realloc(self, translate_support_code): FUNC_TP = lltype.Ptr(lltype.FuncType([llmemory.GCREF, lltype.Signed], llmemory.GCREF)) base_ofs = self.get_baseofs_of_frame_field() def realloc_frame(frame, size): try: if not we_are_translated(): assert not self._exception_emulator[0] frame = lltype.cast_opaque_ptr(jitframe.JITFRAMEPTR, frame) if size > frame.jf_frame_info.jfi_frame_depth: # update the frame_info size, which is for whatever reason # not up to date # frame info lives on assembler stack, so we need to enable # writing enter_assembler_writing() frame.jf_frame_info.update_frame_depth(base_ofs, size) leave_assembler_writing() new_frame = jitframe.JITFRAME.allocate(frame.jf_frame_info) frame.jf_forward = new_frame i = 0 while i < len(frame.jf_frame): new_frame.jf_frame[i] = frame.jf_frame[i] frame.jf_frame[i] = 0 i += 1 new_frame.jf_savedata = frame.jf_savedata new_frame.jf_guard_exc = frame.jf_guard_exc # all other fields are empty llop.gc_writebarrier(lltype.Void, new_frame) return lltype.cast_opaque_ptr(llmemory.GCREF, new_frame) except Exception as e: print "Unhandled exception", e, "in realloc_frame" return lltype.nullptr(llmemory.GCREF.TO) def realloc_frame_crash(frame, size): print "frame", frame, "size", size return lltype.nullptr(llmemory.GCREF.TO) if not translate_support_code: fptr = llhelper(FUNC_TP, realloc_frame) else: FUNC = FUNC_TP.TO args_s = [lltype_to_annotation(ARG) for ARG in FUNC.ARGS] s_result = lltype_to_annotation(FUNC.RESULT) mixlevelann = MixLevelHelperAnnotator(self.rtyper) graph = mixlevelann.getgraph(realloc_frame, args_s, s_result) fptr = mixlevelann.graph2delayed(graph, FUNC) mixlevelann.finish() self.realloc_frame = ptr2int(fptr) if not translate_support_code: fptr = llhelper(FUNC_TP, realloc_frame_crash) else: FUNC = FUNC_TP.TO args_s = [lltype_to_annotation(ARG) for ARG in FUNC.ARGS] s_result = lltype_to_annotation(FUNC.RESULT) mixlevelann = MixLevelHelperAnnotator(self.rtyper) graph = mixlevelann.getgraph(realloc_frame_crash, args_s, s_result) fptr = mixlevelann.graph2delayed(graph, FUNC) mixlevelann.finish() self.realloc_frame_crash = ptr2int(fptr)
def _annotate(rtyper, f, args_s, s_result): ann = MixLevelHelperAnnotator(rtyper) graph = ann.getgraph(f, args_s, s_result) ann.finish() return graph
class BaseGCTransformer(object): finished_helpers = False curr_block = None 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 get_lltype_of_exception_value(self): exceptiondata = self.translator.rtyper.exceptiondata return exceptiondata.lltype_of_exception_value def need_minimal_transform(self, graph): self.seen_graphs.add(graph) self.minimal_transform.add(graph) def inline_helpers_into(self, graph): from rpython.translator.backendopt.inline import iter_callsites to_enum = [] for called, block, i in iter_callsites(graph, None): if called in self.graphs_to_inline: to_enum.append(called) any_inlining = False for inline_graph in to_enum: try: inline.inline_function(self.translator, inline_graph, graph, self.lltype_to_classdef, self.raise_analyzer, cleanup=False) any_inlining = True except inline.CannotInline as e: print 'CANNOT INLINE:', e print '\t%s into %s' % (inline_graph, graph) raise # for now, make it a fatal error cleanup_graph(graph) if any_inlining: constant_fold_graph(graph) return any_inlining def inline_helpers_and_postprocess(self, graphs): for graph in graphs: any_inlining = self.inline and self.inline_helpers_into(graph) self.postprocess_graph(graph, any_inlining) def postprocess_graph(self, graph, any_inlining): pass def compute_borrowed_vars(self, graph): # the input args are borrowed, and stay borrowed for as long as they # are not merged with other values. var_families = DataFlowFamilyBuilder(graph).get_variable_families() borrowed_reps = {} for v in graph.getargs(): borrowed_reps[var_families.find_rep(v)] = True # no support for returning borrowed values so far retvar = graph.getreturnvar() def is_borrowed(v1): return (var_families.find_rep(v1) in borrowed_reps and v1 is not retvar) return is_borrowed def transform_block(self, block, is_borrowed): llops = LowLevelOpList() self.curr_block = block self.livevars = [var for var in block.inputargs if var_needsgc(var) and not is_borrowed(var)] allvars = [var for var in block.getvariables() if var_needsgc(var)] self.var_last_needed_in = dict.fromkeys(allvars, 0) for i, op in enumerate(block.operations): for var in op.args: if not var_needsgc(var): continue self.var_last_needed_in[var] = i for link in block.exits: for var in link.args: if not var_needsgc(var): continue self.var_last_needed_in[var] = len(block.operations) + 1 for i, op in enumerate(block.operations): hop = GcHighLevelOp(self, op, i, llops) hop.dispatch() if len(block.exits) != 0: # i.e not the return block assert not block.canraise deadinallexits = set(self.livevars) for link in block.exits: deadinallexits.difference_update(set(link.args)) for var in deadinallexits: self.pop_alive(var, llops) for link in block.exits: livecounts = dict.fromkeys(set(self.livevars) - deadinallexits, 1) for v, v2 in zip(link.args, link.target.inputargs): if is_borrowed(v2): continue if v in livecounts: livecounts[v] -= 1 elif var_needsgc(v): # 'v' is typically a Constant here, but it can be # a borrowed variable going into a non-borrowed one livecounts[v] = -1 self.links_to_split[link] = livecounts block.operations[:] = llops self.livevars = None self.var_last_needed_in = None self.curr_block = None def start_transforming_graph(self, graph): pass # for asmgcc.py def transform_graph(self, graph): if graph in self.minimal_transform: if self.minimalgctransformer: self.minimalgctransformer.transform_graph(graph) self.minimal_transform.remove(graph) return if graph in self.seen_graphs: return self.seen_graphs.add(graph) self.start_transforming_graph(graph) self.links_to_split = {} # link -> vars to pop_alive across the link # for sanity, we need an empty block at the start of the graph inserted_empty_startblock = False if not starts_with_empty_block(graph): insert_empty_startblock(graph) inserted_empty_startblock = True is_borrowed = self.compute_borrowed_vars(graph) for block in graph.iterblocks(): self.transform_block(block, is_borrowed) for link, livecounts in self.links_to_split.iteritems(): llops = LowLevelOpList() for var, livecount in livecounts.iteritems(): for i in range(livecount): self.pop_alive(var, llops) for i in range(-livecount): self.push_alive(var, llops) if llops: if link.prevblock.exitswitch is None: link.prevblock.operations.extend(llops) else: insert_empty_block(link, llops) # remove the empty block at the start of the graph, which should # still be empty (but let's check) if starts_with_empty_block(graph) and inserted_empty_startblock: old_startblock = graph.startblock graph.startblock = graph.startblock.exits[0].target checkgraph(graph) self.links_to_split = None v = Variable('vanishing_exc_value') v.concretetype = self.get_lltype_of_exception_value() llops = LowLevelOpList() self.pop_alive(v, llops) graph.exc_cleanup = (v, list(llops)) return is_borrowed # xxx for tests only def annotate_helper(self, ll_helper, ll_args, ll_result, inline=False): assert not self.finished_helpers args_s = map(lltype_to_annotation, ll_args) s_result = lltype_to_annotation(ll_result) graph = self.mixlevelannotator.getgraph(ll_helper, args_s, s_result) # the produced graphs does not need to be fully transformed self.need_minimal_transform(graph) if inline: self.graphs_to_inline[graph] = True FUNCTYPE = lltype.FuncType(ll_args, ll_result) return self.mixlevelannotator.graph2delayed(graph, FUNCTYPE=FUNCTYPE) def inittime_helper(self, ll_helper, ll_args, ll_result, inline=True): ptr = self.annotate_helper(ll_helper, ll_args, ll_result, inline=inline) return Constant(ptr, lltype.typeOf(ptr)) def annotate_finalizer(self, ll_finalizer, ll_args, ll_result): fptr = self.annotate_helper(ll_finalizer, ll_args, ll_result) self.ll_finalizers_ptrs.append(fptr) return fptr def finish_helpers(self, backendopt=True): if self.translator is not None: self.mixlevelannotator.finish_annotate() if self.translator is not None: self.mixlevelannotator.finish_rtype() if backendopt: self.mixlevelannotator.backend_optimize() self.finished_helpers = True # Make sure that the database also sees all finalizers now. # It is likely that the finalizers need special support there newgcdependencies = self.ll_finalizers_ptrs return newgcdependencies def get_finish_helpers(self): return self.finish_helpers def finish_tables(self): pass def get_finish_tables(self): return self.finish_tables def finish(self, backendopt=True): self.finish_helpers(backendopt=backendopt) self.finish_tables() def transform_generic_set(self, hop): opname = hop.spaceop.opname v_new = hop.spaceop.args[-1] v_old = hop.genop('g' + opname[1:], hop.inputargs()[:-1], resulttype=v_new.concretetype) self.push_alive(v_new, hop.llops) hop.rename('bare_' + opname) self.pop_alive(v_old, hop.llops) def push_alive(self, var, llops): pass def pop_alive(self, var, llops): pass def var_needs_set_transform(self, var): return False def default(self, hop): hop.llops.append(hop.spaceop) def gct_setfield(self, hop): if self.var_needs_set_transform(hop.spaceop.args[-1]): self.transform_generic_set(hop) else: hop.rename('bare_' + hop.spaceop.opname) gct_setarrayitem = gct_setfield gct_setinteriorfield = gct_setfield gct_raw_store = gct_setfield gct_getfield = default def gct_zero_gc_pointers_inside(self, hop): pass def gct_gc_writebarrier_before_copy(self, hop): # We take the conservative default and return False here, meaning # that rgc.ll_arraycopy() will do the copy by hand (i.e. with a # 'for' loop). Subclasses that have their own logic, or that don't # need any kind of write barrier, may return True. op = hop.spaceop hop.genop("same_as", [rmodel.inputconst(lltype.Bool, False)], resultvar=op.result) def gct_gc_pin(self, hop): op = hop.spaceop hop.genop("same_as", [rmodel.inputconst(lltype.Bool, False)], resultvar=op.result) def gct_gc_unpin(self, hop): pass def gct_gc__is_pinned(self, hop): op = hop.spaceop hop.genop("same_as", [rmodel.inputconst(lltype.Bool, False)], resultvar=op.result) def gct_gc_identityhash(self, hop): # must be implemented in the various GCs raise NotImplementedError def gct_gc_id(self, hop): # this assumes a non-moving GC. Moving GCs need to override this hop.rename('cast_ptr_to_int') def gct_gc_heap_stats(self, hop): from rpython.memory.gc.base import ARRAY_TYPEID_MAP return hop.cast_result(rmodel.inputconst(lltype.Ptr(ARRAY_TYPEID_MAP), lltype.nullptr(ARRAY_TYPEID_MAP)))
class BaseGCTransformer(object): finished_helpers = False curr_block = None 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.graphs_to_inline = {} self.graph_dependencies = {} self.ll_finalizers_ptrs = [] if self.MinimalGCTransformer: self.minimalgctransformer = self.MinimalGCTransformer(self) else: self.minimalgctransformer = None def get_lltype_of_exception_value(self): exceptiondata = self.translator.rtyper.exceptiondata return exceptiondata.lltype_of_exception_value def need_minimal_transform(self, graph): self.seen_graphs.add(graph) self.minimal_transform.add(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 as e: print 'CANNOT INLINE:', e print '\t%s into %s' % (inline_graph, graph) cleanup_graph(graph) if must_constfold: constant_fold_graph(graph) def compute_borrowed_vars(self, graph): # the input args are borrowed, and stay borrowed for as long as they # are not merged with other values. var_families = DataFlowFamilyBuilder(graph).get_variable_families() borrowed_reps = {} for v in graph.getargs(): borrowed_reps[var_families.find_rep(v)] = True # no support for returning borrowed values so far retvar = graph.getreturnvar() def is_borrowed(v1): return (var_families.find_rep(v1) in borrowed_reps and v1 is not retvar) return is_borrowed def transform_block(self, block, is_borrowed): llops = LowLevelOpList() self.curr_block = block self.livevars = [var for var in block.inputargs if var_needsgc(var) and not is_borrowed(var)] allvars = [var for var in block.getvariables() if var_needsgc(var)] self.var_last_needed_in = dict.fromkeys(allvars, 0) for i, op in enumerate(block.operations): for var in op.args: if not var_needsgc(var): continue self.var_last_needed_in[var] = i for link in block.exits: for var in link.args: if not var_needsgc(var): continue self.var_last_needed_in[var] = len(block.operations) + 1 for i, op in enumerate(block.operations): hop = GcHighLevelOp(self, op, i, llops) hop.dispatch() if len(block.exits) != 0: # i.e not the return block assert not block.canraise deadinallexits = set(self.livevars) for link in block.exits: deadinallexits.difference_update(set(link.args)) for var in deadinallexits: self.pop_alive(var, llops) for link in block.exits: livecounts = dict.fromkeys(set(self.livevars) - deadinallexits, 1) for v, v2 in zip(link.args, link.target.inputargs): if is_borrowed(v2): continue if v in livecounts: livecounts[v] -= 1 elif var_needsgc(v): # 'v' is typically a Constant here, but it can be # a borrowed variable going into a non-borrowed one livecounts[v] = -1 self.links_to_split[link] = livecounts block.operations[:] = llops self.livevars = None self.var_last_needed_in = None self.curr_block = None def transform_graph(self, graph): if graph in self.minimal_transform: if self.minimalgctransformer: self.minimalgctransformer.transform_graph(graph) self.minimal_transform.remove(graph) return if graph in self.seen_graphs: return self.seen_graphs.add(graph) self.links_to_split = {} # link -> vars to pop_alive across the link # for sanity, we need an empty block at the start of the graph inserted_empty_startblock = False if not starts_with_empty_block(graph): insert_empty_startblock(graph) inserted_empty_startblock = True is_borrowed = self.compute_borrowed_vars(graph) for block in graph.iterblocks(): self.transform_block(block, is_borrowed) for link, livecounts in self.links_to_split.iteritems(): llops = LowLevelOpList() for var, livecount in livecounts.iteritems(): for i in range(livecount): self.pop_alive(var, llops) for i in range(-livecount): self.push_alive(var, llops) if llops: if link.prevblock.exitswitch is None: link.prevblock.operations.extend(llops) else: insert_empty_block(link, llops) # remove the empty block at the start of the graph, which should # still be empty (but let's check) if starts_with_empty_block(graph) and inserted_empty_startblock: old_startblock = graph.startblock graph.startblock = graph.startblock.exits[0].target checkgraph(graph) self.links_to_split = None v = Variable('vanishing_exc_value') v.concretetype = self.get_lltype_of_exception_value() llops = LowLevelOpList() self.pop_alive(v, llops) graph.exc_cleanup = (v, list(llops)) return is_borrowed # xxx for tests only def annotate_helper(self, ll_helper, ll_args, ll_result, inline=False): assert not self.finished_helpers args_s = map(lltype_to_annotation, ll_args) s_result = lltype_to_annotation(ll_result) graph = self.mixlevelannotator.getgraph(ll_helper, args_s, s_result) # the produced graphs does not need to be fully transformed self.need_minimal_transform(graph) if inline: self.graphs_to_inline[graph] = True FUNCTYPE = lltype.FuncType(ll_args, ll_result) return self.mixlevelannotator.graph2delayed(graph, FUNCTYPE=FUNCTYPE) def inittime_helper(self, ll_helper, ll_args, ll_result, inline=True): ptr = self.annotate_helper(ll_helper, ll_args, ll_result, inline=inline) return Constant(ptr, lltype.typeOf(ptr)) def annotate_finalizer(self, ll_finalizer, ll_args, ll_result): fptr = self.annotate_helper(ll_finalizer, ll_args, ll_result) self.ll_finalizers_ptrs.append(fptr) return fptr def finish_helpers(self, backendopt=True): if self.translator is not None: self.mixlevelannotator.finish_annotate() if self.translator is not None: self.mixlevelannotator.finish_rtype() if backendopt: self.mixlevelannotator.backend_optimize() self.finished_helpers = True # Make sure that the database also sees all finalizers now. # It is likely that the finalizers need special support there newgcdependencies = self.ll_finalizers_ptrs return newgcdependencies def get_finish_helpers(self): return self.finish_helpers def finish_tables(self): pass def get_finish_tables(self): return self.finish_tables def finish(self, backendopt=True): self.finish_helpers(backendopt=backendopt) self.finish_tables() def transform_generic_set(self, hop): opname = hop.spaceop.opname v_new = hop.spaceop.args[-1] v_old = hop.genop('g' + opname[1:], hop.inputargs()[:-1], resulttype=v_new.concretetype) self.push_alive(v_new, hop.llops) hop.rename('bare_' + opname) self.pop_alive(v_old, hop.llops) def push_alive(self, var, llops): pass def pop_alive(self, var, llops): pass def var_needs_set_transform(self, var): return False def default(self, hop): hop.llops.append(hop.spaceop) def gct_setfield(self, hop): if self.var_needs_set_transform(hop.spaceop.args[-1]): self.transform_generic_set(hop) else: hop.rename('bare_' + hop.spaceop.opname) gct_setarrayitem = gct_setfield gct_setinteriorfield = gct_setfield gct_raw_store = gct_setfield gct_getfield = default def gct_zero_gc_pointers_inside(self, hop): pass def gct_gc_writebarrier_before_copy(self, hop): # We take the conservative default and return False here, meaning # that rgc.ll_arraycopy() will do the copy by hand (i.e. with a # 'for' loop). Subclasses that have their own logic, or that don't # need any kind of write barrier, may return True. op = hop.spaceop hop.genop("same_as", [rmodel.inputconst(lltype.Bool, False)], resultvar=op.result) def gct_gc_pin(self, hop): op = hop.spaceop hop.genop("same_as", [rmodel.inputconst(lltype.Bool, False)], resultvar=op.result) def gct_gc_unpin(self, hop): pass def gct_gc__is_pinned(self, hop): op = hop.spaceop hop.genop("same_as", [rmodel.inputconst(lltype.Bool, False)], resultvar=op.result) def gct_gc_identityhash(self, hop): # must be implemented in the various GCs raise NotImplementedError def gct_gc_id(self, hop): # this assumes a non-moving GC. Moving GCs need to override this hop.rename('cast_ptr_to_int') def gct_gc_heap_stats(self, hop): from rpython.memory.gc.base import ARRAY_TYPEID_MAP return hop.cast_result(rmodel.inputconst(lltype.Ptr(ARRAY_TYPEID_MAP), lltype.nullptr(ARRAY_TYPEID_MAP))) def get_prebuilt_hash(self, obj): return None
def __init__(self, translator): self.translator = translator self.raise_analyzer = canraise.RaiseAnalyzer(translator) edata = translator.rtyper.exceptiondata self.lltype_of_exception_value = edata.lltype_of_exception_value self.lltype_of_exception_type = edata.lltype_of_exception_type self.mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) exc_data, null_type, null_value = self.setup_excdata() (assertion_error_ll_exc_type, assertion_error_ll_exc) = self.get_builtin_exception(AssertionError) (n_i_error_ll_exc_type, n_i_error_ll_exc) = self.get_builtin_exception(NotImplementedError) self.c_assertion_error_ll_exc_type = constant_value( assertion_error_ll_exc_type) self.c_n_i_error_ll_exc_type = constant_value(n_i_error_ll_exc_type) def rpyexc_occured(): exc_type = exc_data.exc_type return bool(exc_type) def rpyexc_fetch_type(): return exc_data.exc_type def rpyexc_fetch_value(): return exc_data.exc_value def rpyexc_clear(): exc_data.exc_type = null_type exc_data.exc_value = null_value def rpyexc_raise(etype, evalue): # When compiling in debug mode, the following ll_asserts will # crash the program as soon as it raises AssertionError or # NotImplementedError. Useful when you are in a debugger. # When compiling in release mode, AssertionErrors and # NotImplementedErrors are raised normally, and only later # caught by debug_catch_exception and printed, which allows # us to see at least part of the traceback for them. ll_assert(etype != assertion_error_ll_exc_type, "AssertionError") ll_assert(etype != n_i_error_ll_exc_type, "NotImplementedError") exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_start_traceback(lltype.Void, etype) def rpyexc_reraise(etype, evalue): exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_reraise_traceback(lltype.Void, etype) def rpyexc_fetch_exception(): evalue = rpyexc_fetch_value() rpyexc_clear() return evalue def rpyexc_restore_exception(evalue): if evalue: exc_data.exc_type = ll_inst_type(evalue) exc_data.exc_value = evalue self.rpyexc_occured_ptr = self.build_func("RPyExceptionOccurred", rpyexc_occured, [], lltype.Bool) self.rpyexc_fetch_type_ptr = self.build_func( "RPyFetchExceptionType", rpyexc_fetch_type, [], self.lltype_of_exception_type) self.rpyexc_fetch_value_ptr = self.build_func( "RPyFetchExceptionValue", rpyexc_fetch_value, [], self.lltype_of_exception_value) self.rpyexc_clear_ptr = self.build_func("RPyClearException", rpyexc_clear, [], lltype.Void) self.rpyexc_raise_ptr = self.build_func( "RPyRaiseException", self.noinline(rpyexc_raise), [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_reraise_ptr = self.build_func( "RPyReRaiseException", rpyexc_reraise, [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_fetch_exception_ptr = self.build_func( "RPyFetchException", rpyexc_fetch_exception, [], self.lltype_of_exception_value) self.rpyexc_restore_exception_ptr = self.build_func( "RPyRestoreException", self.noinline(rpyexc_restore_exception), [self.lltype_of_exception_value], lltype.Void) self.build_extra_funcs() self.mixlevelannotator.finish() self.lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping( )
class ExceptionTransformer(object): def __init__(self, translator): self.translator = translator self.raise_analyzer = canraise.RaiseAnalyzer(translator) edata = translator.rtyper.exceptiondata self.lltype_of_exception_value = edata.lltype_of_exception_value self.lltype_of_exception_type = edata.lltype_of_exception_type self.mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) exc_data, null_type, null_value = self.setup_excdata() (assertion_error_ll_exc_type, assertion_error_ll_exc) = self.get_builtin_exception(AssertionError) (n_i_error_ll_exc_type, n_i_error_ll_exc) = self.get_builtin_exception(NotImplementedError) self.c_assertion_error_ll_exc_type = constant_value( assertion_error_ll_exc_type) self.c_n_i_error_ll_exc_type = constant_value(n_i_error_ll_exc_type) def rpyexc_occured(): exc_type = exc_data.exc_type return bool(exc_type) def rpyexc_fetch_type(): return exc_data.exc_type def rpyexc_fetch_value(): return exc_data.exc_value def rpyexc_clear(): exc_data.exc_type = null_type exc_data.exc_value = null_value def rpyexc_raise(etype, evalue): # When compiling in debug mode, the following ll_asserts will # crash the program as soon as it raises AssertionError or # NotImplementedError. Useful when you are in a debugger. # When compiling in release mode, AssertionErrors and # NotImplementedErrors are raised normally, and only later # caught by debug_catch_exception and printed, which allows # us to see at least part of the traceback for them. ll_assert(etype != assertion_error_ll_exc_type, "AssertionError") ll_assert(etype != n_i_error_ll_exc_type, "NotImplementedError") exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_start_traceback(lltype.Void, etype) def rpyexc_reraise(etype, evalue): exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_reraise_traceback(lltype.Void, etype) def rpyexc_fetch_exception(): evalue = rpyexc_fetch_value() rpyexc_clear() return evalue def rpyexc_restore_exception(evalue): if evalue: exc_data.exc_type = ll_inst_type(evalue) exc_data.exc_value = evalue self.rpyexc_occured_ptr = self.build_func( "RPyExceptionOccurred", rpyexc_occured, [], lltype.Bool) self.rpyexc_fetch_type_ptr = self.build_func( "RPyFetchExceptionType", rpyexc_fetch_type, [], self.lltype_of_exception_type) self.rpyexc_fetch_value_ptr = self.build_func( "RPyFetchExceptionValue", rpyexc_fetch_value, [], self.lltype_of_exception_value) self.rpyexc_clear_ptr = self.build_func( "RPyClearException", rpyexc_clear, [], lltype.Void) self.rpyexc_raise_ptr = self.build_func( "RPyRaiseException", self.noinline(rpyexc_raise), [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_reraise_ptr = self.build_func( "RPyReRaiseException", rpyexc_reraise, [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_fetch_exception_ptr = self.build_func( "RPyFetchException", rpyexc_fetch_exception, [], self.lltype_of_exception_value) self.rpyexc_restore_exception_ptr = self.build_func( "RPyRestoreException", self.noinline(rpyexc_restore_exception), [self.lltype_of_exception_value], lltype.Void) self.build_extra_funcs() self.mixlevelannotator.finish() self.lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping() def noinline(self, fn): fn = func_with_new_name(fn, fn.__name__) fn._dont_inline_ = True return fn def build_func(self, name, fn, inputtypes, rettype, **kwds): l2a = lltype_to_annotation graph = self.mixlevelannotator.getgraph(fn, map(l2a, inputtypes), l2a(rettype)) return self.constant_func(name, inputtypes, rettype, graph, exception_policy="exc_helper", **kwds) def get_builtin_exception(self, Class): edata = self.translator.rtyper.exceptiondata bk = self.translator.annotator.bookkeeper error_def = bk.getuniqueclassdef(Class) error_ll_exc = edata.get_standard_ll_exc_instance( self.translator.rtyper, error_def) error_ll_exc_type = ll_inst_type(error_ll_exc) return error_ll_exc_type, error_ll_exc def transform_completely(self): for graph in self.translator.graphs: self.create_exception_handling(graph) def create_exception_handling(self, graph): """After an exception in a direct_call (or indirect_call), that is not caught by an explicit except statement, we need to reraise the exception. So after this direct_call we need to test if an exception had occurred. If so, we return from the current graph with a special value (False/-1/-1.0/null). Because of the added exitswitch we need an additional block. """ if hasattr(graph, 'exceptiontransformed'): assert self.same_obj(self.exc_data_ptr, graph.exceptiontransformed) return else: self.raise_analyzer.analyze_direct_call(graph) graph.exceptiontransformed = self.exc_data_ptr join_blocks(graph) # collect the blocks before changing them n_need_exc_matching_blocks = 0 n_gen_exc_checks = 0 # entrymap = mkentrymap(graph) if graph.exceptblock in entrymap: for link in entrymap[graph.exceptblock]: self.transform_jump_to_except_block(graph, entrymap, link) # for block in list(graph.iterblocks()): self.replace_fetch_restore_operations(block) need_exc_matching, gen_exc_checks = self.transform_block(graph, block) n_need_exc_matching_blocks += need_exc_matching n_gen_exc_checks += gen_exc_checks cleanup_graph(graph) return n_need_exc_matching_blocks, n_gen_exc_checks def replace_fetch_restore_operations(self, block): # the gctransformer will create these operations. It looks as if the # order of transformations is important - but the gctransformer will # put them in a new graph, so all transformations will run again. for i in range(len(block.operations)): opname = block.operations[i].opname if opname == 'gc_fetch_exception': block.operations[i].opname = "direct_call" block.operations[i].args = [self.rpyexc_fetch_exception_ptr] elif opname == 'gc_restore_exception': block.operations[i].opname = "direct_call" block.operations[i].args.insert(0, self.rpyexc_restore_exception_ptr) elif opname == 'get_exception_addr': # only for lltype block.operations[i].opname = "direct_call" block.operations[i].args.insert(0, self.rpyexc_get_exception_addr_ptr) elif opname == 'get_exc_value_addr': # only for lltype block.operations[i].opname = "direct_call" block.operations[i].args.insert(0, self.rpyexc_get_exc_value_addr_ptr) def transform_block(self, graph, block): need_exc_matching = False n_gen_exc_checks = 0 if block is graph.exceptblock: return need_exc_matching, n_gen_exc_checks elif block is graph.returnblock: return need_exc_matching, n_gen_exc_checks last_operation = len(block.operations) - 1 if block.exitswitch == c_last_exception: need_exc_matching = True last_operation -= 1 elif (len(block.exits) == 1 and block.exits[0].target is graph.returnblock and len(block.operations) and (block.exits[0].args[0].concretetype is lltype.Void or block.exits[0].args[0] is block.operations[-1].result) and block.operations[-1].opname not in ('malloc', # special cases 'malloc_nonmovable')): last_operation -= 1 lastblock = block for i in range(last_operation, -1, -1): op = block.operations[i] if not self.raise_analyzer.can_raise(op): continue splitlink = split_block(None, block, i+1) afterblock = splitlink.target if lastblock is block: lastblock = afterblock self.gen_exc_check(block, graph.returnblock, afterblock) n_gen_exc_checks += 1 if need_exc_matching: assert lastblock.exitswitch == c_last_exception if not self.raise_analyzer.can_raise(lastblock.operations[-1]): #print ("operation %s cannot raise, but has exception" # " guarding in graph %s" % (lastblock.operations[-1], # graph)) lastblock.exitswitch = None lastblock.recloseblock(lastblock.exits[0]) lastblock.exits[0].exitcase = None else: self.insert_matching(lastblock, graph) return need_exc_matching, n_gen_exc_checks def comes_from_last_exception(self, entrymap, link): seen = set() pending = [(link, link.args[1])] while pending: link, v = pending.pop() if (link, v) in seen: continue seen.add((link, v)) if link.last_exc_value is not None and v is link.last_exc_value: return True block = link.prevblock if block is None: continue for op in block.operations[::-1]: if v is op.result: if op.opname == 'cast_pointer': v = op.args[0] else: break for link in entrymap.get(block, ()): for v1, v2 in zip(link.args, block.inputargs): if v2 is v: pending.append((link, v1)) return False def transform_jump_to_except_block(self, graph, entrymap, link): reraise = self.comes_from_last_exception(entrymap, link) result = Variable() result.concretetype = lltype.Void block = Block([copyvar(None, v) for v in graph.exceptblock.inputargs]) if reraise: block.operations = [ SpaceOperation("direct_call", [self.rpyexc_reraise_ptr] + block.inputargs, result), ] else: block.operations = [ SpaceOperation("direct_call", [self.rpyexc_raise_ptr] + block.inputargs, result), SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void)), ] link.target = block RETTYPE = graph.returnblock.inputargs[0].concretetype l = Link([error_constant(RETTYPE)], graph.returnblock) block.recloseblock(l) def insert_matching(self, block, graph): proxygraph, op = self.create_proxy_graph(block.operations[-1]) block.operations[-1] = op #non-exception case block.exits[0].exitcase = block.exits[0].llexitcase = None # use the dangerous second True flag :-) inliner = inline.OneShotInliner( self.translator, graph, self.lltype_to_classdef, inline_guarded_calls=True, inline_guarded_calls_no_matter_what=True, raise_analyzer=self.raise_analyzer) inliner.inline_once(block, len(block.operations)-1) #block.exits[0].exitcase = block.exits[0].llexitcase = False def create_proxy_graph(self, op): """ creates a graph which calls the original function, checks for raised exceptions, fetches and then raises them again. If this graph is inlined, the correct exception matching blocks are produced.""" # XXX slightly annoying: construct a graph by hand # but better than the alternative result = copyvar(None, op.result) opargs = [] inputargs = [] callargs = [] ARGTYPES = [] for var in op.args: if isinstance(var, Variable): v = Variable() v.concretetype = var.concretetype inputargs.append(v) opargs.append(v) callargs.append(var) ARGTYPES.append(var.concretetype) else: opargs.append(var) newop = SpaceOperation(op.opname, opargs, result) startblock = Block(inputargs) startblock.operations.append(newop) newgraph = FunctionGraph("dummy_exc1", startblock) startblock.closeblock(Link([result], newgraph.returnblock)) newgraph.returnblock.inputargs[0].concretetype = op.result.concretetype self.gen_exc_check(startblock, newgraph.returnblock) excblock = Block([]) llops = rtyper.LowLevelOpList(None) var_value = self.gen_getfield('exc_value', llops) var_type = self.gen_getfield('exc_type' , llops) # c_check1 = self.c_assertion_error_ll_exc_type c_check2 = self.c_n_i_error_ll_exc_type llops.genop('debug_catch_exception', [var_type, c_check1, c_check2]) # self.gen_setfield('exc_value', self.c_null_evalue, llops) self.gen_setfield('exc_type', self.c_null_etype, llops) excblock.operations[:] = llops newgraph.exceptblock.inputargs[0].concretetype = self.lltype_of_exception_type newgraph.exceptblock.inputargs[1].concretetype = self.lltype_of_exception_value excblock.closeblock(Link([var_type, var_value], newgraph.exceptblock)) startblock.exits[True].target = excblock startblock.exits[True].args = [] fptr = self.constant_func("dummy_exc1", ARGTYPES, op.result.concretetype, newgraph) return newgraph, SpaceOperation("direct_call", [fptr] + callargs, op.result) def gen_exc_check(self, block, returnblock, normalafterblock=None): #var_exc_occured = Variable() #var_exc_occured.concretetype = lltype.Bool #block.operations.append(SpaceOperation("safe_call", [self.rpyexc_occured_ptr], var_exc_occured)) llops = rtyper.LowLevelOpList(None) spaceop = block.operations[-1] alloc_shortcut = self.check_for_alloc_shortcut(spaceop) if alloc_shortcut: var_no_exc = self.gen_nonnull(spaceop.result, llops) else: v_exc_type = self.gen_getfield('exc_type', llops) var_no_exc = self.gen_isnull(v_exc_type, llops) block.operations.extend(llops) block.exitswitch = var_no_exc #exception occurred case b = Block([]) b.operations = [SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void))] l = Link([error_constant(returnblock.inputargs[0].concretetype)], returnblock) b.closeblock(l) l = Link([], b) l.exitcase = l.llexitcase = False #non-exception case l0 = block.exits[0] l0.exitcase = l0.llexitcase = True block.recloseblock(l0, l) insert_zeroing_op = False if spaceop.opname == 'malloc': flavor = spaceop.args[1].value['flavor'] if flavor == 'gc': insert_zeroing_op = True elif spaceop.opname == 'malloc_nonmovable': # xxx we cannot insert zero_gc_pointers_inside after # malloc_nonmovable, because it can return null. For now # we simply always force the zero=True flag on # malloc_nonmovable. c_flags = spaceop.args[1] c_flags.value = c_flags.value.copy() spaceop.args[1].value['zero'] = True # NB. when inserting more special-cases here, keep in mind that # you also need to list the opnames in transform_block() # (see "special cases") if insert_zeroing_op: if normalafterblock is None: normalafterblock = insert_empty_block(None, l0) v_result = spaceop.result if v_result in l0.args: result_i = l0.args.index(v_result) v_result_after = normalafterblock.inputargs[result_i] else: v_result_after = copyvar(None, v_result) l0.args.append(v_result) normalafterblock.inputargs.append(v_result_after) normalafterblock.operations.insert( 0, SpaceOperation('zero_gc_pointers_inside', [v_result_after], varoftype(lltype.Void))) def setup_excdata(self): EXCDATA = lltype.Struct('ExcData', ('exc_type', self.lltype_of_exception_type), ('exc_value', self.lltype_of_exception_value)) self.EXCDATA = EXCDATA exc_data = lltype.malloc(EXCDATA, immortal=True) null_type = lltype.nullptr(self.lltype_of_exception_type.TO) null_value = lltype.nullptr(self.lltype_of_exception_value.TO) self.exc_data_ptr = exc_data self.cexcdata = Constant(exc_data, lltype.Ptr(self.EXCDATA)) self.c_null_etype = Constant(null_type, self.lltype_of_exception_type) self.c_null_evalue = Constant(null_value, self.lltype_of_exception_value) return exc_data, null_type, null_value def constant_func(self, name, inputtypes, rettype, graph, **kwds): FUNC_TYPE = lltype.FuncType(inputtypes, rettype) fn_ptr = lltype.functionptr(FUNC_TYPE, name, graph=graph, **kwds) return Constant(fn_ptr, lltype.Ptr(FUNC_TYPE)) def gen_getfield(self, name, llops): c_name = inputconst(lltype.Void, name) return llops.genop('getfield', [self.cexcdata, c_name], resulttype = getattr(self.EXCDATA, name)) def gen_setfield(self, name, v_value, llops): c_name = inputconst(lltype.Void, name) llops.genop('setfield', [self.cexcdata, c_name, v_value]) def gen_isnull(self, v, llops): return llops.genop('ptr_iszero', [v], lltype.Bool) def gen_nonnull(self, v, llops): return llops.genop('ptr_nonzero', [v], lltype.Bool) def same_obj(self, ptr1, ptr2): return ptr1._same_obj(ptr2) def check_for_alloc_shortcut(self, spaceop): if spaceop.opname in ('malloc', 'malloc_varsize'): return True elif spaceop.opname == 'direct_call': fnobj = spaceop.args[0].value._obj if hasattr(fnobj, '_callable'): oopspec = getattr(fnobj._callable, 'oopspec', None) if oopspec and oopspec == 'newlist(length)': return True return False def build_extra_funcs(self): EXCDATA = self.EXCDATA exc_data = self.exc_data_ptr def rpyexc_get_exception_addr(): return (llmemory.cast_ptr_to_adr(exc_data) + llmemory.offsetof(EXCDATA, 'exc_type')) def rpyexc_get_exc_value_addr(): return (llmemory.cast_ptr_to_adr(exc_data) + llmemory.offsetof(EXCDATA, 'exc_value')) self.rpyexc_get_exception_addr_ptr = self.build_func( "RPyGetExceptionAddr", rpyexc_get_exception_addr, [], llmemory.Address) self.rpyexc_get_exc_value_addr_ptr = self.build_func( "RPyGetExcValueAddr", rpyexc_get_exc_value_addr, [], llmemory.Address)
def __init__(self, translator): self.translator = translator self.raise_analyzer = canraise.RaiseAnalyzer(translator) edata = translator.rtyper.exceptiondata self.lltype_of_exception_value = edata.lltype_of_exception_value self.lltype_of_exception_type = edata.lltype_of_exception_type self.mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper) exc_data, null_type, null_value = self.setup_excdata() (assertion_error_ll_exc_type, assertion_error_ll_exc) = self.get_builtin_exception(AssertionError) (n_i_error_ll_exc_type, n_i_error_ll_exc) = self.get_builtin_exception(NotImplementedError) self.c_assertion_error_ll_exc_type = constant_value( assertion_error_ll_exc_type) self.c_n_i_error_ll_exc_type = constant_value(n_i_error_ll_exc_type) def rpyexc_occured(): exc_type = exc_data.exc_type return bool(exc_type) def rpyexc_fetch_type(): return exc_data.exc_type def rpyexc_fetch_value(): return exc_data.exc_value def rpyexc_clear(): exc_data.exc_type = null_type exc_data.exc_value = null_value def rpyexc_raise(etype, evalue): # When compiling in debug mode, the following ll_asserts will # crash the program as soon as it raises AssertionError or # NotImplementedError. Useful when you are in a debugger. # When compiling in release mode, AssertionErrors and # NotImplementedErrors are raised normally, and only later # caught by debug_catch_exception and printed, which allows # us to see at least part of the traceback for them. ll_assert(etype != assertion_error_ll_exc_type, "AssertionError") ll_assert(etype != n_i_error_ll_exc_type, "NotImplementedError") exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_start_traceback(lltype.Void, etype) def rpyexc_reraise(etype, evalue): exc_data.exc_type = etype exc_data.exc_value = evalue lloperation.llop.debug_reraise_traceback(lltype.Void, etype) def rpyexc_fetch_exception(): evalue = rpyexc_fetch_value() rpyexc_clear() return evalue def rpyexc_restore_exception(evalue): if evalue: exc_data.exc_type = ll_inst_type(evalue) exc_data.exc_value = evalue self.rpyexc_occured_ptr = self.build_func( "RPyExceptionOccurred", rpyexc_occured, [], lltype.Bool) self.rpyexc_fetch_type_ptr = self.build_func( "RPyFetchExceptionType", rpyexc_fetch_type, [], self.lltype_of_exception_type) self.rpyexc_fetch_value_ptr = self.build_func( "RPyFetchExceptionValue", rpyexc_fetch_value, [], self.lltype_of_exception_value) self.rpyexc_clear_ptr = self.build_func( "RPyClearException", rpyexc_clear, [], lltype.Void) self.rpyexc_raise_ptr = self.build_func( "RPyRaiseException", self.noinline(rpyexc_raise), [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_reraise_ptr = self.build_func( "RPyReRaiseException", rpyexc_reraise, [self.lltype_of_exception_type, self.lltype_of_exception_value], lltype.Void, jitcallkind='rpyexc_raise') # for the JIT self.rpyexc_fetch_exception_ptr = self.build_func( "RPyFetchException", rpyexc_fetch_exception, [], self.lltype_of_exception_value) self.rpyexc_restore_exception_ptr = self.build_func( "RPyRestoreException", self.noinline(rpyexc_restore_exception), [self.lltype_of_exception_value], lltype.Void) self.build_extra_funcs() self.mixlevelannotator.finish() self.lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping()
class WarmRunnerDesc(object): def __init__(self, translator, policy=None, backendopt=True, CPUClass=None, ProfilerClass=EmptyProfiler, **kwds): pyjitpl._warmrunnerdesc = self # this is a global for debugging only! self.set_translator(translator) self.memory_manager = memmgr.MemoryManager() self.build_cpu(CPUClass, **kwds) self.inline_inlineable_portals() self.find_portals() self.codewriter = codewriter.CodeWriter(self.cpu, self.jitdrivers_sd) if policy is None: policy = JitPolicy() policy.set_supports_floats(self.cpu.supports_floats) policy.set_supports_longlong(self.cpu.supports_longlong) policy.set_supports_singlefloats(self.cpu.supports_singlefloats) graphs = self.codewriter.find_all_graphs(policy) policy.dump_unsafe_loops() self.check_access_directly_sanity(graphs) if backendopt: self.prejit_optimizations(policy, graphs) elif self.opt.listops: self.prejit_optimizations_minimal_inline(policy, graphs) self.build_meta_interp(ProfilerClass, translator.config.translation.jit_opencoder_model) self.make_args_specifications() # from rpython.jit.metainterp.virtualref import VirtualRefInfo vrefinfo = VirtualRefInfo(self) self.codewriter.setup_vrefinfo(vrefinfo) # from rpython.jit.metainterp import counter if self.cpu.translate_support_code: self.jitcounter = counter.JitCounter(translator=translator) else: self.jitcounter = counter.DeterministicJitCounter() # self.hooks = policy.jithookiface self.make_virtualizable_infos() self.make_driverhook_graphs() self.make_enter_functions() self.rewrite_jit_merge_points(policy) verbose = False # not self.cpu.translate_support_code self.rewrite_access_helpers() self.create_jit_entry_points() jitcodes = self.codewriter.make_jitcodes(verbose=verbose) self.metainterp_sd.jitcodes = jitcodes self.rewrite_can_enter_jits() self.rewrite_set_param_and_get_stats() self.rewrite_force_virtual(vrefinfo) self.rewrite_jitcell_accesses() self.rewrite_force_quasi_immutable() self.add_finish() self.metainterp_sd.finish_setup(self.codewriter) def finish(self): vinfos = set([jd.virtualizable_info for jd in self.jitdrivers_sd]) for vinfo in vinfos: if vinfo is not None: vinfo.finish() self.metainterp_sd.finish_setup_descrs() if self.cpu.translate_support_code: self.annhelper.finish() def _freeze_(self): return True def set_translator(self, translator): self.translator = translator self.rtyper = translator.rtyper self.gcdescr = gc.get_description(translator.config) def inline_inlineable_portals(self): """ Find all the graphs which have been decorated with @jitdriver.inline and inline them in the callers, making them JIT portals. Then, create a fresh copy of the jitdriver for each of those new portals, because they cannot share the same one. See test_ajit::test_inline_jit_merge_point """ from rpython.translator.backendopt.inline import ( inlinable_static_callers, auto_inlining) jmp_calls = {} def get_jmp_call(graph, _inline_jit_merge_point_): # there might be multiple calls to the @inlined function: the # first time we see it, we remove the call to the jit_merge_point # and we remember the corresponding op. Then, we create a new call # to it every time we need a new one (i.e., for each callsite # which becomes a new portal) try: op, jmp_graph = jmp_calls[graph] except KeyError: op, jmp_graph = fish_jmp_call(graph, _inline_jit_merge_point_) jmp_calls[graph] = op, jmp_graph # # clone the op newargs = op.args[:] newresult = Variable() newresult.concretetype = op.result.concretetype op = SpaceOperation(op.opname, newargs, newresult) return op, jmp_graph def fish_jmp_call(graph, _inline_jit_merge_point_): # graph is function which has been decorated with # @jitdriver.inline, so its very first op is a call to the # function which contains the actual jit_merge_point: fish it! jmp_block, op_jmp_call = next(callee.iterblockops()) msg = ("The first operation of an _inline_jit_merge_point_ graph must be " "a direct_call to the function passed to @jitdriver.inline()") assert op_jmp_call.opname == 'direct_call', msg jmp_funcobj = op_jmp_call.args[0].value._obj assert jmp_funcobj._callable is _inline_jit_merge_point_, msg jmp_block.operations.remove(op_jmp_call) return op_jmp_call, jmp_funcobj.graph # find all the graphs which call an @inline_in_portal function callgraph = inlinable_static_callers(self.translator.graphs, store_calls=True) new_callgraph = [] new_portals = set() inlined_jit_merge_points = set() for caller, block, op_call, callee in callgraph: func = getattr(callee, 'func', None) _inline_jit_merge_point_ = getattr(func, '_inline_jit_merge_point_', None) if _inline_jit_merge_point_: _inline_jit_merge_point_._always_inline_ = True inlined_jit_merge_points.add(_inline_jit_merge_point_) op_jmp_call, jmp_graph = get_jmp_call(callee, _inline_jit_merge_point_) # # now we move the op_jmp_call from callee to caller, just # before op_call. We assume that the args passed to # op_jmp_call are the very same which are received by callee # (i.e., the one passed to op_call) assert len(op_call.args) == len(op_jmp_call.args) op_jmp_call.args[1:] = op_call.args[1:] idx = block.operations.index(op_call) block.operations.insert(idx, op_jmp_call) # # finally, we signal that we want to inline op_jmp_call into # caller, so that finally the actuall call to # driver.jit_merge_point will be seen there new_callgraph.append((caller, jmp_graph)) new_portals.add(caller) # inline them! inline_threshold = 0.1 # we rely on the _always_inline_ set above auto_inlining(self.translator, inline_threshold, new_callgraph) # clean up _always_inline_ = True, it can explode later for item in inlined_jit_merge_points: del item._always_inline_ # make a fresh copy of the JitDriver in all newly created # jit_merge_points self.clone_inlined_jit_merge_points(new_portals) def clone_inlined_jit_merge_points(self, graphs): """ Find all the jit_merge_points in the given graphs, and replace the original JitDriver with a fresh clone. """ if not graphs: return for graph, block, pos in find_jit_merge_points(graphs): op = block.operations[pos] v_driver = op.args[1] driver = v_driver.value if not driver.inline_jit_merge_point: continue new_driver = driver.clone() c_new_driver = Constant(new_driver, v_driver.concretetype) op.args[1] = c_new_driver def find_portals(self): self.jitdrivers_sd = [] graphs = self.translator.graphs for graph, block, pos in find_jit_merge_points(graphs): support.autodetect_jit_markers_redvars(graph) self.split_graph_and_record_jitdriver(graph, block, pos) # assert (len(set([jd.jitdriver for jd in self.jitdrivers_sd])) == len(self.jitdrivers_sd)), \ "there are multiple jit_merge_points with the same jitdriver" def split_graph_and_record_jitdriver(self, graph, block, pos): op = block.operations[pos] jd = JitDriverStaticData() jd._jit_merge_point_in = graph args = op.args[2:] s_binding = self.translator.annotator.binding jd._portal_args_s = [s_binding(v) for v in args] graph = copygraph(graph) [jmpp] = find_jit_merge_points([graph]) graph.startblock = support.split_before_jit_merge_point(*jmpp) # XXX this is incredibly obscure, but this is sometiems necessary # so we don't explode in checkgraph. for reasons unknown this # is not contanied within simplify_graph removenoops.remove_same_as(graph) # a crash in the following checkgraph() means that you forgot # to list some variable in greens=[] or reds=[] in JitDriver, # or that a jit_merge_point() takes a constant as an argument. checkgraph(graph) for v in graph.getargs(): assert isinstance(v, Variable) assert len(dict.fromkeys(graph.getargs())) == len(graph.getargs()) self.translator.graphs.append(graph) jd.portal_graph = graph # it's a bit unbelievable to have a portal without func assert hasattr(graph, "func") graph.func._dont_inline_ = True graph.func._jit_unroll_safe_ = True jd.jitdriver = block.operations[pos].args[1].value jd.vec = jd.jitdriver.vec jd.portal_runner_ptr = "<not set so far>" jd.result_type = history.getkind(jd.portal_graph.getreturnvar() .concretetype)[0] self.jitdrivers_sd.append(jd) def check_access_directly_sanity(self, graphs): from rpython.translator.backendopt.inline import collect_called_graphs jit_graphs = set(graphs) for graph in collect_called_graphs(self.translator.entry_point_graph, self.translator): if graph in jit_graphs: continue assert not getattr(graph, 'access_directly', False) def prejit_optimizations(self, policy, graphs): from rpython.translator.backendopt.all import backend_optimizations backend_optimizations(self.translator, graphs=graphs, merge_if_blocks=True, constfold=True, remove_asserts=True, really_remove_asserts=True, replace_we_are_jitted=False) def prejit_optimizations_minimal_inline(self, policy, graphs): from rpython.translator.backendopt.inline import auto_inline_graphs auto_inline_graphs(self.translator, graphs, 0.01) def build_cpu(self, CPUClass, translate_support_code=False, no_stats=False, supports_floats=True, supports_longlong=True, supports_singlefloats=True, **kwds): assert CPUClass is not None self.opt = history.Options(**kwds) if no_stats: stats = history.NoStats() else: stats = history.Stats(None) self.stats = stats if translate_support_code: self.annhelper = MixLevelHelperAnnotator(self.translator.rtyper) cpu = CPUClass(self.translator.rtyper, self.stats, self.opt, translate_support_code, gcdescr=self.gcdescr) if not supports_floats: cpu.supports_floats = False if not supports_longlong: cpu.supports_longlong = False if not supports_singlefloats: cpu.supports_singlefloats = False self.cpu = cpu def build_meta_interp(self, ProfilerClass, opencoder_model): from rpython.jit.metainterp.opencoder import Model, BigModel self.metainterp_sd = MetaInterpStaticData(self.cpu, self.opt, ProfilerClass=ProfilerClass, warmrunnerdesc=self) if opencoder_model == 'big': self.metainterp_sd.opencoder_model = BigModel else: self.metainterp_sd.opencoder_model = Model self.stats.metainterp_sd = self.metainterp_sd def make_virtualizable_infos(self): vinfos = {} for jd in self.jitdrivers_sd: # jd.greenfield_info = None for name in jd.jitdriver.greens: if '.' in name: from rpython.jit.metainterp.greenfield import GreenFieldInfo jd.greenfield_info = GreenFieldInfo(self.cpu, jd) break # if not jd.jitdriver.virtualizables: jd.virtualizable_info = None jd.index_of_virtualizable = -1 continue else: assert jd.greenfield_info is None, "XXX not supported yet" # jitdriver = jd.jitdriver assert len(jitdriver.virtualizables) == 1 # for now [vname] = jitdriver.virtualizables # XXX skip the Voids here too jd.index_of_virtualizable = jitdriver.reds.index(vname) # index = jd.num_green_args + jd.index_of_virtualizable VTYPEPTR = jd._JIT_ENTER_FUNCTYPE.ARGS[index] if VTYPEPTR not in vinfos: from rpython.jit.metainterp.virtualizable import VirtualizableInfo vinfos[VTYPEPTR] = VirtualizableInfo(self, VTYPEPTR) jd.virtualizable_info = vinfos[VTYPEPTR] def make_enter_functions(self): for jd in self.jitdrivers_sd: self.make_enter_function(jd) def make_enter_function(self, jd): from rpython.jit.metainterp.warmstate import WarmEnterState state = WarmEnterState(self, jd) maybe_compile_and_run, EnterJitAssembler = state.make_entry_point() jd.warmstate = state def crash_in_jit(e): tb = not we_are_translated() and sys.exc_info()[2] try: raise e except jitexc.JitException: raise # go through except MemoryError: raise # go through except StackOverflow: raise # go through except Exception as e: if not we_are_translated(): print "~~~ Crash in JIT!" print '~~~ %s: %s' % (e.__class__, e) if sys.stdout == sys.__stdout__: import pdb; pdb.post_mortem(tb) raise e.__class__, e, tb fatalerror('~~~ Crash in JIT! %s' % (e,)) crash_in_jit._dont_inline_ = True def maybe_enter_jit(*args): try: maybe_compile_and_run(state.increment_threshold, *args) except Exception as e: crash_in_jit(e) maybe_enter_jit._always_inline_ = True jd._maybe_enter_jit_fn = maybe_enter_jit jd._maybe_compile_and_run_fn = maybe_compile_and_run jd._EnterJitAssembler = EnterJitAssembler def make_driverhook_graphs(self): # annhelper = MixLevelHelperAnnotator(self.translator.rtyper) for jd in self.jitdrivers_sd: jd._get_printable_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_printable_location, annmodel.SomeString()) jd._get_unique_id_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_unique_id, annmodel.SomeInteger()) jd._confirm_enter_jit_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.confirm_enter_jit, annmodel.s_Bool, onlygreens=False) jd._can_never_inline_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.can_never_inline, annmodel.s_Bool) jd._should_unroll_one_iteration_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.should_unroll_one_iteration, annmodel.s_Bool) # items = [] types = () pos = () if jd.jitdriver.get_location: assert hasattr(jd.jitdriver.get_location, '_loc_types'), """ You must decorate your get_location function: from rpython.rlib.rjitlog import rjitlog as jl @jl.returns(jl.MP_FILENAME, jl.MP_XXX, ...) def get_loc(your, green, keys): name = "x.txt" # extract it from your green keys return (name, ...) """ types = jd.jitdriver.get_location._loc_types del jd.jitdriver.get_location._loc_types # for _,type in types: if type == 's': items.append(annmodel.SomeString()) elif type == 'i': items.append(annmodel.SomeInteger()) else: raise NotImplementedError s_Tuple = annmodel.SomeTuple(items) jd._get_location_ptr = self._make_hook_graph(jd, annhelper, jd.jitdriver.get_location, s_Tuple) jd._get_loc_types = types annhelper.finish() def _make_hook_graph(self, jitdriver_sd, annhelper, func, s_result, s_first_arg=None, onlygreens=True): if func is None: return None # if not onlygreens: assert not jitdriver_sd.jitdriver.autoreds, ( "reds='auto' is not compatible with JitDriver hooks such as " "confirm_enter_jit") extra_args_s = [] if s_first_arg is not None: extra_args_s.append(s_first_arg) # args_s = jitdriver_sd._portal_args_s if onlygreens: args_s = args_s[:len(jitdriver_sd._green_args_spec)] graph = annhelper.getgraph(func, extra_args_s + args_s, s_result) funcptr = annhelper.graph2delayed(graph) return funcptr def make_args_specifications(self): for jd in self.jitdrivers_sd: self.make_args_specification(jd) def make_args_specification(self, jd): graph = jd._jit_merge_point_in _, _, op = locate_jit_merge_point(graph) greens_v, reds_v = support.decode_hp_hint_args(op) ALLARGS = [v.concretetype for v in (greens_v + reds_v)] jd._green_args_spec = [v.concretetype for v in greens_v] jd.red_args_types = [history.getkind(v.concretetype) for v in reds_v] jd.num_green_args = len(jd._green_args_spec) jd.num_red_args = len(jd.red_args_types) RESTYPE = graph.getreturnvar().concretetype (jd._JIT_ENTER_FUNCTYPE, jd._PTR_JIT_ENTER_FUNCTYPE) = self.cpu.ts.get_FuncType(ALLARGS, lltype.Void) (jd._PORTAL_FUNCTYPE, jd._PTR_PORTAL_FUNCTYPE) = self.cpu.ts.get_FuncType(ALLARGS, RESTYPE) # if jd.result_type == 'v': ASMRESTYPE = lltype.Void elif jd.result_type == history.INT: ASMRESTYPE = lltype.Signed elif jd.result_type == history.REF: ASMRESTYPE = llmemory.GCREF elif jd.result_type == history.FLOAT: ASMRESTYPE = lltype.Float else: assert False (_, jd._PTR_ASSEMBLER_HELPER_FUNCTYPE) = self.cpu.ts.get_FuncType( [llmemory.GCREF, llmemory.GCREF], ASMRESTYPE) def rewrite_jitcell_accesses(self): jitdrivers_by_name = {} for jd in self.jitdrivers_sd: name = jd.jitdriver.name if name != 'jitdriver': jitdrivers_by_name[name] = jd m = _find_jit_markers(self.translator.graphs, ('get_jitcell_at_key', 'trace_next_iteration', 'dont_trace_here', 'trace_next_iteration_hash')) accessors = {} def get_accessor(name, jitdriver_name, function, ARGS, green_arg_spec): a = accessors.get((name, jitdriver_name)) if a: return a d = {'function': function, 'cast_instance_to_gcref': cast_instance_to_gcref, 'lltype': lltype} arg_spec = ", ".join([("arg%d" % i) for i in range(len(ARGS))]) arg_converters = [] for i, spec in enumerate(green_arg_spec): if isinstance(spec, lltype.Ptr): arg_converters.append("arg%d = lltype.cast_opaque_ptr(type%d, arg%d)" % (i, i, i)) d['type%d' % i] = spec convert = ";".join(arg_converters) if name == 'get_jitcell_at_key': exec py.code.Source(""" def accessor(%s): %s return cast_instance_to_gcref(function(%s)) """ % (arg_spec, convert, arg_spec)).compile() in d FUNC = lltype.Ptr(lltype.FuncType(ARGS, llmemory.GCREF)) elif name == "trace_next_iteration_hash": exec py.code.Source(""" def accessor(arg0): function(arg0) """).compile() in d FUNC = lltype.Ptr(lltype.FuncType([lltype.Unsigned], lltype.Void)) else: exec py.code.Source(""" def accessor(%s): %s function(%s) """ % (arg_spec, convert, arg_spec)).compile() in d FUNC = lltype.Ptr(lltype.FuncType(ARGS, lltype.Void)) func = d['accessor'] ll_ptr = self.helper_func(FUNC, func) accessors[(name, jitdriver_name)] = ll_ptr return ll_ptr for graph, block, index in m: op = block.operations[index] jitdriver_name = op.args[1].value JitCell = jitdrivers_by_name[jitdriver_name].warmstate.JitCell ARGS = [x.concretetype for x in op.args[2:]] if op.args[0].value == 'get_jitcell_at_key': func = JitCell.get_jitcell elif op.args[0].value == 'dont_trace_here': func = JitCell.dont_trace_here elif op.args[0].value == 'trace_next_iteration_hash': func = JitCell.trace_next_iteration_hash else: func = JitCell._trace_next_iteration argspec = jitdrivers_by_name[jitdriver_name]._green_args_spec accessor = get_accessor(op.args[0].value, jitdriver_name, func, ARGS, argspec) v_result = op.result c_accessor = Constant(accessor, concretetype=lltype.Void) newop = SpaceOperation('direct_call', [c_accessor] + op.args[2:], v_result) block.operations[index] = newop def rewrite_can_enter_jits(self): sublists = {} for jd in self.jitdrivers_sd: sublists[jd.jitdriver] = jd, [] jd.no_loop_header = True # loop_headers = find_loop_headers(self.translator.graphs) for graph, block, index in loop_headers: op = block.operations[index] jitdriver = op.args[1].value assert jitdriver in sublists, \ "loop_header with no matching jit_merge_point" jd, sublist = sublists[jitdriver] jd.no_loop_header = False # can_enter_jits = find_can_enter_jit(self.translator.graphs) for graph, block, index in can_enter_jits: op = block.operations[index] jitdriver = op.args[1].value assert jitdriver in sublists, \ "can_enter_jit with no matching jit_merge_point" assert not jitdriver.autoreds, ( "can_enter_jit not supported with a jitdriver that " "has reds='auto'") jd, sublist = sublists[jitdriver] origportalgraph = jd._jit_merge_point_in if graph is not origportalgraph: sublist.append((graph, block, index)) jd.no_loop_header = False else: pass # a 'can_enter_jit' before the 'jit-merge_point', but # originally in the same function: we ignore it here # see e.g. test_jitdriver.test_simple for jd in self.jitdrivers_sd: _, sublist = sublists[jd.jitdriver] self.rewrite_can_enter_jit(jd, sublist) def rewrite_can_enter_jit(self, jd, can_enter_jits): FUNCPTR = jd._PTR_JIT_ENTER_FUNCTYPE jit_enter_fnptr = self.helper_func(FUNCPTR, jd._maybe_enter_jit_fn) if len(can_enter_jits) == 0: # see test_warmspot.test_no_loop_at_all operations = jd.portal_graph.startblock.operations op1 = operations[0] assert (op1.opname == 'jit_marker' and op1.args[0].value == 'jit_merge_point') op0 = SpaceOperation( 'jit_marker', [Constant('can_enter_jit', lltype.Void)] + op1.args[1:], None) operations.insert(0, op0) can_enter_jits = [(jd.portal_graph, jd.portal_graph.startblock, 0)] for graph, block, index in can_enter_jits: if graph is jd._jit_merge_point_in: continue op = block.operations[index] greens_v, reds_v = support.decode_hp_hint_args(op) args_v = greens_v + reds_v vlist = [Constant(jit_enter_fnptr, FUNCPTR)] + args_v v_result = Variable() v_result.concretetype = lltype.Void newop = SpaceOperation('direct_call', vlist, v_result) block.operations[index] = newop def helper_func(self, FUNCPTR, func): if not self.cpu.translate_support_code: return llhelper(FUNCPTR, func) FUNC = FUNCPTR.TO args_s = [lltype_to_annotation(ARG) for ARG in FUNC.ARGS] s_result = lltype_to_annotation(FUNC.RESULT) graph = self.annhelper.getgraph(func, args_s, s_result) return self.annhelper.graph2delayed(graph, FUNC) def rewrite_access_helpers(self): ah = find_access_helpers(self.translator.graphs) for graph, block, index in ah: op = block.operations[index] self.rewrite_access_helper(op) def create_jit_entry_points(self): for func, args, result in all_jit_entrypoints: self.helper_func(lltype.Ptr(lltype.FuncType(args, result)), func) annotated_jit_entrypoints.append((func, None)) def rewrite_access_helper(self, op): # make sure we make a copy of function so it no longer belongs # to extregistry func = op.args[1].value if func.func_name.startswith('stats_'): # get special treatment since we rewrite it to a call that accepts # jit driver assert len(op.args) >= 3, ("%r must have a first argument " "(which is None)" % (func,)) func = func_with_new_name(func, func.func_name + '_compiled') def new_func(ignored, *args): return func(self, *args) ARGS = [lltype.Void] + [arg.concretetype for arg in op.args[3:]] else: ARGS = [arg.concretetype for arg in op.args[2:]] new_func = func_with_new_name(func, func.func_name + '_compiled') RESULT = op.result.concretetype FUNCPTR = lltype.Ptr(lltype.FuncType(ARGS, RESULT)) ptr = self.helper_func(FUNCPTR, new_func) op.opname = 'direct_call' op.args = [Constant(ptr, FUNCPTR)] + op.args[2:] def rewrite_jit_merge_points(self, policy): for jd in self.jitdrivers_sd: self.rewrite_jit_merge_point(jd, policy) def rewrite_jit_merge_point(self, jd, policy): # # Mutate the original portal graph from this: # # def original_portal(..): # stuff # while 1: # jit_merge_point(*args) # more stuff # # to that: # # def original_portal(..): # stuff # return portal_runner(*args) # # def portal_runner(*args): # while 1: # try: # return portal(*args) # except JitException, e: # return handle_jitexception(e) # # def portal(*args): # while 1: # more stuff # origportalgraph = jd._jit_merge_point_in portalgraph = jd.portal_graph PORTALFUNC = jd._PORTAL_FUNCTYPE # ____________________________________________________________ # Prepare the portal_runner() helper # from rpython.jit.metainterp.warmstate import specialize_value from rpython.jit.metainterp.warmstate import unspecialize_value portal_ptr = self.cpu.ts.functionptr(PORTALFUNC, 'portal', graph=portalgraph) jd._portal_ptr = portal_ptr # portalfunc_ARGS = [] nums = {} for i, ARG in enumerate(PORTALFUNC.ARGS): kind = history.getkind(ARG) assert kind != 'void' if i < len(jd.jitdriver.greens): color = 'green' else: color = 'red' attrname = '%s_%s' % (color, kind) count = nums.get(attrname, 0) nums[attrname] = count + 1 portalfunc_ARGS.append((ARG, attrname, count)) portalfunc_ARGS = unrolling_iterable(portalfunc_ARGS) # rtyper = self.translator.rtyper RESULT = PORTALFUNC.RESULT result_kind = history.getkind(RESULT) assert result_kind.startswith(jd.result_type) ts = self.cpu.ts state = jd.warmstate maybe_compile_and_run = jd._maybe_compile_and_run_fn EnterJitAssembler = jd._EnterJitAssembler def ll_portal_runner(*args): try: # maybe enter from the function's start. maybe_compile_and_run( state.increment_function_threshold, *args) # # then run the normal portal function, i.e. the # interpreter's main loop. It might enter the jit # via maybe_enter_jit(), which typically ends with # handle_fail() being called, which raises on the # following exceptions --- catched here, because we # want to interrupt the whole interpreter loop. return support.maybe_on_top_of_llinterp(rtyper, portal_ptr)(*args) except jitexc.JitException as e: result = handle_jitexception(e) if result_kind != 'void': result = specialize_value(RESULT, result) return result def handle_jitexception(e): # XXX there are too many exceptions all around... while True: if isinstance(e, EnterJitAssembler): try: return e.execute() except jitexc.JitException as e: continue # if isinstance(e, jitexc.ContinueRunningNormally): args = () for ARGTYPE, attrname, count in portalfunc_ARGS: x = getattr(e, attrname)[count] x = specialize_value(ARGTYPE, x) args = args + (x,) try: result = support.maybe_on_top_of_llinterp(rtyper, portal_ptr)(*args) except jitexc.JitException as e: continue if result_kind != 'void': result = unspecialize_value(result) return result # if result_kind == 'void': if isinstance(e, jitexc.DoneWithThisFrameVoid): return None if result_kind == 'int': if isinstance(e, jitexc.DoneWithThisFrameInt): return e.result if result_kind == 'ref': if isinstance(e, jitexc.DoneWithThisFrameRef): return e.result if result_kind == 'float': if isinstance(e, jitexc.DoneWithThisFrameFloat): return e.result # if isinstance(e, jitexc.ExitFrameWithExceptionRef): value = ts.cast_to_baseclass(e.value) if not we_are_translated(): raise LLException(ts.get_typeptr(value), value) else: value = cast_base_ptr_to_instance(Exception, value) assert value is not None raise value # raise AssertionError("all cases should have been handled") jd._ll_portal_runner = ll_portal_runner # for debugging jd.portal_runner_ptr = self.helper_func(jd._PTR_PORTAL_FUNCTYPE, ll_portal_runner) jd.portal_runner_adr = llmemory.cast_ptr_to_adr(jd.portal_runner_ptr) jd.portal_calldescr = self.cpu.calldescrof( jd._PTR_PORTAL_FUNCTYPE.TO, jd._PTR_PORTAL_FUNCTYPE.TO.ARGS, jd._PTR_PORTAL_FUNCTYPE.TO.RESULT, EffectInfo.MOST_GENERAL) vinfo = jd.virtualizable_info def assembler_call_helper(deadframe, virtualizableref): fail_descr = self.cpu.get_latest_descr(deadframe) try: fail_descr.handle_fail(deadframe, self.metainterp_sd, jd) except jitexc.JitException as e: return handle_jitexception(e) else: assert 0, "should have raised" jd._assembler_call_helper = assembler_call_helper # for debugging jd._assembler_helper_ptr = self.helper_func( jd._PTR_ASSEMBLER_HELPER_FUNCTYPE, assembler_call_helper) jd.assembler_helper_adr = llmemory.cast_ptr_to_adr( jd._assembler_helper_ptr) if vinfo is not None: jd.vable_token_descr = vinfo.vable_token_descr def handle_jitexception_from_blackhole(bhcaller, e): result = handle_jitexception(e) if result_kind == 'void': pass elif result_kind == 'int': bhcaller._setup_return_value_i(result) elif result_kind == 'ref': bhcaller._setup_return_value_r(result) elif result_kind == 'float': bhcaller._setup_return_value_f(result) else: assert False jd.handle_jitexc_from_bh = handle_jitexception_from_blackhole # ____________________________________________________________ # Now mutate origportalgraph to end with a call to portal_runner_ptr # origblock, origindex, op = locate_jit_merge_point(origportalgraph) assert op.opname == 'jit_marker' assert op.args[0].value == 'jit_merge_point' greens_v, reds_v = support.decode_hp_hint_args(op) vlist = [Constant(jd.portal_runner_ptr, jd._PTR_PORTAL_FUNCTYPE)] vlist += greens_v vlist += reds_v v_result = Variable() v_result.concretetype = PORTALFUNC.RESULT newop = SpaceOperation('direct_call', vlist, v_result) del origblock.operations[origindex:] origblock.operations.append(newop) origblock.exitswitch = None origblock.recloseblock(Link([v_result], origportalgraph.returnblock)) # the origportal now can raise (even if it did not raise before), # which means that we cannot inline it anywhere any more, but that's # fine since any forced inlining has been done before # checkgraph(origportalgraph) def add_finish(self): def finish(): if self.metainterp_sd.profiler.initialized: self.metainterp_sd.profiler.finish() self.metainterp_sd.cpu.finish_once() if self.cpu.translate_support_code: call_final_function(self.translator, finish, annhelper=self.annhelper) def rewrite_set_param_and_get_stats(self): from rpython.rtyper.lltypesystem.rstr import STR closures = {} graphs = self.translator.graphs _, PTR_SET_PARAM_FUNCTYPE = self.cpu.ts.get_FuncType([lltype.Signed], lltype.Void) _, PTR_SET_PARAM_STR_FUNCTYPE = self.cpu.ts.get_FuncType( [lltype.Ptr(STR)], lltype.Void) def make_closure(jd, fullfuncname, is_string): if jd is None: def closure(i): if is_string: i = hlstr(i) for jd in self.jitdrivers_sd: getattr(jd.warmstate, fullfuncname)(i) else: state = jd.warmstate def closure(i): if is_string: i = hlstr(i) getattr(state, fullfuncname)(i) if is_string: TP = PTR_SET_PARAM_STR_FUNCTYPE else: TP = PTR_SET_PARAM_FUNCTYPE funcptr = self.helper_func(TP, closure) return Constant(funcptr, TP) # for graph, block, i in find_set_param(graphs): op = block.operations[i] if op.args[1].value is not None: for jd in self.jitdrivers_sd: if jd.jitdriver is op.args[1].value: break else: assert 0, "jitdriver of set_param() not found" else: jd = None funcname = op.args[2].value key = jd, funcname if key not in closures: closures[key] = make_closure(jd, 'set_param_' + funcname, funcname == 'enable_opts') op.opname = 'direct_call' op.args[:3] = [closures[key]] def rewrite_force_virtual(self, vrefinfo): all_graphs = self.translator.graphs vrefinfo.replace_force_virtual_with_call(all_graphs) def replace_force_quasiimmut_with_direct_call(self, op): ARG = op.args[0].concretetype mutatefieldname = op.args[1].value key = (ARG, mutatefieldname) if key in self._cache_force_quasiimmed_funcs: cptr = self._cache_force_quasiimmed_funcs[key] else: from rpython.jit.metainterp import quasiimmut func = quasiimmut.make_invalidation_function(ARG, mutatefieldname) FUNC = lltype.Ptr(lltype.FuncType([ARG], lltype.Void)) llptr = self.helper_func(FUNC, func) cptr = Constant(llptr, FUNC) self._cache_force_quasiimmed_funcs[key] = cptr op.opname = 'direct_call' op.args = [cptr, op.args[0]] def rewrite_force_quasi_immutable(self): self._cache_force_quasiimmed_funcs = {} graphs = self.translator.graphs for graph, block, i in find_force_quasi_immutable(graphs): self.replace_force_quasiimmut_with_direct_call(block.operations[i])