diff mbox series

[2/7] libdiagnostics v3: implementation

Message ID 20240815181156.1815075-3-dmalcolm@redhat.com
State New
Headers show
Series v3 of libdiagnostics | expand

Commit Message

David Malcolm Aug. 15, 2024, 6:11 p.m. UTC
Changed in v3:
* Added a --enable-libdiagnostics to configure.ac.  It is disabled
  by default, and requires --enable-host-shared.
* Split out gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp into
  another patch
* Update copyright year
* class diagnostic_logical_location: Add get_name_for_path_output.
* as_diagnostic_event_id: New function.
* on_begin_text_diagnostic: Make "info" const.
* diagnostic_manager::~diagnostic_manager: Free
  m_line_table.m_location_adhoc_data_map.data and
  m_line_table.info_ordinary.maps.
* diagnostic_manager::new_location_from_file_and_line: Move code into
  ensure_linemap_for_file_and_line.
* diagnostic_manager::new_location_from_file_line_column: Use
  ensure_linemap_for_file_and_line rather than always using LC_ENTER.
* class libdiagnostics_path_event: New.
* class libdiagnostics_path_thread: New.
* struct diagnostic_execution_path: New.
* diagnostic::diagnostic: Initialize m_path.
* diagnostic::add_execution_path: New.
* diagnostic::take_execution_path: New.
* diagnostic::m_path: New field.
* diagnostic_text_sink::diagnostic_text_sink: Initialize
  m_current_logical_loc.  Call diagnostic_urls_init, set_show_cwe,
  set_show_rules, and set m_show_column.
* diagnostic_text_sink::on_begin_text_diagnostic: Various fixes.
* sarif_sink::sarif_sink: Add "main_input_file" param.
* diagnostic_manager::write_patch: Update for change to pretty_printer.
* diagnostic_manager::new_execution_path:  New.
* diagnostic_manager_add_sarif_sink: Add "main_input_file" param.
* diagnostic_manager_debug_dump_location: Call diagnostic_finish.
* diagnostic_set_logical_location: Allow logical_loc to be NULL.
* diagnostic_add_execution_path: New.
* diagnostic_manager_new_execution_path: New.
* diagnostic_take_execution_path: New.
* diagnostic_execution_path_release: New.
* diagnostic_execution_path_add_event: New.
* diagnostic_execution_path_add_event_va: New.

Changed in v2:
* Changed diagnostic_location_t -> const diagnostic_physical_location *
* new entrypoint: diagnostic_finish_va
* new debugging entrypoints for dumping to a FILE *
* Makefile.in: dropped FULL_DRIVER_NAME from libdiagnostics
* fix up for my recent changes to gcc/diagnostic.h

Blurb from v1:

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.

ChangeLog:
	* configure: Regenerate.
	* configure.ac: Add --enable-libdiagnostics.

gcc/ChangeLog:
	* Makefile.in (enable_libdiagnostics): New.
	(lang_checks): If libdiagnostics is enabled, add
	check-libdiagnostics.
	(ALL_HOST_OBJS): If libdiagnostics is enabled, add
	$(libdiagnostics_OBJS).
	(start.encap): Add LIBDIAGNOSTICS.
	(libdiagnostics_OBJS): New.
	(LIBDIAGNOSTICS_VERSION_NUM): New, adapted from code in
	jit/Make-lang.in.
	(LIBDIAGNOSTICS_MINOR_NUM): Likewise.
	(LIBDIAGNOSTICS_RELEASE_NUM): Likewise.
	(LIBDIAGNOSTICS_FILENAME): Likewise.
	(LIBDIAGNOSTICS_IMPORT_LIB): Likewise.
	(libdiagnostics): Likewise.
	(LIBDIAGNOSTICS_AGE): Likewise.
	(LIBDIAGNOSTICS_BASENAME): Likewise.
	(LIBDIAGNOSTICS_SONAME): Likewise.
	(LIBDIAGNOSTICS_LINKER_NAME): Likewise.
	(LIBDIAGNOSTICS_COMMA): Likewise.
	(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION): Likewise.
	(LIBDIAGNOSTICS_SONAME_OPTION): Likewise.
	(LIBDIAGNOSTICS_SONAME_SYMLINK): Likewise.
	(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): Likewise.
	(LIBDIAGNOSTICS_FILENAME): Likewise.
	(libdiagnostics.serial): Likewise.
	(LIBDIAGNOSTICS_EXTRA_OPTS): Likewise.
	(install): If libdiagnostics is enabled, add
	install-libdiagnostics.
	(libdiagnostics.install-headers): New.
	(libdiagnostics.install-common): New, adapted from code in
	jit/Make-lang.in.
	(install-libdiagnostics): New.
	* configure: Regenerate.
	* configure.ac (check_languages): Add check-libdiagnostics.
	(--enable-libdiagnostics): New.
	* diagnostic-event-id.h (diagnostic_event_id_t::zero_based): New.
	* doc/install.texi (--enable-libdiagnostics): New.
	* libdiagnostics.cc: New file.
	* libdiagnostics.map: New file.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 configure                 |   42 +
 configure.ac              |   35 +
 gcc/Makefile.in           |  179 +++-
 gcc/configure             |   26 +-
 gcc/configure.ac          |   16 +
 gcc/diagnostic-event-id.h |    6 +
 gcc/doc/install.texi      |    7 +
 gcc/libdiagnostics.cc     | 1652 +++++++++++++++++++++++++++++++++++++
 gcc/libdiagnostics.map    |   72 ++
 9 files changed, 2032 insertions(+), 3 deletions(-)
 create mode 100644 gcc/libdiagnostics.cc
 create mode 100644 gcc/libdiagnostics.map
diff mbox series

Patch

diff --git a/configure b/configure
index 51bf1d1add18..be6e41439990 100755
--- a/configure
+++ b/configure
@@ -691,6 +691,7 @@  extra_host_libiberty_configure_flags
 stage1_languages
 host_libs_picflag
 CRAB1_LIBS
+enable_libdiagnostics
 PICFLAG
 host_shared
 gcc_host_pie
@@ -844,6 +845,7 @@  enable_linker_plugin_configure_flags
 enable_linker_plugin_flags
 enable_host_pie
 enable_host_shared
+enable_libdiagnostics
 enable_stage1_languages
 enable_objc_gc
 with_target_bdw_gc
@@ -1578,6 +1580,7 @@  Optional Features:
                           plugins [none]
   --enable-host-pie       build position independent host executables
   --enable-host-shared    build host code as shared libraries
+  --enable-libdiagnostics build libdiagnostics shared library
   --enable-stage1-languages[=all]
                           choose additional languages to build during stage1.
                           Mostly useful for compiler development
@@ -8876,6 +8879,45 @@  fi
 
 
 
+
+# Check for libdiagnostics support.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable libdiagnostics" >&5
+$as_echo_n "checking whether to enable libdiagnostics... " >&6; }
+# Check whether --enable-libdiagnostics was given.
+if test "${enable_libdiagnostics+set}" = set; then :
+  enableval=$enable_libdiagnostics; enable_libdiagnostics=$enableval
+else
+  enable_libdiagnostics=no
+fi
+
+
+if test x$enable_libdiagnostics = xyes; then
+  # Disable libdiagnostics if -enable-host-shared not specified
+  # but not if building for Mingw. All code in Windows
+  # is position independent code (PIC).
+  case $target in
+     *mingw*) ;;
+     *)
+       if test x$host_shared != xyes; then
+	 as_fn_error $? "
+Enabling libdiagnostics requires --enable-host-shared.
+
+--enable-host-shared typically slows the rest of the compiler down by
+a few %, so you must explicitly enable it.
+
+If you want to build both libdiagnostics and the regular compiler, it is often
+best to do this via two separate configure/builds, in separate
+directories, to avoid imposing the performance cost of
+--enable-host-shared on the regular compiler." "$LINENO" 5
+       fi
+       ;;
+   esac
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_libdiagnostics" >&5
+$as_echo "$enable_libdiagnostics" >&6; }
+
+
+
 # Rust requires -ldl and -lpthread if you are using an old glibc that does not include them by
 # default, so we check for them here
 
diff --git a/configure.ac b/configure.ac
index 20457005e299..ba2712d6dd52 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2037,6 +2037,41 @@  fi
 
 AC_SUBST(PICFLAG)
 
+
+# Check for libdiagnostics support.
+AC_MSG_CHECKING([whether to enable libdiagnostics])
+AC_ARG_ENABLE(libdiagnostics,
+[AS_HELP_STRING([--enable-libdiagnostics],
+		[build libdiagnostics shared library])],
+enable_libdiagnostics=$enableval,
+enable_libdiagnostics=no)
+
+if test x$enable_libdiagnostics = xyes; then
+  # Disable libdiagnostics if -enable-host-shared not specified
+  # but not if building for Mingw. All code in Windows
+  # is position independent code (PIC).
+  case $target in
+     *mingw*) ;;
+     *)
+       if test x$host_shared != xyes; then
+	 AC_MSG_ERROR([
+Enabling libdiagnostics requires --enable-host-shared.
+
+--enable-host-shared typically slows the rest of the compiler down by
+a few %, so you must explicitly enable it.
+
+If you want to build both libdiagnostics and the regular compiler, it is often
+best to do this via two separate configure/builds, in separate
+directories, to avoid imposing the performance cost of
+--enable-host-shared on the regular compiler.])
+       fi
+       ;;
+   esac
+fi
+AC_MSG_RESULT($enable_libdiagnostics)
+AC_SUBST(enable_libdiagnostics)
+
+
 # Rust requires -ldl and -lpthread if you are using an old glibc that does not include them by
 # default, so we check for them here
 
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 8fba8f7db6a2..3e4c7bd645f9 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -436,6 +436,8 @@  endif
 
 enable_host_shared = @enable_host_shared@
 
+enable_libdiagnostics = @enable_libdiagnostics@
+
 enable_as_accelerator = @enable_as_accelerator@
 
 CPPLIB = ../libcpp/libcpp.a
@@ -615,6 +617,9 @@  xm_include_list=@xm_include_list@
 xm_defines=@xm_defines@
 lang_checks=
 lang_checks_parallelized=
+ifeq (@enable_libdiagnostics@,yes)
+lang_checks += check-libdiagnostics
+endif
 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@
 lang_tree_files=@lang_tree_files@
@@ -1873,6 +1878,10 @@  endif
 # compilation or not.
 ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
 
+ifeq (@enable_libdiagnostics@,yes)
+ALL_HOST_OBJS += $(libdiagnostics_OBJS)
+endif
+
 BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
 	$(CPPLIB) $(LIBDECNUMBER)
 
@@ -2179,7 +2188,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
@@ -2268,6 +2277,129 @@  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
+
+# libdiagnostics
+
+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)
+
+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)
+
+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)
+
+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
@@ -3808,6 +3940,10 @@  ifeq ($(enable_plugin),yes)
 install: install-plugin
 endif
 
+ifeq ($(enable_libdiagnostics),yes)
+install: install-libdiagnostics
+endif
+
 install-strip: override INSTALL_PROGRAM = $(INSTALL_STRIP_PROGRAM)
 ifneq ($(STRIP),)
 install-strip: STRIPPROG = $(STRIP)
@@ -3984,6 +4120,47 @@  install-driver: installdirs xgcc$(exeext)
 	  fi; \
 	fi
 
+libdiagnostics.install-headers: installdirs
+	$(INSTALL_DATA) $(srcdir)/libdiagnostics.h \
+	  $(DESTDIR)$(includedir)/libdiagnostics.h
+	$(INSTALL_DATA) $(srcdir)/libdiagnostics++.h \
+	  $(DESTDIR)$(includedir)/libdiagnostics++.h
+
+ifneq (,$(findstring mingw,$(target)))
+libdiagnostics.install-common: installdirs libdiagnostics.install-headers
+# Install import library
+	$(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_IMPORT_LIB) \
+	  $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_IMPORT_LIB)
+# Install DLL file
+	$(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \
+	  $(DESTDIR)$(bindir)/$(LIBDIAGNOSTICS_FILENAME)
+
+else
+ifneq (,$(findstring darwin,$(host)))
+# but only one level for Darwin
+
+libdiagnostics.install-common: installdirs libdiagnostics.install-headers
+	$(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \
+	  $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_FILENAME)
+	ln -sf \
+	  $(LIBDIAGNOSTICS_SONAME_SYMLINK)\
+	  $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+
+else
+libdiagnostics.install-common: installdirs libdiagnostics.install-headers
+	$(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \
+	  $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_FILENAME)
+	ln -sf \
+	  $(LIBDIAGNOSTICS_FILENAME) \
+	  $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_SONAME_SYMLINK)
+	ln -sf \
+	  $(LIBDIAGNOSTICS_SONAME_SYMLINK)\
+	  $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+endif
+endif
+
+install-libdiagnostics: libdiagnostics.install-common
+
 # Install the info files.
 # $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir
 # to do the install.
diff --git a/gcc/configure b/gcc/configure
index 557ea5fa3ac9..5e2f4644c528 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -637,6 +637,8 @@  LD_PICFLAG
 PICFLAG
 enable_default_pie
 enable_host_bind_now
+LIBDIAGNOSTICS
+enable_libdiagnostics
 enable_host_pie
 enable_host_shared
 enable_plugin
@@ -1051,6 +1053,7 @@  enable_version_specific_runtime_libs
 enable_plugin
 enable_host_shared
 enable_host_pie
+enable_libdiagnostics
 enable_host_bind_now
 enable_libquadmath_support
 with_linker_hash_style
@@ -1824,6 +1827,7 @@  Optional Features:
   --enable-plugin         enable plugin support
   --enable-host-shared    build host code as shared libraries
   --enable-host-pie       build host code as PIE
+  --enable-libdiagnostics build libdiagnostics shared library
   --enable-host-bind-now  link host code as BIND_NOW
   --disable-libquadmath-support
                           disable libquadmath support for Fortran
@@ -21406,7 +21410,7 @@  else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 21409 "configure"
+#line 21413 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -21512,7 +21516,7 @@  else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 21515 "configure"
+#line 21519 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -33788,6 +33792,9 @@  for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
 done
+if test x$enable_libdiagnostics = xyes; then
+	check_languages="$check_languages check-libdiagnostics"
+fi
 
 selftest_languages=
 for language in $all_selected_languages
@@ -34226,6 +34233,21 @@  fi
 
 
 
+# Check whether --enable-libdiagnostics was given.
+if test "${enable_libdiagnostics+set}" = set; then :
+  enableval=$enable_libdiagnostics;
+fi
+
+
+
+if test "$enable_libdiagnostics" = "yes"; then
+  LIBDIAGNOSTICS='libdiagnostics'
+else
+  LIBDIAGNOSTICS=''
+fi
+
+
+
 # Enable --enable-host-bind-now
 # Check whether --enable-host-bind-now was given.
 if test "${enable_host_bind_now+set}" = set; then :
diff --git a/gcc/configure.ac b/gcc/configure.ac
index eaa01d0d7e56..0edd1b92f5fb 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -7371,6 +7371,9 @@  for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
 done
+if test x$enable_libdiagnostics = xyes; then
+	check_languages="$check_languages check-libdiagnostics"
+fi
 
 selftest_languages=
 for language in $all_selected_languages
@@ -7603,6 +7606,19 @@  AC_ARG_ENABLE(host-pie,
 		[build host code as PIE])])
 AC_SUBST(enable_host_pie)
 
+AC_ARG_ENABLE(libdiagnostics,
+[AS_HELP_STRING([--enable-libdiagnostics],
+		[build libdiagnostics shared library])])
+AC_SUBST(enable_libdiagnostics)
+
+if test "$enable_libdiagnostics" = "yes"; then
+  LIBDIAGNOSTICS='libdiagnostics'
+else
+  LIBDIAGNOSTICS=''
+fi
+AC_SUBST(LIBDIAGNOSTICS)
+
+
 # Enable --enable-host-bind-now
 AC_ARG_ENABLE(host-bind-now,
 [AS_HELP_STRING([--enable-host-bind-now],
diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h
index 78c2ccbbc99d..51e802e149b7 100644
--- a/gcc/diagnostic-event-id.h
+++ b/gcc/diagnostic-event-id.h
@@ -47,6 +47,12 @@  class diagnostic_event_id_t
     return m_index + 1;
   }
 
+  int zero_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index;
+  }
+
  private:
   static const int UNKNOWN_EVENT_IDX = -1;
   int m_index; // zero-based
diff --git a/gcc/doc/install.texi b/gcc/doc/install.texi
index 4973f195daf9..f1d64833cc8a 100644
--- a/gcc/doc/install.texi
+++ b/gcc/doc/install.texi
@@ -1226,6 +1226,13 @@  virtual calls in verifiable mode at all.  However the libvtv library will
 still be built (see @option{--disable-libvtv} to turn off building libvtv).
 @option{--disable-vtable-verify} is the default.
 
+@item --enable-libdiagnostics
+Specify whether to build @code{libdiagnostics}, a shared library exposing
+GCC's diagnostics capabilities via a C API, and a C++ wrapper API adding
+``syntactic sugar''.
+
+This option requires @option{--enable-host-shared} on non-Windows hosts.
+
 @item --disable-gcov
 Specify that the run-time library used for coverage analysis
 and associated host tools should not be built.
diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc
new file mode 100644
index 000000000000..00afafe396b2
--- /dev/null
+++ b/gcc/libdiagnostics.cc
@@ -0,0 +1,1652 @@ 
+/* C++ implementation of a pure C API for emitting diagnostics.
+   Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#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-path.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_physical_location
+{
+  diagnostic_physical_location (diagnostic_manager *mgr,
+				location_t inner)
+  : m_mgr (mgr),
+    m_inner (inner)
+  {}
+
+  diagnostic_manager *m_mgr;
+  location_t m_inner;
+};
+
+static location_t
+as_location_t (const diagnostic_physical_location *loc)
+{
+  if (!loc)
+    return UNKNOWN_LOCATION;
+  return loc->m_inner;
+}
+
+/* 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;
+      }
+  }
+
+  enum diagnostic_logical_location_kind_t get_external_kind () const
+  {
+    return m_kind;
+  }
+
+  const diagnostic_logical_location *get_parent () const { return m_parent; }
+
+  label_text get_name_for_path_output () const
+  {
+    return label_text::borrow (m_short_name.get_str ());
+  }
+
+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;
+};
+
+static diagnostic_event_id
+as_diagnostic_event_id (diagnostic_event_id_t id)
+{
+  return id.zero_based ();
+}
+
+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);
+
+  diagnostic_manager &m_mgr;
+
+  /* One context per sink.  */
+  diagnostic_context m_dc;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_text_sink : public sink
+{
+public:
+  diagnostic_text_sink (diagnostic_manager &mgr,
+			FILE *dst_stream,
+			enum diagnostic_colorize colorize);
+
+  void
+  on_begin_text_diagnostic (const diagnostic_info *info);
+
+  diagnostic_source_printing_options &get_source_printing_options ()
+  {
+    return m_dc.m_source_printing;
+  }
+
+  void
+  set_colorize (enum diagnostic_colorize colorize);
+
+private:
+  const diagnostic_logical_location *m_current_logical_loc;
+};
+
+class sarif_sink : public sink
+{
+public:
+  sarif_sink (diagnostic_manager &mgr,
+	      FILE *dst_stream,
+	      const diagnostic_file *main_input_file,
+	      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),
+      m_edit_context (m_file_cache)
+  {
+    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;
+
+    for (auto iter :m_location_t_map)
+      delete iter.second;
+
+    free (m_line_table.m_location_adhoc_data_map.data);
+    free (m_line_table.info_ordinary.maps);
+  }
+
+  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;
+  }
+
+  const diagnostic_physical_location *
+  new_location_from_file_and_line (const diagnostic_file *file,
+				   diagnostic_line_num_t line_num)
+  {
+    ensure_linemap_for_file_and_line (file, line_num);
+    location_t loc = linemap_position_for_column (&m_line_table, 0);
+    return new_location (loc);
+  }
+
+  const diagnostic_physical_location *
+  new_location_from_file_line_column (const diagnostic_file *file,
+				      diagnostic_line_num_t line_num,
+				      diagnostic_column_num_t column_num)
+  {
+    ensure_linemap_for_file_and_line (file, line_num);
+    location_t loc = linemap_position_for_column (&m_line_table, column_num);
+    return new_location (loc);
+  }
+
+  const diagnostic_physical_location *
+  new_location_from_range (const diagnostic_physical_location *loc_caret,
+			   const diagnostic_physical_location *loc_start,
+			   const diagnostic_physical_location *loc_end)
+  {
+    return new_location
+      (m_line_table.make_location (as_location_t (loc_caret),
+				   as_location_t (loc_start),
+				   as_location_t (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;
+  }
+
+  diagnostic_execution_path *
+  new_execution_path ();
+
+  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_physical_location (const diagnostic_physical_location *loc) const
+  {
+    if (!loc)
+      return;
+    gcc_assert (loc->m_mgr == this);
+  }
+
+  /* TODO: Various things still use 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 () const
+  {
+    line_table = const_cast<line_maps *> (&m_line_table);
+  }
+
+private:
+  void
+  ensure_linemap_for_file_and_line (const diagnostic_file *file,
+				    diagnostic_line_num_t linenum)
+  {
+    /* 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
+      {
+	line_map *map
+	  = const_cast<line_map *>
+	    (linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
+			  file->get_name (), 0));
+	((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+      }
+    linemap_line_start (&m_line_table, linenum, 100);
+  }
+
+  const diagnostic_physical_location *
+  new_location (location_t loc)
+  {
+    if (loc == UNKNOWN_LOCATION)
+      return nullptr;
+    if (diagnostic_physical_location **slot = m_location_t_map.get (loc))
+      return *slot;
+    diagnostic_physical_location *phys_loc
+      = new diagnostic_physical_location (this, loc);
+    m_location_t_map.put (loc, phys_loc);
+    return phys_loc;
+  }
+
+  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;
+  hash_map<int_hash<location_t, UNKNOWN_LOCATION, UINT_MAX>,
+	   diagnostic_physical_location *> m_location_t_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;
+};
+
+class libdiagnostics_path_event : public diagnostic_event
+{
+public:
+  libdiagnostics_path_event (const diagnostic_physical_location *physical_loc,
+			     const diagnostic_logical_location *logical_loc,
+			     unsigned stack_depth,
+			     const char *gmsgid,
+			     va_list *args)
+  : m_physical_loc (physical_loc),
+    m_logical_loc (logical_loc),
+    m_stack_depth (stack_depth)
+  {
+    m_desc_uncolored = make_desc (gmsgid, args, false);
+    m_desc_colored = make_desc (gmsgid, args, true);
+  }
+
+  /* diagnostic_event vfunc implementations.  */
+
+  location_t get_location () const final override
+  {
+    return as_location_t (m_physical_loc);
+  }
+
+  int get_stack_depth () const final override
+  {
+    return m_stack_depth;
+  }
+
+  label_text get_desc (bool can_colorize) const final override
+  {
+    const label_text &text = can_colorize ? m_desc_colored : m_desc_uncolored;
+    return label_text::borrow (text.get ());
+  }
+
+  const logical_location *get_logical_location () const
+  {
+    return m_logical_loc;
+  }
+
+  meaning get_meaning () const final override
+  {
+    return meaning ();
+  }
+
+  bool connect_to_next_event_p () const final override
+  {
+    return false; // TODO
+  }
+
+  diagnostic_thread_id_t get_thread_id () const final override
+  {
+    return 0;
+  }
+
+private:
+  static label_text make_desc (const char *gmsgid,
+			       va_list *args,
+			       bool colorize)
+  {
+    va_list copy_of_args;
+    va_copy (copy_of_args, *args);
+
+    // TODO: when should localization happen?
+    text_info text (gmsgid, &copy_of_args, errno);
+    pretty_printer pp;
+    pp_show_color (&pp) = colorize;
+    pp.set_output_stream (nullptr);
+    pp_format (&pp, &text);
+    pp_output_formatted_text (&pp, nullptr);
+    label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+
+    va_end (copy_of_args);
+
+    return result;
+  }
+
+  const diagnostic_physical_location *m_physical_loc;
+  const diagnostic_logical_location *m_logical_loc;
+  unsigned m_stack_depth;
+  label_text m_desc_uncolored;
+  label_text m_desc_colored;
+};
+
+class libdiagnostics_path_thread : public diagnostic_thread
+{
+public:
+  libdiagnostics_path_thread (const char *name) : m_name (name) {}
+  label_text get_name (bool) const final override
+  {
+    return label_text::borrow (m_name);
+  }
+
+private:
+  const char *m_name; // has been i18n-ed and formatted
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_execution_path : public diagnostic_path
+{
+  diagnostic_execution_path ()
+  : m_thread ("")
+  {
+  }
+
+  diagnostic_event_id_t
+  add_event_va (const diagnostic_physical_location *physical_loc,
+		const diagnostic_logical_location *logical_loc,
+		unsigned stack_depth,
+		const char *gmsgid,
+		va_list *args)
+  {
+    m_events.push_back (::make_unique<libdiagnostics_path_event> (physical_loc,
+								  logical_loc,
+								  stack_depth,
+								  gmsgid,
+								  args));
+    return m_events.size () - 1;
+  }
+
+  /* diagnostic_path vfunc implementations.  */
+
+  unsigned num_events () const final override
+  {
+    return m_events.size ();
+  }
+  const diagnostic_event & get_event (int idx) const final override
+  {
+    return *m_events[idx];
+  }
+  unsigned num_threads () const final override { return 1; }
+  const diagnostic_thread &
+  get_thread (diagnostic_thread_id_t) const final override
+  {
+    return m_thread;
+  }
+
+  bool
+  same_function_p (int event_idx_a,
+		   int event_idx_b) const final override
+  {
+    const logical_location *logical_loc_a
+      = m_events[event_idx_a]->get_logical_location ();
+    const logical_location *logical_loc_b
+      = m_events[event_idx_b]->get_logical_location ();
+
+    // TODO:
+    /* Pointer equality, so we may want to uniqify logical loc ptrs.  */
+    return logical_loc_a == logical_loc_b;
+  }
+
+private:
+  libdiagnostics_path_thread m_thread;
+  std::vector<std::unique_ptr<libdiagnostics_path_event>> m_events;
+};
+
+/* 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),
+    m_path (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; }
+
+  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 (const diagnostic_physical_location *loc)
+  {
+    m_rich_loc.set_range (0, as_location_t (loc), SHOW_RANGE_WITH_CARET);
+  }
+
+  void
+  add_location (const diagnostic_physical_location *loc)
+  {
+    m_rich_loc.add_range (as_location_t (loc),
+			  SHOW_RANGE_WITHOUT_CARET);
+  }
+
+  void
+  add_location_with_label (const diagnostic_physical_location *loc,
+			   const char *text)
+  {
+    std::unique_ptr<range_label> label
+      = ::make_unique <impl_range_label> (text);
+    m_rich_loc.add_range (as_location_t (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;
+  }
+
+  diagnostic_execution_path *
+  add_execution_path ()
+  {
+    m_path = ::make_unique<diagnostic_execution_path> ();
+    m_rich_loc.set_path (m_path.get ());
+    return m_path.get ();
+  }
+
+  void
+  take_execution_path (diagnostic_execution_path *path)
+  {
+    m_path = std::unique_ptr<diagnostic_execution_path> (path);
+    m_rich_loc.set_path (path);
+  }
+
+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;
+  std::unique_ptr<diagnostic_execution_path> m_path;
+};
+
+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;
+    case DIAGNOSTIC_LEVEL_SORRY:
+      return DK_SORRY;
+    }
+}
+
+/* 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;
+GCC_DIAGNOSTIC_PUSH_IGNORED(-Wsuggest-attribute=format)
+  diagnostic_set_info (&info, msgid, args, diag.get_rich_location (),
+		       diagnostic_t_from_diagnostic_level (diag.get_level ()));
+GCC_DIAGNOSTIC_POP
+  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.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr));
+}
+
+sink::~sink ()
+{
+  diagnostic_finish (&m_dc);
+}
+
+/* struct diagnostic_text_sink : public sink.  */
+
+static diagnostic_color_rule_t
+get_color_rule (enum diagnostic_colorize colorize)
+{
+  switch (colorize)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_COLORIZE_IF_TTY:
+      return DIAGNOSTICS_COLOR_AUTO;
+      break;
+    case DIAGNOSTIC_COLORIZE_NO:
+      return DIAGNOSTICS_COLOR_NO;
+      break;
+    case DIAGNOSTIC_COLORIZE_YES:
+      return DIAGNOSTICS_COLOR_YES;
+      break;
+    }
+}
+
+diagnostic_text_sink::diagnostic_text_sink (diagnostic_manager &mgr,
+					    FILE *dst_stream,
+					    enum diagnostic_colorize colorize)
+: sink (mgr),
+  m_current_logical_loc (nullptr)
+{
+  m_dc.set_show_cwe (true);
+  m_dc.set_show_rules (true);
+
+  diagnostic_color_init (&m_dc, get_color_rule (colorize));
+  diagnostic_urls_init (&m_dc);
+  m_dc.printer->set_output_stream (dst_stream);
+  diagnostic_starter (&m_dc)
+    = [] (diagnostic_context *context,
+	  const diagnostic_info *info)
+      {
+	diagnostic_text_sink *sink
+	  = static_cast<diagnostic_text_sink *> (context->m_client_aux_data);
+	sink->on_begin_text_diagnostic (info);
+      };
+  m_dc.set_show_cwe (true);
+  m_dc.set_show_rules (true);
+  m_dc.m_show_column = true;
+  m_dc.m_source_printing.enabled = true;
+  m_dc.m_source_printing.colorize_source_p = true;
+
+  /* We don't currently expose a way for clients to manipulate the
+     following.  */
+  m_dc.m_source_printing.show_labels_p = true;
+  m_dc.m_source_printing.show_line_numbers_p = true;
+  m_dc.m_source_printing.min_margin_width = 6;
+  m_dc.set_path_format (DPF_INLINE_EVENTS);
+}
+
+void
+diagnostic_text_sink::set_colorize (enum diagnostic_colorize colorize)
+{
+  diagnostic_color_init (&m_dc, get_color_rule (colorize));
+}
+
+void
+diagnostic_text_sink::on_begin_text_diagnostic (const 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)
+	{
+	  pp_set_prefix (m_dc.printer, nullptr);
+	  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;
+	      // TODO: handle other cases
+	    }
+	}
+    }
+  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,
+			const diagnostic_file *main_input_file,
+			enum diagnostic_sarif_version)
+: sink (mgr)
+{
+  const char *main_input_filename = main_input_file->get_name ();
+  diagnostic_output_format_init_sarif_stream (m_dc,
+					      mgr.get_line_table (),
+					      main_input_filename,
+					      true,
+					      dst_stream);
+}
+
+/* struct diagnostic_manager.  */
+
+void
+diagnostic_manager::write_patch (FILE *dst_stream)
+{
+  pretty_printer pp;
+  pp.set_output_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;
+}
+
+diagnostic_execution_path *
+diagnostic_manager::new_execution_path ()
+{
+  return new diagnostic_execution_path ();
+}
+
+/* Error-checking at the API boundary.  */
+
+#define FAIL_IF_NULL(PTR_ARG) \
+  do {						    \
+    GCC_DIAGNOSTIC_PUSH_IGNORED(-Wnonnull-compare)  \
+    if (!(PTR_ARG)) {				    \
+      fprintf (stderr, "%s: %s must be non-NULL\n", \
+	       __func__, #PTR_ARG);		    \
+      abort ();					    \
+    }						    \
+    GCC_DIAGNOSTIC_POP				    \
+  } while (0)
+
+/* 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)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (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)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (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)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (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)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (value);
+
+  diag_mgr->get_client_version_info ()->m_version_url.set (value);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_text_sink *
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+				  FILE *dst_stream,
+				  enum diagnostic_colorize colorize)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (dst_stream);
+
+  diagnostic_text_sink *result
+    = new diagnostic_text_sink (*diag_mgr, dst_stream, colorize);
+  diag_mgr->add_sink (std::unique_ptr<sink> (result));
+  return result;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink,
+						  int value)
+{
+  FAIL_IF_NULL (text_sink);
+
+  text_sink->get_source_printing_options ().enabled = value;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_text_sink_set_colorize (diagnostic_text_sink *text_sink,
+				   enum diagnostic_colorize colorize)
+{
+  FAIL_IF_NULL (text_sink);
+
+  text_sink->set_colorize (colorize);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink,
+							       int value)
+{
+  FAIL_IF_NULL (text_sink);
+
+  text_sink->get_source_printing_options ().colorize_source_p = value;
+}
+
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+				   FILE *dst_stream,
+				   const diagnostic_file *main_input_file,
+				   enum diagnostic_sarif_version version)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (dst_stream);
+  FAIL_IF_NULL (main_input_file);
+
+  diag_mgr->add_sink (make_unique<sarif_sink> (*diag_mgr,
+					       dst_stream,
+					       main_input_file,
+					       version));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+				FILE *dst_stream)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (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)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (name);
+
+  return diag_mgr->new_file (name, sarif_source_language);
+}
+
+void
+diagnostic_manager_debug_dump_file (diagnostic_manager *,
+				    const diagnostic_file *file,
+				    FILE *out)
+{
+  FAIL_IF_NULL (out);
+  if (file)
+    {
+      if (file->get_sarif_source_language ())
+	{
+	  fprintf (out, "file(name=\"%s\", sarif_source_language=\"%s\")",
+		   file->get_name (),
+		   file->get_sarif_source_language ());
+	}
+      else
+	{
+	  fprintf (out, "file(name=\"%s\")",
+		   file->get_name ());
+	}
+    }
+  else
+    fprintf (out, "(null)");
+}
+
+
+/* Public entrypoint.  */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+						    const diagnostic_file *file,
+						    diagnostic_line_num_t linenum)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (file);
+
+  return diag_mgr->new_location_from_file_and_line (file, linenum);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_physical_location *
+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)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (file);
+
+  return diag_mgr->new_location_from_file_line_column (file,
+						       line_num,
+						       column_num);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+					    const diagnostic_physical_location *loc_caret,
+					    const diagnostic_physical_location *loc_start,
+					    const diagnostic_physical_location *loc_end)
+{
+  FAIL_IF_NULL (diag_mgr);
+
+  return diag_mgr->new_location_from_range (loc_caret,
+					    loc_start,
+					    loc_end);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr,
+					const diagnostic_physical_location *loc,
+					FILE *out)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (out);
+
+  if (loc)
+    {
+      const location_t cpplib_loc = as_location_t (loc);
+      diag_mgr->set_line_table_global ();
+      const expanded_location exp_loc (expand_location (cpplib_loc));
+
+      diagnostic_context dc;
+      diagnostic_initialize (&dc, 0);
+      dc.m_show_column = true;
+
+      label_text loc_text = dc.get_location_text (exp_loc);
+      fprintf (out, "%s", loc_text.get ());
+
+      diagnostic_finish (&dc);
+    }
+  else
+    fprintf (out, "(null)");
+}
+
+/* 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)
+{
+  FAIL_IF_NULL (diag_mgr);
+
+  return diag_mgr->new_logical_location (kind,
+					 parent,
+					 short_name,
+					 fully_qualified_name,
+					 decorated_name);
+}
+
+void
+diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr,
+						const diagnostic_logical_location *loc,
+						FILE *out)
+{
+  FAIL_IF_NULL (diag_mgr);
+  FAIL_IF_NULL (out);
+
+  if (loc)
+    {
+      fprintf (out, "logical_location(kind=");
+      switch (loc->get_external_kind ())
+	{
+	default:
+	  gcc_unreachable ();
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+	  fprintf (out, "function");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+	  fprintf (out, "member");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+	  fprintf (out, "module");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+	  fprintf (out, "namespace");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+	  fprintf (out, "file");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+	  fprintf (out, "return_type");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+	  fprintf (out, "parameter");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+	  fprintf (out, "variable");
+	  break;
+	}
+      if (const diagnostic_logical_location *parent = loc->get_parent ())
+	diagnostic_manager_debug_dump_logical_location (diag_mgr,
+							parent,
+							out);
+      if (const char *val = loc->get_short_name ())
+	fprintf (out, ", short_name=\"%s\"", val);
+      if (const char *val = loc->get_name_with_scope ())
+	fprintf (out, ", fully_qualified_name=\"%s\"", val);
+      if (const char *val = loc->get_internal_name ())
+	fprintf (out, ", decorated_name=\"%s\"", val);
+      fprintf (out, ")");
+    }
+  else
+    fprintf (out, "(null)");
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+{
+  FAIL_IF_NULL (diag_mgr);
+  diag_mgr->begin_group ();
+}
+
+/* Public entrypoint.  */
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+{
+  FAIL_IF_NULL (diag_mgr);
+  diag_mgr->end_group ();
+}
+
+/* Public entrypoint.  */
+
+diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+		  enum diagnostic_level level)
+{
+  FAIL_IF_NULL (diag_mgr);
+
+  return new diagnostic (*diag_mgr, level);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_cwe (diagnostic *diag,
+		    unsigned cwe_id)
+{
+  FAIL_IF_NULL (diag);
+
+  diag->set_cwe (cwe_id);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_rule (diagnostic *diag,
+		     const char *title,
+		     const char *url)
+{
+  FAIL_IF_NULL (diag);
+
+  diag->add_rule (title, url);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_location (diagnostic *diag,
+			 const diagnostic_physical_location *loc)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+  diag->set_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location (diagnostic *diag,
+			 const diagnostic_physical_location *loc)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+  diag->add_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location_with_label (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *text)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  FAIL_IF_NULL (text);
+
+  diag->add_location_with_label (loc, text);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_logical_location (diagnostic *diag,
+				 const diagnostic_logical_location *logical_loc)
+{
+  FAIL_IF_NULL (diag);
+
+  diag->set_logical_location (logical_loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+					  const diagnostic_physical_location *loc,
+					  const char *addition)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  FAIL_IF_NULL (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_before (as_location_t (loc),
+						       addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+					 const diagnostic_physical_location *loc,
+					 const char *addition)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  FAIL_IF_NULL (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_after (as_location_t (loc),
+						      addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *replacement)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  FAIL_IF_NULL (replacement);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_replace (as_location_t (loc),
+						 replacement);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+				   const diagnostic_physical_location *loc)
+{
+  FAIL_IF_NULL (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_remove (as_location_t (loc));
+}
+
+/* Public entrypoint.  */
+
+diagnostic_execution_path *
+diagnostic_add_execution_path (diagnostic *diag)
+{
+  FAIL_IF_NULL (diag);
+
+  return diag->add_execution_path ();
+}
+
+/* Public entrypoint.  */
+
+diagnostic_execution_path *
+diagnostic_manager_new_execution_path (diagnostic_manager *manager)
+{
+  FAIL_IF_NULL (manager);
+
+  return manager->new_execution_path ();
+}
+
+/* Public entrypoint.  */
+
+extern void
+diagnostic_take_execution_path (diagnostic *diag,
+				diagnostic_execution_path *path)
+{
+  FAIL_IF_NULL (diag);
+  FAIL_IF_NULL (path);
+
+  return diag->take_execution_path (path);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_execution_path_release (diagnostic_execution_path *path)
+{
+  delete path;
+}
+
+/* Public entrypoint.  */
+
+diagnostic_event_id
+diagnostic_execution_path_add_event (diagnostic_execution_path *path,
+				     const diagnostic_physical_location *physical_loc,
+				     const diagnostic_logical_location *logical_loc,
+				     unsigned stack_depth,
+				     const char *gmsgid, ...)
+{
+  FAIL_IF_NULL (path);
+  FAIL_IF_NULL (gmsgid);
+
+  va_list args;
+  va_start (args, gmsgid);
+  diagnostic_event_id_t result = path->add_event_va (physical_loc,
+						     logical_loc,
+						     stack_depth,
+						     gmsgid, &args);
+  va_end (args);
+
+  return as_diagnostic_event_id (result);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_event_id
+diagnostic_execution_path_add_event_va (diagnostic_execution_path *path,
+					const diagnostic_physical_location *physical_loc,
+					const diagnostic_logical_location *logical_loc,
+					unsigned stack_depth,
+					const char *gmsgid,
+					va_list *args)
+{
+  FAIL_IF_NULL (path);
+  FAIL_IF_NULL (gmsgid);
+
+  diagnostic_event_id_t result = path->add_event_va (physical_loc,
+						     logical_loc,
+						     stack_depth,
+						     gmsgid, args);
+  return as_diagnostic_event_id (result);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_finish (diagnostic *diag, const char *gmsgid, ...)
+{
+  FAIL_IF_NULL (diag);
+
+  va_list args;
+  va_start (args, gmsgid);
+  diagnostic_finish_va (diag, gmsgid, &args);
+  va_end (args);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_finish_va (diagnostic *diag, const char *gmsgid, va_list *args)
+{
+  FAIL_IF_NULL (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;
+  diag->get_manager ().emit (*diag, gmsgid, args);
+  delete diag;
+}
diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map
new file mode 100644
index 000000000000..cc32b76d016a
--- /dev/null
+++ b/gcc/libdiagnostics.map
@@ -0,0 +1,72 @@ 
+# Linker script for libdiagnostics.so
+#   Copyright (C) 2023-2024 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_text_sink_set_source_printing_enabled;
+    diagnostic_text_sink_set_colorize;
+    diagnostic_text_sink_set_labelled_source_colorization_enabled;
+    diagnostic_manager_add_sarif_sink;
+    diagnostic_manager_write_patch;
+    diagnostic_manager_new_file;
+    diagnostic_manager_debug_dump_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_debug_dump_location;
+    diagnostic_manager_new_logical_location;
+    diagnostic_manager_debug_dump_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_add_execution_path;
+    diagnostic_manager_new_execution_path;
+    diagnostic_take_execution_path;
+    diagnostic_execution_path_release;
+    diagnostic_execution_path_add_event;
+    diagnostic_execution_path_add_event_va;
+
+    diagnostic_finish;
+    diagnostic_finish_va;
+
+  local: *;
+};