Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(95)

Side by Side Diff: tools/graphviz-create.py

Issue 11363163: Utility Graphviz-create.py. It looks at hydrogen.cfg files and creates graphviz (dot) output of var… Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Review comments Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright 2012 the V8 project authors. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following
12 # disclaimer in the documentation and/or other materials provided
13 # with the distribution.
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived
16 # from this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import collections
31 import glob
32 import optparse
33 import os
34 from os.path import join
35 import shlex
36 import subprocess
37 import sys
38 import tempfile
39
40 # Parse() returns:
41 # a map of compilations (functions)
42 # each compilation has a map of phases, and a name
43 # each phase has a map of blocks, and a name
44 # each block has a map of locals, and a map of statements
45 # functions, phases, blocks, statements
46 #
47 # The first statement of foo, in final phase
48 # data["foo"]["Z_Code generation"]["B0"][0]
49 #
50 # a phase is the typical area of work. Offers
51 # phase.visit_statements(visitor) - visit every statement in the phase
52 # phase.visit_blocks(visitor)
53 #
54 # of course,
55 # blocks.visit_statements(visitor)
56 # blocks.visit_locals(visitor)
57 #
58
59
60 USAGE="""usage: %prog [OPTION]...
61
62 Looks at a hydrogen cfg file and produces graphviz (ie, dot) output.
63 Output is directed to stdout for further customization before creating
64 a graph.
65
66 Examples:
67 # Run on the last function defined in the file, looking at the final
68 # HIR
69 $ %prog > output.dot
70
71 # Create the graphic too, this time with HIR, all on one line
72 $ %prog --displayHIR | dot -Tpng > out.png
73
74 # Examine a different phase of compilation for the last function
75 # Output to the screen
76 $ %prog --phase="H_Redundant phi elimination" --displayHIR
77
78 # Create an html report of the last function, with all HIR
79 $ %prog --command="report"
80 """
81
82
83 def OrderedDictionary():
84 return collections.OrderedDict()
85
86
87 def diemsg(msg):
88 print "Unexpected error: %s" % msg
89 sys.exit(1)
90
91
92 class Block(object):
93 """A compilation block."""
94
95 def __init__(self, name, predecessors, successors):
96 self.name = name
97 self.predecessors = predecessors
98 self.successors = successors
99 self.hir = OrderedDictionary()
100 self.lir = OrderedDictionary()
101 self.locals = OrderedDictionary()
102
103 def GetName(self):
104 return self.name
105
106 def GetPredecessors(self):
107 return self.predecessors
108
109 def GetSuccessors(self):
110 return self.successors
111
112 def AddHIR(self,statement):
113 self.hir[len(self.hir)] = statement
114
115 def GetHIRs(self):
116 return self.hir.values()
117
118 def AddLIR(self,statement):
119 self.lir[len(self.lir)] = statement
120
121 def GetLIRs(self):
122 return self.lir.values()
123
124 def visit(self,visitor):
125 """Visit every HIR statement in the block."""
126 for h in hir.values():
127 visitor(h)
128
129 def AddLocal(self,name,local):
130 self.locals[name] = local
131
132 def visit_locals(self,visitor):
133 for l in self.locals.values():
134 visitor(l)
135
136 def GetLocals(self):
137 return self.locals.values()
138
139 def __str__(self):
140 return "%s (pred: %d) (succ: %d)" % (
141 self.name,
142 len(self.predecessors),
143 len(self.successors))
144
145
146 class Phase(object):
147 """A compilation phase."""
148
149 def __init__(self, name):
150 self.name = name
151 self.blocks = OrderedDictionary()
152
153 def GetName(self):
154 return self.name
155
156 def AddBlock(self,name,block):
157 self.blocks[name] = block
158
159 def GetBlocks(self):
160 return self.blocks.values()
161
162 def GetBlock(self,block):
163 return self.blocks[block]
164
165 def visit_statements(self,visitor):
166 for block in self.blocks:
167 block.visit(visitor)
168
169 def visit_blocks(self,visitor):
170 for block in self.blocks.values():
171 visitor(block)
172
173 def Print(self,p):
174 p.write(self + "\n");
175 for block in self.blocks.values():
176 p.write(block + "\n")
177
178 def __str__(self):
179 return "%s (%d blocks)" % (
180 self.name,
181 len(self.blocks))
182
183
184 class Compilation(object):
185 """A compilation unit."""
186
187 def __init__(self, name, method):
188 self.phases = OrderedDictionary()
189 self.name = name
190 self.method = method
191
192 def AddPhase(self, name, phase):
193 self.phases[name] = phase
194
195 def AllPhases(self):
196 for phase in self.phases.itervalues():
197 yield phase
198
199 def AllPhaseNames(self):
200 return self.phases.keys()
201
202 def GetName(self):
203 return self.name
204
205 def GetPhase(self,phase):
206 return self.phases[phase]
207
208 def GetPreviousPhase(self,phase):
209 # useful for diffs
210 index = self.phases.keys().index(phase)
211 if index == 0:
212 diemsg("No previous phase of " + phase)
213 return self.phases[self.phases.keys()[index - 1]]
214
215 def Print(self,p):
216 printer.write("Compilation %s\n" % self.name)
217 for phase in self.AllPhases():
218 phase.Print(p)
219
220 def __str__(self):
221 return "%s (%d phases)" % (
222 self.name,
223 len(self.phases))
224
225
226 def BuildOptions():
227 result = optparse.OptionParser(USAGE)
228 result.add_option("--input",
229 help="Input file (default %default)",
230 default="hydrogen.cfg")
231 result.add_option("--command",
232 help="The command to run: 'diff','report','%default' (default)",
233 default="blocks")
234 result.add_option("--function",
235 help="The function to analyze (default %default)",
236 default="@last")
237 result.add_option("--phase",
238 help="The compilation phase to examine (default %default)",
239 default="Z_Code generation")
240 result.add_option("--displayHIR",
241 help="Display HIR in blocks? (default %default)",
242 default=False, action="store_true")
243 result.add_option("--displayLIR",
244 help="Display LIR in blocks? (default %default)",
245 default=False, action="store_true")
246 result.add_option("-v", "--verbose", help="Verbose output (default %default)",
247 default=False, action="store_true")
248 result.add_option("-d", "--debug", help="Debug output (default %default)",
249 default=False, action="store_true")
250 return result
251
252
253 def ProcessOptions(options):
254 if not options.command in ["blocks","diff","report"]:
255 print "Unknown command %s" % options.command
256 return False
257 return True
258
259
260 def parseKeyString(line):
261 tokens = shlex.split(line.strip())
262 return tokens
263
264
265 class CFGReader(object):
266 """CFG file reader."""
267
268 def __init__(self, filename):
269 self.file = open(filename, "r")
270 self.savedline = None
271
272 def Dispose(self):
273 self.log.close()
274 self.log_file.close()
275
276 def pushline(self,line):
277 self.savedline = line
278
279 def nextline(self):
280 # we can push one line back
281 if self.savedline:
282 s = self.savedline
283 self.savedline = None
284 return s
285
286 return self.file.readline()
287
288 def Parse(self):
289 """Return a map of compilation units."""
290 compilations = OrderedDictionary()
291 while True:
292 compilation = self.ParseCompilation()
293 if not compilation:
294 break
295 compilations[compilation.name] = compilation
296 return compilations
297
298 def ParseCompilation(self):
299 line = self.nextline()
300 if not line:
301 return None
302 assert "begin_compilation" == line.strip()
303 pair = parseKeyString(self.nextline())
304 assert"name" == pair[0]
305 name = pair[1]
306 compilation = Compilation(name,name)
307 # now read into configs
308 while line.strip() != "end_compilation":
309 line = self.nextline()
310 if not line:
311 diemsg("Unexpected end of file")
312
313 while True:
314 phase = self.ParsePhase()
315 if not phase:
316 break
317 compilation.AddPhase(phase.name,phase)
318 return compilation
319
320 def ParseHIRs(self,block):
321 while True:
322 line = self.nextline().strip()
323 if line == "end_HIR":
324 break
325 # we don't need the first number ("0 ") and some symbols at the end
326 line = line[2:]
327 line = line.replace("<|@","")
328 block.AddHIR(line)
329
330 def ParseLIRs(self,block):
331 while True:
332 line = self.nextline().strip()
333 if line == "end_LIR":
334 break
335 # we don't need some symbols at the end
336 line = line.replace("<|@","")
337 block.AddLIR(line)
338
339 def ParseLocals(self,block):
340 while True:
341 line = self.nextline()
342 line = line.strip()
343 if line == "end_locals":
344 break
345 if len(line) > 0 and (line[0]>='0' and line[0]<='9'):
346 keys = parseKeyString(line)
347 block.AddLocal(keys[1],line)
348
349 def ParseBlock(self):
350 line = self.nextline()
351 if not line:
352 return None
353
354 if line.strip() != "begin_block":
355 self.pushline(line)
356 return None
357
358 assert "begin_block" == line.strip()
359 pair = parseKeyString(self.nextline())
360 assert "name" == pair[0]
361 name = pair[1]
362 # read pred and succ
363 while True:
364 line = self.nextline()
365 if line.strip() == "begin_states":
366 break;
367 pair = parseKeyString(line)
368 if pair[0] == "predecessors":
369 pred = pair[1:]
370 elif pair[0] == "successors":
371 succ = pair[1:]
372
373 block = Block(name,pred,succ)
374 # add hrs if found
375 while True:
376 line = self.nextline().strip()
377 if line == "end_block":
378 break;
379 elif line == "begin_locals":
380 self.ParseLocals(block)
381 elif line == "begin_LIR":
382 self.ParseLIRs(block)
383 elif line == "begin_HIR":
384 self.ParseHIRs(block)
385
386 return block
387
388 def ParsePhase(self):
389 line = self.nextline()
390 if not line:
391 return None
392
393 if line.strip() != "begin_cfg":
394 self.pushline(line)
395 return None
396
397 assert "begin_cfg" == line.strip()
398 pair = parseKeyString(self.nextline())
399 assert "name" == pair[0]
400 name = pair[1]
401 phase = Phase(name)
402
403 while True:
404 block = self.ParseBlock()
405 if not block:
406 break
407 phase.AddBlock(block.name,block)
408
409 # just consume blocks
410 # consume the "end_cfg" message
411 assert "end_cfg" == self.nextline().strip()
412 return phase
413
414
415 def DumpCompilations(compilations,p):
416 p.write("len = %d\n" % len(compilations))
417 for comp in compilations.itervalues():
418 comp.Print(p)
419
420
421 def WriteTempFile(lines):
422 temp = tempfile.NamedTemporaryFile(mode="w+t")
423 for line in lines:
424 temp.write(line)
425 temp.write("\n")
426 temp.seek(0)
427 return temp
428
429
430 def diffText(lines1,lines2):
431 """Takes two lists of text lines, creates a diff output of them as an array"""
432
433 try:
434 temp1 = WriteTempFile(lines1)
435 temp2 = WriteTempFile(lines2)
436 outputlines = []
437 cmdline = "diff -u %s %s" % (temp1.name, temp2.name)
438 process = subprocess.Popen(cmdline,
439 shell=True,
440 stdout=subprocess.PIPE,
441 stderr=subprocess.STDOUT)
442 pipe = process.stdout
443 try:
444 headerLines = 2
445 lineCount = 0
446 for line in pipe:
447 if lineCount > headerLines:
448 # strip newline
449 outputlines.append(line.strip())
450 lineCount = lineCount + 1
451 except Exception as e:
452 diemsg("error in diff = " + str(e))
453 finally:
454 pipe.close()
455
456 finally:
457 temp1.close()
458 temp2.close()
459
460 return outputlines
461
462
463 def Color(color,line):
464 return "<font color=\"%s\">%s</font>" % (color,line)
465
466
467 def LeftLineBreak():
468 return "<br align=\"left\"/>"
469
470
471 def HtmlLikeEscape(line):
472 return line.replace("<","&lt;").replace(">","&gt;")
473
474
475 def TableBegin():
476 return "<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">\n"
477
478
479 def TableEnd():
480 return "</table>\n"
481
482
483 def NameRow(blockName):
484 return "<tr><td><b>%s</b></td></tr>\n" % blockName
485
486
487 def FormatLine(line):
488 tesc = HtmlLikeEscape(line)
489 # Deal with diff output
490 if tesc.startswith("-"):
491 tesc = Color("red",tesc)
492 elif tesc.startswith("+"):
493 tesc = Color("blue",tesc)
494 return " %s%s\n" % (tesc, LeftLineBreak())
495
496
497 def FormatLines(lineList):
498 result = ""
499 if len(lineList) > 0:
500 result += "<tr><td>\n"
501 for l in lineList:
502 result = result + FormatLine(l)
503 result = result + "</td></tr>\n"
504 return result
505
506
507 def FormatLabel(blockName,localsList,hirList,lirList):
508 label = TableBegin()
509 label += NameRow(blockName)
510 label += FormatLines(localsList)
511 label += FormatLines(hirList)
512 label += FormatLines(lirList)
513 label += TableEnd()
514 return label
515
516
517 def DotBlockVisit(displayHIR,displayLIR,p,block):
518 localsList = []
519 hirList = []
520 lirList = []
521
522 if displayHIR or displayLIR:
523 localsList = block.GetLocals()
524 if displayHIR:
525 hirList = block.GetHIRs()
526 if displayLIR:
527 lirList = block.GetLIRs()
528
529 label = FormatLabel(block.GetName(),localsList,hirList,lirList)
530 # extra newline for readability
531 label = "\n" + label
532 p.write(" block%s [shape=plaintext,label=<%s>];\n"
533 % (block.GetName(), label))
534 for succ in block.GetSuccessors():
535 p.write(" block%s -> block%s;\n" % (block.GetName(), succ))
536
537
538 def DotBlocks(functionName,phase,displayHIR,displayLIR,p):
539 """Diagram the blocks in the given function."""
540 p.write("digraph Blocks_%s {\n" % functionName)
541 p.write(" ratio = 1.0;\n")
542 phase.visit_blocks(lambda b: DotBlockVisit(displayHIR,displayLIR,p,b))
543 p.write("}\n")
544
545
546 def DotDiffVisit(diffs,p,block):
547 label = FormatLabel(block.GetName(),[],diffs[block.GetName()],[])
548 p.write(" block%s [shape=plaintext,label=<%s>];\n"
549 % (block.GetName(), label))
550 for succ in block.GetSuccessors():
551 p.write(" block%s -> block%s;\n" % (block.GetName(), succ))
552
553
554 def DotDiff(functionName, phasePrev, phase, changedBlocksOnly, p):
555 """Run a diff block by block between two phases in the given function."""
556 p.write("// comparing phase %s with next phase %s\n" % (phasePrev.GetName(),
557 phase.GetName()))
558 p.write("digraph Diff_%s {\n" % functionName)
559 p.write(" ratio = 1.0;\n")
560 # need to run diff -u phasePrev phase
561 # on the HIR output
562 # I think the # of blocks should be the same
563 diffResults = {}
564 blocks = phasePrev.GetBlocks()
565 for blockPrev in blocks:
566 block = phase.GetBlock(blockPrev.GetName())
567 diffResults[blockPrev.GetName()] = diffText(blockPrev.GetHIRs(),
568 block.GetHIRs())
569
570 phasePrev.visit_blocks(lambda b: DotDiffVisit(diffResults,p,b))
571 p.write("}\n")
572
573
574 reportText = """<html>
575 <title>Report on %s</title>
576 <body>
577 <h2>Block structure</h2>
578 <a href=blocks.svg><img width=800 src=blocks.svg></a>
579 <h2>All HIR</h2>
580 <a href=hirblocks.svg><img width=800 src=hirblocks.svg></a>
581 <h2>All LIR</h2>
582 <a href=lirblocks.svg><img width=800 src=lirblocks.svg></a>
583 <h2>Diffs</h2>
584 %s
585 </body>
586 </html>"""
587
588
589 diffsTemplate = """<h2>%s</h2>
590 <a href=%s><img width=800 src=%s></a>"""
591
592
593 def AsFilename(phaseName,ext):
594 phaseName = phaseName.replace(" ","_")
595 phaseName += ext
596 return phaseName
597
598
599 def DotReport(function):
600 """Make a report on the function."""
601 function_name = function.GetName().replace(".","_")
602 directory_name = "report_%s" % function_name
603 if not os.path.exists(directory_name):
604 os.mkdirs(directory_name)
605
606 htmlfilename = directory_name + "/index.html"
607 with open(htmlfilename, "w+t") as p:
608 print "Writing %s..." % htmlfilename
609 diffs = ""
610 phases = function.AllPhaseNames()
611 for ph in phases[1:]:
612 phfilename = AsFilename(ph,".svg")
613 diffs += diffsTemplate % (ph,phfilename,phfilename)
614 htmlText = reportText % (function_name,diffs)
615 p.write(htmlText + "\n")
616
617
618 # Write all dot files
619 # get last phase
620 phase = function.GetPhase(phases[-1])
621 with open(directory_name + "/blocks.dot", "w+t") as p:
622 DotBlocks(function_name,phase,False,False,p)
623
624 with open(directory_name + "/hirblocks.dot", "w+t") as p:
625 DotBlocks(function_name,phase,True,False,p)
626
627 with open(directory_name + "/lirblocks.dot", "w+t") as p:
628 DotBlocks(function_name,phase,False,True,p)
629
630 print "Computing diffs..."
631
632 for ph in phases[1:]:
633 phase = function.GetPhase(ph)
634 phase_previous = function.GetPreviousPhase(ph)
635 with open(directory_name + "/" + AsFilename(ph,".dot"), "w+t") as p:
636 DotDiff(function_name, phase_previous, phase, True, p)
637
638 # Compile the dot files
639 command_line = "dot -Tsvg %s > %s"
640 # diff -u %s %s" % (temp1.name, temp2.name)
641 files = glob.glob(directory_name + "/*.dot")
642 for f in files:
643 print "compiling " + f
644 process = subprocess.Popen(command_line % (f,f.replace(".dot",".svg")),
645 shell=True)
646 process.wait()
647 print "done"
648
649
650 def Main():
651 parser = BuildOptions()
652 (options, args) = parser.parse_args()
653 if not ProcessOptions(options):
654 parser.print_help()
655 return 1
656
657 # Open input file and parse compilations, phases, blocks
658 reader = CFGReader(options.input)
659 compilations = reader.Parse()
660
661 if options.debug:
662 DumpCompilations(compilations, sys.stdout)
663
664 function = options.function
665 if function == "@last":
666 function = compilations.keys()[-1]
667 elif function == "@first":
668 function = compilations.keys()[0]
669 if options.verbose:
670 print "// dump function %s" % function
671
672 phase = options.phase
673
674 # find the method
675 if not function in compilations:
676 diemsg("function " + function + " not found in input file")
677
678 function = compilations[function]
679 # find the phase
680 phase = function.GetPhase(phase)
681
682 # now what we do depends on the command
683
684 function_name = function.GetName().replace(".","_")
685 if options.command == "blocks":
686 DotBlocks(function_name, phase, options.displayHIR,
687 options.displayLIR, sys.stdout)
688 elif options.command == "diff":
689 # take the phase and the preceding phase.
690 previous_phase = function.GetPreviousPhase(phase.GetName())
691 DotDiff(function_name, previous_phase, phase, True, sys.stdout)
692 elif options.command == "report":
693 DotReport(function)
694 return 0
695
696
697 if __name__ == "__main__":
698 sys.exit(Main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698