diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9b85787..cd17591 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1421,6 +1421,9 @@ OBJS = \
 	omp-grid.o \
 	omp-low.o \
 	omp-simd-clone.o \
+	optinfo.o \
+	optinfo-emit-diagnostics.o \
+	optinfo-emit-json.o \
 	optabs.o \
 	optabs-libfuncs.o \
 	optabs-query.o \
@@ -2248,6 +2251,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
@@ -2583,6 +2587,7 @@ GTFILES = $(CPP_ID_DATA_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/internal-fn.h \
   $(srcdir)/hsa-common.c \
   $(srcdir)/calls.c \
+  $(srcdir)/dumpfile.h \
   @all_gtfiles@
 
 # Compute the list of GT header files from the corresponding C sources,
diff --git a/gcc/cgraph.c b/gcc/cgraph.c
index 3899467..191280e 100644
--- a/gcc/cgraph.c
+++ b/gcc/cgraph.c
@@ -268,7 +268,7 @@ void
 symbol_table::initialize (void)
 {
   if (!dump_file)
-    dump_file = dump_begin (TDI_cgraph, NULL);
+    set_dump_file (dump_begin (TDI_cgraph, NULL));
 
   if (!ipa_clones_dump_file)
     ipa_clones_dump_file = dump_begin (TDI_clones, NULL);
@@ -3582,7 +3582,7 @@ cgraph_node::get_body (void)
       const char *saved_dump_file_name = dump_file_name;
       dump_flags_t saved_dump_flags = dump_flags;
       dump_file_name = NULL;
-      dump_file = NULL;
+      set_dump_file (NULL);
 
       push_cfun (DECL_STRUCT_FUNCTION (decl));
       execute_all_ipa_transforms ();
@@ -3593,7 +3593,7 @@ cgraph_node::get_body (void)
       updated = true;
 
       current_pass = saved_current_pass;
-      dump_file = saved_dump_file;
+      set_dump_file (saved_dump_file);
       dump_file_name = saved_dump_file_name;
       dump_flags = saved_dump_flags;
     }
diff --git a/gcc/cgraphunit.c b/gcc/cgraphunit.c
index 04b6919..7cfb8a0 100644
--- a/gcc/cgraphunit.c
+++ b/gcc/cgraphunit.c
@@ -928,8 +928,7 @@ walk_polymorphic_call_targets (hash_set<void *> *reachable_call_targets,
 	    }
           if (dump_enabled_p ())
             {
-	      location_t locus = gimple_location_safe (edge->call_stmt);
-	      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, locus,
+	      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, edge->call_stmt,
 			       "devirtualizing call in %s to %s\n",
 			       edge->caller->name (), target->name ());
 	    }
diff --git a/gcc/common.opt b/gcc/common.opt
index 4aebcaf..21882fc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -506,6 +506,11 @@ Driver Negative(Qn)
 R
 Driver Joined Separate
 
+fremarks
+Common Remark Var(flag_remarks)
+Emit diagnostic remarks about optimizations
+FIXME: should this be -fdiagnostics-foo or somesuch?
+
 S
 Driver
 
@@ -1942,6 +1947,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 283b4eb..33f3d21 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -134,6 +134,13 @@ struct gomp_single;
 struct gomp_target;
 struct gomp_teams;
 
+/* Subclasses of symtab_node_def, using indentation to show the class
+   hierarchy.  */
+
+class symtab_node;
+  struct cgraph_node;
+  class varpool_node;
+
 union section;
 typedef union section section;
 struct gcc_options;
@@ -325,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/coverage.c b/gcc/coverage.c
index 84fff13..acea676 100644
--- a/gcc/coverage.c
+++ b/gcc/coverage.c
@@ -342,7 +342,7 @@ get_coverage_counts (unsigned counter, unsigned expected,
       static int warned = 0;
 
       if (!warned++ && dump_enabled_p ())
-	dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, input_location,
+	dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, optinfo_location (),
                          (flag_guess_branch_prob
                           ? "file %s not found, execution counts estimated\n"
                           : "file %s not found, execution counts assumed to "
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index 3ee21bc..bcc3767 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -84,6 +84,8 @@ static struct color_cap color_dict[] =
   { "error", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED), 5, false },
   { "warning", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA),
 	       7, false },
+  { "remark", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN),
+	       6, false },
   { "note", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
   { "range1", SGR_SEQ (COLOR_FG_GREEN), 6, false },
   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index aa5807e..63166b8 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -69,6 +69,8 @@ extern bool warning_at (location_t, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool warning_at (rich_location *, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern bool remark (location_t, int, const char *, ...)
+    ATTRIBUTE_GCC_DIAG(3,4);
 extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void error_n (location_t, unsigned HOST_WIDE_INT, const char *,
 		     const char *, ...)
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index e22c17b..97ed88b 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -492,6 +492,7 @@ diagnostic_action_after_output (diagnostic_context *context,
     {
     case DK_DEBUG:
     case DK_NOTE:
+    case DK_REMARK:
     case DK_ANACHRONISM:
     case DK_WARNING:
       break;
@@ -1274,6 +1275,22 @@ warning_n (location_t location, int opt, unsigned HOST_WIDE_INT n,
   return ret;
 }
 
+/* Emit an optimization remark at LOCATION.  This is used by the optinfo
+   framework.
+   Return true if the remark was printed, false if it was inhibited.  */
+// FIXME: we don't yet have a more fine-grained way of inhibiting remarks
+
+bool
+remark (location_t location, int opt, const char *gmsgid, ...)
+{
+  va_list ap;
+  va_start (ap, gmsgid);
+  rich_location richloc (line_table, location);
+  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_REMARK);
+  va_end (ap);
+  return ret;
+}
+
 /* A "pedantic" warning at LOCATION: issues a warning unless
    -pedantic-errors was given on the command line, in which case it
    issues an error.  Use this for diagnostics required by the relevant
diff --git a/gcc/diagnostic.def b/gcc/diagnostic.def
index ce3dc56..b58095d 100644
--- a/gcc/diagnostic.def
+++ b/gcc/diagnostic.def
@@ -37,6 +37,7 @@ DEFINE_DIAGNOSTIC_KIND (DK_SORRY, "sorry, unimplemented: ", "error")
 DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "warning: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note: ", "note")
+DEFINE_DIAGNOSTIC_KIND (DK_REMARK, "remark: ", "remark")
 DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug: ", "note")
 /* These two would be re-classified as DK_WARNING or DK_ERROR, so the
 prefix does not matter.  */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index b06ea6e..c880c60 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -141,6 +141,7 @@ only one of these two forms, whichever one is not the default.
 * Debugging Options::   Producing debuggable code.
 * Optimize Options::    How much optimization?
 * Instrumentation Options:: Enabling profiling and extra run-time error checking.
+* Remarks::             Details on how your code is being optimized.
 * Preprocessor Options:: Controlling header files and macro definitions.
                          Also, getting dependency information for Make.
 * Assembler Options::   Passing options to the assembler.
@@ -416,7 +417,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
@@ -470,6 +472,10 @@ Objective-C and Objective-C++ Dialects}.
 -finstrument-functions-exclude-function-list=@var{sym},@var{sym},@dots{} @gol
 -finstrument-functions-exclude-file-list=@var{file},@var{file},@dots{}}
 
+@item Remarks
+@xref{Remarks,,Options to Control Remarks from the Compiler}.
+@gccoptlist{-fremarks}
+
 @item Preprocessor Options
 @xref{Preprocessor Options,,Options Controlling the Preprocessor}.
 @gccoptlist{-A@var{question}=@var{answer} @gol
@@ -9904,6 +9910,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
@@ -12037,6 +12052,23 @@ The NOP instructions are inserted at---and maybe before, depending on
 @end table
 
 
+@node Remarks
+@section Options to Control Remarks from the Compiler
+@cindex remarks
+@cindex options, remarks
+
+These options are aimed at advanced users who may be interested
+in seeing additional diagnostics from the compiler, giving information
+on the decisions it is making on the code.
+
+@table @gcctabopt
+@item -fremarks
+@opindex fremarks
+Emit diagnostic remarks about optimizations.
+FIXME: better description needed here.
+
+@end table
+
 @node Preprocessor Options
 @section Options Controlling the Preprocessor
 @cindex preprocessor options
diff --git a/gcc/dumpfile.c b/gcc/dumpfile.c
index 2f11284..ede2a25 100644
--- a/gcc/dumpfile.c
+++ b/gcc/dumpfile.c
@@ -29,6 +29,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "profile-count.h"
 #include "tree-cfg.h"
 #include "langhooks.h"
+#include "optinfo.h"
+#include "cgraph.h"
+#include "optinfo-emit-json.h"
 
 /* If non-NULL, return one past-the-end of the matching SUBPART of
    the WHOLE string.  */
@@ -36,18 +39,52 @@ along with GCC; see the file COPYING3.  If not see
    (strncmp (whole, part, strlen (part)) ? NULL : whole + strlen (part))
 
 static dump_flags_t pflags;		      /* current dump_flags */
-static dump_flags_t alt_flags;		      /* current opt_info flags */
 
 static void dump_loc (dump_flags_t, FILE *, source_location);
+
+/* Current -fopt-info output stream, if any, and flags.  */
+static FILE *alt_dump_file = NULL;
+static dump_flags_t alt_flags;
+
 static FILE *dump_open_alternate_stream (struct dump_file_info *);
 
 /* These are currently used for communicating between passes.
    However, instead of accessing them directly, the passes can use
    dump_printf () for dumps.  */
 FILE *dump_file = NULL;
-FILE *alt_dump_file = NULL;
 const char *dump_file_name;
 dump_flags_t dump_flags;
+bool dumps_are_enabled = false;
+
+
+/* Update the "dumps_are_enabled" global; to be called whenever dump_file
+   or alt_dump_file change.  */
+
+static void
+refresh_dumps_are_enabled ()
+{
+  dumps_are_enabled = (dump_file || alt_dump_file || optinfo_enabled_p ());
+}
+
+/* Set global "dump_file" to NEW_DUMP_FILE, refreshing the "dumps_are_enabled"
+   global.  */
+
+void
+set_dump_file (FILE *new_dump_file)
+{
+  dump_file = new_dump_file;
+  refresh_dumps_are_enabled ();
+}
+
+/* Set "alt_dump_file" to NEW_ALT_DUMP_FILE, refreshing the "dumps_are_enabled"
+   global.  */
+
+static void
+set_alt_dump_file (FILE *new_alt_dump_file)
+{
+  alt_dump_file = new_alt_dump_file;
+  refresh_dumps_are_enabled ();
+}
 
 #define DUMP_FILE_INFO(suffix, swtch, dkind, num) \
   {suffix, swtch, NULL, NULL, NULL, NULL, NULL, dkind, TDF_NONE, TDF_NONE, \
@@ -74,14 +111,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[] =
@@ -132,7 +161,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},
@@ -360,7 +389,7 @@ dump_open_alternate_stream (struct dump_file_info *dfi)
 
 /* Print source location on DFILE if enabled.  */
 
-void
+static void
 dump_loc (dump_flags_t dump_kind, FILE *dfile, source_location loc)
 {
   if (dump_kind)
@@ -373,6 +402,8 @@ dump_loc (dump_flags_t dump_kind, FILE *dfile, source_location loc)
                  DECL_SOURCE_FILE (current_function_decl),
                  DECL_SOURCE_LINE (current_function_decl),
                  DECL_SOURCE_COLUMN (current_function_decl));
+      /* Indentation based on scope depth.  */
+      fprintf (dfile, "%*s", get_dump_scope_depth (), "");
     }
 }
 
@@ -388,25 +419,43 @@ dump_gimple_stmt (dump_flags_t dump_kind, dump_flags_t extra_dump_flags,
 
   if (alt_dump_file && (dump_kind & alt_flags))
     print_gimple_stmt (alt_dump_file, gs, spc, dump_flags | extra_dump_flags);
+
+  if (optinfo_enabled_p ())
+    {
+      /* Attempt to consolidate via optinfo_guard.  */
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      info.add_stmt (gs, extra_dump_flags);
+    }
 }
 
 /* Similar to dump_gimple_stmt, except additionally print source location.  */
 
 void
-dump_gimple_stmt_loc (dump_flags_t dump_kind, source_location loc,
+dump_gimple_stmt_loc (dump_flags_t dump_kind, const optinfo_location &loc,
 		      dump_flags_t extra_dump_flags, gimple *gs, int spc)
 {
+  location_t srcloc = loc.get_location_t ();
+
   if (dump_file && (dump_kind & pflags))
     {
-      dump_loc (dump_kind, dump_file, loc);
+      dump_loc (dump_kind, dump_file, srcloc);
       print_gimple_stmt (dump_file, gs, spc, dump_flags | extra_dump_flags);
     }
 
   if (alt_dump_file && (dump_kind & alt_flags))
     {
-      dump_loc (dump_kind, alt_dump_file, loc);
+      dump_loc (dump_kind, alt_dump_file, srcloc);
       print_gimple_stmt (alt_dump_file, gs, spc, dump_flags | extra_dump_flags);
     }
+
+  if (optinfo_enabled_p ())
+    {
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      info.handle_loc (loc);
+      info.add_stmt (gs, extra_dump_flags);
+    }
 }
 
 /* Dump expression tree T using EXTRA_DUMP_FLAGS on dump streams if
@@ -421,37 +470,59 @@ dump_generic_expr (dump_flags_t dump_kind, dump_flags_t extra_dump_flags,
 
   if (alt_dump_file && (dump_kind & alt_flags))
       print_generic_expr (alt_dump_file, t, dump_flags | extra_dump_flags);
-}
 
+  if (optinfo_enabled_p ())
+    {
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      info.add_tree (t, extra_dump_flags);
+    }
+}
 
 /* Similar to dump_generic_expr, except additionally print the source
    location.  */
 
+/* Output a formatted message using FORMAT on appropriate dump streams.  */
+
 void
-dump_generic_expr_loc (dump_flags_t dump_kind, source_location loc,
-		       dump_flags_t extra_dump_flags, tree t)
+dump_printf (dump_flags_t dump_kind, const char *format, ...)
 {
   if (dump_file && (dump_kind & pflags))
     {
-      dump_loc (dump_kind, dump_file, loc);
-      print_generic_expr (dump_file, t, dump_flags | extra_dump_flags);
+      va_list ap;
+      va_start (ap, format);
+      vfprintf (dump_file, format, ap);
+      va_end (ap);
     }
 
   if (alt_dump_file && (dump_kind & alt_flags))
     {
-      dump_loc (dump_kind, alt_dump_file, loc);
-      print_generic_expr (alt_dump_file, t, dump_flags | extra_dump_flags);
+      va_list ap;
+      va_start (ap, format);
+      vfprintf (alt_dump_file, format, ap);
+      va_end (ap);
+    }
+  if (optinfo_enabled_p ())
+    {
+      /* Attempt to consolidate via optinfo_guard.  */
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      va_list ap;
+      va_start (ap, format);
+      info.add_printf_va (format, ap);
+      va_end (ap);
     }
 }
 
-/* Output a formatted message using FORMAT on appropriate dump streams.  */
+/* Similar to dump_printf, except source location is also printed.  */
 
 void
-dump_printf (dump_flags_t dump_kind, const char *format, ...)
+dump_printf_loc (dump_flags_t dump_kind, source_location loc,
+		 const char *format, ...)
 {
   if (dump_file && (dump_kind & pflags))
     {
       va_list ap;
+      dump_loc (dump_kind, dump_file, loc);
       va_start (ap, format);
       vfprintf (dump_file, format, ap);
       va_end (ap);
@@ -460,22 +531,35 @@ dump_printf (dump_flags_t dump_kind, const char *format, ...)
   if (alt_dump_file && (dump_kind & alt_flags))
     {
       va_list ap;
+      dump_loc (dump_kind, alt_dump_file, loc);
       va_start (ap, format);
       vfprintf (alt_dump_file, format, ap);
       va_end (ap);
     }
+  if (optinfo_enabled_p ())
+    {
+      /* Attempt to consolidate via optinfo_guard.  */
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      va_list ap;
+      va_start (ap, format);
+      info.add_printf_va (format, ap);
+      va_end (ap);
+    }
 }
 
-/* Similar to dump_printf, except source location is also printed.  */
+/* As above, but via an optinfo_location.  */
 
 void
-dump_printf_loc (dump_flags_t dump_kind, source_location loc,
+dump_printf_loc (dump_flags_t dump_kind, const optinfo_location &loc,
 		 const char *format, ...)
 {
+  location_t srcloc = loc.get_location_t ();
+
   if (dump_file && (dump_kind & pflags))
     {
       va_list ap;
-      dump_loc (dump_kind, dump_file, loc);
+      dump_loc (dump_kind, dump_file, srcloc);
       va_start (ap, format);
       vfprintf (dump_file, format, ap);
       va_end (ap);
@@ -484,11 +568,23 @@ dump_printf_loc (dump_flags_t dump_kind, source_location loc,
   if (alt_dump_file && (dump_kind & alt_flags))
     {
       va_list ap;
-      dump_loc (dump_kind, alt_dump_file, loc);
+      dump_loc (dump_kind, alt_dump_file, srcloc);
       va_start (ap, format);
       vfprintf (alt_dump_file, format, ap);
       va_end (ap);
     }
+
+  if (optinfo_enabled_p ())
+    {
+      /* Attempt to consolidate via optinfo_guard.  */
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      info.handle_loc (loc);
+      va_list ap;
+      va_start (ap, format);
+      info.add_printf_va (format, ap);
+      va_end (ap);
+    }
 }
 
 /* Output VALUE in decimal to appropriate dump streams.  */
@@ -504,6 +600,13 @@ dump_dec (dump_flags_t dump_kind, const poly_int<N, C> &value)
 
   if (alt_dump_file && (dump_kind & alt_flags))
     print_dec (value, alt_dump_file, sgn);
+
+  if (optinfo_enabled_p ())
+    {
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      info.add_poly_int<N,C> (value);
+    }
 }
 
 template void dump_dec (dump_flags_t, const poly_uint16 &);
@@ -512,6 +615,79 @@ template void dump_dec (dump_flags_t, const poly_uint64 &);
 template void dump_dec (dump_flags_t, const poly_offset_int &);
 template void dump_dec (dump_flags_t, const poly_widest_int &);
 
+/* Output the name of NODE on appropriate dump streams.  */
+
+void
+dump_symtab_node (dump_flags_t dump_kind, symtab_node *node)
+{
+  if (dump_file && (dump_kind & pflags))
+    fprintf (dump_file, "%s", node->dump_name ());
+
+  if (alt_dump_file && (dump_kind & alt_flags))
+    fprintf (alt_dump_file, "%s", node->dump_name ());
+
+  if (optinfo_enabled_p ())
+    {
+      /* Attempt to consolidate via optinfo_guard.  */
+      pending_optinfo info = optinfo_guard::ensure_pending ();
+      info.handle_dump_file_kind (dump_kind);
+      info.add_symtab_node (node);
+    }
+}
+
+/* The current nesting depth of dump scopes, for showing nesting
+   via indentation).  */
+
+static unsigned int dump_scope_depth;
+
+/* Get the current dump scope-nesting depth.
+   For use by remarks and -fopt-info (for showing nesting via indentation).  */
+
+unsigned int
+get_dump_scope_depth ()
+{
+  return dump_scope_depth;
+}
+
+/* Push a nested dump scope.  */
+
+void
+dump_begin_scope (const char *name, const optinfo_location &loc,
+		  const optinfo_impl_location &impl_location)
+{
+  /* Specialcase, to avoid going through dump_printf_loc,
+     so that we can create a optinfo of kind OPTINFO_KIND_SCOPE.  */
+
+  if (dump_file)
+    {
+      dump_loc (MSG_NOTE, dump_file, loc.get_location_t ());
+      fprintf (dump_file, "=== %s ===\n", name);
+    }
+
+  if (alt_dump_file)
+    {
+      dump_loc (MSG_NOTE, alt_dump_file, loc.get_location_t ());
+      fprintf (alt_dump_file, "=== %s ===\n", name);
+    }
+
+  if (optinfo_enabled_p ())
+    {
+      pending_optinfo info (impl_location, OPTINFO_KIND_SCOPE, loc);
+      info.add_printf ("=== %s ===", name);
+    }
+
+  dump_scope_depth++;
+}
+
+/* Pop a nested optinfo scope.  */
+
+void
+dump_end_scope ()
+{
+  dump_scope_depth--;
+  optimization_records_maybe_pop_optinfo_scope ();
+}
+
 /* Start a dump for PHASE. Store user-supplied dump flags in
    *FLAG_PTR.  Return the number of streams opened.  Set globals
    DUMP_FILE, and ALT_DUMP_FILE to point to the opened streams, and
@@ -541,7 +717,7 @@ dump_start (int phase, dump_flags_t *flag_ptr)
         }
       free (name);
       dfi->pstream = stream;
-      dump_file = dfi->pstream;
+      set_dump_file (dfi->pstream);
       /* Initialize current dump flags. */
       pflags = dfi->pflags;
     }
@@ -551,7 +727,7 @@ dump_start (int phase, dump_flags_t *flag_ptr)
     {
       dfi->alt_stream = stream;
       count++;
-      alt_dump_file = dfi->alt_stream;
+      set_alt_dump_file (dfi->alt_stream);
       /* Initialize current -fopt-info flags. */
       alt_flags = dfi->alt_flags;
     }
@@ -582,8 +758,8 @@ dump_finish (int phase)
 
   dfi->alt_stream = NULL;
   dfi->pstream = NULL;
-  dump_file = NULL;
-  alt_dump_file = NULL;
+  set_dump_file (NULL);
+  set_alt_dump_file (NULL);
   dump_flags = TDF_NONE;
   alt_flags = TDF_NONE;
   pflags = TDF_NONE;
@@ -1021,6 +1197,7 @@ dump_basic_block (dump_flags_t dump_kind, basic_block bb, int indent)
     dump_bb (dump_file, bb, indent, TDF_DETAILS);
   if (alt_dump_file && (dump_kind & alt_flags))
     dump_bb (alt_dump_file, bb, indent, TDF_DETAILS);
+  // TODO: should this also write to optinfo?
 }
 
 /* Dump FUNCTION_DECL FN as tree dump PHASE.  */
diff --git a/gcc/dumpfile.h b/gcc/dumpfile.h
index 4941349..6eacc1b 100644
--- a/gcc/dumpfile.h
+++ b/gcc/dumpfile.h
@@ -223,21 +223,58 @@ struct dump_file_info
   bool graph_dump_initialized;
 };
 
+/* A class for describing a source-code location for an optinfo,
+   with various constructors for convenience.
+   In particular, this lets us associate optinfo instances
+   with hotness information (e.g. from PGO), allowing them to
+   be prioritized by code hotness.
+   Currently this is GTY-marked, so that vect_location can be a
+   GC root.  */
+
+class GTY(()) optinfo_location
+{
+ public:
+  optinfo_location () : m_stmt (NULL) {}
+  optinfo_location (gimple *stmt) : m_stmt (stmt) {}
+
+  location_t get_location_t () const;
+
+  gimple *m_stmt;
+  // Or maybe a basic_block and a location_t, so that we can support RTL.
+  // Or maybe just a profile_count and a location_t.
+};
+
+class optinfo_impl_location;
+
 /* In dumpfile.c */
 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 void dump_printf (dump_flags_t, const char *, ...) ATTRIBUTE_PRINTF_2;
+/* TODO: eliminate this overload:
+   Remaining uses of source_location/location_t are in:
+   - loop-unroll.c (which is RTL-based),
+   - coverage.c, profile.c (which use input_location for error-handling),
+   - value-prof.c: stmt or current_function_decl
+ */
 extern void dump_printf_loc (dump_flags_t, source_location,
                              const char *, ...) ATTRIBUTE_PRINTF_3;
+extern void dump_printf_loc (dump_flags_t, const optinfo_location &,
+                             const char *, ...) ATTRIBUTE_PRINTF_3;
 extern void dump_function (int phase, tree fn);
 extern void dump_basic_block (dump_flags_t, basic_block, int);
-extern void dump_generic_expr_loc (dump_flags_t, source_location, dump_flags_t, tree);
 extern void dump_generic_expr (dump_flags_t, dump_flags_t, tree);
-extern void dump_gimple_stmt_loc (dump_flags_t, source_location, dump_flags_t,
+extern void dump_gimple_stmt_loc (dump_flags_t, const optinfo_location &, dump_flags_t,
 				  gimple *, int);
 extern void dump_gimple_stmt (dump_flags_t, dump_flags_t, gimple *, int);
+extern void dump_symtab_node (dump_flags_t, symtab_node *);
+
+extern unsigned int get_dump_scope_depth ();
+extern void dump_begin_scope (const char *name, const optinfo_location &loc,
+			      const optinfo_impl_location &impl_location);
+extern void dump_end_scope ();
+
 extern void print_combine_total_stats (void);
 extern bool enable_rtl_dump_file (void);
 
@@ -254,17 +291,21 @@ extern void dump_bb (FILE *, basic_block, int, dump_flags_t);
 
 /* Global variables used to communicate with passes.  */
 extern FILE *dump_file;
-extern FILE *alt_dump_file;
 extern dump_flags_t dump_flags;
 extern const char *dump_file_name;
+extern bool dumps_are_enabled;
+
+extern void set_dump_file (FILE *new_dump_file);
 
 /* Return true if any of the dumps is enabled, false otherwise. */
 static inline bool
 dump_enabled_p (void)
 {
-  return (dump_file || alt_dump_file);
+  return GCC_UNLIKELY (dumps_are_enabled);
 }
 
+extern const kv_pair<optgroup_flags_t> optgroup_options[];
+
 namespace gcc {
 
 class dump_manager
diff --git a/gcc/fortran/gfc-diagnostic.def b/gcc/fortran/gfc-diagnostic.def
index 565fa83..a10b6aa 100644
--- a/gcc/fortran/gfc-diagnostic.def
+++ b/gcc/fortran/gfc-diagnostic.def
@@ -37,6 +37,7 @@ DEFINE_DIAGNOSTIC_KIND (DK_SORRY, "sorry, unimplemented", "error")
 DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "Warning", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note", "note")
+DEFINE_DIAGNOSTIC_KIND (DK_REMARK, "remark ", "remark")
 DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug", "note")
 /* These two would be re-classified as DK_WARNING or DK_ERROR, so the
 prefix does not matter.  */
diff --git a/gcc/gengtype.c b/gcc/gengtype.c
index 0db5528..166baa2 100644
--- a/gcc/gengtype.c
+++ b/gcc/gengtype.c
@@ -1724,7 +1724,8 @@ open_base_files (void)
       "tree-dfa.h", "tree-ssa.h", "reload.h", "cpp-id-data.h", "tree-chrec.h",
       "except.h", "output.h",  "cfgloop.h", "target.h", "lto-streamer.h",
       "target-globals.h", "ipa-ref.h", "cgraph.h", "symbol-summary.h",
-      "ipa-prop.h", "ipa-fnsummary.h", "dwarf2out.h", "omp-offload.h", NULL
+      "ipa-prop.h", "ipa-fnsummary.h", "dwarf2out.h", "omp-offload.h",
+      "dumpfile.h", NULL
     };
     const char *const *ifp;
     outf_p gtype_desc_c;
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index c1d8442..c023274 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -347,8 +347,7 @@ fold_gimple_assign (gimple_stmt_iterator *si)
 		  {
 		    if (dump_enabled_p ())
 		      {
-			location_t loc = gimple_location_safe (stmt);
-			dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+			dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, stmt,
 					 "resolving virtual function address "
 					 "reference to function %s\n",
 					 targets.length () == 1
@@ -4060,8 +4059,7 @@ gimple_fold_call (gimple_stmt_iterator *gsi, bool inplace)
 	      tree lhs = gimple_call_lhs (stmt);
 	      if (dump_enabled_p ())
 		{
-		  location_t loc = gimple_location_safe (stmt);
-		  dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+		  dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, stmt,
 				   "folding virtual function call to %s\n",
 		 		   targets.length () == 1
 		  		   ? targets[0]->name ()
diff --git a/gcc/gimple-loop-interchange.cc b/gcc/gimple-loop-interchange.cc
index eb35263..ef8df28 100644
--- a/gcc/gimple-loop-interchange.cc
+++ b/gcc/gimple-loop-interchange.cc
@@ -523,7 +523,7 @@ loop_cand::analyze_iloop_reduction_var (tree var)
 
   /* Handle and verify a series of stmts feeding the reduction op.  */
   if (single_use != next_def
-      && !check_reduction_path (UNKNOWN_LOCATION, m_loop, phi, next,
+      && !check_reduction_path (optinfo_location (), m_loop, phi, next,
 				gimple_assign_rhs_code (single_use)))
     return false;
 
@@ -1578,7 +1578,10 @@ bool
 tree_loop_interchange::interchange (vec<data_reference_p> datarefs,
 				    vec<ddr_p> ddrs)
 {
-  location_t loc = find_loop_location (m_loop_nest[0]);
+  optinfo_location loc = find_loop_location (m_loop_nest[0]);
+
+  OPTINFO_SCOPE ("tree_loop_interchange::interchange", loc);
+
   bool changed_p = false;
   /* In each iteration we try to interchange I-th loop with (I+1)-th loop.
      The overall effect is to push inner loop to outermost level in whole
diff --git a/gcc/gimple-pretty-print.c b/gcc/gimple-pretty-print.c
index 405d9e3..c1f28cd 100644
--- a/gcc/gimple-pretty-print.c
+++ b/gcc/gimple-pretty-print.c
@@ -2796,7 +2796,7 @@ dump_implicit_edges (pretty_printer *buffer, basic_block bb, int indent,
 /* Dumps basic block BB to buffer BUFFER with details described by FLAGS and
    indented by INDENT spaces.  */
 
-static void
+void
 gimple_dump_bb_buff (pretty_printer *buffer, basic_block bb, int indent,
 		     dump_flags_t flags)
 {
diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
index 6ae6a3b..e7a5607 100644
--- a/gcc/gimple-pretty-print.h
+++ b/gcc/gimple-pretty-print.h
@@ -34,6 +34,8 @@ extern void print_gimple_expr (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
 extern void pp_gimple_stmt_1 (pretty_printer *, gimple *, int, dump_flags_t);
 extern void gimple_dump_bb (FILE *, basic_block, int, dump_flags_t);
 extern void gimple_dump_bb_for_graph (pretty_printer *, basic_block);
+extern void gimple_dump_bb_buff (pretty_printer *buffer, basic_block bb, int indent,
+				 dump_flags_t flags);
 extern void dump_ssaname_info_to_file (FILE *, tree, int);
 extern void percent_G_format (text_info *);
 
diff --git a/gcc/graphite-isl-ast-to-gimple.c b/gcc/graphite-isl-ast-to-gimple.c
index b607b12..58ebb2d 100644
--- a/gcc/graphite-isl-ast-to-gimple.c
+++ b/gcc/graphite-isl-ast-to-gimple.c
@@ -1409,7 +1409,7 @@ scop_to_isl_ast (scop_p scop)
   isl_ctx_set_max_operations (scop->isl_context, old_max_operations);
   if (isl_ctx_last_error (scop->isl_context) != isl_error_none)
     {
-      location_t loc = find_loop_location
+      optinfo_location loc = find_loop_location
 	(scop->scop_info->region.entry->dest->loop_father);
       if (isl_ctx_last_error (scop->isl_context) == isl_error_quota)
 	dump_printf_loc (MSG_MISSED_OPTIMIZATION, loc,
@@ -1518,7 +1518,7 @@ graphite_regenerate_ast_isl (scop_p scop)
 
   if (t.codegen_error_p ())
     {
-      location_t loc = find_loop_location
+      optinfo_location loc = find_loop_location
 	(scop->scop_info->region.entry->dest->loop_father);
       dump_printf_loc (MSG_MISSED_OPTIMIZATION, loc,
 		       "loop nest not optimized, code generation error\n");
diff --git a/gcc/graphite-optimize-isl.c b/gcc/graphite-optimize-isl.c
index 456a797..0eb59e0 100644
--- a/gcc/graphite-optimize-isl.c
+++ b/gcc/graphite-optimize-isl.c
@@ -160,7 +160,7 @@ optimize_isl (scop_p scop)
   if (!scop->transformed_schedule
       || isl_ctx_last_error (scop->isl_context) != isl_error_none)
     {
-      location_t loc = find_loop_location
+      optinfo_location loc = find_loop_location
 	(scop->scop_info->region.entry->dest->loop_father);
       if (isl_ctx_last_error (scop->isl_context) == isl_error_quota)
 	dump_printf_loc (MSG_MISSED_OPTIMIZATION, loc,
@@ -182,7 +182,7 @@ optimize_isl (scop_p scop)
 
   if (same_schedule)
     {
-      location_t loc = find_loop_location
+      optinfo_location loc = find_loop_location
 	(scop->scop_info->region.entry->dest->loop_father);
       dump_printf_loc (MSG_NOTE, loc,
 		       "loop nest not optimized, optimized schedule is "
diff --git a/gcc/graphite-scop-detection.c b/gcc/graphite-scop-detection.c
index 0dafc39..3c53ce0 100644
--- a/gcc/graphite-scop-detection.c
+++ b/gcc/graphite-scop-detection.c
@@ -61,7 +61,7 @@ public:
   set_dump_file (FILE *f)
   {
     gcc_assert (f);
-    dump_file = f;
+    ::set_dump_file (f);
   }
 
   friend debug_printer &
diff --git a/gcc/graphite.c b/gcc/graphite.c
index bcf4828..1358888 100644
--- a/gcc/graphite.c
+++ b/gcc/graphite.c
@@ -412,7 +412,7 @@ graphite_transform_loops (void)
 	changed = true;
 	if (graphite_regenerate_ast_isl (scop))
 	  {
-	    location_t loc = find_loop_location
+	    optinfo_location loc = find_loop_location
 	      (scops[i]->scop_info->region.entry->dest->loop_father);
 	    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
 			     "loop nest optimized\n");
diff --git a/gcc/ipa-devirt.c b/gcc/ipa-devirt.c
index 308b6e6..e99d8cc 100644
--- a/gcc/ipa-devirt.c
+++ b/gcc/ipa-devirt.c
@@ -3755,8 +3755,7 @@ ipa_devirt (void)
 	      {
 		if (dump_enabled_p ())
                   {
-                    location_t locus = gimple_location_safe (e->call_stmt);
-                    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, locus,
+                    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, e->call_stmt,
 				     "speculatively devirtualizing call "
 				     "in %s to %s\n",
 				     n->dump_name (),
diff --git a/gcc/ipa-prop.c b/gcc/ipa-prop.c
index daada4d..bc02fd0 100644
--- a/gcc/ipa-prop.c
+++ b/gcc/ipa-prop.c
@@ -2843,8 +2843,7 @@ ipa_make_edge_direct_to_target (struct cgraph_edge *ie, tree target,
 	    {
 	      if (dump_enabled_p ())
 		{
-		  location_t loc = gimple_location_safe (ie->call_stmt);
-		  dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+		  dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, ie->call_stmt,
 				   "discovered direct call non-invariant %s\n",
 				   ie->caller->dump_name ());
 		}
@@ -2854,8 +2853,7 @@ ipa_make_edge_direct_to_target (struct cgraph_edge *ie, tree target,
 
           if (dump_enabled_p ())
 	    {
-	      location_t loc = gimple_location_safe (ie->call_stmt);
-	      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+	      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, ie->call_stmt,
 			       "discovered direct call to non-function in %s, "
 			       "making it __builtin_unreachable\n",
 			       ie->caller->dump_name ());
@@ -2943,9 +2941,7 @@ ipa_make_edge_direct_to_target (struct cgraph_edge *ie, tree target,
      }
   if (dump_enabled_p ())
     {
-      location_t loc = gimple_location_safe (ie->call_stmt);
-
-      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, ie->call_stmt,
 		       "converting indirect call in %s to direct call to %s\n",
 		       ie->caller->name (), callee->name ());
     }
diff --git a/gcc/ipa.c b/gcc/ipa.c
index 82fc334..3b6b5e5 100644
--- a/gcc/ipa.c
+++ b/gcc/ipa.c
@@ -225,13 +225,8 @@ walk_polymorphic_call_targets (hash_set<void *> *reachable_call_targets,
 		       (builtin_decl_implicit (BUILT_IN_UNREACHABLE));
 
 	  if (dump_enabled_p ())
-            {
-	      location_t locus;
-	      if (edge->call_stmt)
-		locus = gimple_location (edge->call_stmt);
-	      else
-		locus = UNKNOWN_LOCATION;
-	      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, locus,
+	    {
+	      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, edge->call_stmt,
 			       "devirtualizing call in %s to %s\n",
 			       edge->caller->dump_name (),
 			       target->dump_name ());
diff --git a/gcc/omp-grid.c b/gcc/omp-grid.c
index ffa301e..5f0dac8 100644
--- a/gcc/omp-grid.c
+++ b/gcc/omp-grid.c
@@ -91,7 +91,7 @@ struct grid_prop
   bool tiling;
   /* Location of the target construct for optimization information
      messages.  */
-  location_t target_loc;
+  optinfo_location target_loc;
   /* The collapse clause of the involved loops.  Collapse value of all of them
      must be the same for gridification to take place.  */
   size_t collapse;
@@ -177,10 +177,10 @@ grid_find_single_omp_among_assignments_1 (gimple_seq seq, grid_prop *grid,
 				   GRID_MISSED_MSG_PREFIX "%s construct "
 				   "contains multiple OpenMP constructs\n",
 				   name);
-		  dump_printf_loc (MSG_NOTE, gimple_location (*ret),
+		  dump_printf_loc (MSG_NOTE, *ret,
 				   "The first OpenMP construct within "
 				   "a parallel\n");
-		  dump_printf_loc (MSG_NOTE, gimple_location (stmt),
+		  dump_printf_loc (MSG_NOTE, stmt,
 				   "The second OpenMP construct within "
 				   "a parallel\n");
 		}
@@ -195,7 +195,7 @@ grid_find_single_omp_among_assignments_1 (gimple_seq seq, grid_prop *grid,
 	      dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			       GRID_MISSED_MSG_PREFIX "%s construct contains "
 			       "a complex statement\n", name);
-	      dump_printf_loc (MSG_NOTE, gimple_location (stmt),
+	      dump_printf_loc (MSG_NOTE, stmt,
 			       "This statement cannot be analyzed for "
 			       "gridification\n");
 	    }
@@ -286,7 +286,7 @@ grid_find_ungridifiable_statement (gimple_stmt_iterator *gsi,
    loop that is evaluated for possible gridification.  */
 
 static bool
-grid_parallel_clauses_gridifiable (gomp_parallel *par, location_t tloc)
+grid_parallel_clauses_gridifiable (gomp_parallel *par, optinfo_location tloc)
 {
   tree clauses = gimple_omp_parallel_clauses (par);
   while (clauses)
@@ -300,7 +300,7 @@ grid_parallel_clauses_gridifiable (gomp_parallel *par, location_t tloc)
 			       GRID_MISSED_MSG_PREFIX "because there is "
 			       "a num_threads clause of the parallel "
 			       "construct\n");
-	      dump_printf_loc (MSG_NOTE, gimple_location (par),
+	      dump_printf_loc (MSG_NOTE, par,
 			       "Parallel construct has a num_threads clause\n");
 	    }
 	  return false;
@@ -311,7 +311,7 @@ grid_parallel_clauses_gridifiable (gomp_parallel *par, location_t tloc)
 	      dump_printf_loc (MSG_MISSED_OPTIMIZATION, tloc,
 			       GRID_MISSED_MSG_PREFIX "a reduction clause "
 			       "is present\n ");
-	      dump_printf_loc (MSG_NOTE, gimple_location (par),
+	      dump_printf_loc (MSG_NOTE, par,
 			       "Parallel construct has a reduction clause\n");
 	    }
 	  return false;
@@ -341,7 +341,7 @@ grid_inner_loop_gridifiable_p (gomp_for *gfor, grid_prop *grid)
 			   GRID_MISSED_MSG_PREFIX "the inner loop "
 			   "loop bounds computation contains a complex "
 			   "statement\n");
-	  dump_printf_loc (MSG_NOTE, gimple_location (gfor),
+	  dump_printf_loc (MSG_NOTE, gfor,
 			   "Loop construct cannot be analyzed for "
 			   "gridification\n");
 	}
@@ -361,7 +361,7 @@ grid_inner_loop_gridifiable_p (gomp_for *gfor, grid_prop *grid)
 		  dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 				   GRID_MISSED_MSG_PREFIX "the inner loop "
 				   "has a non-automatic schedule clause\n");
-		  dump_printf_loc (MSG_NOTE, gimple_location (gfor),
+		  dump_printf_loc (MSG_NOTE, gfor,
 				   "Loop construct has a non automatic "
 				   "schedule clause\n");
 		}
@@ -375,7 +375,7 @@ grid_inner_loop_gridifiable_p (gomp_for *gfor, grid_prop *grid)
 	      dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			       GRID_MISSED_MSG_PREFIX "a reduction "
 			       "clause is present\n ");
-	      dump_printf_loc (MSG_NOTE, gimple_location (gfor),
+	      dump_printf_loc (MSG_NOTE, gfor,
 			       "Loop construct has a reduction schedule "
 			       "clause\n");
 	    }
@@ -404,7 +404,7 @@ grid_inner_loop_gridifiable_p (gomp_for *gfor, grid_prop *grid)
 			     GRID_MISSED_MSG_PREFIX "the inner loop contains "
 			     "statement %s which cannot be transformed\n",
 			     gimple_code_name[(int) gimple_code (bad)]);
-	  dump_printf_loc (MSG_NOTE, gimple_location (bad),
+	  dump_printf_loc (MSG_NOTE, bad,
 			   "This statement cannot be analyzed for "
 			   "gridification\n");
 	}
@@ -422,7 +422,7 @@ grid_inner_loop_gridifiable_p (gomp_for *gfor, grid_prop *grid)
 static bool
 grid_dist_follows_simple_pattern (gomp_for *dist, grid_prop *grid)
 {
-  location_t tloc = grid->target_loc;
+  optinfo_location tloc = grid->target_loc;
   gimple *stmt = grid_find_single_omp_among_assignments (gimple_omp_body (dist),
 							 grid, "distribute");
   gomp_parallel *par;
@@ -468,7 +468,7 @@ grid_gfor_follows_tiling_pattern (gomp_for *gfor, grid_prop *grid)
 	  dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			   GRID_MISSED_MSG_PREFIX "an inner loop is not "
 			   "a simple for loop\n");
-	  dump_printf_loc (MSG_NOTE, gimple_location (gfor),
+	  dump_printf_loc (MSG_NOTE, gfor,
 			   "This statement is not a simple for loop\n");
 	}
       return false;
@@ -484,7 +484,7 @@ grid_gfor_follows_tiling_pattern (gomp_for *gfor, grid_prop *grid)
 	  dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			   GRID_MISSED_MSG_PREFIX "an inner loop does not "
 			   "have use the same collapse clause\n");
-	  dump_printf_loc (MSG_NOTE, gimple_location (gfor),
+	  dump_printf_loc (MSG_NOTE, gfor,
 			   "Loop construct uses a different collapse clause\n");
 	}
       return false;
@@ -524,7 +524,7 @@ grid_gfor_follows_tiling_pattern (gomp_for *gfor, grid_prop *grid)
 	      dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			       GRID_MISSED_MSG_PREFIX "the distribute and "
 			       "an internal loop do not agree on tile size\n");
-	      dump_printf_loc (MSG_NOTE, gimple_location (gfor),
+	      dump_printf_loc (MSG_NOTE, gfor,
 			       "Loop construct does not seem to loop over "
 			       "a tile size\n");
 	    }
@@ -636,7 +636,7 @@ grid_dist_follows_tiling_pattern (gimple_seq seq, grid_prop *grid,
 		  dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 				   GRID_MISSED_MSG_PREFIX "the distribute "
 				   "construct contains a try..catch region\n");
-		  dump_printf_loc (MSG_NOTE, gimple_location (try_stmt),
+		  dump_printf_loc (MSG_NOTE, try_stmt,
 				   "This statement cannot be analyzed for "
 				   "tiled gridification\n");
 		}
@@ -661,7 +661,7 @@ grid_dist_follows_tiling_pattern (gimple_seq seq, grid_prop *grid,
 	      dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			       GRID_MISSED_MSG_PREFIX "the distribute "
 			       "construct contains a call\n");
-	      dump_printf_loc (MSG_NOTE, gimple_location (stmt),
+	      dump_printf_loc (MSG_NOTE, stmt,
 			       "This statement cannot be analyzed for "
 			       "tiled gridification\n");
 	    }
@@ -677,7 +677,7 @@ grid_dist_follows_tiling_pattern (gimple_seq seq, grid_prop *grid,
 				   GRID_MISSED_MSG_PREFIX "a parallel "
 				   "construct contains another parallel "
 				   "construct\n");
-		  dump_printf_loc (MSG_NOTE, gimple_location (stmt),
+		  dump_printf_loc (MSG_NOTE, stmt,
 				   "This parallel construct is nested in "
 				   "another one\n");
 		}
@@ -698,7 +698,7 @@ grid_dist_follows_tiling_pattern (gimple_seq seq, grid_prop *grid,
 				   GRID_MISSED_MSG_PREFIX "a loop "
 				   "construct is not nested within a parallel "
 				   "construct\n");
-		  dump_printf_loc (MSG_NOTE, gimple_location (stmt),
+		  dump_printf_loc (MSG_NOTE, stmt,
 				   "This loop construct is not nested in "
 				   "a parallel construct\n");
 		}
@@ -714,7 +714,7 @@ grid_dist_follows_tiling_pattern (gimple_seq seq, grid_prop *grid,
 	      dump_printf_loc (MSG_MISSED_OPTIMIZATION, grid->target_loc,
 			       GRID_MISSED_MSG_PREFIX "the distribute "
 			       "construct contains a complex statement\n");
-	      dump_printf_loc (MSG_NOTE, gimple_location (stmt),
+	      dump_printf_loc (MSG_NOTE, stmt,
 			       "This statement cannot be analyzed for "
 			       "tiled gridification\n");
 	    }
@@ -734,7 +734,7 @@ grid_target_follows_gridifiable_pattern (gomp_target *target, grid_prop *grid)
   if (gimple_omp_target_kind (target) != GF_OMP_TARGET_KIND_REGION)
     return false;
 
-  location_t tloc = gimple_location (target);
+  optinfo_location tloc = target;
   grid->target_loc = tloc;
   gimple *stmt
     = grid_find_single_omp_among_assignments (gimple_omp_body (target),
@@ -1257,14 +1257,13 @@ grid_attempt_target_gridification (gomp_target *target,
 				   gbind *tgt_bind)
 {
   /* removed group_size */
-  grid_prop grid;
-  memset (&grid, 0, sizeof (grid));
+  grid_prop grid = {};
   if (!target || !grid_target_follows_gridifiable_pattern (target, &grid))
     return;
 
   location_t loc = gimple_location (target);
   if (dump_enabled_p ())
-    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, target,
 		     "Target construct will be turned into a gridified HSA "
 		     "kernel\n");
 
diff --git a/gcc/opt-functions.awk b/gcc/opt-functions.awk
index 2c371e5..48ecac5 100644
--- a/gcc/opt-functions.awk
+++ b/gcc/opt-functions.awk
@@ -105,6 +105,7 @@ function switch_flags (flags)
 	  test_flag("Undocumented", flags,  " | CL_UNDOCUMENTED") \
 	  test_flag("NoDWARFRecord", flags,  " | CL_NO_DWARF_RECORD") \
 	  test_flag("Warning", flags,  " | CL_WARNING") \
+	  test_flag("Remark", flags,  " | CL_REMARK") \
 	  test_flag("(Optimization|PerFunction)", flags,  " | CL_OPTIMIZATION")
 	sub( "^0 \\| ", "", result )
 	return result
diff --git a/gcc/optinfo-emit-diagnostics.cc b/gcc/optinfo-emit-diagnostics.cc
new file mode 100644
index 0000000..8b86205
--- /dev/null
+++ b/gcc/optinfo-emit-diagnostics.cc
@@ -0,0 +1,141 @@
+/* Emit optimization information as "remark" diagnostics.
+   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 "tree-pass.h"
+#include "gimple.h"
+#include "pretty-print.h"
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "options.h"
+#include "cgraph.h"
+
+#include "optinfo.h"
+#include "optinfo-emit-diagnostics.h"
+#include "optinfo-internal.h"
+
+/* If diagnostic "remarks" are enabled, then emit OPTINFO as a remark.  */
+
+void
+emit_optinfo_as_diagnostic_remark (const optinfo *optinfo)
+{
+  if (!flag_remarks)
+    return;
+
+  pretty_printer pp;
+  pp_needs_newline (&pp) = true;
+  pp_translate_identifiers (&pp) = false;
+
+  bool show_color = pp_show_color (global_dc->printer);
+
+  /* Start with scope-based indentation.  */
+  for (unsigned i = get_dump_scope_depth (); i > 0; i--)
+    pp_space (&pp);
+
+  /* Print the items into PP.  */
+  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;
+	    pp_string (&pp, as_text->get_text ());
+	  }
+	  break;
+	case OPTINFO_ITEM_KIND_TREE:
+	  {
+	    const optinfo_item_tree *as_tree
+	      = (const optinfo_item_tree *)item;
+	    pp_begin_quote (&pp, show_color);
+	    dump_generic_node (&pp, as_tree->get_node (), 0, TDF_DETAILS,
+			       false);
+	    pp_end_quote (&pp, show_color);
+	  }
+	  break;
+	case OPTINFO_ITEM_KIND_GIMPLE:
+	  {
+	    const optinfo_item_gimple *as_gimple
+	      = (const optinfo_item_gimple *)item;
+	    gimple *stmt = as_gimple->get_stmt ();
+	    pp_begin_quote (&pp, show_color);
+	    pp_gimple_stmt_1 (&pp, stmt, 0, TDF_SLIM);
+	    pp_end_quote (&pp, show_color);
+	  }
+	  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 ();
+	    pp_begin_quote (&pp, show_color);
+	    pp_string (&pp, node->dump_name ());
+	    pp_end_quote (&pp, show_color);
+	  }
+	  break;
+	}
+    }
+
+  /* Add metadata: which pass?  */
+  if (current_pass)
+    {
+      pp_string (&pp, " [");
+      pp_string (&pp, colorize_start (show_color,
+				      diagnostic_get_color_for_kind (DK_REMARK)));
+      pp_string (&pp, "pass=");
+      pp_string (&pp, current_pass->name);
+      pp_string (&pp, colorize_stop (show_color));
+      pp_string (&pp, "]");
+    }
+
+  /* Add metadata: hotness.  */
+  gimple *stmt = optinfo->get_location ().m_stmt;
+  if (stmt)
+    if (stmt->bb)
+      if (stmt->bb->count.initialized_p ())
+	{
+	  profile_count count = stmt->bb->count;
+	  pp_string (&pp, " [");
+	  pp_string (&pp, colorize_start (show_color,
+					  diagnostic_get_color_for_kind (DK_NOTE)));
+	  pp_string (&pp, "count(");
+	  pp_string (&pp, profile_quality_as_string (count.quality ()));
+	  pp_string (&pp, ")=");
+	  pp_scalar (&pp, "%li", count.to_gcov_type ());
+	  pp_string (&pp, colorize_stop (show_color));
+	  pp_string (&pp, "]");
+	}
+
+  const char *msg = pp_formatted_text (&pp);
+  location_t loc = optinfo->get_location_t ();
+
+  remark (loc, 0, "%s", msg);
+}
diff --git a/gcc/optinfo-emit-diagnostics.h b/gcc/optinfo-emit-diagnostics.h
new file mode 100644
index 0000000..820cefd
--- /dev/null
+++ b/gcc/optinfo-emit-diagnostics.h
@@ -0,0 +1,26 @@
+/* Emit optimization information as "remark" diagnostics.
+   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_DIAGNOSTICS_H
+#define GCC_OPTINFO_EMIT_DIAGNOSTICS_H
+
+extern void emit_optinfo_as_diagnostic_remark (const optinfo *);
+
+#endif /* #ifndef GCC_OPTINFO_EMIT_DIAGNOSTICS_H */
diff --git a/gcc/optinfo-emit-json.cc b/gcc/optinfo-emit-json.cc
new file mode 100644
index 0000000..3cf4d68
--- /dev/null
+++ b/gcc/optinfo-emit-json.cc
@@ -0,0 +1,617 @@
+/* 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"
+
+/* Create a JSON object representing LOC.  */
+
+static json::object *
+impl_location_to_json (optinfo_impl_location 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));
+  obj->set ("function", new json::string (loc.m_function));
+  return obj;
+}
+
+/* Create a JSON object representing LOC.  */
+
+static json::object *
+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.  */
+
+static json::object *
+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 *
+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.  */
+
+static json::object *
+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.  */
+
+static json::value *
+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 ();
+	  obj->set ("fndecl",
+		    new json::string (lang_hooks.decl_printable_name (fndecl, 2)));
+	  if (*locus != UNKNOWN_LOCATION)
+	    obj->set ("site", location_to_json (*locus));
+	  array->append (obj);
+	}
+    }
+
+  return array;
+}
+
+/* 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.  */
+
+static json::array *root_tuple;
+
+/* The currently open scopes, for expressing nested optimization records.  */
+
+static vec<json::array *> scopes;
+
+/* Add a json description of PASS and its siblings to ARR, recursing into
+   child passes (adding their descriptions within a "children" array).  */
+
+static void
+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);
+}
+
+/* Perform startup activity for -fsave-optimization-record.  */
+
+void
+optimization_records_start ()
+{
+  /* Bail immediately if recording not enabled.  */
+  if (!flag_save_optimization_record)
+    return;
+
+  root_tuple = new json::array ();
+
+  /* Populate with metadata; compare with toplev.c: print_version.  */
+  json::object *metadata = new json::object ();
+  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 ();
+  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 ();
+  root_tuple->append (records);
+
+  scopes.safe_push (records);
+}
+
+/* Perform cleanup activity for -fsave-optimization-record.
+
+   Currently, the file is written out here in one go.  */
+
+void
+optimization_records_finish ()
+{
+  /* Bail immediately if recording not enabled.  */
+  if (!root_tuple)
+    return;
+
+  char *filename = concat (dump_base_name, ".opt-record.json", NULL);
+  FILE *outfile = fopen (filename, "w");
+  if (outfile)
+    {
+      root_tuple->dump (outfile);
+      fclose (outfile);
+    }
+  else
+    error_at (UNKNOWN_LOCATION, "unable to write optimization records to %qs",
+	      filename); // FIXME: more info?
+  free (filename);
+
+  delete root_tuple;
+  root_tuple = NULL;
+}
+
+/* Did the user request optimization records to be written out?  */
+
+bool
+optimization_records_enabled_p ()
+{
+  return root_tuple != 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 immediately if recording not enabled.  */
+  if (!root_tuple)
+    return;
+
+  gimple *stmt = optinfo->get_location ().m_stmt;//get_best_stmt ();
+
+  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 (current_pass)
+    obj->set ("pass", get_id_value_for_pass (current_pass));
+
+  location_t loc = UNKNOWN_LOCATION;
+  if (stmt)
+    {
+      loc = gimple_location (stmt);
+      if (stmt->bb)
+	if (stmt->bb->count.initialized_p ())
+	  obj->set ("count", profile_count_to_json (stmt->bb->count));
+    }
+
+  /* Record any location, handling the case where of an UNKNOWN_LOCATION
+     within an inlined block.  */
+  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));
+
+  /* Add to innermost scope.  */
+  gcc_assert (scopes.length () > 0);
+  scopes[scopes.length () - 1]->append (obj);
+
+  /* Potentially push the scope.  */
+  if (optinfo->get_kind () == OPTINFO_KIND_SCOPE)
+    {
+      json::array *children = new json::array ();
+      obj->set ("children", children);
+      scopes.safe_push (children);
+    }
+}
+
+/* Handling for the end of an optinfo scope for the
+   optimization records sink.  */
+
+void
+optimization_records_maybe_pop_optinfo_scope ()
+{
+  /* Bail immediately if recording not enabled.  */
+  if (!root_tuple)
+    return;
+
+  scopes.pop ();
+}
+
+/* Generate a JSON object describing BB, adding it to CFG_OBJ.
+   Compare with dump_bb, dump_bb_info etc.  */
+
+static void
+dump_bb_to_json (basic_block bb, json::array *cfg_obj)
+{
+  json::object *bb_obj = new json::object ();
+  cfg_obj->append (bb_obj);
+
+  /* Basic metadata.  Compare with dump_bb_info.  */
+
+  bb_obj->set ("index", new json::number (bb->index));
+  if (bb->count.initialized_p ())
+    bb_obj->set ("count", profile_count_to_json (bb->count));
+
+  /* Flags.  */
+  static const char * const bb_bitnames[] =
+    {
+#define DEF_BASIC_BLOCK_FLAG(NAME,IDX) #NAME ,
+#include "cfg-flags.def"
+      NULL
+#undef DEF_BASIC_BLOCK_FLAG
+    };
+  const unsigned n_bitnames = sizeof (bb_bitnames) / sizeof (char *);
+  json::array *bb_flags = new json::array ();
+  bb_obj->set ("flags", bb_flags);
+  for (unsigned i = 0; i < n_bitnames; i++)
+    if (bb->flags & (1 << i))
+      bb_flags->append (new json::string (bb_bitnames[i]));
+
+  bb_obj->set ("discriminator", new json::number (bb->discriminator));
+
+  if (bb->index >= NUM_FIXED_BLOCKS)
+    {
+      /* For now, just capture all of the statements as one string.  */
+      pretty_printer pp;
+      pp.buffer->stream = NULL;
+      pp.buffer->flush_p = false;
+      pp_needs_newline (&pp) = true;
+      gimple_dump_bb_buff (&pp, bb, 0, TDF_NONE);
+      bb_obj->set ("stmts", new json::string (pp_formatted_text (&pp)));
+    }
+
+  json::array *succs = new json::array ();
+  bb_obj->set ("succs", succs);
+  edge_iterator ei;
+  edge e;
+  FOR_EACH_EDGE (e, ei, bb->succs)
+    {
+      /* compare with dump_edge_info.  */
+      json::object *edge_obj = new json::object ();
+      succs->append (edge_obj);
+      edge_obj->set ("dest", new json::number (e->dest->index));
+
+      /* Edge flags.  */
+      static const char * const bitnames[] =
+	{
+#define DEF_EDGE_FLAG(NAME,IDX) #NAME ,
+#include "cfg-flags.def"
+	  NULL
+#undef DEF_EDGE_FLAG
+	};
+
+      json::array *edge_flags = new json::array ();
+      edge_obj->set ("flags", edge_flags);
+
+      gcc_assert (e->flags <= EDGE_ALL_FLAGS);
+      int flags = e->flags;
+      for (int i = 0; flags; i++)
+	if (flags & (1 << i))
+	  {
+	    flags &= ~(1 << i);
+	    edge_flags->append (new json::string (bitnames[i]));
+	  }
+    }
+}
+
+/* Populate FN_OBJ based on FUN.
+   Compare with dump_function_to_file.  */
+
+static void
+dump_function_to_json (function *fun, json::object *fn_obj)
+{
+  tree arg, var, old_current_fndecl = current_function_decl;
+  struct function *dsf;
+  bool ignore_topmost_bind = false, any_var = false;
+  basic_block bb;
+  tree chain;
+  tree fndecl = fun->decl;
+
+  current_function_decl = fndecl;
+
+  if (fun && fun->decl == fndecl
+      && fun->cfg
+      && basic_block_info_for_fn (fun))
+    {
+      json::array *cfg_obj = new json::array ();
+      fn_obj->set ("cfg", cfg_obj);
+
+      FOR_ALL_BB_FN (bb, fun)
+	dump_bb_to_json (bb, cfg_obj);
+    }
+
+  current_function_decl = old_current_fndecl;
+}
+
+
+/* Add a record describing the state of FN after PASS to the queue of
+   records to be written.  */
+
+void
+optimization_records_maybe_record_function (function *fn,
+					    opt_pass *pass)
+{
+  gcc_assert (fn);
+  gcc_assert (pass);
+
+  tree fndecl = fn->decl;
+
+  json::object *obj = new json::object ();
+  obj->set ("kind", new json::string ("state"));
+  obj->set ("pass", get_id_value_for_pass (pass));
+  const char *fnname = get_fnname_from_decl (fndecl);
+  obj->set ("function", new json::string (fnname));
+
+  if (fn->curr_properties & PROP_trees)
+    {
+      dump_function_to_json (fn, obj);
+    }
+  else
+    {
+      // TODO?  RTL equivalent is:
+      //print_rtl_with_bb (dump_file, get_insns (), dump_flags);
+    }
+
+  gcc_assert (scopes.length () == 1);
+  scopes[scopes.length () - 1]->append (obj);
+}
+
+
diff --git a/gcc/optinfo-emit-json.h b/gcc/optinfo-emit-json.h
new file mode 100644
index 0000000..5468946
--- /dev/null
+++ b/gcc/optinfo-emit-json.h
@@ -0,0 +1,39 @@
+/* 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_optinfo_scope ();
+extern void optimization_records_maybe_record_function (function *fun,
+							opt_pass *pass);
+
+
+#endif /* #ifndef GCC_OPTINFO_EMIT_JSON_H */
diff --git a/gcc/optinfo-internal.h b/gcc/optinfo-internal.h
new file mode 100644
index 0000000..3f267ee
--- /dev/null
+++ b/gcc/optinfo-internal.h
@@ -0,0 +1,145 @@
+/* Implementation details of optinfo.
+   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_INTERNAL_H
+#define GCC_OPTINFO_INTERNAL_H
+
+/* Multiple dispatch: there are various kinds of items within an optinfo,
+   and various destinations to send optinfo to.
+
+   Handling this for now by exposing all of the item subclasses,
+   and having the destinations handle them with "switch" statements.  */
+
+/* An enum for discriminating between optinfo_item subclasses.  */
+
+enum optinfo_item_kind
+{
+  OPTINFO_ITEM_KIND_TEXT,
+  OPTINFO_ITEM_KIND_TREE,
+  OPTINFO_ITEM_KIND_GIMPLE,
+  OPTINFO_ITEM_KIND_SYMTAB_NODE
+};
+
+/* Base class for items within an optinfo.  */
+
+class optinfo_item
+{
+ public:
+  virtual ~optinfo_item () {}
+
+  virtual enum optinfo_item_kind get_kind () const = 0;
+};
+
+/* optinfo_item subclasses.  */
+
+/* Item within an optinfo: text, either owned by the item
+   (for optinfo_printf), or borrowed (for string literals).  */
+
+class optinfo_item_text : public optinfo_item
+{
+ public:
+  optinfo_item_text (char *text, bool owned)
+  : m_text (text), m_owned (owned)
+  {}
+  ~optinfo_item_text ()
+  {
+    if (m_owned)
+      free (m_text);
+  }
+
+  enum optinfo_item_kind get_kind () const FINAL OVERRIDE
+  {
+    return OPTINFO_ITEM_KIND_TEXT;
+  }
+
+  const char *get_text () const { return m_text; }
+
+  void trim_trailing_whitespace ();
+
+ private:
+  char *m_text;
+  bool m_owned;
+};
+
+/* Item within an optinfo: a tree, with dump flags.
+   Note that this is not GTY-marked, as it's assumed that instances
+   are short-lived.  */
+
+class optinfo_item_tree : public optinfo_item
+{
+ public:
+  optinfo_item_tree (tree node, dump_flags_t dump_flags)
+    : m_node (node), m_dump_flags (dump_flags)
+  {}
+  enum optinfo_item_kind get_kind () const FINAL OVERRIDE
+  {
+    return OPTINFO_ITEM_KIND_TREE;
+  }
+
+  tree get_node () const { return m_node; }
+  dump_flags_t get_flags () const { return m_dump_flags; }
+
+ private:
+  tree m_node;
+  dump_flags_t m_dump_flags;
+};
+
+/* Item within an optinfo: a gimple statement.
+   Note that this is not GTY-marked, as it's assumed that instances
+   are short-lived.  */
+
+class optinfo_item_gimple : public optinfo_item
+{
+ public:
+  optinfo_item_gimple (gimple *stmt, dump_flags_t dump_flags)
+    : m_stmt (stmt), m_dump_flags (dump_flags) {}
+  enum optinfo_item_kind get_kind () const FINAL OVERRIDE
+  {
+    return OPTINFO_ITEM_KIND_GIMPLE;
+  }
+
+  gimple *get_stmt () const { return m_stmt; }
+  dump_flags_t get_flags () const { return m_dump_flags; }
+
+ private:
+  gimple *m_stmt;
+  dump_flags_t m_dump_flags;
+};
+
+/* Item within an optinfo: a symbol table node.
+   Note that this is not GTY-marked, as it's assumed that instances
+   are short-lived.  */
+
+class optinfo_item_symtab_node : public optinfo_item
+{
+ public:
+  optinfo_item_symtab_node (symtab_node *node) : m_node (node) {}
+  enum optinfo_item_kind get_kind () const FINAL OVERRIDE
+  {
+    return OPTINFO_ITEM_KIND_SYMTAB_NODE;
+  }
+
+  symtab_node *get_node () const { return m_node; }
+
+ private:
+  symtab_node *m_node;
+};
+
+#endif /* #ifndef GCC_OPTINFO_INTERNAL_H */
diff --git a/gcc/optinfo.cc b/gcc/optinfo.cc
new file mode 100644
index 0000000..9f02bfc
--- /dev/null
+++ b/gcc/optinfo.cc
@@ -0,0 +1,298 @@
+/* Optimization information.
+   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 "optinfo.h"
+#include "optinfo-emit-diagnostics.h"
+#include "optinfo-emit-json.h"
+#include "optinfo-internal.h"
+#include "selftest.h"
+
+/* Remove any trailing whitespace characters from this text item.
+   Primarily for use in stripping trailing newline characters when
+   emitting remarks (since the diagnostic subsystem doesn't expect
+   trailing newlines in messages).  */
+
+void
+optinfo_item_text::trim_trailing_whitespace ()
+{
+  size_t len = strlen (m_text);
+  if (len == 0)
+    return;
+
+  size_t new_len = len;
+  while (new_len > 0 && ISSPACE (m_text[new_len - 1]))
+    new_len--;
+
+  if (new_len == len)
+    return;
+
+  if (m_owned)
+    m_text[new_len] = '\0';
+  else
+    {
+      m_text = xstrndup (m_text, new_len);
+      m_owned = true;
+    }
+}
+
+/* Get the source location from this optinfo_location.  */
+
+location_t
+optinfo_location::get_location_t () const
+{
+  if (m_stmt)
+    return gimple_location (m_stmt);
+  else
+    return UNKNOWN_LOCATION;
+}
+
+
+/* Get a string from KIND.  */
+
+const char *
+optinfo_kind_to_string (enum optinfo_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case OPTINFO_KIND_SUCCESS:
+      return "success";
+    case OPTINFO_KIND_FAILURE:
+      return "failure";
+    case OPTINFO_KIND_NOTE:
+      return "note";
+    case OPTINFO_KIND_SCOPE:
+      return "scope";
+    }
+}
+
+/* optinfo's dtor.  */
+
+optinfo::~optinfo ()
+{
+  /* Eliminate any trailing whitespace.  */
+  while (m_items.length () > 0)
+    {
+      optinfo_item *last_item = m_items[m_items.length () - 1];
+      if (last_item->get_kind () != OPTINFO_ITEM_KIND_TEXT)
+	break;
+
+      optinfo_item_text *last_text = (optinfo_item_text *)last_item;
+      last_text->trim_trailing_whitespace ();
+
+      if (strlen (last_text->get_text ()) > 0)
+	break;
+
+      m_items.pop ();
+      delete last_item;
+    }
+
+  /* Flush the pending information.  */
+  emit ();
+
+  /* Cleanup.  */
+  unsigned i;
+  optinfo_item *item;
+  FOR_EACH_VEC_ELT (m_items, i, item)
+    delete item;
+}
+
+/* Get a location_t from an optinfo.  */
+
+location_t
+optinfo::get_location_t () const
+{
+  return m_loc.get_location_t ();
+}
+
+/* Emit the optinfo to all of the active destinations.  */
+
+void
+optinfo::emit () const
+{
+  /* -fsave-optimization-record.  */
+  optimization_records_maybe_record_optinfo (this);
+
+  /* -fremarks.  */
+  emit_optinfo_as_diagnostic_remark (this);
+}
+
+/* pending_optinfo's ctor.  */
+
+pending_optinfo::pending_optinfo (const optinfo_impl_location &impl_location,
+				  enum optinfo_kind kind,
+				  optinfo_location loc)
+  : m_optinfo (new optinfo (impl_location, kind, loc))
+{
+}
+
+/* Update the underlying optinfo's kind based on DUMP_KIND.  */
+
+void
+pending_optinfo::handle_dump_file_kind (dump_flags_t dump_kind)
+{
+  if (dump_kind & MSG_OPTIMIZED_LOCATIONS)
+    m_optinfo->m_kind = OPTINFO_KIND_SUCCESS;
+  else if (dump_kind & MSG_MISSED_OPTIMIZATION)
+    m_optinfo->m_kind = OPTINFO_KIND_FAILURE;
+  else if (dump_kind & MSG_NOTE)
+    m_optinfo->m_kind = OPTINFO_KIND_NOTE;
+}
+
+/* Update the underlying optinfo's location based on LOC.  */
+
+void
+pending_optinfo::handle_loc (optinfo_location loc)
+{
+  m_optinfo->m_loc = loc;
+}
+
+/* Append a string literal to this pending_optinfo.  */
+
+void
+pending_optinfo::add_string (const char *str)
+{
+  optinfo_item *item
+    = new optinfo_item_text (const_cast <char *> (str), false);
+  m_optinfo->m_items.safe_push (item);
+}
+
+/* Append printf-formatted text to this pending_optinfo.  */
+
+void
+pending_optinfo::add_printf (const char *format, ...)
+{
+  va_list ap;
+  va_start (ap, format);
+  add_printf_va (format, ap);
+  va_end (ap);
+}
+
+/* Append printf-formatted text to this pending_optinfo.  */
+
+void
+pending_optinfo::add_printf_va (const char *format, va_list ap)
+{
+  char *formatted_text = xvasprintf (format, ap);
+  optinfo_item *item
+    = new optinfo_item_text (formatted_text, true);
+  m_optinfo->m_items.safe_push (item);
+}
+
+/* Append a gimple statement to this pending_optinfo.  */
+
+void
+pending_optinfo::add_stmt (gimple *stmt, dump_flags_t dump_flags)
+{
+  m_optinfo->m_items.safe_push (new optinfo_item_gimple (stmt, dump_flags));
+}
+
+/* Append a tree node to this pending_optinfo.  */
+
+void
+pending_optinfo::add_tree (tree node, dump_flags_t dump_flags)
+{
+  m_optinfo->m_items.safe_push (new optinfo_item_tree (node, dump_flags));
+}
+
+/* Append a symbol table node to this pending_optinfo.  */
+
+void
+pending_optinfo::add_symtab_node (symtab_node *node)
+{
+  m_optinfo->m_items.safe_push (new optinfo_item_symtab_node (node));
+}
+
+/* Append the decimal represenation of a wide_int_ref to this
+   pending_optinfo.  */
+
+void
+pending_optinfo::add_dec (const wide_int_ref &wi, signop sgn)
+{
+  char buf[WIDE_INT_PRINT_BUFFER_SIZE];
+  print_dec (wi, buf, sgn);
+  optinfo_item *item
+    = new optinfo_item_text (xstrdup (buf), true);
+  m_optinfo->m_items.safe_push (item);
+}
+
+/* Should optinfo instances be created?
+   All creation of optinfos should be guarded by this predicate.
+   Return true if any optinfo destinations are active.  */
+
+bool optinfo_enabled_p ()
+{
+  return optimization_records_enabled_p () || flag_remarks;
+}
+
+/* Return true if any of the active optinfo destinations make use
+   of inlining information.
+   (if true, then the information is preserved).  */
+
+bool optinfo_wants_inlining_info_p ()
+{
+  return optimization_records_enabled_p ();
+}
+
+/* class optinfo_guard. */
+
+pending_optinfo *optinfo_guard::pending;
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Verify that optinfo_item_text::trim_trailing_whitespace turns
+   INPUT into EXPECTED.  */
+
+static void
+test_trim_trailing_whitespace (const char *input, const char *expected)
+{
+  optinfo_item_text item (const_cast <char *> (input), false);
+  item.trim_trailing_whitespace ();
+  ASSERT_STREQ (item.get_text (), expected);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+optinfo_cc_tests ()
+{
+  /* Verify that optinfo_item_text::trim_trailing_whitespace works.  */
+  test_trim_trailing_whitespace ("", "");
+  test_trim_trailing_whitespace ("\n", "");
+  test_trim_trailing_whitespace ("foo", "foo");
+  test_trim_trailing_whitespace ("foo\n", "foo");
+  test_trim_trailing_whitespace ("foo\n\n", "foo");
+  test_trim_trailing_whitespace ("foo bar \n\n", "foo bar");
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/optinfo.h b/gcc/optinfo.h
new file mode 100644
index 0000000..23b17f8
--- /dev/null
+++ b/gcc/optinfo.h
@@ -0,0 +1,298 @@
+/* Optimization information.
+   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_H
+#define GCC_OPTINFO_H
+
+#include "unique-ptr.h"
+#include "dumpfile.h"
+
+/* An "optinfo" is a bundle of information describing part of an
+   optimization, which can be emitted to zero or more of several
+   destinations:
+
+   * as a "remark" through the diagnostics subsystem
+
+   * saved to a file via "-fsave-optimization-record"
+
+   They are generated in response to calls to calls to the "dump_*" API
+   in dumpfile.h
+
+   Building an optinfo instance is non-trivial, so all usage should be
+   guarded by
+
+     if (optinfo_enabled_p ())
+
+   which is off by default.
+
+   They're intended to be be short-lived; in particular, there's no
+   interaction with GTY: it's assumed that no GC happens during the
+   lifetime of an optinfo.  */
+
+/* Forward decls.  */
+class pending_optinfo;
+
+/* optinfo-internal.h.  */
+class optinfo_item;
+
+/* Should optinfo instances be created?
+   All creation of optinfos should be guarded by this predicate.
+   Return true if any optinfo destinations are active.  */
+
+extern bool optinfo_enabled_p ();
+
+/* Return true if any of the active optinfo destinations make use
+   of inlining information.
+   (if true, then the information is preserved).  */
+
+extern bool optinfo_wants_inlining_info_p ();
+
+/* A way to identify where in the compiler source that optimization information
+   is being emitted from.  */
+/* FIXME: taken from selftest::location; should this be refactored?  */
+
+struct optinfo_impl_location
+{
+  optinfo_impl_location (const char *file, int line, const char *function)
+    : m_file (file), m_line (line), m_function (function) {}
+
+  const char *m_file;
+  int m_line;
+  const char *m_function;
+};
+
+/* The current source location, expressed as an optinfo_impl_location.  */
+
+#define OPTINFO_IMPL_LOCATION \
+  (optinfo_impl_location (__FILE__, __LINE__, __FUNCTION__))
+
+/* The various kinds of optinfo.  */
+
+enum optinfo_kind
+{
+  OPTINFO_KIND_SUCCESS,
+  OPTINFO_KIND_FAILURE,
+  OPTINFO_KIND_NOTE,
+  OPTINFO_KIND_SCOPE
+};
+
+extern const char *optinfo_kind_to_string (enum optinfo_kind kind);
+
+/* A bundle of information describing part of an optimization.
+
+   Note that this is not GTY-marked, as it's assumed that instances
+   are short-lived.  */
+
+class optinfo
+{
+  friend class pending_optinfo;
+ public:
+  optinfo (const optinfo_impl_location &impl_location,
+	   enum optinfo_kind kind,
+	   optinfo_location loc)
+  : m_impl_location (impl_location), m_kind (kind), m_loc (loc), m_items ()
+  {}
+  ~optinfo ();
+
+  optinfo_impl_location get_impl_location () const { return m_impl_location; }
+  enum optinfo_kind get_kind () const { return m_kind; }
+  optinfo_location get_location () const { return m_loc; }
+  unsigned int num_items () const { return m_items.length (); }
+  const optinfo_item *get_item (unsigned int i) const { return m_items[i]; }
+
+  location_t get_location_t () const;
+
+ private:
+   void emit () const;
+
+ private:
+  optinfo_impl_location m_impl_location;
+  enum optinfo_kind m_kind;
+  optinfo_location m_loc;
+  auto_vec <optinfo_item *> m_items;
+};
+
+/* Support class for building and emitting optinfo instances.
+
+   The info is emitted to all active sinks when the pending_optinfo
+   goes out of scope.  */
+
+class pending_optinfo
+{
+ public:
+  pending_optinfo (const optinfo_impl_location &impl_location,
+		   enum optinfo_kind kind,
+		   optinfo_location loc);
+
+  void handle_dump_file_kind (dump_flags_t);
+  void handle_loc (optinfo_location loc);
+
+  /* Pre-canned ways of adding information to the optinfo.  */
+  void add_string (const char *str);
+  void add_printf (const char *format, ...) ATTRIBUTE_PRINTF_2;
+  void add_printf_va (const char *format, va_list ap) ATTRIBUTE_PRINTF (2, 0);
+  void add_stmt (gimple *stmt, dump_flags_t dump_flags);
+  void add_tree (tree node, dump_flags_t dump_flags);
+  void add_symtab_node (symtab_node *node);
+  void add_dec (const wide_int_ref &wi, signop sgn);
+
+  template<unsigned int N, typename C>
+  void add_poly_int (const poly_int<N, C> &value)
+  {
+    /* Compare with dump_dec (MSG_NOTE, ).  */
+
+    STATIC_ASSERT (poly_coeff_traits<C>::signedness >= 0);
+    signop sgn = poly_coeff_traits<C>::signedness ? SIGNED : UNSIGNED;
+
+    if (value.is_constant ())
+      add_dec (value.coeffs[0], sgn);
+    else
+      {
+	add_string ("[");
+	for (unsigned int i = 0; i < N; ++i)
+	  {
+	    add_dec (value.coeffs[i], sgn);
+	    add_string (i == N - 1 ? "]" : ",");
+	  }
+      }
+  }
+
+ private:
+  // FIXME: are we allowed this yet?  or do we need a gnu::shared_ptr?
+  std::shared_ptr <optinfo> m_optinfo;
+};
+
+/* Implementation detail of the OPTINFO_SCOPE macro below.
+
+   A RAII-style class intended to make it easy to emit optinfo remarks
+   about entering and exiting the body of a given function.  */
+
+class optinfo_scope
+{
+ public:
+  optinfo_scope (const char *name, optinfo_location loc,
+		 const optinfo_impl_location &impl_location)
+  : m_name (name), m_loc (loc)
+  {
+    if (dump_enabled_p ())
+      dump_begin_scope (name, loc, impl_location);
+  }
+  ~optinfo_scope ()
+  {
+    if (dump_enabled_p ())
+      dump_end_scope ();
+  }
+
+ private:
+  const char *m_name;
+  optinfo_location m_loc;
+};
+
+/* A macro for emitting an optinfo note about entering a scope,
+   pushing and popping the scope, so that all optinfos "within"
+   the scope are nested within it.  */
+
+#define OPTINFO_SCOPE(NAME, LOC) \
+  optinfo_scope scope (NAME, LOC, (OPTINFO_IMPL_LOCATION))
+
+/* A RAII class for consolidating "dump_*" calls, so that a single
+   optimization record is created holding all of the "dump_*" calls
+   made within the lifetime of the guard.
+
+   Can also be used in conditionals:
+
+     if (optinfo_guard guard = optinfo_guard (location, OPTINFO_IMPL_LOCATION))
+       {
+          SUITE
+       }
+
+   so that all dump_* calls within "SUITE" are consolidated into
+   one optimization record.  */
+
+class optinfo_guard
+{
+ public:
+  optinfo_guard (const optinfo_location &loc,
+		 const optinfo_impl_location &impl_location)
+  : m_enabled (optinfo_enabled_p ())
+  {
+    /* Early bailout.  */
+    if (GCC_LIKELY (!m_enabled))
+      return;
+
+    /* We assume this stuff gets optimized away.  */
+    gcc_assert (pending == NULL);
+    pending = new pending_optinfo (impl_location,
+				   OPTINFO_KIND_NOTE,
+				   loc);
+  }
+  ~optinfo_guard ()
+  {
+    /* Early bailout.  */
+    if (GCC_LIKELY (!m_enabled))
+      return;
+
+    gcc_assert (pending != NULL);
+    delete (pending);
+    pending = NULL;
+  }
+
+  operator bool () { return dump_enabled_p (); }
+
+  /* If there's an open guard, return its pending_optinfo (thus
+     consolidating all calls within the open guard into the same optinfo)
+
+     Otherwise, return a new pending_optinfo, which will own a
+     temporary one-time optinfo.
+
+     The various dump_* calls lazily create optinfo via:
+     via:
+
+        if (optinfo_enabled_p ())
+	  {
+	     pending_optinfo info = optinfo_guard::ensure_pending ();
+	     [...add stuff to info...]
+          }
+
+     which effectively consolidates all dump calls into the same
+     optinfo within an optinfo_guard, but also handles the case
+     where there isn't an optinfo_guard (by falling back to putting
+     each one in a separate optinfo).  */
+
+  static pending_optinfo ensure_pending ()
+  {
+    if (pending)
+      {
+	return *pending;
+      }
+    else
+      {
+	return pending_optinfo (OPTINFO_IMPL_LOCATION, // FIXME
+				OPTINFO_KIND_NOTE,
+				optinfo_location ()); // FIXME
+      }
+  }
+
+ private:
+  static pending_optinfo *pending;
+  bool m_enabled;
+};
+
+#endif /* #ifndef GCC_OPTINFO_H */
diff --git a/gcc/opts.c b/gcc/opts.c
index 33efcc0..8029c08 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -1435,6 +1435,9 @@ print_specific_help (unsigned int include_flags,
 	case CL_WARNING:
 	  description = _("The following options control compiler warning messages");
 	  break;
+	case CL_REMARK:
+	  description = _("The following options control compiler remarks");
+	  break;
 	case CL_OPTIMIZATION:
 	  description = _("The following options control optimizations");
 	  break;
@@ -1875,6 +1878,7 @@ common_handle_option (struct gcc_options *opts,
 	      { "optimizers", CL_OPTIMIZATION },
 	      { "target", CL_TARGET },
 	      { "warnings", CL_WARNING },
+	      { "remarks", CL_REMARK },
 	      { "undocumented", CL_UNDOCUMENTED },
 	      { "params", CL_PARAMS },
 	      { "joined", CL_JOINED },
diff --git a/gcc/opts.h b/gcc/opts.h
index 3c4065ea..d9df788 100644
--- a/gcc/opts.h
+++ b/gcc/opts.h
@@ -137,20 +137,21 @@ extern const unsigned int cl_lang_count;
 #define CL_DRIVER		(1U << 19) /* Driver option.  */
 #define CL_TARGET		(1U << 20) /* Target-specific option.  */
 #define CL_COMMON		(1U << 21) /* Language-independent.  */
+#define CL_REMARK		(1U << 22) /* Enables an (optional) remark.  */
 
 #define CL_MIN_OPTION_CLASS	CL_PARAMS
-#define CL_MAX_OPTION_CLASS	CL_COMMON
+#define CL_MAX_OPTION_CLASS	CL_REMARK
 
 /* From here on the bits describe attributes of the options.
    Before this point the bits have described the class of the option.
    This distinction is important because --help will not list options
    which only have these higher bits set.  */
 
-#define CL_JOINED		(1U << 22) /* If takes joined argument.  */
-#define CL_SEPARATE		(1U << 23) /* If takes a separate argument.  */
-#define CL_UNDOCUMENTED		(1U << 24) /* Do not output with --help.  */
-#define CL_NO_DWARF_RECORD	(1U << 25) /* Do not add to producer string.  */
-#define CL_PCH_IGNORE		(1U << 26) /* Do compare state for pch.  */
+#define CL_JOINED		(1U << 23) /* If takes joined argument.  */
+#define CL_SEPARATE		(1U << 24) /* If takes a separate argument.  */
+#define CL_UNDOCUMENTED		(1U << 25) /* Do not output with --help.  */
+#define CL_NO_DWARF_RECORD	(1U << 26) /* Do not add to producer string.  */
+#define CL_PCH_IGNORE		(1U << 27) /* Do compare state for pch.  */
 
 /* Flags for an enumerated option argument.  */
 #define CL_ENUM_CANONICAL	(1 << 0) /* Canonical for this value.  */
diff --git a/gcc/passes.c b/gcc/passes.c
index 832f0b3..7c064aa 100644
--- a/gcc/passes.c
+++ b/gcc/passes.c
@@ -63,6 +63,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h" /* for fnotice */
 #include "stringpool.h"
 #include "attribs.h"
+#include "optinfo.h"
+#include "optinfo-emit-json.h"
 
 using namespace gcc;
 
@@ -1734,6 +1736,19 @@ execute_function_dump (function *fn, void *data)
     }
 }
 
+/* Helper function to perform function body dump.  */
+
+static void
+execute_optinfo_function_dump (function *fn, void *data)
+{
+  opt_pass *pass = (opt_pass *)data;
+
+  if (!optimization_records_enabled_p ())
+    return;
+
+  optimization_records_maybe_record_function (fn, pass);
+}
+
 /* This function is called when an internal compiler error is encountered.
    Ensure that function dump is made available before compiler is aborted.  */
 
@@ -2202,6 +2217,8 @@ execute_one_ipa_transform_pass (struct cgraph_node *node,
 
   if (dump_file)
     do_per_function (execute_function_dump, pass);
+  if (optinfo_enabled_p ())
+    do_per_function (execute_optinfo_function_dump, pass);
   pass_fini_dump_file (pass);
 
   current_pass = NULL;
@@ -2503,6 +2520,9 @@ execute_one_pass (opt_pass *pass)
   else if (dump_file)
     do_per_function (execute_function_dump, pass);
 
+  if (optimization_records_enabled_p ())
+    do_per_function (execute_optinfo_function_dump, pass);
+
   if (!current_function_decl)
     symtab->process_new_functions ();
 
diff --git a/gcc/profile-count.c b/gcc/profile-count.c
index 3d411cf..6a17f5e 100644
--- a/gcc/profile-count.c
+++ b/gcc/profile-count.c
@@ -33,6 +33,34 @@ along with GCC; see the file COPYING3.  If not see
 #include "wide-int.h"
 #include "sreal.h"
 
+/* Get a string describing QUALITY.  */
+
+const char *
+profile_quality_as_string (enum profile_quality quality)
+{
+  switch (quality)
+    {
+    default:
+      gcc_unreachable ();
+    case profile_uninitialized:
+      return "uninitialized";
+    case profile_guessed_local:
+      return "guessed_local";
+    case profile_guessed_global0:
+      return "guessed_global0";
+    case profile_guessed_global0adjusted:
+      return "guessed_global0adjusted";
+    case profile_guessed:
+      return "guessed";
+    case profile_afdo:
+      return "afdo";
+    case profile_adjusted:
+      return "adjusted";
+    case profile_precise:
+      return "precise";
+    }
+}
+
 /* Dump THIS to F.  */
 
 void
diff --git a/gcc/profile-count.h b/gcc/profile-count.h
index c83fa3b..f4d0c340 100644
--- a/gcc/profile-count.h
+++ b/gcc/profile-count.h
@@ -59,6 +59,8 @@ enum profile_quality {
   profile_precise
 };
 
+extern const char *profile_quality_as_string (enum profile_quality);
+
 /* The base value for branch probability notes and edge probabilities.  */
 #define REG_BR_PROB_BASE  10000
 
@@ -721,6 +723,9 @@ public:
       return m_quality == profile_precise;
     }
 
+  /* Get the quality of the count.  */
+  enum profile_quality quality () const { return m_quality; }
+
   /* When merging basic blocks, the two different profile counts are unified.
      Return true if this can be done without losing info about profile.
      The only case we care about here is when first BB contains something
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 36879cf..b74c94a 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -71,6 +71,7 @@ selftest::run_tests ()
   typed_splay_tree_c_tests ();
   unique_ptr_tests_cc_tests ();
   json_cc_tests ();
+  optinfo_cc_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 173700b..d20b636 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -199,6 +199,7 @@ extern void hash_map_tests_c_tests ();
 extern void hash_set_tests_c_tests ();
 extern void input_c_tests ();
 extern void json_cc_tests ();
+extern void optinfo_cc_tests ();
 extern void predict_c_tests ();
 extern void pretty_print_c_tests ();
 extern void read_rtl_function_c_tests ();
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 5a19fc9..1f0a079 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -96,6 +96,8 @@ set plugin_test_list [list \
 	  must-tail-call-2.c } \
     { expensive_selftests_plugin.c \
 	  expensive-selftests-1.c } \
+    { remarks_plugin.c \
+	  remarks-1.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
diff --git a/gcc/testsuite/gcc.dg/plugin/remarks-1.c b/gcc/testsuite/gcc.dg/plugin/remarks-1.c
new file mode 100644
index 0000000..9139b9d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/remarks-1.c
@@ -0,0 +1,30 @@
+/* { dg-do compile } */
+/* { dg-options "-fremarks" } */
+
+extern void test_string_literal (void);
+extern void test_tree (void);
+extern void test_gimple (int);
+extern void test_cgraph_node (void);
+extern void test_printf (void);
+extern void test_wide_int (void);
+extern void test_poly_int (void);
+extern void test_scopes (void);
+
+void test_remarks (void)
+{
+  test_string_literal (); /* { dg-remark "test of remark for test_string_literal" } */
+  test_tree (); /* { dg-remark "test of tree: '0'" } */
+  test_gimple (42); /* { dg-remark "test of gimple: 'test_gimple \\(42\\);'" } */
+  test_cgraph_node (); /* { dg-remark "test of callgraph node: 'test_cgraph_node/.*'" } */
+  test_printf (); /* { dg-remark "test of optinfo printf: 42" } */
+  test_wide_int (); /* { dg-remark "test of wide int: 0" } */
+  test_poly_int (); /* { dg-remark "test of poly int: 42" } */
+
+  test_scopes (); /* { dg-line test_scopes_line } */
+  /* { dg-remark "=== outer scope ===" "" { target *-*-* } test_scopes_line } */
+  /* { dg-remark " at outer scope" "" { target *-*-* } test_scopes_line } */
+  /* { dg-remark " === middle scope ===" "" { target *-*-* } test_scopes_line } */
+  /* { dg-remark "  at middle scope" "" { target *-*-* } test_scopes_line } */
+  /* { dg-remark "  === innermost scope ===" "" { target *-*-* } test_scopes_line } */
+  /* { dg-remark "   at innermost scope" "" { target *-*-* } test_scopes_line } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/remarks_plugin.c b/gcc/testsuite/gcc.dg/plugin/remarks_plugin.c
new file mode 100644
index 0000000..823339d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/remarks_plugin.c
@@ -0,0 +1,159 @@
+/* Test of remark-emission by optinfo.  */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "context.h"
+#include "optinfo.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "cgraph.h"
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_remarks =
+{
+  GIMPLE_PASS, /* type */
+  "test_remarks", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_test_remarks : public gimple_opt_pass
+{
+public:
+  pass_test_remarks(gcc::context *ctxt)
+    : gimple_opt_pass(pass_data_test_remarks, ctxt)
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) { return true; }
+  virtual unsigned int execute (function *);
+
+}; // class pass_test_remarks
+
+unsigned int
+pass_test_remarks::execute (function *fun)
+{
+  basic_block bb;
+
+  if (!dump_enabled_p ())
+    return 0;
+  
+  FOR_ALL_BB_FN (bb, fun)
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !gsi_end_p (gsi); gsi_next (&gsi))
+      {
+	gimple *stmt = gsi_stmt (gsi);
+	gcall *call = dyn_cast <gcall *> (stmt);
+	if (!call)
+	  continue;
+	tree callee_decl = gimple_call_fndecl (call);
+	if (!callee_decl)
+	  continue;
+	tree callee_name = DECL_NAME (callee_decl);
+	if (!callee_name)
+	  continue;
+	const char *callee = IDENTIFIER_POINTER (callee_name);
+
+	/* Various optinfo tests, done at callsites,
+	   controlled by the callee name.  */
+	if (strcmp (callee, "test_string_literal") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    dump_printf_loc (MSG_NOTE, stmt, "test of remark for ");
+	    dump_printf (MSG_NOTE, callee);
+	  }
+	else if (strcmp (callee, "test_tree") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    dump_printf_loc (MSG_NOTE, stmt, "test of tree: ");
+	    dump_generic_expr (MSG_NOTE, TDF_SLIM, integer_zero_node);
+	  }
+	else if (strcmp (callee, "test_gimple") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    dump_printf_loc (MSG_NOTE, stmt, "test of gimple: ");
+	    dump_gimple_stmt (MSG_NOTE, TDF_SLIM, stmt, 0);
+	  }
+	else if (strcmp (callee, "test_cgraph_node") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    dump_printf_loc (MSG_NOTE, stmt, "test of callgraph node: ");
+	    dump_symtab_node (MSG_NOTE, cgraph_node::get (callee_decl));
+	  }
+	else if (strcmp (callee, "test_printf") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    dump_printf_loc (MSG_NOTE, stmt, "test of optinfo printf: %d", 42);
+	  }
+	else if (strcmp (callee, "test_wide_int") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    HOST_WIDE_INT val = 0;
+	    dump_printf_loc (MSG_NOTE, stmt,
+			     "test of wide int: " HOST_WIDE_INT_PRINT_DEC,
+			     val);
+	  }
+	else if (strcmp (callee, "test_poly_int") == 0)
+	  {
+	    optinfo_guard guard (stmt, OPTINFO_IMPL_LOCATION);
+	    dump_printf_loc (MSG_NOTE, stmt, "test of poly int: ");
+	    dump_dec (MSG_NOTE, poly_int64 (42));
+	  }
+	else if (strcmp (callee, "test_scopes") == 0)
+	  {
+	    OPTINFO_SCOPE ("outer scope", stmt);
+	    {
+	      dump_printf_loc (MSG_NOTE, stmt, "at outer scope");
+	      OPTINFO_SCOPE ("middle scope", stmt);
+	      {
+		dump_printf_loc (MSG_NOTE, stmt, "at middle scope");
+		OPTINFO_SCOPE ("innermost scope", stmt);
+		dump_printf_loc (MSG_NOTE, stmt, "at innermost scope");
+	      }
+	    }
+	  }
+      }
+
+  return 0;
+}
+
+static gimple_opt_pass *
+make_pass_test_remarks (gcc::context *ctxt)
+{
+  return new pass_test_remarks (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  pass_info.pass = make_pass_test_remarks (g);
+  pass_info.reference_pass_name = "ssa";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_AFTER;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index a15c5d5..906ee3b 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -1154,6 +1154,15 @@ proc dg-locus { args } {
     verbose "process-message:\n${dg-messages}" 2
 }
 
+# Handle remarks.
+
+proc dg-remark { args } {
+    # Make this variable available here and to the saved proc.
+    upvar dg-messages dg-messages
+
+    process-message saved-dg-error "remark: " "$args"
+}
+
 # Check the existence of a gdb in the path, and return true if there
 # is one.
 #
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-loop-distribution.c b/gcc/tree-loop-distribution.c
index c6e0a60..553b8ea 100644
--- a/gcc/tree-loop-distribution.c
+++ b/gcc/tree-loop-distribution.c
@@ -3117,7 +3117,7 @@ pass_loop_distribution::execute (function *fun)
 	    break;
 
 	  const char *str = loop->inner ? " nest" : "";
-	  location_t loc = find_loop_location (loop);
+	  optinfo_location loc = find_loop_location (loop);
 	  if (!cd)
 	    {
 	      calculate_dominance_info (CDI_DOMINATORS);
@@ -3145,8 +3145,8 @@ pass_loop_distribution::execute (function *fun)
 	      break;
 	    }
 
-	  if (dump_file && (dump_flags & TDF_DETAILS))
-	    fprintf (dump_file, "Loop%s %d not distributed.\n", str, loop->num);
+	  dump_printf_loc (MSG_MISSED_OPTIMIZATION | TDF_DETAILS, loc,
+			   "Loop%s %d not distributed.\n", str, loop->num);
 	}
     }
 
diff --git a/gcc/tree-nested.c b/gcc/tree-nested.c
index f1557c9..2a12eef 100644
--- a/gcc/tree-nested.c
+++ b/gcc/tree-nested.c
@@ -3334,7 +3334,7 @@ lower_nested_functions (tree fndecl)
 
   gimplify_all_functions (cgn);
 
-  dump_file = dump_begin (TDI_nested, &dump_flags);
+  set_dump_file (dump_begin (TDI_nested, &dump_flags));
   if (dump_file)
     fprintf (dump_file, "\n;; Function %s\n\n",
 	     lang_hooks.decl_printable_name (fndecl, 2));
@@ -3361,7 +3361,7 @@ lower_nested_functions (tree fndecl)
   if (dump_file)
     {
       dump_end (TDI_nested, dump_file);
-      dump_file = NULL;
+      set_dump_file (NULL);
     }
 }
 
diff --git a/gcc/tree-parloops.c b/gcc/tree-parloops.c
index aa74427..a9d084b 100644
--- a/gcc/tree-parloops.c
+++ b/gcc/tree-parloops.c
@@ -3284,7 +3284,6 @@ parallelize_loops (bool oacc_kernels_p)
   struct tree_niter_desc niter_desc;
   struct obstack parloop_obstack;
   HOST_WIDE_INT estimated;
-  source_location loop_loc;
 
   /* Do not parallelize loops in the functions created by parallelization.  */
   if (!oacc_kernels_p
@@ -3409,7 +3408,7 @@ parallelize_loops (bool oacc_kernels_p)
       changed = true;
       skip_loop = loop->inner;
 
-      loop_loc = find_loop_location (loop);
+      optinfo_location loop_loc = find_loop_location (loop);
       if (loop->inner)
 	dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loop_loc,
 			 "parallelizing outer loop %d\n", loop->num);
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
diff --git a/gcc/tree-ssa-loop-ivcanon.c b/gcc/tree-ssa-loop-ivcanon.c
index 24bf60e..e85a6ad 100644
--- a/gcc/tree-ssa-loop-ivcanon.c
+++ b/gcc/tree-ssa-loop-ivcanon.c
@@ -691,7 +691,7 @@ try_unroll_loop_completely (struct loop *loop,
 			    edge exit, tree niter, bool may_be_zero,
 			    enum unroll_level ul,
 			    HOST_WIDE_INT maxiter,
-			    location_t locus, bool allow_peel)
+			    optinfo_location locus, bool allow_peel)
 {
   unsigned HOST_WIDE_INT n_unroll = 0;
   bool n_unroll_found = false;
@@ -1162,7 +1162,7 @@ canonicalize_loop_induction_variables (struct loop *loop,
   tree niter;
   HOST_WIDE_INT maxiter;
   bool modified = false;
-  location_t locus = UNKNOWN_LOCATION;
+  optinfo_location locus;
   struct tree_niter_desc niter_desc;
   bool may_be_zero = false;
 
@@ -1177,7 +1177,7 @@ canonicalize_loop_induction_variables (struct loop *loop,
 	= niter_desc.may_be_zero && !integer_zerop (niter_desc.may_be_zero);
     }
   if (TREE_CODE (niter) == INTEGER_CST)
-    locus = gimple_location (last_stmt (exit->src));
+    locus = last_stmt (exit->src);
   else
     {
       /* For non-constant niter fold may_be_zero into niter again.  */
@@ -1204,7 +1204,7 @@ canonicalize_loop_induction_variables (struct loop *loop,
 	niter = find_loop_niter_by_eval (loop, &exit);
 
       if (exit)
-        locus = gimple_location (last_stmt (exit->src));
+        locus = last_stmt (exit->src);
 
       if (TREE_CODE (niter) != INTEGER_CST)
 	exit = NULL;
diff --git a/gcc/tree-ssa-loop-ivopts.c b/gcc/tree-ssa-loop-ivopts.c
index b313571..d80541c 100644
--- a/gcc/tree-ssa-loop-ivopts.c
+++ b/gcc/tree-ssa-loop-ivopts.c
@@ -7525,7 +7525,7 @@ tree_ssa_iv_optimize_loop (struct ivopts_data *data, struct loop *loop)
 
   gcc_assert (!data->niters);
   data->current_loop = loop;
-  data->loop_loc = find_loop_location (loop);
+  data->loop_loc = find_loop_location (loop).get_location_t ();
   data->speed = optimize_loop_for_speed_p (loop);
 
   if (dump_file && (dump_flags & TDF_DETAILS))
diff --git a/gcc/tree-ssa-loop-niter.c b/gcc/tree-ssa-loop-niter.c
index 7a54c5f..3aee0f6 100644
--- a/gcc/tree-ssa-loop-niter.c
+++ b/gcc/tree-ssa-loop-niter.c
@@ -2447,7 +2447,7 @@ number_of_iterations_exit (struct loop *loop, edge exit,
     return true;
 
   if (warn)
-    dump_printf_loc (MSG_MISSED_OPTIMIZATION, gimple_location_safe (stmt),
+    dump_printf_loc (MSG_MISSED_OPTIMIZATION, stmt,
 		     "missed loop optimization: niters analysis ends up "
 		     "with assumptions.\n");
 
diff --git a/gcc/tree-ssa-sccvn.c b/gcc/tree-ssa-sccvn.c
index 3d025c2..e5eddf9 100644
--- a/gcc/tree-ssa-sccvn.c
+++ b/gcc/tree-ssa-sccvn.c
@@ -5866,8 +5866,7 @@ eliminate_dom_walker::before_dom_children (basic_block b)
 		    fn = builtin_decl_implicit (BUILT_IN_UNREACHABLE);
 		  if (dump_enabled_p ())
 		    {
-		      location_t loc = gimple_location (stmt);
-		      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+		      dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, stmt,
 				       "converting indirect call to "
 				       "function %s\n",
 				       lang_hooks.decl_printable_name (fn, 2));
diff --git a/gcc/tree-vect-loop.c b/gcc/tree-vect-loop.c
index 77bd909..e0378ee 100644
--- a/gcc/tree-vect-loop.c
+++ b/gcc/tree-vect-loop.c
@@ -2723,7 +2723,7 @@ needs_fold_left_reduction_p (tree type, tree_code code,
    reduction operation CODE has a handled computation expression.  */
 
 bool
-check_reduction_path (location_t loc, loop_p loop, gphi *phi, tree loop_arg,
+check_reduction_path (optinfo_location loc, loop_p loop, gphi *phi, tree loop_arg,
 		      enum tree_code code)
 {
   auto_vec<std::pair<ssa_op_iter, use_operand_p> > path;
diff --git a/gcc/tree-vectorizer.c b/gcc/tree-vectorizer.c
index 8ff90b3..4ebad5c 100644
--- a/gcc/tree-vectorizer.c
+++ b/gcc/tree-vectorizer.c
@@ -81,8 +81,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-pretty-print.h"
 
 
-/* Loop or bb location.  */
-source_location vect_location;
+/* Loop or bb location, with hotness information.  */
+optinfo_location vect_location;
 
 /* Vector mapping GIMPLE stmt to stmt_vec_info. */
 vec<stmt_vec_info> *stmt_vec_info_vec;
@@ -872,7 +872,7 @@ vectorize_loops (void)
 	  }
       }
 
-  vect_location = UNKNOWN_LOCATION;
+  vect_location = optinfo_location ();
 
   statistics_counter_event (cfun, "Vectorized loops", num_vectorized_loops);
   if (dump_enabled_p ()
@@ -1207,7 +1207,7 @@ increase_alignment (void)
 {
   varpool_node *vnode;
 
-  vect_location = UNKNOWN_LOCATION;
+  vect_location = optinfo_location ();
   type_align_map = new hash_map<tree, unsigned>;
 
   /* Increase the alignment of all global arrays for vectorization.  */
diff --git a/gcc/tree-vectorizer.h b/gcc/tree-vectorizer.h
index 2255d96..8fa86ef 100644
--- a/gcc/tree-vectorizer.h
+++ b/gcc/tree-vectorizer.h
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-data-ref.h"
 #include "tree-hash-traits.h"
 #include "target.h"
+#include "optinfo.h"
 
 /* Used for naming of new temporaries.  */
 enum vect_var_kind {
@@ -1422,18 +1423,22 @@ vect_get_scalar_dr_size (struct data_reference *dr)
   return tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (DR_REF (dr))));
 }
 
-/* Source location */
-extern source_location vect_location;
+/* Source location + hotness information. */
 
-/* If dumping is enabled, emit a MSG_NOTE at vect_location about
-   entering MSG within the vectorizer.  MSG should be a string literal. */
+extern GTY(()) optinfo_location vect_location;
+
+/* A macro for emitting an optinfo note about entering a scope at
+   vect_location, pushing and popping the scope, so that all optinfos
+   "within" the scope are nested within it.  */
 
 #define VECT_SCOPE(MSG) \
-  do {						\
-    if (dump_enabled_p ())			\
-      dump_printf_loc (MSG_NOTE, vect_location, \
-		       "=== " MSG " ===\n");	\
-  } while (0)
+  OPTINFO_SCOPE (MSG, vect_location)
+
+/* A conditional, using RAII to consolidate all dump_ calls within the
+   suite so that a single optimization record is created, at vect_location.  */
+
+#define IF_VECT_DUMP \
+  if (optinfo_guard guard = optinfo_guard (vect_location, OPTINFO_IMPL_LOCATION))
 
 /*-----------------------------------------------------------------*/
 /* Function prototypes.                                            */
@@ -1451,7 +1456,7 @@ extern void vect_loop_versioning (loop_vec_info, unsigned int, bool,
 extern struct loop *vect_do_peeling (loop_vec_info, tree, tree,
 				     tree *, tree *, tree *, int, bool, bool);
 extern void vect_prepare_for_masked_peels (loop_vec_info);
-extern source_location find_loop_location (struct loop *);
+extern optinfo_location find_loop_location (struct loop *);
 extern bool vect_can_advance_ivs_p (loop_vec_info);
 
 /* In tree-vect-stmts.c.  */
@@ -1567,7 +1572,7 @@ extern tree vect_create_addr_base_for_vector_ref (gimple *, gimple_seq *,
 extern gimple *vect_force_simple_reduction (loop_vec_info, gimple *,
 					    bool *, bool);
 /* Used in gimple-loop-interchange.c.  */
-extern bool check_reduction_path (location_t, loop_p, gphi *, tree,
+extern bool check_reduction_path (optinfo_location, loop_p, gphi *, tree,
 				  enum tree_code);
 /* Drive for loop analysis stage.  */
 extern loop_vec_info vect_analyze_loop (struct loop *, loop_vec_info);
diff --git a/gcc/value-prof.c b/gcc/value-prof.c
index d50a179..9ca0003 100644
--- a/gcc/value-prof.c
+++ b/gcc/value-prof.c
@@ -1271,13 +1271,11 @@ find_func_by_profile_id (int profile_id)
 bool
 check_ic_target (gcall *call_stmt, struct cgraph_node *target)
 {
-   location_t locus;
    if (gimple_check_call_matching_types (call_stmt, target->decl, true))
      return true;
 
-   locus =  gimple_location (call_stmt);
    if (dump_enabled_p ())
-     dump_printf_loc (MSG_MISSED_OPTIMIZATION, locus,
+     dump_printf_loc (MSG_MISSED_OPTIMIZATION, call_stmt,
                       "Skipping target %s with mismatching types for icall\n",
                       target->name ());
    return false;
