-fsave-optimization-record: add contrib/optrecord.py

Message ID 1532106739-57062-1-git-send-email-dmalcolm@redhat.com
State New
Headers show
Series
  • -fsave-optimization-record: add contrib/optrecord.py
Related show

Commit Message

David Malcolm July 20, 2018, 5:12 p.m.
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

-- 
1.8.5.3

Comments

Richard Biener July 23, 2018, 9:46 a.m. | #1
On Fri, Jul 20, 2018 at 6:27 PM David Malcolm <dmalcolm@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 <dmalcolm@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

>
David Malcolm July 23, 2018, 7:20 p.m. | #2
On Mon, 2018-07-23 at 11:46 +0200, Richard Biener wrote:
> On Fri, Jul 20, 2018 at 6:27 PM David Malcolm <dmalcolm@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 <dmalcolm@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

> >
Richard Biener July 24, 2018, 2:11 p.m. | #3
On Mon, Jul 23, 2018 at 9:20 PM David Malcolm <dmalcolm@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 <dmalcolm@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 <dmalcolm@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

> > >
David Malcolm July 25, 2018, 2:59 p.m. | #4
On Tue, 2018-07-24 at 16:11 +0200, Richard Biener wrote:
> On Mon, Jul 23, 2018 at 9:20 PM David Malcolm <dmalcolm@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 <dmalcolm@redhat.co

> > > m>

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


[CCing Rainer and Mike]

A related matter that may affect this: currently there's not much test
coverage for -fsave-optimization-record in trunk (sorry).

"trunk" currently has:

(a) selftest::test_building_json_from_dump_calls, which captures the
results of some dump calls, and does very minimal textual verification
of the JSON that would be emitted by -fsave-optimization-record.

(b) gcc.c-torture/compile/pr86636.c, which merely verifies that we
don't ICE with a particular usage of -fsave-optimization-record.

Ideally we'd have some test coverage of the file written out by -fsave-
optimization-record: that it's valid JSON, that it conforms to the
expected internal structure, and that the expected data is correct and
complete (relative to some known dump calls; I have a plugin for
testing this if need be, in gcc.dg/plugins).

I don't know if Tcl has any JSON support, but in Python, JSON support
is built-in to the standard library, so I wonder if there's a case for
having a DejaGnu directive to (optionally) call out to a Python script
to check the JSON file that's been written, using this optrecord.py
module to handle loading the JSON.  Doing so would implicitly check
that that the emitted adheres to the expected internal structure, and
the script could add additional testcase-specific verifications.

The directive would have to check for the presence of Python, and emit
an UNSUPPORTED if unavailable.

If that sounds sane (and I'm willing to try implementing it), then that
suggests that optrecord.py should live in the gcc source tree, and be
installed somewhere (though I'm not sure where).

Alternatively, this could be done as a selftest, by adding some
functions to json.h/cc for inspecting and traversing the in-memory JSON
tree.  Though that wouldn't be as effective as an "end-to-
end"/integration test.

Thoughts?

Dave


> > 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 <dmalcolm@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_lo

> > > > cati

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

> > > >
Jeff Law Aug. 3, 2018, 6:07 p.m. | #5
On 07/25/2018 08:59 AM, David Malcolm wrote:
> On Tue, 2018-07-24 at 16:11 +0200, Richard Biener wrote:

>> On Mon, Jul 23, 2018 at 9:20 PM David Malcolm <dmalcolm@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 <dmalcolm@redhat.co

>>>> m>

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

> 

> [CCing Rainer and Mike]

> 

> A related matter that may affect this: currently there's not much test

> coverage for -fsave-optimization-record in trunk (sorry).

> 

> "trunk" currently has:

> 

> (a) selftest::test_building_json_from_dump_calls, which captures the

> results of some dump calls, and does very minimal textual verification

> of the JSON that would be emitted by -fsave-optimization-record.

> 

> (b) gcc.c-torture/compile/pr86636.c, which merely verifies that we

> don't ICE with a particular usage of -fsave-optimization-record.

> 

> Ideally we'd have some test coverage of the file written out by -fsave-

> optimization-record: that it's valid JSON, that it conforms to the

> expected internal structure, and that the expected data is correct and

> complete (relative to some known dump calls; I have a plugin for

> testing this if need be, in gcc.dg/plugins).

> 

> I don't know if Tcl has any JSON support, but in Python, JSON support

> is built-in to the standard library, so I wonder if there's a case for

> having a DejaGnu directive to (optionally) call out to a Python script

> to check the JSON file that's been written, using this optrecord.py

> module to handle loading the JSON.  Doing so would implicitly check

> that that the emitted adheres to the expected internal structure, and

> the script could add additional testcase-specific verifications.

> 

> The directive would have to check for the presence of Python, and emit

> an UNSUPPORTED if unavailable.

I'd support this.  When a suitable python isn't available it fails
gracefully and gives us independent testing that the resulting bits are
proper JSON.

jeff

Patch

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 <dmalcolm@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()