From 9883e7e6e4a4dfc797c2e998f628ceecbf2b1ff2 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 10:14:46 -0500 Subject: [PATCH 01/11] crude experimental UI built using Clutter and GTK --- Makefile | 3 + browser/main.py | 503 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 506 insertions(+) create mode 100644 browser/main.py diff --git a/Makefile b/Makefile index 8a8205f0..c4824bf8 100644 --- a/Makefile +++ b/Makefile @@ -194,6 +194,9 @@ debug: plugin demo: plugin $(INVOCATION_ENV_VARS) $(srcdir)./gcc-with-cpychecker -c $(PYTHON_INCLUDES) demo.c +browser-demo: plugin + $(INVOCATION_ENV_VARS) $(srcdir)./gcc-with-python browser/main.py -c $(PYTHON_INCLUDES) demo.c + json-examples: plugin $(INVOCATION_ENV_VARS) $(srcdir)./gcc-with-cpychecker -I/usr/include/python2.7 -c libcpychecker/html/test/example1/bug.c diff --git a/browser/main.py b/browser/main.py new file mode 100644 index 00000000..f9b3fbce --- /dev/null +++ b/browser/main.py @@ -0,0 +1,503 @@ +# Copyright 2013 David Malcolm +# Copyright 2013 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# . + +import re +import sys +sys.argv = [] + +from gi.repository import GtkClutter +GtkClutter.init(sys.argv) +from gi.repository import Clutter, GObject, Gtk, Gdk, Cogl + +#print(dir(Clutter)) +#print(dir(GtkClutter)) + +import gcc +from gccutils import get_src_for_loc, cfg_to_dot, invoke_dot + +DEBUG_LAYOUT = 1 + +def random_color(): + from random import randint + r = randint(64, 255) + g = randint(64, 255) + b = randint(64, 255) + a = 255 + return Clutter.Color().new(r, g, b, a) + +def make_text(txt): + #print(txt) + actor = Clutter.Text.new() + actor.set_text(txt) + actor.set_font_name('monospace') + #actor.set_size(50, 50) + #print(actor.get_position()) + #print('text size: %s' % (actor.get_size(),)) + if 0: # DEBUG_LAYOUT: + actor.set_background_color(random_color()) + return actor + +def make_grid(parent, width, height): + color = Clutter.Color().new(0, 0, 0, 16) + for x in range(0, width, 100): + for y in range(0, height, 100): + a = make_text('(%i, %i)' % (x, y)) + a.set_background_color(color) + a.set_position(x, y) + parent.add_actor(a) + +#def get_layout(graph): + +# Graphviz expresses width/height in inches, whereas we have pixels: +#INCHES_PER_PIXEL = 1/256. +#INCHES_PER_PIXEL = 1 +INCHES_PER_PIXEL = 1/72. + +def split_attrs(attrs): + attrvals = {} + for clause in attrs.split(', '): + m = re.match('(\S+)=(.+)', clause) + attrname, value = m.groups() + attrvals[attrname] = value + return attrvals + +def iter_node_attrs(dot): + """ + Yield a sequence of (nodename, attrdict) pairs + """ + for line in dot.splitlines(): + # e.g. + # bb1 [width="0.17361", height="0.069444", fixedsize=true, pos="206,3"]; + # print line + m = re.match('\s*([a-z0-9]+) \[(.*)\];', line) + if m: + #print m.groups() + nodename, attrs = m.groups() + if nodename.startswith('bb'): # FIXME + attrvals = split_attrs(attrs) + yield nodename, split_attrs(attrs) + else: + # print 'unmatched' + pass + +def iter_edge_attrs(dot): + """ + Yield a sequence of (edgename, attrdict) pairs + """ + for line in dot.splitlines(): + # e.g. + # bb2 -> bb3 [pos="e,241.6,387.23 396.72,521.99 380,511.51 361.13,498.86 345,486 308.97,457.28 271.58,419.24 248.55,394.69"]; + print line + m = re.match('\s*([a-z0-9]+) -> ([a-z0-9]+) \[(.*)\];', line) + if m: + print m.groups() + srcnodename, dstnodename, attrs = m.groups() + yield srcnodename, dstnodename, split_attrs(attrs) + else: + # print 'unmatched' + pass + +class GraphView(GtkClutter.Embed): + def __init__(self, fun): + GtkClutter.Embed.__init__(self) + + stage = self.get_stage() + + if DEBUG_LAYOUT: + make_grid(stage, 1024, 768) + + test = Clutter.Text.new() + test.set_text('hello world') + stage.add_actor(test) + test.set_position(200, 5) + + """ + transition = Clutter.PropertyTransition.new('opacity') + transition.set_duration(1000) + transition.set_repeat_count(-1) # forever + transition.set_auto_reverse(True) + transition.set_loop(True) + #transition.set_from(255) + #transition.set_to(0) + #print(dir(transition)) + # from/to don't do anything + test.add_transition("animate-opacity", transition) + transition.start() + """ + + self.fun = fun + self.stage = stage + self.actor_for_node = {} + self.actor_for_edge = {} + self.actors_for_nodes = [] + self.nodes = [] + self.edge_by_node_pair = {} + + self.selected_nodes = set() + + for bb in fun.cfg.basic_blocks: + self.nodes.append(bb) + bbactor = BBActor(self, fun, bb) + self.actor_for_node[bb] = bbactor + stage.add_actor(bbactor) + bbactor.set_position(bb.index * 150, bb.index * 75) + self.actors_for_nodes.append(bbactor) + + for outedge in bb.succs: + self.edge_by_node_pair[(outedge.src, outedge.dest)] = outedge + edgeactor = EdgeActor(self, outedge) + stage.add_actor(edgeactor) + self.actor_for_edge[outedge] = edgeactor + + self._generate_layout() + + stage.set_scale(100, 5) + # ^^ doesn't seem to do anything + + def _make_dot(self): + result = 'digraph %s {\n' % 'test' + result += ' node [shape=box];\n' + for bbactor in self.actors_for_nodes: + w, h = bbactor.get_size() + # Express width and height in graphviz "inches", which we'll + result += (' bb%i [width=%f, height=%f, fixedsize=true];\n' + % (bbactor.bb.index, w * INCHES_PER_PIXEL, h * INCHES_PER_PIXEL)) + for outedge in bbactor.bb.succs: + result += (' bb%i -> bb%i;\n' + % (outedge.src.index, outedge.dest.index)) + #result += self._edges_to_dot(ctxt) + result += '}\n' + return result + + def _generate_layout(self): + dot = self._make_dot() + print(dot) + from gccutils import invoke_dot + #invoke_dot(dot) + + from subprocess import Popen, PIPE + p = Popen(['dot', '-Tdot'], + stdin=PIPE, stdout=PIPE, stderr=PIPE) + out, err = p.communicate(dot.encode('ascii')) + print(out) + + # Locate bounding box + bb_w = None + bb_h = None + for line in out.splitlines(): + # e.g. + # graph [bb="0,0,1328,630"]; + m = re.match('\s+graph \[bb="([0-9]+),([0-9]+),([0-9]+),([0-9]+)"\];', line) + if m: + print m.groups() + bb_w = int(m.group(3)) + bb_h = int(m.group(4)) + assert bb_w + assert bb_h + + self.stage.set_size(bb_w, bb_h) + + for nodename, attrdict in iter_node_attrs(out): + print(nodename, attrdict) + assert nodename.startswith('bb') + index = int(nodename[2:]) + # http://www.graphviz.org/doc/info/attrs.html#a:pos + # "pos" is the center of the node, in points + pos = attrdict['pos'][1:-1] # strip off quotes + x, y = pos.split(',') + x, y = float(x), float(y) + # These are in graphviz points, which are 1/72th of an inch + POINTS_TO_INCH = 72 + POINTS_TO_PIXELS = POINTS_TO_INCH * INCHES_PER_PIXEL + SCALE = POINTS_TO_PIXELS# * 2 + SCALE = 1 + x, y = x * SCALE, y * SCALE + + # Flip y axis: + y = bb_h - y + + actor = self.actors_for_nodes[index] + assert actor.bb.index == index + + # Clutter uses top-left for position + # Adjust from center to TL: + w, h = actor.get_size() + x = x - (w / 2.) + y = y - (h / 2.) + + if not DEBUG_LAYOUT: + # Directly set the position: + actor.set_position(x, y) + else: + # Animate to it, with a bounce: see + # https://clutter-and-mx-under-python3.readthedocs.org/en/latest/clutter_animation.html + actor.set_position(x, 0) + _actor_anim = actor.animatev( + Clutter.AnimationMode.EASE_OUT_BOUNCE, + 1500, + ["x", "y"], + [x, y] ) + + for srcnodename, destnodename, attrdict in iter_edge_attrs(out): + print(srcnodename, destnodename, attrdict) + + assert srcnodename.startswith('bb') + srcindex = int(srcnodename[2:]) + + assert destnodename.startswith('bb') + destindex = int(destnodename[2:]) + + srcnode = self.nodes[srcindex] + destnode = self.nodes[destindex] + + edge = self.edge_by_node_pair[(srcnode, destnode)] + edgeactor = self.actor_for_edge[edge] + + if 'pos' in attrdict: + posstr = attrdict['pos'][1:-1] # remove quotes + result = [] + for coord in posstr.split(' '): + coordvals = coord.split(',') + if len(coordvals) == 3: + assert coordvals[0] == 'e' + x, y = coordvals[1:3] + else: + x, y = coordvals[0:2] + x = float(x) + y = bb_h - float(y) + result.append( (x, y) ) + #print(result) + edgeactor.set_coords(result) + + def select_one_node(self, node): + print('select_one_node(%r)' % node) + # Deselect all: + for oldnode in self.selected_nodes: + actor = self.actor_for_node[oldnode] + actor.is_selected = False + actor.set_background_color(self.NORMAL) + self.selected_nodes = set() + + # Select this node: + self.selected_nodes.add(node) + actor = self.actor_for_node[node] + actor.is_selected = True + actor.set_background_color(self.SELECTED) + + def toggle_node_selection(self, node): + print('toggle_node_selection(%r)' % node) + actor = self.actor_for_node[node] + if actor.is_selected: + self.selected_nodes.remove(node) + actor.is_selected = False + actor.set_background_color(self.NORMAL) + else: + self.selected_nodes.add(node) + actor.is_selected = True + actor.set_background_color(self.SELECTED) + + def add_node_to_selection(self, node): + print('add_node_to_selection(%r)' % node) + actor = self.actor_for_node[node] + self.selected_nodes.add(node) + actor.is_selected = True + actor.set_background_color(self.SELECTED) + +class BBActor(Clutter.Actor): + def __init__(self, gv, fun, bb): + + Clutter.Actor.__init__(self) + self.gv = gv + self.fun = fun + self.bb = bb + self.is_selected = False + + #print(dir(Clutter)) + #self.add_actor( + layout = Clutter.TableLayout() + self.set_layout_manager(layout) + + if bb.index == 0: + label = 'ENTRY' + elif bb.index == 1: + label = 'EXIT' + else: + label = 'bb: %i' % bb.index + + layout.pack(make_text(label), + column=0, row=0) + + if DEBUG_LAYOUT: + self.set_background_color(random_color()) + + # TODO: phi nodes + if bb.gimple: + row = 1 + cursrcline = None + for stmtidx, stmt in enumerate(bb.gimple): + if stmt.loc is not None: + if cursrcline != stmt.loc.line: + cursrcline = stmt.loc.line + code = get_src_for_loc(stmt.loc).rstrip() + srctext = make_text('%4i %s' % (cursrcline, code)) + layout.pack(srctext, column=0, row=row) + + text = make_text(str(stmt)) + layout.pack(text, column=1, row=row) + row += 1 + """ + text = make_text('more stuff') + layout.pack(text, column=1, row=row) + row += 1 + text = make_text('yet more stuff') + layout.pack(text, column=1, row=row) + row += 1 + """ + + action = Clutter.ClickAction.new() + self.add_action(action) + def foo(*args): + print('clicked on %s, args: %s' % (self.bb, args)) + event = Clutter.get_current_event() + state = event.get_state() + if state & Clutter.ModifierType.CONTROL_MASK: + self.gv.toggle_node_selection(self.bb) + return + if state & Clutter.ModifierType.SHIFT_MASK: + self.gv.add_node_to_selection(self.bb) + return + self.gv.select_one_node(self.bb) + #self.set_background_color(random_color()) + action.connect('clicked', foo) + self.set_reactive(True) + + #rect = Clutter.Rectangle.new() + #rect.size = (20, 20) + #rect.position = (10, 30) + #rect.color = Clutter.Color() + #self.add_actor(rect) + + #self.set_size(50, 50) + #print('self.get_size(): %s' % (self.get_size(), )) + #print('self.allocation: %s' % (self.allocation, )) + + def get_top_middle(self): + x, y = self.get_position() + w, h = self.get_size() + return (x + w/2., y) + + def get_bottom_middle(self): + x, y = self.get_position() + w, h = self.get_size() + return (x + w/2., y + h) + +class EdgeActor(Clutter.Actor): + def __init__(self, gv, edge): + Clutter.Actor.__init__(self) + self.gv = gv + self.edge = edge + self._coords = None + + def do_paint(self): + #print('do_paint: %s' % self.edge) + # Get edge locations + actor_for_src = self.gv.actor_for_node[self.edge.src] + actor_for_dest = self.gv.actor_for_node[self.edge.dest] + + #Cogl.Color.set() + Cogl.path_new() + if 0: + print(self._coords) + Cogl.path_move_to(*self._coords[0]) + for coord in self._coords[1:]: + Cogl.path_line_to(*coord) + Cogl.path_stroke() + else: + Cogl.path_move_to(*actor_for_src.get_bottom_middle()) + Cogl.path_line_to(*actor_for_dest.get_top_middle()) + Cogl.path_stroke() + + def set_coords(self, coords): + self._coords = coords + +class MainWindow(Gtk.Window): + def __init__(self, fun): + Gtk.Window.__init__(self) + + self.connect('destroy', lambda w: Gtk.main_quit()) + self.set_default_size(1024, 768) + self.set_title(fun.decl.name) + + display = Gdk.Display.get_default() + screen = display.get_default_screen() + #css_provider = Gtk.CssProvider() + #print(dir(css_provider)) + #props = css_provider.get_style(None) + #print(dir(props)) + + gv = GraphView(fun) + self.add(gv) + + def RGBA_to_clutter(rgba): + return Clutter.Color().new(rgba.red * 255, + rgba.green * 255, + rgba.blue * 255, + rgba.alpha * 255) + + sctxt = self.get_style_context()# Gtk.StyleContext() + print(sctxt) + #print(dir(sctxt)) + print(sctxt.get_color(Gtk.StateFlags.NORMAL)) + gv.NORMAL = RGBA_to_clutter(sctxt.get_color(Gtk.StateFlags.NORMAL)) + + print(sctxt.get_color(Gtk.StateFlags.BACKDROP)) + gv.BACKDROP = sctxt.get_color(Gtk.StateFlags.BACKDROP) + print(dir(gv.BACKDROP)) + gv.stage.set_background_color(RGBA_to_clutter(gv.BACKDROP)) + + print(sctxt.get_color(Gtk.StateFlags.PRELIGHT)) + gv.PRELIGHT = sctxt.get_color(Gtk.StateFlags.PRELIGHT) + + print(sctxt.get_color(Gtk.StateFlags.NORMAL | Gtk.StateFlags.SELECTED)) + gv.SELECTED = RGBA_to_clutter(sctxt.get_color(Gtk.StateFlags.SELECTED)) + + print(sctxt.get_color(Gtk.StateFlags.FOCUSED)) + gv.FOCUSED = sctxt.get_color(Gtk.StateFlags.FOCUSED) + + print(sctxt.get_color(Gtk.StateFlags.INSENSITIVE)) + gv.INSENSITIVE = sctxt.get_color(Gtk.StateFlags.INSENSITIVE) + + + self.show_all() + + +# We'll implement this as a custom pass, to be called directly after the +# builtin "cfg" pass, which generates the CFG: + +class ShowGimple(gcc.GimplePass): + def execute(self, fun): + # (the CFG should be set up by this point, and the GIMPLE is not yet + # in SSA form) + if fun and fun.cfg: + mw = MainWindow(fun) + print(dir(Gtk.StyleProvider)) + Gtk.main() + +#print(dir(Gtk.StateFlags)) + +ps = ShowGimple(name='show-gimple') +ps.register_after('cfg') From b0eb17d53342bf9d9e5a02b6da5762c95eddd25d Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 10:45:01 -0500 Subject: [PATCH 02/11] use "n[0-9]+" rather than "bb[0-9+]" in dot as a precursor to generalizing to arbitrary graphs --- browser/main.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/browser/main.py b/browser/main.py index f9b3fbce..0905df4f 100644 --- a/browser/main.py +++ b/browser/main.py @@ -81,15 +81,14 @@ def iter_node_attrs(dot): """ for line in dot.splitlines(): # e.g. - # bb1 [width="0.17361", height="0.069444", fixedsize=true, pos="206,3"]; + # n1 [width="0.17361", height="0.069444", fixedsize=true, pos="206,3"]; # print line - m = re.match('\s*([a-z0-9]+) \[(.*)\];', line) + m = re.match('\s*(n[0-9]+) \[(.*)\];', line) if m: #print m.groups() nodename, attrs = m.groups() - if nodename.startswith('bb'): # FIXME - attrvals = split_attrs(attrs) - yield nodename, split_attrs(attrs) + attrvals = split_attrs(attrs) + yield nodename, split_attrs(attrs) else: # print 'unmatched' pass @@ -100,9 +99,9 @@ def iter_edge_attrs(dot): """ for line in dot.splitlines(): # e.g. - # bb2 -> bb3 [pos="e,241.6,387.23 396.72,521.99 380,511.51 361.13,498.86 345,486 308.97,457.28 271.58,419.24 248.55,394.69"]; + # n2 -> n3 [pos="e,241.6,387.23 396.72,521.99 380,511.51 361.13,498.86 345,486 308.97,457.28 271.58,419.24 248.55,394.69"]; print line - m = re.match('\s*([a-z0-9]+) -> ([a-z0-9]+) \[(.*)\];', line) + m = re.match('\s*(n[0-9]+) -> (n[0-9]+) \[(.*)\];', line) if m: print m.groups() srcnodename, dstnodename, attrs = m.groups() @@ -174,10 +173,10 @@ def _make_dot(self): for bbactor in self.actors_for_nodes: w, h = bbactor.get_size() # Express width and height in graphviz "inches", which we'll - result += (' bb%i [width=%f, height=%f, fixedsize=true];\n' + result += (' n%i [width=%f, height=%f, fixedsize=true];\n' % (bbactor.bb.index, w * INCHES_PER_PIXEL, h * INCHES_PER_PIXEL)) for outedge in bbactor.bb.succs: - result += (' bb%i -> bb%i;\n' + result += (' n%i -> n%i;\n' % (outedge.src.index, outedge.dest.index)) #result += self._edges_to_dot(ctxt) result += '}\n' @@ -213,8 +212,8 @@ def _generate_layout(self): for nodename, attrdict in iter_node_attrs(out): print(nodename, attrdict) - assert nodename.startswith('bb') - index = int(nodename[2:]) + assert nodename.startswith('n') + index = int(nodename[1:]) # http://www.graphviz.org/doc/info/attrs.html#a:pos # "pos" is the center of the node, in points pos = attrdict['pos'][1:-1] # strip off quotes @@ -255,11 +254,11 @@ def _generate_layout(self): for srcnodename, destnodename, attrdict in iter_edge_attrs(out): print(srcnodename, destnodename, attrdict) - assert srcnodename.startswith('bb') - srcindex = int(srcnodename[2:]) + assert srcnodename.startswith('n') + srcindex = int(srcnodename[1]) - assert destnodename.startswith('bb') - destindex = int(destnodename[2:]) + assert destnodename.startswith('n') + destindex = int(destnodename[1:]) srcnode = self.nodes[srcindex] destnode = self.nodes[destindex] From e6cd42c993b7a14cd114c6c6fea6b3207ffe4f51 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 11:24:12 -0500 Subject: [PATCH 03/11] introduce gccutils.graph.cfg and use it to generalize the graph-viewing classes --- browser/main.py | 94 ++++++++++++++++++++++++++----------------- gccutils/graph/cfg.py | 73 +++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 37 deletions(-) create mode 100644 gccutils/graph/cfg.py diff --git a/browser/main.py b/browser/main.py index 0905df4f..8e450a35 100644 --- a/browser/main.py +++ b/browser/main.py @@ -111,9 +111,12 @@ def iter_edge_attrs(dot): pass class GraphView(GtkClutter.Embed): - def __init__(self, fun): + def __init__(self, fun, graph): GtkClutter.Embed.__init__(self) + self.fun = fun + self.graph = graph + stage = self.get_stage() if DEBUG_LAYOUT: @@ -138,26 +141,24 @@ def __init__(self, fun): transition.start() """ - self.fun = fun self.stage = stage self.actor_for_node = {} self.actor_for_edge = {} - self.actors_for_nodes = [] - self.nodes = [] self.edge_by_node_pair = {} + self.nodes_by_id = {} + self.selected_nodes = set() - for bb in fun.cfg.basic_blocks: - self.nodes.append(bb) - bbactor = BBActor(self, fun, bb) - self.actor_for_node[bb] = bbactor + for node in graph.nodes: + self.nodes_by_id[id(node)] = node + bbactor = self._make_actor_for_node(fun, node) + self.actor_for_node[node] = bbactor stage.add_actor(bbactor) - bbactor.set_position(bb.index * 150, bb.index * 75) - self.actors_for_nodes.append(bbactor) - for outedge in bb.succs: - self.edge_by_node_pair[(outedge.src, outedge.dest)] = outedge + for outedge in node.succs: + self.edge_by_node_pair[(outedge.srcnode, + outedge.dstnode)] = outedge edgeactor = EdgeActor(self, outedge) stage.add_actor(edgeactor) self.actor_for_edge[outedge] = edgeactor @@ -167,17 +168,20 @@ def __init__(self, fun): stage.set_scale(100, 5) # ^^ doesn't seem to do anything + def _make_actor_for_node(self, fun, node): + raise NotImplementedError + def _make_dot(self): result = 'digraph %s {\n' % 'test' result += ' node [shape=box];\n' - for bbactor in self.actors_for_nodes: - w, h = bbactor.get_size() + for node, actor in self.actor_for_node.iteritems(): + w, h = actor.get_size() # Express width and height in graphviz "inches", which we'll result += (' n%i [width=%f, height=%f, fixedsize=true];\n' - % (bbactor.bb.index, w * INCHES_PER_PIXEL, h * INCHES_PER_PIXEL)) - for outedge in bbactor.bb.succs: + % (id(node), w * INCHES_PER_PIXEL, h * INCHES_PER_PIXEL)) + for outedge in node.succs: result += (' n%i -> n%i;\n' - % (outedge.src.index, outedge.dest.index)) + % (id(outedge.srcnode), id(outedge.dstnode))) #result += self._edges_to_dot(ctxt) result += '}\n' return result @@ -213,7 +217,7 @@ def _generate_layout(self): for nodename, attrdict in iter_node_attrs(out): print(nodename, attrdict) assert nodename.startswith('n') - index = int(nodename[1:]) + node_id = int(nodename[1:]) # http://www.graphviz.org/doc/info/attrs.html#a:pos # "pos" is the center of the node, in points pos = attrdict['pos'][1:-1] # strip off quotes @@ -229,8 +233,8 @@ def _generate_layout(self): # Flip y axis: y = bb_h - y - actor = self.actors_for_nodes[index] - assert actor.bb.index == index + node = self.nodes_by_id[node_id] + actor = self.actor_for_node[node] # Clutter uses top-left for position # Adjust from center to TL: @@ -255,15 +259,15 @@ def _generate_layout(self): print(srcnodename, destnodename, attrdict) assert srcnodename.startswith('n') - srcindex = int(srcnodename[1]) + src_node_id = int(srcnodename[1:]) assert destnodename.startswith('n') - destindex = int(destnodename[1:]) + dst_node_id = int(destnodename[1:]) - srcnode = self.nodes[srcindex] - destnode = self.nodes[destindex] + srcnode = self.nodes_by_id[src_node_id] + dstnode = self.nodes_by_id[dst_node_id] - edge = self.edge_by_node_pair[(srcnode, destnode)] + edge = self.edge_by_node_pair[(srcnode, dstnode)] edgeactor = self.actor_for_edge[edge] if 'pos' in attrdict: @@ -316,15 +320,26 @@ def add_node_to_selection(self, node): actor.is_selected = True actor.set_background_color(self.SELECTED) -class BBActor(Clutter.Actor): - def __init__(self, gv, fun, bb): - +class NodeActor(Clutter.Actor): + def __init__(self, gv, node): Clutter.Actor.__init__(self) self.gv = gv - self.fun = fun - self.bb = bb + self.node = node self.is_selected = False +############################################################################ +# Graph-viewing subclasses specific to CFG +############################################################################ + +class CFGView(GraphView): + def _make_actor_for_node(self, fun, node): + return BBActor(self, fun, node) + +class BBActor(NodeActor): + def __init__(self, gv, fun, bb): + NodeActor.__init__(self, gv, bb) + self.fun = fun + #print(dir(Clutter)) #self.add_actor( layout = Clutter.TableLayout() @@ -370,16 +385,16 @@ def __init__(self, gv, fun, bb): action = Clutter.ClickAction.new() self.add_action(action) def foo(*args): - print('clicked on %s, args: %s' % (self.bb, args)) + print('clicked on %s, args: %s' % (self.node, args)) event = Clutter.get_current_event() state = event.get_state() if state & Clutter.ModifierType.CONTROL_MASK: - self.gv.toggle_node_selection(self.bb) + self.gv.toggle_node_selection(self.node) return if state & Clutter.ModifierType.SHIFT_MASK: - self.gv.add_node_to_selection(self.bb) + self.gv.add_node_to_selection(self.node) return - self.gv.select_one_node(self.bb) + self.gv.select_one_node(self.node) #self.set_background_color(random_color()) action.connect('clicked', foo) self.set_reactive(True) @@ -414,8 +429,8 @@ def __init__(self, gv, edge): def do_paint(self): #print('do_paint: %s' % self.edge) # Get edge locations - actor_for_src = self.gv.actor_for_node[self.edge.src] - actor_for_dest = self.gv.actor_for_node[self.edge.dest] + actor_for_src = self.gv.actor_for_node[self.edge.srcnode] + actor_for_dest = self.gv.actor_for_node[self.edge.dstnode] #Cogl.Color.set() Cogl.path_new() @@ -433,6 +448,8 @@ def do_paint(self): def set_coords(self, coords): self._coords = coords +############################################################################ + class MainWindow(Gtk.Window): def __init__(self, fun): Gtk.Window.__init__(self) @@ -448,7 +465,10 @@ def __init__(self, fun): #props = css_provider.get_style(None) #print(dir(props)) - gv = GraphView(fun) + from gccutils.graph.cfg import CFG + graph = CFG(fun) + + gv = CFGView(fun, graph) self.add(gv) def RGBA_to_clutter(rgba): diff --git a/gccutils/graph/cfg.py b/gccutils/graph/cfg.py new file mode 100644 index 00000000..8f90d1fa --- /dev/null +++ b/gccutils/graph/cfg.py @@ -0,0 +1,73 @@ +# Copyright 2013 David Malcolm +# Copyright 2013 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# . + +import gcc + +from gccutils.graph import Graph, Node, Edge + +############################################################################ +# A CFG, built as a Graph instance +############################################################################ +class CFG(Graph): + __slots__ = ('fun', 'innercfg', + 'node_for_innerbb', 'edge_for_inneredge',) + + def __init__(self, fun): + Graph.__init__(self) + self.fun = fun + self.innercfg = fun.cfg + self.node_for_innerbb = {} + self.edge_for_inneredge = {} + + # Build wrapper nodes: + for innerbb in self.innercfg.basic_blocks: + bbnode = BasicBlock(innerbb) + self.node_for_innerbb[innerbb] = bbnode + self.add_node(bbnode) + + # Build wrapper edges: + for innerbb in self.innercfg.basic_blocks: + for inneredge in innerbb.succs: + srcnode = self.node_for_innerbb[inneredge.src] + dstnode = self.node_for_innerbb[inneredge.dest] + edge = self.add_edge(srcnode, dstnode, inneredge) + self.edge_for_inneredge[inneredge] = edge + + def _make_edge(self, srcnode, dstnode, inneredge): + return CFGEdge(srcnode, dstnode, inneredge) + +class BasicBlock(Node): + __slots__ = ('innerbb', ) + + def __init__(self, innerbb): + Node.__init__(self) + self.innerbb = innerbb + + @property + def index(self): + return self.innerbb.index + + @property + def gimple(self): + return self.innerbb.gimple + +class CFGEdge(Edge): + __slots__ = ('inneredge', ) + + def __init__(self, srcnode, dstnode, inneredge): + Edge.__init__(self, srcnode, dstnode) + self.inneredge = inneredge From 42f538065b0aeffeaa78a2acd1af884c16f52579 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 11:30:35 -0500 Subject: [PATCH 04/11] remove stray "fun" parameters --- browser/main.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/browser/main.py b/browser/main.py index 8e450a35..e71d9361 100644 --- a/browser/main.py +++ b/browser/main.py @@ -111,10 +111,9 @@ def iter_edge_attrs(dot): pass class GraphView(GtkClutter.Embed): - def __init__(self, fun, graph): + def __init__(self, graph): GtkClutter.Embed.__init__(self) - self.fun = fun self.graph = graph stage = self.get_stage() @@ -152,7 +151,7 @@ def __init__(self, fun, graph): for node in graph.nodes: self.nodes_by_id[id(node)] = node - bbactor = self._make_actor_for_node(fun, node) + bbactor = self._make_actor_for_node(node) self.actor_for_node[node] = bbactor stage.add_actor(bbactor) @@ -168,7 +167,7 @@ def __init__(self, fun, graph): stage.set_scale(100, 5) # ^^ doesn't seem to do anything - def _make_actor_for_node(self, fun, node): + def _make_actor_for_node(self, node): raise NotImplementedError def _make_dot(self): @@ -332,13 +331,12 @@ def __init__(self, gv, node): ############################################################################ class CFGView(GraphView): - def _make_actor_for_node(self, fun, node): - return BBActor(self, fun, node) + def _make_actor_for_node(self, node): + return BBActor(self, node) class BBActor(NodeActor): - def __init__(self, gv, fun, bb): + def __init__(self, gv, bb): NodeActor.__init__(self, gv, bb) - self.fun = fun #print(dir(Clutter)) #self.add_actor( @@ -468,7 +466,7 @@ def __init__(self, fun): from gccutils.graph.cfg import CFG graph = CFG(fun) - gv = CFGView(fun, graph) + gv = CFGView(graph) self.add(gv) def RGBA_to_clutter(rgba): From e4fbc844c9f22f0c23f5cc3b1764d31e3434801c Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 11:48:59 -0500 Subject: [PATCH 05/11] introduce gccutils.graph.callgraph and use it to further generalize the graph-viewing code --- browser/main.py | 149 +++++++++++++++++++++++------------- gccutils/graph/callgraph.py | 62 +++++++++++++++ 2 files changed, 157 insertions(+), 54 deletions(-) create mode 100644 gccutils/graph/callgraph.py diff --git a/browser/main.py b/browser/main.py index e71d9361..ead7904f 100644 --- a/browser/main.py +++ b/browser/main.py @@ -326,8 +326,47 @@ def __init__(self, gv, node): self.node = node self.is_selected = False + def get_top_middle(self): + x, y = self.get_position() + w, h = self.get_size() + return (x + w/2., y) + + def get_bottom_middle(self): + x, y = self.get_position() + w, h = self.get_size() + return (x + w/2., y + h) + +class EdgeActor(Clutter.Actor): + def __init__(self, gv, edge): + Clutter.Actor.__init__(self) + self.gv = gv + self.edge = edge + self._coords = None + + def do_paint(self): + #print('do_paint: %s' % self.edge) + # Get edge locations + actor_for_src = self.gv.actor_for_node[self.edge.srcnode] + actor_for_dest = self.gv.actor_for_node[self.edge.dstnode] + + #Cogl.Color.set() + Cogl.path_new() + if 0: + print(self._coords) + Cogl.path_move_to(*self._coords[0]) + for coord in self._coords[1:]: + Cogl.path_line_to(*coord) + Cogl.path_stroke() + else: + Cogl.path_move_to(*actor_for_src.get_bottom_middle()) + Cogl.path_line_to(*actor_for_dest.get_top_middle()) + Cogl.path_stroke() + + def set_coords(self, coords): + self._coords = coords + ############################################################################ -# Graph-viewing subclasses specific to CFG +# Graph-viewing subclasses specific to CFGs: ############################################################################ class CFGView(GraphView): @@ -407,54 +446,37 @@ def foo(*args): #print('self.get_size(): %s' % (self.get_size(), )) #print('self.allocation: %s' % (self.allocation, )) - def get_top_middle(self): - x, y = self.get_position() - w, h = self.get_size() - return (x + w/2., y) +############################################################################ +# Graph-viewing subclasses specific to callgraphs: +############################################################################ - def get_bottom_middle(self): - x, y = self.get_position() - w, h = self.get_size() - return (x + w/2., y + h) +class CallgraphView(GraphView): + def _make_actor_for_node(self, node): + return CallgraphNodeActor(self, node) -class EdgeActor(Clutter.Actor): - def __init__(self, gv, edge): - Clutter.Actor.__init__(self) - self.gv = gv - self.edge = edge - self._coords = None +class CallgraphNodeActor(NodeActor): + def __init__(self, gv, node): + NodeActor.__init__(self, gv, node) - def do_paint(self): - #print('do_paint: %s' % self.edge) - # Get edge locations - actor_for_src = self.gv.actor_for_node[self.edge.srcnode] - actor_for_dest = self.gv.actor_for_node[self.edge.dstnode] + layout = Clutter.TableLayout() + self.set_layout_manager(layout) - #Cogl.Color.set() - Cogl.path_new() - if 0: - print(self._coords) - Cogl.path_move_to(*self._coords[0]) - for coord in self._coords[1:]: - Cogl.path_line_to(*coord) - Cogl.path_stroke() - else: - Cogl.path_move_to(*actor_for_src.get_bottom_middle()) - Cogl.path_line_to(*actor_for_dest.get_top_middle()) - Cogl.path_stroke() + label = self.node.innernode.decl.name + layout.pack(make_text(label), + column=0, row=0) - def set_coords(self, coords): - self._coords = coords + if DEBUG_LAYOUT: + self.set_background_color(random_color()) ############################################################################ class MainWindow(Gtk.Window): - def __init__(self, fun): + def __init__(self, gv): Gtk.Window.__init__(self) self.connect('destroy', lambda w: Gtk.main_quit()) self.set_default_size(1024, 768) - self.set_title(fun.decl.name) + #self.set_title(fun.decl.name) display = Gdk.Display.get_default() screen = display.get_default_screen() @@ -463,10 +485,6 @@ def __init__(self, fun): #props = css_provider.get_style(None) #print(dir(props)) - from gccutils.graph.cfg import CFG - graph = CFG(fun) - - gv = CFGView(graph) self.add(gv) def RGBA_to_clutter(rgba): @@ -502,19 +520,42 @@ def RGBA_to_clutter(rgba): self.show_all() -# We'll implement this as a custom pass, to be called directly after the -# builtin "cfg" pass, which generates the CFG: - -class ShowGimple(gcc.GimplePass): - def execute(self, fun): - # (the CFG should be set up by this point, and the GIMPLE is not yet - # in SSA form) - if fun and fun.cfg: - mw = MainWindow(fun) - print(dir(Gtk.StyleProvider)) +if 1: + # CFG viewing for each function: + + # We'll implement this as a custom pass, to be called directly after the + # builtin "cfg" pass, which generates the CFG: + + class ShowGimple(gcc.GimplePass): + def execute(self, fun): + # (the CFG should be set up by this point, and the GIMPLE is not yet + # in SSA form) + if fun and fun.cfg: + from gccutils.graph.cfg import CFG + graph = CFG(fun) + gv = CFGView(graph) + mw = MainWindow(gv) + Gtk.main() + + ps = ShowGimple(name='show-gimple') + ps.register_after('cfg') + +else: + # Callgraph viewer: + + def on_pass_execution(p, fn): + if p.name == '*free_lang_data': + # The '*free_lang_data' pass is called once, rather than per-function, + # and occurs immediately after "*build_cgraph_edges", which is the + # pass that initially builds the callgraph + # + # So at this point we're likely to get a good view of the callgraph + # before further optimization passes manipulate it + from gccutils.graph.callgraph import Callgraph + graph = Callgraph() + gv = CallgraphView(graph) + mw = MainWindow(gv) Gtk.main() -#print(dir(Gtk.StateFlags)) - -ps = ShowGimple(name='show-gimple') -ps.register_after('cfg') + gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION, + on_pass_execution) diff --git a/gccutils/graph/callgraph.py b/gccutils/graph/callgraph.py new file mode 100644 index 00000000..d11412bf --- /dev/null +++ b/gccutils/graph/callgraph.py @@ -0,0 +1,62 @@ +# Copyright 2013 David Malcolm +# Copyright 2013 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# . + +import gcc + +from gccutils.graph import Graph, Node, Edge + +############################################################################ +# The callgraph, built as a Graph instance +############################################################################ +class Callgraph(Graph): + __slots__ = ('node_for_innernode', 'edge_for_inneredge',) + + def __init__(self): + Graph.__init__(self) + self.node_for_innernode = {} + self.edge_for_inneredge = {} + + # Build wrapper nodes: + for innernode in gcc.get_callgraph_nodes(): + node = CallgraphNode(innernode) + self.node_for_innernode[innernode] = node + self.add_node(node) + + # Build wrapper edges: + for innernode in gcc.get_callgraph_nodes(): + for inneredge in innernode.callees: + srcnode = self.node_for_innernode[inneredge.caller] + dstnode = self.node_for_innernode[inneredge.callee] + edge = self.add_edge(srcnode, dstnode, inneredge) + self.edge_for_inneredge[inneredge] = edge + + def _make_edge(self, srcnode, dstnode, inneredge): + return CallgraphEdge(srcnode, dstnode, inneredge) + +class CallgraphNode(Node): + __slots__ = ('innernode', ) + + def __init__(self, innernode): + Node.__init__(self) + self.innernode = innernode + +class CallgraphEdge(Edge): + __slots__ = ('inneredge', ) + + def __init__(self, srcnode, dstnode, inneredge): + Edge.__init__(self, srcnode, dstnode) + self.inneredge = inneredge From 6d8f0dad0be67b6dffded38c05c19cc985da57f3 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 12:14:17 -0500 Subject: [PATCH 06/11] get scaling of the graph view to work --- browser/main.py | 122 +++++++++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 54 deletions(-) diff --git a/browser/main.py b/browser/main.py index ead7904f..c3e5e966 100644 --- a/browser/main.py +++ b/browser/main.py @@ -115,11 +115,14 @@ def __init__(self, graph): GtkClutter.Embed.__init__(self) self.graph = graph + self.selected_nodes = set() + + self.actor_for_node = {} stage = self.get_stage() if DEBUG_LAYOUT: - make_grid(stage, 1024, 768) + #make_grid(stage, 1024, 768) test = Clutter.Text.new() test.set_text('hello world') @@ -140,40 +143,85 @@ def __init__(self, graph): transition.start() """ - self.stage = stage - self.actor_for_node = {} + self.ga = GraphActor(self) + stage.add_actor(self.ga) + + self.ga.set_scale(0.5, 0.5) + + def _make_actor_for_node(self, node): + raise NotImplementedError + + def select_one_node(self, node): + print('select_one_node(%r)' % node) + # Deselect all: + for oldnode in self.selected_nodes: + actor = self.actor_for_node[oldnode] + actor.is_selected = False + actor.set_background_color(self.NORMAL) + self.selected_nodes = set() + + # Select this node: + self.selected_nodes.add(node) + actor = self.actor_for_node[node] + actor.is_selected = True + actor.set_background_color(self.SELECTED) + + def toggle_node_selection(self, node): + print('toggle_node_selection(%r)' % node) + actor = self.actor_for_node[node] + if actor.is_selected: + self.selected_nodes.remove(node) + actor.is_selected = False + actor.set_background_color(self.NORMAL) + else: + self.selected_nodes.add(node) + actor.is_selected = True + actor.set_background_color(self.SELECTED) + + def add_node_to_selection(self, node): + print('add_node_to_selection(%r)' % node) + actor = self.actor_for_node[node] + self.selected_nodes.add(node) + actor.is_selected = True + actor.set_background_color(self.SELECTED) + +class GraphActor(Clutter.Actor): + """ + Setting the scale on the "stage" doesn't seem to zoom things, so + we place all top-level actors for the graph (nodes and edges) as + children of a GraphActor, and scale the GraphActor + """ + def __init__(self, gv): + Clutter.Actor.__init__(self) + self.gv = gv + self.actor_for_edge = {} self.edge_by_node_pair = {} self.nodes_by_id = {} - self.selected_nodes = set() + if DEBUG_LAYOUT: + make_grid(self, 1024, 768) - for node in graph.nodes: + for node in gv.graph.nodes: self.nodes_by_id[id(node)] = node - bbactor = self._make_actor_for_node(node) - self.actor_for_node[node] = bbactor - stage.add_actor(bbactor) + bbactor = gv._make_actor_for_node(node) + gv.actor_for_node[node] = bbactor + self.add_actor(bbactor) for outedge in node.succs: self.edge_by_node_pair[(outedge.srcnode, outedge.dstnode)] = outedge - edgeactor = EdgeActor(self, outedge) - stage.add_actor(edgeactor) + edgeactor = EdgeActor(gv, outedge) + self.add_actor(edgeactor) self.actor_for_edge[outedge] = edgeactor self._generate_layout() - stage.set_scale(100, 5) - # ^^ doesn't seem to do anything - - def _make_actor_for_node(self, node): - raise NotImplementedError - def _make_dot(self): result = 'digraph %s {\n' % 'test' result += ' node [shape=box];\n' - for node, actor in self.actor_for_node.iteritems(): + for node, actor in self.gv.actor_for_node.iteritems(): w, h = actor.get_size() # Express width and height in graphviz "inches", which we'll result += (' n%i [width=%f, height=%f, fixedsize=true];\n' @@ -211,7 +259,7 @@ def _generate_layout(self): assert bb_w assert bb_h - self.stage.set_size(bb_w, bb_h) + self.set_size(bb_w, bb_h) for nodename, attrdict in iter_node_attrs(out): print(nodename, attrdict) @@ -233,7 +281,7 @@ def _generate_layout(self): y = bb_h - y node = self.nodes_by_id[node_id] - actor = self.actor_for_node[node] + actor = self.gv.actor_for_node[node] # Clutter uses top-left for position # Adjust from center to TL: @@ -285,40 +333,6 @@ def _generate_layout(self): #print(result) edgeactor.set_coords(result) - def select_one_node(self, node): - print('select_one_node(%r)' % node) - # Deselect all: - for oldnode in self.selected_nodes: - actor = self.actor_for_node[oldnode] - actor.is_selected = False - actor.set_background_color(self.NORMAL) - self.selected_nodes = set() - - # Select this node: - self.selected_nodes.add(node) - actor = self.actor_for_node[node] - actor.is_selected = True - actor.set_background_color(self.SELECTED) - - def toggle_node_selection(self, node): - print('toggle_node_selection(%r)' % node) - actor = self.actor_for_node[node] - if actor.is_selected: - self.selected_nodes.remove(node) - actor.is_selected = False - actor.set_background_color(self.NORMAL) - else: - self.selected_nodes.add(node) - actor.is_selected = True - actor.set_background_color(self.SELECTED) - - def add_node_to_selection(self, node): - print('add_node_to_selection(%r)' % node) - actor = self.actor_for_node[node] - self.selected_nodes.add(node) - actor.is_selected = True - actor.set_background_color(self.SELECTED) - class NodeActor(Clutter.Actor): def __init__(self, gv, node): Clutter.Actor.__init__(self) @@ -502,7 +516,7 @@ def RGBA_to_clutter(rgba): print(sctxt.get_color(Gtk.StateFlags.BACKDROP)) gv.BACKDROP = sctxt.get_color(Gtk.StateFlags.BACKDROP) print(dir(gv.BACKDROP)) - gv.stage.set_background_color(RGBA_to_clutter(gv.BACKDROP)) + #gv.stage.set_background_color(RGBA_to_clutter(gv.BACKDROP)) print(sctxt.get_color(Gtk.StateFlags.PRELIGHT)) gv.PRELIGHT = sctxt.get_color(Gtk.StateFlags.PRELIGHT) From 818244467ff7ec3e6cd5029e5fbacbee56d06c06 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 1 Mar 2013 12:44:26 -0500 Subject: [PATCH 07/11] support (simplistic) drag and zoom --- browser/main.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/browser/main.py b/browser/main.py index c3e5e966..6e2521ea 100644 --- a/browser/main.py +++ b/browser/main.py @@ -146,7 +146,27 @@ def __init__(self, graph): self.ga = GraphActor(self) stage.add_actor(self.ga) - self.ga.set_scale(0.5, 0.5) + self.set_scale(0.5) + + #print(dir(Gdk.EventMask)) + self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | + Gdk.EventMask.SCROLL_MASK) + def on_button_press(*args): + print('on_button_press: %r' % (args, )) + self.connect("button-press-event", on_button_press) + def on_key_press(*args): + print('on_key_press: %r' % (args, )) + self.connect("key-press-event", on_key_press) + def on_scroll_event(view, scrollevent): + # print('on_scroll_event: %r %r' % (view, scrollevent)) + direction = scrollevent.direction + if direction == Gdk.ScrollDirection.UP: + self.zoom_in() + return True + elif direction == Gdk.ScrollDirection.DOWN: + self.zoom_out() + return True + self.connect("scroll-event", on_scroll_event) def _make_actor_for_node(self, node): raise NotImplementedError @@ -185,6 +205,16 @@ def add_node_to_selection(self, node): actor.is_selected = True actor.set_background_color(self.SELECTED) + def set_scale(self, scale): + self.scale = scale + self.ga.set_scale(self.scale, self.scale) + + def zoom_in(self): + self.set_scale(self.scale * 1.1) + + def zoom_out(self): + self.set_scale(self.scale * 0.9) + class GraphActor(Clutter.Actor): """ Setting the scale on the "stage" doesn't seem to zoom things, so @@ -218,6 +248,10 @@ def __init__(self, gv): self._generate_layout() + # Allow dragging this around: + self.add_action(Clutter.DragAction.new()) + self.set_reactive(True) + def _make_dot(self): result = 'digraph %s {\n' % 'test' result += ' node [shape=box];\n' From ca0d039c6009377893f535fcd882c59779e0ddce Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Sun, 3 Mar 2013 19:51:03 -0500 Subject: [PATCH 08/11] pick sanish default colors --- browser/main.py | 66 +++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/browser/main.py b/browser/main.py index 6e2521ea..ef2ea7fe 100644 --- a/browser/main.py +++ b/browser/main.py @@ -29,7 +29,7 @@ import gcc from gccutils import get_src_for_loc, cfg_to_dot, invoke_dot -DEBUG_LAYOUT = 1 +DEBUG_LAYOUT = 0 def random_color(): from random import randint @@ -119,6 +119,10 @@ def __init__(self, graph): self.actor_for_node = {} + self.NORMAL = Clutter.Color().new(237, 236, 235, 255) + self.SELECTED = Clutter.Color().new(0, 0, 237, 255) + self.BACKDROP = Clutter.Color().new(0, 0, 0, 255) + stage = self.get_stage() if DEBUG_LAYOUT: @@ -442,6 +446,8 @@ def __init__(self, gv, bb): if DEBUG_LAYOUT: self.set_background_color(random_color()) + else: + self.set_background_color(gv.NORMAL) # TODO: phi nodes if bb.gimple: @@ -535,35 +541,35 @@ def __init__(self, gv): self.add(gv) - def RGBA_to_clutter(rgba): - return Clutter.Color().new(rgba.red * 255, - rgba.green * 255, - rgba.blue * 255, - rgba.alpha * 255) - - sctxt = self.get_style_context()# Gtk.StyleContext() - print(sctxt) - #print(dir(sctxt)) - print(sctxt.get_color(Gtk.StateFlags.NORMAL)) - gv.NORMAL = RGBA_to_clutter(sctxt.get_color(Gtk.StateFlags.NORMAL)) - - print(sctxt.get_color(Gtk.StateFlags.BACKDROP)) - gv.BACKDROP = sctxt.get_color(Gtk.StateFlags.BACKDROP) - print(dir(gv.BACKDROP)) - #gv.stage.set_background_color(RGBA_to_clutter(gv.BACKDROP)) - - print(sctxt.get_color(Gtk.StateFlags.PRELIGHT)) - gv.PRELIGHT = sctxt.get_color(Gtk.StateFlags.PRELIGHT) - - print(sctxt.get_color(Gtk.StateFlags.NORMAL | Gtk.StateFlags.SELECTED)) - gv.SELECTED = RGBA_to_clutter(sctxt.get_color(Gtk.StateFlags.SELECTED)) - - print(sctxt.get_color(Gtk.StateFlags.FOCUSED)) - gv.FOCUSED = sctxt.get_color(Gtk.StateFlags.FOCUSED) - - print(sctxt.get_color(Gtk.StateFlags.INSENSITIVE)) - gv.INSENSITIVE = sctxt.get_color(Gtk.StateFlags.INSENSITIVE) - + if 0: + def RGBA_to_clutter(rgba): + return Clutter.Color().new(rgba.red * 255, + rgba.green * 255, + rgba.blue * 255, + rgba.alpha * 255) + + sctxt = self.get_style_context()# Gtk.StyleContext() + print(sctxt) + #print(dir(sctxt)) + print(sctxt.get_color(Gtk.StateFlags.NORMAL)) + gv.NORMAL = RGBA_to_clutter(sctxt.get_color(Gtk.StateFlags.NORMAL)) + + print(sctxt.get_color(Gtk.StateFlags.BACKDROP)) + gv.BACKDROP = sctxt.get_color(Gtk.StateFlags.BACKDROP) + print(dir(gv.BACKDROP)) + #gv.stage.set_background_color(RGBA_to_clutter(gv.BACKDROP)) + + print(sctxt.get_color(Gtk.StateFlags.PRELIGHT)) + gv.PRELIGHT = sctxt.get_color(Gtk.StateFlags.PRELIGHT) + + print(sctxt.get_color(Gtk.StateFlags.NORMAL | Gtk.StateFlags.SELECTED)) + gv.SELECTED = RGBA_to_clutter(sctxt.get_color(Gtk.StateFlags.SELECTED)) + + print(sctxt.get_color(Gtk.StateFlags.FOCUSED)) + gv.FOCUSED = sctxt.get_color(Gtk.StateFlags.FOCUSED) + + print(sctxt.get_color(Gtk.StateFlags.INSENSITIVE)) + gv.INSENSITIVE = sctxt.get_color(Gtk.StateFlags.INSENSITIVE) self.show_all() From 64ca1508a45019e5dd31d5f6aa2601e0d2a96ab8 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Sun, 3 Mar 2013 20:32:04 -0500 Subject: [PATCH 09/11] add a prelight effect when the mouse passes over a node --- browser/main.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/browser/main.py b/browser/main.py index ef2ea7fe..dbade61e 100644 --- a/browser/main.py +++ b/browser/main.py @@ -120,7 +120,11 @@ def __init__(self, graph): self.actor_for_node = {} self.NORMAL = Clutter.Color().new(237, 236, 235, 255) + self.NORMAL_PRELIGHT = Clutter.Color().new(237 * 1.05, 236 * 1.05, 235 * 1.05, 255) + self.SELECTED = Clutter.Color().new(0, 0, 237, 255) + self.SELECTED_PRELIGHT = Clutter.Color().new(0, 0, 237 * 1.05, 255) + self.BACKDROP = Clutter.Color().new(0, 0, 0, 255) stage = self.get_stage() @@ -377,6 +381,20 @@ def __init__(self, gv, node): self.gv = gv self.node = node self.is_selected = False + def on_enter(*args): + self.set_background_color( + gv.SELECTED_PRELIGHT + if self.is_selected + else + gv.NORMAL_PRELIGHT) + def on_leave(*args): + self.set_background_color( + gv.SELECTED + if self.is_selected + else + gv.NORMAL) + self.connect('enter-event', on_enter) + self.connect('leave-event', on_leave) def get_top_middle(self): x, y = self.get_position() From 5da0b299f7ea4b584c3d152d69c79b0bc43c8008 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 4 Mar 2013 07:08:45 -0500 Subject: [PATCH 10/11] reduce debug spew --- browser/main.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/browser/main.py b/browser/main.py index dbade61e..b3fec412 100644 --- a/browser/main.py +++ b/browser/main.py @@ -100,10 +100,10 @@ def iter_edge_attrs(dot): for line in dot.splitlines(): # e.g. # n2 -> n3 [pos="e,241.6,387.23 396.72,521.99 380,511.51 361.13,498.86 345,486 308.97,457.28 271.58,419.24 248.55,394.69"]; - print line + #print line m = re.match('\s*(n[0-9]+) -> (n[0-9]+) \[(.*)\];', line) if m: - print m.groups() + #print m.groups() srcnodename, dstnodename, attrs = m.groups() yield srcnodename, dstnodename, split_attrs(attrs) else: @@ -160,10 +160,12 @@ def __init__(self, graph): self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.SCROLL_MASK) def on_button_press(*args): - print('on_button_press: %r' % (args, )) + #print('on_button_press: %r' % (args, )) + pass self.connect("button-press-event", on_button_press) def on_key_press(*args): - print('on_key_press: %r' % (args, )) + #print('on_key_press: %r' % (args, )) + pass self.connect("key-press-event", on_key_press) def on_scroll_event(view, scrollevent): # print('on_scroll_event: %r %r' % (view, scrollevent)) @@ -180,7 +182,7 @@ def _make_actor_for_node(self, node): raise NotImplementedError def select_one_node(self, node): - print('select_one_node(%r)' % node) + #print('select_one_node(%r)' % node) # Deselect all: for oldnode in self.selected_nodes: actor = self.actor_for_node[oldnode] @@ -195,7 +197,7 @@ def select_one_node(self, node): actor.set_background_color(self.SELECTED) def toggle_node_selection(self, node): - print('toggle_node_selection(%r)' % node) + #print('toggle_node_selection(%r)' % node) actor = self.actor_for_node[node] if actor.is_selected: self.selected_nodes.remove(node) @@ -207,7 +209,7 @@ def toggle_node_selection(self, node): actor.set_background_color(self.SELECTED) def add_node_to_selection(self, node): - print('add_node_to_selection(%r)' % node) + #print('add_node_to_selection(%r)' % node) actor = self.actor_for_node[node] self.selected_nodes.add(node) actor.is_selected = True @@ -277,7 +279,7 @@ def _make_dot(self): def _generate_layout(self): dot = self._make_dot() - print(dot) + #print(dot) from gccutils import invoke_dot #invoke_dot(dot) @@ -285,7 +287,7 @@ def _generate_layout(self): p = Popen(['dot', '-Tdot'], stdin=PIPE, stdout=PIPE, stderr=PIPE) out, err = p.communicate(dot.encode('ascii')) - print(out) + #print(out) # Locate bounding box bb_w = None @@ -295,16 +297,16 @@ def _generate_layout(self): # graph [bb="0,0,1328,630"]; m = re.match('\s+graph \[bb="([0-9]+),([0-9]+),([0-9]+),([0-9]+)"\];', line) if m: - print m.groups() + #print m.groups() bb_w = int(m.group(3)) bb_h = int(m.group(4)) - assert bb_w - assert bb_h + assert bb_w is not None + assert bb_h is not None self.set_size(bb_w, bb_h) for nodename, attrdict in iter_node_attrs(out): - print(nodename, attrdict) + #print(nodename, attrdict) assert nodename.startswith('n') node_id = int(nodename[1:]) # http://www.graphviz.org/doc/info/attrs.html#a:pos @@ -345,7 +347,7 @@ def _generate_layout(self): [x, y] ) for srcnodename, destnodename, attrdict in iter_edge_attrs(out): - print(srcnodename, destnodename, attrdict) + #print(srcnodename, destnodename, attrdict) assert srcnodename.startswith('n') src_node_id = int(srcnodename[1:]) @@ -494,7 +496,7 @@ def __init__(self, gv, bb): action = Clutter.ClickAction.new() self.add_action(action) def foo(*args): - print('clicked on %s, args: %s' % (self.node, args)) + #print('clicked on %s, args: %s' % (self.node, args)) event = Clutter.get_current_event() state = event.get_state() if state & Clutter.ModifierType.CONTROL_MASK: From fbc2129950bf62c753feedf2befd3d57a7c85f45 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 4 Mar 2013 15:08:34 -0500 Subject: [PATCH 11/11] add a sidebar showing a zoomable view of the source code --- browser/main.py | 90 ++++++++++++++++++++++++++++++++++++++++++----- gccutils/pango.py | 29 +++++++++++++++ 2 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 gccutils/pango.py diff --git a/browser/main.py b/browser/main.py index b3fec412..24427021 100644 --- a/browser/main.py +++ b/browser/main.py @@ -28,6 +28,7 @@ import gcc from gccutils import get_src_for_loc, cfg_to_dot, invoke_dot +from gccutils.pango import escape as pango_escape DEBUG_LAYOUT = 0 @@ -51,6 +52,15 @@ def make_text(txt): actor.set_background_color(random_color()) return actor +def make_markup(markup): + """ + Build a Clutter.Text using Pango markup + """ + actor = Clutter.Text.new() + actor.set_markup(markup) + actor.set_font_name('monospace') + return actor + def make_grid(parent, width, height): color = Clutter.Color().new(0, 0, 0, 16) for x in range(0, width, 100): @@ -60,6 +70,64 @@ def make_grid(parent, width, height): a.set_position(x, y) parent.add_actor(a) +############################################################################ + +class SourceWidget(GtkClutter.Embed): + def __init__(self, src): + GtkClutter.Embed.__init__(self) + + self.src = src + + stage = self.get_stage() + + actor = SourceActor(self) + stage.add_actor(actor) + actor.set_scale(0.5, 0.5) + + stage.set_size(*actor.get_size()) + +class SourceActor(Clutter.Actor): + def __init__(self, tw): + Clutter.Actor.__init__(self) + + layout = Clutter.TableLayout() + self.set_layout_manager(layout) + + actors_for_line = {} + + lines = [] + + from pygments.lexers import CLexer + from pygments.styles.default import DefaultStyle as styler + + lexer = CLexer() # FIXME + markuplines = [] + for token, value in CLexer().get_tokens(tw.src): + style = styler.style_for_token(token) + + def make_markup_for_token(tok, style): + if style['color']: + return '%s' % (style['color'], pango_escape(tok)) + else: + return pango_escape(tok) + + if value == '\n': + markuplines.append('') + elif '\n' in value: + markuplines += [make_markup_for_token(line, style) + for line in value.splitlines()] + else: + markuplines[-1] = markuplines[-1] + make_markup_for_token(value, style) + + for linenum, line in enumerate(markuplines, start=1): + lhs = make_text('%5i' % linenum) + rhs = make_markup(line) + layout.pack(lhs, column=0, row=linenum - 1) + layout.pack(rhs, column=1, row=linenum - 1) + actors_for_line[linenum] = (lhs, rhs) + +############################################################################ + #def get_layout(graph): # Graphviz expresses width/height in inches, whereas we have pixels: @@ -545,21 +613,20 @@ def __init__(self, gv, node): ############################################################################ class MainWindow(Gtk.Window): - def __init__(self, gv): + def __init__(self, gv, src): Gtk.Window.__init__(self) self.connect('destroy', lambda w: Gtk.main_quit()) self.set_default_size(1024, 768) #self.set_title(fun.decl.name) - display = Gdk.Display.get_default() - screen = display.get_default_screen() - #css_provider = Gtk.CssProvider() - #print(dir(css_provider)) - #props = css_provider.get_style(None) - #print(dir(props)) + hpaned = Gtk.HPaned.new() + self.add(hpaned) - self.add(gv) + tw = SourceWidget(src) + hpaned.add(tw) + hpaned.add(gv) + hpaned.set_position(300) # FIXME if 0: def RGBA_to_clutter(rgba): @@ -608,7 +675,11 @@ def execute(self, fun): from gccutils.graph.cfg import CFG graph = CFG(fun) gv = CFGView(graph) - mw = MainWindow(gv) + + with open(fun.start.file) as f: + src = f.read() + mw = MainWindow(gv, src) + mw.set_title('%s CFG' % fun.decl.name) Gtk.main() ps = ShowGimple(name='show-gimple') @@ -629,6 +700,7 @@ def on_pass_execution(p, fn): graph = Callgraph() gv = CallgraphView(graph) mw = MainWindow(gv) + mw.set_title('Callgraph') Gtk.main() gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION, diff --git a/gccutils/pango.py b/gccutils/pango.py new file mode 100644 index 00000000..885943c2 --- /dev/null +++ b/gccutils/pango.py @@ -0,0 +1,29 @@ +# Copyright 2013 David Malcolm +# Copyright 2013 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# . + +def escape(text): + """ + Convert the text to GMarkup (for Pango) + """ + # Pango uses GMarkup; see e.g.: + # http://developer.gnome.org/glib/2.34/glib-Simple-XML-Subset-Parser.html + escapes = { '&' : '&', + '<' : '<', + '>' : '>', + '"' : '"', + "'" : '''} + return ''.join(escapes.get(c, c) for c in text)