├── README └── graph-llvm-ir /README: -------------------------------------------------------------------------------- 1 | (Useful) combinations of rendering options: 2 | 3 | --control (default): 4 | 5 | Renders both explicit control flow present in LLVM IR (sequential 6 | between statements in basic blocks, jumps between basic blocks), 7 | and dataflow dependencies. Control flow has higher weight (that 8 | means that control flow edges tend to be more straight). 9 | 10 | --dag-control: 11 | 12 | Ignore explicit flow control present in LLVM IR and instead compute 13 | order of evaluation of independent (i.e. disconnected) dataflow DAGs 14 | within basic block. Root node of a DAG consider to be an instruction 15 | of type void. (The idea is that void instruction is executed solely 16 | for side effect, and then it must be last instruction in evaluation of 17 | some DAG, i.e. its root. This is clearly a heuristic, which needs to 18 | be tested on various inputs yet.) 19 | 20 | 21 | --block 22 | 23 | For both options above, you can add --block to clusterize 24 | instructions of the same basic block together within a rectangle 25 | block. This seems like natural way to do it, but leaves questions 26 | open where to put leaves of dataflow graphs (i.e. variables, constants, 27 | etc.) So far, these are rendered as DAG structure also, which means 28 | they are not part of any basic block cluster. But rendering them 29 | in such way leads to edges going from different basic blocks to the 30 | same leaf nodes, leading to a mess in the graph. Possible other 31 | options: duplicate leaf nodes; don't render at all (can be kinda 32 | assumed). 33 | 34 | --block-edges 35 | 36 | This makes control edges between basic blocks actually go between 37 | basic blocks, not specific instructions in them. This may be useful 38 | for some kinds of presentations. This also removes extra nodes 39 | to represent labels. Results of the latter changes are mixed though, 40 | it leads to not ideal placing of leaf non-cluster nodes and thus 41 | deformed graphs. 42 | -------------------------------------------------------------------------------- /graph-llvm-ir: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import optparse 4 | 5 | from llvm.core import * 6 | import llvm 7 | 8 | 9 | #USE_CLUSTERS = 0 10 | CLUSTER_EDGES = 0 11 | INV_NODES = 0 12 | #EXPLICIT_CONTROL = 0 13 | #CONTROL_BETWEEN_DATAFLOW_TREES = 1 14 | 15 | 16 | tmp_i = 1 17 | def number_tmps(mod): 18 | """This function establishes explicit names for nameless numeric 19 | temporaries in IR. It also should give human-readable IDs to each 20 | statement in IR. Actually, as this is SSA, it uses result temporary 21 | name as an ID for statement. And fails here, because void-typed 22 | statements do not allow to set temporary name. So, this needs rework, 23 | and so far worked around during graph construction. 24 | """ 25 | global tmp_i 26 | for f in mod.functions: 27 | # print `f` 28 | for b in f.basic_blocks: 29 | # print "BB name:", b.name 30 | for i in b.instructions: 31 | # print i 32 | if not i.name and i.type != Type.void(): 33 | i.name = "t%d" % tmp_i 34 | tmp_i += 1 35 | 36 | 37 | class Graph: 38 | 39 | def __init__(self, f, out, options): 40 | self.f = f 41 | self.out = out 42 | self.options = options 43 | self.edges = [] 44 | self.anon_bblock_cnt = 0 45 | self.anon_bblock_names = {} 46 | self.void_instr_cnt = 0 47 | self.void_instr_names = {} 48 | 49 | def write(self, line=""): 50 | self.out.write(line + "\n") 51 | 52 | def start_graph(self): 53 | self.write("digraph G {") 54 | self.write("compound=true") 55 | if self.options.dag_control: 56 | self.write("rankdir=BT") 57 | if self.options.block_edges and not self.options.block_edges_helpers: 58 | # If we use cluster edges w/o intervening nodes, we need to bump 59 | # rank (vertical) separation, because otherwise there's very 60 | # little vert. space left to render edges after cutting out 61 | # cluster rectangle 62 | self.write("ranksep=1") 63 | self.write('label="Black edges - dataflow, red edges - control flow"') 64 | 65 | def edge(self, fro, to, extra=""): 66 | self.edges.append("\"%s\" -> \"%s\"%s" % (fro, to, extra)) 67 | 68 | def block_name(self, b): 69 | """Returns basic block name, i.e. its entry label, or made name 70 | if label if absent.""" 71 | if b.name: 72 | return b.name 73 | if b in self.anon_bblock_names: 74 | return self.anon_bblock_names[b] 75 | self.anon_bblock_cnt += 1 76 | n = "unk_block_%d" % self.anon_bblock_cnt 77 | self.anon_bblock_names[b] = n 78 | return n 79 | 80 | def instr_name(self, i): 81 | """Returns instruction name, for which result variable name is used. 82 | If result variable name is absent (void statement), make up name. 83 | """ 84 | if i in self.void_instr_names: 85 | return self.void_instr_names[i] 86 | n = i.name 87 | if not n: 88 | self.void_instr_cnt += 1 89 | n = "_%d" % self.void_instr_cnt 90 | self.void_instr_names[i] = n 91 | return n 92 | 93 | def declare_clusters(self): 94 | if self.options.block: 95 | # Pre-allocate label nodes to subgraphs, otherwise Graphviz puts them to wrong subgraphs 96 | for b in self.f.basic_blocks: 97 | name = self.block_name(b) 98 | # if not self.options.block_edges_helpers: 99 | if 1: 100 | self.write("subgraph \"cluster_%s\" {" % name) 101 | 102 | if not self.options.block_edges: 103 | self.write('\"%s\" [label="label: \"%s\""]' % (name, name)) 104 | elif self.options.block_edges_helpers: 105 | self.write('\"%s\" [shape=point height=0.02 width=0.02 color=red fixedsize=true]' % name) 106 | 107 | # if not self.options.block_edges_helpers: 108 | if 1: 109 | self.write("}") 110 | self.write() 111 | 112 | 113 | def render(self): 114 | # print `f` 115 | self.start_graph() 116 | self.declare_clusters() 117 | lab = 1 118 | for b in self.f.basic_blocks: 119 | block_name = self.block_name(b) 120 | self.edges = [] 121 | if self.options.block: 122 | self.write("subgraph \"cluster_%s\" {" % block_name) 123 | self.write("label=%s" % block_name) 124 | # if not self.options.block_edges: 125 | # self.write('\"%s\" [label="label: %s"]' % (block_name, block_name)) 126 | # elif self.options.block_edges_helpers: 127 | # self.write('\"%s\" [shape=point]' % (b.name)) 128 | 129 | # Create block entry label node and edge from it to first IR instruction 130 | if not self.options.block_edges or self.options.block_edges_helpers: 131 | attr = "[color=red]" 132 | if b.name == "entry": 133 | attr += "[weight=5]" 134 | if self.options.block_edges: 135 | attr += "[lhead=\"cluster_%s\"]" % block_name 136 | if self.options.control: 137 | if b.instructions[0].name == "": 138 | n = self.instr_name(b.instructions[0]) 139 | self.edge(block_name, n, attr) 140 | else: 141 | self.edge(block_name, b.instructions[0].name, attr) 142 | 143 | if self.options.dag_control: 144 | last_void_inst = block_name 145 | for i in b.instructions: 146 | if i.type == Type.void(): 147 | n = self.instr_name(i) 148 | # self.edge(last_void_inst, n, "[color=blue]") 149 | self.edge(n, last_void_inst, "[color=blue dir=back]") 150 | last_void_inst = n 151 | 152 | last_inst_name = None 153 | for i in b.instructions: 154 | n = self.instr_name(i) 155 | self.write('\"%s\" [label="%s"]' % (n, i)) 156 | if self.options.control: 157 | if last_inst_name: 158 | self.edge(last_inst_name, n, "[color=red weight=2]") 159 | else: 160 | if i.opcode_name == "br" and len(i.operands) == 1: 161 | self.edge(last_inst_name, n, "[color=red]") 162 | 163 | for a in i.operands: 164 | if isinstance(a, Constant) and not a.name: 165 | arg_val = a 166 | else: 167 | arg_val = a.name 168 | if i.opcode_name == "br" and type(a) is BasicBlock: 169 | # For jump targets, we jump from current node to label (arg) 170 | if self.options.block_edges and not self.options.block_edges_helpers: 171 | arg_val = a.instructions[0].name 172 | attrs = "[color=red]" 173 | if self.options.block_edges: 174 | attrs += "[color=red][lhead=\"cluster_%s\"][ltail=\"cluster_%s\"][weight=5]" % (a.name, block_name) 175 | if self.options.block_edges_helpers: 176 | attrs += "[arrowhead=none]" 177 | self.edge(n, arg_val, attrs) 178 | else: 179 | # For data, flow is from opearnd to operation 180 | self.edge(arg_val, n) 181 | last_inst_name = n 182 | if self.options.block: 183 | self.write("}") 184 | for e in self.edges: 185 | self.write(e) 186 | self.write() 187 | self.write("}") 188 | 189 | 190 | if __name__ == "__main__": 191 | optparser = optparse.OptionParser(usage="%prog ") 192 | optparser.add_option('-b', '--block', 193 | action="store_true", default=False, 194 | help="draw basic blocks as clusters (%default)") 195 | optparser.add_option('-c', '--control', 196 | action="store_true", 197 | help="draw explicit control flow based on instruction order (default)") 198 | optparser.add_option('', '--dag-control', 199 | action="store_true", 200 | help="analyze DAGs in a basic block and draw implied control flow among them (consider using --no-control)") 201 | optparser.add_option('', '--block-edges', 202 | action="store_true", default=False, 203 | help="(try to) draw inter-block edges between blocks, not between nodes") 204 | optparser.add_option('', '--block-edges-helpers', 205 | action="store_true", default=False, 206 | help="Add Graphviz-specific hacks to produce better layout") 207 | 208 | options, args = optparser.parse_args(sys.argv[1:]) 209 | if len(args) != 1: 210 | optparser.error("Wrong number of arguments") 211 | 212 | if not options.control and not options.dag_control: 213 | options.control = True 214 | 215 | with open(args[0]) as asm: 216 | mod = Module.from_assembly(asm) 217 | 218 | number_tmps(mod) 219 | 220 | for f in mod.functions: 221 | if not f.is_declaration: 222 | print("Writing %s.dot" % f.name) 223 | with open(f.name + ".dot", "w") as out: 224 | g = Graph(f, out, options) 225 | g.render() 226 | --------------------------------------------------------------------------------