Index: llvm/trunk/utils/opt-viewer/opt-viewer.py =================================================================== --- llvm/trunk/utils/opt-viewer/opt-viewer.py +++ llvm/trunk/utils/opt-viewer/opt-viewer.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python2.7 + +from __future__ import print_function + +desc = '''Generate HTML output to visualize optimization records from the YAML files +generated with -fsave-optimization-record and -fdiagnostics-show-hotness. + +The tools requires PyYAML to be installed.''' + +import yaml +import argparse +import os.path +import subprocess +import shutil + +parser = argparse.ArgumentParser(description=desc) +parser.add_argument('yaml_files', nargs='+') +parser.add_argument('output_dir') +args = parser.parse_args() + +p = subprocess.Popen(['c++filt', '-n'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) +def demangle(name): + p.stdin.write(name + '\n') + return p.stdout.readline().rstrip() + +class Remark(yaml.YAMLObject): + @property + def File(self): + return self.DebugLoc['File'] + + @property + def Line(self): + return int(self.DebugLoc['Line']) + + @property + def Column(self): + return self.DebugLoc['Column'] + + def getDebugLoc(self): + return "{}:{}:{}".format(self.File, self.Line, self.Column) + + def getLink(self): + return "{}#L{}".format(SourceFileRenderer.html_file_name(self.File), self.Line) + + def getArgString(self, pair): + if pair[0] == 'Callee' or pair[0] == 'Caller': + return demangle(pair[1]) + return pair[1] + + @property + def message(self): + # Args is a list of mappings (dictionaries) with each dictionary with + # exactly one key-value pair. + values = [self.getArgString(mapping.items()[0]) for mapping in self.Args] + return demangle("".join(values)) + +class Analysis(Remark): + yaml_tag = '!Analysis' + + @property + def color(self): return "white" + +class AnalysisFPCommute(Analysis): + yaml_tag = '!AnalysisFPCommute' + +class AnalysisAliasing(Analysis): + yaml_tag = '!AnalysisAliasing' + +class Passed(Remark): + yaml_tag = '!Passed' + + @property + def color(self): return "green" + +class Missed(Remark): + yaml_tag = '!Missed' + + @property + def color(self): return "red" + +class SourceFileRenderer: + def __init__(self, filename): + self.source_stream = open(filename) + self.stream = open(os.path.join(args.output_dir, SourceFileRenderer.html_file_name(filename)), 'w') + + def render_source_line(self, linenum, line): + print(''' + +{linenum} + + +
{line}
+'''.format(**locals()), file=self.stream) + + def render_inline_remarks(self, r): + print(''' + + +{r.Hotness} +{r.Pass} +{r.message} +'''.format(**locals()), file=self.stream) + + def render(self, line_remarks): + print(''' + + + + + +
+ + + + + + +''', file=self.stream) + for (linenum, line) in enumerate(self.source_stream.readlines(), start=1): + self.render_source_line(linenum, line) + for remark in line_remarks.get(linenum, []): + self.render_inline_remarks(remark) + print(''' +
LineHotnessOptimizationSource
+ +''', file=self.stream) + + @classmethod + def html_file_name(cls, filename): + return filename.replace('/', '_') + ".html" + +class IndexRenderer: + def __init__(self): + self.stream = open(os.path.join(args.output_dir, 'index.html'), 'w') + + def render_entry(self, remark): + html = SourceFileRenderer.html_file_name(remark.File) + link = "{}".format(remark.getLink(), remark.getDebugLoc()) + + dem_name = demangle(remark.Function) + print("{}{}{}{}".format( + link, + remark.Hotness, dem_name, remark.color, remark.Pass), file=self.stream) + + def render(self, all_remarks): + print(''' + + + + + +
+ + + + + + +''', file=self.stream) + for remark in all_remarks: + self.render_entry(remark) + print(''' +
Source LocationHotnessFunctionPass
+ +''', file=self.stream) + + +all_remarks = [] +file_remarks = dict() + +for input_file in args.yaml_files: + f = open(input_file) + docs = yaml.load_all(f) + for remark in docs: + if hasattr(remark, 'Hotness'): + file_remarks.setdefault(remark.File, dict()).setdefault(remark.Line, []).append(remark); + all_remarks.append(remark) + +all_remarks = sorted(all_remarks, key=lambda r: r.Hotness, reverse=True) + +if not os.path.exists(args.output_dir): + os.mkdir(args.output_dir) + +for (filename, remarks) in file_remarks.iteritems(): + SourceFileRenderer(filename).render(remarks) + +IndexRenderer().render(all_remarks) + +shutil.copy(os.path.join(os.path.dirname(os.path.realpath(__file__)), "style.css"), args.output_dir) Index: llvm/trunk/utils/opt-viewer/style.css =================================================================== --- llvm/trunk/utils/opt-viewer/style.css +++ llvm/trunk/utils/opt-viewer/style.css @@ -0,0 +1,125 @@ +.red { + background-color: #ffd0d0; +} +.cyan { + background-color: cyan; +} +body { + font-family: -apple-system, sans-serif; +} +pre { + margin-top: 0px !important; + margin-bottom: 0px !important; +} +.source-name-title { + padding: 5px 10px; + border-bottom: 1px solid #dbdbdb; + background-color: #eee; + line-height: 35px; +} +.centered { + display: table; + margin-left: left; + margin-right: auto; + border: 1px solid #dbdbdb; + border-radius: 3px; +} +.expansion-view { + background-color: rgba(0, 0, 0, 0); + margin-left: 0px; + margin-top: 5px; + margin-right: 5px; + margin-bottom: 5px; + border: 1px solid #dbdbdb; + border-radius: 3px; +} +table { + border-collapse: collapse; +} +.light-row { + background: #ffffff; + border: 1px solid #dbdbdb; +} +.column-entry { + text-align: right; +} +.column-entry-left { + text-align: left; +} +.column-entry-white { + text-align: right; + background-color: #ffffff; +} +.column-entry-red { + text-align: right; + background-color: #ffd0d0; +} +.column-entry-green { + text-align: right; + background-color: #d0ffd0; +} +.column-entry-yellow { + text-align: left; + background-color: #ffe1a6; +} +.line-number { + text-align: right; + color: #aaa; +} +.covered-line { + text-align: right; + color: #0080ff; +} +.uncovered-line { + text-align: right; + color: #ff3300; +} +.tooltip { + position: relative; + display: inline; + background-color: #b3e6ff; + text-decoration: none; +} +.tooltip span.tooltip-content { + position: absolute; + width: 100px; + margin-left: -50px; + color: #FFFFFF; + background: #000000; + height: 30px; + line-height: 30px; + text-align: center; + visibility: hidden; + border-radius: 6px; +} +.tooltip span.tooltip-content:after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + margin-left: -8px; + width: 0; height: 0; + border-top: 8px solid #000000; + border-right: 8px solid transparent; + border-left: 8px solid transparent; +} +:hover.tooltip span.tooltip-content { + visibility: visible; + opacity: 0.8; + bottom: 30px; + left: 50%; + z-index: 999; +} +th, td { + vertical-align: top; + padding: 2px 5px; + border-collapse: collapse; + border-right: solid 1px #eee; + border-left: solid 1px #eee; +} +td:first-child { + border-left: none; +} +td:last-child { + border-right: none; +}