| Index: tools/graphviz-create.py
|
| diff --git a/tools/graphviz-create.py b/tools/graphviz-create.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..d1d61536b1b7a8fd2ac693da5c986ca3d740de49
|
| --- /dev/null
|
| +++ b/tools/graphviz-create.py
|
| @@ -0,0 +1,698 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# Copyright 2012 the V8 project authors. All rights reserved.
|
| +# Redistribution and use in source and binary forms, with or without
|
| +# modification, are permitted provided that the following conditions are
|
| +# met:
|
| +#
|
| +# * Redistributions of source code must retain the above copyright
|
| +# notice, this list of conditions and the following disclaimer.
|
| +# * Redistributions in binary form must reproduce the above
|
| +# copyright notice, this list of conditions and the following
|
| +# disclaimer in the documentation and/or other materials provided
|
| +# with the distribution.
|
| +# * Neither the name of Google Inc. nor the names of its
|
| +# contributors may be used to endorse or promote products derived
|
| +# from this software without specific prior written permission.
|
| +#
|
| +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| +
|
| +import collections
|
| +import glob
|
| +import optparse
|
| +import os
|
| +from os.path import join
|
| +import shlex
|
| +import subprocess
|
| +import sys
|
| +import tempfile
|
| +
|
| +# Parse() returns:
|
| +# a map of compilations (functions)
|
| +# each compilation has a map of phases, and a name
|
| +# each phase has a map of blocks, and a name
|
| +# each block has a map of locals, and a map of statements
|
| +# functions, phases, blocks, statements
|
| +#
|
| +# The first statement of foo, in final phase
|
| +# data["foo"]["Z_Code generation"]["B0"][0]
|
| +#
|
| +# a phase is the typical area of work. Offers
|
| +# phase.visit_statements(visitor) - visit every statement in the phase
|
| +# phase.visit_blocks(visitor)
|
| +#
|
| +# of course,
|
| +# blocks.visit_statements(visitor)
|
| +# blocks.visit_locals(visitor)
|
| +#
|
| +
|
| +
|
| +USAGE="""usage: %prog [OPTION]...
|
| +
|
| +Looks at a hydrogen cfg file and produces graphviz (ie, dot) output.
|
| +Output is directed to stdout for further customization before creating
|
| +a graph.
|
| +
|
| +Examples:
|
| + # Run on the last function defined in the file, looking at the final
|
| + # HIR
|
| + $ %prog > output.dot
|
| +
|
| + # Create the graphic too, this time with HIR, all on one line
|
| + $ %prog --displayHIR | dot -Tpng > out.png
|
| +
|
| + # Examine a different phase of compilation for the last function
|
| + # Output to the screen
|
| + $ %prog --phase="H_Redundant phi elimination" --displayHIR
|
| +
|
| + # Create an html report of the last function, with all HIR
|
| + $ %prog --command="report"
|
| +"""
|
| +
|
| +
|
| +def OrderedDictionary():
|
| + return collections.OrderedDict()
|
| +
|
| +
|
| +def diemsg(msg):
|
| + print "Unexpected error: %s" % msg
|
| + sys.exit(1)
|
| +
|
| +
|
| +class Block(object):
|
| + """A compilation block."""
|
| +
|
| + def __init__(self, name, predecessors, successors):
|
| + self.name = name
|
| + self.predecessors = predecessors
|
| + self.successors = successors
|
| + self.hir = OrderedDictionary()
|
| + self.lir = OrderedDictionary()
|
| + self.locals = OrderedDictionary()
|
| +
|
| + def GetName(self):
|
| + return self.name
|
| +
|
| + def GetPredecessors(self):
|
| + return self.predecessors
|
| +
|
| + def GetSuccessors(self):
|
| + return self.successors
|
| +
|
| + def AddHIR(self,statement):
|
| + self.hir[len(self.hir)] = statement
|
| +
|
| + def GetHIRs(self):
|
| + return self.hir.values()
|
| +
|
| + def AddLIR(self,statement):
|
| + self.lir[len(self.lir)] = statement
|
| +
|
| + def GetLIRs(self):
|
| + return self.lir.values()
|
| +
|
| + def visit(self,visitor):
|
| + """Visit every HIR statement in the block."""
|
| + for h in hir.values():
|
| + visitor(h)
|
| +
|
| + def AddLocal(self,name,local):
|
| + self.locals[name] = local
|
| +
|
| + def visit_locals(self,visitor):
|
| + for l in self.locals.values():
|
| + visitor(l)
|
| +
|
| + def GetLocals(self):
|
| + return self.locals.values()
|
| +
|
| + def __str__(self):
|
| + return "%s (pred: %d) (succ: %d)" % (
|
| + self.name,
|
| + len(self.predecessors),
|
| + len(self.successors))
|
| +
|
| +
|
| +class Phase(object):
|
| + """A compilation phase."""
|
| +
|
| + def __init__(self, name):
|
| + self.name = name
|
| + self.blocks = OrderedDictionary()
|
| +
|
| + def GetName(self):
|
| + return self.name
|
| +
|
| + def AddBlock(self,name,block):
|
| + self.blocks[name] = block
|
| +
|
| + def GetBlocks(self):
|
| + return self.blocks.values()
|
| +
|
| + def GetBlock(self,block):
|
| + return self.blocks[block]
|
| +
|
| + def visit_statements(self,visitor):
|
| + for block in self.blocks:
|
| + block.visit(visitor)
|
| +
|
| + def visit_blocks(self,visitor):
|
| + for block in self.blocks.values():
|
| + visitor(block)
|
| +
|
| + def Print(self,p):
|
| + p.write(self + "\n");
|
| + for block in self.blocks.values():
|
| + p.write(block + "\n")
|
| +
|
| + def __str__(self):
|
| + return "%s (%d blocks)" % (
|
| + self.name,
|
| + len(self.blocks))
|
| +
|
| +
|
| +class Compilation(object):
|
| + """A compilation unit."""
|
| +
|
| + def __init__(self, name, method):
|
| + self.phases = OrderedDictionary()
|
| + self.name = name
|
| + self.method = method
|
| +
|
| + def AddPhase(self, name, phase):
|
| + self.phases[name] = phase
|
| +
|
| + def AllPhases(self):
|
| + for phase in self.phases.itervalues():
|
| + yield phase
|
| +
|
| + def AllPhaseNames(self):
|
| + return self.phases.keys()
|
| +
|
| + def GetName(self):
|
| + return self.name
|
| +
|
| + def GetPhase(self,phase):
|
| + return self.phases[phase]
|
| +
|
| + def GetPreviousPhase(self,phase):
|
| + # useful for diffs
|
| + index = self.phases.keys().index(phase)
|
| + if index == 0:
|
| + diemsg("No previous phase of " + phase)
|
| + return self.phases[self.phases.keys()[index - 1]]
|
| +
|
| + def Print(self,p):
|
| + printer.write("Compilation %s\n" % self.name)
|
| + for phase in self.AllPhases():
|
| + phase.Print(p)
|
| +
|
| + def __str__(self):
|
| + return "%s (%d phases)" % (
|
| + self.name,
|
| + len(self.phases))
|
| +
|
| +
|
| +def BuildOptions():
|
| + result = optparse.OptionParser(USAGE)
|
| + result.add_option("--input",
|
| + help="Input file (default %default)",
|
| + default="hydrogen.cfg")
|
| + result.add_option("--command",
|
| + help="The command to run: 'diff','report','%default' (default)",
|
| + default="blocks")
|
| + result.add_option("--function",
|
| + help="The function to analyze (default %default)",
|
| + default="@last")
|
| + result.add_option("--phase",
|
| + help="The compilation phase to examine (default %default)",
|
| + default="Z_Code generation")
|
| + result.add_option("--displayHIR",
|
| + help="Display HIR in blocks? (default %default)",
|
| + default=False, action="store_true")
|
| + result.add_option("--displayLIR",
|
| + help="Display LIR in blocks? (default %default)",
|
| + default=False, action="store_true")
|
| + result.add_option("-v", "--verbose", help="Verbose output (default %default)",
|
| + default=False, action="store_true")
|
| + result.add_option("-d", "--debug", help="Debug output (default %default)",
|
| + default=False, action="store_true")
|
| + return result
|
| +
|
| +
|
| +def ProcessOptions(options):
|
| + if not options.command in ["blocks","diff","report"]:
|
| + print "Unknown command %s" % options.command
|
| + return False
|
| + return True
|
| +
|
| +
|
| +def parseKeyString(line):
|
| + tokens = shlex.split(line.strip())
|
| + return tokens
|
| +
|
| +
|
| +class CFGReader(object):
|
| + """CFG file reader."""
|
| +
|
| + def __init__(self, filename):
|
| + self.file = open(filename, "r")
|
| + self.savedline = None
|
| +
|
| + def Dispose(self):
|
| + self.log.close()
|
| + self.log_file.close()
|
| +
|
| + def pushline(self,line):
|
| + self.savedline = line
|
| +
|
| + def nextline(self):
|
| + # we can push one line back
|
| + if self.savedline:
|
| + s = self.savedline
|
| + self.savedline = None
|
| + return s
|
| +
|
| + return self.file.readline()
|
| +
|
| + def Parse(self):
|
| + """Return a map of compilation units."""
|
| + compilations = OrderedDictionary()
|
| + while True:
|
| + compilation = self.ParseCompilation()
|
| + if not compilation:
|
| + break
|
| + compilations[compilation.name] = compilation
|
| + return compilations
|
| +
|
| + def ParseCompilation(self):
|
| + line = self.nextline()
|
| + if not line:
|
| + return None
|
| + assert "begin_compilation" == line.strip()
|
| + pair = parseKeyString(self.nextline())
|
| + assert"name" == pair[0]
|
| + name = pair[1]
|
| + compilation = Compilation(name,name)
|
| + # now read into configs
|
| + while line.strip() != "end_compilation":
|
| + line = self.nextline()
|
| + if not line:
|
| + diemsg("Unexpected end of file")
|
| +
|
| + while True:
|
| + phase = self.ParsePhase()
|
| + if not phase:
|
| + break
|
| + compilation.AddPhase(phase.name,phase)
|
| + return compilation
|
| +
|
| + def ParseHIRs(self,block):
|
| + while True:
|
| + line = self.nextline().strip()
|
| + if line == "end_HIR":
|
| + break
|
| + # we don't need the first number ("0 ") and some symbols at the end
|
| + line = line[2:]
|
| + line = line.replace("<|@","")
|
| + block.AddHIR(line)
|
| +
|
| + def ParseLIRs(self,block):
|
| + while True:
|
| + line = self.nextline().strip()
|
| + if line == "end_LIR":
|
| + break
|
| + # we don't need some symbols at the end
|
| + line = line.replace("<|@","")
|
| + block.AddLIR(line)
|
| +
|
| + def ParseLocals(self,block):
|
| + while True:
|
| + line = self.nextline()
|
| + line = line.strip()
|
| + if line == "end_locals":
|
| + break
|
| + if len(line) > 0 and (line[0]>='0' and line[0]<='9'):
|
| + keys = parseKeyString(line)
|
| + block.AddLocal(keys[1],line)
|
| +
|
| + def ParseBlock(self):
|
| + line = self.nextline()
|
| + if not line:
|
| + return None
|
| +
|
| + if line.strip() != "begin_block":
|
| + self.pushline(line)
|
| + return None
|
| +
|
| + assert "begin_block" == line.strip()
|
| + pair = parseKeyString(self.nextline())
|
| + assert "name" == pair[0]
|
| + name = pair[1]
|
| + # read pred and succ
|
| + while True:
|
| + line = self.nextline()
|
| + if line.strip() == "begin_states":
|
| + break;
|
| + pair = parseKeyString(line)
|
| + if pair[0] == "predecessors":
|
| + pred = pair[1:]
|
| + elif pair[0] == "successors":
|
| + succ = pair[1:]
|
| +
|
| + block = Block(name,pred,succ)
|
| + # add hrs if found
|
| + while True:
|
| + line = self.nextline().strip()
|
| + if line == "end_block":
|
| + break;
|
| + elif line == "begin_locals":
|
| + self.ParseLocals(block)
|
| + elif line == "begin_LIR":
|
| + self.ParseLIRs(block)
|
| + elif line == "begin_HIR":
|
| + self.ParseHIRs(block)
|
| +
|
| + return block
|
| +
|
| + def ParsePhase(self):
|
| + line = self.nextline()
|
| + if not line:
|
| + return None
|
| +
|
| + if line.strip() != "begin_cfg":
|
| + self.pushline(line)
|
| + return None
|
| +
|
| + assert "begin_cfg" == line.strip()
|
| + pair = parseKeyString(self.nextline())
|
| + assert "name" == pair[0]
|
| + name = pair[1]
|
| + phase = Phase(name)
|
| +
|
| + while True:
|
| + block = self.ParseBlock()
|
| + if not block:
|
| + break
|
| + phase.AddBlock(block.name,block)
|
| +
|
| + # just consume blocks
|
| + # consume the "end_cfg" message
|
| + assert "end_cfg" == self.nextline().strip()
|
| + return phase
|
| +
|
| +
|
| +def DumpCompilations(compilations,p):
|
| + p.write("len = %d\n" % len(compilations))
|
| + for comp in compilations.itervalues():
|
| + comp.Print(p)
|
| +
|
| +
|
| +def WriteTempFile(lines):
|
| + temp = tempfile.NamedTemporaryFile(mode="w+t")
|
| + for line in lines:
|
| + temp.write(line)
|
| + temp.write("\n")
|
| + temp.seek(0)
|
| + return temp
|
| +
|
| +
|
| +def diffText(lines1,lines2):
|
| + """Takes two lists of text lines, creates a diff output of them as an array"""
|
| +
|
| + try:
|
| + temp1 = WriteTempFile(lines1)
|
| + temp2 = WriteTempFile(lines2)
|
| + outputlines = []
|
| + cmdline = "diff -u %s %s" % (temp1.name, temp2.name)
|
| + process = subprocess.Popen(cmdline,
|
| + shell=True,
|
| + stdout=subprocess.PIPE,
|
| + stderr=subprocess.STDOUT)
|
| + pipe = process.stdout
|
| + try:
|
| + headerLines = 2
|
| + lineCount = 0
|
| + for line in pipe:
|
| + if lineCount > headerLines:
|
| + # strip newline
|
| + outputlines.append(line.strip())
|
| + lineCount = lineCount + 1
|
| + except Exception as e:
|
| + diemsg("error in diff = " + str(e))
|
| + finally:
|
| + pipe.close()
|
| +
|
| + finally:
|
| + temp1.close()
|
| + temp2.close()
|
| +
|
| + return outputlines
|
| +
|
| +
|
| +def Color(color,line):
|
| + return "<font color=\"%s\">%s</font>" % (color,line)
|
| +
|
| +
|
| +def LeftLineBreak():
|
| + return "<br align=\"left\"/>"
|
| +
|
| +
|
| +def HtmlLikeEscape(line):
|
| + return line.replace("<","<").replace(">",">")
|
| +
|
| +
|
| +def TableBegin():
|
| + return "<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">\n"
|
| +
|
| +
|
| +def TableEnd():
|
| + return "</table>\n"
|
| +
|
| +
|
| +def NameRow(blockName):
|
| + return "<tr><td><b>%s</b></td></tr>\n" % blockName
|
| +
|
| +
|
| +def FormatLine(line):
|
| + tesc = HtmlLikeEscape(line)
|
| + # Deal with diff output
|
| + if tesc.startswith("-"):
|
| + tesc = Color("red",tesc)
|
| + elif tesc.startswith("+"):
|
| + tesc = Color("blue",tesc)
|
| + return " %s%s\n" % (tesc, LeftLineBreak())
|
| +
|
| +
|
| +def FormatLines(lineList):
|
| + result = ""
|
| + if len(lineList) > 0:
|
| + result += "<tr><td>\n"
|
| + for l in lineList:
|
| + result = result + FormatLine(l)
|
| + result = result + "</td></tr>\n"
|
| + return result
|
| +
|
| +
|
| +def FormatLabel(blockName,localsList,hirList,lirList):
|
| + label = TableBegin()
|
| + label += NameRow(blockName)
|
| + label += FormatLines(localsList)
|
| + label += FormatLines(hirList)
|
| + label += FormatLines(lirList)
|
| + label += TableEnd()
|
| + return label
|
| +
|
| +
|
| +def DotBlockVisit(displayHIR,displayLIR,p,block):
|
| + localsList = []
|
| + hirList = []
|
| + lirList = []
|
| +
|
| + if displayHIR or displayLIR:
|
| + localsList = block.GetLocals()
|
| + if displayHIR:
|
| + hirList = block.GetHIRs()
|
| + if displayLIR:
|
| + lirList = block.GetLIRs()
|
| +
|
| + label = FormatLabel(block.GetName(),localsList,hirList,lirList)
|
| + # extra newline for readability
|
| + label = "\n" + label
|
| + p.write(" block%s [shape=plaintext,label=<%s>];\n"
|
| + % (block.GetName(), label))
|
| + for succ in block.GetSuccessors():
|
| + p.write(" block%s -> block%s;\n" % (block.GetName(), succ))
|
| +
|
| +
|
| +def DotBlocks(functionName,phase,displayHIR,displayLIR,p):
|
| + """Diagram the blocks in the given function."""
|
| + p.write("digraph Blocks_%s {\n" % functionName)
|
| + p.write(" ratio = 1.0;\n")
|
| + phase.visit_blocks(lambda b: DotBlockVisit(displayHIR,displayLIR,p,b))
|
| + p.write("}\n")
|
| +
|
| +
|
| +def DotDiffVisit(diffs,p,block):
|
| + label = FormatLabel(block.GetName(),[],diffs[block.GetName()],[])
|
| + p.write(" block%s [shape=plaintext,label=<%s>];\n"
|
| + % (block.GetName(), label))
|
| + for succ in block.GetSuccessors():
|
| + p.write(" block%s -> block%s;\n" % (block.GetName(), succ))
|
| +
|
| +
|
| +def DotDiff(functionName, phasePrev, phase, changedBlocksOnly, p):
|
| + """Run a diff block by block between two phases in the given function."""
|
| + p.write("// comparing phase %s with next phase %s\n" % (phasePrev.GetName(),
|
| + phase.GetName()))
|
| + p.write("digraph Diff_%s {\n" % functionName)
|
| + p.write(" ratio = 1.0;\n")
|
| + # need to run diff -u phasePrev phase
|
| + # on the HIR output
|
| + # I think the # of blocks should be the same
|
| + diffResults = {}
|
| + blocks = phasePrev.GetBlocks()
|
| + for blockPrev in blocks:
|
| + block = phase.GetBlock(blockPrev.GetName())
|
| + diffResults[blockPrev.GetName()] = diffText(blockPrev.GetHIRs(),
|
| + block.GetHIRs())
|
| +
|
| + phasePrev.visit_blocks(lambda b: DotDiffVisit(diffResults,p,b))
|
| + p.write("}\n")
|
| +
|
| +
|
| +reportText = """<html>
|
| +<title>Report on %s</title>
|
| +<body>
|
| +<h2>Block structure</h2>
|
| +<a href=blocks.svg><img width=800 src=blocks.svg></a>
|
| +<h2>All HIR</h2>
|
| +<a href=hirblocks.svg><img width=800 src=hirblocks.svg></a>
|
| +<h2>All LIR</h2>
|
| +<a href=lirblocks.svg><img width=800 src=lirblocks.svg></a>
|
| +<h2>Diffs</h2>
|
| +%s
|
| +</body>
|
| +</html>"""
|
| +
|
| +
|
| +diffsTemplate = """<h2>%s</h2>
|
| +<a href=%s><img width=800 src=%s></a>"""
|
| +
|
| +
|
| +def AsFilename(phaseName,ext):
|
| + phaseName = phaseName.replace(" ","_")
|
| + phaseName += ext
|
| + return phaseName
|
| +
|
| +
|
| +def DotReport(function):
|
| + """Make a report on the function."""
|
| + function_name = function.GetName().replace(".","_")
|
| + directory_name = "report_%s" % function_name
|
| + if not os.path.exists(directory_name):
|
| + os.mkdirs(directory_name)
|
| +
|
| + htmlfilename = directory_name + "/index.html"
|
| + with open(htmlfilename, "w+t") as p:
|
| + print "Writing %s..." % htmlfilename
|
| + diffs = ""
|
| + phases = function.AllPhaseNames()
|
| + for ph in phases[1:]:
|
| + phfilename = AsFilename(ph,".svg")
|
| + diffs += diffsTemplate % (ph,phfilename,phfilename)
|
| + htmlText = reportText % (function_name,diffs)
|
| + p.write(htmlText + "\n")
|
| +
|
| +
|
| + # Write all dot files
|
| + # get last phase
|
| + phase = function.GetPhase(phases[-1])
|
| + with open(directory_name + "/blocks.dot", "w+t") as p:
|
| + DotBlocks(function_name,phase,False,False,p)
|
| +
|
| + with open(directory_name + "/hirblocks.dot", "w+t") as p:
|
| + DotBlocks(function_name,phase,True,False,p)
|
| +
|
| + with open(directory_name + "/lirblocks.dot", "w+t") as p:
|
| + DotBlocks(function_name,phase,False,True,p)
|
| +
|
| + print "Computing diffs..."
|
| +
|
| + for ph in phases[1:]:
|
| + phase = function.GetPhase(ph)
|
| + phase_previous = function.GetPreviousPhase(ph)
|
| + with open(directory_name + "/" + AsFilename(ph,".dot"), "w+t") as p:
|
| + DotDiff(function_name, phase_previous, phase, True, p)
|
| +
|
| + # Compile the dot files
|
| + command_line = "dot -Tsvg %s > %s"
|
| + # diff -u %s %s" % (temp1.name, temp2.name)
|
| + files = glob.glob(directory_name + "/*.dot")
|
| + for f in files:
|
| + print "compiling " + f
|
| + process = subprocess.Popen(command_line % (f,f.replace(".dot",".svg")),
|
| + shell=True)
|
| + process.wait()
|
| + print "done"
|
| +
|
| +
|
| +def Main():
|
| + parser = BuildOptions()
|
| + (options, args) = parser.parse_args()
|
| + if not ProcessOptions(options):
|
| + parser.print_help()
|
| + return 1
|
| +
|
| + # Open input file and parse compilations, phases, blocks
|
| + reader = CFGReader(options.input)
|
| + compilations = reader.Parse()
|
| +
|
| + if options.debug:
|
| + DumpCompilations(compilations, sys.stdout)
|
| +
|
| + function = options.function
|
| + if function == "@last":
|
| + function = compilations.keys()[-1]
|
| + elif function == "@first":
|
| + function = compilations.keys()[0]
|
| + if options.verbose:
|
| + print "// dump function %s" % function
|
| +
|
| + phase = options.phase
|
| +
|
| + # find the method
|
| + if not function in compilations:
|
| + diemsg("function " + function + " not found in input file")
|
| +
|
| + function = compilations[function]
|
| + # find the phase
|
| + phase = function.GetPhase(phase)
|
| +
|
| + # now what we do depends on the command
|
| +
|
| + function_name = function.GetName().replace(".","_")
|
| + if options.command == "blocks":
|
| + DotBlocks(function_name, phase, options.displayHIR,
|
| + options.displayLIR, sys.stdout)
|
| + elif options.command == "diff":
|
| + # take the phase and the preceding phase.
|
| + previous_phase = function.GetPreviousPhase(phase.GetName())
|
| + DotDiff(function_name, previous_phase, phase, True, sys.stdout)
|
| + elif options.command == "report":
|
| + DotReport(function)
|
| + return 0
|
| +
|
| +
|
| +if __name__ == "__main__":
|
| + sys.exit(Main())
|
|
|