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?

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
> > 

Reply via email to