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 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 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 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 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 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 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 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 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 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_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 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 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 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 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_regalloc_call(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 block = Block([v1]) block.operations = [ SpaceOperation('int_add', [v1, Constant(1, lltype.Signed)], v2), 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] -> %i0 int_return %i0 """)
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 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 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 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]
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 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]
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_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 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 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 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 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 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 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 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 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 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 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 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.canraise: 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(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 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 newvar(self): v = Variable(self.name) v.concretetype = self.TYPE return v
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()
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 _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
def varoftype(concretetype, name=None): var = Variable(name) var.concretetype = concretetype return var
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)
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()
def merge(link_state_tuples, orig_must_be_materialized=False): """ Function that actually merges states. Objects also get materialized here. """ new_state = {} link_state_tuples = link_state_tuples[:] # copy so we can mutate it # We'll have one of the links in a proper variable to "guide" the algorithm # around it. We'll iterate thru the array to mirror the changes to the rest sample_link, sample_state = link_state_tuples.pop() inputargs = sample_link.target.inputargs # the big problem here is aliasing. there can be several variables that # store the same vobj. this mapping must be the same across all links # to keep track, use the following dict, mapping # vobj -> (vobj1, ..., vobjn) # for each vobj from all the incoming links passed_vobjs = {} # map vobj in sample_state -> vobj in new_state merged_vobjs = {} inputargsindex = 0 while inputargsindex < len(sample_link.args): sample_obj = sample_link.args[inputargsindex] sample_vobj = sample_state.get(sample_obj, None) targ = inputargs[inputargsindex] must_be_materialized = orig_must_be_materialized if sample_vobj is None: must_be_materialized = True # set the flag accordingly instead of a huge if clause if not must_be_materialized: vobj_list = [sample_vobj] for lnk, state in link_state_tuples: obj = lnk.args[inputargsindex] vobj = state.get(obj) if vobj is None: must_be_materialized = True break if not sample_vobj.identical_malloc_args(vobj): must_be_materialized = True vobj_list.append(vobj) else: # all the same! check aliasing for vobj in vobj_list: if vobj in passed_vobjs: if passed_vobjs[vobj] != vobj_list: # different aliasing! too bad must_be_materialized = True else: passed_vobjs[vobj] = vobj_list if must_be_materialized: # We can't merge! materialize all objects! changed = materialize_object(sample_obj, sample_state, sample_link.prevblock.operations) for lnk, state in link_state_tuples: changed = materialize_object( lnk.args[inputargsindex], state, lnk.prevblock.operations) or changed if changed: # we forced something! that can have all kinds of weird effects # if the virtual has already been passed to the target block # earlier. therefore, we restart. inputargsindex = 0 new_state.clear() passed_vobjs.clear() merged_vobjs.clear() continue else: # We can merge: objects are virtual and classes are the same new_vobj = merged_vobjs.get(sample_vobj) if new_vobj is None: new_vobj = VirtualObject(sample_vobj.concretetype, sample_vobj.malloc_args) merged_vobjs[sample_vobj] = new_vobj for key, v in sample_vobj.vars.iteritems(): m = Variable() m.concretetype = v.concretetype inputargs.insert((inputargsindex + 1), m) sample_link.args.insert((inputargsindex + 1), v) for lnk, state in link_state_tuples: vo = state[lnk.args[inputargsindex]] try: newarg = vo.vars[key] except KeyError: # uninitialized field! newarg = Constant( lltype.nullptr(v.concretetype.TO), v.concretetype) lnk.args.insert((inputargsindex + 1), newarg) new_vobj.vars[key] = m new_state[targ] = new_vobj new_vobj.aliases.add(targ) inputargsindex += 1 # safety check: size of state can only shrink vobjset = set(new_state.values()) for _, state in link_state_tuples: assert len(vobjset) <= len(set(state.values())) return new_state