|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +# A script for viewing the CFG of SIL and llvm IR. |
| 4 | + |
| 5 | +# For vim users: use the following lines in .vimrc |
| 6 | +# |
| 7 | +# com! -nargs=? Funccfg silent ?{$?,/^}/w !viewcfg <args> |
| 8 | +# com! -range -nargs=? Viewcfg silent <line1>,<line2>w !viewcfg <args> |
| 9 | +# |
| 10 | +# to add these commands: |
| 11 | +# |
| 12 | +# :Funccfg displays the CFG of the current SIL/llvm function. |
| 13 | +# :<range>Viewcfg displays the sub-CFG of the selected range. |
| 14 | +# |
| 15 | +# Note: viewcfg should be in the $PATH and .dot files should be associated |
| 16 | +# with the Graphviz app. |
| 17 | + |
| 18 | +import re |
| 19 | +import sys |
| 20 | +import tempfile |
| 21 | +import subprocess |
| 22 | +import os |
| 23 | + |
| 24 | +def help(): |
| 25 | + print """\ |
| 26 | +Usage: |
| 27 | +
|
| 28 | +viewcfg [output-suffix] < file |
| 29 | +
|
| 30 | +By default all CFGs are opened in the same window. |
| 31 | +Use the a unique output-suffix to open a CFG in a new window. |
| 32 | +""" |
| 33 | + |
| 34 | +class Block: |
| 35 | + |
| 36 | + currentIndex = 0 |
| 37 | + |
| 38 | + def __init__(self, name, preds): |
| 39 | + self.name = name |
| 40 | + self.content = None |
| 41 | + self.lastLineContent = None |
| 42 | + self.preds = [] |
| 43 | + self.succs = None |
| 44 | + self.lastLine = None |
| 45 | + self.index = Block.currentIndex |
| 46 | + Block.currentIndex += 1 |
| 47 | + if preds is not None: |
| 48 | + for pred in re.split("[, %]", preds): |
| 49 | + canPred = pred.strip() |
| 50 | + if canPred: |
| 51 | + self.preds.append(canPred) |
| 52 | + |
| 53 | + def addLine(self, text): |
| 54 | + if self.content is None: |
| 55 | + self.content = "" |
| 56 | + escapedText = re.sub(r'([\\<>{}"|])', r'\\\1', text[0:80]).rstrip() |
| 57 | + self.content += escapedText + '\\l' |
| 58 | + self.lastLine = text |
| 59 | + |
| 60 | + def getSuccs(self): |
| 61 | + if self.succs is None: |
| 62 | + self.succs = [] |
| 63 | + if self.lastLine is not None: |
| 64 | + for match in re.finditer(r'\bbb[0-9]+\b', self.lastLine): |
| 65 | + self.succs.append(match.group()) |
| 66 | + |
| 67 | + for match in re.finditer(r'\blabel %(\S+)\b', self.lastLine): |
| 68 | + self.succs.append(match.group(1)) |
| 69 | + |
| 70 | + return self.succs |
| 71 | + |
| 72 | +def main(): |
| 73 | + suffix = "" |
| 74 | + if len(sys.argv) >= 2: |
| 75 | + if sys.argv[1].startswith('-'): |
| 76 | + help() |
| 77 | + return |
| 78 | + suffix = sys.argv[1] |
| 79 | + |
| 80 | + blocks = { } |
| 81 | + curBlock = None |
| 82 | + silBlockPattern = re.compile(r'^(\S+)(\(.*\))?: *(\/\/ *Preds:(.*))?$') |
| 83 | + llvmBlockPattern1 = re.compile(r'^(\S+): *; *preds =(.*)?$') |
| 84 | + llvmBlockPattern2 = re.compile(r'^; <label>:(\d+) *; *preds =(.*)?$') |
| 85 | + |
| 86 | + # Scan the input file. |
| 87 | + |
| 88 | + for line in sys.stdin: |
| 89 | + silBlockMatch = silBlockPattern.match(line) |
| 90 | + llvmBlockMatch1 = llvmBlockPattern1.match(line) |
| 91 | + llvmBlockMatch2 = llvmBlockPattern2.match(line) |
| 92 | + blockName = None |
| 93 | + preds = None |
| 94 | + if silBlockMatch: |
| 95 | + blockName = silBlockMatch.group(1) |
| 96 | + preds = silBlockMatch.group(4) |
| 97 | + elif llvmBlockMatch1: |
| 98 | + blockName = llvmBlockMatch1.group(1) |
| 99 | + preds = llvmBlockMatch1.group(2) |
| 100 | + elif llvmBlockMatch2: |
| 101 | + blockName = llvmBlockMatch2.group(1) |
| 102 | + preds = llvmBlockMatch2.group(2) |
| 103 | + elif line.startswith(' '): |
| 104 | + if curBlock is not None: |
| 105 | + curBlock.addLine(line) |
| 106 | + elif not line[:1].isspace(): |
| 107 | + if line.startswith('}') and blocks: |
| 108 | + break |
| 109 | + curBlock = None |
| 110 | + |
| 111 | + if blockName is not None: |
| 112 | + curBlock = Block(blockName, preds) |
| 113 | + curBlock.addLine(line) |
| 114 | + blocks[blockName] = curBlock |
| 115 | + |
| 116 | + |
| 117 | + # Add empty blocks which we didn't see, but which are referenced. |
| 118 | + |
| 119 | + newBlocks = { } |
| 120 | + for name, block in blocks.iteritems(): |
| 121 | + for adjName in (block.preds + block.getSuccs()): |
| 122 | + if not adjName in blocks: |
| 123 | + newBlocks[adjName] = Block(adjName, None) |
| 124 | + |
| 125 | + blocks = dict(blocks.items() + newBlocks.items()) |
| 126 | + |
| 127 | + # Add missing edges if we didn't see a successor in the terminator |
| 128 | + # but the block is mentioned in the pred list of the successor. |
| 129 | + |
| 130 | + for name, block in blocks.iteritems(): |
| 131 | + for predName in block.preds: |
| 132 | + predBlock = blocks[predName] |
| 133 | + if not name in predBlock.getSuccs(): |
| 134 | + predBlock.getSuccs().append(name) |
| 135 | + |
| 136 | + # Write the output dot file. |
| 137 | + |
| 138 | + fileName = tempfile.gettempdir() + "/viewcfg" + suffix + ".dot" |
| 139 | + outFile = open(fileName, "w") |
| 140 | + |
| 141 | + outFile.write('digraph "CFG" {\n') |
| 142 | + for name, block in blocks.iteritems(): |
| 143 | + if block.content is not None: |
| 144 | + outFile.write("\tNode" + str(block.index) + \ |
| 145 | + " [shape=record,label=\"{" + block.content + "}\"];\n") |
| 146 | + else: |
| 147 | + outFile.write("\tNode" + str(block.index) + \ |
| 148 | + " [shape=record,color=gray,fontcolor=gray,label=\"{" + \ |
| 149 | + block.name + "}\"];\n") |
| 150 | + |
| 151 | + for succName in block.getSuccs(): |
| 152 | + succBlock = blocks[succName] |
| 153 | + outFile.write("\tNode" + str(block.index) + " -> Node" + \ |
| 154 | + str(succBlock.index) + ";\n") |
| 155 | + |
| 156 | + outFile.write("}\n") |
| 157 | + outFile.flush() |
| 158 | + os.fsync(outFile.fileno()) |
| 159 | + outFile.close |
| 160 | + |
| 161 | + # Open the dot file (should be done with Graphviz). |
| 162 | + |
| 163 | + subprocess.call(["open", fileName]) |
| 164 | + |
| 165 | +main() |
| 166 | + |
0 commit comments