Optimize HeaderChecker by parallelizing ReachabilityCache pre-calculation and removing lock contention.

This change introduces a pre-calculation phase in HeaderChecker::Run
that concurrently populates the ReachabilityCache for all relevant
targets using a WorkerPool. It also eliminates lock contention during
the file-checking phase by using atomic completion flags and a lock-free
fast path in ReachabilityCache::SearchForDependencyTo.

Key optimizations:
- Concurrent BFS pre-calculation during HeaderChecker::Run.
- Reusing the WorkerPool across pre-calculation and file-checking phases.
- Lock-free dependency lookup using std::atomic<bool> completion flags
  with acquire/release semantics in the common case where the cache is warm.
- Significantly reduces lock contention (shared_mutex) on macOS.

Verified with existing HeaderChecker unittests and profiling on
Chromium.

Benchmark 1: ./gn_main gen --root=/Users/tikuta/chromium/src --check
/Users/tikuta/chromium/src/out/no_clang_modules
  Time (mean ± σ):      6.306 s ±  0.025 s    [User: 27.219 s, System: 21.382 s]
  Range (min … max):    6.283 s …  6.333 s    5 runs

Benchmark 2: ./gn_optimized gen --root=/Users/tikuta/chromium/src
--check /Users/tikuta/chromium/src/out/no_clang_modules
  Time (mean ± σ):      6.083 s ±  0.024 s    [User: 27.499 s, System: 26.226 s]
  Range (min … max):    6.049 s …  6.112 s    5 runs

Summary
  ./gn_optimized gen --root=/Users/tikuta/chromium/src --check /Users/tikuta/chromium/src/out/no_clang_modules ran
    1.04 ± 0.01 times faster than ./gn_main gen --root=/Users/tikuta/chromium/src --check /Users/tikuta/chromium/src/out/no_clang_modules

Bug: 484862257
Change-Id: I4c9609f0b57693a1a209f4c36b810bb1597a39a9
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21260
Reviewed-by: David Turner <digit@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/header_checker.cc b/src/gn/header_checker.cc
index aba451b..8044896 100644
--- a/src/gn/header_checker.cc
+++ b/src/gn/header_checker.cc
@@ -163,7 +163,46 @@
     if (check->IsBinary())
       AddTargetToFileMap(check, &files_to_check);
   }
-  RunCheckOverFiles(files_to_check, force_check);
+
+  WorkerPool pool;
+  {
+    ScopedTrace precompute_trace(TraceItem::TRACE_CHECK_HEADERS,
+                                 "Precompute reachability");
+    std::set<const Target*> targets_to_precompute;
+    for (const auto& file : files_to_check) {
+      for (const auto& target_info : file.second) {
+        if (target_info.target->check_includes())
+          targets_to_precompute.insert(target_info.target);
+      }
+    }
+
+    if (!targets_to_precompute.empty()) {
+      task_count_.Increment();
+      for (const auto* target : targets_to_precompute) {
+        task_count_.Increment();
+        pool.PostTask([this, target]() {
+          ReachabilityCache& cache = GetReachabilityCacheForTarget(target);
+          cache.PerformDependencyWalk(true);
+          cache.PerformDependencyWalk(false);
+          if (!task_count_.Decrement()) {
+            std::unique_lock<std::mutex> lock(task_count_lock_);
+            task_count_cv_.notify_one();
+          }
+        });
+      }
+
+      if (!task_count_.Decrement()) {
+        std::unique_lock<std::mutex> lock(task_count_lock_);
+        task_count_cv_.notify_one();
+      }
+
+      std::unique_lock<std::mutex> lock(task_count_lock_);
+      while (!task_count_.IsZero())
+        task_count_cv_.wait(lock);
+    }
+  }
+
+  RunCheckOverFiles(files_to_check, force_check, &pool);
 
   if (errors_.empty())
     return true;
@@ -171,8 +210,9 @@
   return false;
 }
 
-void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
-  WorkerPool pool;
+void HeaderChecker::RunCheckOverFiles(const FileMap& files,
+                                      bool force_check,
+                                      WorkerPool* pool) {
   task_count_.Increment();
 
   for (const auto& file : files) {
@@ -204,11 +244,14 @@
       continue;
 
     task_count_.Increment();
-    pool.PostTask([this, targets = std::move(targets_to_check),
-                   file = file.first]() { DoWork(targets, file); });
+    pool->PostTask([this, targets = std::move(targets_to_check),
+                    file = file.first]() { DoWork(targets, file); });
   }
 
-  task_count_.Decrement();
+  if (!task_count_.Decrement()) {
+    std::unique_lock<std::mutex> lock(task_count_lock_);
+    task_count_cv_.notify_one();
+  }
 
   // Wait for all tasks posted by this method to complete.
   std::unique_lock<std::mutex> auto_lock(task_count_lock_);
@@ -328,12 +371,13 @@
 
 void HeaderChecker::ReachabilityCache::PerformDependencyWalk(bool permitted) {
   // Conduct the actual BFS.
+  if (permitted ? permitted_complete_.load(std::memory_order_relaxed)
+                : any_complete_.load(std::memory_order_relaxed)) {
+    return;
+  }
+
   BreadcrumbTable& breadcrumbs =
       permitted ? permitted_breadcrumbs_ : any_breadcrumbs_;
-  bool& complete = permitted ? permitted_complete_ : any_complete_;
-
-  if (complete)
-    return;
 
   // work_queue maintains a queue of targets which need to be considered as part
   // of dependency chain, in the order they were first traversed. Each time a
@@ -364,28 +408,30 @@
       }
     }
   }
-  complete = true;
+  if (permitted) {
+    permitted_complete_.store(true, std::memory_order_release);
+  } else {
+    any_complete_.store(true, std::memory_order_release);
+  }
 }
 
 bool HeaderChecker::ReachabilityCache::SearchForDependencyTo(
     const Target* search_for,
     bool permitted,
     Chain* chain) {
-  {
-    std::shared_lock<std::shared_mutex> read_lock(lock_);
-    if (permitted ? permitted_complete_ : any_complete_) {
-      return SearchBreadcrumbs(search_for, permitted, chain);
-    }
+  if (permitted ? permitted_complete_.load(std::memory_order_acquire)
+                : any_complete_.load(std::memory_order_acquire)) {
+    return SearchBreadcrumbs(search_for, permitted, chain);
   }
 
   {
     std::unique_lock<std::shared_mutex> write_lock(lock_);
-    if (!(permitted ? permitted_complete_ : any_complete_)) {
+    if (!(permitted ? permitted_complete_.load(std::memory_order_relaxed)
+                    : any_complete_.load(std::memory_order_relaxed))) {
       PerformDependencyWalk(permitted);
     }
   }
 
-  std::shared_lock<std::shared_mutex> read_lock(lock_);
   return SearchBreadcrumbs(search_for, permitted, chain);
 }
 
diff --git a/src/gn/header_checker.h b/src/gn/header_checker.h
index 083272f..c3c688b 100644
--- a/src/gn/header_checker.h
+++ b/src/gn/header_checker.h
@@ -6,6 +6,7 @@
 #define TOOLS_GN_HEADER_CHECKER_H_
 
 #include <array>
+#include <atomic>
 #include <condition_variable>
 #include <functional>
 #include <map>
@@ -28,6 +29,7 @@
 class InputFile;
 class SourceFile;
 class Target;
+class WorkerPool;
 
 namespace base {
 class FilePath;
@@ -196,20 +198,19 @@
                                bool permitted,
                                Chain* chain);
 
+    // Conducts a breadth-first search through the dependency graph to find a
+    // shortest chain from source_target_.
+    void PerformDependencyWalk(bool permitted);
+
     const Target* source_target() const { return source_target_; }
 
    private:
     // Reconstructs the shortest dependency chain to the given target if it was
     // found during a previous walk of the given type. Returns true on success.
-    // Assumes the lock is held.
     bool SearchBreadcrumbs(const Target* search_for,
                            bool permitted,
                            Chain* chain) const;
 
-    // Conducts a breadth-first search through the dependency graph to find a
-    // shortest chain from source_target_. Assumes unique lock is held.
-    void PerformDependencyWalk(bool permitted);
-
     const Target* source_target_;
 
     mutable std::shared_mutex lock_;
@@ -218,8 +219,8 @@
     // Breadcrumbs for the shortest path of any type.
     BreadcrumbTable any_breadcrumbs_;
 
-    bool permitted_complete_ = false;
-    bool any_complete_ = false;
+    std::atomic<bool> permitted_complete_ = false;
+    std::atomic<bool> any_complete_ = false;
   };
 
   struct TargetInfo {
@@ -242,7 +243,9 @@
 
   // Backend for Run() that takes the list of files to check. The errors_ list
   // will be populate on failure.
-  void RunCheckOverFiles(const FileMap& flies, bool force_check);
+  void RunCheckOverFiles(const FileMap& files,
+                         bool force_check,
+                         WorkerPool* pool);
 
   void DoWork(const std::vector<const Target*>& targets,
               const SourceFile& file);