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? 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_location') + 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