https://gcc.gnu.org/g:5f88037941d6fbfef7281f767dcc31a5aa61f0cd

commit r16-2548-g5f88037941d6fbfef7281f767dcc31a5aa61f0cd
Author: David Malcolm <dmalc...@redhat.com>
Date:   Fri Jul 25 15:13:44 2025 -0400

    diagnostics: move file_cache from input.{cc,h} to 
diagnostics/file-cache.{cc,h}
    
    No functional change intended.
    
    gcc/ChangeLog:
            * Makefile.in (OBJS-libcommon): Add diagnostics/file-cache.o.
            * diagnostics/changes.cc: Update for file_cache and char_span
            moving from input.h to diagnostics/file-cache.h and into the
            "diagnostics::" namespace.
            * diagnostics/context.cc: Likewise.
            * diagnostics/diagnostics-selftests.cc: Likewise.
            * diagnostics/diagnostics-selftests.h: Likewise.
            * diagnostics/file-cache.cc: New file, based on the file_cache
            and file_cache_slot material in input.cc.
            * diagnostics/file-cache.h: Likewise for input.h.
            * diagnostics/selftest-source-printing.h: Update for file_cache
            and char_span moving from input.h to diagnostics/file-cache.h and
            into the "diagnostics::" namespace.
            * diagnostics/source-printing.cc: Likewise.
            * final.cc: Likewise.
            * gcc-rich-location.cc: Likewise.
            * input.cc (default_charset_callback): Move to
            diagnostics/file-cache.cc.
            (file_cache::initialize_input_context): Likewise.
            (class file_cache_slot): Likewise.
            (file_cache::tune): Likewise.
            (file_cache::lookup_file): Likewise.
            (file_cache::forcibly_evict_file): Likewise.
            (file_cache::missing_trailing_newline_p): Likewise.
            (file_cache::add_buffered_content): Likewise.
            (file_cache::evicted_cache_tab_entry): Likewise.
            (file_cache::add_file): Likewise.
            (file_cache::file_cache): Likewise.
            (file_cache::dump): Likewise.
            (file_cache::dump): Likewise.
            (file_cache::lookup_or_add_file): Likewise.
            (find_end_of_line): Likewise.
            (file_cache::get_source_line): Likewise.
            (check_line): Likewise.
            (test_replacement): Likewise.
            (test_reading_source_line): Likewise.
            (test_reading_source_buffer): Likewise.
            * input.h (class char_span): Move to diagnostics/file-cache.h and
            into the "diagnostics::" namespace.
            (class file_cache_slot): Likewise.
            (class file_cache): Likewise.
            * libgdiagnostics.cc: Update for file_cache and char_span moving
            from input.h to diagnostics/file-cache.h and into the
            "diagnostics::" namespace.
            * selftest.cc: Likewise.
            * selftest.h: Likewise.
            * substring-locations.h: Likewise.
            * toplev.cc: Likewise.
    
    gcc/c-family/ChangeLog:
            * c-format.cc: Update for file_cache and char_span moving from
            input.h to diagnostics/file-cache.h and into the "diagnostics::"
            namespace.
            * c-indentation.cc: Likewise.
    
    gcc/testsuite/ChangeLog:
            * gcc.dg/plugin/diagnostic_plugin_test_show_locus.cc: Update for
            file_cache and char_span moving from input.h to
            diagnostics/file-cache.h and into the "diagnostics::" namespace.
    
    Signed-off-by: David Malcolm <dmalc...@redhat.com>

Diff:
---
 gcc/Makefile.in                                    |    1 +
 gcc/c-family/c-format.cc                           |    6 +-
 gcc/c-family/c-indentation.cc                      |   19 +-
 gcc/diagnostics/changes.cc                         |    1 +
 gcc/diagnostics/context.cc                         |    1 +
 gcc/diagnostics/diagnostics-selftests.cc           |    1 +
 gcc/diagnostics/diagnostics-selftests.h            |    1 +
 gcc/diagnostics/file-cache.cc                      | 1083 ++++++++++++++++++++
 gcc/diagnostics/file-cache.h                       |  125 +++
 gcc/diagnostics/selftest-source-printing.h         |    1 +
 gcc/diagnostics/source-printing.cc                 |   64 +-
 gcc/final.cc                                       |    3 +-
 gcc/gcc-rich-location.cc                           |    7 +-
 gcc/input.cc                                       | 1069 +------------------
 gcc/input.h                                        |  104 +-
 gcc/libgdiagnostics.cc                             |    3 +-
 gcc/selftest.cc                                    |    5 +-
 gcc/selftest.h                                     |    8 +-
 gcc/substring-locations.h                          |    2 +-
 .../plugin/diagnostic_plugin_test_show_locus.cc    |    8 +-
 gcc/toplev.cc                                      |    1 +
 21 files changed, 1308 insertions(+), 1205 deletions(-)

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 24d7b885319c..7314a3b42252 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1858,6 +1858,7 @@ OBJS-libcommon = \
        diagnostics/color.o \
        diagnostics/context.o \
        diagnostics/digraphs.o \
+       diagnostics/file-cache.o \
        diagnostics/output-spec.o \
        diagnostics/html-sink.o \
        diagnostics/sarif-sink.o \
diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
index 6934087ce5c3..c8e00b27181c 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "substring-locations.h"
 #include "selftest.h"
 #include "diagnostics/selftest-context.h"
+#include "diagnostics/file-cache.h"
 #include "builtins.h"
 #include "attribs.h"
 #include "c-family/c-type-mismatch.h"
@@ -4634,7 +4635,7 @@ get_corrected_substring (const substring_loc &fmt_loc,
   if (caret.column > finish.column)
     return NULL;
 
-  char_span line
+  diagnostics::char_span line
     = global_dc->get_file_cache ().get_source_line (start.file, start.line);
   if (!line)
     return NULL;
@@ -4646,7 +4647,8 @@ get_corrected_substring (const substring_loc &fmt_loc,
      specification, up to the (but not including) the length modifier.
      In the above example, this would be "%-+*.*".  */
   int length_up_to_type = caret.column - start.column;
-  char_span prefix_span = line.subspan (start.column - 1, length_up_to_type);
+  diagnostics::char_span prefix_span
+    = line.subspan (start.column - 1, length_up_to_type);
   char *prefix = prefix_span.xstrdup ();
 
   /* Now attempt to generate a suggestion for the rest of the specification
diff --git a/gcc/c-family/c-indentation.cc b/gcc/c-family/c-indentation.cc
index 2e8261d4977b..bb214fc259b7 100644
--- a/gcc/c-family/c-indentation.cc
+++ b/gcc/c-family/c-indentation.cc
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-indentation.h"
 #include "selftest.h"
 #include "diagnostic.h"
+#include "diagnostics/file-cache.h"
 
 /* Round up VIS_COLUMN to nearest tab stop. */
 
@@ -45,13 +46,13 @@ next_tab_stop (unsigned int vis_column, unsigned int 
tab_width)
    on the line (up to or before EXPLOC).  */
 
 static bool
-get_visual_column (file_cache &fc,
+get_visual_column (diagnostics::file_cache &fc,
                   expanded_location exploc,
                   unsigned int *out,
                   unsigned int *first_nws,
                   unsigned int tab_width)
 {
-  char_span line = fc.get_source_line (exploc.file, exploc.line);
+  diagnostics::char_span line = fc.get_source_line (exploc.file, exploc.line);
   if (!line)
     return false;
   if ((size_t)exploc.column > line.length ())
@@ -88,14 +89,14 @@ get_visual_column (file_cache &fc,
    Otherwise, return false, leaving *FIRST_NWS untouched.  */
 
 static bool
-get_first_nws_vis_column (file_cache &fc,
+get_first_nws_vis_column (diagnostics::file_cache &fc,
                          const char *file, int line_num,
                          unsigned int *first_nws,
                          unsigned int tab_width)
 {
   gcc_assert (first_nws);
 
-  char_span line = fc.get_source_line (file, line_num);
+  diagnostics::char_span line = fc.get_source_line (file, line_num);
   if (!line)
     return false;
   unsigned int vis_column = 0;
@@ -160,7 +161,7 @@ get_first_nws_vis_column (file_cache &fc,
    Return true if such an unindent/outdent is detected.  */
 
 static bool
-detect_intervening_unindent (file_cache &fc,
+detect_intervening_unindent (diagnostics::file_cache &fc,
                             const char *file,
                             int body_line,
                             int next_stmt_line,
@@ -335,7 +336,7 @@ should_warn_for_misleading_indentation (const 
token_indent_info &guard_tinfo,
   if (next_stmt_exploc.file != body_exploc.file)
     return false;
 
-  file_cache &fc = global_dc->get_file_cache ();
+  diagnostics::file_cache &fc = global_dc->get_file_cache ();
 
   /* If NEXT_STMT_LOC and BODY_LOC are on the same line, consider
      the location of the guard.
@@ -691,7 +692,7 @@ test_next_tab_stop ()
 
 static void
 assert_get_visual_column_succeeds (const location &loc,
-                                  file_cache &fc,
+                                  diagnostics::file_cache &fc,
                                   const char *file, int line, int column,
                                   const unsigned int tab_width,
                                   unsigned int expected_visual_column,
@@ -735,7 +736,7 @@ assert_get_visual_column_succeeds (const location &loc,
 
 static void
 assert_get_visual_column_fails (const location &loc,
-                               file_cache &fc,
+                               diagnostics::file_cache &fc,
                                const char *file, int line, int column,
                                const unsigned int tab_width)
 {
@@ -783,7 +784,7 @@ test_get_visual_column ()
                         "\t line 2\n");
   line_table_test ltt;
   temp_source_file tmp (SELFTEST_LOCATION, ".txt", content);
-  file_cache fc;
+  diagnostics::file_cache fc;
 
   const unsigned int tab_width = 8;
   const char *file = tmp.get_filename ();
diff --git a/gcc/diagnostics/changes.cc b/gcc/diagnostics/changes.cc
index 4dce4c7b64fe..290d6022b5ae 100644
--- a/gcc/diagnostics/changes.cc
+++ b/gcc/diagnostics/changes.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostics/changes.h"
 #include "pretty-print.h"
 #include "diagnostics/color.h"
+#include "diagnostics/file-cache.h"
 #include "selftest.h"
 
 namespace diagnostics {
diff --git a/gcc/diagnostics/context.cc b/gcc/diagnostics/context.cc
index f91f13f5d7df..64bb315bd1fa 100644
--- a/gcc/diagnostics/context.cc
+++ b/gcc/diagnostics/context.cc
@@ -49,6 +49,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "pretty-print-urlifier.h"
 #include "diagnostics/logical-locations.h"
 #include "diagnostics/buffering.h"
+#include "diagnostics/file-cache.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
diff --git a/gcc/diagnostics/diagnostics-selftests.cc 
b/gcc/diagnostics/diagnostics-selftests.cc
index b6fcf08aac1c..94a212a6c93a 100644
--- a/gcc/diagnostics/diagnostics-selftests.cc
+++ b/gcc/diagnostics/diagnostics-selftests.cc
@@ -40,6 +40,7 @@ void
 run_diagnostics_selftests ()
 {
   color_cc_tests ();
+  file_cache_cc_tests ();
   source_printing_cc_tests ();
   html_sink_cc_tests ();
   sarif_sink_cc_tests ();
diff --git a/gcc/diagnostics/diagnostics-selftests.h 
b/gcc/diagnostics/diagnostics-selftests.h
index 501814d8ffb2..994ebad52804 100644
--- a/gcc/diagnostics/diagnostics-selftests.h
+++ b/gcc/diagnostics/diagnostics-selftests.h
@@ -36,6 +36,7 @@ extern void changes_cc_tests ();
 extern void color_cc_tests ();
 extern void context_cc_tests ();
 extern void digraphs_cc_tests ();
+extern void file_cache_cc_tests ();
 extern void html_sink_cc_tests ();
 extern void lazy_paths_cc_tests ();
 extern void output_spec_cc_tests ();
diff --git a/gcc/diagnostics/file-cache.cc b/gcc/diagnostics/file-cache.cc
new file mode 100644
index 000000000000..febeb031f1bf
--- /dev/null
+++ b/gcc/diagnostics/file-cache.cc
@@ -0,0 +1,1083 @@
+/* Caching input files for use by diagnostics.
+   Copyright (C) 2004-2025 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"
+#include "system.h"
+#include "coretypes.h"
+#include "cpplib.h"
+#include "diagnostics/file-cache.h"
+#include "selftest.h"
+
+#ifndef HAVE_ICONV
+#define HAVE_ICONV 0
+#endif
+
+namespace diagnostics {
+
+/* Input charset configuration.  */
+static const char *default_charset_callback (const char *)
+{
+  return nullptr;
+}
+
+void
+file_cache::initialize_input_context (diagnostic_input_charset_callback ccb,
+                                     bool should_skip_bom)
+{
+  m_input_context.ccb = (ccb ? ccb : default_charset_callback);
+  m_input_context.should_skip_bom = should_skip_bom;
+}
+
+/* This is a cache used by get_next_line to store the content of a
+   file to be searched for file lines.  */
+class file_cache_slot
+{
+public:
+  file_cache_slot ();
+  ~file_cache_slot ();
+
+  void dump (FILE *out, int indent) const;
+  void DEBUG_FUNCTION dump () const { dump (stderr, 0); }
+
+  bool read_line_num (size_t line_num,
+                     char ** line, ssize_t *line_len);
+
+  /* Accessors.  */
+  const char *get_file_path () const { return m_file_path; }
+  unsigned get_use_count () const { return m_use_count; }
+  bool missing_trailing_newline_p () const
+  {
+    return m_missing_trailing_newline;
+  }
+  char_span get_full_file_content ();
+
+  void inc_use_count () { m_use_count++; }
+
+  bool create (const file_cache::input_context &in_context,
+              const char *file_path, FILE *fp, unsigned highest_use_count);
+  void evict ();
+  void set_content (const char *buf, size_t sz);
+
+  static size_t tune (size_t line_record_size_)
+  {
+    size_t ret = line_record_size;
+    line_record_size = line_record_size_;
+    return ret;
+  }
+
+ private:
+  /* These are information used to store a line boundary.  */
+  class line_info
+  {
+  public:
+    /* The line number.  It starts from 1.  */
+    size_t line_num;
+
+    /* The position (byte count) of the beginning of the line,
+       relative to the file data pointer.  This starts at zero.  */
+    size_t start_pos;
+
+    /* The position (byte count) of the last byte of the line.  This
+       normally points to the '\n' character, or to one byte after the
+       last byte of the file, if the file doesn't contain a '\n'
+       character.  */
+    size_t end_pos;
+
+    line_info (size_t l, size_t s, size_t e)
+      : line_num (l), start_pos (s), end_pos (e)
+    {}
+
+    line_info ()
+      :line_num (0), start_pos (0), end_pos (0)
+    {}
+
+    static bool less_than(const line_info &a, const line_info &b)
+    {
+      return a.line_num < b.line_num;
+    }
+  };
+
+  bool needs_read_p () const;
+  bool needs_grow_p () const;
+  void maybe_grow ();
+  bool read_data ();
+  bool maybe_read_data ();
+  bool get_next_line (char **line, ssize_t *line_len);
+  bool read_next_line (char ** line, ssize_t *line_len);
+  bool goto_next_line ();
+
+  static const size_t buffer_size = 4 * 1024;
+  static size_t line_record_size;
+  static size_t recent_cached_lines_shift;
+
+  /* The number of time this file has been accessed.  This is used
+     to designate which file cache to evict from the cache
+     array.  */
+  unsigned m_use_count;
+
+  /* The file_path is the key for identifying a particular file in
+     the cache.  This copy is owned by the slot.  */
+  char *m_file_path;
+
+  FILE *m_fp;
+
+  /* True when an read error happened.  */
+  bool m_error;
+
+  /* This points to the content of the file that we've read so
+     far.  */
+  char *m_data;
+
+  /* The allocated buffer to be freed may start a little earlier than DATA,
+     e.g. if a UTF8 BOM was skipped at the beginning.  */
+  int m_alloc_offset;
+
+  /*  The size of the DATA array above.*/
+  size_t m_size;
+
+  /* The number of bytes read from the underlying file so far.  This
+     must be less (or equal) than SIZE above.  */
+  size_t m_nb_read;
+
+  /* The index of the beginning of the current line.  */
+  size_t m_line_start_idx;
+
+  /* The number of the previous line read.  This starts at 1.  Zero
+     means we've read no line so far.  */
+  size_t m_line_num;
+
+  /* Could this file be missing a trailing newline on its final line?
+     Initially true (to cope with empty files), set to true/false
+     as each line is read.  */
+  bool m_missing_trailing_newline;
+
+  /* This is a record of the beginning and end of the lines we've seen
+     while reading the file.  This is useful to avoid walking the data
+     from the beginning when we are asked to read a line that is
+     before LINE_START_IDX above.  When the lines exceed line_record_size
+     this is scaled down dynamically, with the line_info becoming anchors.  */
+  vec<line_info, va_heap> m_line_record;
+
+  /* A cache of the recently seen lines. This is maintained as a ring
+     buffer. */
+  vec<line_info, va_heap> m_line_recent;
+
+  /* First and last valid entry in m_line_recent.  */
+  size_t m_line_recent_last, m_line_recent_first;
+
+  void offset_buffer (int offset)
+  {
+    gcc_assert (offset < 0 ? m_alloc_offset + offset >= 0
+               : (size_t) offset <= m_size);
+    gcc_assert (m_data);
+    m_alloc_offset += offset;
+    m_data += offset;
+    m_size -= offset;
+  }
+
+};
+
+size_t file_cache_slot::line_record_size = 0;
+size_t file_cache_slot::recent_cached_lines_shift = 8;
+
+/* Tune file_cache.  */
+void
+file_cache::tune (size_t num_file_slots, size_t lines)
+{
+  if (file_cache_slot::tune (lines) != lines
+      || m_num_file_slots != num_file_slots)
+    {
+      delete[] m_file_slots;
+      m_file_slots = new file_cache_slot[num_file_slots];
+    }
+  m_num_file_slots = num_file_slots;
+}
+
+static const char *
+find_end_of_line (const char *s, size_t len);
+
+/* Lookup the cache used for the content of a given file accessed by
+   caret diagnostic.  Return the found cached file, or NULL if no
+   cached file was found.  */
+
+file_cache_slot *
+file_cache::lookup_file (const char *file_path)
+{
+  gcc_assert (file_path);
+
+  /* This will contain the found cached file.  */
+  file_cache_slot *r = NULL;
+  for (unsigned i = 0; i < m_num_file_slots; ++i)
+    {
+      file_cache_slot *c = &m_file_slots[i];
+      if (c->get_file_path () && !strcmp (c->get_file_path (), file_path))
+       {
+         c->inc_use_count ();
+         r = c;
+       }
+    }
+
+  if (r)
+    r->inc_use_count ();
+
+  return r;
+}
+
+/* Purge any mention of FILENAME from the cache of files used for
+   printing source code.  For use in selftests when working
+   with tempfiles.  */
+
+void
+file_cache::forcibly_evict_file (const char *file_path)
+{
+  gcc_assert (file_path);
+
+  file_cache_slot *r = lookup_file (file_path);
+  if (!r)
+    /* Not found.  */
+    return;
+
+  r->evict ();
+}
+
+/* Determine if FILE_PATH missing a trailing newline on its final line.
+   Only valid to call once all of the file has been loaded, by
+   requesting a line number beyond the end of the file.  */
+
+bool
+file_cache::missing_trailing_newline_p (const char *file_path)
+{
+  gcc_assert (file_path);
+
+  file_cache_slot *r = lookup_or_add_file (file_path);
+  return r->missing_trailing_newline_p ();
+}
+
+void
+file_cache::add_buffered_content (const char *file_path,
+                                 const char *buffer,
+                                 size_t sz)
+{
+  gcc_assert (file_path);
+
+  file_cache_slot *r = lookup_file (file_path);
+  if (!r)
+    {
+      unsigned highest_use_count = 0;
+      r = evicted_cache_tab_entry (&highest_use_count);
+      if (!r->create (m_input_context, file_path, nullptr, highest_use_count))
+       return;
+    }
+
+  r->set_content (buffer, sz);
+}
+
+void
+file_cache_slot::evict ()
+{
+  free (m_file_path);
+  m_file_path = NULL;
+  if (m_fp)
+    fclose (m_fp);
+  m_error = false;
+  m_fp = NULL;
+  m_nb_read = 0;
+  m_line_start_idx = 0;
+  m_line_num = 0;
+  m_line_record.truncate (0);
+  m_line_recent_first = 0;
+  m_line_recent_last = 0;
+  m_use_count = 0;
+  m_missing_trailing_newline = true;
+}
+
+/* Return the file cache that has been less used, recently, or the
+   first empty one.  If HIGHEST_USE_COUNT is non-null,
+   *HIGHEST_USE_COUNT is set to the highest use count of the entries
+   in the cache table.  */
+
+file_cache_slot*
+file_cache::evicted_cache_tab_entry (unsigned *highest_use_count)
+{
+  file_cache_slot *to_evict = &m_file_slots[0];
+  unsigned huc = to_evict->get_use_count ();
+  for (unsigned i = 1; i < m_num_file_slots; ++i)
+    {
+      file_cache_slot *c = &m_file_slots[i];
+      bool c_is_empty = (c->get_file_path () == NULL);
+
+      if (c->get_use_count () < to_evict->get_use_count ()
+         || (to_evict->get_file_path () && c_is_empty))
+       /* We evict C because it's either an entry with a lower use
+          count or one that is empty.  */
+       to_evict = c;
+
+      if (huc < c->get_use_count ())
+       huc = c->get_use_count ();
+
+      if (c_is_empty)
+       /* We've reached the end of the cache; subsequent elements are
+          all empty.  */
+       break;
+    }
+
+  if (highest_use_count)
+    *highest_use_count = huc;
+
+  return to_evict;
+}
+
+/* Create the cache used for the content of a given file to be
+   accessed by caret diagnostic.  This cache is added to an array of
+   cache and can be retrieved by lookup_file_in_cache_tab.  This
+   function returns the created cache.  Note that only the last
+   m_num_file_slots files are cached.
+
+   This can return nullptr if the FILE_PATH can't be opened for
+   reading, or if the content can't be converted to the input_charset.  */
+
+file_cache_slot*
+file_cache::add_file (const char *file_path)
+{
+
+  FILE *fp = fopen (file_path, "r");
+  if (fp == NULL)
+    return NULL;
+
+  unsigned highest_use_count = 0;
+  file_cache_slot *r = evicted_cache_tab_entry (&highest_use_count);
+  if (!r->create (m_input_context, file_path, fp, highest_use_count))
+    return NULL;
+  return r;
+}
+
+/* Get a borrowed char_span to the full content of this file
+   as decoded according to the input charset, encoded as UTF-8.  */
+
+char_span
+file_cache_slot::get_full_file_content ()
+{
+  char *line;
+  ssize_t line_len;
+  while (get_next_line (&line, &line_len))
+    {
+    }
+  return char_span (m_data, m_nb_read);
+}
+
+/* Populate this slot for use on FILE_PATH and FP, dropping any
+   existing cached content within it.  */
+
+bool
+file_cache_slot::create (const file_cache::input_context &in_context,
+                        const char *file_path, FILE *fp,
+                        unsigned highest_use_count)
+{
+  m_file_path = file_path ? xstrdup (file_path) : nullptr;
+  if (m_fp)
+    fclose (m_fp);
+  m_error = false;
+  m_fp = fp;
+  if (m_alloc_offset)
+    offset_buffer (-m_alloc_offset);
+  m_nb_read = 0;
+  m_line_start_idx = 0;
+  m_line_num = 0;
+  m_line_recent_first = 0;
+  m_line_recent_last = 0;
+  m_line_record.truncate (0);
+  /* Ensure that this cache entry doesn't get evicted next time
+     add_file_to_cache_tab is called.  */
+  m_use_count = ++highest_use_count;
+  m_missing_trailing_newline = true;
+
+
+  /* Check the input configuration to determine if we need to do any
+     transformations, such as charset conversion or BOM skipping.  */
+  if (const char *input_charset = in_context.ccb (file_path))
+    {
+      /* Need a full-blown conversion of the input charset.  */
+      fclose (m_fp);
+      m_fp = NULL;
+      const cpp_converted_source cs
+       = cpp_get_converted_source (file_path, input_charset);
+      if (!cs.data)
+       return false;
+      if (m_data)
+       XDELETEVEC (m_data);
+      m_data = cs.data;
+      m_nb_read = m_size = cs.len;
+      m_alloc_offset = cs.data - cs.to_free;
+    }
+  else if (in_context.should_skip_bom)
+    {
+      if (read_data ())
+       {
+         const int offset = cpp_check_utf8_bom (m_data, m_nb_read);
+         offset_buffer (offset);
+         m_nb_read -= offset;
+       }
+    }
+
+  return true;
+}
+
+void
+file_cache_slot::set_content (const char *buf, size_t sz)
+{
+  m_data = (char *)xmalloc (sz);
+  memcpy (m_data, buf, sz);
+  m_nb_read = m_size = sz;
+  m_alloc_offset = 0;
+
+  if (m_fp)
+    {
+      fclose (m_fp);
+      m_fp = nullptr;
+    }
+}
+
+/* file_cache's ctor.  */
+
+file_cache::file_cache ()
+: m_num_file_slots (16), m_file_slots (new file_cache_slot[m_num_file_slots])
+{
+  initialize_input_context (nullptr, false);
+}
+
+/* file_cache's dtor.  */
+
+file_cache::~file_cache ()
+{
+  delete[] m_file_slots;
+}
+
+void
+file_cache::dump (FILE *out, int indent) const
+{
+  for (size_t i = 0; i < m_num_file_slots; ++i)
+    {
+      fprintf (out, "%*sslot[%i]:\n", indent, "", (int)i);
+      m_file_slots[i].dump (out, indent + 2);
+    }
+}
+
+void
+file_cache::dump () const
+{
+  dump (stderr, 0);
+}
+
+/* Lookup the cache used for the content of a given file accessed by
+   caret diagnostic.  If no cached file was found, create a new cache
+   for this file, add it to the array of cached file and return
+   it.
+
+   This can return nullptr on a cache miss if FILE_PATH can't be opened for
+   reading, or if the content can't be converted to the input_charset.  */
+
+file_cache_slot*
+file_cache::lookup_or_add_file (const char *file_path)
+{
+  file_cache_slot *r = lookup_file (file_path);
+  if (r == NULL)
+    r = add_file (file_path);
+  return r;
+}
+
+/* Default constructor for a cache of file used by caret
+   diagnostic.  */
+
+file_cache_slot::file_cache_slot ()
+: m_use_count (0), m_file_path (NULL), m_fp (NULL), m_error (false), m_data 
(0),
+  m_alloc_offset (0), m_size (0), m_nb_read (0), m_line_start_idx (0),
+  m_line_num (0), m_missing_trailing_newline (true),
+  m_line_recent_last (0), m_line_recent_first (0)
+{
+  m_line_record.create (0);
+  m_line_recent.create (1U << recent_cached_lines_shift);
+  for (int i = 0; i < 1 << recent_cached_lines_shift; i++)
+    m_line_recent.quick_push (file_cache_slot::line_info (0, 0, 0));
+}
+
+/* Destructor for a cache of file used by caret diagnostic.  */
+
+file_cache_slot::~file_cache_slot ()
+{
+  free (m_file_path);
+  if (m_fp)
+    {
+      fclose (m_fp);
+      m_fp = NULL;
+    }
+  if (m_data)
+    {
+      offset_buffer (-m_alloc_offset);
+      XDELETEVEC (m_data);
+      m_data = 0;
+    }
+  m_line_record.release ();
+  m_line_recent.release ();
+}
+
+void
+file_cache_slot::dump (FILE *out, int indent) const
+{
+  if (!m_file_path)
+    {
+      fprintf (out, "%*s(unused)\n", indent, "");
+      return;
+    }
+  fprintf (out, "%*sfile_path: %s\n", indent, "", m_file_path);
+  fprintf (out, "%*sfp: %p\n", indent, "", (void *)m_fp);
+  fprintf (out, "%*sneeds_read_p: %i\n", indent, "", (int)needs_read_p ());
+  fprintf (out, "%*sneeds_grow_p: %i\n", indent, "", (int)needs_grow_p ());
+  fprintf (out, "%*suse_count: %i\n", indent, "", m_use_count);
+  fprintf (out, "%*ssize: %zi\n", indent, "", m_size);
+  fprintf (out, "%*snb_read: %zi\n", indent, "", m_nb_read);
+  fprintf (out, "%*sstart_line_idx: %zi\n", indent, "", m_line_start_idx);
+  fprintf (out, "%*sline_num: %zi\n", indent, "", m_line_num);
+  fprintf (out, "%*smissing_trailing_newline: %i\n",
+          indent, "", (int)m_missing_trailing_newline);
+  fprintf (out, "%*sline records (%i):\n",
+          indent, "", m_line_record.length ());
+  int idx = 0;
+  for (auto &line : m_line_record)
+    fprintf (out, "%*s[%i]: line %zi: byte offsets: %zi-%zi\n",
+            indent + 2, "",
+            idx++, line.line_num, line.start_pos, line.end_pos);
+}
+
+/* Returns TRUE iff the cache would need to be filled with data coming
+   from the file.  That is, either the cache is empty or full or the
+   current line is empty.  Note that if the cache is full, it would
+   need to be extended and filled again.  */
+
+bool
+file_cache_slot::needs_read_p () const
+{
+  return m_fp && (m_nb_read == 0
+         || m_nb_read == m_size
+         || (m_line_start_idx >= m_nb_read - 1));
+}
+
+/*  Return TRUE iff the cache is full and thus needs to be
+    extended.  */
+
+bool
+file_cache_slot::needs_grow_p () const
+{
+  return m_nb_read == m_size;
+}
+
+/* Grow the cache if it needs to be extended.  */
+
+void
+file_cache_slot::maybe_grow ()
+{
+  if (!needs_grow_p ())
+    return;
+
+  if (!m_data)
+    {
+      gcc_assert (m_size == 0 && m_alloc_offset == 0);
+      m_size = buffer_size;
+      m_data = XNEWVEC (char, m_size);
+    }
+  else
+    {
+      const int offset = m_alloc_offset;
+      offset_buffer (-offset);
+      m_size *= 2;
+      m_data = XRESIZEVEC (char, m_data, m_size);
+      offset_buffer (offset);
+    }
+}
+
+/*  Read more data into the cache.  Extends the cache if need be.
+    Returns TRUE iff new data could be read.  */
+
+bool
+file_cache_slot::read_data ()
+{
+  if (feof (m_fp) || ferror (m_fp))
+    return false;
+
+  maybe_grow ();
+
+  char * from = m_data + m_nb_read;
+  size_t to_read = m_size - m_nb_read;
+  size_t nb_read = fread (from, 1, to_read, m_fp);
+
+  if (ferror (m_fp))
+    {
+      m_error = true;
+      return false;
+    }
+
+  m_nb_read += nb_read;
+  return !!nb_read;
+}
+
+/* Read new data iff the cache needs to be filled with more data
+   coming from the file FP.  Return TRUE iff the cache was filled with
+   mode data.  */
+
+bool
+file_cache_slot::maybe_read_data ()
+{
+  if (!needs_read_p ())
+    return false;
+  return read_data ();
+}
+
+/* Helper function for file_cache_slot::get_next_line (), to find the end of
+   the next line.  Returns with the memchr convention, i.e. nullptr if a line
+   terminator was not found.  We need to determine line endings in the same
+   manner that libcpp does: any of \n, \r\n, or \r is a line ending.  */
+
+static const char *
+find_end_of_line (const char *s, size_t len)
+{
+  for (const auto end = s + len; s != end; ++s)
+    {
+      if (*s == '\n')
+       return s;
+      if (*s == '\r')
+       {
+         const auto next = s + 1;
+         if (next == end)
+           {
+             /* Don't find the line ending if \r is the very last character
+                in the buffer; we do not know if it's the end of the file or
+                just the end of what has been read so far, and we wouldn't
+                want to break in the middle of what's actually a \r\n
+                sequence.  Instead, we will handle the case of a file ending
+                in a \r later.  */
+             break;
+           }
+         return (*next == '\n' ? next : s);
+       }
+    }
+  return nullptr;
+}
+
+/* Read a new line from file FP, using C as a cache for the data
+   coming from the file.  Upon successful completion, *LINE is set to
+   the beginning of the line found.  *LINE points directly in the
+   line cache and is only valid until the next call of get_next_line.
+   *LINE_LEN is set to the length of the line.  Note that the line
+   does not contain any terminal delimiter.  This function returns
+   true if some data was read or process from the cache, false
+   otherwise.  Note that subsequent calls to get_next_line might
+   make the content of *LINE invalid.  */
+
+bool
+file_cache_slot::get_next_line (char **line, ssize_t *line_len)
+{
+  /* Fill the cache with data to process.  */
+  maybe_read_data ();
+
+  size_t remaining_size = m_nb_read - m_line_start_idx;
+  if (remaining_size == 0)
+    /* There is no more data to process.  */
+    return false;
+
+  const char *line_start = m_data + m_line_start_idx;
+
+  const char *next_line_start = NULL;
+  size_t len = 0;
+  const char *line_end = find_end_of_line (line_start, remaining_size);
+  if (line_end == NULL)
+    {
+      /* We haven't found an end-of-line delimiter in the cache.
+        Fill the cache with more data from the file and look again.  */
+      while (maybe_read_data ())
+       {
+         line_start = m_data + m_line_start_idx;
+         remaining_size = m_nb_read - m_line_start_idx;
+         line_end = find_end_of_line (line_start, remaining_size);
+         if (line_end != NULL)
+           {
+             next_line_start = line_end + 1;
+             break;
+           }
+       }
+      if (line_end == NULL)
+       {
+         /* We've loaded all the file into the cache and still no
+            terminator.  Let's say the line ends up at one byte past the
+            end of the file.  This is to stay consistent with the case
+            of when the line ends up with a terminator and line_end points to
+            that.  That consistency is useful below in the len calculation.
+
+            If the file ends in a \r, we didn't identify it as a line
+            terminator above, so do that now instead.  */
+         line_end = m_data + m_nb_read;
+         if (m_nb_read && line_end[-1] == '\r')
+           {
+             --line_end;
+             m_missing_trailing_newline = false;
+           }
+         else
+           m_missing_trailing_newline = true;
+       }
+      else
+       m_missing_trailing_newline = false;
+    }
+  else
+    {
+      next_line_start = line_end + 1;
+      m_missing_trailing_newline = false;
+    }
+
+  if (m_error)
+    return false;
+
+  /* At this point, we've found the end of the of line.  It either points to
+     the line terminator or to one byte after the last byte of the file.  */
+  gcc_assert (line_end != NULL);
+
+  len = line_end - line_start;
+
+  if (m_line_start_idx < m_nb_read)
+    *line = const_cast<char *> (line_start);
+
+  ++m_line_num;
+
+  /* Now update our line record so that re-reading lines from the
+     before m_line_start_idx is faster.  */
+  size_t rlen = m_line_record.length ();
+  /* Only update when beyond the previously cached region.  */
+  if (rlen == 0 || m_line_record[rlen - 1].line_num < m_line_num)
+    {
+      size_t spacing
+       = (rlen >= 2
+          ? (m_line_record[rlen - 1].line_num
+             - m_line_record[rlen - 2].line_num) : 1);
+      size_t delta
+       = rlen >= 1 ? m_line_num - m_line_record[rlen - 1].line_num : 1;
+
+      size_t max_size = line_record_size;
+      /* One anchor per hundred input lines.  */
+      if (max_size == 0)
+       max_size = m_line_num / 100;
+
+      /* If we're too far beyond drop half of the lines to rebalance.  */
+      if (rlen == max_size && delta >= spacing * 2)
+       {
+         size_t j = 0;
+         for (size_t i = 1; i < rlen; i += 2)
+           m_line_record[j++] = m_line_record[i];
+         m_line_record.truncate (j);
+         rlen = j;
+         spacing *= 2;
+       }
+
+      if (rlen < max_size && delta >= spacing)
+       {
+         file_cache_slot::line_info li (m_line_num, m_line_start_idx,
+                                        line_end - m_data);
+         m_line_record.safe_push (li);
+       }
+    }
+
+  /* Cache recent tail lines separately for fast access. This assumes
+     most accesses do not skip backwards.  */
+  if (m_line_recent_last == m_line_recent_first
+      || m_line_recent[m_line_recent_last].line_num == m_line_num - 1)
+    {
+      size_t mask = ((size_t) 1 << recent_cached_lines_shift) - 1;
+      m_line_recent_last = (m_line_recent_last + 1) & mask;
+      if (m_line_recent_last == m_line_recent_first)
+       m_line_recent_first = (m_line_recent_first + 1) & mask;
+      m_line_recent[m_line_recent_last]
+       = file_cache_slot::line_info (m_line_num, m_line_start_idx,
+                                     line_end - m_data);
+    }
+
+  /* Update m_line_start_idx so that it points to the next line to be
+     read.  */
+  if (next_line_start)
+    m_line_start_idx = next_line_start - m_data;
+  else
+    /* We didn't find any terminal '\n'.  Let's consider that the end
+       of line is the end of the data in the cache.  The next
+       invocation of get_next_line will either read more data from the
+       underlying file or return false early because we've reached the
+       end of the file.  */
+    m_line_start_idx = m_nb_read;
+
+  *line_len = len;
+
+  return true;
+}
+
+/* Consume the next bytes coming from the cache (or from its
+   underlying file if there are remaining unread bytes in the file)
+   until we reach the next end-of-line (or end-of-file).  There is no
+   copying from the cache involved.  Return TRUE upon successful
+   completion.  */
+
+bool
+file_cache_slot::goto_next_line ()
+{
+  char *l;
+  ssize_t len;
+
+  return get_next_line (&l, &len);
+}
+
+/* Read an arbitrary line number LINE_NUM from the file cached in C.
+   If the line was read successfully, *LINE points to the beginning
+   of the line in the file cache and *LINE_LEN is the length of the
+   line.  *LINE is not nul-terminated, but may contain zero bytes.
+   *LINE is only valid until the next call of read_line_num.
+   This function returns bool if a line was read.  */
+
+bool
+file_cache_slot::read_line_num (size_t line_num,
+                               char ** line, ssize_t *line_len)
+{
+  gcc_assert (line_num > 0);
+
+  /* Is the line in the recent line cache?
+     This assumes the main file processing is only using
+     a single contiguous cursor with only temporary excursions.  */
+  if (m_line_recent_first != m_line_recent_last
+       && m_line_recent[m_line_recent_first].line_num <= line_num
+       && m_line_recent[m_line_recent_last].line_num >= line_num)
+    {
+      line_info &last = m_line_recent[m_line_recent_last];
+      size_t mask = (1U << recent_cached_lines_shift) - 1;
+      size_t idx = (m_line_recent_last - (last.line_num - line_num)) & mask;
+      line_info &recent = m_line_recent[idx];
+      gcc_assert (recent.line_num == line_num);
+      *line = m_data + recent.start_pos;
+      *line_len = recent.end_pos - recent.start_pos;
+      return true;
+    }
+
+  if (line_num <= m_line_num)
+    {
+      line_info l (line_num, 0, 0);
+      int i = m_line_record.lower_bound (l, line_info::less_than);
+      if (i == 0)
+       {
+         m_line_start_idx = 0;
+         m_line_num = 0;
+       }
+      else if (m_line_record[i - 1].line_num == line_num)
+       {
+         /* We have the start/end of the line.  */
+         *line = m_data + m_line_record[i - 1].start_pos;
+         *line_len = m_line_record[i - 1].end_pos - m_line_record[i - 
1].start_pos;
+         return true;
+       }
+      else
+       {
+        gcc_assert (m_line_record[i - 1].line_num < m_line_num);
+        m_line_start_idx = m_line_record[i - 1].start_pos;
+        m_line_num = m_line_record[i - 1].line_num - 1;
+       }
+    }
+
+  /*  Let's walk from line m_line_num up to line_num - 1, without
+      copying any line.  */
+  while (m_line_num < line_num - 1)
+    if (!goto_next_line ())
+      return false;
+
+  /* The line we want is the next one.  Let's read it.  */
+  return get_next_line (line, line_len);
+}
+
+/* Return the physical source line that corresponds to FILE_PATH/LINE.
+   The line is not nul-terminated.  The returned pointer is only
+   valid until the next call of location_get_source_line.
+   Note that the line can contain several null characters,
+   so the returned value's length has the actual length of the line.
+   If the function fails, a NULL char_span is returned.  */
+
+char_span
+file_cache::get_source_line (const char *file_path, int line)
+{
+  char *buffer = NULL;
+  ssize_t len;
+
+  if (line == 0)
+    return char_span (NULL, 0);
+
+  if (file_path == NULL)
+    return char_span (NULL, 0);
+
+  file_cache_slot *c = lookup_or_add_file (file_path);
+  if (c == NULL)
+    return char_span (NULL, 0);
+
+  bool read = c->read_line_num (line, &buffer, &len);
+  if (!read)
+    return char_span (NULL, 0);
+
+  return char_span (buffer, len);
+}
+
+char_span
+file_cache::get_source_file_content (const char *file_path)
+{
+  file_cache_slot *c = lookup_or_add_file (file_path);
+  if (c == nullptr)
+    return char_span (nullptr, 0);
+  return c->get_full_file_content ();
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+ using temp_source_file = ::selftest::temp_source_file;
+
+/* Verify reading of a specific line LINENUM in TMP, FC.  */
+
+static void
+check_line (temp_source_file &tmp, file_cache &fc, int linenum)
+{
+  char_span line = fc.get_source_line (tmp.get_filename (), linenum);
+  int n;
+  const char *b = line.get_buffer ();
+  size_t l = line.length ();
+  char buf[5];
+  ASSERT_LT (l, 5);
+  memcpy (buf, b, l);
+  buf[l] = '\0';
+  ASSERT_TRUE (sscanf (buf, "%d", &n) == 1);
+  ASSERT_EQ (n, linenum);
+}
+
+/* Test file cache replacement.  */
+
+static void
+test_replacement ()
+{
+  const int maxline = 1000;
+
+  char *vec = XNEWVEC (char, maxline * 5);
+  char *p = vec;
+  int i;
+  for (i = 1; i <= maxline; i++)
+    p += sprintf (p, "%d\n", i);
+
+  temp_source_file tmp (SELFTEST_LOCATION, ".txt", vec);
+  free (vec);
+  file_cache fc;
+
+  for (i = 2; i <= maxline; i++)
+    {
+      check_line (tmp, fc, i);
+      check_line (tmp, fc, i - 1);
+      if (i >= 10)
+       check_line (tmp, fc, i - 9);
+      if (i >= 350) /* Exceed the look behind cache.  */
+       check_line (tmp, fc, i - 300);
+    }
+  for (i = 5; i <= maxline; i += 100)
+    check_line (tmp, fc, i);
+  for (i = 1; i <= maxline; i++)
+    check_line (tmp, fc, i);
+}
+
+/* Verify reading of input files (e.g. for caret-based diagnostics).  */
+
+static void
+test_reading_source_line ()
+{
+  /* Create a tempfile and write some text to it.  */
+  temp_source_file tmp (SELFTEST_LOCATION, ".txt",
+                       "01234567890123456789\n"
+                       "This is the test text\n"
+                       "This is the 3rd line");
+  file_cache fc;
+
+  /* Read back a specific line from the tempfile.  */
+  char_span source_line = fc.get_source_line (tmp.get_filename (), 3);
+  ASSERT_TRUE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () != NULL);
+  ASSERT_EQ (20, source_line.length ());
+  ASSERT_TRUE (!strncmp ("This is the 3rd line",
+                        source_line.get_buffer (), source_line.length ()));
+
+  source_line = fc.get_source_line (tmp.get_filename (), 2);
+  ASSERT_TRUE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () != NULL);
+  ASSERT_EQ (21, source_line.length ());
+  ASSERT_TRUE (!strncmp ("This is the test text",
+                        source_line.get_buffer (), source_line.length ()));
+
+  source_line = fc.get_source_line (tmp.get_filename (), 4);
+  ASSERT_FALSE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () == NULL);
+}
+
+/* Verify reading from buffers (e.g. for sarif-replay).  */
+
+static void
+test_reading_source_buffer ()
+{
+  const char *text = ("01234567890123456789\n"
+                     "This is the test text\n"
+                     "This is the 3rd line");
+  const char *filename = "foo.txt";
+  file_cache fc;
+  fc.add_buffered_content (filename, text, strlen (text));
+
+  /* Read back a specific line from the tempfile.  */
+  char_span source_line = fc.get_source_line (filename, 3);
+  ASSERT_TRUE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () != NULL);
+  ASSERT_EQ (20, source_line.length ());
+  ASSERT_TRUE (!strncmp ("This is the 3rd line",
+                        source_line.get_buffer (), source_line.length ()));
+
+  source_line = fc.get_source_line (filename, 2);
+  ASSERT_TRUE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () != NULL);
+  ASSERT_EQ (21, source_line.length ());
+  ASSERT_TRUE (!strncmp ("This is the test text",
+                        source_line.get_buffer (), source_line.length ()));
+
+  source_line = fc.get_source_line (filename, 4);
+  ASSERT_FALSE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () == NULL);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+file_cache_cc_tests ()
+{
+  test_reading_source_line ();
+  test_reading_source_buffer ();
+  test_replacement ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+} // namespace diagnostics
diff --git a/gcc/diagnostics/file-cache.h b/gcc/diagnostics/file-cache.h
new file mode 100644
index 000000000000..832a960d5913
--- /dev/null
+++ b/gcc/diagnostics/file-cache.h
@@ -0,0 +1,125 @@
+/* Caching input files for use by diagnostics.
+   Copyright (C) 2004-2025 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/>.  */
+
+#ifndef GCC_DIAGNOSTICS_FILE_CACHE_H
+#define GCC_DIAGNOSTICS_FILE_CACHE_H
+
+namespace diagnostics {
+
+/* A class capturing the bounds of a buffer, to allow for run-time
+   bounds-checking in a checked build.  */
+
+class char_span
+{
+ public:
+  char_span (const char *ptr, size_t n_elts) : m_ptr (ptr), m_n_elts (n_elts) 
{}
+
+  /* Test for a non-NULL pointer.  */
+  operator bool() const { return m_ptr; }
+
+  /* Get length, not including any 0-terminator (which may not be,
+     in fact, present).  */
+  size_t length () const { return m_n_elts; }
+
+  const char *get_buffer () const { return m_ptr; }
+
+  char operator[] (int idx) const
+  {
+    gcc_assert (idx >= 0);
+    gcc_assert ((size_t)idx < m_n_elts);
+    return m_ptr[idx];
+  }
+
+  char_span subspan (int offset, int n_elts) const
+  {
+    gcc_assert (offset >= 0);
+    gcc_assert (offset < (int)m_n_elts);
+    gcc_assert (n_elts >= 0);
+    gcc_assert (offset + n_elts <= (int)m_n_elts);
+    return char_span (m_ptr + offset, n_elts);
+  }
+
+  char *xstrdup () const
+  {
+    return ::xstrndup (m_ptr, m_n_elts);
+  }
+
+ private:
+  const char *m_ptr;
+  size_t m_n_elts;
+};
+
+/* Forward decl of slot within file_cache, so that the definition doesn't
+   need to be in this header.  */
+class file_cache_slot;
+
+/* A cache of source files for use when emitting diagnostics
+   (and in a few places in the C/C++ frontends).
+
+   Results are only valid until the next call to the cache, as
+   slots can be evicted.
+
+   Filenames are stored by pointer, and so must outlive the cache
+   instance.  */
+
+class file_cache
+{
+ public:
+  file_cache ();
+  ~file_cache ();
+
+  void dump (FILE *out, int indent) const;
+  void DEBUG_FUNCTION dump () const;
+
+  file_cache_slot *lookup_or_add_file (const char *file_path);
+  void forcibly_evict_file (const char *file_path);
+
+  /* See comments in diagnostic.h about the input conversion context.  */
+  struct input_context
+  {
+    diagnostic_input_charset_callback ccb;
+    bool should_skip_bom;
+  };
+  void initialize_input_context (diagnostic_input_charset_callback ccb,
+                                bool should_skip_bom);
+
+  char_span get_source_file_content (const char *file_path);
+  char_span get_source_line (const char *file_path, int line);
+  bool missing_trailing_newline_p (const char *file_path);
+
+  void add_buffered_content (const char *file_path,
+                            const char *buffer,
+                            size_t sz);
+
+  void tune (size_t num_file_slots, size_t lines);
+
+ private:
+  file_cache_slot *evicted_cache_tab_entry (unsigned *highest_use_count);
+  file_cache_slot *add_file (const char *file_path);
+  file_cache_slot *lookup_file (const char *file_path);
+
+ private:
+  size_t m_num_file_slots;
+  file_cache_slot *m_file_slots;
+  input_context m_input_context;
+};
+
+} // namespace diagnostics
+
+#endif // #ifndef GCC_DIAGNOSTICS_FILE_CACHE_H
diff --git a/gcc/diagnostics/selftest-source-printing.h 
b/gcc/diagnostics/selftest-source-printing.h
index 8c5866d37cd5..451c12042750 100644
--- a/gcc/diagnostics/selftest-source-printing.h
+++ b/gcc/diagnostics/selftest-source-printing.h
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_DIAGNOSTICS_SELFTEST_SOURCE_PRINTING_H
 
 #include "selftest.h"
+#include "diagnostics/file-cache.h"
 
 /* The selftest code should entirely disappear in a production
    configuration, hence we guard all of it with #if CHECKING_P.  */
diff --git a/gcc/diagnostics/source-printing.cc 
b/gcc/diagnostics/source-printing.cc
index ec26fa6a08ba..9f06728366e1 100644
--- a/gcc/diagnostics/source-printing.cc
+++ b/gcc/diagnostics/source-printing.cc
@@ -37,6 +37,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "text-art/types.h"
 #include "text-art/theme.h"
 #include "diagnostics/source-printing-effects.h"
+#include "diagnostics/file-cache.h"
 #include "xml.h"
 #include "xml-printer.h"
 
@@ -201,7 +202,7 @@ enum column_unit {
 class exploc_with_display_col : public expanded_location
 {
  public:
-  exploc_with_display_col (file_cache &fc,
+  exploc_with_display_col (diagnostics::file_cache &fc,
                           const expanded_location &exploc,
                           const cpp_char_column_policy &policy,
                           enum location_aspect aspect)
@@ -814,7 +815,7 @@ class layout
 
   const diagnostics::source_printing_options &m_options;
   const line_maps *m_line_table;
-  file_cache &m_file_cache;
+  diagnostics::file_cache &m_file_cache;
   const text_art::ascii_theme m_fallback_theme;
   const text_art::theme &m_theme;
   diagnostics::source_effect_info *m_effect_info;
@@ -1250,7 +1251,7 @@ static cpp_char_column_policy def_policy ()
    e.g. in test_diagnostic_show_locus_one_liner_utf8().  */
 
 static layout_range
-make_range (file_cache &fc,
+make_range (diagnostics::file_cache &fc,
            int start_line, int start_col, int end_line, int end_col)
 {
   const expanded_location start_exploc
@@ -1279,7 +1280,7 @@ make_range (file_cache &fc,
 static void
 test_layout_range_for_single_point ()
 {
-  file_cache fc;
+  diagnostics::file_cache fc;
   layout_range point = make_range (fc, 7, 10, 7, 10);
 
   /* Tests for layout_range::contains_point.  */
@@ -1316,7 +1317,7 @@ test_layout_range_for_single_point ()
 static void
 test_layout_range_for_single_line ()
 {
-  file_cache fc;
+  diagnostics::file_cache fc;
   layout_range example_a = make_range (fc, 2, 22, 2, 38);
 
   /* Tests for layout_range::contains_point.  */
@@ -1359,7 +1360,7 @@ test_layout_range_for_single_line ()
 static void
 test_layout_range_for_multiple_lines ()
 {
-  file_cache fc;
+  diagnostics::file_cache fc;
   layout_range example_b = make_range (fc, 3, 14, 5, 8);
 
   /* Tests for layout_range::contains_point.  */
@@ -2201,8 +2202,9 @@ layout::calculate_x_offset_display ()
       return;
     }
 
-  const char_span line = m_file_cache.get_source_line (m_exploc.file,
-                                                      m_exploc.line);
+  const diagnostics::char_span line
+    = m_file_cache.get_source_line (m_exploc.file,
+                                   m_exploc.line);
   if (!line)
     {
       /* Nothing to do, we couldn't find the source line.  */
@@ -3159,7 +3161,7 @@ public:
 
 /* Get the range of bytes or display columns that HINT would affect.  */
 static column_range
-get_affected_range (file_cache &fc,
+get_affected_range (diagnostics::file_cache &fc,
                    const cpp_char_column_policy &policy,
                    const fixit_hint *hint, enum column_unit col_unit)
 {
@@ -3189,7 +3191,7 @@ get_affected_range (file_cache &fc,
 /* Get the range of display columns that would be printed for HINT.  */
 
 static column_range
-get_printed_columns (file_cache &fc,
+get_printed_columns (diagnostics::file_cache &fc,
                     const cpp_char_column_policy &policy,
                     const fixit_hint *hint)
 {
@@ -3251,7 +3253,7 @@ public:
     m_display_cols = cpp_display_width (m_text, m_byte_length, m_policy);
   }
 
-  void overwrite (int dst_offset, const char_span &src_span)
+  void overwrite (int dst_offset, const diagnostics::char_span &src_span)
   {
     gcc_assert (dst_offset >= 0);
     gcc_assert (dst_offset + src_span.length () < m_alloc_sz);
@@ -3312,7 +3314,7 @@ correction::ensure_terminated ()
 class line_corrections
 {
 public:
-  line_corrections (file_cache &fc,
+  line_corrections (diagnostics::file_cache &fc,
                    const char_display_policy &policy,
                    const char *filename,
                    linenum_type row)
@@ -3323,7 +3325,7 @@ public:
 
   void add_hint (const fixit_hint *hint);
 
-  file_cache &m_file_cache;
+  diagnostics::file_cache &m_file_cache;
   const char_display_policy &m_policy;
   const char *m_filename;
   linenum_type m_row;
@@ -3346,9 +3348,12 @@ line_corrections::~line_corrections ()
 class source_line
 {
 public:
-  source_line (file_cache &fc, const char *filename, int line);
+  source_line (diagnostics::file_cache &fc, const char *filename, int line);
 
-  char_span as_span () { return char_span (chars, width); }
+  diagnostics::char_span as_span ()
+  {
+    return diagnostics::char_span (chars, width);
+  }
 
   const char *chars;
   int width;
@@ -3356,9 +3361,11 @@ public:
 
 /* source_line's ctor.  */
 
-source_line::source_line (file_cache &fc, const char *filename, int line)
+source_line::source_line (diagnostics::file_cache &fc,
+                         const char *filename,
+                         int line)
 {
-  char_span span = fc.get_source_line (filename, line);
+  diagnostics::char_span span = fc.get_source_line (filename, line);
   chars = span.get_buffer ();
   width = span.length ();
 }
@@ -3422,9 +3429,10 @@ line_corrections::add_hint (const fixit_hint *hint)
                (old_byte_len,
                 line.as_span ().subspan (between.start - 1,
                                          between.finish + 1 - between.start));
-             last_correction->overwrite (old_byte_len + between_byte_len,
-                                         char_span (hint->get_string (),
-                                                    hint->get_length ()));
+             last_correction->overwrite
+               (old_byte_len + between_byte_len,
+                diagnostics::char_span (hint->get_string (),
+                                        hint->get_length ()));
              last_correction->m_byte_length = new_byte_len;
              last_correction->ensure_terminated ();
              last_correction->m_affected_bytes.finish
@@ -3713,7 +3721,7 @@ layout_printer<Sink>::print_line (linenum_type row)
 {
   typename Sink::auto_check_tag_nesting sentinel (m_sink);
 
-  char_span line
+  diagnostics::char_span line
     = m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row);
   if (!line)
     return;
@@ -4215,7 +4223,7 @@ test_layout_x_offset_display_utf8 (const line_table_case 
&case_)
   ASSERT_EQ (1, LOCATION_LINE (line_end));
   ASSERT_EQ (line_bytes, LOCATION_COLUMN (line_end));
 
-  char_span lspan = f.m_fc.get_source_line (f.get_filename (), 1);
+  diagnostics::char_span lspan = f.m_fc.get_source_line (f.get_filename (), 1);
   ASSERT_EQ (line_display_cols,
             cpp_display_width (lspan.get_buffer (), lspan.length (),
                                def_policy ()));
@@ -4367,7 +4375,7 @@ test_layout_x_offset_display_tab (const line_table_case 
&case_)
   ASSERT_EQ (7, extra_width[10]);
 
   temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
-  file_cache fc;
+  diagnostics::file_cache fc;
   line_table_test ltt (case_);
 
   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
@@ -4379,7 +4387,7 @@ test_layout_x_offset_display_tab (const line_table_case 
&case_)
     return;
 
   /* Check that cpp_display_width handles the tabs as expected.  */
-  char_span lspan = fc.get_source_line (tmp.get_filename (), 1);
+  diagnostics::char_span lspan = fc.get_source_line (tmp.get_filename (), 1);
   ASSERT_EQ ('\t', *(lspan.get_buffer () + (tab_col - 1)));
   for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
     {
@@ -5712,7 +5720,7 @@ test_diagnostic_show_locus_one_liner_utf8 (const 
line_table_case &case_)
   ASSERT_EQ (1, LOCATION_LINE (line_end));
   ASSERT_EQ (31, LOCATION_COLUMN (line_end));
 
-  char_span lspan = f.m_fc.get_source_line (f.get_filename (), 1);
+  diagnostics::char_span lspan = f.m_fc.get_source_line (f.get_filename (), 1);
   ASSERT_EQ (25, cpp_display_width (lspan.get_buffer (), lspan.length (),
                                    def_policy ()));
   ASSERT_EQ (25, location_compute_display_column (f.m_fc,
@@ -6057,7 +6065,7 @@ test_overlapped_fixit_printing (const line_table_case 
&case_)
   const char *content
     = ("  foo *f = (foo *)ptr->field;\n");
   temp_source_file tmp (SELFTEST_LOCATION, ".C", content);
-  file_cache fc;
+  diagnostics::file_cache fc;
   line_table_test ltt (case_);
 
   const line_map_ordinary *ord_map
@@ -6295,7 +6303,7 @@ test_overlapped_fixit_printing_utf8 (const 
line_table_case &case_)
   /* Example where 3 fix-it hints are printed as one.  */
   {
     test_context dc;
-    file_cache &fc = dc.get_file_cache ();
+    diagnostics::file_cache &fc = dc.get_file_cache ();
     rich_location richloc (line_table, expr);
     richloc.add_fixit_replace (open_paren, "const_cast<");
     richloc.add_fixit_replace (close_paren, "> (");
@@ -6520,7 +6528,7 @@ test_overlapped_fixit_printing_2 (const line_table_case 
&case_)
   /* Two insertions, in the wrong order.  */
   {
     test_context dc;
-    file_cache &fc = dc.get_file_cache ();
+    diagnostics::file_cache &fc = dc.get_file_cache ();
 
     rich_location richloc (line_table, col_20);
     richloc.add_fixit_insert_before (col_23, "{");
diff --git a/gcc/final.cc b/gcc/final.cc
index a4dbab72914b..afcb0bb9efbc 100644
--- a/gcc/final.cc
+++ b/gcc/final.cc
@@ -83,6 +83,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "function-abi.h"
 #include "common/common-target.h"
 #include "diagnostic.h"
+#include "diagnostics/file-cache.h"
 
 #include "dwarf2out.h"
 
@@ -2116,7 +2117,7 @@ asm_show_source (const char *filename, int linenum)
   if (!filename)
     return;
 
-  char_span line
+  diagnostics::char_span line
     = global_dc->get_file_cache ().get_source_line (filename, linenum);
   if (!line)
     return;
diff --git a/gcc/gcc-rich-location.cc b/gcc/gcc-rich-location.cc
index 20959879321a..beb7b6c9f205 100644
--- a/gcc/gcc-rich-location.cc
+++ b/gcc/gcc-rich-location.cc
@@ -37,6 +37,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "cpplib.h"
 #include "diagnostic.h"
+#include "diagnostics/file-cache.h"
 
 /* Add a range to the rich_location, covering expression EXPR,
    using LABEL if non-NULL. */
@@ -79,11 +80,11 @@ gcc_rich_location::add_fixit_misspelled_id (location_t 
misspelled_token_loc,
 /* Return true if there is nothing on LOC's line before LOC.  */
 
 static bool
-blank_line_before_p (file_cache &fc,
+blank_line_before_p (diagnostics::file_cache &fc,
                     location_t loc)
 {
   expanded_location exploc = expand_location (loc);
-  char_span line = fc.get_source_line (exploc.file, exploc.line);
+  diagnostics::char_span line = fc.get_source_line (exploc.file, exploc.line);
   if (!line)
     return false;
   if (line.length () < (size_t)exploc.column)
@@ -101,7 +102,7 @@ blank_line_before_p (file_cache &fc,
    If true is returned then *OUT_START_OF_LINE is written to.  */
 
 static bool
-use_new_line (file_cache &fc,
+use_new_line (diagnostics::file_cache &fc,
              location_t insertion_point, location_t indent,
              location_t *out_start_of_line)
 {
diff --git a/gcc/input.cc b/gcc/input.cc
index afd74f6a25f2..b9a553954401 100644
--- a/gcc/input.cc
+++ b/gcc/input.cc
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "intl.h"
 #include "diagnostic.h"
+#include "diagnostics/file-cache.h"
 #include "selftest.h"
 #include "cpplib.h"
 
@@ -35,188 +36,6 @@ special_fname_builtin ()
   return _("<built-in>");
 }
 
-/* Input charset configuration.  */
-static const char *default_charset_callback (const char *)
-{
-  return nullptr;
-}
-
-void
-file_cache::initialize_input_context (diagnostic_input_charset_callback ccb,
-                                     bool should_skip_bom)
-{
-  m_input_context.ccb = (ccb ? ccb : default_charset_callback);
-  m_input_context.should_skip_bom = should_skip_bom;
-}
-
-/* This is a cache used by get_next_line to store the content of a
-   file to be searched for file lines.  */
-class file_cache_slot
-{
-public:
-  file_cache_slot ();
-  ~file_cache_slot ();
-
-  void dump (FILE *out, int indent) const;
-  void DEBUG_FUNCTION dump () const { dump (stderr, 0); }
-
-  bool read_line_num (size_t line_num,
-                     char ** line, ssize_t *line_len);
-
-  /* Accessors.  */
-  const char *get_file_path () const { return m_file_path; }
-  unsigned get_use_count () const { return m_use_count; }
-  bool missing_trailing_newline_p () const
-  {
-    return m_missing_trailing_newline;
-  }
-  char_span get_full_file_content ();
-
-  void inc_use_count () { m_use_count++; }
-
-  bool create (const file_cache::input_context &in_context,
-              const char *file_path, FILE *fp, unsigned highest_use_count);
-  void evict ();
-  void set_content (const char *buf, size_t sz);
-
-  static size_t tune (size_t line_record_size_)
-  {
-    size_t ret = line_record_size;
-    line_record_size = line_record_size_;
-    return ret;
-  }
-
- private:
-  /* These are information used to store a line boundary.  */
-  class line_info
-  {
-  public:
-    /* The line number.  It starts from 1.  */
-    size_t line_num;
-
-    /* The position (byte count) of the beginning of the line,
-       relative to the file data pointer.  This starts at zero.  */
-    size_t start_pos;
-
-    /* The position (byte count) of the last byte of the line.  This
-       normally points to the '\n' character, or to one byte after the
-       last byte of the file, if the file doesn't contain a '\n'
-       character.  */
-    size_t end_pos;
-
-    line_info (size_t l, size_t s, size_t e)
-      : line_num (l), start_pos (s), end_pos (e)
-    {}
-
-    line_info ()
-      :line_num (0), start_pos (0), end_pos (0)
-    {}
-
-    static bool less_than(const line_info &a, const line_info &b)
-    {
-      return a.line_num < b.line_num;
-    }
-  };
-
-  bool needs_read_p () const;
-  bool needs_grow_p () const;
-  void maybe_grow ();
-  bool read_data ();
-  bool maybe_read_data ();
-  bool get_next_line (char **line, ssize_t *line_len);
-  bool read_next_line (char ** line, ssize_t *line_len);
-  bool goto_next_line ();
-
-  static const size_t buffer_size = 4 * 1024;
-  static size_t line_record_size;
-  static size_t recent_cached_lines_shift;
-
-  /* The number of time this file has been accessed.  This is used
-     to designate which file cache to evict from the cache
-     array.  */
-  unsigned m_use_count;
-
-  /* The file_path is the key for identifying a particular file in
-     the cache.  This copy is owned by the slot.  */
-  char *m_file_path;
-
-  FILE *m_fp;
-
-  /* True when an read error happened.  */
-  bool m_error;
-
-  /* This points to the content of the file that we've read so
-     far.  */
-  char *m_data;
-
-  /* The allocated buffer to be freed may start a little earlier than DATA,
-     e.g. if a UTF8 BOM was skipped at the beginning.  */
-  int m_alloc_offset;
-
-  /*  The size of the DATA array above.*/
-  size_t m_size;
-
-  /* The number of bytes read from the underlying file so far.  This
-     must be less (or equal) than SIZE above.  */
-  size_t m_nb_read;
-
-  /* The index of the beginning of the current line.  */
-  size_t m_line_start_idx;
-
-  /* The number of the previous line read.  This starts at 1.  Zero
-     means we've read no line so far.  */
-  size_t m_line_num;
-
-  /* Could this file be missing a trailing newline on its final line?
-     Initially true (to cope with empty files), set to true/false
-     as each line is read.  */
-  bool m_missing_trailing_newline;
-
-  /* This is a record of the beginning and end of the lines we've seen
-     while reading the file.  This is useful to avoid walking the data
-     from the beginning when we are asked to read a line that is
-     before LINE_START_IDX above.  When the lines exceed line_record_size
-     this is scaled down dynamically, with the line_info becoming anchors.  */
-  vec<line_info, va_heap> m_line_record;
-
-  /* A cache of the recently seen lines. This is maintained as a ring
-     buffer. */
-  vec<line_info, va_heap> m_line_recent;
-
-  /* First and last valid entry in m_line_recent.  */
-  size_t m_line_recent_last, m_line_recent_first;
-
-  void offset_buffer (int offset)
-  {
-    gcc_assert (offset < 0 ? m_alloc_offset + offset >= 0
-               : (size_t) offset <= m_size);
-    gcc_assert (m_data);
-    m_alloc_offset += offset;
-    m_data += offset;
-    m_size -= offset;
-  }
-
-};
-
-size_t file_cache_slot::line_record_size = 0;
-size_t file_cache_slot::recent_cached_lines_shift = 8;
-
-/* Tune file_cache.  */
-void
-file_cache::tune (size_t num_file_slots, size_t lines)
-{
-  if (file_cache_slot::tune (lines) != lines
-      || m_num_file_slots != num_file_slots)
-    {
-      delete[] m_file_slots;
-      m_file_slots = new file_cache_slot[num_file_slots];
-    }
-  m_num_file_slots = num_file_slots;
-}
-
-static const char *
-find_end_of_line (const char *s, size_t len);
-
 /* Current position in real source file.  */
 
 location_t input_location = UNKNOWN_LOCATION;
@@ -320,738 +139,13 @@ expand_location_1 (const line_maps *set,
   return xloc;
 }
 
-/* Lookup the cache used for the content of a given file accessed by
-   caret diagnostic.  Return the found cached file, or NULL if no
-   cached file was found.  */
-
-file_cache_slot *
-file_cache::lookup_file (const char *file_path)
-{
-  gcc_assert (file_path);
-
-  /* This will contain the found cached file.  */
-  file_cache_slot *r = NULL;
-  for (unsigned i = 0; i < m_num_file_slots; ++i)
-    {
-      file_cache_slot *c = &m_file_slots[i];
-      if (c->get_file_path () && !strcmp (c->get_file_path (), file_path))
-       {
-         c->inc_use_count ();
-         r = c;
-       }
-    }
-
-  if (r)
-    r->inc_use_count ();
-
-  return r;
-}
-
-/* Purge any mention of FILENAME from the cache of files used for
-   printing source code.  For use in selftests when working
-   with tempfiles.  */
-
-void
-file_cache::forcibly_evict_file (const char *file_path)
-{
-  gcc_assert (file_path);
-
-  file_cache_slot *r = lookup_file (file_path);
-  if (!r)
-    /* Not found.  */
-    return;
-
-  r->evict ();
-}
-
-/* Determine if FILE_PATH missing a trailing newline on its final line.
-   Only valid to call once all of the file has been loaded, by
-   requesting a line number beyond the end of the file.  */
-
-bool
-file_cache::missing_trailing_newline_p (const char *file_path)
-{
-  gcc_assert (file_path);
-
-  file_cache_slot *r = lookup_or_add_file (file_path);
-  return r->missing_trailing_newline_p ();
-}
-
-void
-file_cache::add_buffered_content (const char *file_path,
-                                 const char *buffer,
-                                 size_t sz)
-{
-  gcc_assert (file_path);
-
-  file_cache_slot *r = lookup_file (file_path);
-  if (!r)
-    {
-      unsigned highest_use_count = 0;
-      r = evicted_cache_tab_entry (&highest_use_count);
-      if (!r->create (m_input_context, file_path, nullptr, highest_use_count))
-       return;
-    }
-
-  r->set_content (buffer, sz);
-}
-
-void
-file_cache_slot::evict ()
-{
-  free (m_file_path);
-  m_file_path = NULL;
-  if (m_fp)
-    fclose (m_fp);
-  m_error = false;
-  m_fp = NULL;
-  m_nb_read = 0;
-  m_line_start_idx = 0;
-  m_line_num = 0;
-  m_line_record.truncate (0);
-  m_line_recent_first = 0;
-  m_line_recent_last = 0;
-  m_use_count = 0;
-  m_missing_trailing_newline = true;
-}
-
-/* Return the file cache that has been less used, recently, or the
-   first empty one.  If HIGHEST_USE_COUNT is non-null,
-   *HIGHEST_USE_COUNT is set to the highest use count of the entries
-   in the cache table.  */
-
-file_cache_slot*
-file_cache::evicted_cache_tab_entry (unsigned *highest_use_count)
-{
-  file_cache_slot *to_evict = &m_file_slots[0];
-  unsigned huc = to_evict->get_use_count ();
-  for (unsigned i = 1; i < m_num_file_slots; ++i)
-    {
-      file_cache_slot *c = &m_file_slots[i];
-      bool c_is_empty = (c->get_file_path () == NULL);
-
-      if (c->get_use_count () < to_evict->get_use_count ()
-         || (to_evict->get_file_path () && c_is_empty))
-       /* We evict C because it's either an entry with a lower use
-          count or one that is empty.  */
-       to_evict = c;
-
-      if (huc < c->get_use_count ())
-       huc = c->get_use_count ();
-
-      if (c_is_empty)
-       /* We've reached the end of the cache; subsequent elements are
-          all empty.  */
-       break;
-    }
-
-  if (highest_use_count)
-    *highest_use_count = huc;
-
-  return to_evict;
-}
-
-/* Create the cache used for the content of a given file to be
-   accessed by caret diagnostic.  This cache is added to an array of
-   cache and can be retrieved by lookup_file_in_cache_tab.  This
-   function returns the created cache.  Note that only the last
-   m_num_file_slots files are cached.
-
-   This can return nullptr if the FILE_PATH can't be opened for
-   reading, or if the content can't be converted to the input_charset.  */
-
-file_cache_slot*
-file_cache::add_file (const char *file_path)
-{
-
-  FILE *fp = fopen (file_path, "r");
-  if (fp == NULL)
-    return NULL;
-
-  unsigned highest_use_count = 0;
-  file_cache_slot *r = evicted_cache_tab_entry (&highest_use_count);
-  if (!r->create (m_input_context, file_path, fp, highest_use_count))
-    return NULL;
-  return r;
-}
-
-/* Get a borrowed char_span to the full content of this file
-   as decoded according to the input charset, encoded as UTF-8.  */
-
-char_span
-file_cache_slot::get_full_file_content ()
-{
-  char *line;
-  ssize_t line_len;
-  while (get_next_line (&line, &line_len))
-    {
-    }
-  return char_span (m_data, m_nb_read);
-}
-
-/* Populate this slot for use on FILE_PATH and FP, dropping any
-   existing cached content within it.  */
-
-bool
-file_cache_slot::create (const file_cache::input_context &in_context,
-                        const char *file_path, FILE *fp,
-                        unsigned highest_use_count)
-{
-  m_file_path = file_path ? xstrdup (file_path) : nullptr;
-  if (m_fp)
-    fclose (m_fp);
-  m_error = false;
-  m_fp = fp;
-  if (m_alloc_offset)
-    offset_buffer (-m_alloc_offset);
-  m_nb_read = 0;
-  m_line_start_idx = 0;
-  m_line_num = 0;
-  m_line_recent_first = 0;
-  m_line_recent_last = 0;
-  m_line_record.truncate (0);
-  /* Ensure that this cache entry doesn't get evicted next time
-     add_file_to_cache_tab is called.  */
-  m_use_count = ++highest_use_count;
-  m_missing_trailing_newline = true;
-
-
-  /* Check the input configuration to determine if we need to do any
-     transformations, such as charset conversion or BOM skipping.  */
-  if (const char *input_charset = in_context.ccb (file_path))
-    {
-      /* Need a full-blown conversion of the input charset.  */
-      fclose (m_fp);
-      m_fp = NULL;
-      const cpp_converted_source cs
-       = cpp_get_converted_source (file_path, input_charset);
-      if (!cs.data)
-       return false;
-      if (m_data)
-       XDELETEVEC (m_data);
-      m_data = cs.data;
-      m_nb_read = m_size = cs.len;
-      m_alloc_offset = cs.data - cs.to_free;
-    }
-  else if (in_context.should_skip_bom)
-    {
-      if (read_data ())
-       {
-         const int offset = cpp_check_utf8_bom (m_data, m_nb_read);
-         offset_buffer (offset);
-         m_nb_read -= offset;
-       }
-    }
-
-  return true;
-}
-
-void
-file_cache_slot::set_content (const char *buf, size_t sz)
-{
-  m_data = (char *)xmalloc (sz);
-  memcpy (m_data, buf, sz);
-  m_nb_read = m_size = sz;
-  m_alloc_offset = 0;
-
-  if (m_fp)
-    {
-      fclose (m_fp);
-      m_fp = nullptr;
-    }
-}
-
-/* file_cache's ctor.  */
-
-file_cache::file_cache ()
-: m_num_file_slots (16), m_file_slots (new file_cache_slot[m_num_file_slots])
-{
-  initialize_input_context (nullptr, false);
-}
-
-/* file_cache's dtor.  */
-
-file_cache::~file_cache ()
-{
-  delete[] m_file_slots;
-}
-
-void
-file_cache::dump (FILE *out, int indent) const
-{
-  for (size_t i = 0; i < m_num_file_slots; ++i)
-    {
-      fprintf (out, "%*sslot[%i]:\n", indent, "", (int)i);
-      m_file_slots[i].dump (out, indent + 2);
-    }
-}
-
-void
-file_cache::dump () const
-{
-  dump (stderr, 0);
-}
-
-/* Lookup the cache used for the content of a given file accessed by
-   caret diagnostic.  If no cached file was found, create a new cache
-   for this file, add it to the array of cached file and return
-   it.
-
-   This can return nullptr on a cache miss if FILE_PATH can't be opened for
-   reading, or if the content can't be converted to the input_charset.  */
-
-file_cache_slot*
-file_cache::lookup_or_add_file (const char *file_path)
-{
-  file_cache_slot *r = lookup_file (file_path);
-  if (r == NULL)
-    r = add_file (file_path);
-  return r;
-}
-
-/* Default constructor for a cache of file used by caret
-   diagnostic.  */
-
-file_cache_slot::file_cache_slot ()
-: m_use_count (0), m_file_path (NULL), m_fp (NULL), m_error (false), m_data 
(0),
-  m_alloc_offset (0), m_size (0), m_nb_read (0), m_line_start_idx (0),
-  m_line_num (0), m_missing_trailing_newline (true),
-  m_line_recent_last (0), m_line_recent_first (0)
-{
-  m_line_record.create (0);
-  m_line_recent.create (1U << recent_cached_lines_shift);
-  for (int i = 0; i < 1 << recent_cached_lines_shift; i++)
-    m_line_recent.quick_push (file_cache_slot::line_info (0, 0, 0));
-}
-
-/* Destructor for a cache of file used by caret diagnostic.  */
-
-file_cache_slot::~file_cache_slot ()
-{
-  free (m_file_path);
-  if (m_fp)
-    {
-      fclose (m_fp);
-      m_fp = NULL;
-    }
-  if (m_data)
-    {
-      offset_buffer (-m_alloc_offset);
-      XDELETEVEC (m_data);
-      m_data = 0;
-    }
-  m_line_record.release ();
-  m_line_recent.release ();
-}
-
-void
-file_cache_slot::dump (FILE *out, int indent) const
-{
-  if (!m_file_path)
-    {
-      fprintf (out, "%*s(unused)\n", indent, "");
-      return;
-    }
-  fprintf (out, "%*sfile_path: %s\n", indent, "", m_file_path);
-  fprintf (out, "%*sfp: %p\n", indent, "", (void *)m_fp);
-  fprintf (out, "%*sneeds_read_p: %i\n", indent, "", (int)needs_read_p ());
-  fprintf (out, "%*sneeds_grow_p: %i\n", indent, "", (int)needs_grow_p ());
-  fprintf (out, "%*suse_count: %i\n", indent, "", m_use_count);
-  fprintf (out, "%*ssize: %zi\n", indent, "", m_size);
-  fprintf (out, "%*snb_read: %zi\n", indent, "", m_nb_read);
-  fprintf (out, "%*sstart_line_idx: %zi\n", indent, "", m_line_start_idx);
-  fprintf (out, "%*sline_num: %zi\n", indent, "", m_line_num);
-  fprintf (out, "%*smissing_trailing_newline: %i\n",
-          indent, "", (int)m_missing_trailing_newline);
-  fprintf (out, "%*sline records (%i):\n",
-          indent, "", m_line_record.length ());
-  int idx = 0;
-  for (auto &line : m_line_record)
-    fprintf (out, "%*s[%i]: line %zi: byte offsets: %zi-%zi\n",
-            indent + 2, "",
-            idx++, line.line_num, line.start_pos, line.end_pos);
-}
-
-/* Returns TRUE iff the cache would need to be filled with data coming
-   from the file.  That is, either the cache is empty or full or the
-   current line is empty.  Note that if the cache is full, it would
-   need to be extended and filled again.  */
-
-bool
-file_cache_slot::needs_read_p () const
-{
-  return m_fp && (m_nb_read == 0
-         || m_nb_read == m_size
-         || (m_line_start_idx >= m_nb_read - 1));
-}
-
-/*  Return TRUE iff the cache is full and thus needs to be
-    extended.  */
-
-bool
-file_cache_slot::needs_grow_p () const
-{
-  return m_nb_read == m_size;
-}
-
-/* Grow the cache if it needs to be extended.  */
-
-void
-file_cache_slot::maybe_grow ()
-{
-  if (!needs_grow_p ())
-    return;
-
-  if (!m_data)
-    {
-      gcc_assert (m_size == 0 && m_alloc_offset == 0);
-      m_size = buffer_size;
-      m_data = XNEWVEC (char, m_size);
-    }
-  else
-    {
-      const int offset = m_alloc_offset;
-      offset_buffer (-offset);
-      m_size *= 2;
-      m_data = XRESIZEVEC (char, m_data, m_size);
-      offset_buffer (offset);
-    }
-}
-
-/*  Read more data into the cache.  Extends the cache if need be.
-    Returns TRUE iff new data could be read.  */
-
-bool
-file_cache_slot::read_data ()
-{
-  if (feof (m_fp) || ferror (m_fp))
-    return false;
-
-  maybe_grow ();
-
-  char * from = m_data + m_nb_read;
-  size_t to_read = m_size - m_nb_read;
-  size_t nb_read = fread (from, 1, to_read, m_fp);
-
-  if (ferror (m_fp))
-    {
-      m_error = true;
-      return false;
-    }
-
-  m_nb_read += nb_read;
-  return !!nb_read;
-}
-
-/* Read new data iff the cache needs to be filled with more data
-   coming from the file FP.  Return TRUE iff the cache was filled with
-   mode data.  */
-
-bool
-file_cache_slot::maybe_read_data ()
-{
-  if (!needs_read_p ())
-    return false;
-  return read_data ();
-}
-
-/* Helper function for file_cache_slot::get_next_line (), to find the end of
-   the next line.  Returns with the memchr convention, i.e. nullptr if a line
-   terminator was not found.  We need to determine line endings in the same
-   manner that libcpp does: any of \n, \r\n, or \r is a line ending.  */
-
-static const char *
-find_end_of_line (const char *s, size_t len)
-{
-  for (const auto end = s + len; s != end; ++s)
-    {
-      if (*s == '\n')
-       return s;
-      if (*s == '\r')
-       {
-         const auto next = s + 1;
-         if (next == end)
-           {
-             /* Don't find the line ending if \r is the very last character
-                in the buffer; we do not know if it's the end of the file or
-                just the end of what has been read so far, and we wouldn't
-                want to break in the middle of what's actually a \r\n
-                sequence.  Instead, we will handle the case of a file ending
-                in a \r later.  */
-             break;
-           }
-         return (*next == '\n' ? next : s);
-       }
-    }
-  return nullptr;
-}
-
-/* Read a new line from file FP, using C as a cache for the data
-   coming from the file.  Upon successful completion, *LINE is set to
-   the beginning of the line found.  *LINE points directly in the
-   line cache and is only valid until the next call of get_next_line.
-   *LINE_LEN is set to the length of the line.  Note that the line
-   does not contain any terminal delimiter.  This function returns
-   true if some data was read or process from the cache, false
-   otherwise.  Note that subsequent calls to get_next_line might
-   make the content of *LINE invalid.  */
-
-bool
-file_cache_slot::get_next_line (char **line, ssize_t *line_len)
-{
-  /* Fill the cache with data to process.  */
-  maybe_read_data ();
-
-  size_t remaining_size = m_nb_read - m_line_start_idx;
-  if (remaining_size == 0)
-    /* There is no more data to process.  */
-    return false;
-
-  const char *line_start = m_data + m_line_start_idx;
-
-  const char *next_line_start = NULL;
-  size_t len = 0;
-  const char *line_end = find_end_of_line (line_start, remaining_size);
-  if (line_end == NULL)
-    {
-      /* We haven't found an end-of-line delimiter in the cache.
-        Fill the cache with more data from the file and look again.  */
-      while (maybe_read_data ())
-       {
-         line_start = m_data + m_line_start_idx;
-         remaining_size = m_nb_read - m_line_start_idx;
-         line_end = find_end_of_line (line_start, remaining_size);
-         if (line_end != NULL)
-           {
-             next_line_start = line_end + 1;
-             break;
-           }
-       }
-      if (line_end == NULL)
-       {
-         /* We've loaded all the file into the cache and still no
-            terminator.  Let's say the line ends up at one byte past the
-            end of the file.  This is to stay consistent with the case
-            of when the line ends up with a terminator and line_end points to
-            that.  That consistency is useful below in the len calculation.
-
-            If the file ends in a \r, we didn't identify it as a line
-            terminator above, so do that now instead.  */
-         line_end = m_data + m_nb_read;
-         if (m_nb_read && line_end[-1] == '\r')
-           {
-             --line_end;
-             m_missing_trailing_newline = false;
-           }
-         else
-           m_missing_trailing_newline = true;
-       }
-      else
-       m_missing_trailing_newline = false;
-    }
-  else
-    {
-      next_line_start = line_end + 1;
-      m_missing_trailing_newline = false;
-    }
-
-  if (m_error)
-    return false;
-
-  /* At this point, we've found the end of the of line.  It either points to
-     the line terminator or to one byte after the last byte of the file.  */
-  gcc_assert (line_end != NULL);
-
-  len = line_end - line_start;
-
-  if (m_line_start_idx < m_nb_read)
-    *line = const_cast<char *> (line_start);
-
-  ++m_line_num;
-
-  /* Now update our line record so that re-reading lines from the
-     before m_line_start_idx is faster.  */
-  size_t rlen = m_line_record.length ();
-  /* Only update when beyond the previously cached region.  */
-  if (rlen == 0 || m_line_record[rlen - 1].line_num < m_line_num)
-    {
-      size_t spacing
-       = (rlen >= 2
-          ? (m_line_record[rlen - 1].line_num
-             - m_line_record[rlen - 2].line_num) : 1);
-      size_t delta
-       = rlen >= 1 ? m_line_num - m_line_record[rlen - 1].line_num : 1;
-
-      size_t max_size = line_record_size;
-      /* One anchor per hundred input lines.  */
-      if (max_size == 0)
-       max_size = m_line_num / 100;
-
-      /* If we're too far beyond drop half of the lines to rebalance.  */
-      if (rlen == max_size && delta >= spacing * 2)
-       {
-         size_t j = 0;
-         for (size_t i = 1; i < rlen; i += 2)
-           m_line_record[j++] = m_line_record[i];
-         m_line_record.truncate (j);
-         rlen = j;
-         spacing *= 2;
-       }
-
-      if (rlen < max_size && delta >= spacing)
-       {
-         file_cache_slot::line_info li (m_line_num, m_line_start_idx,
-                                        line_end - m_data);
-         m_line_record.safe_push (li);
-       }
-    }
-
-  /* Cache recent tail lines separately for fast access. This assumes
-     most accesses do not skip backwards.  */
-  if (m_line_recent_last == m_line_recent_first
-      || m_line_recent[m_line_recent_last].line_num == m_line_num - 1)
-    {
-      size_t mask = ((size_t) 1 << recent_cached_lines_shift) - 1;
-      m_line_recent_last = (m_line_recent_last + 1) & mask;
-      if (m_line_recent_last == m_line_recent_first)
-       m_line_recent_first = (m_line_recent_first + 1) & mask;
-      m_line_recent[m_line_recent_last]
-       = file_cache_slot::line_info (m_line_num, m_line_start_idx,
-                                     line_end - m_data);
-    }
-
-  /* Update m_line_start_idx so that it points to the next line to be
-     read.  */
-  if (next_line_start)
-    m_line_start_idx = next_line_start - m_data;
-  else
-    /* We didn't find any terminal '\n'.  Let's consider that the end
-       of line is the end of the data in the cache.  The next
-       invocation of get_next_line will either read more data from the
-       underlying file or return false early because we've reached the
-       end of the file.  */
-    m_line_start_idx = m_nb_read;
-
-  *line_len = len;
-
-  return true;
-}
-
-/* Consume the next bytes coming from the cache (or from its
-   underlying file if there are remaining unread bytes in the file)
-   until we reach the next end-of-line (or end-of-file).  There is no
-   copying from the cache involved.  Return TRUE upon successful
-   completion.  */
-
-bool
-file_cache_slot::goto_next_line ()
-{
-  char *l;
-  ssize_t len;
-
-  return get_next_line (&l, &len);
-}
-
-/* Read an arbitrary line number LINE_NUM from the file cached in C.
-   If the line was read successfully, *LINE points to the beginning
-   of the line in the file cache and *LINE_LEN is the length of the
-   line.  *LINE is not nul-terminated, but may contain zero bytes.
-   *LINE is only valid until the next call of read_line_num.
-   This function returns bool if a line was read.  */
-
-bool
-file_cache_slot::read_line_num (size_t line_num,
-                               char ** line, ssize_t *line_len)
-{
-  gcc_assert (line_num > 0);
-
-  /* Is the line in the recent line cache?
-     This assumes the main file processing is only using
-     a single contiguous cursor with only temporary excursions.  */
-  if (m_line_recent_first != m_line_recent_last
-       && m_line_recent[m_line_recent_first].line_num <= line_num
-       && m_line_recent[m_line_recent_last].line_num >= line_num)
-    {
-      line_info &last = m_line_recent[m_line_recent_last];
-      size_t mask = (1U << recent_cached_lines_shift) - 1;
-      size_t idx = (m_line_recent_last - (last.line_num - line_num)) & mask;
-      line_info &recent = m_line_recent[idx];
-      gcc_assert (recent.line_num == line_num);
-      *line = m_data + recent.start_pos;
-      *line_len = recent.end_pos - recent.start_pos;
-      return true;
-    }
-
-  if (line_num <= m_line_num)
-    {
-      line_info l (line_num, 0, 0);
-      int i = m_line_record.lower_bound (l, line_info::less_than);
-      if (i == 0)
-       {
-         m_line_start_idx = 0;
-         m_line_num = 0;
-       }
-      else if (m_line_record[i - 1].line_num == line_num)
-       {
-         /* We have the start/end of the line.  */
-         *line = m_data + m_line_record[i - 1].start_pos;
-         *line_len = m_line_record[i - 1].end_pos - m_line_record[i - 
1].start_pos;
-         return true;
-       }
-      else
-       {
-        gcc_assert (m_line_record[i - 1].line_num < m_line_num);
-        m_line_start_idx = m_line_record[i - 1].start_pos;
-        m_line_num = m_line_record[i - 1].line_num - 1;
-       }
-    }
-
-  /*  Let's walk from line m_line_num up to line_num - 1, without
-      copying any line.  */
-  while (m_line_num < line_num - 1)
-    if (!goto_next_line ())
-      return false;
-
-  /* The line we want is the next one.  Let's read it.  */
-  return get_next_line (line, line_len);
-}
-
-/* Return the physical source line that corresponds to FILE_PATH/LINE.
-   The line is not nul-terminated.  The returned pointer is only
-   valid until the next call of location_get_source_line.
-   Note that the line can contain several null characters,
-   so the returned value's length has the actual length of the line.
-   If the function fails, a NULL char_span is returned.  */
-
-char_span
-file_cache::get_source_line (const char *file_path, int line)
-{
-  char *buffer = NULL;
-  ssize_t len;
-
-  if (line == 0)
-    return char_span (NULL, 0);
-
-  if (file_path == NULL)
-    return char_span (NULL, 0);
-
-  file_cache_slot *c = lookup_or_add_file (file_path);
-  if (c == NULL)
-    return char_span (NULL, 0);
-
-  bool read = c->read_line_num (line, &buffer, &len);
-  if (!read)
-    return char_span (NULL, 0);
-
-  return char_span (buffer, len);
-}
-
 /* Return a NUL-terminated copy of the source text between two locations, or
    NULL if the arguments are invalid.  The caller is responsible for freeing
    the return value.  */
 
 char *
-get_source_text_between (file_cache &fc, location_t start, location_t end)
+get_source_text_between (diagnostics::file_cache &fc,
+                        location_t start, location_t end)
 {
   expanded_location expstart
     = expand_location_to_spelling_point (start, LOCATION_ASPECT_START);
@@ -1076,7 +170,8 @@ get_source_text_between (file_cache &fc, location_t start, 
location_t end)
   /* For a single line we need to trim both edges.  */
   if (expstart.line == expend.line)
     {
-      char_span line = fc.get_source_line (expstart.file, expstart.line);
+      diagnostics::char_span line
+       = fc.get_source_line (expstart.file, expstart.line);
       if (line.length () < 1)
        return NULL;
       int s = expstart.column - 1;
@@ -1093,7 +188,7 @@ get_source_text_between (file_cache &fc, location_t start, 
location_t end)
      parts of the start and end lines off depending on column values.  */
   for (int lnum = expstart.line; lnum <= expend.line; ++lnum)
     {
-      char_span line = fc.get_source_line (expstart.file, lnum);
+      diagnostics::char_span line = fc.get_source_line (expstart.file, lnum);
       if (line.length () < 1 && (lnum != expstart.line && lnum != expend.line))
        continue;
 
@@ -1138,16 +233,6 @@ get_source_text_between (file_cache &fc, location_t 
start, location_t end)
   return xstrdup (buf);
 }
 
-
-char_span
-file_cache::get_source_file_content (const char *file_path)
-{
-  file_cache_slot *c = lookup_or_add_file (file_path);
-  if (c == nullptr)
-    return char_span (nullptr, 0);
-  return c->get_full_file_content ();
-}
-
 /* Test if the location originates from the spelling location of a
    builtin-tokens.  That is, return TRUE if LOC is a (possibly
    virtual) location of a built-in token that appears in the expansion
@@ -1280,13 +365,13 @@ make_location (location_t caret, source_range src_range)
    source line in order to calculate the display width.  If that cannot be done
    for any reason, then returns the byte column as a fallback.  */
 int
-location_compute_display_column (file_cache &fc,
+location_compute_display_column (diagnostics::file_cache &fc,
                                 expanded_location exploc,
                                 const cpp_char_column_policy &policy)
 {
   if (!(exploc.file && *exploc.file && exploc.line && exploc.column))
     return exploc.column;
-  char_span line = fc.get_source_line (exploc.file, exploc.line);
+  diagnostics::char_span line = fc.get_source_line (exploc.file, exploc.line);
   /* If line is NULL, this function returns exploc.column which is the
      desired fallback.  */
   return cpp_byte_column_to_display_column (line.get_buffer (), line.length (),
@@ -1431,7 +516,7 @@ dump_labelled_location_range (FILE *stream,
 void
 dump_location_info (FILE *stream)
 {
-  file_cache fc;
+  diagnostics::file_cache fc;
 
   /* Visualize the reserved locations.  */
   dump_labelled_location_range (stream, "RESERVED LOCATIONS",
@@ -1506,8 +591,8 @@ dump_location_info (FILE *stream)
            {
              /* Beginning of a new source line: draw the line.  */
 
-             char_span line_text = fc.get_source_line (exploc.file,
-                                                       exploc.line);
+             diagnostics::char_span line_text
+               = fc.get_source_line (exploc.file, exploc.line);
              if (!line_text)
                break;
              fprintf (stream,
@@ -1757,7 +842,7 @@ class auto_cpp_string_vec :  public auto_vec <cpp_string>
 
 static const char *
 get_substring_ranges_for_loc (cpp_reader *pfile,
-                             file_cache &fc,
+                             diagnostics::file_cache &fc,
                              string_concat_db *concats,
                              location_t strloc,
                              enum cpp_ttype type,
@@ -1837,7 +922,7 @@ get_substring_ranges_for_loc (cpp_reader *pfile,
       if (start.column > finish.column)
        return "range endpoints are reversed";
 
-      char_span line = fc.get_source_line (start.file, start.line);
+      diagnostics::char_span line = fc.get_source_line (start.file, 
start.line);
       if (!line)
        return "unable to read source line";
 
@@ -1852,7 +937,8 @@ get_substring_ranges_for_loc (cpp_reader *pfile,
       if (line.length () < (start.column - 1 + literal_length))
        return "line is not wide enough";
 
-      char_span literal = line.subspan (start.column - 1, literal_length);
+      diagnostics::char_span literal
+       = line.subspan (start.column - 1, literal_length);
 
       cpp_string from;
       from.len = literal_length;
@@ -1925,7 +1011,7 @@ get_substring_ranges_for_loc (cpp_reader *pfile,
 
 const char *
 get_location_within_string (cpp_reader *pfile,
-                           file_cache &fc,
+                           diagnostics::file_cache &fc,
                            string_concat_db *concats,
                            location_t strloc,
                            enum cpp_ttype type,
@@ -2008,7 +1094,7 @@ namespace selftest {
 
 static const char *
 get_source_range_for_char (cpp_reader *pfile,
-                          file_cache &fc,
+                          diagnostics::file_cache &fc,
                           string_concat_db *concats,
                           location_t strloc,
                           enum cpp_ttype type,
@@ -2036,7 +1122,7 @@ get_source_range_for_char (cpp_reader *pfile,
 
 static const char *
 get_num_source_ranges_for_substring (cpp_reader *pfile,
-                                    file_cache &fc,
+                                    diagnostics::file_cache &fc,
                                     string_concat_db *concats,
                                     location_t strloc,
                                     enum cpp_ttype type,
@@ -2359,119 +1445,6 @@ test_make_location_nonpure_range_endpoints (const 
line_table_case &case_)
   ASSERT_FALSE (IS_ADHOC_LOC (get_finish (not_aaa_eq_bbb)));
 }
 
-/* Verify reading of a specific line LINENUM in TMP, FC.  */
-
-static void
-check_line (temp_source_file &tmp, file_cache &fc, int linenum)
-{
-  char_span line = fc.get_source_line (tmp.get_filename (), linenum);
-  int n;
-  const char *b = line.get_buffer ();
-  size_t l = line.length ();
-  char buf[5];
-  ASSERT_LT (l, 5);
-  memcpy (buf, b, l);
-  buf[l] = '\0';
-  ASSERT_TRUE (sscanf (buf, "%d", &n) == 1);
-  ASSERT_EQ (n, linenum);
-}
-
-/* Test file cache replacement.  */
-
-static void
-test_replacement ()
-{
-  const int maxline = 1000;
-
-  char *vec = XNEWVEC (char, maxline * 5);
-  char *p = vec;
-  int i;
-  for (i = 1; i <= maxline; i++)
-    p += sprintf (p, "%d\n", i);
-
-  temp_source_file tmp (SELFTEST_LOCATION, ".txt", vec);
-  free (vec);
-  file_cache fc;
-
-  for (i = 2; i <= maxline; i++)
-    {
-      check_line (tmp, fc, i);
-      check_line (tmp, fc, i - 1);
-      if (i >= 10)
-       check_line (tmp, fc, i - 9);
-      if (i >= 350) /* Exceed the look behind cache.  */
-       check_line (tmp, fc, i - 300);
-    }
-  for (i = 5; i <= maxline; i += 100)
-    check_line (tmp, fc, i);
-  for (i = 1; i <= maxline; i++)
-    check_line (tmp, fc, i);
-}
-
-/* Verify reading of input files (e.g. for caret-based diagnostics).  */
-
-static void
-test_reading_source_line ()
-{
-  /* Create a tempfile and write some text to it.  */
-  temp_source_file tmp (SELFTEST_LOCATION, ".txt",
-                       "01234567890123456789\n"
-                       "This is the test text\n"
-                       "This is the 3rd line");
-  file_cache fc;
-
-  /* Read back a specific line from the tempfile.  */
-  char_span source_line = fc.get_source_line (tmp.get_filename (), 3);
-  ASSERT_TRUE (source_line);
-  ASSERT_TRUE (source_line.get_buffer () != NULL);
-  ASSERT_EQ (20, source_line.length ());
-  ASSERT_TRUE (!strncmp ("This is the 3rd line",
-                        source_line.get_buffer (), source_line.length ()));
-
-  source_line = fc.get_source_line (tmp.get_filename (), 2);
-  ASSERT_TRUE (source_line);
-  ASSERT_TRUE (source_line.get_buffer () != NULL);
-  ASSERT_EQ (21, source_line.length ());
-  ASSERT_TRUE (!strncmp ("This is the test text",
-                        source_line.get_buffer (), source_line.length ()));
-
-  source_line = fc.get_source_line (tmp.get_filename (), 4);
-  ASSERT_FALSE (source_line);
-  ASSERT_TRUE (source_line.get_buffer () == NULL);
-}
-
-/* Verify reading from buffers (e.g. for sarif-replay).  */
-
-static void
-test_reading_source_buffer ()
-{
-  const char *text = ("01234567890123456789\n"
-                     "This is the test text\n"
-                     "This is the 3rd line");
-  const char *filename = "foo.txt";
-  file_cache fc;
-  fc.add_buffered_content (filename, text, strlen (text));
-
-  /* Read back a specific line from the tempfile.  */
-  char_span source_line = fc.get_source_line (filename, 3);
-  ASSERT_TRUE (source_line);
-  ASSERT_TRUE (source_line.get_buffer () != NULL);
-  ASSERT_EQ (20, source_line.length ());
-  ASSERT_TRUE (!strncmp ("This is the 3rd line",
-                        source_line.get_buffer (), source_line.length ()));
-
-  source_line = fc.get_source_line (filename, 2);
-  ASSERT_TRUE (source_line);
-  ASSERT_TRUE (source_line.get_buffer () != NULL);
-  ASSERT_EQ (21, source_line.length ());
-  ASSERT_TRUE (!strncmp ("This is the test text",
-                        source_line.get_buffer (), source_line.length ()));
-
-  source_line = fc.get_source_line (filename, 4);
-  ASSERT_FALSE (source_line);
-  ASSERT_TRUE (source_line.get_buffer () == NULL);
-}
-
 /* Tests of lexing.  */
 
 /* Verify that token TOK from PARSER has cpp_token_as_text
@@ -2628,7 +1601,7 @@ public:
   line_table_test m_ltt;
   cpp_reader_ptr m_parser;
   temp_source_file m_tempfile;
-  file_cache m_file_cache;
+  diagnostics::file_cache m_file_cache;
   string_concat_db m_concats;
   bool m_implicitly_expect_EOF;
 };
@@ -4356,10 +3329,6 @@ input_cc_tests ()
   for_each_line_table_case 
(test_lexer_string_locations_raw_string_unterminated);
   for_each_line_table_case (test_lexer_char_constants);
 
-  test_reading_source_line ();
-  test_reading_source_buffer ();
-  test_replacement ();
-
   test_line_offset_overflow ();
 
   test_cpp_utf8 ();
diff --git a/gcc/input.h b/gcc/input.h
index b0a1ca0f58f2..eeef290c1db8 100644
--- a/gcc/input.h
+++ b/gcc/input.h
@@ -23,7 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "line-map.h"
 
-class file_cache;
+namespace diagnostics { class file_cache; }
 
 extern GTY(()) class line_maps *line_table;
 extern GTY(()) class line_maps *saved_line_table;
@@ -69,110 +69,12 @@ extern expanded_location expand_location (location_t);
 class cpp_char_column_policy;
 
 extern int
-location_compute_display_column (file_cache &fc,
+location_compute_display_column (diagnostics::file_cache &fc,
                                 expanded_location exploc,
                                 const cpp_char_column_policy &policy);
 
-/* A class capturing the bounds of a buffer, to allow for run-time
-   bounds-checking in a checked build.  */
-
-class char_span
-{
- public:
-  char_span (const char *ptr, size_t n_elts) : m_ptr (ptr), m_n_elts (n_elts) 
{}
-
-  /* Test for a non-NULL pointer.  */
-  operator bool() const { return m_ptr; }
-
-  /* Get length, not including any 0-terminator (which may not be,
-     in fact, present).  */
-  size_t length () const { return m_n_elts; }
-
-  const char *get_buffer () const { return m_ptr; }
-
-  char operator[] (int idx) const
-  {
-    gcc_assert (idx >= 0);
-    gcc_assert ((size_t)idx < m_n_elts);
-    return m_ptr[idx];
-  }
-
-  char_span subspan (int offset, int n_elts) const
-  {
-    gcc_assert (offset >= 0);
-    gcc_assert (offset < (int)m_n_elts);
-    gcc_assert (n_elts >= 0);
-    gcc_assert (offset + n_elts <= (int)m_n_elts);
-    return char_span (m_ptr + offset, n_elts);
-  }
-
-  char *xstrdup () const
-  {
-    return ::xstrndup (m_ptr, m_n_elts);
-  }
-
- private:
-  const char *m_ptr;
-  size_t m_n_elts;
-};
-
 extern char *
-get_source_text_between (file_cache &, location_t, location_t);
-
-/* Forward decl of slot within file_cache, so that the definition doesn't
-   need to be in this header.  */
-class file_cache_slot;
-
-/* A cache of source files for use when emitting diagnostics
-   (and in a few places in the C/C++ frontends).
-
-   Results are only valid until the next call to the cache, as
-   slots can be evicted.
-
-   Filenames are stored by pointer, and so must outlive the cache
-   instance.  */
-
-class file_cache
-{
- public:
-  file_cache ();
-  ~file_cache ();
-
-  void dump (FILE *out, int indent) const;
-  void DEBUG_FUNCTION dump () const;
-
-  file_cache_slot *lookup_or_add_file (const char *file_path);
-  void forcibly_evict_file (const char *file_path);
-
-  /* See comments in diagnostic.h about the input conversion context.  */
-  struct input_context
-  {
-    diagnostic_input_charset_callback ccb;
-    bool should_skip_bom;
-  };
-  void initialize_input_context (diagnostic_input_charset_callback ccb,
-                                bool should_skip_bom);
-
-  char_span get_source_file_content (const char *file_path);
-  char_span get_source_line (const char *file_path, int line);
-  bool missing_trailing_newline_p (const char *file_path);
-
-  void add_buffered_content (const char *file_path,
-                            const char *buffer,
-                            size_t sz);
-
-  void tune (size_t num_file_slots, size_t lines);
-
- private:
-  file_cache_slot *evicted_cache_tab_entry (unsigned *highest_use_count);
-  file_cache_slot *add_file (const char *file_path);
-  file_cache_slot *lookup_file (const char *file_path);
-
- private:
-  size_t m_num_file_slots;
-  file_cache_slot *m_file_slots;
-  input_context m_input_context;
-};
+get_source_text_between (diagnostics::file_cache &, location_t, location_t);
 
 extern expanded_location
 expand_location_to_spelling_point (location_t,
diff --git a/gcc/libgdiagnostics.cc b/gcc/libgdiagnostics.cc
index 9ae4d210e35f..bc7ca8a195bd 100644
--- a/gcc/libgdiagnostics.cc
+++ b/gcc/libgdiagnostics.cc
@@ -26,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "diagnostic.h"
 #include "diagnostics/color.h"
+#include "diagnostics/file-cache.h"
 #include "diagnostics/url.h"
 #include "diagnostics/metadata.h"
 #include "diagnostics/paths.h"
@@ -1358,7 +1359,7 @@ diagnostic_file::set_buffered_content (const char *buf, 
size_t sz)
   m_content = std::make_unique<content_buffer> (buf, sz);
 
   // Populate file_cache:
-  file_cache &fc = m_mgr.get_dc ().get_file_cache ();
+  diagnostics::file_cache &fc = m_mgr.get_dc ().get_file_cache ();
   fc.add_buffered_content (m_name.get_str (), buf, sz);
 }
 
diff --git a/gcc/selftest.cc b/gcc/selftest.cc
index 4fdce07c77f8..dc903407f8ed 100644
--- a/gcc/selftest.cc
+++ b/gcc/selftest.cc
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "selftest.h"
+#include "diagnostics/file-cache.h"
 #include "intl.h"
 
 #if CHECKING_P
@@ -186,7 +187,7 @@ assert_str_startswith (const location &loc,
 /* Constructor.  Generate a name for the file.  */
 
 named_temp_file::named_temp_file (const char *suffix,
-                                 file_cache *fc)
+                                 diagnostics::file_cache *fc)
 {
   m_filename = make_temp_file (suffix);
   ASSERT_NE (m_filename, NULL);
@@ -210,7 +211,7 @@ named_temp_file::~named_temp_file ()
 temp_source_file::temp_source_file (const location &loc,
                                    const char *suffix,
                                    const char *content,
-                                   file_cache *fc)
+                                   diagnostics::file_cache *fc)
 : named_temp_file (suffix, fc)
 {
   FILE *out = fopen (get_filename (), "w");
diff --git a/gcc/selftest.h b/gcc/selftest.h
index d57f3d63b5d2..4501d34181c7 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -25,7 +25,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #if CHECKING_P
 
-class file_cache;
+namespace diagnostics { class file_cache; }
 
 namespace selftest {
 
@@ -100,13 +100,13 @@ extern void assert_str_startswith (const location &loc,
 class named_temp_file
 {
  public:
-  named_temp_file (const char *suffix, file_cache *fc = nullptr);
+  named_temp_file (const char *suffix, diagnostics::file_cache *fc = nullptr);
   ~named_temp_file ();
   const char *get_filename () const { return m_filename; }
 
  private:
   char *m_filename;
-  file_cache *m_file_cache;
+  diagnostics::file_cache *m_file_cache;
 };
 
 /* A class for writing out a temporary sourcefile for use in selftests
@@ -116,7 +116,7 @@ class temp_source_file : public named_temp_file
 {
  public:
   temp_source_file (const location &loc, const char *suffix,
-                   const char *content, file_cache *fc = nullptr);
+                   const char *content, diagnostics::file_cache *fc = nullptr);
   temp_source_file (const location &loc, const char *suffix,
                    const char *content, size_t sz);
 };
diff --git a/gcc/substring-locations.h b/gcc/substring-locations.h
index 32427dc8b8be..94139e249090 100644
--- a/gcc/substring-locations.h
+++ b/gcc/substring-locations.h
@@ -125,7 +125,7 @@ class format_string_diagnostic_t
    LANG_HOOKS_GET_SUBSTRING_LOCATION.  */
 
 extern const char *get_location_within_string (cpp_reader *pfile,
-                                              file_cache &fc,
+                                              diagnostics::file_cache &fc,
                                               string_concat_db *concats,
                                               location_t strloc,
                                               enum cpp_ttype type,
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.cc 
b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.cc
index 5c277d71e18a..c867d463a64e 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.cc
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.cc
@@ -63,6 +63,7 @@
 #include "gcc-rich-location.h"
 #include "text-range-label.h"
 #include "diagnostics/text-sink.h"
+#include "diagnostics/file-cache.h"
 
 int plugin_is_GPL_compatible;
 
@@ -443,8 +444,8 @@ test_show_locus (function *fun)
       rich_location richloc (line_table, loc);
       for (int line = start_line; line <= finish_line; line++)
        {
-         file_cache &fc = global_dc->get_file_cache ();
-         char_span content = fc.get_source_line (file, line);
+         diagnostics::file_cache &fc = global_dc->get_file_cache ();
+         diagnostics::char_span content = fc.get_source_line (file, line);
          gcc_assert (content);
          /* Split line up into words.  */
          for (int idx = 0; idx < content.length (); idx++)
@@ -464,7 +465,8 @@ test_show_locus (function *fun)
                      richloc.add_range (word, SHOW_RANGE_WITH_CARET, &label);
 
                      /* Add a fixit, converting to upper case.  */
-                     char_span word_span = content.subspan (start_idx, idx - 
start_idx);
+                     diagnostics::char_span word_span
+                       = content.subspan (start_idx, idx - start_idx);
                      char *copy = word_span.xstrdup ();
                      for (char *ch = copy; *ch; ch++)
                        *ch = TOUPPER (*ch);
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index e0eac813ce72..8d02f62412bb 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -82,6 +82,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gcse.h"
 #include "omp-offload.h"
 #include "diagnostics/changes.h"
+#include "diagnostics/file-cache.h"
 #include "tree-pass.h"
 #include "dumpfile.h"
 #include "ipa-fnsummary.h"

Reply via email to