Share ResolvedTargetData across targets in --ide=json

GenerateJSON() builds a description for every resolved target, and
TargetDescBuilder::BuildDescription() constructed a fresh
ResolvedTargetData for each one to compute inherited libs/frameworks.
ResolvedTargetData is a memoizing cache whose lib/framework computation
recurses over the transitive linked-deps subgraph; recreating it per
target threw away all memoization and made the whole pass quadratic.

Add an optional ResolvedTargetData* parameter to
DescBuilder::DescriptionForTarget() and have json_project_writer pass a
single shared instance for all targets. Other callers (command_desc)
keep the previous per-call behavior via the nullptr default.

For a Chromium out/debug tree (31145 targets), "gn gen --ide=json" drops
from ~62.5s to ~2.1s for the JSON phase (total gen 64s -> 3.8s). The
generated 507 MB project.json is byte-identical before and after.

Change-Id: I5480c5145b292221032b5a447f3eeaede6f1c54e
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23460
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Philipp Wollermann <philwo@google.com>
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index 143726d..8dce294 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -319,8 +319,11 @@
                     const std::set<std::string>& what,
                     bool all,
                     bool tree,
-                    bool blame)
-      : BaseDescBuilder(what, all, tree, blame), target_(target) {}
+                    bool blame,
+                    ResolvedTargetData* resolved)
+      : BaseDescBuilder(what, all, tree, blame),
+        target_(target),
+        resolved_(resolved) {}
 
   std::unique_ptr<base::DictionaryValue> BuildDescription() {
     auto res = std::make_unique<base::DictionaryValue>();
@@ -592,7 +595,11 @@
     // currently implement a blame feature for this since the bottom-up
     // inheritance makes this difficult.
 
-    ResolvedTargetData resolved;
+    // Use the caller-provided cache if any, otherwise a local one. Sharing a
+    // single instance across many targets avoids recomputing the transitive
+    // dependency walk for every target.
+    ResolvedTargetData local_resolved;
+    ResolvedTargetData& resolved = resolved_ ? *resolved_ : local_resolved;
 
     // Libs can be part of any target and get recursively pushed up the chain,
     // so display them regardless of target type.
@@ -946,6 +953,8 @@
   }
 
   const Target* target_;
+  // Optional shared cache for inherited lib/framework info; may be null.
+  ResolvedTargetData* resolved_ = nullptr;
 };
 
 }  // namespace
@@ -955,11 +964,12 @@
     const std::string& what,
     bool all,
     bool tree,
-    bool blame) {
+    bool blame,
+    ResolvedTargetData* resolved) {
   std::set<std::string> w;
   if (!what.empty())
     w.insert(what);
-  TargetDescBuilder b(target, w, all, tree, blame);
+  TargetDescBuilder b(target, w, all, tree, blame, resolved);
   return b.BuildDescription();
 }
 
diff --git a/src/gn/desc_builder.h b/src/gn/desc_builder.h
index e0e95a3..57c7f88 100644
--- a/src/gn/desc_builder.h
+++ b/src/gn/desc_builder.h
@@ -8,15 +8,23 @@
 #include "base/values.h"
 #include "gn/target.h"
 
+class ResolvedTargetData;
+
 class DescBuilder {
  public:
-  // Creates Dictionary representation for given target
+  // Creates Dictionary representation for given target.
+  //
+  // If |resolved| is non-null it is used to compute (and memoize) inherited
+  // lib/framework information. Callers that describe many targets in a row
+  // should pass a single shared instance to avoid recomputing the transitive
+  // dependency walk for every target (which is quadratic otherwise).
   static std::unique_ptr<base::DictionaryValue> DescriptionForTarget(
       const Target* target,
       const std::string& what,
       bool all,
       bool tree,
-      bool blame);
+      bool blame,
+      ResolvedTargetData* resolved = nullptr);
 
   // Creates Dictionary representation for given config
   static std::unique_ptr<base::DictionaryValue> DescriptionForConfig(
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc
index 9eb6e77..498c0b2 100644
--- a/src/gn/json_project_writer.cc
+++ b/src/gn/json_project_writer.cc
@@ -19,6 +19,7 @@
 #include "gn/desc_builder.h"
 #include "gn/filesystem_utils.h"
 #include "gn/invoke_python.h"
+#include "gn/resolved_target_data.h"
 #include "gn/scheduler.h"
 #include "gn/settings.h"
 #include "gn/string_output_buffer.h"
@@ -385,14 +386,17 @@
   json_writer.EndDict();  // build_settings
 
   std::map<Label, const Toolchain*> toolchains;
+  // Shared across all targets so that inherited lib/framework information is
+  // memoized once rather than recomputed per target (avoids quadratic blowup).
+  ResolvedTargetData resolved;
   json_writer.BeginDict("targets");
   {
     for (const auto* target : sorted_targets) {
-      auto description =
-          DescBuilder::DescriptionForTarget(target, "", false, false, false);
+      auto description = DescBuilder::DescriptionForTarget(
+          target, "", false, false, false, &resolved);
       // Outputs need to be asked for separately.
-      auto outputs = DescBuilder::DescriptionForTarget(target, "source_outputs",
-                                                       false, false, false);
+      auto outputs = DescBuilder::DescriptionForTarget(
+          target, "source_outputs", false, false, false, &resolved);
       base::DictionaryValue* outputs_value = nullptr;
       if (outputs->GetDictionary("source_outputs", &outputs_value) &&
           !outputs_value->empty()) {