diff --git a/gcc/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index 0e2d8bdcfdd..23869c3ea6e 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "memmodel.h"
 #include "tm_p.h"		/* For C_COMMON_OVERRIDE_OPTIONS.  */
 #include "diagnostic.h"
+#include "diagnostics/file-cache.h"
 #include "c-pragma.h"
 #include "flags.h"
 #include "toplev.h"
@@ -535,6 +536,7 @@ c_common_handle_option (size_t scode, const char *arg, HOST_WIDE_INT value,
 
     case OPT_fpreprocessed:
       cpp_opts->preprocessed = value;
+      global_dc->get_file_cache ().set_preprocessed (value);
       break;
 
     case OPT_fdebug_cpp:
diff --git a/gcc/diagnostics/file-cache.cc b/gcc/diagnostics/file-cache.cc
index d1b7990a3e3..f19955fd422 100644
--- a/gcc/diagnostics/file-cache.cc
+++ b/gcc/diagnostics/file-cache.cc
@@ -45,6 +45,126 @@ file_cache::initialize_input_context (diagnostic_input_charset_callback ccb,
   m_input_context.should_skip_bom = should_skip_bom;
 }
 
+/* Contains info about a single linemarker seen in a preprocessed file.
+   Is used by the linemarker_cache.  */
+struct linemarker_cache_entry
+{
+  /* The source file name.  */
+  char *source_name;
+
+  /* The line in the file containing the linemarker, counting from 1.  */
+  int line;
+
+  /* The line the linemarker refers to.  */
+  int source_line;
+
+  /* The entry's index in the sorted record of all entries.  */
+  unsigned int record_idx;
+
+  linemarker_cache_entry (char_span sn, int l, int sl, unsigned int i)
+    : source_name (sn.xstrdup ()), line (l), source_line (sl), record_idx (i)
+  {}
+  ~linemarker_cache_entry ()
+  {
+    free (source_name);
+  }
+};
+
+/* A cache that is used by get_source_line_preprocessed to speed up
+   finding the correct line to read based on linemarker data.
+   The file_cache owns a single cache for the preprocessed input file.
+   Helps to avoid repeatedly scanning the whole file for linemarkers via
+   read_line_num.  */
+struct linemarker_cache
+{
+  /* String portion of the hash is owned by linemarker_cache_entry,
+     so we use nofree for the linemarker_hash_map.  */
+  typedef hash_map<nofree_string_hash, vec<linemarker_cache_entry *, va_heap>>
+    linemarker_hash_map;
+
+  /* For the memoization map, source names are copied so we use
+     free_string_hash.  */
+  typedef pair_hash<free_string_hash, int_hash<int, -1, -2>>
+    source_file_line_hash;
+  typedef hash_map<source_file_line_hash, int> result_line_map;
+
+  /* The maximum line number of all linemarkers we've seen so far.  */
+  int m_max_linemarker_line;
+
+  /* This hash map is the main entry point to the cache.
+     It maps source paths to a vector of linemarker_cache_entry pointers.
+     It allows efficient lookup of the closest linemarker.
+     For each source file, linemarkers are unique with both physical line
+     and referenced source line numbers in ascending order.
+     The same source line number can be referenced by multiple linemarkers.
+  */
+  linemarker_hash_map *m_linemarkers_by_source_name;
+
+  /* A sorted (by line number ascending) record of all unique linemarkers
+     we encounter.  This is used for efficiently determining if a candidate
+     from the cache is the best candidate.  */
+  vec<linemarker_cache_entry *, va_heap> m_linemarkers;
+
+  /* This map is used for plain memoization of linemarker to
+     physical source line mappings.  This is an additional optimization used
+     to avoid repeated calls to get_source_line_preprocessed
+     for the same source_file and line.
+     Expects a copy of the source_file string and assumes ownership.
+  */
+  result_line_map *m_result_lines_by_source;
+
+  /* Default ctor.  */
+  linemarker_cache ()
+    : m_max_linemarker_line (0),
+      m_linemarkers_by_source_name (new linemarker_hash_map),
+      m_result_lines_by_source (new result_line_map)
+  {
+    m_linemarkers.create (0);
+  }
+
+  /* Used in map traversal to clean up linemarker vectors.  */
+  static bool
+  release_linemarker_cache_vector (ATTRIBUTE_UNUSED const char *const &,
+				   vec<linemarker_cache_entry *> *vec,
+				   ATTRIBUTE_UNUSED void *)
+  {
+    // linemarker_cache_entry is owned by m_linemarkers so
+    // we only need to release the vector.
+    vec->release ();
+    return true;
+  }
+
+  /* Destructor.  */
+  ~linemarker_cache ()
+  {
+    m_linemarkers_by_source_name
+      ->traverse<void *, &release_linemarker_cache_vector> (NULL);
+    delete m_linemarkers_by_source_name;
+    delete m_result_lines_by_source;
+
+    for (unsigned i = 0; i < m_linemarkers.length (); ++i)
+      delete m_linemarkers[i];
+    m_linemarkers.release ();
+  }
+
+  int get_max_linemarker_line () const
+  {
+    return m_max_linemarker_line;
+  }
+  void set_max_linemarker_line (int l)
+  {
+    m_max_linemarker_line = l;
+  }
+
+  int get_result_line (const char *source_name, int source_line) const;
+  void add_result_line (const char *source_name, int source_line, int line);
+  bool get_closest_linemarker (const char *source_name, int source_line,
+			       int *out_linemarker_line,
+			       int *out_linemarker_source_line) const;
+  void add_linemarker_unique (char_span source_name, int line,
+			      int source_line);
+};
+
 /* 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
@@ -191,7 +311,6 @@ public:
     m_data += offset;
     m_size -= offset;
   }
-
 };
 
 size_t file_cache_slot::line_record_size = 0;
@@ -457,7 +576,9 @@ file_cache_slot::set_content (const char *buf, size_t sz)
 /* file_cache's ctor.  */
 
 file_cache::file_cache ()
-: m_num_file_slots (16), m_file_slots (new file_cache_slot[m_num_file_slots])
+: m_num_file_slots (16), m_file_slots (new file_cache_slot[m_num_file_slots]),
+  m_linemarker_cache (new linemarker_cache),
+  m_main_input_file_path (nullptr), m_is_preprocessed (false)
 {
   initialize_input_context (nullptr, false);
 }
@@ -467,6 +588,7 @@ file_cache::file_cache ()
 file_cache::~file_cache ()
 {
   delete[] m_file_slots;
+  delete m_linemarker_cache;
 }
 
 void
@@ -960,6 +1082,310 @@ file_cache::get_source_file_content (const char *file_path)
   return c->get_full_file_content ();
 }
 
+/* Find the closest cached linemarker for a given SOURCE_LINE and SOURCE_NAME.
+
+   Closest is defined by the smallest distance between the cached source line
+   and the given source line (i.e. the nearest left neighbor or exact match).
+   If we can safely determine that the cached linemarker is the best candidate,
+   return true, otherwise false.  If we have a valid match, store the linemarker
+   physical line number in OUT_LINEMARKER_LINE and the referenced source line
+   in OUT_LINEMARKER_SOURCE_LINE.
+
+   If we are certain that the candidate not the best because we know
+   about more linemarkers after, set OUT_LINEMARKER_LINE to the line of the
+   last known linemarker and OUT_LINEMARKER_SOURCE_LINE to 0.
+   This way, the caller does not have to rescan lines.  */
+
+bool
+linemarker_cache::get_closest_linemarker (
+  const char *source_name, int source_line, int *out_linemarker_line,
+  int *out_linemarker_source_line) const
+{
+  *out_linemarker_source_line = 0;
+  *out_linemarker_line = 0;
+
+  // get sorted list of linemarkers for the given source file from cache
+  vec<linemarker_cache_entry *, va_heap> *linemarkers
+    = m_linemarkers_by_source_name->get (source_name);
+  if (!linemarkers)
+    return false;
+  if (linemarkers->length () == 0)
+    return false;
+
+  // perform a binary search to find the nearest left neighbor or exact match
+  int left = 0, right = linemarkers->length () - 1;
+  while (left <= right)
+    {
+      int mid = left + (right - left) / 2;
+      if ((*linemarkers)[mid]->source_line > source_line)
+       {
+	 right = mid - 1;
+       }
+      else
+       {
+	 left = mid + 1;
+       }
+    }
+  if (right < 0)
+    return false;
+
+  *out_linemarker_source_line = (*linemarkers)[right]->source_line;
+  *out_linemarker_line = (*linemarkers)[right]->line;
+
+  // If there is a linemarker for any file above the candidate, we can
+  // check if the target source line falls in the range between.
+  // If so, the candidate is the best.
+  if (m_linemarkers.length () > (*linemarkers)[right]->record_idx + 1)
+    {
+      linemarker_cache_entry *next_lm
+	= m_linemarkers[(*linemarkers)[right]->record_idx + 1];
+      size_t lines_to_next_lm = next_lm->line - (*linemarkers)[right]->line - 1;
+      size_t offset = source_line - (*linemarkers)[right]->source_line;
+      if (offset < lines_to_next_lm)
+       return true;
+      else
+       {
+	 // we know this candidate cannot be the right one
+	 // because the space between the candidate and the next linemarker
+	 // is not large enough for the offset.
+	 *out_linemarker_source_line = 0;
+	 *out_linemarker_line = 0;
+       }
+    }
+
+  return false;
+}
+
+/* Add a linemarker to the linemarker record in the file_cache_slot  **/
+void
+linemarker_cache::add_linemarker_unique (char_span source_name,
+							  int line,
+							  int source_line)
+{
+  if (line <= get_max_linemarker_line ())
+    return; // seen already
+  set_max_linemarker_line (line);
+
+  // first, add the linemarker to the sorted record along with its index
+  linemarker_cache_entry *entry
+    = new linemarker_cache_entry{source_name, line, source_line,
+				 m_linemarkers.length ()};
+
+  m_linemarkers.safe_push (entry);
+
+  // then, link the entry to the source_file based lookup table,
+  // creating the table entry if it doesn't exist yet
+  vec<linemarker_cache_entry *, va_heap> *linemarkers
+    = m_linemarkers_by_source_name->get (entry->source_name);
+  if (!linemarkers)
+    {
+      vec<linemarker_cache_entry *, va_heap> linemarkers_vec
+	= vec<linemarker_cache_entry *, va_heap> ();
+      linemarkers_vec.create (1);
+      linemarkers_vec.safe_push (entry);
+      m_linemarkers_by_source_name->put (entry->source_name, linemarkers_vec);
+    }
+  else
+    {
+      linemarkers->safe_push (entry);
+    }
+}
+
+/* Memoize a result line for get_source_line_preprocessed calls.  */
+void
+linemarker_cache::add_result_line (const char *source_name,
+						    int source_line,
+						    int result_line)
+{
+  m_result_lines_by_source->put (string_int_pair (xstrdup (source_name),
+						  source_line),
+				 result_line);
+}
+
+/* Return a memoized line number for the given SOURCE_NAME and SOURCE_LINE.
+   If no result is cached, return 0.
+   The cached line number is the physical line which is referenced by
+   the cached linemarker for these inputs.  */
+int
+linemarker_cache::get_result_line (const char *source_name,
+						    int source_line) const
+{
+  int *cached_line = m_result_lines_by_source->get (
+    string_int_pair (source_name, source_line));
+  return cached_line ? *cached_line : 0;
+}
+
+/* Return true if LINE is a linemarker for the given SOURCE_NAME.
+   Linemarkers are of the form:
+   # LINENUM "SOURCE_NAME" [FLAGS]
+   Store the referenced line number in OUT_LINE_NUM.
+   If it is any linemarker, even for a different file, write the file name
+   out to OUT_LINEMARKER_SOURCE_NAME.  */
+static bool
+is_linemarker_for_file (char_span line, const char *source_name,
+			int *out_line_num,
+			char_span *out_linemarker_source_name)
+{
+  // skip leading whitespace
+  size_t i = 0;
+  while (line.length () > i && ISBLANK (line[i]))
+    ++i;
+
+  // a linemarker starts with '# ' followed by a line number
+  if (line.length () <= i + 2 || line[i] != '#' || line[i + 1] != ' '
+      || !ISDIGIT (line[i + 2]))
+    return false;
+
+  *out_line_num = atoi (line.get_buffer () + i + 2);
+
+  // find the leading quote of the source name
+  for (i += 3; i < line.length () && line[i] != '"'; ++i)
+    ;
+  if (i >= line.length ())
+    return false;
+
+  // find the trailing quote of the source name
+  size_t source_name_start = i + 1;
+  size_t j = source_name_start;
+  for (; j < line.length () && line[j] != '"'; ++j)
+    ;
+  if (j >= line.length ())
+    return false;
+
+  size_t source_name_length = j - source_name_start;
+  *out_linemarker_source_name = line.subspan (source_name_start,
+					      source_name_length);
+
+  // the linemarker is only for SOURCE_NAME if the names match exactly
+  if (strlen (source_name) != source_name_length)
+    return false;
+  return memcmp (out_linemarker_source_name->get_buffer (), source_name,
+		 source_name_length) == 0;
+}
+
+/* Return the physical source line that corresponds to SOURCE_NAME/LINE.
+   Read the line from the preprocessed file at FILE_PATH.
+   The line is not nul-terminated.  The returned pointer is only
+   valid until the next call of 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.
+
+   If we for example want to get line 7 of file.cpp from the preprocessed
+   output, the corresponding line marker might look like this:
+   # 5 "file.cpp"    // line 2050 of the preprocessed file
+   int func ()
+   {
+     int a = 4;
+     int b = 5;
+
+   This means that line 2051 of the preprocessed file is line 5 of the
+   original file.cpp.  So to get line 7 of file.cpp we have to read line
+   2053 of the preprocessed file.  */
+
+char_span
+file_cache::get_source_line_preprocessed (const char *source_name,
+					  const char *file_path, int line)
+{
+  char *buffer = NULL;
+  ssize_t len;
+
+  if (line == 0)
+    return char_span (NULL, 0);
+
+  file_cache_slot *c = lookup_or_add_file (file_path);
+  if (c == NULL)
+    return char_span (NULL, 0);
+
+  // return memoized result if available
+  int cached_line = m_linemarker_cache->get_result_line (source_name, line);
+  if (cached_line != 0)
+    {
+      bool read = c->read_line_num (cached_line, &buffer, &len);
+      if (read)
+       return char_span (buffer, len);
+    }
+
+  // attempt to find a linemarker in the cache
+  int linemarker_line = 0; // the physical line num of the candidate
+  int linemarker_loc = 0;  // the referenced source line num of the candidate
+  bool is_best
+    = m_linemarker_cache->get_closest_linemarker (source_name, line,
+						  &linemarker_line,
+						  &linemarker_loc);
+
+  // we continue reading from (highest) cached location if the potential
+  // candidate is not the best already
+  int current_line = m_linemarker_cache->get_max_linemarker_line () + 1;
+  char_span linemarker_source_name = char_span (NULL, 0);
+  int linemarker_source_line = 0;
+  bool is_match = false;
+  while (!is_best && c->read_line_num (current_line, &buffer, &len))
+    {
+      linemarker_source_name = char_span (NULL, 0);
+      linemarker_source_line = 0;
+      is_match = is_linemarker_for_file (char_span (buffer, len), source_name,
+					 &linemarker_source_line,
+					 &linemarker_source_name);
+
+      if (linemarker_source_name)
+       {
+	 m_linemarker_cache->add_linemarker_unique (linemarker_source_name,
+						    current_line,
+						    linemarker_source_line);
+	 if (linemarker_line > 0 && linemarker_loc > 0)
+	    {
+	      // if our last candidate fits the range to the next linemarker,
+	      // we can stop searching
+	      int lines_to_next_lm = current_line - linemarker_line - 1;
+	      int offset = line - linemarker_loc;
+	      if (offset < lines_to_next_lm)
+		break;
+	      else
+		{
+		  // reset the candidate if we have passed the range
+		  linemarker_line = 0;
+		  linemarker_loc = 0;
+		}
+	    }
+	 if (is_match)
+	    {
+	      // new candidate
+	      linemarker_line = current_line;
+	      linemarker_loc = linemarker_source_line;
+	    }
+       }
+      ++current_line;
+    }
+
+  if (linemarker_line == 0 || linemarker_loc == 0)
+    return char_span (NULL, 0);
+
+  // we have a matching linemarker, read the indicated line
+  size_t physical_line = linemarker_line + (line - linemarker_loc) + 1;
+  bool read = c->read_line_num (physical_line, &buffer, &len);
+  if (!read)
+    return char_span (NULL, 0);
+
+  // memoize result line
+  m_linemarker_cache->add_result_line (source_name, line, physical_line);
+  return char_span (buffer, len);
+}
+
+/* Get the source line for FILE_PATH/LINE.  When compiling preprocessed input,
+   read the line from the preprocessed input file instead, so that diagnostics
+   remain correct when the original source file is unavailable (e.g. within a
+   compile cluster) or has changed since preprocessing.  */
+
+char_span
+file_cache::get_source_line_maybe_preprocessed (const char *file_path, int line)
+{
+  if (m_is_preprocessed && m_main_input_file_path != nullptr)
+    return get_source_line_preprocessed (file_path, m_main_input_file_path,
+					 line);
+  return get_source_line (file_path, line);
+}
+
 #if CHECKING_P
 
 namespace selftest {
@@ -1079,6 +1505,268 @@ test_reading_source_buffer ()
   ASSERT_TRUE (source_line.get_buffer () == NULL);
 }
 
+/* Verify reading of preprocessed input files
+   (e.g. for caret-based diagnostics).  */
+
+static void
+test_parsing_linemarker ()
+{
+  auto assert_is_linemarker_for_file = [] (const ::selftest::location &loc,
+					   const char *buf,
+					   const char *expected_file_name,
+					   int expected_line_num)
+  {
+    int line_num;
+    char_span file_name (nullptr, 0);
+    bool result = is_linemarker_for_file (char_span (buf, strlen (buf)),
+					  expected_file_name,
+					  &line_num, &file_name);
+    ASSERT_TRUE_AT (loc, result);
+    ASSERT_EQ_AT (loc, line_num, expected_line_num);
+    ASSERT_TRUE_AT (loc, file_name);
+    char *actual_file_name = file_name.xstrdup ();
+    ASSERT_STREQ_AT (loc, expected_file_name, actual_file_name);
+    free (actual_file_name);
+  };
+#define ASSERT_IS_LINEMARKER_FOR_FILE(BUF, EXPECTED_FILENAME, EXPECTED_LINE_NUM) \
+  SELFTEST_BEGIN_STMT							\
+    assert_is_linemarker_for_file (SELFTEST_LOCATION, (BUF),		\
+				   (EXPECTED_FILENAME), (EXPECTED_LINE_NUM)); \
+  SELFTEST_END_STMT
+
+  /* EXPECTED_PARSED_NAME asserts the source name parsed out of the linemarker
+     for cases where it does not match the file we search for; it is nullptr
+     when the input is not a linemarker at all.  */
+  auto assert_is_not_linemarker_for_file
+    = [] (const ::selftest::location &loc, const char *buf,
+	  const char *file_to_match, const char *expected_parsed_name)
+  {
+    int line_num;
+    char_span file_name (nullptr, 0);
+    bool result = is_linemarker_for_file (char_span (buf, strlen (buf)),
+					  file_to_match, &line_num, &file_name);
+    ASSERT_FALSE_AT (loc, result);
+    if (expected_parsed_name)
+      {
+	ASSERT_TRUE_AT (loc, file_name);
+	char *actual_file_name = file_name.xstrdup ();
+	ASSERT_STREQ_AT (loc, expected_parsed_name, actual_file_name);
+	free (actual_file_name);
+      }
+    else
+      ASSERT_FALSE_AT (loc, file_name);
+  };
+#define ASSERT_IS_NOT_LINEMARKER_FOR_FILE(BUF, FILENAME, EXPECTED_PARSED_NAME) \
+  SELFTEST_BEGIN_STMT							\
+    assert_is_not_linemarker_for_file (SELFTEST_LOCATION, (BUF), (FILENAME), \
+				       (EXPECTED_PARSED_NAME));		\
+  SELFTEST_END_STMT
+
+  ASSERT_IS_LINEMARKER_FOR_FILE ("# 112 \"test.cpp\"\n", "test.cpp", 112);
+
+  /* Linemarker but for a different file.  */
+  ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.hpp\"\n", "test.h",
+				     "test.hpp");
+  ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.h\"\n", "test.hpp",
+				     "test.h");
+
+  /* Not a linemarker.  */
+  ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("int main () \n", "test.cpp", nullptr);
+
+  /* Linemarker with flags.  */
+  ASSERT_IS_LINEMARKER_FOR_FILE ("# 23 \"test.h\" 1\n", "test.h", 23);
+
+  /* Malformed linemarkers.  */
+  ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.h   \n", "test.h", nullptr);
+  ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 2 test.h\n", "test.h", nullptr);
+  ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("#  23 \"test.h\"\n", "test.h", nullptr);
+
+  /* Linemarker not starting at beginning of line, spaces and tabs.  */
+  ASSERT_IS_LINEMARKER_FOR_FILE ("      # 23 \"test.hpp\"\n", "test.hpp", 23);
+
+#undef ASSERT_IS_LINEMARKER_FOR_FILE
+#undef ASSERT_IS_NOT_LINEMARKER_FOR_FILE
+}
+
+static void
+test_linemarker_cache ()
+{
+  linemarker_cache cache;
+
+  // Represents a file like
+  // # 1 "test.cpp" 1
+  // # 1 "test.cpp"
+  // content
+  // # 1 "test.h" 1
+  // # 2 "test.h"
+  // ...
+  // # 35 "test.h"
+  cache.add_linemarker_unique (char_span ("test.cpp", 8), 1, 1);
+  cache.add_linemarker_unique (char_span ("test.cpp", 8), 2, 1);
+
+  int line_num = 0;
+  int source_line_num = 0;
+
+  /* Matching linemarkers.  */
+  bool best
+    = cache.get_closest_linemarker ("test.cpp", 4, &line_num,
+				    &source_line_num);
+  // not surely the best but a match
+  // because there are no further linemarkers known yet
+  ASSERT_FALSE (best);
+  ASSERT_EQ (2, line_num);
+  ASSERT_EQ (1, source_line_num);
+
+  // add more linemarkers
+  cache.add_linemarker_unique (char_span ("test.h", 6), 5, 2);
+  cache.add_linemarker_unique (char_span ("test.h", 6), 15, 35);
+
+  // repeat previous test but with more cache entries
+  best
+    = cache.get_closest_linemarker ("test.cpp", 1, &line_num,
+				    &source_line_num);
+  // best because there is room for one line before the next linemarker
+  ASSERT_TRUE (best);
+  // left-nearest neighbor, i.e. the last linemarker before the source line
+  ASSERT_EQ (2, line_num);
+  ASSERT_EQ (1, source_line_num);
+
+  best
+    = cache.get_closest_linemarker ("test.cpp", 4, &line_num,
+				    &source_line_num);
+  // not the best because there are linemarkers after and the
+  // space between is not enough for the source line offset
+  ASSERT_FALSE (best);
+  ASSERT_EQ (0, line_num);
+  ASSERT_EQ (0, source_line_num);
+
+  best = cache.get_closest_linemarker ("test.h", 10, &line_num,
+				       &source_line_num);
+  // best because there are 9 lines between the linemarker and the next
+  // We can target source line offsets between 0 and 8
+  // and the offset is 8
+  ASSERT_TRUE (best);
+  ASSERT_EQ (5, line_num);
+  ASSERT_EQ (2, source_line_num);
+  best = cache.get_closest_linemarker ("test.h", 11, &line_num,
+				       &source_line_num);
+  // one source line further already falls on the next linemarker
+  ASSERT_FALSE (best);
+  ASSERT_EQ (0, line_num);
+  ASSERT_EQ (0, source_line_num);
+
+  best = cache.get_closest_linemarker ("test.h", 40, &line_num,
+				       &source_line_num);
+  // no linemarker in the cache after this, so we would need to scan further
+  ASSERT_FALSE (best);
+  ASSERT_EQ (15, line_num);
+  ASSERT_EQ (35, source_line_num);
+
+  /* No match.  */
+  best = cache.get_closest_linemarker ("test.h", 1, &line_num,
+				       &source_line_num);
+  // no left-nearest neighbor
+  ASSERT_FALSE (best);
+  ASSERT_EQ (0, line_num);
+  ASSERT_EQ (0, source_line_num);
+
+  best = cache.get_closest_linemarker ("no_match.cpp", 3, &line_num,
+				       &source_line_num);
+  ASSERT_FALSE (best);
+  ASSERT_EQ (0, line_num);
+  ASSERT_EQ (0, source_line_num);
+}
+
+static void
+test_reading_source_line_preprocessed ()
+{
+  /* Create a tempfile and write some valid preprocessor output.  */
+  temp_source_file tmp (SELFTEST_LOCATION, ".cpp.ii",
+			"# 1 \"test.cpp\"\n"
+			"# 1 \"test.cpp\"\n"
+			"# 1 \"test.h\" 1\n"
+			"void test_func () \n"
+			"# 35 \"test.h\"\n"
+			"    do_something_else ();\n"
+			"}\n"
+			"# 35 \"test.hpp\"\n"
+			"    do_nothing ();\n"
+			"}\n"
+			"# 2 \"test.cpp\" 2\n"
+			"\n"
+			"int main () \n"
+			"    do_something ();\n"
+			"\n"
+			"    int i = 5;\n"
+			"    unsigned j = 3;\n"
+			"    if (i > j)\n"
+			"    return 0;\n"
+			"\n"
+			"# 38 \"test.h\"\n"
+			"    random_func ();\n"
+			"    test_func ();\n"
+			"}\n");
+  file_cache fc;
+
+  // Perform all tests twice to verify memoized calls behave the same
+  for (int i = 0; i < 2; i++)
+    {
+      /* Read back a specific line from the tempfile.  */
+      char_span source_line
+	= fc.get_source_line_preprocessed ("test.cpp", tmp.get_filename (), 4);
+      ASSERT_TRUE (source_line);
+      ASSERT_TRUE (source_line.get_buffer () != NULL);
+      ASSERT_EQ (20, source_line.length ());
+      ASSERT_TRUE (!strncmp ("    do_something ();", source_line.get_buffer (),
+			     source_line.length ()));
+
+      source_line
+	= fc.get_source_line_preprocessed ("test.h", tmp.get_filename (), 35);
+      ASSERT_TRUE (source_line);
+      ASSERT_TRUE (source_line.get_buffer () != NULL);
+      ASSERT_EQ (25, source_line.length ());
+      ASSERT_TRUE (!strncmp ("    do_something_else ();",
+			     source_line.get_buffer (), source_line.length ()));
+
+      source_line
+	= fc.get_source_line_preprocessed ("test.h", tmp.get_filename (), 39);
+      ASSERT_TRUE (source_line);
+      ASSERT_TRUE (source_line.get_buffer () != NULL);
+      ASSERT_EQ (17, source_line.length ());
+      ASSERT_TRUE (!strncmp ("    test_func ();", source_line.get_buffer (),
+			     source_line.length ()));
+
+      source_line
+	= fc.get_source_line_preprocessed ("test.hpp", tmp.get_filename (), 35);
+      ASSERT_TRUE (source_line);
+      ASSERT_TRUE (source_line.get_buffer () != NULL);
+      ASSERT_EQ (18, source_line.length ());
+      ASSERT_TRUE (!strncmp ("    do_nothing ();", source_line.get_buffer (),
+			     source_line.length ()));
+
+      // file not present in preprocessor output
+      source_line
+	= fc.get_source_line_preprocessed ("other.h", tmp.get_filename (), 4);
+      ASSERT_FALSE (source_line);
+      ASSERT_TRUE (source_line.get_buffer () == NULL);
+
+      // empty lines omitted by preprocessor
+      source_line
+	= fc.get_source_line_preprocessed ("test.h", tmp.get_filename (), 2);
+      ASSERT_FALSE (source_line);
+      ASSERT_TRUE (source_line.get_buffer () == NULL);
+    }
+
+  // Verify that the cache is working as expected
+  // line not previously read but all linemarkers are in the cache
+  char_span source_line
+    = fc.get_source_line_preprocessed ("test.cpp", tmp.get_filename (), 6);
+  ASSERT_TRUE (source_line);
+  ASSERT_TRUE (source_line.get_buffer () != NULL);
+  ASSERT_TRUE (!strncmp ("    int i = 5;", source_line.get_buffer (),
+			 source_line.length ()));
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -1087,6 +1775,9 @@ file_cache_cc_tests ()
   test_reading_source_line ();
   test_reading_source_buffer ();
   test_replacement ();
+  test_parsing_linemarker ();
+  test_linemarker_cache ();
+  test_reading_source_line_preprocessed ();
 }
 
 } // namespace selftest
diff --git a/gcc/diagnostics/file-cache.h b/gcc/diagnostics/file-cache.h
index 09a16a887f4..6efd3276fcf 100644
--- a/gcc/diagnostics/file-cache.h
+++ b/gcc/diagnostics/file-cache.h
@@ -69,6 +69,10 @@ class char_span
    need to be in this header.  */
 class file_cache_slot;
 
+/* Forward decl of the linemarker cache used when reading source lines from
+   a preprocessed input file; its definition is private to file-cache.cc.  */
+struct linemarker_cache;
+
 /* A cache of source files for use when emitting diagnostics
    (and in a few places in the C/C++ frontends).
 
@@ -101,6 +105,10 @@ class file_cache
 
   char_span get_source_file_content (const char *file_path);
   char_span get_source_line (const char *file_path, int line);
+  char_span get_source_line_preprocessed (const char *source_name,
+					  const char *file_path, int line);
+  char_span get_source_line_maybe_preprocessed (const char *file_path,
+						int line);
   bool missing_trailing_newline_p (const char *file_path);
 
   void add_buffered_content (const char *file_path,
@@ -109,6 +117,13 @@ class file_cache
 
   void tune (size_t num_file_slots, size_t lines);
 
+  /* Configure reading of source lines from a preprocessed input file.  */
+  void set_preprocessed (bool preprocessed) { m_is_preprocessed = preprocessed; }
+  void set_main_input_file_path (const char *path)
+  {
+    m_main_input_file_path = path;
+  }
+
  private:
   file_cache_slot *evicted_cache_tab_entry (unsigned *highest_use_count);
   file_cache_slot *add_file (const char *file_path);
@@ -118,6 +133,17 @@ class file_cache
   size_t m_num_file_slots;
   file_cache_slot *m_file_slots;
   input_context m_input_context;
+
+  /* Cache of linemarkers seen in the preprocessed input file, used to speed
+     up get_source_line_preprocessed.  */
+  linemarker_cache *m_linemarker_cache;
+
+  /* The original (preprocessed) input file to read source lines from when
+     m_is_preprocessed is true.  */
+  const char *m_main_input_file_path;
+
+  /* True if the input file is treated as preprocessed.  */
+  bool m_is_preprocessed;
 };
 
 } // namespace diagnostics
diff --git a/gcc/diagnostics/source-printing.cc b/gcc/diagnostics/source-printing.cc
index db9b3c1f241..607e08d032e 100644
--- a/gcc/diagnostics/source-printing.cc
+++ b/gcc/diagnostics/source-printing.cc
@@ -2202,9 +2202,9 @@ layout::calculate_x_offset_display ()
       return;
     }
 
-  const diagnostics::char_span line
-    = m_file_cache.get_source_line (m_exploc.file,
-				    m_exploc.line);
+  diagnostics::char_span line
+    = m_file_cache.get_source_line_maybe_preprocessed (m_exploc.file,
+						       m_exploc.line);
   if (!line)
     {
       /* Nothing to do, we couldn't find the source line.  */
@@ -3365,7 +3365,8 @@ source_line::source_line (diagnostics::file_cache &fc,
 			  const char *filename,
 			  int line)
 {
-  diagnostics::char_span span = fc.get_source_line (filename, line);
+  diagnostics::char_span span
+    = fc.get_source_line_maybe_preprocessed (filename, line);
   chars = span.get_buffer ();
   width = span.length ();
 }
@@ -3722,7 +3723,9 @@ layout_printer<TextOrHtml>::print_line (linenum_type row)
   typename TextOrHtml::auto_check_tag_nesting sentinel (m_text_or_html);
 
   diagnostics::char_span line
-    = m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row);
+    = m_layout.m_file_cache.get_source_line_maybe_preprocessed
+	(m_layout.m_exploc.file, row);
+
   if (!line)
     return;
 
diff --git a/gcc/opts-global.cc b/gcc/opts-global.cc
index 30fad240d83..4187b97a1a8 100644
--- a/gcc/opts-global.cc
+++ b/gcc/opts-global.cc
@@ -26,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree.h"
 #include "tree-pass.h"
 #include "diagnostic.h"
+#include "diagnostics/file-cache.h"
 #include "opts.h"
 #include "flags.h"
 #include "langhooks.h"
@@ -231,6 +232,11 @@ read_cmdline_options (struct gcc_options *opts, struct gcc_options *opts_set,
 	  if (opts->x_main_input_filename == NULL)
 	    {
 	      opts->x_main_input_filename = decoded_options[i].arg;
+	      // remember original input filename in the file cache
+	      // because if a preprocessed file is compiled, this is
+	      // changed to the original source file name later
+	      dc->get_file_cache ().set_main_input_file_path
+		(opts->x_main_input_filename);
 	      opts->x_main_input_baselength
 		= base_of_path (opts->x_main_input_filename,
 				&opts->x_main_input_basename);
diff --git a/gcc/testsuite/g++.dg/lookup/missing-std-include-11.C b/gcc/testsuite/g++.dg/lookup/missing-std-include-11.C
index ec2c494c557..5128f415c43 100644
--- a/gcc/testsuite/g++.dg/lookup/missing-std-include-11.C
+++ b/gcc/testsuite/g++.dg/lookup/missing-std-include-11.C
@@ -40,4 +40,4 @@ int main ()
 }
 // { dg-additional-files "missing-std-include-10.h" }
 // { dg-regexp {[^\n]*: error: 'strcmp' was not declared in this scope\n *return strcmp [^\n]*;\n *\^~*\n} }
-// { dg-regexp {[^\n]* note: 'strcmp' is defined in header[^\n]*\n #include "missing-std-include-10.h"\n\+#include <cstring>\n // HERE\n} }
+// { dg-regexp {[^\n]* note: 'strcmp' is defined in header[^\n]*\n} }
