Optimize HeaderChecker::IsDependencyOf using HashTableBase

This improved performance of `gn gen --check` from 55.7s to 21.7s on my
M4 Max macbook as memory allocation in unordered_map seems slow on
macOS.

Bug: 484862257
Change-Id: I688c316c72938087d65ad5487f554b10665a29e3
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21120
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: David Turner <digit@google.com>
diff --git a/src/gn/header_checker.cc b/src/gn/header_checker.cc
index ea76f2f..d363591 100644
--- a/src/gn/header_checker.cc
+++ b/src/gn/header_checker.cc
@@ -5,8 +5,6 @@
 #include "gn/header_checker.h"
 
 #include <algorithm>
-#include <unordered_map>
-
 #include "base/containers/queue.h"
 #include "base/files/file_util.h"
 #include "base/strings/string_util.h"
@@ -17,6 +15,7 @@
 #include "gn/config_values_extractors.h"
 #include "gn/err.h"
 #include "gn/filesystem_utils.h"
+#include "gn/hash_table_base.h"
 #include "gn/scheduler.h"
 #include "gn/swift_values.h"
 #include "gn/target.h"
@@ -115,6 +114,59 @@
                                      is_marked_friend->label());
 }
 
+// Data for BreadcrumbNode.
+//
+// This class is a trivial type so it can be used in HashTableBase.
+struct BreadcrumbNode {
+  const Target* target;
+  const Target* src_target;
+  bool is_public;
+
+  bool is_null() const { return !target; }
+  static bool is_tombstone() { return false; }
+  bool is_valid() const { return !is_null(); }
+  size_t hash_value() const { return std::hash<const Target*>()(target); }
+};
+
+struct BreadcrumbTable : public HashTableBase<BreadcrumbNode> {
+  using Base = HashTableBase<BreadcrumbNode>;
+  using Node = Base::Node;
+
+  // Since we only insert, we don't need to return success/failure.
+  // We can also assume that key uniqueness is checked before insertion if necessary,
+  // or that we simply overwrite (though BFS usually checks existence first).
+  //
+  // In IsDependencyOf, we use the return value checking if it was already there.
+  // So we need an Insert that returns whether it was new.
+  bool Insert(const Target* target, const Target* src_target, bool is_public) {
+    size_t hash = std::hash<const Target*>()(target);
+    Node* node = NodeLookup(hash, [target](const Node* n) {
+      return n->target == target;
+    });
+
+    if (node->is_valid())
+      return false;
+
+    node->target = target;
+    node->src_target = src_target;
+    node->is_public = is_public;
+    UpdateAfterInsert(false);
+    return true;
+  }
+
+  // Returns the ChainLink for the given target, or a null-target ChainLink if not found.
+  HeaderChecker::ChainLink GetLink(const Target* target) const {
+    size_t hash = std::hash<const Target*>()(target);
+    const Node* node = NodeLookup(hash, [target](const Node* n) {
+      return n->target == target;
+    });
+
+    if (node->is_valid())
+      return HeaderChecker::ChainLink(node->src_target, node->is_public);
+    return HeaderChecker::ChainLink();
+  }
+};
+
 }  // namespace
 
 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
@@ -592,10 +644,8 @@
   // a shortest dependency chain (in reverse order) from search_from to
   // search_for.
 
-  std::unordered_map<const Target*, ChainLink> breadcrumbs;
-  if (targets_count_ > 0) {
-    breadcrumbs.reserve(std::min(targets_count_, static_cast<size_t>(1024)));
-  }
+  BreadcrumbTable breadcrumbs;
+
   base::queue<ChainLink> work_queue;
   work_queue.push(ChainLink(search_from, true));
 
@@ -610,7 +660,8 @@
       chain->clear();
       while (target != search_from) {
         chain->push_back(cur_link);
-        cur_link = breadcrumbs[target];
+        ChainLink next_link = breadcrumbs.GetLink(target);
+        cur_link = next_link;
         target = cur_link.target;
       }
       chain->push_back(ChainLink(search_from, true));
@@ -619,7 +670,7 @@
 
     // Always consider public dependencies as possibilities.
     for (const auto& dep : target->public_deps()) {
-      if (breadcrumbs.emplace(dep.ptr, cur_link).second)
+      if (breadcrumbs.Insert(dep.ptr, target, cur_link.is_public))
         work_queue.push(ChainLink(dep.ptr, true));
     }
 
@@ -630,7 +681,7 @@
       // public/private-ness.
       first_time = false;
       for (const auto& dep : target->private_deps()) {
-        if (breadcrumbs.emplace(dep.ptr, cur_link).second)
+        if (breadcrumbs.Insert(dep.ptr, target, cur_link.is_public))
           work_queue.push(ChainLink(dep.ptr, false));
       }
     }