#!/usr/bin/env python # viewcfg - A script for viewing the CFG of SIL and LLVM IR -*- python -*- # # This source file is part of the Swift.org open source project # # Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See https://swift.org/LICENSE.txt for license information # See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors # # ---------------------------------------------------------------------------- # # For vim users: use the following lines in .vimrc... # # com! -nargs=? Funccfg silent ?{$?,/^}/w !viewcfg # com! -range -nargs=? Viewcfg silent ,w !viewcfg # # ...to add these commands: # # :Funccfg displays the CFG of the current SIL/LLVM function. # :Viewcfg displays the sub-CFG of the selected range. # # Note: viewcfg should be in the $PATH and .dot files should be associated # with the Graphviz app. # # ---------------------------------------------------------------------------- from __future__ import print_function import re import subprocess import sys import tempfile def help(): print("""\ Usage: viewcfg [output-suffix] < file By default all CFGs are opened in the same window. Use the a unique output-suffix to open a CFG in a new window. """) class Block(object): current_index = 0 def __init__(self, name, preds): self.name = name self.content = None self.preds = [] self.succs = None self.last_line = None self.index = Block.current_index Block.current_index += 1 if preds is not None: for pred in re.split("[, %]", preds): can_pred = pred.strip() if can_pred: self.preds.append(can_pred) def add_line(self, text): if self.content is None: self.content = "" escaped_text = re.sub(r'([\\<>{}"|])', r'\\\1', text[0:80]).rstrip() self.content += escaped_text + '\\l' self.last_line = text def get_succs(self): if self.succs is None: self.succs = [] if self.last_line is not None: for match in re.finditer(r'\bbb[0-9]+\b', self.last_line): self.succs.append(match.group()) it = re.finditer(r'\blabel %"?([^\s"]+)\b', self.last_line) for match in it: self.succs.append(match.group(1)) return self.succs def main(): suffix = "" if len(sys.argv) >= 2: if sys.argv[1].startswith('-'): help() return suffix = sys.argv[1] block_list = [] block_map = {} cur_block = None sil_block_pattern = re.compile(r'^(\S+)(\(.*\))?: *(\/\/ *Preds:(.*))?$') llvm_block_pattern1 = re.compile(r'^"?([^\s"]+)"?: *; *preds =(.*)?$') llvm_block_pattern2 = re.compile(r'^(\d+):? *; *preds =(.*)?$') # Scan the input file. for line in sys.stdin: sil_block_match = sil_block_pattern.match(line) llvm_block_match1 = llvm_block_pattern1.match(line) llvm_block_match2 = llvm_block_pattern2.match(line) block_name = None preds = None if sil_block_match: block_name = sil_block_match.group(1) preds = sil_block_match.group(4) elif llvm_block_match1: block_name = llvm_block_match1.group(1) preds = llvm_block_match1.group(2) elif llvm_block_match2: block_name = llvm_block_match2.group(1) preds = llvm_block_match2.group(2) elif line.startswith(' '): if cur_block is not None: cur_block.add_line(line) elif not line[:1].isspace(): if line.startswith('}') and block_map: break cur_block = None if block_name is not None: cur_block = Block(block_name, preds) cur_block.add_line(line) block_list.append(cur_block) block_map[block_name] = cur_block # Add empty blocks which we didn't see, but which are referenced. new_blocks = {} for block in block_list: for adj_name in (block.preds + block.get_succs()): if adj_name not in block_map: new_blocks[adj_name] = Block(adj_name, None) block_map = dict(block_map.items() + new_blocks.items()) # Add missing edges if we didn't see a successor in the terminator # but the block is mentioned in the pred list of the successor. for block in block_list: for pred_name in block.preds: pred_block = block_map[pred_name] if block.name not in pred_block.get_succs(): pred_block.get_succs().append(block.name) # Write the output dot file. file_name = tempfile.gettempdir() + "/viewcfg" + suffix + ".dot" with open(file_name, 'w') as out_file: out_file.write('digraph "CFG" {\n') for block in block_list: if block.content is not None: out_file.write( "\tNode" + str(block.index) + " [shape=record,label=\"{" + block.content + "}\"];\n") else: out_file.write( "\tNode" + str(block.index) + " [shape=record,color=gray,fontcolor=gray,label=\"{" + block.name + "}\"];\n") for succ_name in block.get_succs(): succ_block = block_map[succ_name] out_file.write( "\tNode" + str(block.index) + " -> Node" + str(succ_block.index) + ";\n") out_file.write("}\n") # Open the dot file. subprocess.call(["open", "-a", "Graphviz", file_name]) if __name__ == '__main__': main()