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

Reply via email to