mirror of
https://github.com/RPCSX/llvm.git
synced 2025-05-13 10:56:01 +00:00

I am planning to use this tool to find too noisy (missed) optimization remarks. Long term it may actually be better to just have another tool that exports the remarks into an sqlite database and perform queries like this in SQL. This splits out the YAML parsing from opt-viewer.py into a new Python module optrecord.py. This is the result of the script on the LLVM testsuite: Total number of remarks 714433 Top 10 remarks by pass: inline 52% gvn 24% licm 13% loop-vectorize 5% asm-printer 3% loop-unroll 1% regalloc 1% inline-cost 0% slp-vectorizer 0% loop-delete 0% Top 10 remarks: gvn/LoadClobbered 20% inline/Inlined 19% inline/CanBeInlined 18% inline/NoDefinition 9% licm/LoadWithLoopInvariantAddressInvalidated 6% licm/Hoisted 6% asm-printer/InstructionCount 3% inline/TooCostly 3% gvn/LoadElim 3% loop-vectorize/MissedDetails 2% Beside some refactoring, I also changed optrecords not to use context to access global data (max_hotness). Because of the separate module this would have required splitting context into two. However it's not possible to access the optrecord context from the SourceFileRenderer when calling back to Remark.RelativeHotness. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@296682 91177308-0d34-0410-b5e6-96231b3b80d8
234 lines
7.2 KiB
Python
Executable File
234 lines
7.2 KiB
Python
Executable File
#!/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 and Pygments Python packages.'''
|
|
|
|
import optrecord
|
|
import functools
|
|
from multiprocessing import Pool
|
|
from multiprocessing import Lock, cpu_count
|
|
import errno
|
|
import argparse
|
|
import os.path
|
|
import re
|
|
import shutil
|
|
from pygments import highlight
|
|
from pygments.lexers.c_cpp import CppLexer
|
|
from pygments.formatters import HtmlFormatter
|
|
import cgi
|
|
|
|
# This allows passing the global context to the child processes.
|
|
class Context:
|
|
def __init__(self, caller_loc = dict()):
|
|
# Map function names to their source location for function where inlining happened
|
|
self.caller_loc = caller_loc
|
|
|
|
context = Context()
|
|
|
|
class SourceFileRenderer:
|
|
def __init__(self, source_dir, output_dir, filename):
|
|
existing_filename = None
|
|
if os.path.exists(filename):
|
|
existing_filename = filename
|
|
else:
|
|
fn = os.path.join(source_dir, filename)
|
|
if os.path.exists(fn):
|
|
existing_filename = fn
|
|
|
|
self.stream = open(os.path.join(output_dir, optrecord.html_file_name(filename)), 'w')
|
|
if existing_filename:
|
|
self.source_stream = open(existing_filename)
|
|
else:
|
|
self.source_stream = None
|
|
print('''
|
|
<html>
|
|
<h1>Unable to locate file {}</h1>
|
|
</html>
|
|
'''.format(filename), file=self.stream)
|
|
|
|
self.html_formatter = HtmlFormatter(encoding='utf-8')
|
|
self.cpp_lexer = CppLexer(stripnl=False)
|
|
|
|
def render_source_lines(self, stream, line_remarks):
|
|
file_text = stream.read()
|
|
html_highlighted = highlight(file_text, self.cpp_lexer, self.html_formatter)
|
|
|
|
# Take off the header and footer, these must be
|
|
# reapplied line-wise, within the page structure
|
|
html_highlighted = html_highlighted.replace('<div class="highlight"><pre>', '')
|
|
html_highlighted = html_highlighted.replace('</pre></div>', '')
|
|
|
|
for (linenum, html_line) in enumerate(html_highlighted.split('\n'), start=1):
|
|
print('''
|
|
<tr>
|
|
<td><a name=\"L{linenum}\">{linenum}</a></td>
|
|
<td></td>
|
|
<td></td>
|
|
<td><div class="highlight"><pre>{html_line}</pre></div></td>
|
|
</tr>'''.format(**locals()), file=self.stream)
|
|
|
|
for remark in line_remarks.get(linenum, []):
|
|
self.render_inline_remarks(remark, html_line)
|
|
|
|
def render_inline_remarks(self, r, line):
|
|
inlining_context = r.DemangledFunctionName
|
|
dl = context.caller_loc.get(r.Function)
|
|
if dl:
|
|
link = optrecord.make_link(dl['File'], dl['Line'] - 2)
|
|
inlining_context = "<a href={link}>{r.DemangledFunctionName}</a>".format(**locals())
|
|
|
|
# Column is the number of characters *including* tabs, keep those and
|
|
# replace everything else with spaces.
|
|
indent = line[:max(r.Column, 1) - 1]
|
|
indent = re.sub('\S', ' ', indent)
|
|
|
|
print('''
|
|
<tr>
|
|
<td></td>
|
|
<td>{r.RelativeHotness}</td>
|
|
<td class=\"column-entry-{r.color}\">{r.Pass}</td>
|
|
<td><pre style="display:inline">{indent}</pre><span class=\"column-entry-yellow\"> {r.message} </span></td>
|
|
<td class=\"column-entry-yellow\">{inlining_context}</td>
|
|
</tr>'''.format(**locals()), file=self.stream)
|
|
|
|
def render(self, line_remarks):
|
|
if not self.source_stream:
|
|
return
|
|
|
|
print('''
|
|
<html>
|
|
<head>
|
|
<link rel='stylesheet' type='text/css' href='style.css'>
|
|
</head>
|
|
<body>
|
|
<div class="centered">
|
|
<table>
|
|
<tr>
|
|
<td>Line</td>
|
|
<td>Hotness</td>
|
|
<td>Optimization</td>
|
|
<td>Source</td>
|
|
<td>Inline Context</td>
|
|
</tr>''', file=self.stream)
|
|
self.render_source_lines(self.source_stream, line_remarks)
|
|
|
|
print('''
|
|
</table>
|
|
</body>
|
|
</html>''', file=self.stream)
|
|
|
|
|
|
class IndexRenderer:
|
|
def __init__(self, output_dir):
|
|
self.stream = open(os.path.join(output_dir, 'index.html'), 'w')
|
|
|
|
def render_entry(self, r, odd):
|
|
escaped_name = cgi.escape(r.DemangledFunctionName)
|
|
print('''
|
|
<tr>
|
|
<td class=\"column-entry-{odd}\"><a href={r.Link}>{r.DebugLocString}</a></td>
|
|
<td class=\"column-entry-{odd}\">{r.RelativeHotness}</td>
|
|
<td class=\"column-entry-{odd}\">{escaped_name}</td>
|
|
<td class=\"column-entry-{r.color}\">{r.Pass}</td>
|
|
</tr>'''.format(**locals()), file=self.stream)
|
|
|
|
def render(self, all_remarks):
|
|
print('''
|
|
<html>
|
|
<head>
|
|
<link rel='stylesheet' type='text/css' href='style.css'>
|
|
</head>
|
|
<body>
|
|
<div class="centered">
|
|
<table>
|
|
<tr>
|
|
<td>Source Location</td>
|
|
<td>Hotness</td>
|
|
<td>Function</td>
|
|
<td>Pass</td>
|
|
</tr>''', file=self.stream)
|
|
for i, remark in enumerate(all_remarks):
|
|
self.render_entry(remark, i % 2)
|
|
print('''
|
|
</table>
|
|
</body>
|
|
</html>''', file=self.stream)
|
|
|
|
|
|
def _render_file(source_dir, output_dir, ctx, entry):
|
|
global context
|
|
context = ctx
|
|
filename, remarks = entry
|
|
SourceFileRenderer(source_dir, output_dir, filename).render(remarks)
|
|
|
|
|
|
def map_remarks(all_remarks):
|
|
# Set up a map between function names and their source location for
|
|
# function where inlining happened
|
|
for remark in all_remarks.itervalues():
|
|
if isinstance(remark, optrecord.Passed) and remark.Pass == "inline" and remark.Name == "Inlined":
|
|
for arg in remark.Args:
|
|
caller = arg.get('Caller')
|
|
if caller:
|
|
context.caller_loc[caller] = arg['DebugLoc']
|
|
|
|
|
|
def generate_report(pmap, all_remarks, file_remarks, source_dir, output_dir, should_display_hotness):
|
|
try:
|
|
os.makedirs(output_dir)
|
|
except OSError as e:
|
|
if e.errno == errno.EEXIST and os.path.isdir(output_dir):
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
_render_file_bound = functools.partial(_render_file, source_dir, output_dir, context)
|
|
pmap(_render_file_bound, file_remarks.items())
|
|
|
|
if should_display_hotness:
|
|
sorted_remarks = sorted(all_remarks.itervalues(), key=lambda r: (r.Hotness, r.__dict__), reverse=True)
|
|
else:
|
|
sorted_remarks = sorted(all_remarks.itervalues(), key=lambda r: (r.File, r.Line, r.Column, r.__dict__))
|
|
IndexRenderer(args.output_dir).render(sorted_remarks)
|
|
|
|
shutil.copy(os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
"style.css"), output_dir)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description=desc)
|
|
parser.add_argument('yaml_files', nargs='+')
|
|
parser.add_argument('output_dir')
|
|
parser.add_argument(
|
|
'--jobs',
|
|
'-j',
|
|
default=cpu_count(),
|
|
type=int,
|
|
help='Max job count (defaults to current CPU count)')
|
|
parser.add_argument(
|
|
'-source-dir',
|
|
'-s',
|
|
default='',
|
|
help='set source directory')
|
|
args = parser.parse_args()
|
|
|
|
if len(args.yaml_files) == 0:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
if args.jobs == 1:
|
|
pmap = map
|
|
else:
|
|
pool = Pool(processes=args.jobs)
|
|
pmap = pool.map
|
|
|
|
all_remarks, file_remarks, should_display_hotness = optrecord.gather_results(pmap, args.yaml_files)
|
|
|
|
map_remarks(all_remarks)
|
|
|
|
generate_report(pmap, all_remarks, file_remarks, args.source_dir, args.output_dir, should_display_hotness)
|