#!/usr/bin/env python3 # 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. # # ---------------------------------------------------------------------------- import argparse import re import subprocess import sys import tempfile fontname = "helvetica" 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() if len(escaped_text) > 0: 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]+\w*\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 parse_args(): parser = argparse.ArgumentParser(description=""" Simple regex based transform of SIL and LLVM-IR into graphviz form. """) parser.add_argument("output_suffix", nargs='?') parser.add_argument("--disable_inst_dump", action='store_true') parser.add_argument("--input_file", type=argparse.FileType('r'), default=sys.stdin) parser.add_argument("--renderer", choices=["Graphviz", "dot"], default="Graphviz", help="Default is Graphviz") return parser.parse_args() def main(): args = parse_args() suffix = "" if args.output_suffix: suffix = args.output_suffix 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 args.input_file.readlines(): 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.update(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') out_file.write("""graph [fontname = "{font}"]; node [fontname = "{font}"]; edge [fontname = "{font}"];""".format(font=fontname)) for block in block_list: if block.content is None or args.disable_inst_dump: out_file.write( "\tNode" + str(block.index) + " [shape=record,color=gray,fontcolor=gray,label=\"{" + block.name + "}\"];\n") else: out_file.write( "\tNode" + str(block.index) + " [shape=record,label=\"{" + block.content + "}\"];\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 with the appropriate renderer. if args.renderer == 'Graphviz': subprocess.call(["open", "-a", "Graphviz", file_name]) elif args.renderer == 'dot': pdf = '%s.pdf' % file_name subprocess.call(["dot", "-Tpdf", file_name, '-o%s' % pdf]) subprocess.call(["open", "-a", "Preview", pdf]) else: raise RuntimeError('Unknown renderer?!') if __name__ == '__main__': main()