From a921ae0480942ffdc2f86c5da4ad92d048cac46c Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 26 Jun 2018 13:46:49 -0400
Subject: [PATCH 10/46] FIXME: WIP on JSON optimization records

---
 gcc/Makefile.in          |   2 +
 gcc/common.opt           |   4 +
 gcc/coretypes.h          |   8 +
 gcc/doc/invoke.texi      |  16 +-
 gcc/dumpfile.c           |  15 +-
 gcc/dumpfile.h           |   3 +
 gcc/optinfo-emit-json.cc | 607 +++++++++++++++++++++++++++++++++++++++++++++++
 gcc/optinfo-emit-json.h  |  37 +++
 gcc/optinfo.cc           |   9 +-
 gcc/optinfo.h            |   2 +-
 gcc/selftest-run-tests.c |   1 +
 gcc/selftest.h           |   1 +
 gcc/toplev.c             |   5 +
 gcc/tree-ssa-live.c      |   4 +-
 14 files changed, 699 insertions(+), 15 deletions(-)
 create mode 100644 gcc/optinfo-emit-json.cc
 create mode 100644 gcc/optinfo-emit-json.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index ca17256..a53385c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1430,6 +1430,7 @@ OBJS = \
 	optabs-tree.o \
 	optinfo.o \
 	optinfo-emit-diagnostics.o \
+	optinfo-emit-json.o \
 	options-save.o \
 	opts-global.o \
 	passes.o \
@@ -2253,6 +2254,7 @@ s-bversion: BASE-VER
 	$(STAMP) s-bversion
 
 CFLAGS-toplev.o += -DTARGET_NAME=\"$(target_noncanonical)\"
+CFLAGS-optinfo-emit-json.o += -DTARGET_NAME=\"$(target_noncanonical)\"
 
 pass-instances.def: $(srcdir)/passes.def $(PASSES_EXTRA) \
 		    $(srcdir)/gen-pass-instances.awk
diff --git a/gcc/common.opt b/gcc/common.opt
index 908432e..89d9508 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1967,6 +1967,10 @@ fopt-info-
 Common Joined RejectNegative Var(common_deferred_options) Defer
 -fopt-info[-<type>=filename]	Dump compiler optimization details.
 
+fsave-optimization-record
+Common Report Var(flag_save_optimization_record) Optimization
+Write a SRCFILE.opt-record.json file detailing what optimizations were performed.
+
 foptimize-register-move
 Common Ignore
 Does nothing. Preserved for backward compatibility.
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index ed0e825..2fd20e4 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -332,6 +332,14 @@ namespace gcc {
 
 typedef std::pair <tree, tree> tree_pair;
 
+/* Define a name->value mapping.  */
+template <typename ValueType>
+struct kv_pair
+{
+  const char *const name;	/* the name of the value */
+  const ValueType value;	/* the value of the name */
+};
+
 #else
 
 struct _dont_use_rtx_here_;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index c96d202..f35801d 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -418,7 +418,8 @@ Objective-C and Objective-C++ Dialects}.
 -freorder-blocks-algorithm=@var{algorithm} @gol
 -freorder-blocks-and-partition  -freorder-functions @gol
 -frerun-cse-after-loop  -freschedule-modulo-scheduled-loops @gol
--frounding-math  -fsched2-use-superblocks  -fsched-pressure @gol
+-frounding-math  -fsave-optimization-record @gol
+-fsched2-use-superblocks  -fsched-pressure @gol
 -fsched-spec-load  -fsched-spec-load-dangerous @gol
 -fsched-stalled-insns-dep[=@var{n}]  -fsched-stalled-insns[=@var{n}] @gol
 -fsched-group-heuristic  -fsched-critical-path-heuristic @gol
@@ -9913,6 +9914,15 @@ Future versions of GCC may provide finer control of this setting
 using C99's @code{FENV_ACCESS} pragma.  This command-line option
 will be used to specify the default state for @code{FENV_ACCESS}.
 
+@item -fsave-optimization-record
+@opindex fsave-optimization-record
+Write a SRCFILE.opt-record.json file detailing what optimizations
+were performed.
+FIXME: The precise format is not yet set in stone, but it ought
+to be stabilized and then documented somewhere.
+FIXME: should this be described here within the optimization options,
+or within the developer options?
+
 @item -fsignaling-nans
 @opindex fsignaling-nans
 Compile code assuming that IEEE signaling NaNs may generate user-visible
@@ -12060,6 +12070,10 @@ on the decisions it is making on the code.
 
 The precise messages and their format are subject to change.
 
+Rather than attempting to parse the textual remark format, consider
+using @option{-fsave-optimization-record}, which works from the same
+data internally.
+
 @table @gcctabopt
 @item -fremarks
 @opindex fremarks
diff --git a/gcc/dumpfile.c b/gcc/dumpfile.c
index b3fce50..b3b0ce3 100644
--- a/gcc/dumpfile.c
+++ b/gcc/dumpfile.c
@@ -38,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cgraph.h"
 #include "tree-pass.h" /* for "current_pass".  */
 #include "optinfo-internal.h" /* for selftests.  */
+#include "optinfo-emit-json.h"
 
 /* If non-NULL, return one past-the-end of the matching SUBPART of
    the WHOLE string.  */
@@ -119,14 +120,6 @@ static struct dump_file_info dump_files[TDI_end] =
   DUMP_FILE_INFO (NULL, "ipa-all", DK_ipa, 0),
 };
 
-/* Define a name->number mapping for a dump flag value.  */
-template <typename ValueType>
-struct kv_pair
-{
-  const char *const name;	/* the name of the value */
-  const ValueType value;	/* the value of the name */
-};
-
 /* Table of dump options. This must be consistent with the TDF_* flags
    in dumpfile.h and opt_info_options below. */
 static const kv_pair<dump_flags_t> dump_options[] =
@@ -177,7 +170,7 @@ static const kv_pair<dump_flags_t> optinfo_verbosity_options[] =
 };
 
 /* Flags used for -fopt-info groups.  */
-static const kv_pair<optgroup_flags_t> optgroup_options[] =
+const kv_pair<optgroup_flags_t> optgroup_options[] =
 {
   {"ipa", OPTGROUP_IPA},
   {"loop", OPTGROUP_LOOP},
@@ -776,6 +769,7 @@ dump_context::end_scope ()
 {
   end_any_optinfo ();
   m_scope_depth--;
+  optimization_records_maybe_pop_dump_scope ();
 }
 
 /* Return the optinfo currently being accumulated, creating one if
@@ -802,7 +796,8 @@ dump_context::begin_next_optinfo (const dump_location_t &loc)
 }
 
 /* End any optinfo that has been accumulated within this context; emitting
-   it to any destinations as appropriate, such as a diagnostic "remark".  */
+   it to any destinations as appropriate, such as a diagnostic "remark",
+   or optimization records.  */
 
 void
 dump_context::end_any_optinfo ()
diff --git a/gcc/dumpfile.h b/gcc/dumpfile.h
index 1e3fdcb..30b8561 100644
--- a/gcc/dumpfile.h
+++ b/gcc/dumpfile.h
@@ -420,6 +420,7 @@ extern FILE *dump_begin (int, dump_flags_t *);
 extern void dump_end (int, FILE *);
 extern int opt_info_switch_p (const char *);
 extern const char *dump_flag_name (int);
+extern const kv_pair<optgroup_flags_t> optgroup_options[];
 
 /* Global variables used to communicate with passes.  */
 extern FILE *dump_file;
@@ -443,6 +444,7 @@ dump_enabled_p (void)
    (b) the -fopt-info destination, if any
    (c) to the "optinfo" destinations, if any:
        (c.1) as diagnostic "remarks"
+       (c.2) as optimization records
 
    dump_* (MSG_*) --> dumpfile.c --+--> (a) dump_file
                                    |
@@ -451,6 +453,7 @@ dump_enabled_p (void)
                                    `--> (c) optinfo
                                             `---> optinfo destinations
                                                   (c.1) diagnostic "remarks"
+                                                  (c.2) optimization records
 
    For optinfos, the dump_*_loc mark the beginning of an optinfo
    instance: all subsequent dump_* calls are consolidated into
diff --git a/gcc/optinfo-emit-json.cc b/gcc/optinfo-emit-json.cc
new file mode 100644
index 0000000..82eb336
--- /dev/null
+++ b/gcc/optinfo-emit-json.cc
@@ -0,0 +1,607 @@
+/* Emit optimization information as JSON files.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic-core.h"
+
+#include "profile.h"
+#include "output.h"
+#include "tree-pass.h"
+
+#include "optinfo.h"
+#include "optinfo-emit-json.h"
+#include "optinfo-internal.h"
+#include "json.h"
+#include "pretty-print.h"
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#include "cgraph.h"
+
+#include "langhooks.h"
+#include "version.h"
+#include "context.h"
+#include "pass_manager.h"
+#include "selftest.h"
+#include "dump-context.h"
+
+/* A class for writing out optimization records in JSON format.  */
+
+class optrecord_json_writer
+{
+public:
+  optrecord_json_writer ();
+  ~optrecord_json_writer ();
+  void write () const;
+  void add_record (const optinfo *optinfo);
+  void pop_scope ();
+
+  void add_record (json::object *obj);
+  json::object *impl_location_to_json (dump_impl_location_t loc);
+  json::object *location_to_json (location_t loc);
+  json::object *profile_count_to_json (profile_count count);
+  json::string *get_id_value_for_pass (opt_pass *pass);
+  json::object *pass_to_json (opt_pass *pass);
+  json::value *inlining_chain_to_json (location_t loc);
+  json::object *optinfo_to_json (const optinfo *optinfo);
+  void add_pass_list (json::array *arr, opt_pass *pass);
+
+private:
+  /* The root value for the JSON file.
+     Currently the JSON values are stored in memory, and flushed when the
+     compiler exits.  It would probably be better to simply write out
+     the JSON as we go.  */
+  json::array *m_root_tuple;
+
+  /* The currently open scopes, for expressing nested optimization records.  */
+  vec<json::array *> m_scopes;
+};
+
+/* optrecord_json_writer's ctor.  Populate the top-level parts of the
+   in-memory JSON representation.  */
+
+optrecord_json_writer::optrecord_json_writer ()
+  : m_root_tuple (NULL), m_scopes ()
+{
+  m_root_tuple = new json::array ();
+
+  /* Populate with metadata; compare with toplev.c: print_version.  */
+  json::object *metadata = new json::object ();
+  m_root_tuple->append (metadata);
+  metadata->set ("format", new json::string ("1"));
+  json::object *generator = new json::object ();
+  metadata->set ("generator", generator);
+  generator->set ("name", new json::string (lang_hooks.name));
+  generator->set ("pkgversion", new json::string (pkgversion_string));
+  generator->set ("version", new json::string (version_string));
+  /* TARGET_NAME is passed in by the Makefile.  */
+  generator->set ("target", new json::string (TARGET_NAME));
+
+  /* TODO: capture command-line?
+     see gen_producer_string in dwarf2out.c (currently static).  */
+
+  /* TODO: capture "any plugins?" flag (or the plugins themselves).  */
+
+  json::array *passes = new json::array ();
+  m_root_tuple->append (passes);
+
+  /* Call add_pass_list for all of the pass lists.  */
+  {
+#define DEF_PASS_LIST(LIST) \
+    add_pass_list (passes, g->get_passes ()->LIST);
+    GCC_PASS_LISTS
+#undef DEF_PASS_LIST
+  }
+
+  json::array *records = new json::array ();
+  m_root_tuple->append (records);
+
+  m_scopes.safe_push (records);
+}
+
+/* optrecord_json_writer's ctor.
+   Delete the in-memory JSON representation.  */
+
+optrecord_json_writer::~optrecord_json_writer ()
+{
+  delete m_root_tuple;
+}
+
+/* Choose an appropriate filename, and write the saved records to it.  */
+
+void
+optrecord_json_writer::write () const
+{
+  char *filename = concat (dump_base_name, ".opt-record.json", NULL);
+  FILE *outfile = fopen (filename, "w");
+  if (outfile)
+    {
+      m_root_tuple->dump (outfile);
+      fclose (outfile);
+    }
+  else
+    error_at (UNKNOWN_LOCATION, "unable to write optimization records to %qs",
+	      filename); // FIXME: more info?
+  free (filename);
+}
+
+/* Add a record for OPTINFO to the queue of records to be written.  */
+
+void
+optrecord_json_writer::add_record (const optinfo *optinfo)
+{
+  json::object *obj = optinfo_to_json (optinfo);
+
+  add_record (obj);
+
+  /* Potentially push the scope.  */
+  if (optinfo->get_kind () == OPTINFO_KIND_SCOPE)
+    {
+      json::array *children = new json::array ();
+      obj->set ("children", children);
+      m_scopes.safe_push (children);
+    }
+}
+
+/* Private methods of optrecord_json_writer.  */
+
+/* Add record OBJ to the the innermost scope.  */
+
+void
+optrecord_json_writer::add_record (json::object *obj)
+{
+  /* Add to innermost scope.  */
+  gcc_assert (m_scopes.length () > 0);
+  m_scopes[m_scopes.length () - 1]->append (obj);
+}
+
+/* Pop the innermost scope.  */
+
+void
+optrecord_json_writer::pop_scope ()
+{
+  m_scopes.pop ();
+}
+
+/* Create a JSON object representing LOC.  */
+
+json::object *
+optrecord_json_writer::impl_location_to_json (dump_impl_location_t loc)
+{
+  json::object *obj = new json::object ();
+  obj->set ("file", new json::string (loc.m_file));
+  obj->set ("line", new json::number (loc.m_line));
+  if (loc.m_function)
+    obj->set ("function", new json::string (loc.m_function));
+  return obj;
+}
+
+/* Create a JSON object representing LOC.  */
+
+json::object *
+optrecord_json_writer::location_to_json (location_t loc)
+{
+  json::object *obj = new json::object ();
+  obj->set ("file", new json::string (LOCATION_FILE (loc)));
+  obj->set ("line", new json::number (LOCATION_LINE (loc)));
+  obj->set ("column", new json::number (LOCATION_COLUMN (loc)));
+  return obj;
+}
+
+/* Create a JSON object representing COUNT.  */
+
+json::object *
+optrecord_json_writer::profile_count_to_json (profile_count count)
+{
+  json::object *obj = new json::object ();
+  obj->set ("value", new json::number (count.to_gcov_type ()));
+  obj->set ("quality",
+	    new json::string (profile_quality_as_string (count.quality ())));
+  return obj;
+}
+
+/* Get a string for use when referring to PASS in the saved optimization
+   records.  */
+
+json::string *
+optrecord_json_writer::get_id_value_for_pass (opt_pass *pass)
+{
+  pretty_printer pp;
+  /* this is host-dependent, but will be consistent for a given host.  */
+  pp_pointer (&pp, static_cast<void *> (pass));
+  return new json::string (pp_formatted_text (&pp));
+}
+
+/* Create a JSON object representing PASS.  */
+
+json::object *
+optrecord_json_writer::pass_to_json (opt_pass *pass)
+{
+  json::object *obj = new json::object ();
+  const char *type = NULL;
+  switch (pass->type)
+    {
+    default:
+      gcc_unreachable ();
+    case GIMPLE_PASS:
+      type = "gimple";
+      break;
+    case RTL_PASS:
+      type = "rtl";
+      break;
+    case SIMPLE_IPA_PASS:
+      type = "simple_ipa";
+      break;
+    case IPA_PASS:
+      type = "ipa";
+      break;
+    }
+  obj->set ("id", get_id_value_for_pass (pass));
+  obj->set ("type", new json::string (type));
+  obj->set ("name", new json::string (pass->name));
+  /* Represent the optgroup flags as an array.  */
+  {
+    json::array *optgroups = new json::array ();
+    obj->set ("optgroups", optgroups);
+    for (const kv_pair<optgroup_flags_t> *optgroup = optgroup_options;
+	 optgroup->name != NULL; optgroup++)
+      if (optgroup->value != OPTGROUP_ALL
+	  && (pass->optinfo_flags & optgroup->value))
+	optgroups->append (new json::string (optgroup->name));
+  }
+  obj->set ("num", new json::number (pass->static_pass_number));
+  return obj;
+}
+
+/* Create a JSON array for LOC representing the chain of inlining
+   locations.
+   Compare with lhd_print_error_function and cp_print_error_function.  */
+
+json::value *
+optrecord_json_writer::inlining_chain_to_json (location_t loc)
+{
+  json::array *array = new json::array ();
+
+  tree abstract_origin = LOCATION_BLOCK (loc);
+
+  while (abstract_origin)
+    {
+      location_t *locus;
+      tree block = abstract_origin;
+
+      locus = &BLOCK_SOURCE_LOCATION (block);
+      tree fndecl = NULL;
+      block = BLOCK_SUPERCONTEXT (block);
+      while (block && TREE_CODE (block) == BLOCK
+	     && BLOCK_ABSTRACT_ORIGIN (block))
+	{
+	  tree ao = BLOCK_ABSTRACT_ORIGIN (block);
+
+	  while (TREE_CODE (ao) == BLOCK
+		 && BLOCK_ABSTRACT_ORIGIN (ao)
+		 && BLOCK_ABSTRACT_ORIGIN (ao) != ao)
+	    ao = BLOCK_ABSTRACT_ORIGIN (ao);
+
+	  if (TREE_CODE (ao) == FUNCTION_DECL)
+	    {
+	      fndecl = ao;
+	      break;
+	    }
+	  else if (TREE_CODE (ao) != BLOCK)
+	    break;
+
+	  block = BLOCK_SUPERCONTEXT (block);
+	}
+      if (fndecl)
+	abstract_origin = block;
+      else
+	{
+	  while (block && TREE_CODE (block) == BLOCK)
+	    block = BLOCK_SUPERCONTEXT (block);
+
+	  if (block && TREE_CODE (block) == FUNCTION_DECL)
+	    fndecl = block;
+	  abstract_origin = NULL;
+	}
+      if (fndecl)
+	{
+	  json::object *obj = new json::object ();
+	  const char *printable_name
+	    = lang_hooks.decl_printable_name (fndecl, 2);
+	  obj->set ("fndecl", new json::string (printable_name));
+	  if (*locus != UNKNOWN_LOCATION)
+	    obj->set ("site", location_to_json (*locus));
+	  array->append (obj);
+	}
+    }
+
+  return array;
+}
+
+/* Create a JSON object representing OPTINFO.  */
+
+json::object *
+optrecord_json_writer::optinfo_to_json (const optinfo *optinfo)
+{
+  json::object *obj = new json::object ();
+
+  obj->set ("impl_location",
+	    impl_location_to_json (optinfo->get_impl_location ()));
+
+  const char *kind_str = optinfo_kind_to_string (optinfo->get_kind ());
+  obj->set ("kind", new json::string (kind_str));
+  json::array *message = new json::array ();
+  obj->set ("message", message);
+  for (unsigned i = 0; i < optinfo->num_items (); i++)
+    {
+      const optinfo_item *item = optinfo->get_item (i);
+      switch (item->get_kind ())
+	{
+	default:
+	  gcc_unreachable ();
+	case OPTINFO_ITEM_KIND_TEXT:
+	  {
+	    const optinfo_item_text *as_text = (const optinfo_item_text *)item;
+	    message->append (new json::string (as_text->get_text ()));
+	  }
+	  break;
+	case OPTINFO_ITEM_KIND_TREE:
+	  {
+	    const optinfo_item_tree *as_tree = (const optinfo_item_tree *)item;
+	    tree node = as_tree->get_node ();
+	    json::object *item = new json::object ();
+	    /* Capture output of:
+	         dump_generic_expr (MSG_NOTE, flags, node);
+	       into a text buffer, and add it as a attribute of ITEM.
+	       This is:
+	         print_generic_expr (alt_dump_file, node, dump_flags | extra_dump_flags);
+	       which is:
+		 maybe_init_pretty_print (file);
+		 dump_generic_node (tree_pp, node, 0, flags, false);
+		 pp_flush (tree_pp);
+	    */
+	    pretty_printer pp;
+	    pp_needs_newline (&pp) = true;
+	    pp_translate_identifiers (&pp) = false;
+
+	    dump_generic_node (&pp, node, 0, as_tree->get_flags (), false);
+
+	    item->set ("expr", new json::string (pp_formatted_text (&pp)));
+
+	    /* Capture any location for the node.  */
+	    if (EXPR_HAS_LOCATION (node))
+	      item->set ("location", location_to_json (EXPR_LOCATION (node)));
+
+	    message->append (item);
+	  }
+	  break;
+	case OPTINFO_ITEM_KIND_GIMPLE:
+	  {
+	    const optinfo_item_gimple *as_gimple = (const optinfo_item_gimple *)item;
+	    gimple *stmt = as_gimple->get_stmt ();
+	    json::object *item = new json::object ();
+	    /* Capture output of:
+		 dump_gimple_stmt (MSG_NOTE, TDF_SLIM, stmt, 0);
+	       into a text buffer, and add it as a attribute of ITEM.
+	       This is:
+		 print_gimple_stmt (dump_file, gs, spc, dump_flags | extra_dump_flags);
+	       which is:
+		 pp_needs_newline (&buffer) = true;
+		 buffer.buffer->stream = file;
+		 pp_gimple_stmt_1 (&buffer, g, spc, flags);
+		 pp_newline_and_flush (&buffer);
+	    */
+	    pretty_printer pp;
+	    pp_needs_newline (&pp) = true;
+	    pp_gimple_stmt_1 (&pp, stmt, 0, TDF_SLIM);
+	    item->set ("stmt", new json::string (pp_formatted_text (&pp)));
+
+	    /* Capture any location for the stmt.  */
+	    if (gimple_location (stmt))
+	      item->set ("location", location_to_json (gimple_location (stmt)));
+
+	    message->append (item);
+	  }
+	  break;
+	case OPTINFO_ITEM_KIND_SYMTAB_NODE:
+	  {
+	    const optinfo_item_symtab_node *as_symtab_node
+	      = (const optinfo_item_symtab_node *)item;
+	    symtab_node *node = as_symtab_node->get_node ();
+	    json::object *item = new json::object ();
+	    item->set ("name", new json::string (node->name ()));
+	    item->set ("order", new json::number (node->order));
+	    if (DECL_SOURCE_LOCATION (node->decl) != UNKNOWN_LOCATION)
+	      item->set ("location",
+			 location_to_json (DECL_SOURCE_LOCATION (node->decl)));
+	    message->append (item);
+	  }
+	  break;
+	}
+   }
+
+  if (optinfo->get_pass ())
+    obj->set ("pass", get_id_value_for_pass (optinfo->get_pass ()));
+
+  profile_count count = optinfo->get_count ();
+  if (count.initialized_p ())
+    obj->set ("count", profile_count_to_json (count));
+
+  /* Record any location, handling the case where of an UNKNOWN_LOCATION
+     within an inlined block.  */
+  location_t loc = optinfo->get_location_t ();
+  if (get_pure_location (line_table, loc) != UNKNOWN_LOCATION)
+    {
+      // TOOD: record the location (just caret for now)
+      // TODO: start/finish also?
+      obj->set ("location", location_to_json (loc));
+    }
+
+  if (current_function_decl)
+    {
+      const char *fnname = get_fnname_from_decl (current_function_decl);
+      obj->set ("function", new json::string (fnname));
+    }
+
+  if (loc != UNKNOWN_LOCATION)
+    obj->set ("inlining_chain", inlining_chain_to_json (loc));
+
+  return obj;
+}
+
+/* Add a json description of PASS and its siblings to ARR, recursing into
+   child passes (adding their descriptions within a "children" array).  */
+
+void
+optrecord_json_writer::add_pass_list (json::array *arr, opt_pass *pass)
+{
+  do
+    {
+      json::object *pass_obj = pass_to_json (pass);
+      arr->append (pass_obj);
+      if (pass->sub)
+	{
+	  json::array *sub = new json::array ();
+	  pass_obj->set ("children", sub);
+	  add_pass_list (sub, pass->sub);
+	}
+      pass = pass->next;
+    }
+  while (pass);
+}
+
+/* File-level interface to rest of compiler (to avoid exposing
+   class optrecord_json_writer outside of this file).  */
+
+static optrecord_json_writer *the_json_writer;
+
+/* Perform startup activity for -fsave-optimization-record.  */
+
+void
+optimization_records_start ()
+{
+  /* Bail if recording not enabled.  */
+  if (!flag_save_optimization_record)
+    return;
+
+  the_json_writer = new optrecord_json_writer ();
+}
+
+/* Perform cleanup activity for -fsave-optimization-record.
+
+   Currently, the file is written out here in one go, before cleaning
+   up.  */
+
+void
+optimization_records_finish ()
+{
+  /* Bail if recording not enabled.  */
+  if (!the_json_writer)
+    return;
+
+  the_json_writer->write ();
+
+  delete the_json_writer;
+  the_json_writer = NULL;
+}
+
+/* Did the user request optimization records to be written out?  */
+
+bool
+optimization_records_enabled_p ()
+{
+  return the_json_writer != NULL;
+}
+
+/* If optimization records were requested, then add a record for OPTINFO
+   to the queue of records to be written.  */
+
+void
+optimization_records_maybe_record_optinfo (const optinfo *optinfo)
+{
+  /* Bail if recording not enabled.  */
+  if (!the_json_writer)
+    return;
+
+  the_json_writer->add_record (optinfo);
+}
+
+/* Handling for the end of a dump scope for the
+   optimization records sink.  */
+
+void
+optimization_records_maybe_pop_dump_scope ()
+{
+  /* Bail if recording not enabled.  */
+  if (!the_json_writer)
+    return;
+
+  the_json_writer->pop_scope ();
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Verify that we can build a JSON optimization record from dump_*
+   calls.  */
+
+static void
+test_building_json_from_dump_calls ()
+{
+  temp_dump_context tmp (true);
+  dump_location_t loc;
+  dump_printf_loc (MSG_NOTE, loc, "test of tree: ");
+  dump_generic_expr (MSG_NOTE, TDF_SLIM, integer_zero_node);
+  optinfo *info = tmp.get_pending_optinfo ();
+  ASSERT_TRUE (info != NULL);
+  ASSERT_EQ (info->num_items (), 2);
+
+  optrecord_json_writer writer;
+  json::object *json_obj = writer.optinfo_to_json (info);
+  ASSERT_TRUE (json_obj != NULL);
+
+  /* Verify that the json is sane.  */
+  char *json_str = json_obj->to_str ();
+  ASSERT_STR_CONTAINS (json_str, "impl_location");
+  ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\"");
+  ASSERT_STR_CONTAINS (json_str,
+		       "\"message\": [\"test of tree: \", {\"expr\": \"0\"}]");
+
+  free (json_str);
+  delete json_obj;
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+optinfo_emit_json_cc_tests ()
+{
+  test_building_json_from_dump_calls ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/optinfo-emit-json.h b/gcc/optinfo-emit-json.h
new file mode 100644
index 0000000..4892992
--- /dev/null
+++ b/gcc/optinfo-emit-json.h
@@ -0,0 +1,37 @@
+/* Emit optimization information as JSON files.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+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/>.  */
+
+#ifndef GCC_OPTINFO_EMIT_JSON_H
+#define GCC_OPTINFO_EMIT_JSON_H
+
+class optinfo;
+struct opt_pass;
+
+extern void optimization_records_start ();
+extern void optimization_records_finish ();
+
+// FIXME: maybe make this inline?
+extern bool optimization_records_enabled_p ();
+
+extern void optimization_records_maybe_record_optinfo (const optinfo *);
+extern void optimization_records_maybe_pop_dump_scope ();
+
+
+#endif /* #ifndef GCC_OPTINFO_EMIT_JSON_H */
diff --git a/gcc/optinfo.cc b/gcc/optinfo.cc
index bc86696..8692837 100644
--- a/gcc/optinfo.cc
+++ b/gcc/optinfo.cc
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "optinfo.h"
 #include "optinfo-internal.h"
 #include "optinfo-emit-diagnostics.h"
+#include "optinfo-emit-json.h"
 #include "dump-context.h"
 #include "selftest.h"
 
@@ -115,6 +116,9 @@ optinfo::emit ()
 
   /* -fremarks.  */
   emit_optinfo_as_diagnostic_remark (this);
+
+  /* -fsave-optimization-record.  */
+  optimization_records_maybe_record_optinfo (this);
 }
 
 /* Update the optinfo's kind based on DUMP_KIND.  */
@@ -206,7 +210,8 @@ optinfo::add_dec (const wide_int_ref &wi, signop sgn)
 bool optinfo_enabled_p ()
 {
   return (dump_context::get ().forcibly_enable_optinfo_p ()
-	  || flag_remarks);
+	  || flag_remarks
+	  || optimization_records_enabled_p ());
 }
 
 /* Return true if any of the active optinfo destinations make use
@@ -215,7 +220,7 @@ bool optinfo_enabled_p ()
 
 bool optinfo_wants_inlining_info_p ()
 {
-  return false;
+  return optimization_records_enabled_p ();
 }
 
 #if CHECKING_P
diff --git a/gcc/optinfo.h b/gcc/optinfo.h
index 8a8f812..007a7ff 100644
--- a/gcc/optinfo.h
+++ b/gcc/optinfo.h
@@ -27,7 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 
    * as a "remark" through the diagnostics subsystem
 
-   * saved to a file as an "optimization record" (not yet implemented)
+   * saved to a file as an "optimization record"
 
    They are generated in response to calls to the "dump_*" API in
    dumpfile.h; repeated calls to the "dump_*" API are consolidated
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 74315da..4a6d559 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -75,6 +75,7 @@ selftest::run_tests ()
   json_cc_tests ();
   optinfo_cc_tests ();
   optinfo_emit_diagnostics_cc_tests ();
+  optinfo_emit_json_cc_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index e8ac28c..16c8b59 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -231,6 +231,7 @@ extern void input_c_tests ();
 extern void json_cc_tests ();
 extern void optinfo_cc_tests ();
 extern void optinfo_emit_diagnostics_cc_tests ();
+extern void optinfo_emit_json_cc_tests ();
 extern void predict_c_tests ();
 extern void pretty_print_c_tests ();
 extern void read_rtl_function_c_tests ();
diff --git a/gcc/toplev.c b/gcc/toplev.c
index d108096..a047390 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -83,6 +83,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-pass.h"
 #include "dumpfile.h"
 #include "ipa-fnsummary.h"
+#include "optinfo-emit-json.h"
 
 #if defined(DBX_DEBUGGING_INFO) || defined(XCOFF_DEBUGGING_INFO)
 #include "dbxout.h"
@@ -487,6 +488,8 @@ compile_file (void)
   if (lang_hooks.decls.post_compilation_parsing_cleanups)
     lang_hooks.decls.post_compilation_parsing_cleanups ();
 
+  optimization_records_finish ();
+
   if (seen_error ())
     return;
 
@@ -2048,6 +2051,8 @@ do_compile ()
 
       timevar_start (TV_PHASE_SETUP);
 
+      optimization_records_start ();
+
       /* This must be run always, because it is needed to compute the FP
 	 predefined macros, such as __LDBL_MAX__, for targets using non
 	 default FP formats.  */
diff --git a/gcc/tree-ssa-live.c b/gcc/tree-ssa-live.c
index 7447f7a..2623d9b 100644
--- a/gcc/tree-ssa-live.c
+++ b/gcc/tree-ssa-live.c
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cfgloop.h"
 #include "stringpool.h"
 #include "attribs.h"
+#include "optinfo.h"
 
 static void verify_live_on_entry (tree_live_info_p);
 
@@ -552,7 +553,8 @@ remove_unused_scope_block_p (tree scope, bool in_ctor_dtor_block)
      ;
    /* When not generating debug info we can eliminate info on unused
       variables.  */
-   else if (!flag_auto_profile && debug_info_level == DINFO_LEVEL_NONE)
+   else if (!flag_auto_profile && debug_info_level == DINFO_LEVEL_NONE
+	    && !optinfo_wants_inlining_info_p ())
      {
        /* Even for -g0 don't prune outer scopes from artificial
 	  functions, otherwise diagnostics using tree_nonartificial_location
-- 
1.8.5.3

