Move cooked_index_shard to new files

This moves cooked_index_shard to a couple of new files,
dwarf2/cooked-index-shard.[ch].  The rationale is the same as the
previous patch: cooked-index.h had to be split to enable other
cleanups.

Approved-By: Simon Marchi <simon.marchi@efficios.com>


diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index b4e5407..0c4102d 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1098,6 +1098,7 @@
 	dwarf2/comp-unit-head.c \
 	dwarf2/cooked-index.c \
 	dwarf2/cooked-index-entry.c \
+	dwarf2/cooked-index-shard.c \
 	dwarf2/cooked-index-worker.c \
 	dwarf2/cooked-indexer.c \
 	dwarf2/cu.c \
@@ -1358,6 +1359,7 @@
 	dwarf2/aranges.h \
 	dwarf2/cooked-index.h \
 	dwarf2/cooked-index-entry.h \
+	dwarf2/cooked-index-shard.h \
 	dwarf2/cooked-index-worker.h \
 	dwarf2/cooked-indexer.h \
 	dwarf2/cu.h \
diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c
new file mode 100644
index 0000000..41844cd
--- /dev/null
+++ b/gdb/dwarf2/cooked-index-shard.c
@@ -0,0 +1,331 @@
+/* Shards for the cooked index
+
+   Copyright (C) 2022-2024 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program 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 of the License, or
+   (at your option) any later version.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "dwarf2/cooked-index-shard.h"
+#include "dwarf2/tag.h"
+#include "dwarf2/index-common.h"
+#include "cp-support.h"
+#include "c-lang.h"
+#include "ada-lang.h"
+
+/* Return true if a plain "main" could be the main program for this
+   language.  Languages that are known to use some other mechanism are
+   excluded here.  */
+
+static bool
+language_may_use_plain_main (enum language lang)
+{
+  /* No need to handle "unknown" here.  */
+  return (lang == language_c
+	  || lang == language_objc
+	  || lang == language_cplus
+	  || lang == language_m2
+	  || lang == language_asm
+	  || lang == language_opencl
+	  || lang == language_minimal);
+}
+
+/* See cooked-index.h.  */
+
+cooked_index_entry *
+cooked_index_shard::create (sect_offset die_offset,
+			    enum dwarf_tag tag,
+			    cooked_index_flag flags,
+			    enum language lang,
+			    const char *name,
+			    cooked_index_entry_ref parent_entry,
+			    dwarf2_per_cu *per_cu)
+{
+  if (tag == DW_TAG_module || tag == DW_TAG_namespace)
+    flags &= ~IS_STATIC;
+  else if (lang == language_cplus
+	   && (tag == DW_TAG_class_type
+	       || tag == DW_TAG_interface_type
+	       || tag == DW_TAG_structure_type
+	       || tag == DW_TAG_union_type
+	       || tag == DW_TAG_enumeration_type
+	       || tag == DW_TAG_enumerator))
+    flags &= ~IS_STATIC;
+  else if (tag_is_type (tag))
+    flags |= IS_STATIC;
+
+  return new (&m_storage) cooked_index_entry (die_offset, tag, flags,
+					      lang, name, parent_entry,
+					      per_cu);
+}
+
+/* See cooked-index.h.  */
+
+cooked_index_entry *
+cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
+			 cooked_index_flag flags, enum language lang,
+			 const char *name, cooked_index_entry_ref parent_entry,
+			 dwarf2_per_cu *per_cu)
+{
+  cooked_index_entry *result = create (die_offset, tag, flags, lang, name,
+				       parent_entry, per_cu);
+  m_entries.push_back (result);
+
+  /* An explicitly-tagged main program should always override the
+     implicit "main" discovery.  */
+  if ((flags & IS_MAIN) != 0)
+    m_main = result;
+  else if ((flags & IS_PARENT_DEFERRED) == 0
+	   && parent_entry.resolved == nullptr
+	   && m_main == nullptr
+	   && language_may_use_plain_main (lang)
+	   && strcmp (name, "main") == 0)
+    m_main = result;
+
+  return result;
+}
+
+/* See cooked-index.h.  */
+
+void
+cooked_index_shard::handle_gnat_encoded_entry
+     (cooked_index_entry *entry,
+      htab_t gnat_entries,
+      std::vector<cooked_index_entry *> &new_entries)
+{
+  /* We decode Ada names in a particular way: operators and wide
+     characters are left as-is.  This is done to make name matching a
+     bit simpler; and for wide characters, it means the choice of Ada
+     source charset does not affect the indexer directly.  */
+  std::string canonical = ada_decode (entry->name, false, false, false);
+  if (canonical.empty ())
+    {
+      entry->canonical = entry->name;
+      return;
+    }
+  std::vector<std::string_view> names = split_name (canonical.c_str (),
+						    split_style::DOT_STYLE);
+  std::string_view tail = names.back ();
+  names.pop_back ();
+
+  const cooked_index_entry *parent = nullptr;
+  for (const auto &name : names)
+    {
+      uint32_t hashval = dwarf5_djb_hash (name);
+      void **slot = htab_find_slot_with_hash (gnat_entries, &name,
+					      hashval, INSERT);
+      /* CUs are processed in order, so we only need to check the most
+	 recent entry.  */
+      cooked_index_entry *last = (cooked_index_entry *) *slot;
+      if (last == nullptr || last->per_cu != entry->per_cu)
+	{
+	  const char *new_name = m_names.insert (name);
+	  last = create (entry->die_offset, DW_TAG_module,
+			 IS_SYNTHESIZED, language_ada, new_name, parent,
+			 entry->per_cu);
+	  last->canonical = last->name;
+	  new_entries.push_back (last);
+	  *slot = last;
+	}
+
+      parent = last;
+    }
+
+  entry->set_parent (parent);
+  entry->canonical = m_names.insert (tail);
+}
+
+/* Hash a cooked index entry by name pointer value.
+
+   We can use pointer equality here because names come from .debug_str, which
+   will normally be unique-ified by the linker.  Also, duplicates are relatively
+   harmless -- they just mean a bit of extra memory is used.  */
+
+struct cooked_index_entry_name_ptr_hash
+{
+  using is_avalanching = void;
+
+  std::uint64_t operator () (const cooked_index_entry *entry) const noexcept
+  {
+    return ankerl::unordered_dense::hash<const char *> () (entry->name);
+  }
+};
+
+/* Compare cooked index entries by name pointer value.  */
+
+struct cooked_index_entry_name_ptr_eq
+{
+  bool operator () (const cooked_index_entry *a,
+		    const cooked_index_entry *b) const noexcept
+  {
+    return a->name == b->name;
+  }
+};
+
+/* See cooked-index.h.  */
+
+void
+cooked_index_shard::finalize (const parent_map_map *parent_maps)
+{
+  gdb::unordered_set<const cooked_index_entry *,
+		     cooked_index_entry_name_ptr_hash,
+		     cooked_index_entry_name_ptr_eq> seen_names;
+
+  auto hash_entry = [] (const void *e)
+    {
+      const cooked_index_entry *entry = (const cooked_index_entry *) e;
+      return dwarf5_djb_hash (entry->canonical);
+    };
+
+  auto eq_entry = [] (const void *a, const void *b) -> int
+    {
+      const cooked_index_entry *ae = (const cooked_index_entry *) a;
+      const std::string_view *sv = (const std::string_view *) b;
+      return (strlen (ae->canonical) == sv->length ()
+	      && strncasecmp (ae->canonical, sv->data (), sv->length ()) == 0);
+    };
+
+  htab_up gnat_entries (htab_create_alloc (10, hash_entry, eq_entry,
+					   nullptr, xcalloc, xfree));
+  std::vector<cooked_index_entry *> new_gnat_entries;
+
+  for (cooked_index_entry *entry : m_entries)
+    {
+      if ((entry->flags & IS_PARENT_DEFERRED) != 0)
+	{
+	  const cooked_index_entry *new_parent
+	    = parent_maps->find (entry->get_deferred_parent ());
+	  entry->resolve_parent (new_parent);
+	}
+
+      /* Note that this code must be kept in sync with
+	 language_requires_canonicalization.  */
+      gdb_assert (entry->canonical == nullptr);
+      if ((entry->flags & IS_LINKAGE) != 0)
+	entry->canonical = entry->name;
+      else if (entry->lang == language_ada)
+	{
+	  /* Newer versions of GNAT emit DW_TAG_module and use a
+	     hierarchical structure.  In this case, we don't need to
+	     do any extra work.  This can be detected by looking for a
+	     GNAT-encoded name.  */
+	  if (strstr (entry->name, "__") == nullptr)
+	    {
+	      entry->canonical = entry->name;
+
+	      /* If the entry does not have a parent, then there's
+		 nothing extra to do here -- the entry itself is
+		 sufficient.
+
+		 However, if it does have a parent, we have to
+		 synthesize an entry with the full name.  This is
+		 unfortunate, but it's necessary due to how some of
+		 the Ada name-lookup code currently works.  For
+		 example, without this, ada_get_tsd_type will
+		 fail.
+
+		 Eventually it would be good to change the Ada lookup
+		 code, and then remove these entries (and supporting
+		 code in cooked_index_entry::full_name).  */
+	      if (entry->get_parent () != nullptr)
+		{
+		  const char *fullname
+		    = entry->full_name (&m_storage, FOR_ADA_LINKAGE_NAME);
+		  cooked_index_entry *linkage = create (entry->die_offset,
+							entry->tag,
+							(entry->flags
+							 | IS_LINKAGE
+							 | IS_SYNTHESIZED),
+							language_ada,
+							fullname,
+							nullptr,
+							entry->per_cu);
+		  linkage->canonical = fullname;
+		  new_gnat_entries.push_back (linkage);
+		}
+	    }
+	  else
+	    handle_gnat_encoded_entry (entry, gnat_entries.get (),
+				       new_gnat_entries);
+	}
+      else if (entry->lang == language_cplus || entry->lang == language_c)
+	{
+	  auto [it, inserted] = seen_names.insert (entry);
+
+	  if (inserted)
+	    {
+	      /* No entry with that name was present, compute the canonical
+		 name.  */
+	      gdb::unique_xmalloc_ptr<char> canon_name
+		= (entry->lang == language_cplus
+		   ? cp_canonicalize_string (entry->name)
+		   : c_canonicalize_name (entry->name));
+	      if (canon_name == nullptr)
+		entry->canonical = entry->name;
+	      else
+		entry->canonical = m_names.insert (std::move (canon_name));
+	    }
+	  else
+	    {
+	      /* An entry with that name was present, re-use its canonical
+		 name.  */
+	      entry->canonical = (*it)->canonical;
+	    }
+	}
+      else
+	entry->canonical = entry->name;
+    }
+
+  /* Make sure any new Ada entries end up in the results.  This isn't
+     done when creating these new entries to avoid invalidating the
+     m_entries iterator used in the foreach above.  */
+  m_entries.insert (m_entries.end (), new_gnat_entries.begin (),
+		    new_gnat_entries.end ());
+
+  m_entries.shrink_to_fit ();
+  std::sort (m_entries.begin (), m_entries.end (),
+	     [] (const cooked_index_entry *a, const cooked_index_entry *b)
+	     {
+	       return *a < *b;
+	     });
+}
+
+/* See cooked-index.h.  */
+
+cooked_index_shard::range
+cooked_index_shard::find (const std::string &name, bool completing) const
+{
+  struct comparator
+  {
+    cooked_index_entry::comparison_mode mode;
+
+    bool operator() (const cooked_index_entry *entry,
+		     const char *name) const noexcept
+    {
+      return cooked_index_entry::compare (entry->canonical, name, mode) < 0;
+    }
+
+    bool operator() (const char *name,
+		     const cooked_index_entry *entry) const noexcept
+    {
+      return cooked_index_entry::compare (entry->canonical, name, mode) > 0;
+    }
+  };
+
+  return std::make_from_tuple<range>
+    (std::equal_range (m_entries.cbegin (), m_entries.cend (), name.c_str (),
+		       comparator { (completing
+				     ? cooked_index_entry::COMPLETE
+				     : cooked_index_entry::MATCH) }));
+}
diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h
new file mode 100644
index 0000000..eb80926
--- /dev/null
+++ b/gdb/dwarf2/cooked-index-shard.h
@@ -0,0 +1,134 @@
+/* Shards for the cooked index
+
+   Copyright (C) 2022-2024 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program 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 of the License, or
+   (at your option) any later version.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_DWARF2_COOKED_INDEX_SHARD_H
+#define GDB_DWARF2_COOKED_INDEX_SHARD_H
+
+#include "dwarf2/cooked-index-entry.h"
+#include "dwarf2/types.h"
+#include "gdbsupport/gdb_obstack.h"
+#include "addrmap.h"
+#include "gdbsupport/iterator-range.h"
+#include "gdbsupport/string-set.h"
+
+/* An index of interesting DIEs.  This is "cooked", in contrast to a
+   mapped .debug_names or .gdb_index, which are "raw".  An entry in
+   the index is of type cooked_index_entry.
+
+   Operations on the index are described below.  They are chosen to
+   make it relatively simple to implement the symtab "quick"
+   methods.  */
+class cooked_index_shard
+{
+public:
+  cooked_index_shard () = default;
+  DISABLE_COPY_AND_ASSIGN (cooked_index_shard);
+
+  /* Create a new cooked_index_entry and register it with this object.
+     Entries are owned by this object.  The new item is returned.  */
+  cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
+			   cooked_index_flag flags, enum language lang,
+			   const char *name,
+			   cooked_index_entry_ref parent_entry,
+			   dwarf2_per_cu *per_cu);
+
+  /* Install a new fixed addrmap from the given mutable addrmap.  */
+  void install_addrmap (addrmap_mutable *map)
+  {
+    gdb_assert (m_addrmap == nullptr);
+    m_addrmap = new (&m_storage) addrmap_fixed (&m_storage, map);
+  }
+
+  friend class cooked_index;
+
+  /* A simple range over part of m_entries.  */
+  typedef iterator_range<std::vector<cooked_index_entry *>::const_iterator>
+       range;
+
+  /* Return a range of all the entries.  */
+  range all_entries () const
+  {
+    return { m_entries.cbegin (), m_entries.cend () };
+  }
+
+  /* Look up an entry by name.  Returns a range of all matching
+     results.  If COMPLETING is true, then a larger range, suitable
+     for completion, will be returned.  */
+  range find (const std::string &name, bool completing) const;
+
+private:
+
+  /* Return the entry that is believed to represent the program's
+     "main".  This will return NULL if no such entry is available.  */
+  const cooked_index_entry *get_main () const
+  {
+    return m_main;
+  }
+
+  /* Look up ADDR in the address map, and return either the
+     corresponding CU, or nullptr if the address could not be
+     found.  */
+  dwarf2_per_cu *lookup (unrelocated_addr addr)
+  {
+    if (m_addrmap == nullptr)
+      return nullptr;
+
+    return (static_cast<dwarf2_per_cu *> (m_addrmap->find ((CORE_ADDR) addr)));
+  }
+
+  /* Create a new cooked_index_entry and register it with this object.
+     Entries are owned by this object.  The new item is returned.  */
+  cooked_index_entry *create (sect_offset die_offset,
+			      enum dwarf_tag tag,
+			      cooked_index_flag flags,
+			      enum language lang,
+			      const char *name,
+			      cooked_index_entry_ref parent_entry,
+			      dwarf2_per_cu *per_cu);
+
+  /* When GNAT emits mangled ("encoded") names in the DWARF, and does
+     not emit the module structure, we still need this structuring to
+     do lookups.  This function recreates that information for an
+     existing entry, modifying ENTRY as appropriate.  Any new entries
+     are added to NEW_ENTRIES.  */
+  void handle_gnat_encoded_entry
+       (cooked_index_entry *entry, htab_t gnat_entries,
+	std::vector<cooked_index_entry *> &new_entries);
+
+  /* Finalize the index.  This should be called a single time, when
+     the index has been fully populated.  It enters all the entries
+     into the internal table and fixes up all missing parent links.
+     This may be invoked in a worker thread.  */
+  void finalize (const parent_map_map *parent_maps);
+
+  /* Storage for the entries.  */
+  auto_obstack m_storage;
+  /* List of all entries.  */
+  std::vector<cooked_index_entry *> m_entries;
+  /* If we found an entry with 'is_main' set, store it here.  */
+  cooked_index_entry *m_main = nullptr;
+  /* The addrmap.  This maps address ranges to dwarf2_per_cu objects.  */
+  addrmap_fixed *m_addrmap = nullptr;
+  /* Storage for canonical names.  */
+  gdb::string_set m_names;
+};
+
+using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>;
+
+#endif /* GDB_DWARF2_COOKED_INDEX_SHARD_H */
diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c
index 9d5f152..5d205a8 100644
--- a/gdb/dwarf2/cooked-index.c
+++ b/gdb/dwarf2/cooked-index.c
@@ -18,14 +18,9 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "dwarf2/cooked-index.h"
-#include "dwarf2/index-common.h"
 #include "dwarf2/read.h"
 #include "dwarf2/stringify.h"
 #include "dwarf2/index-cache.h"
-#include "cp-support.h"
-#include "c-lang.h"
-#include "ada-lang.h"
-#include "dwarf2/tag.h"
 #include "event-top.h"
 #include "exceptions.h"
 #include "split-name.h"
@@ -55,312 +50,6 @@ language_requires_canonicalization (enum language lang)
 	  || lang == language_cplus);
 }
 
-/* Return true if a plain "main" could be the main program for this
-   language.  Languages that are known to use some other mechanism are
-   excluded here.  */
-
-static bool
-language_may_use_plain_main (enum language lang)
-{
-  /* No need to handle "unknown" here.  */
-  return (lang == language_c
-	  || lang == language_objc
-	  || lang == language_cplus
-	  || lang == language_m2
-	  || lang == language_asm
-	  || lang == language_opencl
-	  || lang == language_minimal);
-}
-
-/* See cooked-index.h.  */
-
-cooked_index_entry *
-cooked_index_shard::create (sect_offset die_offset,
-			    enum dwarf_tag tag,
-			    cooked_index_flag flags,
-			    enum language lang,
-			    const char *name,
-			    cooked_index_entry_ref parent_entry,
-			    dwarf2_per_cu *per_cu)
-{
-  if (tag == DW_TAG_module || tag == DW_TAG_namespace)
-    flags &= ~IS_STATIC;
-  else if (lang == language_cplus
-	   && (tag == DW_TAG_class_type
-	       || tag == DW_TAG_interface_type
-	       || tag == DW_TAG_structure_type
-	       || tag == DW_TAG_union_type
-	       || tag == DW_TAG_enumeration_type
-	       || tag == DW_TAG_enumerator))
-    flags &= ~IS_STATIC;
-  else if (tag_is_type (tag))
-    flags |= IS_STATIC;
-
-  return new (&m_storage) cooked_index_entry (die_offset, tag, flags,
-					      lang, name, parent_entry,
-					      per_cu);
-}
-
-/* See cooked-index.h.  */
-
-cooked_index_entry *
-cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
-			 cooked_index_flag flags, enum language lang,
-			 const char *name, cooked_index_entry_ref parent_entry,
-			 dwarf2_per_cu *per_cu)
-{
-  cooked_index_entry *result = create (die_offset, tag, flags, lang, name,
-				       parent_entry, per_cu);
-  m_entries.push_back (result);
-
-  /* An explicitly-tagged main program should always override the
-     implicit "main" discovery.  */
-  if ((flags & IS_MAIN) != 0)
-    m_main = result;
-  else if ((flags & IS_PARENT_DEFERRED) == 0
-	   && parent_entry.resolved == nullptr
-	   && m_main == nullptr
-	   && language_may_use_plain_main (lang)
-	   && strcmp (name, "main") == 0)
-    m_main = result;
-
-  return result;
-}
-
-/* See cooked-index.h.  */
-
-void
-cooked_index_shard::handle_gnat_encoded_entry
-     (cooked_index_entry *entry,
-      htab_t gnat_entries,
-      std::vector<cooked_index_entry *> &new_entries)
-{
-  /* We decode Ada names in a particular way: operators and wide
-     characters are left as-is.  This is done to make name matching a
-     bit simpler; and for wide characters, it means the choice of Ada
-     source charset does not affect the indexer directly.  */
-  std::string canonical = ada_decode (entry->name, false, false, false);
-  if (canonical.empty ())
-    {
-      entry->canonical = entry->name;
-      return;
-    }
-  std::vector<std::string_view> names = split_name (canonical.c_str (),
-						    split_style::DOT_STYLE);
-  std::string_view tail = names.back ();
-  names.pop_back ();
-
-  const cooked_index_entry *parent = nullptr;
-  for (const auto &name : names)
-    {
-      uint32_t hashval = dwarf5_djb_hash (name);
-      void **slot = htab_find_slot_with_hash (gnat_entries, &name,
-					      hashval, INSERT);
-      /* CUs are processed in order, so we only need to check the most
-	 recent entry.  */
-      cooked_index_entry *last = (cooked_index_entry *) *slot;
-      if (last == nullptr || last->per_cu != entry->per_cu)
-	{
-	  const char *new_name = m_names.insert (name);
-	  last = create (entry->die_offset, DW_TAG_module,
-			 IS_SYNTHESIZED, language_ada, new_name, parent,
-			 entry->per_cu);
-	  last->canonical = last->name;
-	  new_entries.push_back (last);
-	  *slot = last;
-	}
-
-      parent = last;
-    }
-
-  entry->set_parent (parent);
-  entry->canonical = m_names.insert (tail);
-}
-
-/* Hash a cooked index entry by name pointer value.
-
-   We can use pointer equality here because names come from .debug_str, which
-   will normally be unique-ified by the linker.  Also, duplicates are relatively
-   harmless -- they just mean a bit of extra memory is used.  */
-
-struct cooked_index_entry_name_ptr_hash
-{
-  using is_avalanching = void;
-
-  std::uint64_t operator () (const cooked_index_entry *entry) const noexcept
-  {
-    return ankerl::unordered_dense::hash<const char *> () (entry->name);
-  }
-};
-
-/* Compare cooked index entries by name pointer value.  */
-
-struct cooked_index_entry_name_ptr_eq
-{
-  bool operator () (const cooked_index_entry *a,
-		    const cooked_index_entry *b) const noexcept
-  {
-    return a->name == b->name;
-  }
-};
-
-/* See cooked-index.h.  */
-
-void
-cooked_index_shard::finalize (const parent_map_map *parent_maps)
-{
-  gdb::unordered_set<const cooked_index_entry *,
-		     cooked_index_entry_name_ptr_hash,
-		     cooked_index_entry_name_ptr_eq> seen_names;
-
-  auto hash_entry = [] (const void *e)
-    {
-      const cooked_index_entry *entry = (const cooked_index_entry *) e;
-      return dwarf5_djb_hash (entry->canonical);
-    };
-
-  auto eq_entry = [] (const void *a, const void *b) -> int
-    {
-      const cooked_index_entry *ae = (const cooked_index_entry *) a;
-      const std::string_view *sv = (const std::string_view *) b;
-      return (strlen (ae->canonical) == sv->length ()
-	      && strncasecmp (ae->canonical, sv->data (), sv->length ()) == 0);
-    };
-
-  htab_up gnat_entries (htab_create_alloc (10, hash_entry, eq_entry,
-					   nullptr, xcalloc, xfree));
-  std::vector<cooked_index_entry *> new_gnat_entries;
-
-  for (cooked_index_entry *entry : m_entries)
-    {
-      if ((entry->flags & IS_PARENT_DEFERRED) != 0)
-	{
-	  const cooked_index_entry *new_parent
-	    = parent_maps->find (entry->get_deferred_parent ());
-	  entry->resolve_parent (new_parent);
-	}
-
-      /* Note that this code must be kept in sync with
-	 language_requires_canonicalization.  */
-      gdb_assert (entry->canonical == nullptr);
-      if ((entry->flags & IS_LINKAGE) != 0)
-	entry->canonical = entry->name;
-      else if (entry->lang == language_ada)
-	{
-	  /* Newer versions of GNAT emit DW_TAG_module and use a
-	     hierarchical structure.  In this case, we don't need to
-	     do any extra work.  This can be detected by looking for a
-	     GNAT-encoded name.  */
-	  if (strstr (entry->name, "__") == nullptr)
-	    {
-	      entry->canonical = entry->name;
-
-	      /* If the entry does not have a parent, then there's
-		 nothing extra to do here -- the entry itself is
-		 sufficient.
-
-		 However, if it does have a parent, we have to
-		 synthesize an entry with the full name.  This is
-		 unfortunate, but it's necessary due to how some of
-		 the Ada name-lookup code currently works.  For
-		 example, without this, ada_get_tsd_type will
-		 fail.
-
-		 Eventually it would be good to change the Ada lookup
-		 code, and then remove these entries (and supporting
-		 code in cooked_index_entry::full_name).  */
-	      if (entry->get_parent () != nullptr)
-		{
-		  const char *fullname
-		    = entry->full_name (&m_storage, FOR_ADA_LINKAGE_NAME);
-		  cooked_index_entry *linkage = create (entry->die_offset,
-							entry->tag,
-							(entry->flags
-							 | IS_LINKAGE
-							 | IS_SYNTHESIZED),
-							language_ada,
-							fullname,
-							nullptr,
-							entry->per_cu);
-		  linkage->canonical = fullname;
-		  new_gnat_entries.push_back (linkage);
-		}
-	    }
-	  else
-	    handle_gnat_encoded_entry (entry, gnat_entries.get (),
-				       new_gnat_entries);
-	}
-      else if (entry->lang == language_cplus || entry->lang == language_c)
-	{
-	  auto [it, inserted] = seen_names.insert (entry);
-
-	  if (inserted)
-	    {
-	      /* No entry with that name was present, compute the canonical
-		 name.  */
-	      gdb::unique_xmalloc_ptr<char> canon_name
-		= (entry->lang == language_cplus
-		   ? cp_canonicalize_string (entry->name)
-		   : c_canonicalize_name (entry->name));
-	      if (canon_name == nullptr)
-		entry->canonical = entry->name;
-	      else
-		entry->canonical = m_names.insert (std::move (canon_name));
-	    }
-	  else
-	    {
-	      /* An entry with that name was present, re-use its canonical
-		 name.  */
-	      entry->canonical = (*it)->canonical;
-	    }
-	}
-      else
-	entry->canonical = entry->name;
-    }
-
-  /* Make sure any new Ada entries end up in the results.  This isn't
-     done when creating these new entries to avoid invalidating the
-     m_entries iterator used in the foreach above.  */
-  m_entries.insert (m_entries.end (), new_gnat_entries.begin (),
-		    new_gnat_entries.end ());
-
-  m_entries.shrink_to_fit ();
-  std::sort (m_entries.begin (), m_entries.end (),
-	     [] (const cooked_index_entry *a, const cooked_index_entry *b)
-	     {
-	       return *a < *b;
-	     });
-}
-
-/* See cooked-index.h.  */
-
-cooked_index_shard::range
-cooked_index_shard::find (const std::string &name, bool completing) const
-{
-  struct comparator
-  {
-    cooked_index_entry::comparison_mode mode;
-
-    bool operator() (const cooked_index_entry *entry,
-		     const char *name) const noexcept
-    {
-      return cooked_index_entry::compare (entry->canonical, name, mode) < 0;
-    }
-
-    bool operator() (const char *name,
-		     const cooked_index_entry *entry) const noexcept
-    {
-      return cooked_index_entry::compare (entry->canonical, name, mode) > 0;
-    }
-  };
-
-  return std::make_from_tuple<range>
-    (std::equal_range (m_entries.cbegin (), m_entries.cend (), name.c_str (),
-		       comparator { (completing
-				     ? cooked_index_entry::COMPLETE
-				     : cooked_index_entry::MATCH) }));
-}
-
 /* See cooked-index.h.  */
 
 void
diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h
index d50d1c9..beef8ff 100644
--- a/gdb/dwarf2/cooked-index.h
+++ b/gdb/dwarf2/cooked-index.h
@@ -28,13 +28,12 @@
 #include "quick-symbol.h"
 #include "gdbsupport/gdb_obstack.h"
 #include "addrmap.h"
-#include "gdbsupport/iterator-range.h"
 #include "dwarf2/mapped-index.h"
 #include "dwarf2/read.h"
 #include "dwarf2/parent-map.h"
 #include "gdbsupport/range-chain.h"
-#include "gdbsupport/string-set.h"
 #include "complaints.h"
+#include "dwarf2/cooked-index-shard.h"
 
 #if CXX_STD_THREAD
 #include <mutex>
@@ -48,110 +47,6 @@ struct cooked_index_entry;
 
 class cooked_index;
 
-/* An index of interesting DIEs.  This is "cooked", in contrast to a
-   mapped .debug_names or .gdb_index, which are "raw".  An entry in
-   the index is of type cooked_index_entry.
-
-   Operations on the index are described below.  They are chosen to
-   make it relatively simple to implement the symtab "quick"
-   methods.  */
-class cooked_index_shard
-{
-public:
-  cooked_index_shard () = default;
-  DISABLE_COPY_AND_ASSIGN (cooked_index_shard);
-
-  /* Create a new cooked_index_entry and register it with this object.
-     Entries are owned by this object.  The new item is returned.  */
-  cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
-			   cooked_index_flag flags, enum language lang,
-			   const char *name,
-			   cooked_index_entry_ref parent_entry,
-			   dwarf2_per_cu *per_cu);
-
-  /* Install a new fixed addrmap from the given mutable addrmap.  */
-  void install_addrmap (addrmap_mutable *map)
-  {
-    gdb_assert (m_addrmap == nullptr);
-    m_addrmap = new (&m_storage) addrmap_fixed (&m_storage, map);
-  }
-
-  friend class cooked_index;
-
-  /* A simple range over part of m_entries.  */
-  typedef iterator_range<std::vector<cooked_index_entry *>::const_iterator>
-       range;
-
-  /* Return a range of all the entries.  */
-  range all_entries () const
-  {
-    return { m_entries.cbegin (), m_entries.cend () };
-  }
-
-  /* Look up an entry by name.  Returns a range of all matching
-     results.  If COMPLETING is true, then a larger range, suitable
-     for completion, will be returned.  */
-  range find (const std::string &name, bool completing) const;
-
-private:
-
-  /* Return the entry that is believed to represent the program's
-     "main".  This will return NULL if no such entry is available.  */
-  const cooked_index_entry *get_main () const
-  {
-    return m_main;
-  }
-
-  /* Look up ADDR in the address map, and return either the
-     corresponding CU, or nullptr if the address could not be
-     found.  */
-  dwarf2_per_cu *lookup (unrelocated_addr addr)
-  {
-    if (m_addrmap == nullptr)
-      return nullptr;
-
-    return (static_cast<dwarf2_per_cu *> (m_addrmap->find ((CORE_ADDR) addr)));
-  }
-
-  /* Create a new cooked_index_entry and register it with this object.
-     Entries are owned by this object.  The new item is returned.  */
-  cooked_index_entry *create (sect_offset die_offset,
-			      enum dwarf_tag tag,
-			      cooked_index_flag flags,
-			      enum language lang,
-			      const char *name,
-			      cooked_index_entry_ref parent_entry,
-			      dwarf2_per_cu *per_cu);
-
-  /* When GNAT emits mangled ("encoded") names in the DWARF, and does
-     not emit the module structure, we still need this structuring to
-     do lookups.  This function recreates that information for an
-     existing entry, modifying ENTRY as appropriate.  Any new entries
-     are added to NEW_ENTRIES.  */
-  void handle_gnat_encoded_entry
-       (cooked_index_entry *entry, htab_t gnat_entries,
-	std::vector<cooked_index_entry *> &new_entries);
-
-  /* Finalize the index.  This should be called a single time, when
-     the index has been fully populated.  It enters all the entries
-     into the internal table and fixes up all missing parent links.
-     This may be invoked in a worker thread.  */
-  void finalize (const parent_map_map *parent_maps);
-
-  /* Storage for the entries.  */
-  auto_obstack m_storage;
-  /* List of all entries.  */
-  std::vector<cooked_index_entry *> m_entries;
-  /* If we found an entry with 'is_main' set, store it here.  */
-  cooked_index_entry *m_main = nullptr;
-  /* The addrmap.  This maps address ranges to dwarf2_per_cu objects.  */
-  addrmap_fixed *m_addrmap = nullptr;
-  /* Storage for canonical names.  */
-  gdb::string_set m_names;
-};
-
-using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>;
-
 /* The possible states of the index.  See the explanatory comment
    before cooked_index for more details.  */
 enum class cooked_state