Commits

Maciej Fijalkowski committed b9519f8

General progress - support inlined calls and bridges

Comments (0)

Files changed (10)

 import inspect
 from pypy.tool.logparser import parse_log_file, extract_category
 from module_finder import load_code
-from loops import (slice_debug_merge_points, parse_log_counts, NonCodeLoop,
-                   parse)
+from loops import NonCodeLoop, parse, slice_debug_merge_points, adjust_bridges
+from storage import LoopStorage
 from display import CodeRepr
 
 from pygments import highlight
 from pygments.formatters import HtmlFormatter
 
 class Server(object):
-    def __init__(self, loops):
-        self.loops = loops
+    def __init__(self, storage):
+        self.storage = storage
 
     def index(self):
-        return flask.render_template('index.html', loops=self.loops)
+        # XXX cache results possibly
+        loops = [slice_debug_merge_points(loop.operations)
+                 for loop in self.storage.loops]
+        return flask.render_template('index.html', loops=loops)
 
     def loop(self):
         no = int(flask.request.args.get('no', '1'))
-        path = [i for i in flask.request.args.get('path', '').split(',')
-                if i]
-        scroll_to = int(flask.request.args.get('scroll_to', '0'))
-        # gather all functions
-        loop = self.loops[no - 1]
-        upper_start = 0
-        for elem in path:
-            nr = int(elem)
-            if nr:
-                upper_start = loop.chunks[nr - 1].lineno # XXX what if it's
-                # also a Function? is it even possible without a bytecode
-                # in between?
-            loop = loop.chunks[nr]
+        ops = adjust_bridges(self.storage.loops[no - 1], flask.request.args)
+        loop = slice_debug_merge_points(ops)
+        path = flask.request.args.get('path', '').split(',')
+        if path:
+            up = '"' + ','.join(path[:-1]) + '"'
+        else:
+            up = '""'
+        for e in path:
+            if e:
+                loop = loop.chunks[int(e)]
         startline, endline = loop.linerange
         code = load_code(loop.filename, loop.name, loop.startlineno)
         source = inspect.getsource(code)
-        return flask.render_template('loop.html',
-                                     source=CodeRepr(source, code, loop),
-                                     startline=scroll_to or startline,
-                                     current_loop=no,
-                                     upper_path=','.join(path[:-1]),
-                                     upper_start=upper_start)
-        
+        d = {'html': flask.render_template('loop.html',
+                                           source=CodeRepr(source, code,
+                                                           loop),
+                                           current_loop=no,
+                                           upper_path=up,
+                                           show_upper_path=bool(path)),
+             'scrollto': startline}
+        return flask.jsonify(d)
+
+    # def show(self): 
+    #     no = int(flask.request.args.get('no', '1'))       
+
+    # def loop(self):
+    #     no = int(flask.request.args.get('no', '1'))
+    #     scroll_to = int(flask.request.args.get('scroll_to', '0'))
+    #     # gather all functions
+    #     loop = self.storage.loops[no - 1]
+    #             upper_start = loop.chunks[nr - 1].lineno # XXX what if it's
+    #             # also a Function? is it even possible without a bytecode
+    #             # in between?
+    #         loop = loop.chunks[nr]
+    #     startline, endline = loop.linerange
+    #     code = load_code(loop.filename, loop.name, loop.startlineno)
+    #     source = inspect.getsource(code)
+    #     return flask.render_template('loop.html',
+    #                                  source=CodeRepr(source, code, loop),
+    #                                  startline=scroll_to or startline,
+    #                                  current_loop=no,
+    #                                  upper_path=','.join(path[:-1]),
+    #                                  upper_start=upper_start)
+
 def main():
     log = parse_log_file('log')
+    storage = LoopStorage()
     #log_counts = parse_log_counts(open('log.count').readlines())
-    loops = [parse(l) for l in
-             extract_category(log, "jit-log-opt-")]
-    parsed = []
-    for loop in loops:
-        try:
-            parsed.append(slice_debug_merge_points(loop))
-        except NonCodeLoop:
-            pass
+    storage.reconnect_loops([parse(l) for l in
+                             extract_category(log, "jit-log-opt-")])
     app = flask.Flask(__name__)
-    server = Server(parsed)
+    server = Server(storage)
     app.debug = True
     app.route('/')(server.index)
     app.route('/loop')(server.loop)
 
         last_lineno = -1
         for chunk in loop.chunks:
-            if isinstance(chunk, Bytecode):
+            if chunk.is_bytecode:
                 no = chunk.lineno
                 if no < last_lineno:
                     no = last_lineno
-[838b10c650a9] {jit-log-opt-loop
+[34290ea893e] {jit-log-opt-loop
 # Loop 0 : loop with 21 ops
 [p0, p1, p2, p3, i4, p5, p6, i7]
 debug_merge_point('<code object f, file 'source.py', line 2> #9 LOAD_FAST')
 guard_false(i19, descr=<Guard3>) [p1, p0, p2, p3, i11, None]
 debug_merge_point('<code object f, file 'source.py', line 2> #9 LOAD_FAST')
 jump(p0, p1, p2, p3, 9, ConstPtr(ptr22), ConstPtr(ptr23), i11, descr=<Loop0>)
-[838b10cb7479] jit-log-opt-loop}
-[838b10f675c9] {jit-log-opt-loop
+[34290f159b2] jit-log-opt-loop}
+[342912842da] {jit-log-opt-loop
 # Loop 1 : loop with 60 ops
 [p0, p1, p2, p3, i4, p5, p6, p7]
 debug_merge_point('<code object inlined_call, file 'source.py', line 12> #9 LOAD_FAST')
 p49 = new_with_vtable(ConstClass(W_IntObject))
 setfield_gc(p49, i37, descr=<SignedFieldDescr pypy.objspace.std.intobject.W_IntObject.inst_intval 8>)
 jump(p0, p1, p2, p3, 9, ConstPtr(ptr51), ConstPtr(ptr52), p49, descr=<Loop1>)
-[838b10fac96f] jit-log-opt-loop}
+[342912dbd2a] jit-log-opt-loop}
+[342915070fe] {jit-log-opt-loop
+# Loop 2 : loop with 43 ops
+[p0, p1, p2, p3, i4, p5, p6, i7, i8]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #15 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #18 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #21 COMPARE_OP')
+i10 = int_lt(i8, 3000)
+guard_true(i10, descr=<Guard17>) [p1, p0, p2, p3, i8, i7]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #24 JUMP_IF_FALSE')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #27 POP_TOP')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #28 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #31 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #34 BINARY_MODULO')
+i12 = int_add(i8, 2147483647)
+i14 = int_and(i12, 2)
+i15 = int_mod(i8, 2)
+i16 = int_xor(i8, 2)
+i18 = int_le(i16, 0)
+i19 = int_is_true(i15)
+i20 = int_and(i18, i19)
+i21 = int_mul(i20, 2)
+i22 = int_add(i15, i21)
+debug_merge_point('<code object bridge, file 'source.py', line 19> #35 JUMP_IF_FALSE')
+i23 = int_is_true(i22)
+guard_false(i23, descr=<Guard18>) [p1, p0, p2, p3, i22, i8, i7]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #52 POP_TOP')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #53 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #56 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #59 INPLACE_ADD')
+i25 = int_add_ovf(i7, 2)
+guard_no_overflow(, descr=<Guard19>) [p1, p0, i25, p2, p3, None, i8, i7]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #60 STORE_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #63 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #66 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #69 INPLACE_ADD')
+i28 = int_add(i8, 1)
+debug_merge_point('<code object bridge, file 'source.py', line 19> #70 STORE_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #73 JUMP_ABSOLUTE')
+i30 = getfield_raw(151937600, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+i32 = int_add(i30, 1)
+setfield_raw(151937600, i32, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+i35 = int_and(i32, 1089470464)
+i36 = int_is_true(i35)
+guard_false(i36, descr=<Guard20>) [p1, p0, p2, p3, i25, i28, None, None, None]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #15 LOAD_FAST')
+jump(p0, p1, p2, p3, 15, ConstPtr(ptr38), ConstPtr(ptr39), i25, i28, descr=<Loop2>)
+[3429153345a] jit-log-opt-loop}
+[3429172a1aa] {jit-log-opt-loop
+# Loop 3 : entry bridge with 50 ops
+[p0, p1, p2, p3, i4, p5, i6, p7, p8, p9, p10]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #15 LOAD_FAST')
+guard_value(i4, 0, descr=<Guard21>) [i4, p1, p0, p2, p3, p5, i6, p7, p8, p9, p10]
+guard_nonnull_class(p10, ConstClass(W_IntObject), descr=<Guard22>) [p1, p0, p10, p2, p3, p5, p7, p8, p9]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #18 LOAD_CONST')
+guard_value(p2, ConstPtr(ptr13), descr=<Guard23>) [p1, p0, p2, p3, p5, p10, p8, p9]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #21 COMPARE_OP')
+i14 = getfield_gc_pure(p10, descr=<SignedFieldDescr pypy.objspace.std.intobject.W_IntObject.inst_intval 8>)
+i16 = int_lt(i14, 3000)
+guard_true(i16, descr=<Guard24>) [p1, p0, p10, p3, p5, p9]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #24 JUMP_IF_FALSE')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #27 POP_TOP')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #28 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #31 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #34 BINARY_MODULO')
+i18 = int_add(i14, 2147483647)
+i20 = int_and(i18, 2)
+i21 = int_mod(i14, 2)
+i22 = int_xor(i14, 2)
+i24 = int_le(i22, 0)
+i25 = int_is_true(i21)
+i26 = int_and(i24, i25)
+i27 = int_mul(i26, 2)
+i28 = int_add(i21, i27)
+debug_merge_point('<code object bridge, file 'source.py', line 19> #35 JUMP_IF_FALSE')
+i29 = int_is_true(i28)
+guard_true(i29, descr=<Guard25>) [p1, p0, p3, p5, p9, p10, i28]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #38 POP_TOP')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #39 LOAD_FAST')
+guard_nonnull_class(p9, ConstClass(W_IntObject), descr=<Guard26>) [p1, p0, p9, p3, p5, p10, None]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #42 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #45 INPLACE_ADD')
+i32 = getfield_gc_pure(p9, descr=<SignedFieldDescr pypy.objspace.std.intobject.W_IntObject.inst_intval 8>)
+i34 = int_add_ovf(i32, 1)
+guard_no_overflow(, descr=<Guard27>) [p1, p0, p9, i34, p3, p5, p10, None]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #46 STORE_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #49 JUMP_FORWARD')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #63 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #66 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #69 INPLACE_ADD')
+i36 = int_add(i14, 1)
+debug_merge_point('<code object bridge, file 'source.py', line 19> #70 STORE_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #73 JUMP_ABSOLUTE')
+i38 = getfield_raw(151937600, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+i40 = int_add(i38, 1)
+setfield_raw(151937600, i40, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+i43 = int_and(i40, 1089470464)
+i44 = int_is_true(i43)
+guard_false(i44, descr=<Guard28>) [p1, p0, p3, p5, i34, i36, None]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #15 LOAD_FAST')
+jump(p0, p1, p3, p5, 15, ConstPtr(ptr46), ConstPtr(ptr47), i34, i36, descr=<Loop2>)
+[3429176185a] jit-log-opt-loop}
+[34291dbe9ee] {jit-log-opt-bridge
+# bridge out of Guard 18 with 23 ops
+[p0, p1, p2, p3, i4, i5, i6]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #38 POP_TOP')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #39 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #42 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #45 INPLACE_ADD')
+i8 = int_add_ovf(i6, 1)
+guard_no_overflow(, descr=<Guard29>) [p0, p1, i8, p2, p3, i5, i6]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #46 STORE_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #49 JUMP_FORWARD')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #63 LOAD_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #66 LOAD_CONST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #69 INPLACE_ADD')
+i10 = int_add_ovf(i5, 1)
+guard_no_overflow(, descr=<Guard30>) [p0, p1, i10, p2, p3, i8, i5, None]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #70 STORE_FAST')
+debug_merge_point('<code object bridge, file 'source.py', line 19> #73 JUMP_ABSOLUTE')
+i13 = getfield_raw(151937600, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+i15 = int_add(i13, 1)
+setfield_raw(151937600, i15, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+i18 = int_and(i15, 1089470464)
+i19 = int_is_true(i18)
+guard_false(i19, descr=<Guard31>) [p0, p1, p2, p3, i10, i8, None, None]
+debug_merge_point('<code object bridge, file 'source.py', line 19> #15 LOAD_FAST')
+jump(p1, p0, p2, p3, 15, ConstPtr(ptr21), ConstPtr(ptr22), i8, i10, descr=<Loop2>)
+[34291dd9dde] jit-log-opt-bridge}
     return f
 
 class Op(object):
+    bridge = None
+    
     def __init__(self, name, args, res, descr):
         self.name = name
         self.args = args
         self.res = res
         self.descr = descr
+        self._is_guard = name.startswith('guard_')
+        if self._is_guard:
+            self.guard_no = int(self.descr[len('<Guard'):-1])
 
     def setfailargs(self, _):
         pass
     def html_repr(self):
         return getattr(self, 'repr_' + self.name, self.generic_repr)()
 
+    def is_guard(self):
+        return self._is_guard
+
     for bin_op, name in [('<', 'int_lt'),
                          ('>', 'int_gt'),
                          ('<=', 'int_le'),
     is_bytecode = False
     
     def __init__(self, chunks, path):
+        self.path = path
         self.chunks = chunks
-        self.path = path
         for chunk in self.chunks:
             if chunk.filename is not None:
                 self.startlineno = chunk.startlineno
             minline = sys.maxint
             maxline = -1
             for chunk in self.chunks:
-                if isinstance(chunk, Bytecode) and chunk.filename is not None:
+                if chunk.is_bytecode and chunk.filename is not None:
                     lineno = chunk.lineno
                     minline = min(minline, lineno)
                     maxline = max(maxline, lineno)
                 print >>out, "  ", source
             chunk.pretty_print(out)
 
-def slice_debug_merge_points(loop):
+def parse_log_counts(lines):
+    for line in lines:
+        pass
+
+def parse(input):
+    return SimpleParser(input, None, {}, 'lltype', None,
+                        nonstrict=True).parse()
+
+def slice_debug_merge_points(operations):
+    """ Slice given operation list into a chain of Bytecode chunks.
+    Also detect inlined functions and make them Function
+    """
     stack = []
 
     def getpath(stack):
-        return ','.join([str(len(v)) for (k, v) in stack])
-    
+        return ",".join([str(len(v)) for _, v in stack])
+
     def append_to_res(bc):
         if not stack:
             stack.append((bc.key(), []))
 
     so_far = []
     stack = []
-    for op in loop.operations:
+    for op in operations:
         if op.name == 'debug_merge_point':
             if so_far:
                 append_to_res(Bytecode(so_far))
     if so_far:
         append_to_res(Bytecode(so_far))
     # wrap stack back up
+    if not stack:
+        # no ops whatsoever
+        return Function([], getpath(stack))
     while True:
         _, next = stack.pop()
         if not stack:
             return Function(next, getpath(stack))
         stack[-1][1].append(Function(next, getpath(stack)))
 
-def parse_log_counts(lines):
-    for line in lines:
-        pass
-
-def parse(input):
-    return SimpleParser(input, None, {}, 'lltype', None,
-                        nonstrict=True).parse()
-
-if __name__ == '__main__':
-    # XXX kill probably
-    from pypy.tool.logparser import parse_log_file, extract_category
-
-    log = parse_log_file('log')
-    loops = [parse(l) for l in
-             extract_category(log, "jit-log-opt-")]
-    loops = [slice_debug_merge_points(loop) for loop in loops]
-    for i, loop in enumerate(loops):
-        loop.pretty_print(sys.stdout)
+def adjust_bridges(loop, bridges):
+    """ Slice given loop according to given bridges to follow. Returns a plain
+    list of operations.
+    """
+    ops = loop.operations
+    res = []
+    i = 0
+    while i < len(ops):
+        op = ops[i]
+        if op.is_guard() and bridges.get('loop-' + str(op.guard_no), None):
+            res.append(op)
+            i = 0
+            ops = op.bridge.operations
+        else:
+            res.append(op)
+            i += 1
+    return res
 
-function select_loop(no)
+var glob_bridge_state = {};
+
+function show_loop(no, path)
 {
-    $.get('/loop?no=' + no, undefined, function(arg) {
-        $("#main").html(arg);
-        $("#main").focus();
+    glob_bridge_state.no = no;
+    if (path) {
+        glob_bridge_state.path = path;
+    } else {
+        delete glob_bridge_state.path;
+    }
+    $.getJSON('/loop', glob_bridge_state, function(arg) {
+        $('#main').html(arg.html).ready(function() {
+            $.scrollTo($('#line-' + arg.scrollto), 200);
+        });
+    });
+}
+
+function document_ready()
+{
+    var l = window.location.search.substr(1).split('&');
+    for (var s in l) {
+        var l2 = l[s].split('=', 2);
+        var name = l2[0];
+        var val = l2[1];
+        if (name == 'show_loop') {
+            show_loop(val);
+        }
+    }
+}
+
+function replace_from(elem, bridge_id)
+{
+    if (glob_bridge_state['loop-' + bridge_id]) {
+        delete glob_bridge_state['loop-' + bridge_id];
+    } else {
+        glob_bridge_state['loop-' + bridge_id] = true;
+    }
+    $.getJSON('/loop', glob_bridge_state, function(res) {
+        $('#main').html(res.html).ready(function() {
+            for (var v in glob_bridge_state) {
+                if (v.search('loop-') != -1) {
+                    if (glob_bridge_state[v]) {
+                        $('#' + v).next().html('&lt;&lt;hide bridge');
+                    } else {
+                        $('#' + v).next().html('&gt;&gt;show bridge');
+                    }
+                }
+            }
+            $.scrollTo($("#loop-" + bridge_id));
+        });
     });
 }
+
+""" This file represents a storage mechanism that let us invent unique names
+for all loops and bridges, so http requests can refer to them by name
+"""
+
+from loops import Function, Bytecode
+
+class LoopStorage(object):
+    def __init__(self):
+        self.loops = None
+        self.functions = {}
+
+    def reconnect_loops(self, loops):
+        """ Re-connect loops in a way that entry bridges are filtered out
+        and normal bridges are associated with guards. Returning list of
+        normal loops.
+        """
+        res = []
+        guard_dict = {}
+        for loop_no, loop in enumerate(loops):
+            for op in loop.operations:
+                if op.name.startswith('guard_'):
+                    guard_dict[int(op.descr[len('<Guard'):-1])] = op
+        for loop in loops:
+            if loop.comment:
+                comment = loop.comment.strip()
+                if 'entry bridge' in comment:
+                    continue
+                elif comment.startswith('# bridge out of'):
+                    no = int(comment[len('# bridge out of Guard '):].split(' ', 1)[0])
+                    guard_dict[no].bridge = loop
+                    loop.no = no
+                    continue
+            res.append(loop)
+        self.loops = res
+        return res

templates/index.html

   <link rel="stylesheet" type="text/css" href="/static/style.css"/>
   <link rel="stylesheet" type="text/css" href="/static/pygments.css"/>
   <script src="/static/jquery.min.js"></script>
+  <script src="/static/jquery.scrollTo-1.4.2-min.js"></script>
   <script src="/static/script.js"></script>
+  <script>
+    $(document).ready(document_ready);
+  </script>
 </head>
 <body>
   <div id="loops">
     <ul>
       {% for item in loops %}
-      <li><a href="loop?no={{loop.index}}">{{item.repr()}}</a></li>
+      <li><a href="#" onClick="show_loop({{loop.index}})">{{item.repr()}}</a></li>
       {% endfor %}
     </ul>
   </div>
   <div id="main">
-     Main
+    Loop placeholder
   </div>
 </body>
 </html>

templates/loop.html

-<html>
-<head>
-  <link rel="stylesheet" type="text/css" href="/static/style.css"/>
-  <link rel="stylesheet" type="text/css" href="/static/pygments.css"/>
-  <script src="/static/jquery.min.js"></script>
-  <script src="/static/jquery.scrollTo-1.4.2-min.js"></script>
-  <script src="/static/script.js"></script>
-  <script>
-    $(document).ready(function() {
-      $.scrollTo($("#line-{{startline}}"), 200);
-    });
-  </script>
-</head>
-<body>
-  <!-- header -->
-  <a href="loop?no={{current_loop}}&path={{upper_path}}&scroll_to={{upper_start}}">Up</a>
-  <!-- end of header -->
-  {% for sourceline in source.lines %}
-     {% if sourceline.in_loop %}
-         <div id="line-{{loop.index + source.firstlineno - 1}}" class="source visible">{{sourceline.line}}</div>
-         {% if sourceline.chunks %}
-            <div class="operations">
-                {% for chunk in sourceline.chunks %}
-                   {% if chunk.is_bytecode %}
-                     <span class="dmp">{{chunk.html_repr() + "\n"}}</span>
-                     {% for op in chunk.operations[1:] %}
-                        <span class="single-operation {{op.extra_style()}}">{{(op.html_repr() + "\n")}}</span>
-                     {% endfor %}
-                   {% else %}
-                     <a class="inlined_call" href="loop?no={{current_loop}}&path={{chunk.path}}">{{(chunk.html_repr())|safe}}</a><br/>
-                   {% endif %}
-                {% endfor %}
-            </div>
-         {% endif %}
-     {% else %}
-         <div id="line-{{loop.index + source.firstlineno - 1}}" class="source hidden">{{sourceline.line}}</div>
-     {% endif %}
-  {% endfor %}
-</body>
+{% if show_upper_path %}
+   <a href="#" onClick="show_loop({{current_loop}}, {{upper_path}})">&lt;-- Up</a>
+{% endif %}
+{% for sourceline in source.lines %}
+   {% if sourceline.in_loop %}
+       <div id="line-{{loop.index + source.firstlineno - 1}}" class="source visible">{{sourceline.line}}</div>
+       {% if sourceline.chunks %}
+          <div class="operations">
+              {% for chunk in sourceline.chunks %}
+                 {% if chunk.is_bytecode %}
+                   <span class="dmp">{{chunk.html_repr()}}</span><br/>
+                   {% for op in chunk.operations[1:] %}
+                      {% if op.bridge %}
+                        <span id="loop-{{op.bridge.no}}" class="guard single-operation">{{op.html_repr()}}</span> <a href="#" onClick="replace_from(this, {{op.bridge.no}})">&gt;&gt;show bridge</a><br/>
+                      {% else %}
+                        <span class="single-operation {{op.extra_style()}}">{{op.html_repr()}}</span><br/>
+                      {% endif %}
+                   {% endfor %}
+                 {% else %}
+                   <a class="inlined_call" onClick="show_loop({{current_loop}}, {{chunk.path}})" href="#">{{(chunk.html_repr())|safe}}</a><br/>
+                 {% endif %}
+              {% endfor %}
+          </div>
+       {% endif %}
+   {% else %}
+       <div id="line-{{loop.index + source.firstlineno - 1}}" class="source hidden">{{sourceline.line}}</div>
+   {% endif %}
+{% endfor %}

test/test_display.py

     pass
 
 class MockChunk(object):
+    is_bytecode = True
+    
     def __init__(self, operations, lineno):
         self.operations = operations
         self.lineno = lineno
 
+class Op(object):
+    bridge = None
+    
+    def __init__(self, a):
+        self.a = a
+
 SOURCE = """def f():
 return a + b
 """
 
 def test_code_repr():
     loop = MockLoop()
-    loop.chunks = [MockChunk([], 3), MockChunk(['a', 'b', 'c'], 4),
-                   MockChunk(['a', 'b'], 4)]
+    loop.chunks = [MockChunk([], 3), MockChunk([Op('a'), Op('b'), Op('c')], 4),
+                   MockChunk([Op('a'), Op('b')], 4)]
     MockLoop.linerange = (4, 5)
     code = MockCode()
     code.co_firstlineno = 3
     assert not repr.lines[0].in_loop
     assert repr.lines[0].chunks == [loop.chunks[0]]
     assert repr.lines[1].chunks == [loop.chunks[1], loop.chunks[2]]
+
+def test_code_repr_bridgestate():
+    pass

test/test_loops.py

 
 from pypy.jit.metainterp.resoperation import ResOperation, rop
 from pypy.jit.metainterp.history import ConstInt, Const
-from loops import slice_debug_merge_points, parse_log_counts, parse, Bytecode,\
-     Function
+from loops import parse, Bytecode, Function, slice_debug_merge_points,\
+     adjust_bridges
 import py
+from storage import LoopStorage
 
 def test_parse():
     ops = parse('''
     debug_merge_point("<code object stuff, file '/tmp/x.py', line 200> #11 SUB")
     i2 = int_add(i1, 1)
     ''')
-    res = slice_debug_merge_points(ops)
+    res = slice_debug_merge_points(ops.operations)
     assert len(res.chunks) == 3
     assert len(res.chunks[0].operations) == 1
     assert len(res.chunks[1].operations) == 2
     debug_merge_point('<code object inner, file 'source.py', line 9> #7 RETURN_VALUE')
     debug_merge_point('<code object inlined_call, file 'source.py', line 12> #31 STORE_FAST')
     """)
-    res = slice_debug_merge_points(ops)
+    res = slice_debug_merge_points(ops.operations)
     assert len(res.chunks) == 3 # two chunks + inlined call
     assert isinstance(res.chunks[0], Bytecode)
     assert isinstance(res.chunks[1], Function)
     assert isinstance(res.chunks[2], Bytecode)
+    assert res.chunks[1].path == "1"
     assert len(res.chunks[1].chunks) == 3
     
 def test_name():
     debug_merge_point("<code object stuff, file '/tmp/x.py', line 202> #11 SUB")
     i2 = int_add(i1, 1)
     ''')
-    res = slice_debug_merge_points(ops)
+    res = slice_debug_merge_points(ops.operations)
     assert res.repr() == res.chunks[0].repr()
     assert res.repr() == "stuff, file '/tmp/x.py', line 200"
     assert res.startlineno == 200
     debug_merge_point("<code object stuff, file '/tmp/x.py', line 202> #11 SUB")
     i2 = int_add(i1, 1)
     ''')
-    res = slice_debug_merge_points(ops)
+    res = slice_debug_merge_points(ops.operations)
     assert res.repr() == res.chunks[1].repr()
 
 
     debug_merge_point("<code object f, file '%(fname)s', line 2> #6 BINARY_ADD")
     debug_merge_point("<code object f, file '%(fname)s', line 2> #7 RETURN_VALUE")
     ''' % locals())
-    res = slice_debug_merge_points(ops)
+    res = slice_debug_merge_points(ops.operations)
     assert res.chunks[1].lineno == 3
 
 def test_linerange():
     debug_merge_point("<code object f, file '%(fname)s', line 5> #22 LOAD_CONST")
     debug_merge_point("<code object f, file '%(fname)s', line 5> #6 SETUP_LOOP")
     ''' % locals())
-    res = slice_debug_merge_points(ops)
+    res = slice_debug_merge_points(ops.operations)
     assert res.linerange == (7, 8)
+
+def test_reassign_loops():
+    main = parse('''
+    [v0]
+    guard_false(v0, descr=<Guard18>) []
+    ''')
+    bridge = parse('''
+    # bridge out of Guard 18 with 13 ops
+    [i0, i1]
+    int_add(i0, i1)
+    ''')
+    entry_bridge = parse('''
+    # Loop 3 : entry bridge
+    []
+    ''')
+    loops = LoopStorage().reconnect_loops([main, bridge, entry_bridge])
+    assert len(loops) == 1
+    assert len(loops[0].operations[0].bridge.operations) == 1
+    assert loops[0].operations[0].bridge.no == 18
+
+def test_adjust_bridges():
+    main = parse('''
+    [v0]
+    guard_false(v0, descr=<Guard13>)
+    guard_true(v0, descr=<Guard5>)
+    ''')
+    bridge = parse('''
+    # bridge out of Guard 13
+    []
+    int_add(0, 1)
+    ''')
+    loops = LoopStorage().reconnect_loops([main, bridge])
+    assert adjust_bridges(main, {})[1].name == 'guard_true'
+    assert adjust_bridges(main, {'loop-13': True})[1].name == 'int_add'