[wasm][gc] Track number of GCs triggered per module

We currently have the problem that we trigger too many code GCs since
{new_potentially_dead_code_size_} is never reset to zero.
This CL adds a counter which tells us how many GCs we ran per native
module. This counter is sampled on each code GC. It will give us a
good understanding of the amount of GC work we are executing in the
wild. The number should stay in the single-digits generally.

R=mstarzinger@chromium.org, mpearson@chromium.org

Bug: v8:8217
Change-Id: I978a98dff76e0f466ff51e067626886b58d52ded
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1615246
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61611}
diff --git a/src/counters-definitions.h b/src/counters-definitions.h
index 1a25c4e..8e51d9d 100644
--- a/src/counters-definitions.h
+++ b/src/counters-definitions.h
@@ -86,7 +86,10 @@
   HR(wasm_module_freed_code_size_mb, V8.WasmModuleCodeSizeFreed, 0, 1024, 64)  \
   /* percent of freed code size per module, collected on GC */                 \
   HR(wasm_module_freed_code_size_percent, V8.WasmModuleCodeSizePercentFreed,   \
-     0, 100, 32)
+     0, 100, 32)                                                               \
+  /* number of code GCs triggered per native module, collected on code GC */   \
+  HR(wasm_module_num_triggered_code_gcs,                                       \
+     V8.WasmModuleNumberOfCodeGCsTriggered, 1, 128, 20)
 
 #define HISTOGRAM_TIMER_LIST(HT)                                               \
   /* Timer histograms, not thread safe: HT(name, caption, max, unit) */        \
diff --git a/src/wasm/wasm-engine.cc b/src/wasm/wasm-engine.cc
index f7c687a..8be3f65 100644
--- a/src/wasm/wasm-engine.cc
+++ b/src/wasm/wasm-engine.cc
@@ -115,6 +115,11 @@
 }  // namespace
 
 struct WasmEngine::CurrentGCInfo {
+  explicit CurrentGCInfo(int8_t gc_sequence_index)
+      : gc_sequence_index(gc_sequence_index) {
+    DCHECK_NE(0, gc_sequence_index);
+  }
+
   // Set of isolates that did not scan their stack yet for used WasmCode, and
   // their scheduled foreground task.
   std::unordered_map<Isolate*, WasmGCForegroundTask*> outstanding_isolates;
@@ -123,10 +128,17 @@
   // Code that is still in-use is removed by the individual isolates.
   std::unordered_set<WasmCode*> dead_code;
 
+  // The number of GCs triggered in the native module that triggered this GC.
+  // This is stored in the histogram for each participating isolate during
+  // execution of that isolate's foreground task.
+  const int8_t gc_sequence_index;
+
   // If during this GC, another GC was requested, we skipped that other GC (we
   // only run one GC at a time). Remember though to trigger another one once
-  // this one finishes.
-  bool run_another_gc = false;
+  // this one finishes. {next_gc_sequence_index} is 0 if no next GC is needed,
+  // and >0 otherwise. It stores the {num_code_gcs_triggered} of the native
+  // module which triggered the next GC.
+  int8_t next_gc_sequence_index = 0;
 };
 
 struct WasmEngine::IsolateInfo {
@@ -175,6 +187,10 @@
   // Code that is not being executed in any isolate any more, but the ref count
   // did not drop to zero yet.
   std::unordered_set<WasmCode*> dead_code;
+
+  // Number of code GCs triggered because code in this native module became
+  // potentially dead.
+  int8_t num_code_gcs_triggered = 0;
 };
 
 WasmEngine::WasmEngine()
@@ -704,6 +720,8 @@
   // a foreground task). In that case, ignore it.
   if (current_gc_info_ == nullptr) return;
   if (!RemoveIsolateFromCurrentGC(isolate)) return;
+  isolate->counters()->wasm_module_num_triggered_code_gcs()->AddSample(
+      current_gc_info_->gc_sequence_index);
   for (WasmCode* code : live_code) current_gc_info_->dead_code.erase(code);
   PotentiallyFinishCurrentGC();
 }
@@ -725,8 +743,9 @@
   base::MutexGuard guard(&mutex_);
   auto it = native_modules_.find(code->native_module());
   DCHECK_NE(native_modules_.end(), it);
-  if (it->second->dead_code.count(code)) return false;  // Code is already dead.
-  auto added = it->second->potentially_dead_code.insert(code);
+  NativeModuleInfo* info = it->second.get();
+  if (info->dead_code.count(code)) return false;  // Code is already dead.
+  auto added = info->potentially_dead_code.insert(code);
   if (!added.second) return false;  // An entry already existed.
   new_potentially_dead_code_size_ += code->instructions().size();
   if (FLAG_wasm_code_gc) {
@@ -736,17 +755,22 @@
             ? 0
             : 1 * MB + code_manager_.committed_code_space() / 10;
     if (new_potentially_dead_code_size_ > dead_code_limit) {
+      bool inc_gc_count =
+          info->num_code_gcs_triggered < std::numeric_limits<int8_t>::max();
       if (current_gc_info_ == nullptr) {
+        if (inc_gc_count) ++info->num_code_gcs_triggered;
         TRACE_CODE_GC(
             "Triggering GC (potentially dead: %zu bytes; limit: %zu bytes).\n",
             new_potentially_dead_code_size_, dead_code_limit);
-        TriggerGC();
-      } else if (!current_gc_info_->run_another_gc) {
+        TriggerGC(info->num_code_gcs_triggered);
+      } else if (current_gc_info_->next_gc_sequence_index == 0) {
+        if (inc_gc_count) ++info->num_code_gcs_triggered;
         TRACE_CODE_GC(
             "Scheduling another GC after the current one (potentially dead: "
             "%zu bytes; limit: %zu bytes).\n",
             new_potentially_dead_code_size_, dead_code_limit);
-        current_gc_info_->run_another_gc = true;
+        current_gc_info_->next_gc_sequence_index = info->num_code_gcs_triggered;
+        DCHECK_NE(0, current_gc_info_->next_gc_sequence_index);
       }
     }
   }
@@ -775,10 +799,10 @@
   }
 }
 
-void WasmEngine::TriggerGC() {
+void WasmEngine::TriggerGC(int8_t gc_sequence_index) {
   DCHECK_NULL(current_gc_info_);
   DCHECK(FLAG_wasm_code_gc);
-  current_gc_info_.reset(new CurrentGCInfo());
+  current_gc_info_.reset(new CurrentGCInfo(gc_sequence_index));
   // Add all potentially dead code to this GC, and trigger a GC task in each
   // isolate.
   for (auto& entry : native_modules_) {
@@ -841,9 +865,9 @@
       dead_code[code->native_module()].push_back(code);
     }
   }
-  bool run_another_gc = current_gc_info_->run_another_gc;
+  int8_t next_gc_sequence_index = current_gc_info_->next_gc_sequence_index;
   current_gc_info_.reset();
-  if (run_another_gc) TriggerGC();
+  if (next_gc_sequence_index != 0) TriggerGC(next_gc_sequence_index);
 
   FreeDeadCodeLocked(dead_code);
 }
diff --git a/src/wasm/wasm-engine.h b/src/wasm/wasm-engine.h
index e0bcecb..fd9ce52 100644
--- a/src/wasm/wasm-engine.h
+++ b/src/wasm/wasm-engine.h
@@ -223,7 +223,7 @@
       Handle<Context> context, const char* api_method_name,
       std::shared_ptr<CompilationResultResolver> resolver);
 
-  void TriggerGC();
+  void TriggerGC(int8_t gc_sequence_index);
 
   // Remove an isolate from the outstanding isolates of the current GC. Returns
   // true if the isolate was still outstanding, false otherwise. Hold {mutex_}