def test_fold_exitswitch_along_one_path(): def g(n): if n == 42: return 5 else: return n+1 def fn(n): if g(n) == 5: return 100 else: return 0 graph, t = get_graph(fn, [int]) from rpython.translator.backendopt import removenoops, inline inline.auto_inline_graphs(t, t.graphs, threshold=999) constant_fold_graph(graph) removenoops.remove_same_as(graph) if option.view: t.view() # check that the graph starts with a condition (which should be 'n==42') # and that if this condition is true, it goes directly to 'return 100'. assert len(graph.startblock.exits) == 2 assert graph.startblock.exits[1].exitcase == True assert graph.startblock.exits[1].target is graph.returnblock check_graph(graph, [10], 0, t) check_graph(graph, [42], 100, t)
def test_switch_on_symbolic(): symb1 = CDefinedIntSymbolic("1", 1) symb2 = CDefinedIntSymbolic("2", 2) symb3 = CDefinedIntSymbolic("3", 3) def fn(x): res = 0 if x == symb1: res += x + 1 elif x == symb2: res += x + 2 elif x == symb3: res += x + 3 res += 1 return res t = TranslationContext() a = t.buildannotator() a.build_types(fn, [int]) rtyper = t.buildrtyper() rtyper.specialize() graph = t.graphs[0] remove_same_as(graph) res = merge_if_blocks_once(graph) assert not res checkgraph(graph)
def split_graph_and_record_jitdriver(self, graph, block, pos): op = block.operations[pos] jd = JitDriverStaticData() jd._jit_merge_point_in = graph args = op.args[2:] s_binding = self.translator.annotator.binding jd._portal_args_s = [s_binding(v) for v in args] graph = copygraph(graph) [jmpp] = find_jit_merge_points([graph]) graph.startblock = support.split_before_jit_merge_point(*jmpp) # XXX this is incredibly obscure, but this is sometiems necessary # so we don't explode in checkgraph. for reasons unknown this # is not contanied within simplify_graph removenoops.remove_same_as(graph) # a crash in the following checkgraph() means that you forgot # to list some variable in greens=[] or reds=[] in JitDriver, # or that a jit_merge_point() takes a constant as an argument. checkgraph(graph) for v in graph.getargs(): assert isinstance(v, Variable) assert len(dict.fromkeys(graph.getargs())) == len(graph.getargs()) self.translator.graphs.append(graph) jd.portal_graph = graph # it's a bit unbelievable to have a portal without func assert hasattr(graph, "func") graph.func._dont_inline_ = True graph.func._jit_unroll_safe_ = True jd.jitdriver = block.operations[pos].args[1].value jd.portal_runner_ptr = "<not set so far>" jd.result_type = history.getkind(jd.portal_graph.getreturnvar() .concretetype)[0] self.jitdrivers_sd.append(jd)
def storesink_graph(graph): def clear_cache_for(cache, concretetype, fieldname): for k in cache.keys(): if k[0].concretetype == concretetype and k[1] == fieldname: del cache[k] added_some_same_as = False for block in graph.iterblocks(): newops = [] cache = {} for op in block.operations: if op.opname == 'getfield': tup = (op.args[0], op.args[1].value) res = cache.get(tup, None) if res is not None: op.opname = 'same_as' op.args = [res] added_some_same_as = True else: cache[tup] = op.result elif op.opname in ['setarrayitem', 'setinteriorfield']: pass elif op.opname == 'setfield': clear_cache_for(cache, op.args[0].concretetype, op.args[1].value) elif has_side_effects(op): cache = {} newops.append(op) if block.operations: block.operations = newops if added_some_same_as: removenoops.remove_same_as(graph)
def test_remove_same_as(): def nothing(x): return x def f(): nothing(False) if nothing(True): return 42 else: return 666 t = TranslationContext() t.buildannotator().build_types(f, []) t.buildrtyper().specialize() # now we make the 'if True' appear f_graph = graphof(t, f) simple_inline_function(t, nothing, f_graph) # here, the graph looks like v21=same_as(True); exitswitch: v21 remove_same_as(f_graph) t.checkgraphs() # only one path should be left for block in f_graph.iterblocks(): assert len(block.exits) <= 1 interp = LLInterpreter(t.rtyper) result = interp.eval_graph(f_graph, []) assert result == 42
def split_graph_and_record_jitdriver(self, graph, block, pos): op = block.operations[pos] jd = JitDriverStaticData() jd._jit_merge_point_in = graph args = op.args[2:] s_binding = self.translator.annotator.binding jd._portal_args_s = [s_binding(v) for v in args] graph = copygraph(graph) [jmpp] = find_jit_merge_points([graph]) graph.startblock = support.split_before_jit_merge_point(*jmpp) # XXX this is incredibly obscure, but this is sometiems necessary # so we don't explode in checkgraph. for reasons unknown this # is not contanied within simplify_graph removenoops.remove_same_as(graph) # a crash in the following checkgraph() means that you forgot # to list some variable in greens=[] or reds=[] in JitDriver, # or that a jit_merge_point() takes a constant as an argument. checkgraph(graph) for v in graph.getargs(): assert isinstance(v, Variable) assert len(dict.fromkeys(graph.getargs())) == len(graph.getargs()) self.translator.graphs.append(graph) jd.portal_graph = graph # it's a bit unbelievable to have a portal without func assert hasattr(graph, "func") graph.func._dont_inline_ = True graph.func._jit_unroll_safe_ = True jd.jitdriver = block.operations[pos].args[1].value jd.portal_runner_ptr = "<not set so far>" jd.result_type = history.getkind( jd.portal_graph.getreturnvar().concretetype)[0] self.jitdrivers_sd.append(jd)
def test_replace_exitswitch_by_constant_bug(): class X: pass def constant9(): x = X() x.n = 3 x.n = 9 return x.n def fn(): n = constant9() if n == 1: return 5 elif n == 2: return 6 elif n == 3: return 8 elif n == 4: return -123 elif n == 5: return 12973 else: return n t = TranslationContext() a = t.buildannotator() a.build_types(fn, []) rtyper = t.buildrtyper() rtyper.specialize() graph = t.graphs[0] remove_same_as(graph) merge_if_blocks_once(graph) from rpython.translator.backendopt import malloc, inline inline.auto_inlining(t, 20) malloc.remove_mallocs(t, t.graphs) from rpython.translator import simplify simplify.join_blocks(graph)
def test_remove_same_as_nonconst(): from rpython.rlib.nonconst import NonConstant from rpython.rtyper.lltypesystem.lloperation import llop from rpython.rtyper.lltypesystem import lltype def f(): if NonConstant(False): x = llop.same_as(lltype.Signed, 666) return 42 t = TranslationContext() t.buildannotator().build_types(f, []) t.buildrtyper().specialize() f_graph = graphof(t, f) #simple_inline_function(t, nothing, f_graph) # here, the graph looks like v21=same_as(True); exitswitch: v21 remove_same_as(f_graph) t.checkgraphs() # only one path should be left for block in f_graph.iterblocks(): assert len(block.exits) <= 1 for block in t.annotator.annotated: assert None not in block.operations interp = LLInterpreter(t.rtyper) result = interp.eval_graph(f_graph, []) assert result == 42
def test_coalesce_exitswitchs(): def g(n): return n > 5 and n < 20 def fn(n): if g(n): return 100 else: return 0 graph, t = get_graph(fn, [int]) from rpython.translator.backendopt import removenoops, inline inline.auto_inline_graphs(t, t.graphs, threshold=999) removenoops.remove_same_as(graph) constant_fold_graph(graph) if option.view: t.view() # check that the graph starts with a condition (which should be 'n > 5') # and that if this condition is false, it goes directly to 'return 0'. assert summary(graph) == {'int_gt': 1, 'int_lt': 1} assert len(graph.startblock.exits) == 2 assert graph.startblock.exits[0].exitcase == False assert graph.startblock.exits[0].target is graph.returnblock check_graph(graph, [2], 0, t) check_graph(graph, [10], 100, t) check_graph(graph, [42], 0, t)
def storesink_graph(graph): """ remove superfluous getfields. use a super-local method: all non-join blocks inherit the heap information from their (single) predecessor """ added_some_same_as = False entrymap = mkentrymap(graph) # all merge blocks are starting points todo = [(block, None, None) for (block, prev_blocks) in entrymap.iteritems() if len(prev_blocks) > 1 or block is graph.startblock] visited = 0 while todo: block, cache, inputlink = todo.pop() visited += 1 if cache is None: cache = {} if block.operations: changed_block = _storesink_block(block, cache, inputlink) added_some_same_as = changed_block or added_some_same_as for link in block.exits: if len(entrymap[link.target]) == 1: new_cache = _translate_cache(cache, link) todo.append((link.target, new_cache, link)) assert visited == len(entrymap) if added_some_same_as: removenoops.remove_same_as(graph) simplify.transform_dead_op_vars(graph)
def test_merge_several(): def merge(n, m): r = -1 if n == 0: if m == 0: r = 0 elif m == 1: r = 1 else: r = 2 elif n == 1: r = 4 else: r = 6 return r t = TranslationContext() a = t.buildannotator() a.build_types(merge, [int, int]) rtyper = t.buildrtyper() rtyper.specialize() graph = tgraphof(t, merge) remove_same_as(graph) merge_if_blocks(graph) assert len(graph.startblock.exits) == 3 assert len(list(graph.iterblocks())) == 3 interp = LLInterpreter(rtyper) for m in range(3): res = interp.eval_graph(graph, [0, m]) assert res == m res = interp.eval_graph(graph, [1, 0]) assert res == 4 res = interp.eval_graph(graph, [2, 0]) assert res == 6
def test_remove_same_as_nonconst(): from rpython.rlib.nonconst import NonConstant from rpython.rtyper.lltypesystem.lloperation import llop from rpython.rtyper.lltypesystem import lltype def f(): if NonConstant(False): x = llop.same_as(lltype.Signed, 666) return 42 t = TranslationContext() t.buildannotator().build_types(f, []) t.buildrtyper().specialize() f_graph = graphof(t, f) # simple_inline_function(t, nothing, f_graph) # here, the graph looks like v21=same_as(True); exitswitch: v21 remove_same_as(f_graph) t.checkgraphs() # only one path should be left for block in f_graph.iterblocks(): assert len(block.exits) <= 1 for block in t.annotator.annotated: assert None not in block.operations interp = LLInterpreter(t.rtyper) result = interp.eval_graph(f_graph, []) assert result == 42
def remove_obvious_noops(): for graph in graphs: removenoops.remove_same_as(graph) simplify.eliminate_empty_blocks(graph) simplify.transform_dead_op_vars(graph, translator) removenoops.remove_duplicate_casts(graph, translator) if config.print_statistics: print "after no-op removal:" print_statistics(translator.graphs[0], translator)
def check_auto_inlining( self, func, sig, multiplier=None, call_count_check=False, remove_same_as=False, heuristic=None, const_fold_first=False, ): t = self.translate(func, sig) if const_fold_first: from rpython.translator.backendopt.constfold import constant_fold_graph from rpython.translator.simplify import eliminate_empty_blocks for graph in t.graphs: constant_fold_graph(graph) eliminate_empty_blocks(graph) if option.view: t.view() # inline! sanity_check(t) # also check before inlining (so we don't blame it) threshold = INLINE_THRESHOLD_FOR_TEST if multiplier is not None: threshold *= multiplier call_count_pred = None if call_count_check: call_count_pred = lambda lbl: True instrument_inline_candidates(t.graphs, threshold) if remove_same_as: for graph in t.graphs: removenoops.remove_same_as(graph) if heuristic is not None: kwargs = {"heuristic": heuristic} else: kwargs = {} auto_inlining(t, threshold, call_count_pred=call_count_pred, **kwargs) sanity_check(t) if option.view: t.view() interp = LLInterpreter(t.rtyper) def eval_func(args): return interp.eval_graph(graphof(t, func), args) return eval_func, t
def remove_mallocs(translator, graphs=None): if graphs is None: graphs = translator.graphs tot = 0 for graph in graphs: count = remove_simple_mallocs(graph, verbose=translator.config.translation.verbose) if count: # remove typical leftovers from malloc removal removenoops.remove_same_as(graph) simplify.eliminate_empty_blocks(graph) simplify.transform_dead_op_vars(graph, translator) tot += count log.malloc("removed %d simple mallocs in total" % tot) return tot
def check_auto_inlining(self, func, sig, multiplier=None, call_count_check=False, remove_same_as=False, heuristic=None, const_fold_first=False): t = self.translate(func, sig) if const_fold_first: from rpython.translator.backendopt.constfold import constant_fold_graph from rpython.translator.simplify import eliminate_empty_blocks for graph in t.graphs: constant_fold_graph(graph) eliminate_empty_blocks(graph) if option.view: t.view() # inline! sanity_check(t) # also check before inlining (so we don't blame it) threshold = INLINE_THRESHOLD_FOR_TEST if multiplier is not None: threshold *= multiplier call_count_pred = None if call_count_check: call_count_pred = lambda lbl: True instrument_inline_candidates(t.graphs, threshold) if remove_same_as: for graph in t.graphs: removenoops.remove_same_as(graph) if heuristic is not None: kwargs = {"heuristic": heuristic} else: kwargs = {} auto_inlining(t, threshold, call_count_pred=call_count_pred, **kwargs) sanity_check(t) if option.view: t.view() interp = LLInterpreter(t.rtyper) def eval_func(args): return interp.eval_graph(graphof(t, func), args) return eval_func, t
def test_merge_if_blocks_bug(): def fn(n): if n == 1: return 5 elif n == 2: return 6 elif n == 3: return 8 elif n == 4: return -123 elif n == 5: return 12973 else: return n graph, t = get_graph(fn, [int]) from rpython.translator.backendopt.removenoops import remove_same_as from rpython.translator.backendopt import merge_if_blocks remove_same_as(graph) merge_if_blocks.merge_if_blocks_once(graph) constant_fold_graph(graph) check_graph(graph, [4], -123, t) check_graph(graph, [9], 9, t)
def test_merge_if_blocks_bug_2(): def fn(): n = llop.same_as(lltype.Signed, 66) if n == 1: return 5 elif n == 2: return 6 elif n == 3: return 8 elif n == 4: return -123 elif n == 5: return 12973 else: return n graph, t = get_graph(fn, []) from rpython.translator.backendopt.removenoops import remove_same_as from rpython.translator.backendopt import merge_if_blocks remove_same_as(graph) merge_if_blocks.merge_if_blocks_once(graph) constant_fold_graph(graph) check_graph(graph, [], 66, t)
def do_test_merge(fn, testvalues): t = TranslationContext() a = t.buildannotator() a.build_types(fn, [type(testvalues[0])]) rtyper = t.buildrtyper() rtyper.specialize() graph = tgraphof(t, fn) assert len(list(graph.iterblocks())) == 4 #startblock, blocks, returnblock remove_same_as(graph) merge_if_blocks_once(graph) assert len(graph.startblock.exits) == 4 assert len(list(graph.iterblocks())) == 2 #startblock, returnblock interp = LLInterpreter(rtyper) for i in testvalues: expected = fn(i) actual = interp.eval_graph(graph, [i]) assert actual == expected
def test_knownswitch_after_exitswitch(): def fn(n): cond = n > 10 if cond: return cond + 5 else: return cond + 17 graph, t = get_graph(fn, [int]) from rpython.translator.backendopt import removenoops removenoops.remove_same_as(graph) constant_fold_graph(graph) if option.view: t.view() assert summary(graph) == {'int_gt': 1} check_graph(graph, [2], 17, t) check_graph(graph, [42], 6, t)
def test_join_blocks_cleans_links(): from rpython.rtyper.lltypesystem import lltype from rpython.flowspace.model import Constant from rpython.translator.backendopt.removenoops import remove_same_as def f(x): return bool(x + 2) def g(x): if f(x): return 1 else: return 2 graph, t = translate(g, [int], backend_optimize=False) fgraph = graphof(t, f) fgraph.startblock.exits[0].args = [Constant(True, lltype.Bool)] # does not crash: previously join_blocks would barf on this remove_same_as(graph) backend_optimizations(t)
def check(self, f, argtypes, no_getfields=0): t = self.translate(f, argtypes) getfields = 0 graph = graphof(t, f) removenoops.remove_same_as(graph) checkgraph(graph) storesink_graph(graph) checkgraph(graph) if option.view: t.view() for block in graph.iterblocks(): for op in block.operations: if op.opname == 'getfield': getfields += 1 if no_getfields != getfields: py.test.fail("Expected %d, got %d getfields" % (no_getfields, getfields))
def test_dont_merge(): def merge(n, m): r = -1 if n == 0: r += m if n == 1: r += 2 * m else: r += 6 return r t = TranslationContext() a = t.buildannotator() a.build_types(merge, [int, int]) rtyper = t.buildrtyper() rtyper.specialize() graph = tgraphof(t, merge) remove_same_as(graph) blocknum = len(list(graph.iterblocks())) merge_if_blocks(graph) assert blocknum == len(list(graph.iterblocks()))
def test_merge_passonvars(): def merge(n, m): if n == 1: return m + 1 elif n == 2: return m + 2 elif n == 3: return m + 3 return m + 4 t = TranslationContext() a = t.buildannotator() a.build_types(merge, [int, int]) rtyper = t.buildrtyper() rtyper.specialize() graph = tgraphof(t, merge) assert len(list(graph.iterblocks())) == 8 remove_same_as(graph) merge_if_blocks_once(graph) assert len(graph.startblock.exits) == 4 interp = LLInterpreter(rtyper) for i in range(1, 5): res = interp.eval_graph(graph, [i, 1]) assert res == i + 1
def partial_escape(translator, graph): """ Main function. Blocks, which we'll work on, are in a dequeue, called "worklist", and are indexing link-state tuples in "statemap". """ insert_links(graph) worklist = deque([graph.startblock]) statemap = defaultdict(list) statemap[graph.startblock] = [(None, {})] finished = set() entrymap = mkentrymap(graph) backedges = find_backedges(graph) number_getfield_removed = 0 while worklist: block = worklist.popleft() must_be_materialized = block.is_final_block() for link in entrymap[block]: if link in backedges: must_be_materialized = True state = get_current_state(statemap[block], must_be_materialized=must_be_materialized) if block.is_final_block(): continue new_operations = [] # Going through the operations for op in block.operations: if op.opname == 'malloc': # Create new entry for every allocation that is not returned if can_remove(op): vobj = VirtualObject(op.result.concretetype, op.args) state[op.result] = vobj vobj.aliases.add(op.result) else: new_operations.append(op) elif op.opname == 'cast_pointer': if op.args[0] in state: # Creating something like an 'alias' for the casting state[op.result] = vobj = state[op.args[0]] vobj.aliases.add(op.result) else: new_operations.append(op) elif op.opname == 'setfield': if op.args[0] in state: state[op.args[0]].vars[op.args[1].value, op.args[0].concretetype] = op.args[2] else: materialize_object(op.args[2], state, new_operations) new_operations.append(op) elif op.opname == 'getfield': key = op.args[1].value, op.args[0].concretetype if op.args[0] in state and key in state[op.args[0]].vars: targ = state[op.args[0]].vars[key] number_getfield_removed += 1 if targ in state: state[op.result] = vobj = state[targ] state[targ].aliases.add(vobj) else: new_operations.append(SpaceOperation('same_as', [targ], op.result)) else: materialize_object(op.args[0], state, new_operations) new_operations.append(op) else: for arg in op.args: materialize_object(arg, state, new_operations) new_operations.append(op) # for all backedges, materialize all arguments (loops aren't supported # properly yet) for exit in block.exits: if exit in backedges or exit.target.is_final_block(): for arg in exit.args: materialize_object(arg, state, new_operations) block.operations = new_operations # We're done with the internals of the block. Editing the lists: finished.add(block) for exit in block.exits: # Only adding to the worklist if all its ancestors are processed for lnk in entrymap[exit.target]: if lnk.prevblock not in finished and lnk not in backedges: break else: if exit.target not in finished and exit.target not in worklist: # XXX worklist.append(exit.target) # setting statemaps: statemap[exit.target].append((exit, state)) if number_getfield_removed: if translator.config.translation.verbose: log.cse("partial escape analysis removed %s getfields in graph %s" % (number_getfield_removed, graph)) else: log.dot() # Done. Cleaning up. remove_same_as(graph) transform_dead_op_vars(graph) eliminate_empty_blocks(graph) join_blocks(graph) checkgraph(graph) return number_getfield_removed