On Mon, Jul 23, 2018 at 9:20 PM David Malcolm <dmalc...@redhat.com> wrote: > > On Mon, 2018-07-23 at 11:46 +0200, Richard Biener wrote: > > On Fri, Jul 20, 2018 at 6:27 PM David Malcolm <dmalc...@redhat.com> > > wrote: > > > > > > This patch adds a Python 3 module to "contrib" for reading the > > > output of > > > -fsave-optimization-record. > > > > > > It can be imported from other Python code, or run standalone as a > > > script, > > > in which case it prints the saved messages in a form resembling GCC > > > diagnostics. > > > > > > OK for trunk? > > > > OK, but shouldn't there maybe a user-visible (and thus installed) > > tool for > > this kind of stuff? Which would mean to place it somewhere else. > > As well as this support code, I've got code that uses it to generate > HTML reports. I'm thinking that all this Python code might be better > to maintain in an entirely separate repository, as a third-party > project (maintained by me, under some suitable Free Software license, > accessible via PyPI), since I suspect that the release cycle ought to > be different from that of gcc itself. > > Would that be a better approach?
Possibly. Richard. > Dave > > > Richard. > > > > > contrib/ChangeLog: > > > * optrecord.py: New file. > > > --- > > > contrib/optrecord.py | 295 > > > +++++++++++++++++++++++++++++++++++++++++++++++++++ > > > 1 file changed, 295 insertions(+) > > > create mode 100755 contrib/optrecord.py > > > > > > diff --git a/contrib/optrecord.py b/contrib/optrecord.py > > > new file mode 100755 > > > index 0000000..b07488e > > > --- /dev/null > > > +++ b/contrib/optrecord.py > > > @@ -0,0 +1,295 @@ > > > +#!/usr/bin/env python3 > > > +# > > > +# Python module for working with the result of -fsave- > > > optimization-record > > > +# Contributed by David Malcolm <dmalc...@redhat.com>. > > > +# > > > +# Copyright (C) 2018 Free Software Foundation, Inc. > > > +# This file is part of GCC. > > > +# > > > +# GCC is free software; you can redistribute it and/or modify it > > > under > > > +# the terms of the GNU General Public License as published by the > > > Free > > > +# Software Foundation; either version 3, or (at your option) any > > > later > > > +# version. > > > +# > > > +# GCC is distributed in the hope that it will be useful, but > > > WITHOUT ANY > > > +# WARRANTY; without even the implied warranty of MERCHANTABILITY > > > or > > > +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public > > > License > > > +# for more details. > > > +# > > > +# You should have received a copy of the GNU General Public > > > License > > > +# along with GCC; see the file COPYING3. If not see > > > +# <http://www.gnu.org/licenses/>. */ > > > + > > > +import argparse > > > +import json > > > +import os > > > +import sys > > > + > > > +class TranslationUnit: > > > + """Top-level class for containing optimization records""" > > > + @staticmethod > > > + def from_filename(filename): > > > + with open(filename) as f: > > > + root_obj = json.load(f) > > > + return TranslationUnit(filename, root_obj) > > > + > > > + def __init__(self, filename, json_obj): > > > + self.filename = filename > > > + self.pass_by_id = {} > > > + > > > + # Expect a 3-tuple > > > + metadata, passes, records = json_obj > > > + > > > + self.format = metadata['format'] > > > + self.generator = metadata['generator'] > > > + self.passes = [Pass(obj, self) for obj in passes] > > > + self.records = [Record(obj, self) for obj in records] > > > + > > > + def __repr__(self): > > > + return ('TranslationUnit(%r, %r, %r, %r)' > > > + % (self.filename, self.generator, self.passes, > > > self.records)) > > > + > > > +class Pass: > > > + """An optimization pass""" > > > + def __init__(self, json_obj, tu): > > > + self.id_ = json_obj['id'] > > > + self.name = json_obj['name'] > > > + self.num = json_obj['num'] > > > + self.optgroups = set(json_obj['optgroups']) # list of > > > strings > > > + self.type = json_obj['type'] > > > + tu.pass_by_id[self.id_] = self > > > + self.children = [Pass(child, tu) > > > + for child in json_obj.get('children', > > > [])] > > > + > > > + def __repr__(self): > > > + return ('Pass(%r, %r, %r, %r)' > > > + % (self.name, self.num, self.optgroups, > > > self.type)) > > > + > > > +def from_optional_json_field(cls, jsonobj, field): > > > + if field not in jsonobj: > > > + return None > > > + return cls(jsonobj[field]) > > > + > > > +class ImplLocation: > > > + """An implementation location (within the compiler itself)""" > > > + def __init__(self, json_obj): > > > + self.file = json_obj['file'] > > > + self.line = json_obj['line'] > > > + self.function = json_obj['function'] > > > + > > > + def __repr__(self): > > > + return ('ImplLocation(%r, %r, %r)' > > > + % (self.file, self.line, self.function)) > > > + > > > +class Location: > > > + """A source location""" > > > + def __init__(self, json_obj): > > > + self.file = json_obj['file'] > > > + self.line = json_obj['line'] > > > + self.column = json_obj['column'] > > > + > > > + def __str__(self): > > > + return '%s:%i:%i' % (self.file, self.line, self.column) > > > + > > > + def __repr__(self): > > > + return ('Location(%r, %r, %r)' > > > + % (self.file, self.line, self.column)) > > > + > > > +class Count: > > > + """An execution count""" > > > + def __init__(self, json_obj): > > > + self.quality = json_obj['quality'] > > > + self.value = json_obj['value'] > > > + > > > + def __repr__(self): > > > + return ('Count(%r, %r)' > > > + % (self.quality, self.value)) > > > + > > > + def is_precise(self): > > > + return self.quality in ('precise', 'adjusted') > > > + > > > +class Record: > > > + """A optimization record: success/failure/note""" > > > + def __init__(self, json_obj, tu): > > > + self.kind = json_obj['kind'] > > > + if 'pass' in json_obj: > > > + self.pass_ = tu.pass_by_id[json_obj['pass']] > > > + else: > > > + self.pass_ = None > > > + self.function = json_obj.get('function', None) > > > + self.impl_location = > > > from_optional_json_field(ImplLocation, json_obj, > > > + 'impl_locati > > > on') > > > + self.message = [Item.from_json(obj) for obj in > > > json_obj['message']] > > > + self.count = from_optional_json_field(Count, json_obj, > > > 'count') > > > + self.location = from_optional_json_field(Location, > > > json_obj, 'location') > > > + if 'inlining_chain' in json_obj: > > > + self.inlining_chain = [InliningNode(obj) > > > + for obj in > > > json_obj['inlining_chain']] > > > + else: > > > + self.inlining_chain = None > > > + self.children = [Record(child, tu) > > > + for child in json_obj.get('children', > > > [])] > > > + > > > + def __repr__(self): > > > + return ('Record(%r, %r, %r, %r, %r)' > > > + % (self.kind, self.message, self.pass_, > > > self.function, > > > + self.children)) > > > + > > > +class InliningNode: > > > + """A node within an inlining chain""" > > > + def __init__(self, json_obj): > > > + self.fndecl = json_obj['fndecl'] > > > + self.site = from_optional_json_field(Location, json_obj, > > > 'site') > > > + > > > +class Item: > > > + """Base class for non-string items within a message""" > > > + @staticmethod > > > + def from_json(json_obj): > > > + if isinstance(json_obj, str): > > > + return json_obj > > > + if 'expr' in json_obj: > > > + return Expr(json_obj) > > > + elif 'stmt' in json_obj: > > > + return Stmt(json_obj) > > > + elif 'symtab_node' in json_obj: > > > + return SymtabNode(json_obj) > > > + else: > > > + raise ValueError('unrecognized item: %r' % json_obj) > > > + > > > +class Expr(Item): > > > + """An expression within a message""" > > > + def __init__(self, json_obj): > > > + self.expr = json_obj['expr'] > > > + self.location = from_optional_json_field(Location, > > > json_obj, 'location') > > > + > > > + def __str__(self): > > > + return self.expr > > > + > > > + def __repr__(self): > > > + return 'Expr(%r)' % self.expr > > > + > > > +class Stmt(Item): > > > + """A statement within a message""" > > > + def __init__(self, json_obj): > > > + self.stmt = json_obj['stmt'] > > > + self.location = from_optional_json_field(Location, > > > json_obj, 'location') > > > + > > > + def __str__(self): > > > + return self.stmt > > > + > > > + def __repr__(self): > > > + return 'Stmt(%r)' % self.stmt > > > + > > > +class SymtabNode(Item): > > > + """A symbol table node within a message""" > > > + def __init__(self, json_obj): > > > + self.node = json_obj['symtab_node'] > > > + self.location = from_optional_json_field(Location, > > > json_obj, 'location') > > > + > > > + def __str__(self): > > > + return self.node > > > + > > > + def __repr__(self): > > > + return 'SymtabNode(%r)' % self.node > > > + > > > +################################################################## > > > ########## > > > + > > > +SGR_START = "\33[" > > > +SGR_END = "m\33[K" > > > +def SGR_SEQ(text): > > > + return SGR_START + text + SGR_END > > > +SGR_RESET = SGR_SEQ("") > > > + > > > +COLOR_SEPARATOR = ";" > > > +COLOR_BOLD = "01" > > > +COLOR_FG_RED = "31" > > > +COLOR_FG_GREEN = "32" > > > +COLOR_FG_CYAN = "36" > > > + > > > +class Printer: > > > + def __init__(self, colorize): > > > + self.colorize = colorize > > > + > > > + def print_to_str(self, record, indent=0): > > > + msg = '' > > > + loc = record.location > > > + if loc: > > > + msg += self.bold('%s: ' % loc) > > > + msg += self.color_for_kind('%s: ' % record.kind, > > > record.kind) > > > + msg += ' ' * indent > > > + for item in record.message: > > > + if isinstance(item, str): > > > + msg += item > > > + elif isinstance(item, (Expr, Stmt, SymtabNode)): > > > + msg += "'" + self.bold(str(item)) + "'" > > > + else: > > > + raise TypeError('unknown message item: %r' % item) > > > + # Strip trailing whitespace (including newlines) > > > + msg = msg.rstrip() > > > + if record.pass_: > > > + msg += ' [%s]' % self.bold('pass=%s' % > > > record.pass_.name) > > > + if record.count: > > > + msg += (' [%s]' > > > + % self.bold('count(%s)=%i' > > > + % (record.count.quality, > > > + record.count.value))) > > > + return msg > > > + > > > + def print_record(self, out, record, indent=0): > > > + msg = self.print_to_str(record, indent) > > > + out.write('%s\n' % msg) > > > + for child in record.children: > > > + self.print_record(out, child, indent + 1) > > > + > > > + def with_color(self, color, text): > > > + if self.colorize: > > > + return SGR_SEQ(color) + text + SGR_RESET > > > + else: > > > + return text > > > + > > > + def bold(self, text): > > > + return self.with_color(COLOR_BOLD, text) > > > + > > > + def bold_green(self, text): > > > + return self.with_color(COLOR_FG_GREEN + COLOR_SEPARATOR + > > > COLOR_BOLD, > > > + text) > > > + > > > + def bold_red(self, text): > > > + return self.with_color(COLOR_FG_RED + COLOR_SEPARATOR + > > > COLOR_BOLD, > > > + text) > > > + > > > + def bold_cyan(self, text): > > > + return self.with_color(COLOR_FG_CYAN + COLOR_SEPARATOR + > > > COLOR_BOLD, > > > + text) > > > + > > > + def color_for_kind(self, text, kind): > > > + if kind == 'success': > > > + return self.bold_green(text) > > > + elif kind == 'failure': > > > + return self.bold_red(text) > > > + else: > > > + return self.bold_cyan(text) > > > + > > > +def should_colorize(stream): > > > + return os.environ['TERM'] != 'dumb' and > > > os.isatty(stream.fileno()) > > > + > > > +################################################################## > > > ########## > > > + > > > +def main(): > > > + """ > > > + If run as a script, read one or more files, and print them to > > > stdout in > > > + a format similar to GCC diagnostics. > > > + """ > > > + parser = argparse.ArgumentParser( > > > + description="Print the results of GCC's -fsave- > > > optimization-record.") > > > + parser.add_argument('filenames', metavar='FILENAME', type=str, > > > nargs='+', > > > + help='the name of the file(s) to be > > > printed') > > > + args = parser.parse_args() > > > + p = Printer(should_colorize(sys.stdout)) > > > + for filename in args.filenames: > > > + tu = TranslationUnit.from_filename(filename) > > > + for r in tu.records: > > > + p.print_record(sys.stdout, r) > > > + > > > +if __name__ == '__main__': > > > + main() > > > -- > > > 1.8.5.3 > > >