diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index ff77d3cdc64..8f93ae48024 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -611,7 +611,7 @@ host_xm_defines=@host_xm_defines@
 xm_file_list=@xm_file_list@
 xm_include_list=@xm_include_list@
 xm_defines=@xm_defines@
-lang_checks=
+lang_checks=check-libdiagnostics
 lang_checks_parallelized=
 lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
 lang_specs_files=@lang_specs_files@
@@ -2153,7 +2153,7 @@ all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
 	libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
 # This is what must be made before installing GCC and converting libraries.
 start.encap: native xgcc$(exeext) cpp$(exeext) specs \
-	libgcc-support lang.start.encap @GENINSRC@ srcextra
+	libgcc-support lang.start.encap libdiagnostics @GENINSRC@ srcextra
 # These can't be made until after GCC can run.
 rest.encap: lang.rest.encap
 # This is what is made with the host's compiler
@@ -2242,6 +2242,136 @@ cpp$(exeext): $(GCC_OBJS) c-family/cppspec.o libcommon-target.a $(LIBDEPS) \
 	  c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \
 	  $(EXTRA_GCC_LIBS) $(LIBS)
 
+
+libdiagnostics_OBJS = libdiagnostics.o \
+	libcommon.a
+
+# FIXME:
+# Define the names for selecting jit in LANGUAGES.
+# Note that it would be nice to move the dependency on g++
+# into the jit rule, but that needs a little bit of work
+# to do the right thing within all.cross.
+
+LIBDIAGNOSTICS_VERSION_NUM = 0
+LIBDIAGNOSTICS_MINOR_NUM = 0
+LIBDIAGNOSTICS_RELEASE_NUM = 1
+
+ifneq (,$(findstring mingw,$(target)))
+LIBDIAGNOSTICS_FILENAME = libdiagnostics-$(LIBDIAGNOSTICS_VERSION_NUM).dll
+LIBDIAGNOSTICS_IMPORT_LIB = libdiagnostics.dll.a
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(FULL_DRIVER_NAME)
+
+else
+
+ifneq (,$(findstring darwin,$(host)))
+
+LIBDIAGNOSTICS_AGE = 1
+LIBDIAGNOSTICS_BASENAME = libdiagnostics
+
+LIBDIAGNOSTICS_SONAME = \
+  ${libdir}/$(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_FILENAME = $(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_LINKER_NAME = $(LIBDIAGNOSTICS_BASENAME).dylib
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+	$(if $(LD_VERSION_SCRIPT_OPTION),\
+	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+	$(if $(LD_SONAME_OPTION), \
+	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_FILENAME)
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(LIBDIAGNOSTICS_SYMLINK) \
+	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) \
+	$(FULL_DRIVER_NAME)
+
+else
+
+LIBDIAGNOSTICS_LINKER_NAME = libdiagnostics.so
+LIBDIAGNOSTICS_SONAME = $(LIBDIAGNOSTICS_LINKER_NAME).$(LIBDIAGNOSTICS_VERSION_NUM)
+LIBDIAGNOSTICS_FILENAME = \
+  $(LIBDIAGNOSTICS_SONAME).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_RELEASE_NUM)
+
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_SONAME)
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+	$(if $(LD_VERSION_SCRIPT_OPTION),\
+	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+	$(if $(LD_SONAME_OPTION), \
+	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(LIBDIAGNOSTICS_SYMLINK) \
+	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) \
+	$(FULL_DRIVER_NAME)
+
+endif
+endif
+
+libdiagnostics.serial = $(LIBDIAGNOSTICS_FILENAME)
+
+# Tell GNU make to ignore these if they exist.
+.PHONY: libdiagnostics
+
+ifneq (,$(findstring mingw,$(target)))
+# Create import library
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,--out-implib,$(LIBDIAGNOSTICS_IMPORT_LIB)
+else
+
+ifneq (,$(findstring darwin,$(host)))
+# TODO : Construct a Darwin-style symbol export file.
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,-compatibility_version,$(LIBDIAGNOSTICS_VERSION_NUM) \
+	-Wl,-current_version,$(LIBDIAGNOSTICS_VERSION_NUM).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_AGE) \
+	$(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+	$(LIBDIAGNOSTICS_SONAME_OPTION)
+else
+
+LIBDIAGNOSTICS_EXTRA_OPTS = $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+	$(LIBDIAGNOSTICS_SONAME_OPTION)
+endif
+endif
+
+$(LIBDIAGNOSTICS_FILENAME): $(libdiagnostics_OBJS) $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+	$(LIBDEPS) $(srcdir)/libdiagnostics.map
+	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),start)
+	+$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ -shared \
+	     $(libdiagnostics_OBJS) \
+	     $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+	     $(LIBDIAGNOSTICS_EXTRA_OPTS)
+	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),end)
+
+# Create symlinks when not building for Windows
+ifeq (,$(findstring mingw,$(target)))
+
+ifeq (,$(findstring darwin,$(host)))
+# but only one level for Darwin, version info is embedded.
+$(LIBDIAGNOSTICS_SONAME_SYMLINK): $(LIBDIAGNOSTICS_FILENAME)
+	ln -sf $(LIBDIAGNOSTICS_FILENAME) $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+endif
+
+$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+	ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+endif
+
 # Dump a specs file to make -B./ read these specs over installed ones.
 $(SPECS): xgcc$(exeext)
 	$(GCC_FOR_TARGET) -dumpspecs > tmp-specs
diff --git a/gcc/configure b/gcc/configure
index 77f33ee4df6..abcb70b1b5f 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -31997,7 +31997,7 @@ $as_echo "#define ENABLE_LTO 1" >>confdefs.h
 	esac
 done
 
-check_languages=
+check_languages=check-libdiagnostics
 for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 10982cdfc09..5968670b2d6 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -7281,7 +7281,7 @@ changequote([,])dnl
 	esac
 done
 
-check_languages=
+check_languages=check-libdiagnostics
 for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
diff --git a/gcc/input.h b/gcc/input.h
index 5eac1dc40a6..e4d230cc112 100644
--- a/gcc/input.h
+++ b/gcc/input.h
@@ -113,10 +113,10 @@ class char_span
   size_t m_n_elts;
 };
 
+// FIXME: eliminate these; use global_dc->m_file_cache
 extern char_span location_get_source_line (const char *file_path, int line);
 extern char *get_source_text_between (location_t, location_t);
 extern char_span get_source_file_content (const char *file_path);
-
 extern bool location_missing_trailing_newline (const char *file_path);
 
 /* Forward decl of slot within file_cache, so that the definition doesn't
diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc
new file mode 100644
index 00000000000..788b28edc53
--- /dev/null
+++ b/gcc/libdiagnostics.cc
@@ -0,0 +1,1124 @@
+/* C++ implementation of a pure C API for emitting diagnostics.
+   Copyright (C) 2023 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/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-url.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-client-data-hooks.h"
+#include "logical-location.h"
+#include "edit-context.h"
+#include "make-unique.h"
+#include "libdiagnostics.h"
+
+class owned_nullable_string
+{
+public:
+  owned_nullable_string () : m_str (nullptr) {}
+  owned_nullable_string (const char *str)
+  : m_str (str ? ::xstrdup (str) : nullptr)
+  {
+  }
+
+  ~owned_nullable_string ()
+  {
+    free (m_str);
+  }
+
+  void set (const char *str)
+  {
+    free (m_str);
+    m_str = str ? ::xstrdup (str) : nullptr;
+  }
+
+  const char *get_str () const { return m_str; }
+
+  char *xstrdup () const
+  {
+    return m_str ? ::xstrdup (m_str) : nullptr;
+  }
+
+private:
+  char *m_str;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_file
+{
+  diagnostic_file (const char *name, const char *sarif_source_language)
+  : m_name (name), m_sarif_source_language (sarif_source_language)
+  {
+  }
+
+  const char *get_name () const { return m_name.get_str (); }
+  const char *get_sarif_source_language () const
+  {
+    return m_sarif_source_language.get_str ();
+  }
+
+private:
+  owned_nullable_string m_name;
+  owned_nullable_string m_sarif_source_language;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_logical_location : public logical_location
+{
+  diagnostic_logical_location (enum diagnostic_logical_location_kind_t kind,
+			       const diagnostic_logical_location *parent,
+			       const char *short_name,
+			       const char *fully_qualified_name,
+			       const char *decorated_name)
+  : m_kind (kind),
+    m_parent (parent),
+    m_short_name (short_name),
+    m_fully_qualified_name (fully_qualified_name),
+    m_decorated_name (decorated_name)
+  {
+  }
+
+  const char *get_short_name () const final override
+  {
+    return m_short_name.get_str ();
+  }
+  const char *get_name_with_scope () const final override
+  {
+    return m_fully_qualified_name.get_str ();
+  }
+  const char *get_internal_name () const final override
+  {
+    return m_decorated_name.get_str ();
+  }
+  enum logical_location_kind get_kind () const final override
+  {
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+	return LOGICAL_LOCATION_KIND_FUNCTION;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+	return LOGICAL_LOCATION_KIND_MEMBER;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+	return LOGICAL_LOCATION_KIND_MODULE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+	return LOGICAL_LOCATION_KIND_NAMESPACE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+	return LOGICAL_LOCATION_KIND_TYPE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+	return LOGICAL_LOCATION_KIND_RETURN_TYPE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+	return LOGICAL_LOCATION_KIND_PARAMETER;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+	return LOGICAL_LOCATION_KIND_VARIABLE;
+      }
+  }
+
+private:
+  enum diagnostic_logical_location_kind_t m_kind;
+  const diagnostic_logical_location *m_parent;
+  owned_nullable_string m_short_name;
+  owned_nullable_string m_fully_qualified_name;
+  owned_nullable_string m_decorated_name;
+};
+
+class sink
+{
+public:
+  virtual ~sink ();
+
+  void begin_group ()
+  {
+    m_dc.begin_group ();
+  }
+  void end_group ()
+  {
+    m_dc.end_group ();
+  }
+
+  void emit (diagnostic &diag, const char *msgid, va_list *args)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+protected:
+  sink (diagnostic_manager &mgr);
+
+  static char *
+  get_option_name_cb (diagnostic_context *, int, diagnostic_t, diagnostic_t)
+  {
+    return nullptr; // FIXME
+  }
+
+  diagnostic_manager &m_mgr;
+
+  /* One context per sink.  */
+  diagnostic_context m_dc;
+};
+
+class text_sink : public sink
+{
+public:
+  text_sink (diagnostic_manager &mgr,
+	     FILE *dst_stream,
+	     enum diagnostic_colorize colorize);
+
+  void
+  on_begin_text_diagnostic (diagnostic_info *info);
+
+private:
+  const diagnostic_logical_location *m_current_logical_loc;
+};
+
+class sarif_sink : public sink
+{
+public:
+  sarif_sink (diagnostic_manager &mgr,
+	      FILE *dst_stream,
+	      enum diagnostic_sarif_version version);
+};
+
+/* Helper for the linemap code.  */
+
+static size_t
+round_alloc_size (size_t s)
+{
+  return s;
+}
+
+class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks
+{
+public:
+  impl_diagnostic_client_data_hooks (diagnostic_manager &mgr)
+  : m_mgr (mgr)
+  {}
+
+  const client_version_info *get_any_version_info () const final override;
+  const logical_location *get_current_logical_location () const final override;
+  const char * maybe_get_sarif_source_language (const char *filename)
+    const final override;
+  void add_sarif_invocation_properties (sarif_object &invocation_obj)
+    const final override;
+
+private:
+  diagnostic_manager &m_mgr;
+};
+
+class impl_client_version_info : public client_version_info
+{
+public:
+  const char *get_tool_name () const final override
+  {
+    return m_name.get_str ();
+  }
+
+  char *maybe_make_full_name () const final override
+  {
+    return m_full_name.xstrdup ();
+  }
+
+  const char *get_version_string () const final override
+  {
+    return m_version.get_str ();
+  }
+
+  char *maybe_make_version_url () const final override
+  {
+    return m_version_url.xstrdup ();
+  }
+
+  void for_each_plugin (plugin_visitor &) const final override
+  {
+    // No-op.
+  }
+
+  owned_nullable_string m_name;
+  owned_nullable_string m_full_name;
+  owned_nullable_string m_version;
+  owned_nullable_string m_version_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_manager
+{
+public:
+  diagnostic_manager ()
+  : m_current_diag (nullptr)
+  {
+    linemap_init (&m_line_table, BUILTINS_LOCATION);
+    m_line_table.m_reallocator = xrealloc;
+    m_line_table.m_round_alloc_size = round_alloc_size;
+    m_line_table.default_range_bits = 5;
+  }
+  ~diagnostic_manager ()
+  {
+    /* Clean up sinks first, as they can use other fields. */
+    for (size_t i = 0; i < m_sinks.size (); i++)
+      m_sinks[i] = nullptr;
+
+    for (auto iter : m_str_to_file_map)
+      delete iter.second;
+  }
+
+  line_maps *get_line_table () { return &m_line_table; }
+  file_cache *get_file_cache () { return &m_file_cache; }
+
+  void write_patch (FILE *dst_stream);
+
+  void add_sink (std::unique_ptr<sink> sink)
+  {
+    m_sinks.push_back (std::move (sink));
+  }
+
+  void emit (diagnostic &diag, const char *msgid, va_list *args)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+  const diagnostic_file *
+  new_file (const char *name,
+	    const char *sarif_source_language)
+  {
+    if (diagnostic_file **slot = m_str_to_file_map.get (name))
+      return *slot;
+    diagnostic_file *file = new diagnostic_file (name, sarif_source_language);
+    m_str_to_file_map.put (file->get_name (), file);
+    return file;
+  }
+
+  diagnostic_location_t
+  new_location_from_file_and_line (const diagnostic_file *file,
+				   diagnostic_line_num_t linenum)
+  {
+    // FIXME: this is a hack...
+    /* Build a simple linemap describing some locations. */
+    if (LINEMAPS_ORDINARY_USED (&m_line_table) == 0)
+      linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+    else
+      {
+	const line_map *map
+	  = linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
+			 file->get_name (), 0);
+	// FIXME:
+	((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+      }
+    linemap_line_start (&m_line_table, linenum, 100);
+    return linemap_position_for_column (&m_line_table, 0);
+  }
+
+
+  diagnostic_location_t
+  new_location_from_file_line_column (const diagnostic_file *file,
+				      diagnostic_line_num_t line_num,
+				      diagnostic_column_num_t column_num)
+  {
+    // FIXME: this is a hack...
+    /* Build a simple linemap describing some locations. */
+    linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+    linemap_line_start (&m_line_table, line_num, 100);
+    return linemap_position_for_column (&m_line_table, column_num);
+  }
+
+  diagnostic_location_t
+  new_location_from_range (diagnostic_location_t loc_caret,
+			   diagnostic_location_t loc_start,
+			   diagnostic_location_t loc_end)
+  {
+    return m_line_table.make_location (loc_caret, loc_start, loc_end);
+  }
+
+  const diagnostic_logical_location *
+  new_logical_location (enum diagnostic_logical_location_kind_t kind,
+			const diagnostic_logical_location *parent,
+			const char *short_name,
+			const char *fully_qualified_name,
+			const char *decorated_name)
+  {
+    std::unique_ptr<diagnostic_logical_location> logical_loc
+      = ::make_unique<diagnostic_logical_location> (kind,
+						    parent,
+						    short_name,
+						    fully_qualified_name,
+						    decorated_name);
+    const diagnostic_logical_location *result = logical_loc.get ();
+    m_logical_locs.push_back (std::move (logical_loc));
+    return result;
+  }
+
+  void begin_group ()
+  {
+    for (auto &sink : m_sinks)
+      sink->begin_group ();
+  }
+
+  void end_group ()
+  {
+    for (auto &sink : m_sinks)
+      sink->end_group ();
+  }
+
+  const char *
+  maybe_get_sarif_source_language (const char *filename)
+  {
+    if (diagnostic_file **slot = m_str_to_file_map.get (filename))
+      {
+	gcc_assert (*slot);
+	return (*slot)->get_sarif_source_language ();
+      }
+    return nullptr;
+  }
+
+  const diagnostic *get_current_diag () { return m_current_diag; }
+
+  const client_version_info *get_client_version_info () const
+  {
+    return &m_client_version_info;
+  }
+  impl_client_version_info *get_client_version_info ()
+  {
+    return &m_client_version_info;
+  }
+
+  void
+  assert_valid_diagnostic_location_t (diagnostic_location_t loc) const
+  {
+    // TODO
+    (void)loc;
+  }
+
+  /* FIXME: Various things still used the "line_table" global variable.
+     Set it to be this diagnostic_manager's m_line_table.
+     Ideally we should eliminate this global (and this function).  */
+  void set_line_table_global ()
+  {
+    line_table = &m_line_table;
+  }
+
+private:
+  line_maps m_line_table;
+  file_cache m_file_cache;
+  impl_client_version_info m_client_version_info;
+  std::vector<std::unique_ptr<sink>> m_sinks;
+  hash_map<nofree_string_hash, diagnostic_file *> m_str_to_file_map;
+  std::vector<std::unique_ptr<diagnostic_logical_location>> m_logical_locs;
+  const diagnostic *m_current_diag;
+  edit_context m_edit_context;
+};
+
+class impl_rich_location : public rich_location
+{
+public:
+  impl_rich_location (line_maps *set)
+  : rich_location (set, UNKNOWN_LOCATION)
+  {}
+};
+
+class impl_range_label : public range_label
+{
+public:
+  impl_range_label (const char *text)
+  : m_text (xstrdup (text))
+  {}
+
+  ~impl_range_label () { free (m_text); }
+
+  label_text get_text (unsigned) const final override
+  {
+    return label_text::borrow (m_text);
+  }
+
+private:
+  char *m_text;
+};
+
+class impl_rule : public diagnostic_metadata::rule
+{
+public:
+  impl_rule (const char *title, const char *url)
+  :  m_title (title),
+     m_url (url)
+  {
+  }
+
+  char *make_description () const final override
+  {
+    return m_title.xstrdup ();
+  }
+
+  char *make_url () const final override
+  {
+    return m_url.xstrdup ();
+  }
+
+private:
+  owned_nullable_string m_title;
+  owned_nullable_string m_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic
+{
+public:
+  diagnostic (diagnostic_manager &diag_mgr,
+	      enum diagnostic_level level)
+  : m_diag_mgr (diag_mgr),
+    m_level (level),
+    m_rich_loc (diag_mgr.get_line_table ()),
+    m_logical_loc (nullptr)
+  {}
+
+  diagnostic_manager &get_manager () const
+  {
+    return m_diag_mgr;
+  }
+
+  enum diagnostic_level get_level () const { return m_level; }
+
+  rich_location *get_rich_location () { return &m_rich_loc; }
+  const diagnostic_metadata *get_metadata () { return &m_metadata; }
+
+#if 0
+  diagnostic_location_t get_location () const { return m_loc; }
+#endif
+
+  void set_cwe (unsigned cwe_id)
+  {
+    m_metadata.add_cwe (cwe_id);
+  }
+
+  void add_rule (const char *title,
+		 const char *url)
+  {
+    std::unique_ptr<impl_rule> rule = ::make_unique<impl_rule> (title, url);
+    m_metadata.add_rule (*rule.get ());
+    m_rules.push_back (std::move (rule));
+  }
+
+  void set_location (diagnostic_location_t loc)
+  {
+    m_rich_loc.set_range (0, loc, SHOW_RANGE_WITH_CARET);
+  }
+
+  void
+  add_location (diagnostic_location_t loc)
+  {
+    m_rich_loc.add_range (loc, SHOW_RANGE_WITHOUT_CARET);
+  }
+
+  void
+  add_location_with_label (diagnostic_location_t loc,
+			   const char *text)
+  {
+    std::unique_ptr<range_label> label = ::make_unique <impl_range_label> (text);
+    m_rich_loc.add_range (loc,
+			  SHOW_RANGE_WITHOUT_CARET,
+			  label.get ());
+    m_labels.push_back (std::move (label));
+  }
+
+  void
+  set_logical_location (const diagnostic_logical_location *logical_loc)
+  {
+    m_logical_loc = logical_loc;
+  }
+  const diagnostic_logical_location *get_logical_location () const
+  {
+    return m_logical_loc;
+  }
+
+private:
+  diagnostic_manager &m_diag_mgr;
+  enum diagnostic_level m_level;
+  impl_rich_location m_rich_loc;
+  const diagnostic_logical_location *m_logical_loc;
+  diagnostic_metadata m_metadata;
+  std::vector<std::unique_ptr<range_label>> m_labels;
+  std::vector<std::unique_ptr<impl_rule>> m_rules;
+};
+
+static diagnostic_t
+diagnostic_t_from_diagnostic_level (enum diagnostic_level level)
+{
+  switch (level)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_LEVEL_ERROR:
+      return DK_ERROR;
+    case DIAGNOSTIC_LEVEL_WARNING:
+      return DK_WARNING;
+    case DIAGNOSTIC_LEVEL_NOTE:
+      return DK_NOTE;
+    }
+}
+
+/* class impl_diagnostic_client_data_hooks.  */
+
+const client_version_info *
+impl_diagnostic_client_data_hooks::get_any_version_info () const
+{
+  return m_mgr.get_client_version_info ();
+}
+
+const logical_location *
+impl_diagnostic_client_data_hooks::get_current_logical_location () const
+{
+  gcc_assert (m_mgr.get_current_diag ());
+
+  return m_mgr.get_current_diag ()->get_logical_location ();
+}
+
+const char *
+impl_diagnostic_client_data_hooks::
+maybe_get_sarif_source_language (const char *filename) const
+{
+  return m_mgr.maybe_get_sarif_source_language (filename);
+}
+
+void
+impl_diagnostic_client_data_hooks::
+add_sarif_invocation_properties (sarif_object &) const
+{
+  // No-op.
+}
+
+/* class sink.  */
+
+void
+sink::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+  diagnostic_info info;
+  diagnostic_set_info (&info, msgid, args, diag.get_rich_location (),
+		       diagnostic_t_from_diagnostic_level (diag.get_level ()));
+  info.metadata = diag.get_metadata ();
+  diagnostic_report_diagnostic (&m_dc, &info);
+}
+
+sink::sink (diagnostic_manager &mgr)
+: m_mgr (mgr)
+{
+  diagnostic_initialize (&m_dc, 0);
+  m_dc.m_client_aux_data = this;
+  m_dc.m_option_name = get_option_name_cb;
+  m_dc.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr));
+  m_dc.file_cache_init ();
+}
+
+sink::~sink ()
+{
+  diagnostic_finish (&m_dc);
+}
+
+/* class text_sink : public sink.  */
+
+text_sink::text_sink (diagnostic_manager &mgr,
+		      FILE *dst_stream,
+		      enum diagnostic_colorize colorize)
+: sink (mgr)
+{
+  m_dc.set_show_cwe (true);
+  m_dc.set_show_rules (true);
+
+  diagnostic_color_rule_t color_rule;
+  switch (colorize)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_COLORIZE_IF_TTY:
+      color_rule = DIAGNOSTICS_COLOR_AUTO;
+      break;
+    case DIAGNOSTIC_COLORIZE_NO:
+      color_rule = DIAGNOSTICS_COLOR_NO;
+      break;
+    case DIAGNOSTIC_COLORIZE_YES:
+      color_rule = DIAGNOSTICS_COLOR_YES;
+      break;
+    }
+  diagnostic_color_init (&m_dc, color_rule);
+  m_dc.printer->buffer->stream = dst_stream;
+  m_dc.m_text_callbacks.begin_diagnostic
+    = [] (diagnostic_context *context,
+	  diagnostic_info *info)
+      {
+	text_sink *sink = static_cast<text_sink *> (context->m_client_aux_data);
+	sink->on_begin_text_diagnostic (info);
+      };
+  m_dc.m_source_printing.enabled = true; // FIXME
+  m_dc.m_source_printing.colorize_source_p = true; // FIXME
+  m_dc.m_source_printing.show_labels_p = true; // FIXME
+  m_dc.m_source_printing.show_line_numbers_p = true; // FIXME
+  m_dc.m_source_printing.min_margin_width = 6; // FIXME
+}
+
+void
+text_sink::on_begin_text_diagnostic (diagnostic_info *info)
+{
+  const diagnostic *diag = m_mgr.get_current_diag ();
+  gcc_assert (diag);
+  const diagnostic_logical_location *diag_logical_loc
+    = diag->get_logical_location ();
+  if (m_current_logical_loc != diag_logical_loc)
+    {
+      m_current_logical_loc = diag_logical_loc;
+      if (m_current_logical_loc)
+	{
+	  char *old_prefix = pp_take_prefix (m_dc.printer);
+	  switch (m_current_logical_loc->get_kind ())
+	    {
+	    default:
+	      break;
+	    case LOGICAL_LOCATION_KIND_FUNCTION:
+	      if (const char *name
+		  = m_current_logical_loc->get_name_with_scope ())
+		{
+		  pp_printf (m_dc.printer, _("In function %qs"), name);
+		  pp_character (m_dc.printer, ':');
+		  pp_newline (m_dc.printer);
+		}
+	      break;
+	      // FIXME: handle other cases
+	    }
+	  m_dc.printer->prefix = old_prefix;
+	}
+    }
+  pp_set_prefix (m_dc.printer,
+		 diagnostic_build_prefix (&m_dc, info));
+}
+
+/* class sarif_sink : public sink.  */
+
+sarif_sink::sarif_sink (diagnostic_manager &mgr,
+			FILE *dst_stream,
+			enum diagnostic_sarif_version)
+: sink (mgr)
+{
+  diagnostic_output_format_init_sarif_stream (&m_dc, dst_stream);
+}
+
+/* struct diagnostic_manager.  */
+
+void
+diagnostic_manager::write_patch (FILE *dst_stream)
+{
+  pretty_printer pp;
+  pp.buffer->stream = dst_stream;
+  m_edit_context.print_diff (&pp, true);
+  pp_flush (&pp);
+}
+
+void
+diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+  set_line_table_global ();
+
+  m_current_diag = &diag;
+  for (auto &sink : m_sinks)
+    {
+      va_list arg_copy;
+      va_copy (arg_copy, *args);
+      sink->emit (diag, msgid, &arg_copy);
+    }
+
+  rich_location *rich_loc = diag.get_rich_location ();
+  if (rich_loc->fixits_can_be_auto_applied_p ())
+    m_edit_context.add_fixits (rich_loc);
+
+  m_current_diag = nullptr;
+}
+
+/* Public entrypoints.  */
+
+/* Public entrypoint for clients to acquire a diagnostic_manager.  */
+
+diagnostic_manager *
+diagnostic_manager_new (void)
+{
+  return new diagnostic_manager ();
+}
+
+/* Public entrypoint for clients to release a diagnostic_manager.  */
+
+void
+diagnostic_manager_release (diagnostic_manager *diag_mgr)
+{
+  delete diag_mgr;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_name.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_full_name.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+				       const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_version.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+				    const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_version_url.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+				  FILE *dst_stream,
+				  enum diagnostic_colorize colorize)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->add_sink (make_unique<text_sink> (*diag_mgr, dst_stream, colorize));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+				   FILE *dst_stream,
+				   enum diagnostic_sarif_version version)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->add_sink (make_unique<sarif_sink> (*diag_mgr, dst_stream, version));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+				FILE *dst_stream)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->write_patch (dst_stream);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+			     const char *name,
+			     const char *sarif_source_language)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (name);
+
+  return diag_mgr->new_file (name, sarif_source_language);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_location_t
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+						    const diagnostic_file *file,
+						    diagnostic_line_num_t linenum)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (file);
+
+  return diag_mgr->new_location_from_file_and_line (file, linenum);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_location_t
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+						       const diagnostic_file *file,
+						       diagnostic_line_num_t line_num,
+					       diagnostic_column_num_t column_num)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (file);
+
+  return diag_mgr->new_location_from_file_line_column (file,
+						       line_num,
+						       column_num);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_location_t
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+					    diagnostic_location_t loc_caret,
+					    diagnostic_location_t loc_start,
+					    diagnostic_location_t loc_end)
+{
+  gcc_assert (diag_mgr);
+
+  return diag_mgr->new_location_from_range (loc_caret,
+					    loc_start,
+					    loc_end);
+}
+
+
+// FIXME: emit text to stderr
+
+#if 0
+void
+diagnostic_location_debug (diagnostic_manager *diag_mgr,
+			   diagnostic_location_t loc,
+			   FILE *stream)
+{
+  text_sink sink (stream);
+  diagnostic d (*diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+}
+#endif
+
+/* Public entrypoint.  */
+
+const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+					 enum diagnostic_logical_location_kind_t kind,
+					 const diagnostic_logical_location *parent,
+					 const char *short_name,
+					 const char *fully_qualified_name,
+					 const char *decorated_name)
+{
+  gcc_assert (diag_mgr);
+
+  return diag_mgr->new_logical_location (kind,
+					 parent,
+					 short_name,
+					 fully_qualified_name,
+					 decorated_name);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+{
+  gcc_assert (diag_mgr);
+  diag_mgr->begin_group ();
+}
+
+/* Public entrypoint.  */
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+{
+  gcc_assert (diag_mgr);
+  diag_mgr->end_group ();
+}
+
+/* Public entrypoint.  */
+
+diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+		  enum diagnostic_level level)
+{
+  gcc_assert (diag_mgr);
+
+  return new diagnostic (*diag_mgr, level);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_cwe (diagnostic *diag,
+		    unsigned cwe_id)
+{
+  gcc_assert (diag);
+
+  diag->set_cwe (cwe_id);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_rule (diagnostic *diag,
+		     const char *title,
+		     const char *url)
+{
+  gcc_assert (diag);
+
+  diag->add_rule (title, url);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_location (diagnostic *diag,
+			 diagnostic_location_t loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+
+  diag->set_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location (diagnostic *diag,
+			 diagnostic_location_t loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+
+  diag->add_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location_with_label (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *text)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (text);
+
+  diag->add_location_with_label (loc, text);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_logical_location (diagnostic *diag,
+				 const diagnostic_logical_location *logical_loc)
+{
+  gcc_assert (diag);
+  gcc_assert (logical_loc);
+
+  diag->set_logical_location (logical_loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+					  diagnostic_location_t loc,
+					  const char *addition)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_before (loc, addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+					 diagnostic_location_t loc,
+					 const char *addition)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_after (loc, addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *replacement)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (replacement);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_replace (loc, replacement);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+				   diagnostic_location_t loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_remove (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_finish (diagnostic *diag, const char *gmsgid, ...)
+{
+  gcc_assert (diag);
+
+  if (const char *tool_name
+      = diag->get_manager ().get_client_version_info ()->m_name.get_str ())
+    progname = tool_name;
+  else
+    progname = "progname";
+  auto_diagnostic_group d;
+  va_list args;
+  va_start (args, gmsgid);
+  gcc_assert (diag);
+  diag->get_manager ().emit (*diag, gmsgid, &args);
+  va_end (args);
+  delete diag;
+}
diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map
new file mode 100644
index 00000000000..06da1d5c9c2
--- /dev/null
+++ b/gcc/libdiagnostics.map
@@ -0,0 +1,57 @@
+# Linker script for libdiagnostics.so
+#   Copyright (C) 2023 Free Software Foundation, Inc.
+#   Contributed by David Malcolm <dmalcolm@redhat.com>.
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GCC is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.  */
+
+# The initial release of the library.
+LIBDIAGNOSTICS_ABI_0
+{
+  global:
+    # Keep this list in order of decls in header file.
+    diagnostic_manager_new;
+    diagnostic_manager_release;
+    diagnostic_manager_set_tool_name;
+    diagnostic_manager_set_full_name;
+    diagnostic_manager_set_version_string;
+    diagnostic_manager_set_version_url;
+    diagnostic_manager_add_text_sink;
+    diagnostic_manager_add_sarif_sink;
+    diagnostic_manager_write_patch;
+    diagnostic_manager_new_file;
+    diagnostic_manager_new_location_from_file_and_line;
+    diagnostic_manager_new_location_from_file_line_column;
+    diagnostic_manager_new_location_from_range;
+    diagnostic_manager_new_logical_location;
+    diagnostic_manager_begin_group;
+    diagnostic_manager_end_group;
+    diagnostic_begin;
+    diagnostic_set_cwe;
+    diagnostic_add_rule;
+    diagnostic_set_location;
+    diagnostic_set_location_with_label;
+    diagnostic_add_location;
+    diagnostic_add_location_with_label;
+    diagnostic_set_logical_location;
+    diagnostic_add_fix_it_hint_insert_before;
+    diagnostic_add_fix_it_hint_insert_after;
+    diagnostic_add_fix_it_hint_replace;
+    diagnostic_add_fix_it_hint_delete;
+    diagnostic_finish;
+
+  local: *;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
new file mode 100644
index 00000000000..bd50a5568a7
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
@@ -0,0 +1,544 @@
+# Test code for libdiagnostics.so
+#
+# FIXME:
+#
+# Test code for libdiagnostics.so
+# We will compile each of jit.dg/test-*.c into an executable
+# dynamically linked against libgccjit.so, and then run each
+# such executable.
+#
+# These executables call into the libgccjit.so API to create
+# code, compile it, and run it, verifying that the results
+# are as expected.  See harness.h for shared code used by all
+# such executables.
+#
+# The executables call into DejaGnu's unit testing C API to
+# report PASS/FAIL results, which this script gathers back
+# up into the Tcl world, reporting a summary of all results
+# across all of the executables.
+
+# Kludge alert:
+# We need g++_init so that it can find the stdlib include path.
+#
+# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper,
+# which normally comes from the definition of
+# ${tool}_maybe_build_wrapper within lib/wrapper.exp.
+#
+# However, for us, ${tool} is "jit".
+# Hence we load wrapper.exp with tool == "g++", so that
+# g++_maybe_build_wrapper is defined.
+set tool g++
+load_lib wrapper.exp
+set tool diagnostics
+
+load_lib dg.exp
+load_lib prune.exp
+load_lib target-supports.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+load_lib gcc.exp
+load_lib g++.exp
+load_lib dejagnu.exp
+load_lib target-supports-dg.exp
+
+# # Skip these tests for targets that don't support -ldiagnostics
+# if { ![check_effective_target_ldiagnostics] } {
+#     return
+# }
+
+# The default do-what keyword.
+set dg-do-what-default compile
+
+# Look for lines of the form:
+#   definitely lost: 11,316 bytes in 235 blocks
+#   indirectly lost: 352 bytes in 4 blocks
+# Ideally these would report zero bytes lost (which is a PASS);
+# for now, report non-zero leaks as XFAILs.
+proc report_leak {kind name logfile line} {
+    set match [regexp "$kind lost: .*" $line result]
+    if $match {
+	verbose "Saw \"$result\" within \"$line\"" 4
+	# Extract bytes and blocks.
+	# These can contain commas as well as numerals,
+	# but we only care about whether we have zero.
+	regexp "$kind lost: (.+) bytes in (.+) blocks" \
+	    $result -> bytes blocks
+	verbose "bytes: '$bytes'" 4
+	verbose "blocks: '$blocks'" 4
+	if { $bytes == 0 } {
+	    pass "$name: $logfile: $result"
+	} else {
+	    xfail "$name: $logfile: $result"
+	}
+    }
+}
+
+proc parse_valgrind_logfile {name logfile} {
+    verbose "parse_valgrind_logfile: $logfile" 2
+    if [catch {set f [open $logfile]}] {
+	fail "$name: unable to read $logfile"
+	return
+    }
+
+    while { [gets $f line] >= 0 } {
+	# Strip off the PID prefix e.g. ==7675==
+	set line [regsub "==\[0-9\]*== " $line ""]
+	verbose $line 2
+
+	report_leak "definitely" $name $logfile $line
+	report_leak "indirectly" $name $logfile $line
+    }
+    close $f
+}
+
+# Given WRES, the result from "wait", issue a PASS
+# if the spawnee exited cleanly, or a FAIL for various kinds of
+# unexpected exits.
+
+proc verify_exit_status { executable wres } {
+    lassign $wres pid spawnid os_error_flag value
+    verbose "pid: $pid" 3
+    verbose "spawnid: $spawnid" 3
+    verbose "os_error_flag: $os_error_flag" 3
+    verbose "value: $value" 3
+
+    # Detect segfaults etc:
+    if { [llength $wres] > 4 } {
+	if { [lindex $wres 4] == "CHILDKILLED" } {
+	    fail "$executable killed: $wres"
+	    return
+	}
+    }
+    if { $os_error_flag != 0 } {
+	fail "$executable: OS error: $wres"
+	return
+    }
+    if { $value != 0 } {
+	fail "$executable: non-zero exit code: $wres"
+	return
+    }
+    pass "$executable exited cleanly"
+}
+
+# This is host_execute from dejagnu.exp commit
+#   126a089777158a7891ff975473939f08c0e31a1c
+# with the following patch applied, and renaming to "fixed_host_execute".
+# See the discussion at
+#  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
+#
+#  --- /usr/share/dejagnu/dejagnu.exp.old  2014-10-08 13:38:57.274068541 -0400
+#  +++ /usr/share/dejagnu/dejagnu.exp      2014-10-10 12:27:51.113813659 -0400
+#  @@ -113,8 +113,6 @@ proc host_execute {args} {
+#       set timetol 0
+#       set arguments ""
+#   
+#  -    expect_before buffer_full { perror "Buffer full" }
+#  -
+#       if { [llength $args] == 0} {
+#          set executable $args
+#       } else {
+
+
+# Execute the executable file, and anaylyse the output for the
+# test state keywords.
+#    Returns:
+#	A "" (empty) string if everything worked, or an error message
+#	if there was a problem.
+#
+proc fixed_host_execute {args} {
+    global env
+    global text
+    global spawn_id
+
+    verbose "fixed_host_execute: $args"
+
+    set timeoutmsg "Timed out: Never got started, "
+    set timeout 100
+    set file all
+    set timetol 0
+    set arguments ""
+
+    if { [llength $args] == 0} {
+	set executable $args
+    } else {
+	set executable [lindex $args 0]
+	set params [lindex $args 1]
+    }
+
+    verbose "The executable is $executable" 2
+    if {![file exists ${executable}]} {
+	perror "The executable, \"$executable\" is missing" 0
+	return "No source file found"
+    } elseif {![file executable ${executable}]} {
+	perror "The executable, \"$executable\" is not usable" 0
+	return "Bad executable found"
+    }
+
+    verbose "params: $params" 2
+
+    # spawn the executable and look for the DejaGnu output messages from the
+    # test case.
+    # spawn -noecho -open [open "|./${executable}" "r"]
+
+    # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment.
+    # Note that it's best to configure gcc with --enable-valgrind-annotations
+    # when testing under valgrind.
+    set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)]
+    if $run_under_valgrind {
+	set valgrind_logfile "${executable}.valgrind.txt"
+	set valgrind_params {"valgrind"}
+	lappend valgrind_params "--leak-check=full"
+	lappend valgrind_params "--log-file=${valgrind_logfile}"
+    } else {
+	set valgrind_params {}
+    }
+    verbose "valgrind_params: $valgrind_params" 2
+
+    set args ${valgrind_params}
+    lappend args "./${executable}"
+    set args [concat $args ${params}]
+    verbose "args: $args" 2
+
+    # We checked that the executable exists above, and can be executed, but
+    # that does not cover other reasons that the launch could fail (e.g.
+    # missing or malformed params); catch such cases here and report them.
+    set err [catch "spawn -noecho $args" pid]
+    set sub_proc_id $spawn_id
+    if { $pid <= 0 || $err != 0 || $sub_proc_id < 0 } {
+        warning "failed to spawn : $args : err = $err"
+    }
+
+    # Increase the buffer size, if needed to avoid spurious buffer-full
+    # events; GCC uses 10000; chose a power of two here.
+    set current_max_match [match_max -i $sub_proc_id]
+    if { $current_max_match < 8192 } {
+        match_max -i $sub_proc_id 8192
+        set used [match_max -i $sub_proc_id]
+    }
+
+    # If we get a buffer-full error, that seems to be unrecoverable so try to
+    # exit in a reasonable manner to avoid wedged processes.
+    expect_after full_buffer {
+        verbose -log "fixed_host_execute: $args FULL BUFFER"
+        # FIXME: this all assumes that closing the connection will cause the
+        # sub-process to terminate (but that is not going to be the case if,
+        # for example, there is something started with -nohup somewhere).
+        # We should explicitly kill it here.
+        # Set the process to be a nowait exit.
+        wait -nowait -i $sub_proc_id
+        catch close
+        perror "${executable} got full_buffer"
+        return "${executable} got full_buffer"
+    }
+
+    set prefix "\[^\r\n\]*"
+    # Work around a Darwin tcl or termios bug that sometimes inserts extra
+    # CR characters into the cooked tty stream
+    set endline "\r\n"
+    if { [istarget *-*-darwin*] } {
+        set endline "\r(\r)*\n"
+    }
+    
+    # Note that the logic here assumes that we cannot (validly) get single
+    # carriage return or line feed characters in the stream.  If those occur,
+    # it will stop any further matching.  We arange for the matching to be
+    # at the start of the buffer - so that if there is any spurious output
+    # to be discarded, it must be done explicitly - not by matching part-way
+    # through the buffer.
+    expect {
+	-re "^$prefix\[0-9\]\[0-9\]:..:..:${text}*$endline" {
+	    regsub "\[\n\r\t\]*NOTE: $text\r\n" $expect_out(0,string) "" output
+	    verbose "$output" 3
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tNOTE: (\[^\r\n\]+)$endline" {
+	    # discard notes.
+	    verbose "Ignored note: $expect_out(1,string)" 2
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tPASSED: (\[^\r\n\]+)$endline" {
+	    pass "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tFAILED: (\[^\r\n\]+)$endline" {
+	    fail "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tUNTESTED: (\[^\r\n\]+)$endline" {
+	    untested "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tUNRESOLVED: (\[^\r\n\]+)$endline" {
+	    unresolved "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^$prefix$endline" {
+            # This matches and discards any other lines (including blank ones).
+            if { [string length $expect_out(buffer)] <= 2 } {
+                set output "blank line"
+            } else {
+	        set output [string range $expect_out(buffer) 0 end-2]
+	    }
+	    verbose -log "DISCARDED $expect_out(spawn_id) : $output"
+	    exp_continue
+	}
+	eof {
+	    # This seems to be the only way that we can reliably know that the
+	    # output is finished since there are cases where further output
+	    # follows the dejagnu test harness totals.
+	    verbose "saw eof" 2
+	}
+	timeout {
+	    if { $timetol <= 2 } {
+	        verbose -log "Timed out with retry (timeout = $timeout)"
+		incr timetol
+		exp_continue
+	    } else {
+	        warning "Timed out executing testcase (timeout = $timeout)"
+		catch close
+		return "Timed out executing test case"
+	    }
+	}
+    }
+
+    # Use "wait" to pick up the sub-process exit state.  If the sub-process is
+    # writing to a file (perhaps under valgrind) then that also needs to be
+    # complete; only attempt this on a valid spawn.
+    if { $sub_proc_id > 0 } {
+        verbose "waiting for $sub_proc_id" 1
+        # Be explicit about what we are waiting for.
+        catch "wait -i $sub_proc_id" wres
+        verbose "wres: $wres" 2
+        verify_exit_status $executable $wres
+    }
+ 
+    if $run_under_valgrind {
+	upvar 2 name name
+	parse_valgrind_logfile $name $valgrind_logfile
+    }
+
+    # force a close of the executable to be safe.
+    catch close
+
+    return ""
+}
+
+# (end of code from dejagnu.exp)
+
+# GCC_UNDER_TEST is needed by gcc_target_compile
+global GCC_UNDER_TEST
+if ![info exists GCC_UNDER_TEST] {
+    set GCC_UNDER_TEST "[find_gcc]"
+}
+
+g++_init
+
+# Initialize dg.
+dg-init
+
+# Gather a list of all tests.
+
+# C tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.c
+set tests [find $srcdir/$subdir test-*.c]
+
+set tests [lsort $tests]
+
+verbose "tests: $tests"
+
+# libgloss has found the driver (as "xgcc" or "gcc) and stored
+# its full path as GCC_UNDER_TEST.
+proc get_path_of_driver {} {
+    global GCC_UNDER_TEST
+
+    verbose "GCC_UNDER_TEST: $GCC_UNDER_TEST"
+    set binary [lindex $GCC_UNDER_TEST 0]
+    verbose "binary: $binary"
+
+    return [file dirname $binary]
+}
+
+# Expand "SRCDIR" within ARG to the location of the top-level
+# src directory
+
+proc diagnostics-expand-vars {arg} {
+    verbose "diagnostics-expand-vars: $arg"
+    global srcdir
+    verbose " srcdir: $srcdir"
+    # "srcdir" is that of the gcc/testsuite directory, so
+    # we need to go up two levels.
+    set arg [string map [list "SRCDIR" $srcdir/../..] $arg]
+    verbose " new arg: $arg"
+    return $arg
+}
+
+# Parameters used when invoking the executables built from the test cases.
+
+global diagnostics-exe-params
+set diagnostics-exe-params {}
+
+# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of
+# the top-level srcdir.
+
+proc dg-diagnostics-set-exe-params { args } {
+    verbose "dg-diagnostics-set-exe-params: $args"
+
+    global diagnostics-exe-params
+    set diagnostics-exe-params {}
+    # Skip initial arg (line number)
+    foreach arg [lrange $args 1 [llength $args] ] {
+	lappend diagnostics-exe-params [diagnostics-expand-vars $arg]
+    }
+}
+
+proc diagnostics-dg-test { prog do_what extra_tool_flags } {
+    verbose "within diagnostics-dg-test..."
+    verbose "  prog: $prog"
+    verbose "  do_what: $do_what"
+    verbose "  extra_tool_flags: $extra_tool_flags"
+
+    global dg-do-what-default
+    set dg-do-what [list ${dg-do-what-default} "" P]
+
+    set tmp [dg-get-options $prog]
+    foreach op $tmp {
+	verbose "Processing option: $op" 3
+	set status [catch "$op" errmsg]
+	if { $status != 0 } {
+	    if { 0 && [info exists errorInfo] } {
+		# This also prints a backtrace which will just confuse
+		# testcase writers, so it's disabled.
+		perror "$name: $errorInfo\n"
+	    } else {
+		perror "$name: $errmsg for \"$op\"\n"
+	    }
+	    perror "$name: $errmsg for \"$op\"" 0
+	    return
+	}
+    }
+
+    # If we're not supposed to try this test on this target, we're done.
+    if { [lindex ${dg-do-what} 1] == "N" } {
+	unsupported "$name"
+	verbose "$name not supported on this target, skipping it" 3
+	return
+    }
+
+    # Determine what to name the built executable.
+    #
+    # We simply append .exe to the filename, e.g.
+    #  "test-foo.c.exe"
+    # since some testcases exist in both
+    #  "test-foo.c" and
+    #  "test-foo.cc"
+    # variants, and we don't want them to clobber each other's
+    # executables.
+    #
+    # This also ensures that the source name makes it into the
+    # pass/fail output, so that we can distinguish e.g. which test-foo
+    # is failing.
+    set output_file "[file tail $prog].exe"
+    verbose "output_file: $output_file"
+
+    # Create the test executable:
+    set extension [file extension $prog]
+    if {$extension == ".cc"} {
+	set compilation_function "g++_target_compile"
+	set options "{additional_flags=$extra_tool_flags}"
+    } else {
+	set compilation_function "gcc_target_compile"
+	# Until recently, <dejagnu.h> assumed -fgnu89-inline
+	# Ideally we should fixincludes it (PR other/63613), but
+	# for now add -fgnu89-inline when compiling C JIT testcases.
+	# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63613
+	# and http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00011.html
+	set options "{additional_flags=$extra_tool_flags -fgnu89-inline}"
+    }
+    verbose "compilation_function=$compilation_function"
+    verbose "options=$options"
+
+    set comp_output [$compilation_function $prog $output_file \
+			 "executable" $options]
+    upvar 1 name name
+    if ![diagnostics_check_compile "$name" "initial compilation" \
+	    $output_file $comp_output] then {
+      return
+    }
+
+    # Run the test executable, capturing the PASS/FAIL textual output
+    # from the C API, converting it into the Tcl API.
+
+    # We need to set LD_LIBRARY_PATH so that the test files can find
+    # libdiagnostics.so
+    # Do this using set_ld_library_path_env_vars from target-libpath.exp
+    # We will restore the old value later using
+    # restore_ld_library_path_env_vars.
+
+    # Unfortunately this API only supports a single saved value, rather
+    # than a stack, and g++_init has already called into this API,
+    # injecting the appropriate value for LD_LIBRARY_PATH for finding
+    # the built copy of libstdc++.
+    # Hence the call to restore_ld_library_path_env_vars would restore
+    # the *initial* value of LD_LIBRARY_PATH, and attempts to run
+    # a C++ testcase after running any prior testcases would thus look
+    # in the wrong place for libstdc++.  This led to failures at startup
+    # of the form:
+    #   ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe)
+    # when the built libstdc++ is more recent that the system libstdc++.
+    #
+    # As a workaround, reset the variable "orig_environment_saved" within
+    # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars
+    # API saves/restores the current value of LD_LIBRARY_PATH (as set up
+    # by g++_init).
+    global orig_environment_saved
+    set orig_environment_saved 0
+
+    global ld_library_path
+    global base_dir
+    set ld_library_path "$base_dir/../../"
+    set_ld_library_path_env_vars
+
+    # dejagnu.exp's host_execute has code to scrape out test results
+    # from the DejaGnu C API and bring back into the tcl world, so we
+    # use that to invoke the built code.
+    # However, it appears to be buggy; see:
+    #  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
+    # We instead call a patched local copy, "fixed_host_execute", defined
+    # above.
+
+    global diagnostics-exe-params
+    set args ${diagnostics-exe-params}
+    set diagnostics-exe-params {}
+
+    set result [fixed_host_execute $output_file $args ]
+    verbose "result: $result"
+
+    restore_ld_library_path_env_vars
+
+    # Normally we would return $comp_output and $output_file to the
+    # caller, which would delete $output_file, the generated executable.
+    # If we need to debug, it's handy to be able to suppress this behavior,
+    # keeping the executable around.
+    
+    global env
+    set preserve_executables [info exists env(PRESERVE_EXECUTABLES)]
+    if $preserve_executables {
+	set output_file ""
+    }
+    
+    return [list $comp_output $output_file]
+}
+
+set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror"
+
+# Main loop.  This will invoke jig-dg-test on each test-*.c file.
+dg-runtest $tests "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
