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 instrument_inline_candidates(graphs, threshold): cache = {None: False} def candidate(graph): try: return cache[graph] except KeyError: res = static_instruction_count(graph) <= threshold cache[graph] = res return res n = 0 for parentgraph in graphs: for block in parentgraph.iterblocks(): ops = block.operations i = len(ops) - 1 while i >= 0: op = ops[i] i -= 1 if op.opname == "direct_call": funcobj = op.args[0].value._obj graph = getattr(funcobj, 'graph', None) if graph is not None: if getattr(getattr(funcobj, '_callable', None), '_dont_inline_', False): continue if candidate(graph): tag = Constant('inline', Void) label = Constant(n, Signed) dummy = Variable() dummy.concretetype = Void count = SpaceOperation('instrument_count', [tag, label], dummy) ops.insert(i + 1, count) n += 1 log.inlining("%d call sites instrumented" % n)
def test_SSA_to_SSI(): c = Variable('c') x = Variable('x') y = Variable('y') b1 = Block([c]) b2 = Block([x]) b3 = Block([]) graph = FunctionGraph('x', b1) b2.operations.append(SpaceOperation('add', [x, c], y)) b2.exitswitch = y b1.closeblock(Link([Constant(0)], b2)) b2.closeblock(Link([y], b2), Link([], b3)) b3.closeblock(Link([y, c], graph.exceptblock)) SSA_to_SSI(graph) assert len(b1.inputargs) == 1 assert len(b2.inputargs) == 2 assert len(b3.inputargs) == 2 assert b2.inputargs == b2.operations[0].args assert len(b1.exits[0].args) == 2 assert b1.exits[0].args[1] is c assert len(b2.exits[0].args) == 2 assert b2.exits[0].args == [y, b2.inputargs[1]] assert len(b2.exits[1].args) == 2 assert len(b3.exits[0].args) == 2 index = b3.inputargs.index(b3.exits[0].args[0]) assert b2.exits[1].args[index] is b2.operations[0].result index = b3.inputargs.index(b3.exits[0].args[1]) assert b2.exits[1].args[index] is b2.inputargs[1]
def test_generalize_string_concat(annotator): hlop = op.add(Variable(), Variable()) s_str = SomeString(can_be_None=True) s_value, s_exc = annotate_op(annotator, hlop, [s_None, s_str]) s_value2, s_exc2 = annotate_op(annotator, hlop, [s_str, s_str]) assert contains_s(s_value2, s_value) assert contains_s(s_exc2, s_exc)
def test_func_simple(): # -------------------- flowgraph building -------------------- # def f(x): # return x+1 x = Variable("x") x.concretetype = Signed result = Variable("result") result.concretetype = Signed one = Constant(1) one.concretetype = Signed op = SpaceOperation("int_add", [x, one], result) block = Block([x]) graph = FunctionGraph("f", block) block.operations.append(op) block.closeblock(Link([result], graph.returnblock)) graph.getreturnvar().concretetype = Signed # -------------------- end -------------------- F = FuncType([Signed], Signed) f = functionptr(F, "f", graph=graph) db = LowLevelDatabase() db.get(f) db.complete() dump_on_stdout(db) S = GcStruct('testing', ('fptr', Ptr(F))) s = malloc(S) s.fptr = f db = LowLevelDatabase() db.get(s) db.complete() dump_on_stdout(db)
def builder(translator, func): # build a hacked graph that doesn't take a *arg any more, but # individual extra arguments graph = translator.buildflowgraph(func) argnames, vararg, kwarg = graph.signature assert vararg, "graph should have a *arg at this point" assert not kwarg, "where does this **arg come from??" argscopy = [Variable(v) for v in graph.getargs()] starargs = [ Variable('stararg%d' % i) for i in range(nb_extra_args) ] newstartblock = Block(argscopy[:-1] + starargs) newtup = op.newtuple(*starargs) newtup.result = argscopy[-1] newstartblock.operations.append(newtup) newstartblock.closeblock(Link(argscopy, graph.startblock)) graph.startblock = newstartblock argnames = argnames + ['.star%d' % i for i in range(nb_extra_args)] graph.signature = Signature(argnames) # note that we can mostly ignore defaults: if nb_extra_args > 0, # then defaults aren't applied. if nb_extra_args == 0, then this # just removes the *arg and the defaults keep their meaning. if nb_extra_args > 0: graph.defaults = None # shouldn't be used in this case checkgraph(graph) return graph
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 test_optimize_goto_if_not__incoming(): v1 = Variable() v1.concretetype = lltype.Bool block = Block([v1]) block.exitswitch = v1 block.exits = [FakeLink(False), FakeLink(True)] assert not Transformer().optimize_goto_if_not(block)
def test_generalize_getitem_string(annotator): hlop = op.getitem(Variable(), Variable()) s_int = SomeInteger() s_str = SomeString(can_be_None=True) s_value, s_exc = annotate_op(annotator, hlop, [s_None, s_int]) s_value2, s_exc2 = annotate_op(annotator, hlop, [s_str, s_int]) assert contains_s(s_value2, s_value) assert contains_s(s_exc2, s_exc)
def test_optimize_goto_if_not__unknownop(): v3 = Variable() v3.concretetype = lltype.Bool block = Block([]) block.operations = [SpaceOperation("foobar", [], v3)] block.exitswitch = v3 block.exits = [FakeLink(False), FakeLink(True)] assert not Transformer().optimize_goto_if_not(block)
def copyvar(annotator, v): """Make a copy of the Variable v, preserving annotations and concretetype.""" assert isinstance(v, Variable) newvar = Variable(v) if annotator is not None and v in annotator.bindings: annotator.transfer_binding(newvar, v) if hasattr(v, 'concretetype'): newvar.concretetype = v.concretetype return newvar
def test_getitem_dict(annotator): bk = annotator.bookkeeper hlop = op.getitem(Variable(), Variable()) with bk.at_position(None): s_dict = bk.newdict() s_dict.dictdef.generalize_key(SomeString()) s_dict.dictdef.generalize_value(SomeInteger()) s_result, _ = annotate_op(annotator, hlop, [s_dict, SomeString()]) assert s_result == SomeInteger()
def test_generalize_getitem_list(annotator): bk = annotator.bookkeeper hlop = op.getitem(Variable(), Variable()) s_int = SomeInteger() with bk.at_position(None): s_empty_list = bk.newlist() s_value, s_exc = annotate_op(annotator, hlop, [s_None, s_int]) s_value2, s_exc2 = annotate_op(annotator, hlop, [s_empty_list, s_int]) assert contains_s(s_value2, s_value) assert contains_s(s_exc2, s_exc)
def test_decode_builtin_call_method(): A = lltype.GcArray(lltype.Signed) def myfoobar(a, i, marker, c): assert marker == 'mymarker' return a[i] * ord(c) myfoobar.oopspec = 'spam.foobar(a, 2, c, i)' TYPE = lltype.FuncType( [lltype.Ptr(A), lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed myarray = lltype.malloc(A, 10) myarray[5] = 42 op = SpaceOperation( 'direct_call', [newconst(fnobj), newconst(myarray), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'spam.foobar' assert opargs == [newconst(myarray), newconst(2), vc, vi]
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) try: for block in graph.iterblocks(): self.transform_block(block, is_borrowed) except GCTransformError as e: e.args = ('[function %s]: %s' % (graph.name, e.message),) raise 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 _translate_arg(arg): if isinstance(arg, Variable): res = local_versions.get(arg, None) if res is None: res = Variable(arg) res.concretetype = arg.concretetype link.args.append(arg) block.inputargs.append(res) local_versions[arg] = res return res else: return arg
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 flowin(self, block, count, vars, newvarsmap): # in this 'block', follow where the 'var' goes to and replace # it by a flattened-out family of variables. This family is given # by newvarsmap, whose keys are the 'flatnames'. def list_newvars(): return [newvarsmap[key] for key in self.flatnames] assert block.operations != () self.newops = [] for op in block.operations: for arg in op.args[1:]: # should be the first arg only assert arg not in vars if op.args and op.args[0] in vars: self.flowin_op(op, vars, newvarsmap) elif op.result in vars: assert op.opname == self.MALLOC_OP progress = True # drop the "malloc" operation newvarsmap = self.flatconstants.copy() # zero initial values # if there are substructures, they are now individually # malloc'ed in an exploded way. (They will typically be # removed again by the next malloc removal pass.) for key in self.needsubmallocs: v = Variable() v.concretetype = self.newvarstype[key] c = Constant(v.concretetype.TO, lltype.Void) if c.value == op.args[0].value: progress = False # replacing a malloc with # the same malloc! newop = self.recreate_malloc(c, v) self.newops.append(newop) newvarsmap[key] = v count[0] += progress else: self.newops.append(op) assert block.exitswitch not in vars for link in block.exits: appended = False newargs = [] for arg in link.args: if arg in vars: if not appended: newargs += list_newvars() appended = True else: newargs.append(arg) link.args[:] = newargs block.operations[:] = self.newops
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(self.translator.annotator, 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(self.translator.annotator, 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 test_optimize_goto_if_not__ptr_iszero(): for opname in ["ptr_iszero", "ptr_nonzero"]: v1 = Variable() v3 = Variable() v3.concretetype = lltype.Bool block = Block([v1]) block.operations = [SpaceOperation(opname, [v1], v3)] block.exitswitch = v3 block.exits = exits = [FakeLink(False), FakeLink(True)] res = Transformer().optimize_goto_if_not(block) assert res == True assert block.operations == [] assert block.exitswitch == (opname, v1, "-live-before") assert block.exits == exits
def test_regalloc_exitswitch_2(self): v1 = Variable(); v1.concretetype = rclass.CLASSTYPE v2 = Variable(); v2.concretetype = rclass.CLASSTYPE v3 = Variable(); v3.concretetype = rclass.CLASSTYPE v4 = Variable(); v4.concretetype = rclass.CLASSTYPE block = Block([]) block.operations = [ SpaceOperation('res_call', [], v1), SpaceOperation('-live-', [], None), ] graph = FunctionGraph('f', block, v4) exclink = Link([v2], graph.returnblock) exclink.llexitcase = 123 # normally an exception class exclink.last_exception = v2 exclink.last_exc_value = "unused" block.exitswitch = c_last_exception block.closeblock(Link([v1], graph.returnblock), exclink) # self.check_assembler(graph, """ res_call -> %i0 -live- catch_exception L1 int_return %i0 --- L1: goto_if_exception_mismatch $123, L2 last_exception -> %i0 int_return %i0 --- L2: reraise """)
def test_rename_on_links(): v1 = Variable() v2 = Variable() v2.concretetype = llmemory.Address v3 = Variable() block = Block([v1]) block.operations = [SpaceOperation("cast_pointer", [v1], v2)] block2 = Block([v3]) block.closeblock(Link([v2], block2)) Transformer().optimize_block(block) assert block.inputargs == [v1] assert block.operations == [] assert block.exits[0].target is block2 assert block.exits[0].args == [v1]
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 simplify_exceptions(graph): """The exception handling caused by non-implicit exceptions starts with an exitswitch on Exception, followed by a lengthy chain of is_/issubtype tests. We collapse them all into the block's single list of exits. """ renaming = {} for block in graph.iterblocks(): if not (block.canraise and block.exits[-1].exitcase is Exception): continue covered = [link.exitcase for link in block.exits[1:-1]] seen = [] preserve = list(block.exits[:-1]) exc = block.exits[-1] last_exception = exc.last_exception last_exc_value = exc.last_exc_value query = exc.target switches = [] # collect the targets while len(query.exits) == 2: newrenaming = {} for lprev, ltarg in zip(exc.args, query.inputargs): newrenaming[ltarg] = lprev.replace(renaming) op = query.operations[0] if not (op.opname in ("is_", "issubtype") and op.args[0].replace(newrenaming) == last_exception): break renaming.update(newrenaming) case = query.operations[0].args[-1].value assert issubclass(case, py.builtin.BaseException) lno, lyes = query.exits assert lno.exitcase == False and lyes.exitcase == True if case not in seen: is_covered = False for cov in covered: if issubclass(case, cov): is_covered = True break if not is_covered: switches.append((case, lyes)) seen.append(case) exc = lno query = exc.target if Exception not in seen: switches.append((Exception, exc)) # construct the block's new exits exits = [] for case, oldlink in switches: link = oldlink.replace(renaming) assert case is not None link.last_exception = last_exception link.last_exc_value = last_exc_value # make the above two variables unique renaming2 = {} for v in link.getextravars(): renaming2[v] = Variable(v) link = link.replace(renaming2) link.exitcase = case exits.append(link) block.recloseblock(*(preserve + exits))
def test_funny_links(): from rpython.flowspace.model import Block, FunctionGraph, \ Variable, Constant, Link from rpython.flowspace.operation import op for i in range(2): v_i = Variable("i") block = Block([v_i]) g = FunctionGraph("is_one", block) op1 = op.eq(v_i, Constant(1)) block.operations.append(op1) block.exitswitch = op1.result tlink = Link([Constant(1)], g.returnblock, True) flink = Link([Constant(0)], g.returnblock, False) links = [tlink, flink] if i: links.reverse() block.closeblock(*links) t = TranslationContext() a = t.buildannotator() a.build_graph_types(g, [annmodel.SomeInteger()]) rtyper = t.buildrtyper() rtyper.specialize() interp = LLInterpreter(rtyper) assert interp.eval_graph(g, [1]) == 1 assert interp.eval_graph(g, [0]) == 0
def test_optimize_goto_if_not(): v1 = Variable() v2 = Variable() v3 = Variable() v3.concretetype = lltype.Bool sp1 = SpaceOperation("foobar", [], None) sp2 = SpaceOperation("foobaz", [], None) block = Block([v1, v2]) block.operations = [sp1, SpaceOperation("int_gt", [v1, v2], v3), sp2] block.exitswitch = v3 block.exits = exits = [FakeLink(False), FakeLink(True)] res = Transformer().optimize_goto_if_not(block) assert res == True assert block.operations == [sp1, sp2] assert block.exitswitch == ("int_gt", v1, v2) assert block.exits == exits
def copy(self): "Make a copy of this state in which all Variables are fresh." newstate = [] for w in self.mergeable: if isinstance(w, Variable): w = Variable(w) newstate.append(w) return FrameState(newstate, self.blocklist, self.next_offset)
def test_is_pure(): from rpython.flowspace.model import Variable, Constant assert llop.bool_not.is_pure([Variable()]) assert llop.debug_assert.is_pure([Variable()]) assert not llop.int_add_ovf.is_pure([Variable(), Variable()]) # S1 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed)) v_s1 = Variable() v_s1.concretetype = lltype.Ptr(S1) assert not llop.setfield.is_pure([v_s1, Constant('x'), Variable()]) assert not llop.getfield.is_pure([v_s1, Constant('y')]) # A1 = lltype.GcArray(lltype.Signed) v_a1 = Variable() v_a1.concretetype = lltype.Ptr(A1) assert not llop.setarrayitem.is_pure([v_a1, Variable(), Variable()]) assert not llop.getarrayitem.is_pure([v_a1, Variable()]) assert llop.getarraysize.is_pure([v_a1]) # S2 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed), hints={'immutable': True}) v_s2 = Variable() v_s2.concretetype = lltype.Ptr(S2) assert not llop.setfield.is_pure([v_s2, Constant('x'), Variable()]) assert llop.getfield.is_pure([v_s2, Constant('y')]) # A2 = lltype.GcArray(lltype.Signed, hints={'immutable': True}) v_a2 = Variable() v_a2.concretetype = lltype.Ptr(A2) assert not llop.setarrayitem.is_pure([v_a2, Variable(), Variable()]) assert llop.getarrayitem.is_pure([v_a2, Variable()]) assert llop.getarraysize.is_pure([v_a2]) # for kind in [rclass.IR_MUTABLE, rclass.IR_IMMUTABLE, rclass.IR_IMMUTABLE_ARRAY, rclass.IR_QUASIIMMUTABLE, rclass.IR_QUASIIMMUTABLE_ARRAY]: accessor = rclass.FieldListAccessor() S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed), hints={'immutable_fields': accessor}) accessor.initialize(S3, {'x': kind}) v_s3 = Variable() v_s3.concretetype = lltype.Ptr(S3) assert not llop.setfield.is_pure([v_s3, Constant('x'), Variable()]) assert not llop.setfield.is_pure([v_s3, Constant('y'), Variable()]) assert llop.getfield.is_pure([v_s3, Constant('x')]) is kind assert not llop.getfield.is_pure([v_s3, Constant('y')])
def _insert_reads(block, varnames): assert len(varnames) == len(block.inputargs) v_entry1 = Variable('entry') for i, name in enumerate(varnames): hlop = op.getattr(v_entry1, const(name)) hlop.result = block.inputargs[i] block.operations.insert(i, hlop) block.inputargs = [v_entry1]
def insert_ll_stackcheck(translator): from rpython.translator.backendopt.support import find_calls_from from rpython.rlib.rstack import stack_check from rpython.tool.algo.graphlib import Edge, make_edge_dict, break_cycles_v rtyper = translator.rtyper graph = rtyper.annotate_helper(stack_check, []) rtyper.specialize_more_blocks() stack_check_ptr = rtyper.getcallable(graph) stack_check_ptr_const = Constant(stack_check_ptr, lltype.typeOf(stack_check_ptr)) edges = set() insert_in = set() block2graph = {} for caller in translator.graphs: pyobj = getattr(caller, 'func', None) if pyobj is not None: if getattr(pyobj, '_dont_insert_stackcheck_', False): continue for block, callee in find_calls_from(translator, caller): if getattr(getattr(callee, 'func', None), 'insert_stack_check_here', False): insert_in.add(callee.startblock) block2graph[callee.startblock] = callee continue if block is not caller.startblock: edges.add((caller.startblock, block)) block2graph[caller.startblock] = caller edges.add((block, callee.startblock)) block2graph[block] = caller edgelist = [Edge(block1, block2) for (block1, block2) in edges] edgedict = make_edge_dict(edgelist) for block in break_cycles_v(edgedict, edgedict): insert_in.add(block) for block in insert_in: v = Variable() v.concretetype = lltype.Void unwind_op = SpaceOperation('direct_call', [stack_check_ptr_const], v) block.operations.insert(0, unwind_op) # prevents cycles of tail calls from occurring -- such cycles would # not consume any stack, so would turn into potentially infinite loops graph = block2graph[block] graph.inhibit_tail_call = True return len(insert_in)
def test_SSA_to_SSI_2(): x = Variable('x') y = Variable('y') z = Variable('z') b1 = Block([x]) b2 = Block([y]) b3 = Block([]) b3.operations.append(SpaceOperation('hello', [y], z)) b1.closeblock(Link([x], b2), Link([], b3)) graph = FunctionGraph('x', b1) SSA_to_SSI(graph) assert b1.inputargs == [x] assert b2.inputargs == [y] assert b3.inputargs == [b3.operations[0].args[0]] assert b1.exits[0].args == [x] assert b1.exits[1].args == [x]
def test_optimize_goto_if_not__exit(): # this case occurs in practice, e.g. with RPython code like: # return bool(p) and p.somefield > 0 v1 = Variable() v2 = Variable() v3 = Variable() v3.concretetype = lltype.Bool block = Block([v1, v2]) block.operations = [SpaceOperation("int_gt", [v1, v2], v3)] block.exitswitch = v3 block.exits = exits = [FakeLink(False), FakeLink(True)] block.exits[1].args = [v3] res = Transformer().optimize_goto_if_not(block) assert res == True assert block.operations == [] assert block.exitswitch == ("int_gt", v1, v2) assert block.exits == exits assert exits[1].args == [const(True)]
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 _copy(v): from rpython.flowspace.flowcontext import FlowSignal if isinstance(v, Variable): return Variable(v) elif isinstance(v, FlowSignal): vars = [_copy(var) for var in v.args] return v.rebuild(*vars) else: return v
def genop(self, opname, args_v, resulttype=None): try: for v in args_v: v.concretetype except AttributeError: raise AssertionError("wrong level! you must call hop.inputargs()" " and pass its result to genop()," " never hop.args_v directly.") vresult = Variable() self.append(SpaceOperation(opname, args_v, vresult)) if resulttype is None: vresult.concretetype = Void return None else: if isinstance(resulttype, Repr): resulttype = resulttype.lowleveltype assert isinstance(resulttype, LowLevelType) vresult.concretetype = resulttype return vresult
def materialize_object(obj_key, state, ops): """ Accepts a VirtualState object and creates the required operations, for its materialization/initialization. XXX: Edits ops in-place """ if obj_key not in state: return False # We're gonna delete the object from the state dict first (since it has # escaped) for correct recursion reasons in case of cyclic dependency. # this needs to be done with all the aliases of the object! vo = state[obj_key] # Thus, we'll make a copy first. assert obj_key in vo.aliases for key in vo.aliases: del state[key] # Starting assembling the operations. Creation and required castings: newvar = Variable() newvar.concretetype = vo.concretetype ops.append(SpaceOperation('malloc', vo.malloc_args, newvar)) # recreate the aliases for var in vo.aliases: if var.concretetype != vo.concretetype: ops.append(SpaceOperation('cast_pointer', [newvar], var)) else: ops.append(SpaceOperation('same_as', [newvar], var)) # Initialization for (key, concretetype), value in vo.vars.items(): if concretetype != vo.concretetype: # we need a cast_pointer v = Variable() v.concretetype = concretetype op = SpaceOperation('cast_pointer', [newvar], v) ops.append(op) target = v else: target = newvar # What if the assigned is a virtual object? Recursion: materialize_object(value, state, ops) m = Variable() m.concretetype = lltype.Void ops.append(SpaceOperation('setfield', [target, Constant(key, lltype.Void), value], m)) return True
def __init__(self, func, code): from rpython.flowspace.flowcontext import SpamBlock locals = [None] * code.co_nlocals for i in range(code.formalargcount): locals[i] = Variable(code.co_varnames[i]) state = FrameState(locals, [], None, [], 0) initialblock = SpamBlock(state) super(PyGraph, self).__init__(self._sanitize_funcname(func), initialblock) self.func = func self.signature = code.signature self.defaults = func.__defaults__ or ()
def generic_exception_matching(self, afterblock, copiedexceptblock): #XXXXX don't look: insert blocks that do exception matching #for the cases where direct matching did not work exc_match = Constant( self.translator.rtyper.exceptiondata.fn_exception_match) exc_match.concretetype = typeOf(exc_match.value) blocks = [] for i, link in enumerate(afterblock.exits[1:]): etype = copiedexceptblock.inputargs[0].copy() evalue = copiedexceptblock.inputargs[1].copy() passon_vars = self.passon_vars(i) block = Block([etype, evalue] + passon_vars) res = Variable() res.concretetype = Bool cexitcase = Constant(link.llexitcase) cexitcase.concretetype = typeOf(cexitcase.value) args = [exc_match, etype, cexitcase] block.operations.append(SpaceOperation("direct_call", args, res)) block.exitswitch = res linkargs = self.find_args_in_exceptional_case(link, link.target, etype, evalue, afterblock, passon_vars) l = Link(linkargs, link.target) l.prevblock = block l.exitcase = True l.llexitcase = True block.closeblock(l) if i > 0: l = Link(blocks[-1].inputargs, block) l.exitcase = False l.llexitcase = False blocks[-1].recloseblock(l, *blocks[-1].exits) blocks.append(block) blocks[-1].recloseblock(*blocks[-1].exits[:1]) blocks[-1].operations = [] blocks[-1].exitswitch = None blocks[-1].exits[0].exitcase = None del blocks[-1].exits[0].llexitcase linkargs = copiedexceptblock.inputargs copiedexceptblock.recloseblock(Link(linkargs, blocks[0]))
def generic_exception_matching(self, afterblock, copiedexceptblock): #XXXXX don't look: insert blocks that do exception matching #for the cases where direct matching did not work exc_match = Constant( self.translator.rtyper.exceptiondata.fn_exception_match) exc_match.concretetype = typeOf(exc_match.value) blocks = [] for i, link in enumerate(afterblock.exits[1:]): etype = copyvar(None, copiedexceptblock.inputargs[0]) evalue = copyvar(None, copiedexceptblock.inputargs[1]) passon_vars = self.passon_vars(i) block = Block([etype, evalue] + passon_vars) res = Variable() res.concretetype = Bool cexitcase = Constant(link.llexitcase) cexitcase.concretetype = typeOf(cexitcase.value) args = [exc_match, etype, cexitcase] block.operations.append(SpaceOperation("direct_call", args, res)) block.exitswitch = res linkargs = self.find_args_in_exceptional_case(link, link.target, etype, evalue, afterblock, passon_vars) l = Link(linkargs, link.target) l.prevblock = block l.exitcase = True l.llexitcase = True block.closeblock(l) if i > 0: l = Link(blocks[-1].inputargs, block) l.exitcase = False l.llexitcase = False blocks[-1].recloseblock(l, *blocks[-1].exits) blocks.append(block) blocks[-1].recloseblock(*blocks[-1].exits[:1]) blocks[-1].operations = [] blocks[-1].exitswitch = None blocks[-1].exits[0].exitcase = None del blocks[-1].exits[0].llexitcase linkargs = copiedexceptblock.inputargs copiedexceptblock.recloseblock(Link(linkargs, blocks[0]))
def test_regalloc_lists(self): v1 = Variable(); v1.concretetype = lltype.Signed v2 = Variable(); v2.concretetype = lltype.Signed v3 = Variable(); v3.concretetype = lltype.Signed v4 = Variable(); v4.concretetype = lltype.Signed v5 = Variable(); v5.concretetype = lltype.Signed block = Block([v1]) block.operations = [ SpaceOperation('int_add', [v1, Constant(1, lltype.Signed)], v2), SpaceOperation('rescall', [ListOfKind('int', [v1, v2])], v5), SpaceOperation('rescall', [ListOfKind('int', [v1, v2])], v3), ] graph = FunctionGraph('f', block, v4) block.closeblock(Link([v3], graph.returnblock)) # self.check_assembler(graph, """ int_add %i0, $1 -> %i1 rescall I[%i0, %i1] -> %i2 rescall I[%i0, %i1] -> %i0 int_return %i0 """)
def guessexception(self, ctx, *cases): block = self.crnt_block links = [] for case in [None] + list(cases): if case is not None: if case is Exception: last_exc = Variable('last_exception') else: last_exc = Constant(case) last_exc_value = Variable('last_exc_value') vars = [last_exc, last_exc_value] vars2 = [Variable(), Variable()] else: vars = [] vars2 = [] egg = EggBlock(vars2, block, case) ctx.pendingblocks.append(egg) link = Link(vars, egg, case) if case is not None: link.extravars(last_exception=last_exc, last_exc_value=last_exc_value) egg.extravars(last_exception=last_exc)
def test_decode_builtin_call_method(): A = lltype.GcArray(lltype.Signed) def myfoobar(a, i, marker, c): assert marker == 'mymarker' return a[i] * ord(c) myfoobar.oopspec = 'spam.foobar(a, 2, c, i)' TYPE = lltype.FuncType([lltype.Ptr(A), lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed myarray = lltype.malloc(A, 10) myarray[5] = 42 op = SpaceOperation('direct_call', [newconst(fnobj), newconst(myarray), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'spam.foobar' assert opargs == [newconst(myarray), newconst(2), vc, vi]
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 l = Link([error_constant(graph)], graph.returnblock) block.recloseblock(l)
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 __init__(self, func, code): from rpython.flowspace.flowcontext import SpamBlock locals = [None] * code.co_nlocals for i in range(code.formalargcount): locals[i] = Variable(code.co_varnames[i]) state = FrameState(locals, [], None, [], 0) initialblock = SpamBlock(state) unsafe = False if func.func_doc and func.func_doc.lstrip().startswith('UNSAFE'): unsafe = True super(PyGraph, self).__init__(self._sanitize_funcname(func), initialblock, unsafe=unsafe) self.func = func self.signature = code.signature self.defaults = func.func_defaults or ()
class HLOperation(SpaceOperation): __metaclass__ = HLOperationMeta pure = False can_overflow = False dispatch = None # number of arguments to dispatch on # (None means special handling) def __init__(self, *args): self.args = list(args) self.result = Variable() self.offset = -1 def replace(self, mapping): newargs = [arg.replace(mapping) for arg in self.args] newresult = self.result.replace(mapping) newop = type(self)(*newargs) newop.result = newresult newop.offset = self.offset return newop @classmethod def make_sc(cls): def sc_operator(ctx, *args_w): return cls(*args_w).eval(ctx) return sc_operator def eval(self, ctx): result = self.constfold() if result is not None: return result return ctx.do_op(self) def constfold(self): return None def consider(self, annotator): args_s = [annotator.annotation(arg) for arg in self.args] spec = type(self).get_specialization(*args_s) return spec(annotator, *self.args) def get_can_only_throw(self, annotator): return None def get_transformer(self, *args_s): return lambda *args: None def transform(self, annotator): args_s = [annotator.annotation(arg) for arg in self.args] transformer = self.get_transformer(*args_s) return transformer(annotator, *self.args)
def prepare_constant_fold_link(link, constants, splitblocks): block = link.target if not block.operations: # when the target block has no operation, there is nothing we can do # except trying to fold an exitswitch if block.exitswitch is not None and block.exitswitch in constants: llexitvalue = constants[block.exitswitch].value rewire_link_for_known_exitswitch(link, llexitvalue) return folded_count = fold_op_list(block.operations, constants, exit_early=True) n = len(block.operations) if block.exitswitch == c_last_exception: n -= 1 # is the next, non-folded operation an indirect_call? if folded_count < n: nextop = block.operations[folded_count] if nextop.opname == 'indirect_call' and nextop.args[0] in constants: # indirect_call -> direct_call callargs = [constants[nextop.args[0]]] constants1 = constants.copy() complete_constants(link, constants1) for v in nextop.args[1:-1]: callargs.append(constants1.get(v, v)) v_result = Variable(nextop.result) v_result.concretetype = nextop.result.concretetype constants[nextop.result] = v_result callop = SpaceOperation('direct_call', callargs, v_result) newblock = insert_empty_block(None, link, [callop]) [link] = newblock.exits assert link.target is block folded_count += 1 if folded_count > 0: splits = splitblocks.setdefault(block, []) splits.append((folded_count, link, constants))
def test_decode_builtin_call_nomethod(): def myfoobar(i, marker, c): assert marker == 'mymarker' return i * ord(c) myfoobar.oopspec = 'foobar(2, c, i)' TYPE = lltype.FuncType([lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed op = SpaceOperation('direct_call', [newconst(fnobj), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'foobar' assert opargs == [newconst(2), vc, vi]
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:
def _try_inline_malloc(self, info): """Try to inline the mallocs creation and manipulation of the Variables in the given LifeTime.""" # the values must be only ever created by a "malloc" lltypes = set() for cp in info.creationpoints: if cp[0] != "op": return False op = cp[2] if not self.check_malloc(op): return False if not self.inline_type(op.args[0].value): return False lltypes.add(op.result.concretetype) # there must be a single largest malloced GcStruct; # all variables can point to it or to initial substructures if len(lltypes) != 1: return False concretetype, = lltypes STRUCT = self.get_STRUCT(concretetype) # must be only ever accessed via getfield/setfield/getsubstruct/ # direct_fieldptr, or touched by ptr_iszero/ptr_nonzero. # Note that same_as and cast_pointer are not recorded in usepoints. self.accessed_substructs = set() for up in info.usepoints: if up[0] != "op": return False kind, node, op, index = up if index != 0: return False if op.opname in self.CHECK_ARRAY_INDEX: if not isinstance(op.args[1], Constant): return False # non-constant array index if op.opname in self.FIELD_ACCESS: pass # ok elif op.opname in self.SUBSTRUCT_ACCESS: self.do_substruct_access(op) else: return False # must not remove mallocs of structures that have a RTTI with a destructor if self.RTTI_dtor(STRUCT): return False # must not remove unions inlined as the only field of a GcStruct if self.union_wrapper(STRUCT): return False # success: replace each variable with a family of variables (one per field) # 'flatnames' is a list of (STRUCTTYPE, fieldname_in_that_struct) that # describes the list of variables that should replace the single # malloc'ed pointer variable that we are about to remove. For primitive # or pointer fields, the new corresponding variable just stores the # actual value. For substructures, if pointers to them are "equivalent" # to pointers to the parent structure (see equivalent_substruct()) then # they are just merged, and flatnames will also list the fields within # that substructure. Other substructures are replaced by a single new # variable which is a pointer to a GcStruct-wrapper; each is malloc'ed # individually, in an exploded way. (The next malloc removal pass will # get rid of them again, in the typical case.) self.flatnames = [] self.flatconstants = {} self.needsubmallocs = [] self.newvarstype = {} # map {item-of-flatnames: concretetype} self.direct_fieldptr_key = {} self.flatten(STRUCT) assert len(self.direct_fieldptr_key) <= 1 variables_by_block = {} for block, var in info.variables: vars = variables_by_block.setdefault(block, set()) vars.add(var) count = [0] for block, vars in variables_by_block.items(): # look for variables arriving from outside the block newvarsmap = None newinputargs = [] inputvars = set() for var in block.inputargs: if var in vars: inputvars.add(var) if newvarsmap is None: newvarsmap = {} for key in self.flatnames: newvar = Variable() newvar.concretetype = self.newvarstype[key] newvarsmap[key] = newvar newinputargs.append(newvar) else: newinputargs.append(var) block.inputargs[:] = newinputargs if inputvars: self.flowin(block, count, inputvars, newvarsmap) # look for variables created inside the block by a malloc vars_created_here = [] for op in block.operations: if self.check_malloc(op) and op.result in vars: vars_created_here.append(op.result) for var in vars_created_here: self.flowin(block, count, {var}, newvarsmap=None) return count[0]
def varoftype(concretetype, name=None): var = Variable(name) var.concretetype = concretetype return var
def newvar(self): v = Variable(self.name) v.concretetype = self.TYPE return v