diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1be4ea02992c..798d4302fa78 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1849,7 +1849,8 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
 # Objects in libcommon-target.a, used by drivers and by the core
 # compiler and containing target-dependent code.
 OBJS-libcommon-target = $(common_out_object_file) prefix.o \
-	opts.o opts-common.o options.o vec.o hooks.o common/common-targhooks.o \
+	opts.o opts-common.o opts-diagnostic.o options.o \
+	vec.o hooks.o common/common-targhooks.o \
 	hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o \
 	options-urls.o
 
diff --git a/gcc/ada/gcc-interface/misc.cc b/gcc/ada/gcc-interface/misc.cc
index 8c921db7dcdd..56742e75dde2 100644
--- a/gcc/ada/gcc-interface/misc.cc
+++ b/gcc/ada/gcc-interface/misc.cc
@@ -308,14 +308,14 @@ internal_error_function (diagnostic_context *context, const char *msgid,
   emergency_dump_function ();
 
   /* Reset the pretty-printer.  */
-  pp_clear_output_area (context->m_printer);
+  pp_clear_output_area (context->get_reference_printer ());
 
   /* Format the message into the pretty-printer.  */
   text_info tinfo (msgid, ap, errno);
-  pp_format_verbatim (context->m_printer, &tinfo);
+  pp_format_verbatim (context->get_reference_printer (), &tinfo);
 
   /* Extract a (writable) pointer to the formatted text.  */
-  buffer = xstrdup (pp_formatted_text (context->m_printer));
+  buffer = xstrdup (pp_formatted_text (context->get_reference_printer ()));
 
   /* Go up to the first newline.  */
   for (p = buffer; *p; p++)
diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
index b4eea6b6581a..e66ab38c9e77 100644
--- a/gcc/analyzer/analyzer-language.cc
+++ b/gcc/analyzer/analyzer-language.cc
@@ -120,7 +120,7 @@ on_finish_translation_unit (const translation_unit &tu)
   log_user the_logger (NULL);
   if (logfile)
     the_logger.set_logger (new logger (logfile, 0, 0,
-				       *global_dc->m_printer));
+				       *global_dc->get_reference_printer ()));
   stash_named_constants (the_logger.get_logger (), tu);
 
   run_callbacks (the_logger.get_logger (), tu);
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 3b23990ef80f..ca81285175b6 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -6327,7 +6327,7 @@ run_checkers ()
     get_or_create_any_logfile ();
     if (dump_fout)
       the_logger.set_logger (new logger (dump_fout, 0, 0,
-					 *global_dc->m_printer));
+					 *global_dc->get_reference_printer ()));
     LOG_SCOPE (the_logger.get_logger ());
 
     impl_run_checkers (the_logger.get_logger ());
diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
index 673933307a82..313df9af1d31 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -280,7 +280,7 @@ function_point::print_source_line (pretty_printer *pp) const
   diagnostic_source_print_policy source_policy (tmp_dc);
   gcc_assert (pp);
   source_policy.print (*pp, richloc, DK_ERROR, nullptr);
-  pp_string (pp, pp_formatted_text (tmp_dc.m_printer));
+  pp_string (pp, pp_formatted_text (tmp_dc.get_reference_printer ()));
 }
 
 /* class program_point.  */
diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
index 035080e90aa1..119859b66d92 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -5579,14 +5579,14 @@ test_type_mismatch_range_labels ()
   richloc.add_range (param, SHOW_RANGE_WITHOUT_CARET, &param_label);
 
   test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR, dc.m_printer);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR, dc.get_reference_printer ());
   if (c_dialect_cxx ())
     /* "char*", without a space.  */
     ASSERT_STREQ ("   printf (\"msg: %i\\n\", msg);\n"
 		  "                 ~^     ~~~\n"
 		  "                  |     |\n"
 		  "                  char* int\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
   else
     /* "char *", with a space.  */
     ASSERT_STREQ ("   printf (\"msg: %i\\n\", msg);\n"
@@ -5594,7 +5594,7 @@ test_type_mismatch_range_labels ()
 		  "                  |     |\n"
 		  "                  |     int\n"
 		  "                  char *\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc
index dc91373f5b98..dcabab0581cb 100644
--- a/gcc/c/c-objc-common.cc
+++ b/gcc/c/c-objc-common.cc
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "attribs.h"
 #include "dwarf2.h"
+#include "make-unique.h"
 
 static bool c_tree_printer (pretty_printer *, text_info *, const char *,
 			    int, bool, bool, bool, bool *, pp_token_list &);
@@ -412,16 +413,9 @@ has_c_linkage (const_tree decl ATTRIBUTE_UNUSED)
 void
 c_initialize_diagnostics (diagnostic_context *context)
 {
-  pretty_printer *base = context->m_printer;
-  c_pretty_printer *pp = XNEW (c_pretty_printer);
-  context->m_printer = new (pp) c_pretty_printer ();
-
-  /* It is safe to free this object because it was previously XNEW()'d.  */
-  base->~pretty_printer ();
-  XDELETE (base);
-
+  context->set_pretty_printer (::make_unique<c_pretty_printer> ());
   c_common_diagnostics_set_defaults (context);
-  diagnostic_format_decoder (context) = &c_tree_printer;
+  context->set_format_decoder (&c_tree_printer);
 }
 
 int
diff --git a/gcc/common.opt b/gcc/common.opt
index 12b25ff486de..dceaa6e8dbf7 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1440,6 +1440,14 @@ fdiagnostics-format=
 Common Joined RejectNegative Enum(diagnostics_output_format)
 -fdiagnostics-format=[text|sarif-stderr|sarif-file|json|json-stderr|json-file]	Select output format.
 
+fdiagnostics-add-output=
+Common Joined RejectNegative
+Add output format.
+
+fdiagnostics-set-output=
+Common Joined RejectNegative
+Set output format.
+
 fdiagnostics-escape-format=
 Common Joined RejectNegative Enum(diagnostics_escape_format)
 -fdiagnostics-escape-format=[unicode|bytes]	Select how to escape non-printable-ASCII bytes in the source for diagnostics that suggest it.
@@ -1487,10 +1495,6 @@ Enum(diagnostics_output_format) String(sarif-stderr) Value(DIAGNOSTICS_OUTPUT_FO
 EnumValue
 Enum(diagnostics_output_format) String(sarif-file) Value(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE)
 
-EnumValue
-Enum(diagnostics_output_format) String(sarif-file-2.2-prerelease) Value(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE)
-; undocumented
-
 fdiagnostics-parseable-fixits
 Common Var(flag_diagnostics_parseable_fixits)
 Print fix-it hints in machine-readable form.
diff --git a/gcc/common.opt.urls b/gcc/common.opt.urls
index e31736cd9945..78e0dc209d14 100644
--- a/gcc/common.opt.urls
+++ b/gcc/common.opt.urls
@@ -564,6 +564,12 @@ UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-colu
 fdiagnostics-format=
 UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-format)
 
+fdiagnostics-add-output=
+UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-add-output)
+
+fdiagnostics-set-output=
+UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-set-output)
+
 fdiagnostics-escape-format=
 UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-escape-format)
 
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 8381f9504880..7c259fbbb8af 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -276,20 +276,15 @@ cp_seen_error ()
 void
 cxx_initialize_diagnostics (diagnostic_context *context)
 {
-  pretty_printer *base = context->m_printer;
-  cxx_pretty_printer *pp = XNEW (cxx_pretty_printer);
-  context->m_printer = new (pp) cxx_pretty_printer ();
-
-  /* It is safe to free this object because it was previously XNEW()'d.  */
-  base->~pretty_printer ();
-  XDELETE (base);
+  cxx_pretty_printer *pp = new cxx_pretty_printer ();
+  pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
+  context->set_pretty_printer (std::unique_ptr<pretty_printer> (pp));
 
   c_common_diagnostics_set_defaults (context);
   diagnostic_text_starter (context) = cp_diagnostic_text_starter;
   /* diagnostic_finalizer is already c_diagnostic_text_finalizer.  */
-  diagnostic_format_decoder (context) = cp_printer;
+  context->set_format_decoder (cp_printer);
   context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
-  pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
 /* Dump an '@module' name suffix for DECL, if any.  */
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index f72e0154dd62..9323023ff7fd 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -4698,7 +4698,7 @@ noisy_p ()
   if (quiet_flag)
     return false;
 
-  pp_needs_newline (global_dc->m_printer) = true;
+  pp_needs_newline (global_dc->get_reference_printer ()) = true;
   diagnostic_set_last_function (global_dc, (diagnostic_info *) NULL);
 
   return true;
diff --git a/gcc/d/d-diagnostic.cc b/gcc/d/d-diagnostic.cc
index d584d2ac2b23..f8c32ae918de 100644
--- a/gcc/d/d-diagnostic.cc
+++ b/gcc/d/d-diagnostic.cc
@@ -208,7 +208,7 @@ d_diagnostic_report_diagnostic (const Loc &loc, int opt, const char *format,
       /* Write verbatim messages with no location direct to stream.  */
       text_info text (expand_d_format (format), &argp, errno, nullptr);
 
-      pretty_printer *const pp = global_dc->m_printer;
+      pretty_printer *const pp = global_dc->get_reference_printer ();
       pp_format_verbatim (pp, &text);
       pp_newline_and_flush (pp);
     }
diff --git a/gcc/diagnostic-buffer.h b/gcc/diagnostic-buffer.h
index deb933a87630..07ccebf9357f 100644
--- a/gcc/diagnostic-buffer.h
+++ b/gcc/diagnostic-buffer.h
@@ -59,6 +59,7 @@ class diagnostic_buffer
   friend class diagnostic_context;
 
   diagnostic_buffer (diagnostic_context &ctxt);
+  ~diagnostic_buffer ();
 
   void dump (FILE *out, int indent) const;
   void DEBUG_FUNCTION dump () const { dump (stderr, 0); }
@@ -73,10 +74,10 @@ class diagnostic_buffer
   void move_to (diagnostic_buffer &dest);
 
  private:
-  void ensure_per_format_buffer ();
+  void ensure_per_format_buffers ();
 
   diagnostic_context &m_ctxt;
-  std::unique_ptr<diagnostic_per_format_buffer> m_per_format_buffer;
+  auto_vec<diagnostic_per_format_buffer *> *m_per_format_buffers;
 
   /* The number of buffered diagnostics of each kind.  */
   diagnostic_counters m_diagnostic_counters;
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index fdc55b364164..ab51fce8ad77 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -104,6 +104,15 @@ public:
   {
     /* No-op.  */
   }
+  void update_printer () final override
+  {
+    m_printer = m_context.clone_printer ();
+    pp_show_color (m_printer.get ()) = false;
+  }
+  bool follows_reference_printer_p () const final override
+  {
+    return false;
+  }
 
 protected:
   json_output_format (diagnostic_context &context,
@@ -501,9 +510,6 @@ static void
 diagnostic_output_format_init_json (diagnostic_context &context,
 				    std::unique_ptr<json_output_format> fmt)
 {
-  /* Suppress normal textual path output.  */
-  context.set_path_format (DPF_NONE);
-
   /* Don't colorize the text.  */
   pp_show_color (fmt->get_printer ()) = false;
   context.set_show_highlight_colors (false);
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 9973301cb29a..5a3fd3f5dac8 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -38,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-diagram.h"
 #include "text-art/canvas.h"
 #include "diagnostic-format-sarif.h"
+#include "diagnostic-format-text.h"
 #include "ordered-hash-map.h"
 #include "sbitmap.h"
 #include "make-unique.h"
@@ -680,12 +681,18 @@ public:
   friend class diagnostic_sarif_format_buffer;
 
   sarif_builder (diagnostic_context &context,
+		 pretty_printer &printer,
 		 const line_maps *line_maps,
 		 const char *main_input_filename_,
 		 bool formatted,
 		 enum sarif_version version);
   ~sarif_builder ();
 
+  void set_printer (pretty_printer &printer)
+  {
+    m_printer = &printer;
+  }
+
   void on_report_diagnostic (const diagnostic_info &diagnostic,
 			     diagnostic_t orig_diag_kind,
 			     diagnostic_sarif_format_buffer *buffer);
@@ -1540,12 +1547,13 @@ sarif_thread_flow::add_location ()
 /* sarif_builder's ctor.  */
 
 sarif_builder::sarif_builder (diagnostic_context &context,
+			      pretty_printer &printer,
 			      const line_maps *line_maps,
 			      const char *main_input_filename_,
 			      bool formatted,
 			      enum sarif_version version)
 : m_context (context),
-  m_printer (context.m_printer),
+  m_printer (&printer),
   m_line_maps (line_maps),
   m_token_printer (*this),
   m_version (version),
@@ -2123,11 +2131,13 @@ sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
 
       diagnostic_source_print_policy source_policy (dc);
       dc.set_escape_format (m_escape_format);
-      source_policy.print (*dc.m_printer, my_rich_loc, DK_ERROR, nullptr);
+      diagnostic_text_output_format text_output (dc);
+      source_policy.print (*text_output.get_printer (),
+			   my_rich_loc, DK_ERROR, nullptr);
 
+      const char *buf = pp_formatted_text (text_output.get_printer ());
       std::unique_ptr<sarif_multiformat_message_string> result
-	= builder.make_multiformat_message_string
-	    (pp_formatted_text (dc.m_printer));
+	= builder.make_multiformat_message_string (buf);
 
       diagnostic_finish (&dc);
 
@@ -3423,6 +3433,28 @@ public:
     m_buffer = buffer;
   }
 
+  bool follows_reference_printer_p () const final override
+  {
+    return false;
+  }
+
+  void update_printer () final override
+  {
+    m_printer = m_context.clone_printer ();
+
+    /* Don't colorize the text.  */
+    pp_show_color (m_printer.get ()) = false;
+
+    /* No textual URLs.  */
+    m_printer->set_url_format (URL_FORMAT_NONE);
+
+    /* Use builder's token printer.  */
+    get_printer ()->set_token_printer (&m_builder.get_token_printer ());
+
+    /* Update the builder to use the new printer.  */
+    m_builder.set_printer (*get_printer ());
+  }
+
   void on_begin_group () final override
   {
     /* No-op,  */
@@ -3458,7 +3490,8 @@ protected:
 		       bool formatted,
 		       enum sarif_version version)
   : diagnostic_output_format (context),
-    m_builder (context, line_maps, main_input_filename_, formatted, version),
+    m_builder (context, *get_printer (), line_maps, main_input_filename_,
+	       formatted, version),
     m_buffer (nullptr)
   {}
 
@@ -3651,15 +3684,8 @@ static void
 diagnostic_output_format_init_sarif (diagnostic_context &context,
 				     std::unique_ptr<sarif_output_format> fmt)
 {
-  /* Suppress normal textual path output.  */
-  context.set_path_format (DPF_NONE);
-
-  /* Don't colorize the text.  */
-  pp_show_color (fmt->get_printer ()) = false;
-  context.set_show_highlight_colors (false);
+  fmt->update_printer ();
 
-  context.m_printer->set_token_printer
-    (&fmt->get_builder ().get_token_printer ());
   context.set_output_format (std::move (fmt));
 }
 
@@ -3683,26 +3709,23 @@ diagnostic_output_format_init_sarif_stderr (diagnostic_context &context,
 						stderr));
 }
 
-/* Populate CONTEXT in preparation for SARIF output to a file named
-   BASE_FILE_NAME.sarif.  */
+/* Attempt to open BASE_FILE_NAME.sarif for writing.
+   Return a non-null diagnostic_output_file,
+   or return a null diagnostic_output_file and complain to CONTEXT
+   using LINE_MAPS.  */
 
-void
-diagnostic_output_format_init_sarif_file (diagnostic_context &context,
+diagnostic_output_file
+diagnostic_output_format_open_sarif_file (diagnostic_context &context,
 					  line_maps *line_maps,
-					  const char *main_input_filename_,
-					  bool formatted,
-					  enum sarif_version version,
 					  const char *base_file_name)
 {
-  gcc_assert (line_maps);
-
   if (!base_file_name)
     {
       rich_location richloc (line_maps, UNKNOWN_LOCATION);
       context.emit_diagnostic_with_group
 	(DK_ERROR, richloc, nullptr, 0,
 	 "unable to determine filename for SARIF output");
-      return;
+      return diagnostic_output_file ();
     }
 
   label_text filename = label_text::take (concat (base_file_name,
@@ -3716,9 +3739,29 @@ diagnostic_output_format_init_sarif_file (diagnostic_context &context,
 	(DK_ERROR, richloc, nullptr, 0,
 	 "unable to open %qs for SARIF output: %m",
 	 filename.get ());
-      return;
+      return diagnostic_output_file ();
     }
-  diagnostic_output_file output_file (outf, true, std::move (filename));
+  return diagnostic_output_file (outf, true, std::move (filename));
+}
+
+/* Populate CONTEXT in preparation for SARIF output to a file named
+   BASE_FILE_NAME.sarif.  */
+
+void
+diagnostic_output_format_init_sarif_file (diagnostic_context &context,
+					  line_maps *line_maps,
+					  const char *main_input_filename_,
+					  bool formatted,
+					  enum sarif_version version,
+					  const char *base_file_name)
+{
+  gcc_assert (line_maps);
+
+  diagnostic_output_file output_file
+    = diagnostic_output_format_open_sarif_file (context,
+						line_maps,
+						base_file_name);
+
   diagnostic_output_format_init_sarif
     (context,
      ::make_unique<sarif_file_output_format> (context,
@@ -3750,6 +3793,23 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
 						stream));
 }
 
+std::unique_ptr<diagnostic_output_format>
+make_sarif_sink (diagnostic_context &context,
+		 const line_maps &line_maps,
+		 const char *main_input_filename_,
+		 enum sarif_version version,
+		 diagnostic_output_file output_file)
+{
+  auto sink = ::make_unique<sarif_file_output_format> (context,
+						       &line_maps,
+						       main_input_filename_,
+						       true,
+						       version,
+						       std::move (output_file));
+  sink->update_printer ();
+  return sink;
+}
+
 #if CHECKING_P
 
 namespace selftest {
@@ -3822,8 +3882,9 @@ test_make_location_object (const line_table_case &case_,
     return;
 
   test_diagnostic_context dc;
-
-  sarif_builder builder (dc, line_table, "MAIN_INPUT_FILENAME", true, version);
+  pretty_printer pp;
+  sarif_builder builder (dc, pp, line_table, "MAIN_INPUT_FILENAME",
+			 true, version);
 
   /* These "columns" are byte offsets, whereas later on the columns
      in the generated SARIF use sarif_builder::get_sarif_column and
diff --git a/gcc/diagnostic-format-sarif.h b/gcc/diagnostic-format-sarif.h
index 5f8751aa3505..f149d2983a42 100644
--- a/gcc/diagnostic-format-sarif.h
+++ b/gcc/diagnostic-format-sarif.h
@@ -35,6 +35,11 @@ enum class sarif_version
   num_versions
 };
 
+extern diagnostic_output_file
+diagnostic_output_format_open_sarif_file (diagnostic_context &context,
+					  line_maps *line_maps,
+					  const char *base_file_name);
+
 extern void
 diagnostic_output_format_init_sarif_stderr (diagnostic_context &context,
 					    const line_maps *line_maps,
@@ -55,6 +60,12 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
 					    bool formatted,
 					    enum sarif_version version,
 					    FILE *stream);
+extern std::unique_ptr<diagnostic_output_format>
+make_sarif_sink (diagnostic_context &context,
+		 const line_maps &line_maps,
+		 const char *main_input_filename_,
+		 enum sarif_version version,
+		 diagnostic_output_file output_file);
 
 /* Concrete subclass of json::object for SARIF property bags
    (SARIF v2.1.0 section 3.8).  */
diff --git a/gcc/diagnostic-format-text.cc b/gcc/diagnostic-format-text.cc
index c23ac6c505d8..fd47ca4a15b2 100644
--- a/gcc/diagnostic-format-text.cc
+++ b/gcc/diagnostic-format-text.cc
@@ -160,6 +160,9 @@ void
 diagnostic_text_output_format::dump (FILE *out, int indent) const
 {
   fprintf (out, "%*sdiagnostic_text_output_format\n", indent, "");
+  fprintf (out, "%*sm_follows_reference_printer: %s\n",
+	   indent, "",
+	   m_follows_reference_printer ? "true" : "false");
   diagnostic_output_format::dump (out, indent);
   fprintf (out, "%*ssaved_output_buffer:\n", indent + 2, "");
   if (m_saved_output_buffer)
@@ -222,6 +225,13 @@ on_report_diagnostic (const diagnostic_info &diagnostic,
 					     orig_diag_kind);
 }
 
+void
+diagnostic_text_output_format::on_report_verbatim (text_info &text)
+{
+  pp_format_verbatim (get_printer (), &text);
+  pp_newline_and_flush (get_printer ());
+}
+
 void
 diagnostic_text_output_format::on_diagram (const diagnostic_diagram &diagram)
 {
@@ -315,6 +325,30 @@ diagnostic_text_output_format::append_note (location_t location,
   va_end (ap);
 }
 
+bool
+diagnostic_text_output_format::follows_reference_printer_p () const
+{
+  return m_follows_reference_printer;
+}
+
+void
+diagnostic_text_output_format::
+update_printer ()
+{
+  pretty_printer *copy_from_pp
+    = (m_follows_reference_printer
+       ? get_context ().get_reference_printer ()
+       : m_printer.get ());
+  const bool show_color = pp_show_color (copy_from_pp);
+  const diagnostic_url_format url_format = copy_from_pp->get_url_format ();
+
+  m_printer = get_context ().clone_printer ();
+
+  pp_show_color (m_printer.get ()) = show_color;
+  m_printer->set_url_format (url_format);
+  // ...etc
+}
+
 /* If DIAGNOSTIC has a CWE identifier, print it.
 
    For example, if the diagnostic metadata associates it with CWE-119,
diff --git a/gcc/diagnostic-format-text.h b/gcc/diagnostic-format-text.h
index b43501eaa4ab..78d1b2da0a0c 100644
--- a/gcc/diagnostic-format-text.h
+++ b/gcc/diagnostic-format-text.h
@@ -32,12 +32,14 @@ along with GCC; see the file COPYING3.  If not see
 class diagnostic_text_output_format : public diagnostic_output_format
 {
 public:
-  diagnostic_text_output_format (diagnostic_context &context)
+  diagnostic_text_output_format (diagnostic_context &context,
+				 bool follows_reference_printer = false)
   : diagnostic_output_format (context),
     m_saved_output_buffer (nullptr),
     m_column_policy (context),
     m_last_module (nullptr),
-    m_includes_seen (nullptr)
+    m_includes_seen (nullptr),
+    m_follows_reference_printer (follows_reference_printer)
   {}
   ~diagnostic_text_output_format ();
 
@@ -51,12 +53,16 @@ public:
   void on_end_group () override {}
   void on_report_diagnostic (const diagnostic_info &,
 			     diagnostic_t orig_diag_kind) override;
+  void on_report_verbatim (text_info &) final override;
   void on_diagram (const diagnostic_diagram &diagram) override;
-  void after_diagnostic (const diagnostic_info &) final override;
+  void after_diagnostic (const diagnostic_info &) override;
   bool machine_readable_stderr_p () const final override
   {
     return false;
   }
+  bool follows_reference_printer_p () const final override;
+
+  void update_printer () override;
 
   /* Helpers for writing lang-specific starters/finalizers for text output.  */
   char *build_prefix (const diagnostic_info &) const;
@@ -77,7 +83,7 @@ public:
   }
   diagnostic_location_print_policy get_location_print_policy () const;
 
-private:
+protected:
   void print_any_cwe (const diagnostic_info &diagnostic);
   void print_any_rules (const diagnostic_info &diagnostic);
   void print_option_information (const diagnostic_info &diagnostic,
@@ -98,6 +104,14 @@ private:
   /* Include files that report_current_module has already listed the
      include path for.  */
   hash_set<location_t, false, location_hash> *m_includes_seen;
+
+  /* If true, this is the initial default text output format created
+     when the diagnostic_context was created, and, in particular, before
+     initializations of color and m_url_format.  Hence this should follow
+     the dc's reference printer for these.
+     If false, this text output was created after the dc was created, and
+     thus tracks its own values for color and m_url_format.  */
+  bool m_follows_reference_printer;
 };
 
 #endif /* ! GCC_DIAGNOSTIC_FORMAT_TEXT_H */
diff --git a/gcc/diagnostic-format.h b/gcc/diagnostic-format.h
index a38d177ce8d1..11238cd95283 100644
--- a/gcc/diagnostic-format.h
+++ b/gcc/diagnostic-format.h
@@ -55,12 +55,21 @@ public:
   virtual void on_report_diagnostic (const diagnostic_info &,
 				     diagnostic_t orig_diag_kind) = 0;
 
+  virtual void on_report_verbatim (text_info &);
+
   virtual void on_diagram (const diagnostic_diagram &diagram) = 0;
   virtual void after_diagnostic (const diagnostic_info &) = 0;
   virtual bool machine_readable_stderr_p () const = 0;
+  virtual bool follows_reference_printer_p () const = 0;
+
+  /* Vfunc called when the diagnostic_context changes its
+     reference printer (either to a new subclass of pretty_printer
+     or when color/url options change).
+     Subclasses should update their m_printer accordingly.  */
+  virtual void update_printer () = 0;
 
   diagnostic_context &get_context () const { return m_context; }
-  pretty_printer *get_printer () const { return m_context.m_printer; }
+  pretty_printer *get_printer () const { return m_printer.get (); }
 
   text_art::theme *get_diagram_theme () const
   {
@@ -71,10 +80,13 @@ public:
 
 protected:
   diagnostic_output_format (diagnostic_context &context)
-  : m_context (context)
+  : m_context (context),
+    m_printer (context.clone_printer ())
   {}
 
+protected:
   diagnostic_context &m_context;
+  std::unique_ptr<pretty_printer> m_printer;
 };
 
 extern void
diff --git a/gcc/diagnostic-global-context.cc b/gcc/diagnostic-global-context.cc
index e82588e54047..bd299d08429b 100644
--- a/gcc/diagnostic-global-context.cc
+++ b/gcc/diagnostic-global-context.cc
@@ -37,7 +37,8 @@ diagnostic_context *global_dc = &global_diagnostic_context;
 /* Standard error reporting routines in increasing order of severity.  */
 
 /* Text to be emitted verbatim to the error message stream; this
-   produces no prefix and disables line-wrapping.  Use rarely.  */
+   produces no prefix and disables line-wrapping.  Use rarely.
+   It is ignored for machine-readable output formats.  */
 void
 verbatim (const char *gmsgid, ...)
 {
@@ -45,8 +46,7 @@ verbatim (const char *gmsgid, ...)
 
   va_start (ap, gmsgid);
   text_info text (_(gmsgid), &ap, errno);
-  pp_format_verbatim (global_dc->m_printer, &text);
-  pp_newline_and_flush (global_dc->m_printer);
+  global_dc->report_verbatim (text);
   va_end (ap);
 }
 
@@ -551,10 +551,8 @@ fnotice (FILE *file, const char *cmsgid, ...)
      emitting free-form text on stderr will lead to corrupt output.
      Skip the message for such cases.  */
   if (file == stderr && global_dc)
-    if (const diagnostic_output_format *output_format
-	  = global_dc->get_output_format ())
-      if (output_format->machine_readable_stderr_p ())
-	return;
+    if (!global_dc->supports_fnotice_on_stderr_p ())
+      return;
 
   va_list ap;
 
diff --git a/gcc/diagnostic-output-file.h b/gcc/diagnostic-output-file.h
index f0ae5e1915ec..6e7b41737cd4 100644
--- a/gcc/diagnostic-output-file.h
+++ b/gcc/diagnostic-output-file.h
@@ -27,6 +27,12 @@ along with GCC; see the file COPYING3.  If not see
 class diagnostic_output_file
 {
 public:
+  diagnostic_output_file ()
+  : m_outf (nullptr),
+    m_owned (false),
+    m_filename ()
+  {
+  }
   diagnostic_output_file (FILE *outf, bool owned, label_text filename)
   : m_outf (outf),
     m_owned (owned),
@@ -60,7 +66,26 @@ public:
   diagnostic_output_file &
   operator= (const diagnostic_output_file &other) = delete;
   diagnostic_output_file &
-  operator= (diagnostic_output_file &&other) = delete;
+  operator= (diagnostic_output_file &&other)
+  {
+    if (m_owned)
+      {
+	gcc_assert (m_outf);
+	fclose (m_outf);
+      }
+
+    m_outf = other.m_outf;
+    other.m_outf = nullptr;
+
+    m_owned = other.m_owned;
+    other.m_owned = false;
+
+    m_filename = std::move (other.m_filename);
+
+    if (m_owned)
+      gcc_assert (m_outf);
+    return *this;
+  }
 
   operator bool () const { return m_outf != nullptr; }
   FILE *get_open_file () const { return m_outf; }
diff --git a/gcc/diagnostic-path.cc b/gcc/diagnostic-path.cc
index 8a6d516ce416..2b1a093f3f22 100644
--- a/gcc/diagnostic-path.cc
+++ b/gcc/diagnostic-path.cc
@@ -1297,7 +1297,7 @@ test_interprocedural_path_1 (pretty_printer *event_pp)
 
   {
     test_diagnostic_context dc;
-    diagnostic_text_output_format text_output (dc);
+    diagnostic_text_output_format text_output (dc, false);
     path_print_policy policy (text_output);
     path_summary summary (policy, path, false);
     ASSERT_EQ (summary.get_num_ranges (), 9);
diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index 79499dc549d3..a2a4b047ff9c 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -3582,7 +3582,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
 			   linemap_position_for_column (line_table,
 							emoji_col));
     layout test_layout (policy, richloc, nullptr);
-    layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+    layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
     lp.print (policy);
     ASSERT_STREQ ("     |         1         \n"
 		  "     |         1         \n"
@@ -3591,7 +3591,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
 		  "that occupies 8 bytes and 4 display columns, starting at "
 		  "column #102.\n"
 		  "     | ^\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
   }
 
   /* Similar to the previous example, but now the offset called for would split
@@ -3609,7 +3609,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
 			   linemap_position_for_column (line_table,
 							emoji_col + 2));
     layout test_layout (dc, richloc, nullptr);
-    layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+    layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
     lp.print (policy);
     ASSERT_STREQ ("     |        1         1 \n"
 		  "     |        1         2 \n"
@@ -3618,7 +3618,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
 		  "that occupies 8 bytes and 4 display columns, starting at "
 		  "column #102.\n"
 		  "     |  ^\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
   }
 
 }
@@ -3690,9 +3690,9 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
       dc.m_tabstop = tabstop;
       diagnostic_source_print_policy policy (dc);
       layout test_layout (policy, richloc, nullptr);
-      layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+      layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
       lp.print (policy);
-      const char *out = pp_formatted_text (dc.m_printer);
+      const char *out = pp_formatted_text (dc.get_reference_printer ());
       ASSERT_EQ (NULL, strchr (out, '\t'));
       const char *left_quote = strchr (out, '`');
       const char *right_quote = strchr (out, '\'');
@@ -3715,7 +3715,7 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
       dc.m_source_printing.show_line_numbers_p = true;
       diagnostic_source_print_policy policy (dc);
       layout test_layout (policy, richloc, nullptr);
-      layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+      layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
       lp.print (policy);
 
       /* We have arranged things so that two columns will be printed before
@@ -3731,7 +3731,7 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
 	  "display columns, starting at column #103.\n"
 	  "     |   ^\n";
       const char *expected_output = (extra_width[tabstop] ? output1 : output2);
-      ASSERT_STREQ (expected_output, pp_formatted_text (dc.m_printer));
+      ASSERT_STREQ (expected_output, pp_formatted_text (dc.get_reference_printer ()));
     }
 }
 
@@ -3887,8 +3887,8 @@ test_one_liner_fixit_remove ()
   /* Test of adding a prefix.  */
   {
     test_diagnostic_context dc;
-    pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-    pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
+    pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:"));
     ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
 		  "TEST PREFIX:          ^~~~~~\n"
 		  "TEST PREFIX:          ------\n",
@@ -3914,8 +3914,8 @@ test_one_liner_fixit_remove ()
     test_diagnostic_context dc;
     dc.m_source_printing.show_ruler_p = true;
     dc.m_source_printing.max_width = 50;
-    pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-    pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
+    pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:"));
     ASSERT_STREQ ("TEST PREFIX:          1         2         3         4         5\n"
 		  "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX: foo = bar.field;\n"
@@ -3930,8 +3930,8 @@ test_one_liner_fixit_remove ()
     dc.m_source_printing.show_ruler_p = true;
     dc.m_source_printing.max_width = 50;
     dc.m_source_printing.show_line_numbers_p = true;
-    pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-    pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
+    pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:"));
     ASSERT_STREQ ("TEST PREFIX:      |          1         2         3         4         5\n"
 		  "TEST PREFIX:      | 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX:    1 | foo = bar.field;\n"
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 4791af9c66e2..477214c15f2b 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -50,6 +50,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "pretty-print-urlifier.h"
 #include "logical-location.h"
 #include "diagnostic-buffer.h"
+#include "make-unique.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -118,7 +119,7 @@ diagnostic_set_caret_max_width (diagnostic_context *context, int value)
 {
   /* One minus to account for the leading empty space.  */
   value = value ? value - 1
-    : (isatty (fileno (pp_buffer (context->m_printer)->m_stream))
+    : (isatty (fileno (pp_buffer (context->get_reference_printer ())->m_stream))
        ? get_terminal_width () - 1 : INT_MAX);
 
   if (value <= 0)
@@ -223,8 +224,7 @@ diagnostic_context::initialize (int n_opts)
 {
   /* Allocate a basic pretty-printer.  Clients will replace this a
      much more elaborated pretty-printer if they wish.  */
-  m_printer = XNEW (pretty_printer);
-  new (m_printer) pretty_printer ();
+  m_reference_printer = ::make_unique<pretty_printer> ().release ();
 
   m_file_cache = new file_cache ();
   m_diagnostic_counters.clear ();
@@ -232,7 +232,7 @@ diagnostic_context::initialize (int n_opts)
   m_n_opts = n_opts;
   m_option_classifier.init (n_opts);
   m_source_printing.enabled = false;
-  diagnostic_set_caret_max_width (this, pp_line_cutoff (m_printer));
+  diagnostic_set_caret_max_width (this, pp_line_cutoff (get_reference_printer ()));
   for (int i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     m_source_printing.caret_chars[i] = '^';
   m_show_cwe = false;
@@ -283,7 +283,7 @@ diagnostic_context::initialize (int n_opts)
   m_edit_context_ptr = nullptr;
   m_diagnostic_groups.m_nesting_depth = 0;
   m_diagnostic_groups.m_emission_count = 0;
-  m_output_format = new diagnostic_text_output_format (*this);
+  m_output_sinks.safe_push (new diagnostic_text_output_format (*this, true));
   m_set_locations_cb = nullptr;
   m_client_data_hooks = nullptr;
   m_diagrams.m_theme = nullptr;
@@ -326,8 +326,12 @@ diagnostic_context::color_init (int value)
       else
 	value = DIAGNOSTICS_COLOR_DEFAULT;
     }
-  pp_show_color (m_printer)
+  pp_show_color (m_reference_printer)
     = colorize_init ((diagnostic_color_rule_t) value);
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      pp_show_color (sink->get_printer ())
+	= pp_show_color (m_reference_printer);
 }
 
 /* Initialize URL support within this context based on VALUE,
@@ -354,8 +358,12 @@ diagnostic_context::urls_init (int value)
 	value = DIAGNOSTICS_URLS_DEFAULT;
     }
 
-  m_printer->set_url_format
+  m_reference_printer->set_url_format
     (determine_url_format ((diagnostic_url_rule_t) value));
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      sink->get_printer ()->set_url_format
+	(m_reference_printer->get_url_format ());
 }
 
 /* Create the file_cache, if not already created, and tell it how to
@@ -375,7 +383,7 @@ diagnostic_context::finish ()
 {
   /* We might be handling a fatal error.
      Close any active diagnostic groups, which may trigger flushing
-     the output format.  */
+     sinks.  */
   while (m_diagnostic_groups.m_nesting_depth > 0)
     end_group ();
 
@@ -383,8 +391,8 @@ diagnostic_context::finish ()
 
   /* Clean ups.  */
 
-  delete m_output_format;
-  m_output_format= nullptr;
+  while (!m_output_sinks.is_empty ())
+    delete m_output_sinks.pop ();
 
   if (m_diagrams.m_theme)
     {
@@ -397,11 +405,8 @@ diagnostic_context::finish ()
 
   m_option_classifier.fini ();
 
-  /* diagnostic_context::initialize allocates this->printer using XNEW
-     and placement-new.  */
-  m_printer->~pretty_printer ();
-  XDELETE (m_printer);
-  m_printer = nullptr;
+  delete m_reference_printer;
+  m_reference_printer = nullptr;
 
   if (m_edit_context_ptr)
     {
@@ -429,10 +434,13 @@ diagnostic_context::dump (FILE *out) const
 {
   fprintf (out, "diagnostic_context:\n");
   m_diagnostic_counters.dump (out, 2);
-  fprintf (out, "  output format:\n");
-  m_output_format->dump (out, 4);
-  fprintf (out, "  printer:\n");
-  m_printer->dump (out, 4);
+  fprintf (out, "  reference printer:\n");
+  m_reference_printer->dump (out, 4);
+  for (unsigned i = 0; i < m_output_sinks.length (); ++i)
+    {
+      fprintf (out, "  sink %i:\n", i);
+      m_output_sinks[i]->dump (out, 4);
+    }
   fprintf (out, "  diagnostic buffer:\n");
   if (m_diagnostic_buffer)
     m_diagnostic_buffer->dump (out, 4);
@@ -457,9 +465,34 @@ void
 diagnostic_context::
 set_output_format (std::unique_ptr<diagnostic_output_format> output_format)
 {
-  delete m_output_format;
-  /* Ideally this field would be a std::unique_ptr.  */
-  m_output_format = output_format.release ();
+  while (!m_output_sinks.is_empty ())
+    delete m_output_sinks.pop ();
+  m_output_sinks.safe_push (output_format.release ());
+}
+
+diagnostic_output_format &
+diagnostic_context::get_output_format (size_t idx) const
+{
+  gcc_assert (idx < m_output_sinks.length ());
+  gcc_assert (m_output_sinks[idx]);
+  return *m_output_sinks[idx];
+}
+
+void
+diagnostic_context::add_sink (std::unique_ptr<diagnostic_output_format> sink)
+{
+  m_output_sinks.safe_push (sink.release ());
+}
+
+/* Return true if there are no machine-readable formats writing to stderr.  */
+
+bool
+diagnostic_context::supports_fnotice_on_stderr_p () const
+{
+  for (auto sink : m_output_sinks)
+    if (sink->machine_readable_stderr_p ())
+      return false;
+  return true;
 }
 
 void
@@ -502,6 +535,55 @@ diagnostic_context::set_urlifier (std::unique_ptr<urlifier> urlifier)
   m_urlifier = urlifier.release ();
 }
 
+/* Set PP as the reference printer for this context.
+   Refresh all output sinks.  */
+
+void
+diagnostic_context::set_pretty_printer (std::unique_ptr<pretty_printer> pp)
+{
+  delete m_reference_printer;
+  m_reference_printer = pp.release ();
+  refresh_output_sinks ();
+}
+
+/* Give all output sinks a chance to rebuild their pretty_printer.  */
+
+void
+diagnostic_context::refresh_output_sinks ()
+{
+  for (auto sink : m_output_sinks)
+    sink->update_printer ();
+}
+
+/* Set FORMAT_DECODER on the reference printer and on the pretty_printer
+   of all output sinks.  */
+
+void
+diagnostic_context::set_format_decoder (printer_fn format_decoder)
+{
+  pp_format_decoder (m_reference_printer) = format_decoder;
+  for (auto sink : m_output_sinks)
+    pp_format_decoder (sink->get_printer ()) = format_decoder;
+}
+
+void
+diagnostic_context::set_show_highlight_colors (bool val)
+{
+  pp_show_highlight_colors (m_reference_printer) = val;
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      pp_show_highlight_colors (sink->get_printer ()) = val;
+}
+
+void
+diagnostic_context::set_prefixing_rule (diagnostic_prefixing_rule_t rule)
+{
+  pp_prefixing_rule (m_reference_printer) = rule;
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      pp_prefixing_rule (sink->get_printer ()) = rule;
+}
+
 void
 diagnostic_context::create_edit_context ()
 {
@@ -1234,8 +1316,6 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 {
   diagnostic_t orig_diag_kind = diagnostic->kind;
 
-  gcc_assert (m_output_format);
-
   /* Every call to report_diagnostic should be within a
      begin_group/end_group pair so that output formats can reliably
      flush diagnostics with on_end_group when the topmost group is ended.  */
@@ -1269,7 +1349,7 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 	 through.  Don't do this more than once.  */
       if ((diagnostic->kind == DK_ICE || diagnostic->kind == DK_ICE_NOBT)
 	  && m_lock == 1)
-	pp_newline_and_flush (m_printer);
+	pp_newline_and_flush (m_reference_printer);
       else
 	error_recursion ();
     }
@@ -1341,20 +1421,38 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 
   /* Is this the initial diagnostic within the stack of groups?  */
   if (m_diagnostic_groups.m_emission_count == 0)
-    m_output_format->on_begin_group ();
+    for (auto sink : m_output_sinks)
+      sink->on_begin_group ();
   m_diagnostic_groups.m_emission_count++;
 
-  /* Run phases 1 and 2 of formatting the message.
-     In particular, some format codes may have side-effects here which need to
-     happen before sending the diagnostic to the output format.
-
-     For example, Fortran's %C and %L formatting codes populate the
-     rich_location.  */
-  pp_format (m_printer, &diagnostic->message);
-
-  /* Call vfunc in the output format.  This is responsible for
-     phase 3 of formatting, and for printing the result.  */
-  m_output_format->on_report_diagnostic (*diagnostic, orig_diag_kind);
+  va_list *orig_args = diagnostic->message.m_args_ptr;
+  for (auto sink : m_output_sinks)
+    {
+      /* Formatting the message is done per-output-format,
+	 so that each output format gets its own set of pp_token_lists
+	 to work with.
+
+	 Run phases 1 and 2 of formatting the message before calling
+	 the format's on_report_diagnostic.
+	 In particular, some format codes may have side-effects here which
+	 need to happen before sending the diagnostic to the output format.
+	 For example, Fortran's %C and %L formatting codes populate the
+	 rich_location.
+	 Such side-effects must be idempotent, since they are run per
+	 output-format.
+
+	 Make a duplicate of the varargs for each call to pp_format,
+	 so that each has its own set to consume.  */
+      va_list copied_args;
+      va_copy (copied_args, *orig_args);
+      diagnostic->message.m_args_ptr = &copied_args;
+      pp_format (sink->get_printer (), &diagnostic->message);
+      va_end (copied_args);
+
+      /* Call vfunc in the output format.  This is responsible for
+	 phase 3 of formatting, and for printing the result.  */
+      sink->on_report_diagnostic (*diagnostic, orig_diag_kind);
+    }
 
   switch (m_extra_output_kind)
     {
@@ -1362,17 +1460,17 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
       break;
     case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1:
       print_parseable_fixits (get_file_cache (),
-			      m_printer, diagnostic->richloc,
+			      m_reference_printer, diagnostic->richloc,
 			      DIAGNOSTICS_COLUMN_UNIT_BYTE,
 			      m_tabstop);
-      pp_flush (m_printer);
+      pp_flush (m_reference_printer);
       break;
     case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v2:
       print_parseable_fixits (get_file_cache (),
-			      m_printer, diagnostic->richloc,
+			      m_reference_printer, diagnostic->richloc,
 			      DIAGNOSTICS_COLUMN_UNIT_DISPLAY,
 			      m_tabstop);
-      pp_flush (m_printer);
+      pp_flush (m_reference_printer);
       break;
     }
   if (m_diagnostic_buffer == nullptr
@@ -1389,11 +1487,26 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   m_lock--;
 
   if (!m_diagnostic_buffer)
-    m_output_format->after_diagnostic (*diagnostic);
+    for (auto sink : m_output_sinks)
+      sink->after_diagnostic (*diagnostic);
 
   return true;
 }
 
+void
+diagnostic_context::report_verbatim (text_info &text)
+{
+  va_list *orig_args = text.m_args_ptr;
+  for (auto sink : m_output_sinks)
+    {
+      va_list copied_args;
+      va_copy (copied_args, *orig_args);
+      text.m_args_ptr = &copied_args;
+      sink->on_report_verbatim (text);
+      va_end (copied_args);
+    }
+}
+
 /* Get the number of digits in the decimal representation of VALUE.  */
 
 int
@@ -1511,8 +1624,8 @@ diagnostic_context::emit_diagram (const diagnostic_diagram &diagram)
   if (m_diagrams.m_theme == nullptr)
     return;
 
-  gcc_assert (m_output_format);
-  m_output_format->on_diagram (diagram);
+  for (auto sink : m_output_sinks)
+    sink->on_diagram (diagram);
 }
 
 /* Inform the user that an error occurred while trying to report some
@@ -1524,7 +1637,7 @@ void
 diagnostic_context::error_recursion ()
 {
   if (m_lock < 3)
-    pp_newline_and_flush (m_printer);
+    pp_newline_and_flush (m_reference_printer);
 
   fnotice (stderr,
 	   "internal compiler error: error reporting routines re-entered.\n");
@@ -1554,7 +1667,7 @@ fancy_abort (const char *file, int line, const char *function)
      initialized yet, or might be in use by another thread).
      Handle such cases as gracefully as possible by falling back to a
      minimal abort handler that only relies on i18n.  */
-  if (global_dc->m_printer == nullptr)
+  if (global_dc->get_reference_printer () == nullptr)
     {
       /* Print the error message.  */
       fnotice (stderr, diagnostic_kind_text[DK_ICE]);
@@ -1597,15 +1710,23 @@ diagnostic_context::end_group ()
 	 If any diagnostics were emitted, give the context a chance
 	 to do something.  */
       if (m_diagnostic_groups.m_emission_count > 0)
-	m_output_format->on_end_group ();
+	for (auto sink : m_output_sinks)
+	  sink->on_end_group ();
       m_diagnostic_groups.m_emission_count = 0;
     }
 }
 
 void
-diagnostic_output_format::dump (FILE *, int) const
+diagnostic_output_format::dump (FILE *out, int indent) const
+{
+  fprintf (out, "%*sprinter:\n", indent, "");
+  m_printer->dump (out, indent + 2);
+}
+
+void
+diagnostic_output_format::on_report_verbatim (text_info &)
 {
-  /* No-op for now.  */
+  /* No-op.  */
 }
 
 /* Set the output format for CONTEXT to FORMAT, using BASE_FILE_NAME for
@@ -1653,15 +1774,6 @@ diagnostic_output_format_init (diagnostic_context &context,
 						sarif_version::v2_1_0,
 						base_file_name);
       break;
-    case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE:
-      diagnostic_output_format_init_sarif_file
-	(context,
-	 line_table,
-	 main_input_filename_,
-	 json_formatting,
-	 sarif_version::v2_2_prerelease_2024_08_08,
-	 base_file_name);
-      break;
     }
 }
 
@@ -1713,15 +1825,22 @@ diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer)
 
   m_diagnostic_buffer = buffer;
 
-  gcc_assert (m_output_format);
   if (buffer)
     {
-      buffer->ensure_per_format_buffer ();
-      gcc_assert (buffer->m_per_format_buffer);
-      m_output_format->set_buffer (buffer->m_per_format_buffer.get ());
+      buffer->ensure_per_format_buffers ();
+      gcc_assert (buffer->m_per_format_buffers);
+      gcc_assert (buffer->m_per_format_buffers->length ()
+		  == m_output_sinks.length ());
+      for (unsigned idx = 0; idx < m_output_sinks.length (); ++idx)
+	{
+	  auto sink = m_output_sinks[idx];
+	  auto per_format_buffer = (*buffer->m_per_format_buffers)[idx];
+	  sink->set_buffer (per_format_buffer);
+	}
     }
   else
-    m_output_format->set_buffer (nullptr);
+    for (auto sink : m_output_sinks)
+      sink->set_buffer (nullptr);
 }
 
 /* Clear BUFFER without flushing it.  */
@@ -1729,8 +1848,10 @@ diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer)
 void
 diagnostic_context::clear_diagnostic_buffer (diagnostic_buffer &buffer)
 {
-  if (buffer.m_per_format_buffer)
-    buffer.m_per_format_buffer->clear ();
+  if (buffer.m_per_format_buffers)
+    for (auto per_format_buffer : *buffer.m_per_format_buffers)
+      per_format_buffer->clear ();
+
   buffer.m_diagnostic_counters.clear ();
 
   /* We need to reset last_location, otherwise we may skip caret lines
@@ -1746,8 +1867,9 @@ diagnostic_context::flush_diagnostic_buffer (diagnostic_buffer &buffer)
   bool had_errors
     = (buffer.m_diagnostic_counters.m_count_for_kind[DK_ERROR] > 0
        || buffer.m_diagnostic_counters.m_count_for_kind[DK_WERROR] > 0);
-  if (buffer.m_per_format_buffer)
-    buffer.m_per_format_buffer->flush ();
+  if (buffer.m_per_format_buffers)
+    for (auto per_format_buffer : *buffer.m_per_format_buffers)
+      per_format_buffer->flush ();
   buffer.m_diagnostic_counters.move_to (m_diagnostic_counters);
 
   action_after_output (had_errors ? DK_ERROR : DK_WARNING);
@@ -1796,17 +1918,29 @@ diagnostic_counters::clear ()
 /* class diagnostic_buffer.  */
 
 diagnostic_buffer::diagnostic_buffer (diagnostic_context &ctxt)
-: m_ctxt (ctxt)
+: m_ctxt (ctxt),
+  m_per_format_buffers (nullptr)
 {
 }
 
+diagnostic_buffer::~diagnostic_buffer ()
+{
+  if (m_per_format_buffers)
+    {
+      for (auto iter : *m_per_format_buffers)
+	delete iter;
+      delete m_per_format_buffers;
+    }
+}
+
 void
 diagnostic_buffer::dump (FILE *out, int indent) const
 {
-  fprintf (out, "%*sm_per_format_buffer:\n", indent, "");
   m_diagnostic_counters.dump (out, indent + 2);
-  if (m_per_format_buffer)
-    m_per_format_buffer->dump (out, indent + 2);
+  fprintf (out, "%*sm_per_format_buffers:\n", indent, "");
+  if (m_per_format_buffers)
+    for (auto per_format_buffer : *m_per_format_buffers)
+      per_format_buffer->dump (out, indent + 2);
   else
     fprintf (out, "%*s(none)\n", indent + 2, "");
 }
@@ -1814,33 +1948,67 @@ diagnostic_buffer::dump (FILE *out, int indent) const
 bool
 diagnostic_buffer::empty_p () const
 {
-  if (m_per_format_buffer)
-    return m_per_format_buffer->empty_p ();
-  else
-    return true;
+  if (m_per_format_buffers)
+    for (auto per_format_buffer : *m_per_format_buffers)
+      /* Query initial buffer.  */
+      return per_format_buffer->empty_p ();
+  return true;
 }
 
 void
 diagnostic_buffer::move_to (diagnostic_buffer &dest)
 {
-  ensure_per_format_buffer ();
-  dest.ensure_per_format_buffer ();
-  m_per_format_buffer->move_to (*dest.m_per_format_buffer);
+  /* Bail if there's nothing to move.  */
+  if (!m_per_format_buffers)
+    return;
+
   m_diagnostic_counters.move_to (dest.m_diagnostic_counters);
+
+  if (!dest.m_per_format_buffers)
+    {
+      /* Optimization for the "move to empty" case:
+	 simply move the vec to the dest.  */
+      dest.m_per_format_buffers = m_per_format_buffers;
+      m_per_format_buffers = nullptr;
+      return;
+    }
+
+  dest.ensure_per_format_buffers ();
+  gcc_assert (m_per_format_buffers);
+  gcc_assert (m_per_format_buffers->length ()
+	      == m_ctxt.m_output_sinks.length ());
+  gcc_assert (dest.m_per_format_buffers);
+  gcc_assert (dest.m_per_format_buffers->length ()
+	      == m_ctxt.m_output_sinks.length ());
+  for (unsigned idx = 0; idx < m_ctxt.m_output_sinks.length (); ++idx)
+    {
+      auto per_format_buffer_src = (*m_per_format_buffers)[idx];
+      auto per_format_buffer_dest = (*dest.m_per_format_buffers)[idx];
+      per_format_buffer_src->move_to (*per_format_buffer_dest);
+    }
 }
 
-/* Lazily get output format to create its own kind of buffer.  */
+/* Lazily get the output formats to create their own kind of buffers.
+   We can't change the output sinks on a context once this has been called
+   on any diagnostic_buffer instances for that context, since there's no
+   way to update all diagnostic_buffer instances for that context.  */
 
 void
-diagnostic_buffer::ensure_per_format_buffer ()
+diagnostic_buffer::ensure_per_format_buffers ()
 {
-  if (!m_per_format_buffer)
+  if (!m_per_format_buffers)
     {
-      gcc_assert (m_ctxt.get_output_format ());
-      m_per_format_buffer
-	= m_ctxt.get_output_format ()->make_per_format_buffer ();
+      m_per_format_buffers = new auto_vec<diagnostic_per_format_buffer *> ();
+      for (unsigned idx = 0; idx < m_ctxt.m_output_sinks.length (); ++idx)
+	{
+	  auto sink = m_ctxt.m_output_sinks[idx];
+	  auto per_format_buffer = sink->make_per_format_buffer ();
+	  m_per_format_buffers->safe_push (per_format_buffer.release ());
+	}
     }
-  gcc_assert (m_per_format_buffer);
+  gcc_assert (m_per_format_buffers);
+  gcc_assert (m_per_format_buffers->length ()
+	      == m_ctxt.m_output_sinks.length ());
 }
 
 /* Really call the system 'abort'.  This has to go right at the end of
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 3da1ae9c8019..46478c9a52b7 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -84,11 +84,7 @@ enum diagnostics_output_format
   DIAGNOSTICS_OUTPUT_FORMAT_SARIF_STDERR,
 
   /* SARIF-based output, to a file.  */
-  DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE,
-
-  /* Undocumented, for use by test suite.
-     SARIF-based output, to a file, using a prerelease of the 2.2 schema.  */
-  DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE
+  DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE
 };
 
 /* An enum for controlling how diagnostic_paths should be printed.  */
@@ -494,6 +490,7 @@ public:
 
   friend class diagnostic_source_print_policy;
   friend class diagnostic_text_output_format;
+  friend class diagnostic_buffer;
 
   typedef void (*set_locations_callback_t) (diagnostic_context *,
 					    diagnostic_info *);
@@ -501,6 +498,8 @@ public:
   void initialize (int n_opts);
   void color_init (int value);
   void urls_init (int value);
+  void set_pretty_printer (std::unique_ptr<pretty_printer> pp);
+  void refresh_output_sinks ();
 
   void finish ();
 
@@ -548,6 +547,7 @@ public:
     ATTRIBUTE_GCC_DIAG(6,0);
 
   bool report_diagnostic (diagnostic_info *);
+  void report_verbatim (text_info &);
 
   diagnostic_t
   classify_diagnostic (diagnostic_option_id option_id,
@@ -576,11 +576,6 @@ public:
 
   void emit_diagram (const diagnostic_diagram &diagram);
 
-  diagnostic_output_format *get_output_format () const
-  {
-    return m_output_format;
-  }
-
   /* Various setters for use by option-handling logic.  */
   void set_output_format (std::unique_ptr<diagnostic_output_format> output_format);
   void set_text_art_charset (enum diagnostic_text_art_charset charset);
@@ -598,10 +593,7 @@ public:
   }
   void set_show_cwe (bool val) { m_show_cwe = val;  }
   void set_show_rules (bool val) { m_show_rules = val; }
-  void set_show_highlight_colors (bool val)
-  {
-    pp_show_highlight_colors (m_printer) = val;
-  }
+  void set_show_highlight_colors (bool val);
   void set_path_format (enum diagnostic_path_format val)
   {
     m_path_format = val;
@@ -614,12 +606,16 @@ public:
     m_escape_format = val;
   }
 
+  void set_format_decoder (printer_fn format_decoder);
+  void set_prefixing_rule (diagnostic_prefixing_rule_t rule);
+
   /* Various accessors.  */
   bool warning_as_error_requested_p () const
   {
     return m_warning_as_error_requested;
   }
   bool show_path_depths_p () const { return m_show_path_depths; }
+  diagnostic_output_format &get_output_format (size_t idx) const;
   enum diagnostic_path_format get_path_format () const { return m_path_format; }
   enum diagnostics_escape_format get_escape_format () const
   {
@@ -719,9 +715,19 @@ public:
 
   std::unique_ptr<pretty_printer> clone_printer () const
   {
-    return m_printer->clone ();
+    return m_reference_printer->clone ();
   }
 
+  pretty_printer *get_reference_printer () const
+  {
+    return m_reference_printer;
+  }
+
+  void
+  add_sink (std::unique_ptr<diagnostic_output_format>);
+
+  bool supports_fnotice_on_stderr_p () const;
+
 private:
   void error_recursion () ATTRIBUTE_NORETURN;
 
@@ -735,13 +741,14 @@ private:
   /* Data members.
      Ideally, all of these would be private.  */
 
-public:
-  /* Where most of the diagnostic formatting work is done.
+private:
+  /* A reference instance of pretty_printer created by the client
+     and owned by the context.  Used for cloning when creating/adding
+     output formats.
      Owned by the context; this would be a std::unique_ptr if
      diagnostic_context had a proper ctor.  */
-  pretty_printer *m_printer;
+  pretty_printer *m_reference_printer;
 
-private:
   /* Cache of source code.
      Owned by the context; this would be a std::unique_ptr if
      diagnostic_context had a proper ctor.  */
@@ -902,11 +909,12 @@ private:
     int m_emission_count;
   } m_diagnostic_groups;
 
-  /* How to output diagnostics (text vs a structured format such as JSON).
-     Must be non-NULL; owned by context.
-     This would be a std::unique_ptr if diagnostic_context had a proper
-     ctor.  */
-  diagnostic_output_format *m_output_format;
+  /* The various sinks to which diagnostics are to be outputted
+     (text vs structured formats such as SARIF).
+     The sinks are owned by the context; this would be a
+     std::vector<std::unique_ptr> if diagnostic_context had a
+     proper ctor.  */
+  auto_vec<diagnostic_output_format *> m_output_sinks;
 
   /* Callback to set the locations of call sites along the inlining
      stack corresponding to a diagnostic location.  Needed to traverse
@@ -982,12 +990,6 @@ diagnostic_text_finalizer (diagnostic_context *context)
 #define diagnostic_context_auxiliary_data(DC) (DC)->m_client_aux_data
 #define diagnostic_info_auxiliary_data(DI) (DI)->x_data
 
-/* Same as pp_format_decoder.  Works on 'diagnostic_context *'.  */
-#define diagnostic_format_decoder(DC) pp_format_decoder ((DC)->m_printer)
-
-/* Same as pp_prefixing_rule.  Works on 'diagnostic_context *'.  */
-#define diagnostic_prefixing_rule(DC) pp_prefixing_rule ((DC)->m_printer)
-
 /* Raise SIGABRT on any diagnostic of severity DK_ERROR or higher.  */
 inline void
 diagnostic_abort_on_error (diagnostic_context *context)
@@ -1000,14 +1002,6 @@ diagnostic_abort_on_error (diagnostic_context *context)
    and similar functions.  */
 extern diagnostic_context *global_dc;
 
-/* Returns whether the diagnostic framework has been intialized already and is
-   ready for use.  */
-inline bool
-diagnostic_ready_p ()
-{
-  return global_dc->m_printer != nullptr;
-}
-
 /* The number of errors that have been issued so far.  Ideally, these
    would take a diagnostic_context as an argument.  */
 #define errorcount global_dc->diagnostic_count (DK_ERROR)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ae52cc68c511..07920e07b4d1 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -307,6 +307,8 @@ Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]}
 -fdiagnostics-urls=@r{[}auto@r{|}never@r{|}always@r{]}
 -fdiagnostics-format=@r{[}text@r{|}sarif-stderr@r{|}sarif-file@r{|}json@r{|}json-stderr@r{|}json-file@r{]}
+-fdiagnostics-add-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
+-fdiagnostics-set-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
 -fno-diagnostics-json-formatting
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret
 -fno-diagnostics-show-event-links
@@ -5902,6 +5904,10 @@ Select a different format for printing diagnostics.
 @var{FORMAT} is @samp{text}, @samp{sarif-stderr}, @samp{sarif-file},
 @samp{json}, @samp{json-stderr}, or @samp{json-file}.
 
+Using this option replaces any additional ``output sinks'' added by
+@option{-fdiagnostics-add-output=}, or that set by
+@option{-fdiagnostics-set-output=}.
+
 The default is @samp{text}.
 
 The @samp{sarif-stderr} and @samp{sarif-file} formats both emit
@@ -5916,6 +5922,97 @@ where the JSON is emitted to.  With @samp{json-stderr}, the JSON is emitted
 to stderr, whereas with @samp{json-file} it is written to
 @file{@var{source}.gcc.json}.
 
+@opindex fdiagnostics-add-output
+@item -fdiagnostics-add-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
+Add an additional ``output sink'' for emitting diagnostics.
+
+@var{DIAGNOSTICS-OUTPUT-SPEC} should specify a scheme, optionally followed
+by @code{:} and one or more @var{KEY}=@var{VALUE} pairs, in this form:
+
+@smallexample
+@var{SCHEME}
+@var{SCHEME}:@var{KEY}=@var{VALUE}
+@var{SCHEME}:@var{KEY}=@var{VALUE},@var{KEY2}=@var{VALUE2}
+@end smallexample
+
+etc.
+
+@var{SCHEME} can be
+
+@table @gcctabopt
+
+@item text
+Emit diagnostics to stderr using GCC's classic text output format.
+
+Supported keys are:
+
+@table @gcctabopt
+
+@item color=@r{[}yes@r{|}no@r{]}
+Override colorization settings from @option{-fdiagnostics-color} for this
+text output.
+
+@end table
+
+@item sarif
+Emit diagnostics to a file in SARIF format.
+
+Supported keys are:
+
+@table @gcctabopt
+
+@item file=@var{FILENAME}
+Specify the filename to write the SARIF output to, potentially with a
+leading absolute or relative path.  If not specified, it defaults to
+@file{@var{source}.sarif}.
+
+@item version=@r{[}2.1@r{|}2.2-prerelease@r{]}
+Specify the version of SARIF to use for the output.  If not specified,
+defaults to 2.1.  @code{2.2-prerelease} uses an unofficial draft of the
+future SARIF 2.2 specification and should only be used for experimentation
+in this release.
+
+@end table
+
+@end table
+
+For example,
+
+@smallexample
+-fdiagnostics-add-output=sarif:version=2.1,file=foo.2.1.sarif
+-fdiagnostics-add-output=sarif:version=2.2-prerelease,file=foo.2.2.sarif
+@end smallexample
+
+would add a pair of outputs, each writing to a different file, using
+versions 2.1 and 2.2 of the SARIF standard respectively.
+
+In EBNF:
+
+@smallexample
+
+@var{diagnostics-output-specifier} = @var{diagnostics-output-name}
+                                   | @var{diagnostics-output-name}, ":", @var{key-value-pairs};
+
+@var{diagnostics-output-name} = "text" | "sarif";
+
+@var{key-value-pairs} = @var{key-value-pair}
+                      | @var{key-value-pair} "," @var{key-value-pairs};
+
+@var{key-value-pair} = @var{key} "=" @var{value};
+
+@var{key} = ? string without a '=' ? ;
+@var{value} = ? string without a ',' ? ;
+
+@end smallexample
+
+@opindex fdiagnostics-set-output
+@item -fdiagnostics-set-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
+This works in a similar way to @option{-fdiagnostics-add-output=} except
+that instead of adding an additional ``output sink'' for diagnostics, it
+replaces all existing output sinks, such as from @option{-fdiagnostics-format=},
+@option{-fdiagnostics-add-output=}, or a prior call to
+@option{-fdiagnostics-set-output=}.
+
 @opindex fno-diagnostics-json-formatting
 @opindex fdiagnostics-json-formatting
 @item -fno-diagnostics-json-formatting
diff --git a/gcc/fortran/error.cc b/gcc/fortran/error.cc
index b1eda94ccb68..050a8f286efd 100644
--- a/gcc/fortran/error.cc
+++ b/gcc/fortran/error.cc
@@ -479,7 +479,7 @@ gfc_diagnostic_build_kind_prefix (diagnostic_context *context,
   gcc_assert (diagnostic->kind < DK_LAST_DIAGNOSTIC_KIND);
   const char *text = _(diagnostic_kind_text[diagnostic->kind]);
   const char *text_cs = "", *text_ce = "";
-  pretty_printer *const pp = context->m_printer;
+  pretty_printer *const pp = context->get_reference_printer ();
 
   if (diagnostic_kind_color[diagnostic->kind])
     {
@@ -951,7 +951,7 @@ gfc_diagnostics_init (void)
   diagnostic_text_starter (global_dc) = gfc_diagnostic_text_starter;
   diagnostic_start_span (global_dc) = gfc_diagnostic_start_span;
   diagnostic_text_finalizer (global_dc) = gfc_diagnostic_text_finalizer;
-  diagnostic_format_decoder (global_dc) = gfc_format_decoder;
+  global_dc->set_format_decoder (gfc_format_decoder);
   global_dc->m_source_printing.caret_chars[0] = '1';
   global_dc->m_source_printing.caret_chars[1] = '2';
   pp_warning_buffer = new diagnostic_buffer (*global_dc);
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 30f89a62f4f4..5fe5fd86a98e 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -50,6 +50,7 @@ compilation is specified by a string called a "spec".  */
 #include "opts-jobserver.h"
 #include "common/common-target.h"
 #include "gcc-urlifier.h"
+#include "opts-diagnostic.h"
 
 #ifndef MATH_LIBRARY
 #define MATH_LIBRARY "m"
@@ -4369,6 +4370,14 @@ driver_handle_option (struct gcc_options *opts,
 	  break;
 	}
 
+    case OPT_fdiagnostics_add_output_:
+      handle_OPT_fdiagnostics_add_output_ (*opts, *dc, arg, loc);
+      break;
+
+    case OPT_fdiagnostics_set_output_:
+      handle_OPT_fdiagnostics_set_output_ (*opts, *dc, arg, loc);
+      break;
+
     case OPT_fdiagnostics_text_art_charset_:
       dc->set_text_art_charset ((enum diagnostic_text_art_charset)value);
       break;
diff --git a/gcc/jit/dummy-frontend.cc b/gcc/jit/dummy-frontend.cc
index 12167829b277..35475b5ad05d 100644
--- a/gcc/jit/dummy-frontend.cc
+++ b/gcc/jit/dummy-frontend.cc
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cgraph.h"
 #include "target.h"
 #include "diagnostic-format-text.h"
+#include "make-unique.h"
 
 #include <mpfr.h>
 
@@ -981,6 +982,49 @@ struct ggc_root_tab jit_root_tab[] =
     LAST_GGC_ROOT_TAB
   };
 
+/* Subclass of diagnostic_output_format for libgccjit: like text
+   output, but capture the message and call add_diagnostic with it
+   on the active playback context.  */
+
+class jit_diagnostic_listener : public diagnostic_text_output_format
+{
+public:
+  jit_diagnostic_listener (diagnostic_context &dc,
+			   gcc::jit::playback::context &playback_ctxt)
+  : diagnostic_text_output_format (dc),
+    m_playback_ctxt (playback_ctxt)
+  {
+  }
+
+  void dump (FILE *out, int indent) const final override
+  {
+    fprintf (out, "%*sjit_diagnostic_listener\n", indent, "");
+    fprintf (out, "%*sm_playback_context: %p\n",
+	     indent + 2, "",
+	     (void *)&m_playback_ctxt);
+  }
+
+  void on_report_diagnostic (const diagnostic_info &info,
+			     diagnostic_t orig_diag_kind)
+  {
+    JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ());
+
+    /* Let the text output format do most of the work.  */
+    diagnostic_text_output_format::on_report_diagnostic (info, orig_diag_kind);
+
+    const char *text = pp_formatted_text (get_printer ());
+
+    /* Delegate to the playback context (and thence to the
+       recording context).  */
+    gcc::jit::active_playback_ctxt->add_diagnostic (text, info);
+
+    pp_clear_output_area (get_printer ());
+  }
+
+private:
+  gcc::jit::playback::context &m_playback_ctxt;
+};
+
 /* JIT-specific implementation of diagnostic callbacks.  */
 
 /* Implementation of "begin_diagnostic".  */
@@ -992,24 +1036,22 @@ jit_begin_diagnostic (diagnostic_text_output_format &,
   gcc_assert (gcc::jit::active_playback_ctxt);
   JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ());
 
-  /* No-op (apart from logging); the real error-handling is done in the
-     "end_diagnostic" hook.  */
+  /* No-op (apart from logging); the real error-handling is done by the
+     jit_diagnostic_listener.  */
 }
 
 /* Implementation of "end_diagnostic".  */
 
 static void
-jit_end_diagnostic (diagnostic_text_output_format &text_output,
-		    const diagnostic_info *diagnostic,
+jit_end_diagnostic (diagnostic_text_output_format &,
+		    const diagnostic_info *,
 		    diagnostic_t)
 {
   gcc_assert (gcc::jit::active_playback_ctxt);
   JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ());
 
-  /* Delegate to the playback context (and thence to the
-     recording context).  */
-  gcc_assert (diagnostic);
-  gcc::jit::active_playback_ctxt->add_diagnostic (&text_output.get_context (), *diagnostic); // FIXME
+  /* No-op (apart from logging); the real error-handling is done by the
+     jit_diagnostic_listener.  */
 }
 
 /* Language hooks.  */
@@ -1030,6 +1072,10 @@ jit_langhook_init (void)
   gcc_assert (global_dc);
   diagnostic_text_starter (global_dc) = jit_begin_diagnostic;
   diagnostic_text_finalizer (global_dc) = jit_end_diagnostic;
+  auto sink
+    = ::make_unique<jit_diagnostic_listener> (*global_dc,
+					      *gcc::jit::active_playback_ctxt);
+  global_dc->set_output_format (std::move (sink));
 
   build_common_tree_nodes (false);
 
diff --git a/gcc/jit/jit-playback.cc b/gcc/jit/jit-playback.cc
index e732c2287518..8dd77e4d5871 100644
--- a/gcc/jit/jit-playback.cc
+++ b/gcc/jit/jit-playback.cc
@@ -3687,14 +3687,9 @@ add_error_va (location *loc, const char *fmt, va_list ap)
 
 void
 playback::context::
-add_diagnostic (diagnostic_context *diag_context,
+add_diagnostic (const char *text,
 		const diagnostic_info &diagnostic)
 {
-  /* At this point the text has been formatted into the pretty-printer's
-     output buffer.  */
-  pretty_printer *pp = diag_context->m_printer;
-  const char *text = pp_formatted_text (pp);
-
   /* Get location information (if any) from the diagnostic.
      The recording::context::add_error[_va] methods require a
      recording::location.  We can't lookup the playback::location
@@ -3714,7 +3709,6 @@ add_diagnostic (diagnostic_context *diag_context,
     }
 
   m_recording_ctxt->add_error (rec_loc, "%s", text);
-  pp_clear_output_area (pp);
 }
 
 /* Dealing with the linemap API.  */
diff --git a/gcc/jit/jit-playback.h b/gcc/jit/jit-playback.h
index 6e97b389cbb0..77ae2add4790 100644
--- a/gcc/jit/jit-playback.h
+++ b/gcc/jit/jit-playback.h
@@ -276,7 +276,7 @@ public:
   get_first_error () const;
 
   void
-  add_diagnostic (diagnostic_context *context,
+  add_diagnostic (const char *text,
 		  const diagnostic_info &diagnostic);
 
   void
diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc
new file mode 100644
index 000000000000..8dd4234ab0f4
--- /dev/null
+++ b/gcc/opts-diagnostic.cc
@@ -0,0 +1,680 @@
+/* Support for -fdiagnostics-add-output= and -fdiagnostics-set-output=.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+
+/* This file implements the options -fdiagnostics-add-output=,
+   -fdiagnostics-set-output=, and their domain-specific language.  */
+
+#include "config.h"
+#define INCLUDE_ARRAY
+#define INCLUDE_MEMORY
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "version.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-format.h"
+#include "diagnostic-format-text.h"
+#include "diagnostic-format-sarif.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+#include "pretty-print-markup.h"
+#include "opts.h"
+#include "options.h"
+#include "make-unique.h"
+
+/* A namespace for handling the DSL of the arguments of
+   -fdiagnostics-add-output= and -fdiagnostics-set-output=.  */
+
+namespace gcc {
+namespace diagnostics_output_spec {
+
+/* Decls.  */
+
+struct context
+{
+public:
+  context (const gcc_options &opts,
+	   diagnostic_context &dc,
+	   line_maps *location_mgr,
+	   location_t loc,
+	   const char *option_name)
+  : m_opts (opts), m_dc (dc), m_location_mgr (location_mgr), m_loc (loc),
+    m_option_name (option_name)
+  {}
+
+  void
+  report_error (const char *gmsgid, ...) const
+    ATTRIBUTE_GCC_DIAG(2,3);
+
+  void
+  report_unknown_key (const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &format_name,
+		      auto_vec<const char *> &known_keys) const;
+
+  void
+  report_missing_key (const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &format_name,
+		      const char *metavar) const;
+
+  diagnostic_output_file
+  open_output_file (label_text &&filename) const;
+
+  const gcc_options &m_opts;
+  diagnostic_context &m_dc;
+  line_maps *m_location_mgr;
+  location_t m_loc;
+  const char *m_option_name;
+};
+
+struct name_and_params
+{
+  std::string m_format;
+  std::vector<std::pair<std::string, std::string>> m_kvs;
+};
+
+static std::unique_ptr<name_and_params>
+parse (const context &ctxt, const char *unparsed_arg);
+
+/* Class for parsing the arguments of -fdiagnostics-add-output= and
+   -fdiagnostics-set-output=, and making diagnostic_output_format
+   instances (or issuing errors).  */
+
+class output_factory
+{
+public:
+  class handler
+  {
+  public:
+    handler (std::string name) : m_name (name) {}
+    virtual ~handler () {}
+
+    const std::string &get_name () const { return m_name; }
+
+    virtual std::unique_ptr<diagnostic_output_format>
+    make_sink (const context &ctxt,
+	       const char *unparsed_arg,
+	       const name_and_params &parsed_arg) const = 0;
+
+  protected:
+    bool
+    parse_bool_value (const context &ctxt,
+		      const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &value,
+		      bool &out) const
+    {
+      if (value == "yes")
+	{
+	  out = true;
+	  return true;
+	}
+      else if (value == "no")
+	{
+	  out = false;
+	  return true;
+	}
+      else
+	{
+	  ctxt.report_error
+	    ("%<%s%s%>:"
+	     " unexpected value %qs for key %qs; expected %qs or %qs",
+	     ctxt.m_option_name, unparsed_arg,
+	     value.c_str (),
+	     key.c_str (),
+	     "yes", "no");
+
+	  return false;
+	}
+    }
+    template <typename EnumType, size_t NumValues>
+    bool
+    parse_enum_value (const context &ctxt,
+		      const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &value,
+		      const std::array<std::pair<const char *, EnumType>, NumValues> &value_names,
+		      EnumType &out) const
+    {
+      for (auto &iter : value_names)
+	if (value == iter.first)
+	  {
+	    out = iter.second;
+	    return true;
+	  }
+
+      auto_vec<const char *> known_values;
+      for (auto iter : value_names)
+	known_values.safe_push (iter.first);
+      pp_markup::comma_separated_quoted_strings e (known_values);
+      ctxt.report_error
+	("%<%s%s%>:"
+	 " unexpected value %qs for key %qs; known values: %e",
+	 ctxt.m_option_name, unparsed_arg,
+	 value.c_str (),
+	 key.c_str (),
+	 &e);
+      return false;
+    }
+
+  private:
+    const std::string m_name;
+  };
+
+  output_factory ();
+
+  std::unique_ptr<diagnostic_output_format>
+  make_sink (const context &ctxt,
+	     const char *unparsed_arg,
+	     const name_and_params &parsed_arg);
+
+  const handler *get_handler (std::string name);
+
+private:
+  std::vector<std::unique_ptr<handler>> m_handlers;
+};
+
+class text_handler : public output_factory::handler
+{
+public:
+  text_handler () : handler ("text") {}
+
+  std::unique_ptr<diagnostic_output_format>
+  make_sink (const context &ctxt,
+	     const char *unparsed_arg,
+	     const name_and_params &parsed_arg) const final override;
+};
+
+class sarif_handler : public output_factory::handler
+{
+public:
+  sarif_handler () : handler ("sarif") {}
+
+  std::unique_ptr<diagnostic_output_format>
+  make_sink (const context &ctxt,
+	     const char *unparsed_arg,
+	     const name_and_params &parsed_arg) const final override;
+};
+
+/* struct context.  */
+
+void
+context::report_error (const char *gmsgid, ...) const
+{
+  m_dc.begin_group ();
+  va_list ap;
+  va_start (ap, gmsgid);
+  rich_location richloc (m_location_mgr, m_loc);
+  m_dc.diagnostic_impl (&richloc, nullptr, -1, gmsgid, &ap, DK_ERROR);
+  va_end (ap);
+  m_dc.end_group ();
+}
+
+void
+context::report_unknown_key (const char *unparsed_arg,
+			     const std::string &key,
+			     const std::string &format_name,
+			     auto_vec<const char *> &known_keys) const
+{
+  pp_markup::comma_separated_quoted_strings e (known_keys);
+  report_error
+    ("%<%s%s%>:"
+     " unknown key %qs for format %qs; known keys: %e",
+     m_option_name, unparsed_arg,
+     key.c_str (), format_name.c_str (), &e);
+}
+
+void
+context::report_missing_key (const char *unparsed_arg,
+			     const std::string &key,
+			     const std::string &format_name,
+			     const char *metavar) const
+{
+  report_error
+    ("%<%s%s%>:"
+     " missing required key %qs for format %qs;"
+     " try %<%s%s:%s=%s%>",
+     m_option_name, unparsed_arg,
+     key.c_str (), format_name.c_str (),
+     m_option_name, format_name.c_str (), key.c_str (), metavar);
+}
+
+std::unique_ptr<name_and_params>
+parse (const context &ctxt, const char *unparsed_arg)
+{
+  name_and_params result;
+  if (const char *const colon = strchr (unparsed_arg, ':'))
+    {
+      result.m_format = std::string (unparsed_arg, colon - unparsed_arg);
+      /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc  .*/
+      const char *iter = colon + 1;
+      const char *last_separator = ":";
+      while (iter)
+	{
+	  /* Look for a non-empty key string followed by '='.  */
+	  const char *eq = strchr (iter, '=');
+	  if (eq == nullptr || eq == iter)
+	    {
+	      /* Missing '='.  */
+	      ctxt.report_error
+		("%<%s%s%>:"
+		 " expected KEY=VALUE-style parameter for format %qs"
+		 " after %qs;"
+		 " got %qs",
+		 ctxt.m_option_name, unparsed_arg,
+		 result.m_format.c_str (),
+		 last_separator,
+		 iter);
+	      return nullptr;
+	    }
+	  std::string key = std::string (iter, eq - iter);
+	  std::string value;
+	  const char *comma = strchr (iter, ',');
+	  if (comma)
+	    {
+	      value = std::string (eq + 1, comma - (eq + 1));
+	      iter = comma + 1;
+	      last_separator = ",";
+	    }
+	  else
+	    {
+	      value = std::string (eq + 1);
+	      iter = nullptr;
+	    }
+	  result.m_kvs.push_back ({std::move (key), std::move (value)});
+	}
+    }
+  else
+    result.m_format = unparsed_arg;
+  return ::make_unique<name_and_params> (std::move (result));
+}
+
+/* class output_factory::handler.  */
+
+/* class output_factory.  */
+
+output_factory::output_factory ()
+{
+  m_handlers.push_back (::make_unique<text_handler> ());
+  m_handlers.push_back (::make_unique<sarif_handler> ());
+}
+
+const output_factory::handler *
+output_factory::get_handler (std::string name)
+{
+  for (auto &iter : m_handlers)
+    if (iter->get_name () == name)
+      return iter.get ();
+  return nullptr;
+}
+
+std::unique_ptr<diagnostic_output_format>
+output_factory::make_sink (const context &ctxt,
+			   const char *unparsed_arg,
+			   const name_and_params &parsed_arg)
+{
+  auto handler = get_handler (parsed_arg.m_format);
+  if (!handler)
+    {
+      auto_vec<const char *> strings;
+      for (auto &iter : m_handlers)
+	strings.safe_push (iter->get_name ().c_str ());
+      pp_markup::comma_separated_quoted_strings e (strings);
+      ctxt.report_error ("%<%s%s%>:"
+			 " unrecognized format %qs; known formats: %e",
+			 ctxt.m_option_name, unparsed_arg,
+			 parsed_arg.m_format.c_str (), &e);
+      return nullptr;
+    }
+
+  return handler->make_sink (ctxt, unparsed_arg, parsed_arg);
+}
+
+/* class text_handler : public output_factory::handler.  */
+
+std::unique_ptr<diagnostic_output_format>
+text_handler::make_sink (const context &ctxt,
+			 const char *unparsed_arg,
+			 const name_and_params &parsed_arg) const
+{
+  bool show_color = pp_show_color (ctxt.m_dc.get_reference_printer ());
+  for (auto& iter : parsed_arg.m_kvs)
+    {
+      const std::string &key = iter.first;
+      const std::string &value = iter.second;
+      if (key == "color")
+	{
+	  if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_color))
+	    return nullptr;
+	  continue;
+	}
+
+      /* Key not found.  */
+      auto_vec<const char *> known_keys;
+      known_keys.safe_push ("color");
+      ctxt.report_unknown_key (unparsed_arg, key, get_name (), known_keys);
+      return nullptr;
+    }
+
+  std::unique_ptr<diagnostic_output_format> sink;
+  sink = ::make_unique<diagnostic_text_output_format> (ctxt.m_dc);
+  return sink;
+}
+
+diagnostic_output_file
+context::open_output_file (label_text &&filename) const
+{
+  FILE *outf = fopen (filename.get (), "w");
+  if (!outf)
+    {
+      rich_location richloc (m_location_mgr, m_loc);
+      m_dc.emit_diagnostic_with_group
+	(DK_ERROR, richloc, nullptr, 0,
+	 "unable to open %qs: %m", filename.get ());
+      return diagnostic_output_file (nullptr, false, std::move (filename));
+    }
+  return diagnostic_output_file (outf, true, std::move (filename));
+}
+
+/* class sarif_handler : public output_factory::handler.  */
+
+std::unique_ptr<diagnostic_output_format>
+sarif_handler::make_sink (const context &ctxt,
+			  const char *unparsed_arg,
+			  const name_and_params &parsed_arg) const
+{
+  enum sarif_version version = sarif_version::v2_1_0;
+  label_text filename;
+  for (auto& iter : parsed_arg.m_kvs)
+    {
+      const std::string &key = iter.first;
+      const std::string &value = iter.second;
+      if (key == "version")
+	{
+	  static const std::array<std::pair<const char *, enum sarif_version>,
+				  (size_t)sarif_version::num_versions> value_names
+	    {{{"2.1", sarif_version::v2_1_0},
+	      {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08}}};
+
+	    if (!parse_enum_value<enum sarif_version> (ctxt, unparsed_arg,
+						       key, value,
+						       value_names,
+						       version))
+	    return nullptr;
+	  continue;
+	}
+      if (key == "file")
+	{
+	  filename = label_text::take (xstrdup (value.c_str ()));
+	  continue;
+	}
+
+      /* Key not found.  */
+      auto_vec<const char *> known_keys;
+      known_keys.safe_push ("file");
+      known_keys.safe_push ("version");
+      ctxt.report_unknown_key (unparsed_arg, key, get_name (), known_keys);
+      return nullptr;
+    }
+
+  diagnostic_output_file output_file;
+  if (filename.get ())
+    output_file = ctxt.open_output_file (std::move (filename));
+  else
+    // Default filename
+    {
+      const char *basename = (ctxt.m_opts.x_dump_base_name
+			      ? ctxt.m_opts.x_dump_base_name
+			      : ctxt.m_opts.x_main_input_basename);
+      output_file = diagnostic_output_format_open_sarif_file (ctxt.m_dc,
+							      line_table,
+							      basename);
+    }
+  if (!output_file)
+    return nullptr;
+
+  auto sink = make_sarif_sink (ctxt.m_dc,
+			       *line_table,
+			       ctxt.m_opts.x_main_input_filename,
+			       version,
+			       std::move (output_file));
+  return sink;
+}
+
+} // namespace diagnostics_output_spec
+} // namespace gcc
+
+void
+handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc)
+{
+  gcc_assert (arg);
+  gcc_assert (line_table);
+
+  const char *const option_name = "-fdiagnostics-add-output=";
+  gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc,
+					      option_name);
+  auto result = gcc::diagnostics_output_spec::parse (ctxt, arg);
+  if (!result)
+    return;
+
+  gcc::diagnostics_output_spec::output_factory factory;
+  auto sink = factory.make_sink (ctxt, arg, *result);
+  if (!sink)
+    return;
+
+  dc.add_sink (std::move (sink));
+}
+
+void
+handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc)
+{
+  gcc_assert (arg);
+  gcc_assert (line_table);
+
+  const char *const option_name = "-fdiagnostics-set-output=";
+  gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc,
+					      option_name);
+  auto result = gcc::diagnostics_output_spec::parse (ctxt, arg);
+  if (!result)
+    return;
+
+  gcc::diagnostics_output_spec::output_factory factory;
+  auto sink = factory.make_sink (ctxt, arg, *result);
+  if (!sink)
+    return;
+
+  dc.set_output_format (std::move (sink));
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* RAII class to temporarily override "progname" to the
+   string "PROGNAME".  */
+
+class auto_fix_progname
+{
+public:
+  auto_fix_progname ()
+  {
+    m_old_progname = progname;
+    progname = "PROGNAME";
+  }
+
+  ~auto_fix_progname ()
+  {
+    progname = m_old_progname;
+  }
+
+private:
+  const char *m_old_progname;
+};
+
+struct parser_test
+{
+  parser_test ()
+  : m_opts (),
+    m_dc (),
+    m_ctxt (m_opts, m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION="),
+    m_fmt (m_dc.get_output_format (0))
+  {
+    pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
+  }
+
+  std::unique_ptr<gcc::diagnostics_output_spec::name_and_params>
+  parse (const char *unparsed_arg)
+  {
+    return gcc::diagnostics_output_spec::parse (m_ctxt, unparsed_arg);
+  }
+
+  bool execution_failed_p () const
+  {
+    return m_dc.execution_failed_p ();
+  }
+
+  const char *
+  get_diagnostic_text () const
+  {
+    return pp_formatted_text (m_fmt.get_printer ());
+  }
+
+private:
+  const gcc_options m_opts;
+  test_diagnostic_context m_dc;
+  gcc::diagnostics_output_spec::context m_ctxt;
+  diagnostic_output_format &m_fmt;
+};
+
+/* Selftests.  */
+
+static void
+test_output_arg_parsing ()
+{
+  auto_fix_quotes fix_quotes;
+  auto_fix_progname fix_progname;
+
+  /* Minimal correct example.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo");
+    ASSERT_EQ (result->m_format, "foo");
+    ASSERT_EQ (result->m_kvs.size (), 0);
+    ASSERT_FALSE (pt.execution_failed_p ());
+  }
+
+  /* Stray trailing colon with no key/value pairs.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `:';"
+		  " got `'\n");
+  }
+
+  /* No key before '='.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:=");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:=':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `:';"
+		  " got `='\n");
+  }
+
+  /* No value for key.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:key,");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:key,':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `:';"
+		  " got `key,'\n");
+  }
+
+  /* Correct example, with one key/value pair.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:key=value");
+    ASSERT_EQ (result->m_format, "foo");
+    ASSERT_EQ (result->m_kvs.size (), 1);
+    ASSERT_EQ (result->m_kvs[0].first, "key");
+    ASSERT_EQ (result->m_kvs[0].second, "value");
+    ASSERT_FALSE (pt.execution_failed_p ());
+  }
+
+  /* Stray trailing comma.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:key=value,");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:key=value,':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `,';"
+		  " got `'\n");
+  }
+
+  /* Correct example, with two key/value pairs.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:color=red,shape=circle");
+    ASSERT_EQ (result->m_format, "foo");
+    ASSERT_EQ (result->m_kvs.size (), 2);
+    ASSERT_EQ (result->m_kvs[0].first, "color");
+    ASSERT_EQ (result->m_kvs[0].second, "red");
+    ASSERT_EQ (result->m_kvs[1].first, "shape");
+    ASSERT_EQ (result->m_kvs[1].second, "circle");
+    ASSERT_FALSE (pt.execution_failed_p ());
+  }
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+opts_diagnostic_cc_tests ()
+{
+  test_output_arg_parsing ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index 48cc21e31a48..95fc16bb3232 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -59,4 +59,15 @@ private:
   void *m_opts;
 };
 
+extern void
+handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc);
+
+extern void
+handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc);
 #endif
diff --git a/gcc/opts-global.cc b/gcc/opts-global.cc
index 9593ec8a8fde..cbf4018b1202 100644
--- a/gcc/opts-global.cc
+++ b/gcc/opts-global.cc
@@ -258,7 +258,7 @@ init_options_once (void)
   initial_lang_mask = lang_hooks.option_lang_mask ();
 
   const bool show_highlight_colors
-    = pp_show_highlight_colors (global_dc->m_printer);
+    = pp_show_highlight_colors (global_dc->get_reference_printer ());
 
   lang_hooks.initialize_diagnostics (global_dc);
   /* ??? Ideally, we should do this earlier and the FEs will override
@@ -269,6 +269,7 @@ init_options_once (void)
 
   diagnostic_color_init (global_dc);
   diagnostic_urls_init (global_dc);
+  global_dc->refresh_output_sinks ();
 }
 
 /* Decode command-line options to an array, like
diff --git a/gcc/opts.cc b/gcc/opts.cc
index e810e30961b9..24d83393a57e 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -2934,7 +2934,7 @@ common_handle_option (struct gcc_options *opts,
       break;
 
     case OPT_fdiagnostics_show_location_:
-      diagnostic_prefixing_rule (dc) = (diagnostic_prefixing_rule_t) value;
+      dc->set_prefixing_rule ((diagnostic_prefixing_rule_t) value);
       break;
 
     case OPT_fdiagnostics_show_caret:
@@ -2973,6 +2973,14 @@ common_handle_option (struct gcc_options *opts,
 	  break;
 	}
 
+    case OPT_fdiagnostics_add_output_:
+      handle_OPT_fdiagnostics_add_output_ (*opts, *dc, arg, loc);
+      break;
+
+    case OPT_fdiagnostics_set_output_:
+      handle_OPT_fdiagnostics_set_output_ (*opts, *dc, arg, loc);
+      break;
+
     case OPT_fdiagnostics_text_art_charset_:
       dc->set_text_art_charset ((enum diagnostic_text_art_charset)value);
       break;
@@ -3057,7 +3065,7 @@ common_handle_option (struct gcc_options *opts,
       break;
 
     case OPT_fmessage_length_:
-      pp_set_line_maximum_length (dc->m_printer, value);
+      pp_set_line_maximum_length (dc->get_reference_printer (), value);
       diagnostic_set_caret_max_width (dc, value);
       break;
 
diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc
index a066fe20066b..a9118b55f18f 100644
--- a/gcc/selftest-diagnostic.cc
+++ b/gcc/selftest-diagnostic.cc
@@ -37,7 +37,7 @@ namespace selftest {
 test_diagnostic_context::test_diagnostic_context ()
 {
   diagnostic_initialize (this, 0);
-  pp_show_color (m_printer) = false;
+  pp_show_color (get_reference_printer ()) = false;
   m_source_printing.enabled = true;
   m_source_printing.show_labels_p = true;
   m_show_column = true;
@@ -86,10 +86,11 @@ test_diagnostic_context::report (diagnostic_t kind,
 const char *
 test_diagnostic_context::test_show_locus (rich_location &richloc)
 {
-  gcc_assert (m_printer);
+  pretty_printer *pp = get_reference_printer ();
+  gcc_assert (pp);
   diagnostic_source_print_policy source_policy (*this);
-  source_policy.print (*m_printer, richloc, DK_ERROR, nullptr);
-  return pp_formatted_text (m_printer);
+  source_policy.print (*pp, richloc, DK_ERROR, nullptr);
+  return pp_formatted_text (pp);
 }
 
 } // namespace selftest
diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc
index d6c88f864ba7..6085e334b917 100644
--- a/gcc/selftest-run-tests.cc
+++ b/gcc/selftest-run-tests.cc
@@ -106,6 +106,7 @@ selftest::run_tests ()
   diagnostic_path_cc_tests ();
   simple_diagnostic_path_cc_tests ();
   attribs_cc_tests ();
+  opts_diagnostic_cc_tests ();
 
   /* This one relies on most of the above.  */
   function_tests_cc_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 5afc9399c619..5109128f7f30 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -241,6 +241,7 @@ extern void input_cc_tests ();
 extern void json_cc_tests ();
 extern void optinfo_emit_json_cc_tests ();
 extern void opts_cc_tests ();
+extern void opts_diagnostic_cc_tests ();
 extern void ordered_hash_map_tests_cc_tests ();
 extern void predict_cc_tests ();
 extern void pretty_print_cc_tests ();
diff --git a/gcc/simple-diagnostic-path.cc b/gcc/simple-diagnostic-path.cc
index 0592080f3ae4..6414cf2c82e8 100644
--- a/gcc/simple-diagnostic-path.cc
+++ b/gcc/simple-diagnostic-path.cc
@@ -226,7 +226,7 @@ simple_diagnostic_path_cc_tests ()
 {
   /* In a few places we use the global dc's printer to determine
      colorization so ensure this off during the tests.  */
-  pretty_printer *global_pp = global_dc->m_printer;
+  pretty_printer *global_pp = global_dc->get_reference_printer ();
   const bool saved_show_color = pp_show_color (global_pp);
   pp_show_color (global_pp) = false;
 
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
index 18153b0733c4..467af16c3d1e 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
@@ -529,7 +529,7 @@ dump_refcnt_info (const hash_map<const region *, int> &region_to_refcnt,
   region_model_manager *mgr = model->get_manager ();
   pretty_printer pp;
   pp_format_decoder (&pp) = default_tree_printer;
-  pp_show_color (&pp) = pp_show_color (global_dc->m_printer);
+  pp_show_color (&pp) = pp_show_color (global_dc->get_reference_printer ());
   pp.set_output_stream (stderr);
 
   for (const auto &region_refcnt : region_to_refcnt)
diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c
index b5328060c394..1c4cfbe07239 100644
--- a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c
+++ b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c
@@ -1,7 +1,7 @@
 /* Test of an ICE triggered within a header file with SARIF 2.2  */
 
 /* { dg-do compile } */
-/* { dg-options "-fdiagnostics-format=sarif-file-2.2-prerelease" } */
+/* { dg-options "-fdiagnostics-set-output=sarif:version=2.2-prerelease" } */
 /* { dg-additional-options "-fno-report-bug" } */
 
 #include "crash-test-ice-in-header.h"  /* { dg-ice "" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
index f7952e7d1d12..954538f962bb 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -147,7 +147,7 @@ example_1 ()
     {
       auto_diagnostic_group d;
       gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
-      simple_diagnostic_path path (global_dc->m_printer);
+      simple_diagnostic_path path (global_dc->get_reference_printer ());
       diagnostic_event_id_t alloc_event_id
 	= path.add_event (gimple_location (call_to_PyList_New),
 			  example_a_fun->decl, 0,
@@ -335,7 +335,7 @@ example_2 ()
       auto_diagnostic_group d;
 
       gcc_rich_location richloc (call_to_free.m_loc);
-      test_diagnostic_path path (global_dc->m_printer);
+      test_diagnostic_path path (global_dc->get_reference_printer ());
       path.add_entry (entry_to_test, 0, "test");
       path.add_call (call_to_make_boxed_int, 0,
 		     entry_to_make_boxed_int, "make_boxed_int");
@@ -420,7 +420,7 @@ example_3 ()
       auto_diagnostic_group d;
 
       gcc_rich_location richloc (call_to_fprintf.m_loc);
-      test_diagnostic_path path (global_dc->m_printer);
+      test_diagnostic_path path (global_dc->get_reference_printer ());
       path.add_entry (entry_to_test, 1, "test");
       path.add_call (call_to_register_handler, 1,
 		     entry_to_register_handler, "register_handler");
@@ -495,7 +495,7 @@ example_4 ()
       auto_diagnostic_group d;
 
       gcc_rich_location richloc (call_to_acquire_lock_a_in_bar.m_loc);
-      test_diagnostic_path path (global_dc->m_printer);
+      test_diagnostic_path path (global_dc->get_reference_printer ());
       diagnostic_thread_id_t thread_1 = path.add_thread ("Thread 1");
       diagnostic_thread_id_t thread_2 = path.add_thread ("Thread 2");
       path.add_entry (entry_to_foo, 0, "foo", thread_1);
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
index 2f95e4a0ceb8..aa03b7d5d2cc 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
@@ -316,13 +316,18 @@ public:
 
   const xml::document &get_document () const { return *m_document; }
 
+  void set_printer (pretty_printer &pp)
+  {
+    m_printer = &pp;
+  }
+
 private:
   std::unique_ptr<xml::element>
   make_element_for_diagnostic (const diagnostic_info &diagnostic,
 			       diagnostic_t orig_diag_kind);
 
   diagnostic_context &m_context;
-  pretty_printer &m_printer;
+  pretty_printer *m_printer;
   const line_maps *m_line_maps;
 
   std::unique_ptr<xml::document> m_document;
@@ -400,7 +405,7 @@ xhtml_builder::xhtml_builder (diagnostic_context &context,
 			      pretty_printer &pp,
 			      const line_maps *line_maps)
 : m_context (context),
-  m_printer (pp),
+  m_printer (&pp),
   m_line_maps (line_maps)
 {
   gcc_assert (m_line_maps);
@@ -565,10 +570,10 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
 
   auto message_span = make_span (label_text::borrow ("gcc-message"));
   xhtml_token_printer tok_printer (*this, *message_span.get ());
-  m_printer.set_token_printer (&tok_printer);
-  pp_output_formatted_text (&m_printer, m_context.get_urlifier ());
-  m_printer.set_token_printer (nullptr);
-  pp_clear_output_area (&m_printer);
+  m_printer->set_token_printer (&tok_printer);
+  pp_output_formatted_text (m_printer, m_context.get_urlifier ());
+  m_printer->set_token_printer (nullptr);
+  pp_clear_output_area (m_printer);
   diag_element->add_child (std::move (message_span));
 
   if (diagnostic.metadata)
@@ -626,10 +631,10 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
     pre->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
     // TODO: ideally we'd like to capture elements within the following:
     diagnostic_show_locus (&m_context, diagnostic.richloc, diagnostic.kind,
-			   &m_printer);
+			   m_printer);
     pre->add_text
-      (label_text::take (xstrdup (pp_formatted_text (&m_printer))));
-    pp_clear_output_area (&m_printer);
+      (label_text::take (xstrdup (pp_formatted_text (m_printer))));
+    pp_clear_output_area (m_printer);
     diag_element->add_child (std::move (pre));
   }
 
@@ -723,6 +728,23 @@ public:
   {
     /* No-op, but perhaps could show paths here.  */
   }
+  bool follows_reference_printer_p () const final override
+  {
+    return false;
+  }
+  void update_printer () final override
+  {
+    m_printer = m_context.clone_printer ();
+
+    /* Don't colorize the text.  */
+    pp_show_color (m_printer.get ()) = false;
+
+    /* No textual URLs.  */
+    m_printer->set_url_format (URL_FORMAT_NONE);
+
+    /* Update the builder to use the new printer.  */
+    m_builder.set_printer (*get_printer ());
+  }
 
   const xml::document &get_document () const
   {
diff --git a/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c b/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c
index 554dad6fa35a..e96efe0bed50 100644
--- a/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c
@@ -48,7 +48,7 @@ test_richloc (rich_location *richloc)
 {
   /* Run the diagnostic and fix-it printing code.  */
   test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, richloc, DK_ERROR, dc.m_printer);
+  diagnostic_show_locus (&dc, richloc, DK_ERROR, dc.get_reference_printer ());
 
   /* Generate a diff.  */
   edit_context ec (global_dc->get_file_cache ());
diff --git a/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c b/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c
new file mode 100644
index 000000000000..ce6505824882
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c
@@ -0,0 +1,16 @@
+/* Verify using -fdiagnostics-add-output=sarif with the defaults.  */
+
+/* { dg-do compile } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif" } */
+
+/* Verify that SARIF output can capture secondary locations
+   relating to a diagnostic.  */
+
+int missing_semicolon (void)
+{
+  return 42 /* { dg-error "expected ';' before '.' token" } */
+}
+
+/* Verify that JSON was written to the output file with the
+   expected version and expected name:
+   { dg-final { verify-sarif-file "2.1" }  } */
diff --git a/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c
new file mode 100644
index 000000000000..1d87bb81e976
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c
@@ -0,0 +1,30 @@
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-add-output=sarif" } */
+
+struct s {};
+struct t {};
+
+typedef struct s S;
+typedef struct t T;
+
+extern S callee_4a (void);
+extern T callee_4b (void);
+
+int test_4 (void)
+{
+  return callee_4a () + callee_4b (); /* { dg-error "invalid operands to binary \+" } */
+
+/* { dg-begin-multiline-output "" }
+   return callee_4a () + callee_4b ();
+          ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~
+          |              |
+          |              T {aka struct t}
+          S {aka struct s}
+   { dg-end-multiline-output "" } */
+}
+
+/* Verify that some JSON was written to a file with the expected name.  */
+/* { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+   .sarif file:
+   { dg-final { run-sarif-pytest bad-binary-op.c "bad-binary-op.py" } } */
diff --git a/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py
new file mode 100644
index 000000000000..fe139e62e417
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py
@@ -0,0 +1,70 @@
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+    return sarif_from_env()
+
+def test_basics(sarif):
+    schema = sarif['$schema']
+    assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"
+
+    version = sarif['version']
+    assert version == "2.1.0"
+
+def test_execution_unsuccessful(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+
+    invocations = run['invocations']
+    assert len(invocations) == 1
+    invocation = invocations[0]
+
+    # We expect the 'error' to make executionSuccessful be false
+    assert invocation['executionSuccessful'] == False
+
+def test_error_location(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+    results = run['results']
+
+    # We expect a single error with annotations.
+    #
+    # The textual form of the diagnostic looks like this:
+    # . PATH/bad-binary-ops.c: In function 'test_4':
+    # . PATH/bad-binary-ops.c:64:23: error: invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'})
+    # .    return callee_4a () + callee_4b (); /* { dg-error "invalid operands to binary \+" } */
+    # .           ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~
+    # .           |              |
+    # .           |              T {aka struct t}
+    # .           S {aka struct s}
+    assert len(results) == 1
+
+    result = results[0]
+    assert result['level'] == 'error'
+
+    assert result['message']['text'] \
+        == "invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'})"
+    locations = result['locations']
+    assert len(locations) == 1
+
+    location = locations[0]
+    assert get_location_artifact_uri(location).endswith('bad-binary-op.c')
+    assert get_location_snippet_text(location) \
+        == "  return callee_4a () + callee_4b (); /* { dg-error \"invalid operands to binary \\+\" } */\n"
+    EXPECTED_LINE = 14
+    assert get_location_physical_region(location)['startLine'] == EXPECTED_LINE
+    assert get_location_physical_region(location)['startColumn'] == 23
+    assert get_location_physical_region(location)['endColumn'] == 24
+
+    annotations = location['annotations']
+    assert len(annotations) == 2
+    assert annotations[0]['startLine'] == EXPECTED_LINE
+    assert annotations[0]['startColumn'] == 10
+    assert annotations[0]['endColumn'] == 22
+    assert annotations[0]['message']['text'] == "S {aka struct s}"
+    assert annotations[1]['startLine'] == EXPECTED_LINE
+    assert annotations[1]['startColumn'] == 25
+    assert annotations[1]['endColumn'] == 37
+    assert annotations[1]['message']['text'] == "T {aka struct t}"
diff --git a/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c
new file mode 100644
index 000000000000..6a673a5f8083
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c
@@ -0,0 +1,27 @@
+/* Verify that we can output multiple different versions of SARIF
+   with -fdiagnostics-add-output (specifying version and filename),
+   as well as usual text output.  */
+
+/* { dg-do compile } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=multiple-outputs-c.2.1.sarif,version=2.1" } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=multiple-outputs-c.2.2.sarif,version=2.2-prerelease" } */
+
+/* Verify that SARIF output can capture secondary locations
+   relating to a diagnostic.  */
+
+int missing_semicolon (void)
+{
+  return 42 /* { dg-error "expected ';' before '.' token" } */
+}
+
+/* Verify that JSON was written to the output files with the
+   expected version and expected names:
+   { dg-final { verify-sarif-file "2.1" "multiple-outputs-c.2.1.sarif" }  }
+   { dg-final { verify-sarif-file "2.2" "multiple-outputs-c.2.2.sarif" }  }
+*/
+
+/* Use a Python script to verify various properties about the generated
+   .sarif files:
+   { dg-final { run-sarif-pytest multiple-outputs-c.2.1 "multiple-outputs.py" } }
+   { dg-final { run-sarif-pytest multiple-outputs-c.2.2 "multiple-outputs.py" } }
+*/
diff --git a/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py
new file mode 100644
index 000000000000..8febfac4c7bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py
@@ -0,0 +1,50 @@
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+    return sarif_from_env()
+
+def test_execution_unsuccessful(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+
+    invocations = run['invocations']
+    assert len(invocations) == 1
+    invocation = invocations[0]
+
+    # We expect the 'error' to make executionSuccessful be false
+    assert invocation['executionSuccessful'] == False
+
+def test_result(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+    results = run['results']
+
+    # We expect a single error with a secondary location and a fix-it hint.
+    #
+    # The textual form of the diagnostic would look like this:
+    #  . PATH/missing-semicolon.c: In function 'missing_semicolon':
+    #  . PATH/missing-semicolon.c:19:12: error: expected ';' before '}' token
+    #  .    19 |   return 42
+    #  .       |            ^
+    #  .       |            ;
+    #  .    20 | }
+    #  .       | ~           
+    assert len(results) == 1
+    
+    result = results[0]
+    assert result['level'] == 'error'
+    assert result['message']['text'] == "expected ';' before '}' token"
+    locations = result['locations']
+    assert len(locations) == 1
+
+    assert len(result['fixes']) == 1
+    assert len(result['fixes'][0]['artifactChanges']) == 1
+    change = result['fixes'][0]['artifactChanges'][0]
+    assert len(change['replacements']) == 1
+    replacement = change['replacements'][0]
+    assert replacement['deletedRegion']['startColumn'] == 12
+    assert replacement['deletedRegion']['endColumn'] == 12
+    assert replacement['insertedContent']['text'] == ';'
diff --git a/gcc/testsuite/lib/scansarif.exp b/gcc/testsuite/lib/scansarif.exp
index f27f3ee2164b..91801fc1157e 100644
--- a/gcc/testsuite/lib/scansarif.exp
+++ b/gcc/testsuite/lib/scansarif.exp
@@ -63,17 +63,24 @@ proc scan-sarif-file-not { args } {
 # The first argument is the version of the SARIF schema to validate against
 # If present can be "2.1" or "2.2"
 # If absent, validate against 2.1
+#
+# If present, the second argument is the expected filename of the .sarif file
 
 proc verify-sarif-file { args } {
     global srcdir subdir
 
     set testcase [testname-for-summary]
     set filename [lindex $testcase 0]
-    set output_file "[file tail $filename].sarif"
 
     set version [lindex $args 0]
     verbose "sarif version: $version" 2
 
+    set output_file [lindex $args 1]
+    verbose "output_file: $output_file" 2
+    if { $output_file == "" } {
+	set output_file "[file tail $filename].sarif"
+    }
+
     if { ![check_effective_target_recent_python3] } {
 	unsupported "$testcase verify-sarif-file: python3 is missing"
 	return
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index c60616bccdb6..779049674b4f 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -231,7 +231,7 @@ announce_function (tree decl)
 	fprintf (stderr, " %s",
 		 identifier_to_locale (lang_hooks.decl_printable_name (decl, 2)));
       fflush (stderr);
-      pp_needs_newline (global_dc->m_printer) = true;
+      pp_needs_newline (global_dc->get_reference_printer ()) = true;
       diagnostic_set_last_function (global_dc, (diagnostic_info *) NULL);
     }
 }
@@ -2389,7 +2389,7 @@ toplev::main (int argc, char **argv)
   if (auto edit_context_ptr = global_dc->get_edit_context ())
     {
       pretty_printer pp;
-      pp_show_color (&pp) = pp_show_color (global_dc->m_printer);
+      pp_show_color (&pp) = pp_show_color (global_dc->get_reference_printer ());
       edit_context_ptr->print_diff (&pp, true);
       pp_flush (&pp);
     }
diff --git a/gcc/tree-diagnostic.cc b/gcc/tree-diagnostic.cc
index b7d2c51c984c..84e7ae7721fc 100644
--- a/gcc/tree-diagnostic.cc
+++ b/gcc/tree-diagnostic.cc
@@ -180,7 +180,7 @@ tree_diagnostics_defaults (diagnostic_context *context)
 {
   diagnostic_text_starter (context) = default_tree_diagnostic_text_starter;
   diagnostic_text_finalizer (context) = default_diagnostic_text_finalizer;
-  diagnostic_format_decoder (context) = default_tree_printer;
+  context->set_format_decoder (default_tree_printer);
   context->set_set_locations_callback (set_inlining_locations);
   context->set_client_data_hooks (make_compiler_data_hooks ());
 }
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index dbc8ef6ae013..aee780f4b8e3 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -66,7 +66,7 @@ public:
   {
     pp_format_decoder (this) = default_tree_printer;
     if (outf == stderr)
-      pp_show_color (this) = pp_show_color (global_dc->m_printer);
+      pp_show_color (this) = pp_show_color (global_dc->get_reference_printer ());
     set_output_stream (outf);
   }
   ~tree_dump_pretty_printer ()
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 624c50db165f..45f0474f9e38 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -12429,7 +12429,7 @@ escaped_string::escape (const char *unescaped)
 	  continue;
 	}
 
-      if (c != '\n' || !pp_is_wrapping_line (global_dc->m_printer))
+      if (c != '\n' || !pp_is_wrapping_line (global_dc->get_reference_printer ()))
 	{
 	  if (escaped == NULL)
 	    {
@@ -15901,7 +15901,7 @@ test_escaped_strings (void)
   ASSERT_STREQ ("foobar", (const char *) msg);
 
   /* Ensure that we have -fmessage-length set to 0.  */
-  pretty_printer *pp = global_dc->m_printer;
+  pretty_printer *pp = global_dc->get_reference_printer ();
   saved_cutoff = pp_line_cutoff (pp);
   pp_line_cutoff (pp) = 0;
 
