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.

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

Reply via email to