public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
From: David Malcolm <dmalcolm@redhat.com>
To: gcc-patches@gcc.gnu.org, binutils@sourceware.org
Cc: Nick Clifton <nickc@redhat.com>,
	Simon Sobisch <simonsobisch@gnu.org>,
	David Malcolm <dmalcolm@redhat.com>
Subject: [PATCH 2/2] libdiagnostics: work-in-progress implementation
Date: Mon,  6 Nov 2023 17:29:58 -0500	[thread overview]
Message-ID: <20231106222959.2707741-3-dmalcolm@redhat.com> (raw)
In-Reply-To: <20231106222959.2707741-1-dmalcolm@redhat.com>

Here's a work-in-progress patch for GCC that adds the implementation
of libdiagnostics.  Various aspects of this need work; posting now
for early feedback on overall direction.  For example, the testsuite
doesn't yet check the output from the test client programs (and I'm
not quite sure of the best way to express that in DejaGnu).

gcc/ChangeLog:
	* Makefile.in (lang_checks): Add check-libdiagnostics.
	(start.encap): Add libdiagnostics.
	(libdiagnostics_OBJS): New.
	...plus a bunch of stuff hacked up from jit/Make-lang.in.
	* configure: Regenerate.
	* configure.ac (check_languages): Add check-libdiagnostics.
	* input.h: Add FIXME.
	* libdiagnostics.cc: New file.
	* libdiagnostics.map: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/libdiagnostics.exp: New, based on jit.exp.
---
 gcc/Makefile.in                               |  134 +-
 gcc/configure                                 |    2 +-
 gcc/configure.ac                              |    2 +-
 gcc/input.h                                   |    2 +-
 gcc/libdiagnostics.cc                         | 1124 +++++++++++++++++
 gcc/libdiagnostics.map                        |   57 +
 .../libdiagnostics.dg/libdiagnostics.exp      |  544 ++++++++
 7 files changed, 1860 insertions(+), 5 deletions(-)
 create mode 100644 gcc/libdiagnostics.cc
 create mode 100644 gcc/libdiagnostics.map
 create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp

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


  parent reply	other threads:[~2023-11-06 22:30 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
2023-11-06 22:29 ` [PATCH 1/2] libdiagnostics: header and examples David Malcolm
2023-11-06 22:29 ` David Malcolm [this message]
2023-11-07  7:54   ` [PATCH 2/2] libdiagnostics: work-in-progress implementation Simon Sobisch
2023-11-07 14:59     ` David Malcolm
2023-11-07 15:35       ` Simon Sobisch
2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
2023-11-07  7:04   ` Simon Sobisch
2023-11-07 14:51     ` David Malcolm
2023-11-07  9:21   ` Clément Chigot
2023-11-07 14:09     ` David Malcolm
2023-11-07 15:57       ` Clément Chigot
2023-11-07 16:18         ` David Malcolm
2023-11-07 10:03   ` Jan Beulich
2023-11-07 14:32     ` David Malcolm
2023-11-07 14:59       ` Jan Beulich
2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
2023-11-21 22:20   ` [PATCH 1/5] libdiagnostics v2: header and examples David Malcolm
2023-11-21 22:20   ` [PATCH 2/5] libdiagnostics v2: work-in-progress implementation David Malcolm
2023-11-21 22:20   ` [PATCH 3/5] libdiagnostics v2: add C++ wrapper API David Malcolm
2023-11-21 22:20   ` [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text David Malcolm
2023-11-28  1:25     ` David Malcolm
2023-11-21 22:20   ` [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info David Malcolm
2023-11-28  1:25     ` David Malcolm
2023-11-21 22:20   ` [PATCH] binutils: v2: experimental use of libdiagnostics in gas David Malcolm
2023-11-22  7:36     ` Jan Beulich
2023-11-21 22:35   ` [PATCH 0/6] v2 of libdiagnostics Simon Sobisch
2023-11-23 17:36   ` Pedro Alves
2024-01-27 23:28     ` Simon Sobisch

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231106222959.2707741-3-dmalcolm@redhat.com \
    --to=dmalcolm@redhat.com \
    --cc=binutils@sourceware.org \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=nickc@redhat.com \
    --cc=simonsobisch@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).