Skip to content

Commit b0bcad7

Browse files
committed
Add a utility script for displaying the CFG of SIL or LLVM IR.
It is useful if you have the SIL already in a file (instead of calling viewCFG() while running the compiler in lldb). Especially useful for vim users: see the comment in the script on how to add commands to view the CFG from inside vim. Swift SVN r30186
1 parent 77ff5b7 commit b0bcad7

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

docs/DebuggingTheCompiler.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ debugging press <CTRL>-C on the LLDB prompt.
113113
Note that this only works in Xcode if the PATH variable in the scheme's
114114
environment setting contains the path to the dot tool.
115115

116+
Other Utilities
117+
```````````````
118+
119+
To view the CFG of a function (or code region) in a SIL file, you can use the
120+
script ``swift/utils/viewcfg``. It also works for LLVM IR files.
121+
The script reads the SIL (or LLVM IR) code from stdin and displays the dot
122+
graph file. Note: .dot files should be associated with the Graphviz app.
123+
124+
116125
Using Breakpoints
117126
`````````````````
118127

utils/viewcfg

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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

Comments
 (0)