mirror of
https://github.com/apple/swift.git
synced 2026-02-27 18:26:24 +01:00
Some of our unit tests now have non-numeric block IDs, like "bb1a". While that may not be ideal, it seems harmless to tweak this regex to handle those cases.
193 lines
6.6 KiB
Python
Executable File
193 lines
6.6 KiB
Python
Executable File
#!/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 <args>
|
|
# com! -range -nargs=? Viewcfg silent <line1>,<line2>w !viewcfg <args>
|
|
#
|
|
# ...to add these commands:
|
|
#
|
|
# :Funccfg displays the CFG of the current SIL/LLVM function.
|
|
# :<range>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 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("--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 = 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')
|
|
out_file.write("""graph [fontname = "{font}"];
|
|
node [fontname = "{font}"];
|
|
edge [fontname = "{font}"];""".format(font=fontname))
|
|
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 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()
|