Version 2.12.0-200.0.dev

Merge commit '5cd0770c9cd9a4c1e525854f4ff9b34f940afc07' into 'dev'
diff --git a/runtime/vm/closure_functions_cache.cc b/runtime/vm/closure_functions_cache.cc
new file mode 100644
index 0000000..a85a15b
--- /dev/null
+++ b/runtime/vm/closure_functions_cache.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "vm/closure_functions_cache.h"
+
+#include "vm/compiler/jit/compiler.h"
+#include "vm/object.h"
+#include "vm/object_store.h"
+
+namespace dart {
+
+FunctionPtr ClosureFunctionsCache::LookupClosureFunction(
+    const Class& owner,
+    TokenPosition token_pos) {
+  auto thread = Thread::Current();
+  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
+  return LookupClosureFunctionLocked(owner, token_pos);
+}
+
+FunctionPtr ClosureFunctionsCache::LookupClosureFunctionLocked(
+    const Class& owner,
+    TokenPosition token_pos) {
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  DEBUG_ASSERT(
+      thread->isolate_group()->program_lock()->IsCurrentThreadReader());
+
+  const auto& closures =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+  auto& closure = Function::Handle(zone);
+  intptr_t num_closures = closures.Length();
+  for (intptr_t i = 0; i < num_closures; i++) {
+    closure ^= closures.At(i);
+    if (closure.token_pos() == token_pos && closure.Owner() == owner.raw()) {
+      return closure.raw();
+    }
+  }
+  return Function::null();
+}
+
+FunctionPtr ClosureFunctionsCache::LookupClosureFunction(
+    const Function& parent,
+    TokenPosition token_pos) {
+  auto thread = Thread::Current();
+  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
+  return LookupClosureFunctionLocked(parent, token_pos);
+}
+
+FunctionPtr ClosureFunctionsCache::LookupClosureFunctionLocked(
+    const Function& parent,
+    TokenPosition token_pos) {
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  DEBUG_ASSERT(
+      thread->isolate_group()->program_lock()->IsCurrentThreadReader());
+
+  const auto& closures =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+  auto& closure = Function::Handle(zone);
+  intptr_t num_closures = closures.Length();
+  for (intptr_t i = 0; i < num_closures; i++) {
+    closure ^= closures.At(i);
+    if (closure.token_pos() == token_pos &&
+        closure.parent_function() == parent.raw()) {
+      return closure.raw();
+    }
+  }
+  return Function::null();
+}
+
+void ClosureFunctionsCache::AddClosureFunctionLocked(const Function& function) {
+  ASSERT(!Compiler::IsBackgroundCompilation());
+
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  DEBUG_ASSERT(
+      thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
+
+  const auto& closures =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+  ASSERT(!closures.IsNull());
+  ASSERT(function.IsNonImplicitClosureFunction());
+  closures.Add(function, Heap::kOld);
+}
+
+intptr_t ClosureFunctionsCache::FindClosureIndex(const Function& needle) {
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
+
+  const auto& closures_array =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+  intptr_t num_closures = closures_array.Length();
+  for (intptr_t i = 0; i < num_closures; i++) {
+    if (closures_array.At(i) == needle.raw()) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+FunctionPtr ClosureFunctionsCache::ClosureFunctionFromIndex(intptr_t idx) {
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
+
+  const auto& closures_array =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+  if (idx < 0 || idx >= closures_array.Length()) {
+    return Function::null();
+  }
+  return Function::RawCast(closures_array.At(idx));
+}
+
+FunctionPtr ClosureFunctionsCache::GetUniqueInnerClosure(
+    const Function& outer) {
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
+
+  const auto& closures =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+  auto& entry = Function::Handle(zone);
+  for (intptr_t i = (closures.Length() - 1); i >= 0; i--) {
+    entry ^= closures.At(i);
+    if (entry.parent_function() == outer.raw()) {
+#if defined(DEBUG)
+      auto& other = Function::Handle(zone);
+      for (intptr_t j = i - 1; j >= 0; j--) {
+        other ^= closures.At(j);
+        ASSERT(other.parent_function() != outer.raw());
+      }
+#endif
+      return entry.raw();
+    }
+  }
+  return Function::null();
+}
+
+void ClosureFunctionsCache::ForAllClosureFunctions(
+    std::function<bool(const Function&)> callback) {
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto object_store = thread->isolate_group()->object_store();
+
+  auto& current_data = Array::Handle(zone);
+  auto& entry = Function::Handle(zone);
+
+  // NOTE: Inner functions may get added to the closures array while iterating -
+  // we guarantee that any closure functions added on this thread by a
+  // [callback] call will be visited as well.
+  //
+  // We avoid holding a lock while accessing the closures array, since often
+  // times [callback] will do very heavy things (e.g. compiling the function).
+  //
+  // This means we can possibly miss a concurrently added closure function -
+  // which the caller should be ok with (or it guarantees that this cannot
+  // happen).
+  const auto& closures =
+      GrowableObjectArray::Handle(zone, object_store->closure_functions());
+
+  {
+    // The empty read locker scope will implicitly issue an acquire memory
+    // fence, which means any closure functions added so far will be visible and
+    // iterated further down.
+    SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
+  }
+
+  // We have an outer loop to ensure any new closure functions added by
+  // [callback] will be iterated as well.
+  intptr_t i = 0;
+  while (true) {
+    intptr_t current_length = closures.Length();
+    if (i == current_length) break;
+
+    current_data = closures.data();
+    if (current_data.Length() < current_length) {
+      current_length = current_data.Length();
+    }
+
+    for (; i < current_length; ++i) {
+      entry ^= current_data.At(i);
+      if (!callback(entry)) {
+        return;  // Stop iteration.
+      }
+    }
+  }
+}
+
+}  // namespace dart
diff --git a/runtime/vm/closure_functions_cache.h b/runtime/vm/closure_functions_cache.h
new file mode 100644
index 0000000..019c12b
--- /dev/null
+++ b/runtime/vm/closure_functions_cache.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_VM_CLOSURE_FUNCTIONS_CACHE_H_
+#define RUNTIME_VM_CLOSURE_FUNCTIONS_CACHE_H_
+
+#include <functional>
+
+#include "vm/allocation.h"
+#include "vm/token_position.h"
+
+namespace dart {
+
+class Class;
+class Function;
+class FunctionPtr;
+
+// Implementation of cache for inner closure functions.
+//
+// This cache is populated lazily by the compiler: When compiling a function,
+// the flow graph builder will recursively traverse the kernel AST for the
+// function and any inner functions. This will cause the lazy-creation of inner
+// closure functions.
+//
+// The cache is currently implemented as O(n) lookup in a growable list.
+//
+// Parts of the VM have certain requirements that are maintained:
+//
+//   * parent functions need to come before inner functions
+//   * closure functions list can grow while iterating
+//   * the index of closure function must be stable
+//
+// If the linear lookup turns out to be too expensive, the list of closures
+// could be maintained in a hash map, with the key being the token position of
+// the closure. There are almost no collisions with this simple hash value.
+// However, iterating over all closure functions becomes more difficult,
+// especially when the list/map changes while iterating over it (see
+// requirements above).
+class ClosureFunctionsCache : public AllStatic {
+ public:
+  static FunctionPtr LookupClosureFunction(const Class& owner,
+                                           TokenPosition pos);
+  static FunctionPtr LookupClosureFunctionLocked(const Class& owner,
+                                                 TokenPosition pos);
+
+  static FunctionPtr LookupClosureFunction(const Function& parent,
+                                           TokenPosition token_pos);
+  static FunctionPtr LookupClosureFunctionLocked(const Function& parent,
+                                                 TokenPosition token_pos);
+
+  static void AddClosureFunctionLocked(const Function& function);
+
+  static intptr_t FindClosureIndex(const Function& needle);
+  static FunctionPtr ClosureFunctionFromIndex(intptr_t idx);
+
+  static FunctionPtr GetUniqueInnerClosure(const Function& outer);
+
+  // Visits all closure functions registered in the object store.
+  //
+  // Iterates in-order, thereby allowing new closures being added during the
+  // iteration.
+  //
+  // The iteration continues until either [callback] returns `false` or all
+  // closure functions have been visited.
+  static void ForAllClosureFunctions(
+      std::function<bool(const Function&)> callback);
+};
+
+}  // namespace dart
+
+#endif  // RUNTIME_VM_CLOSURE_FUNCTIONS_CACHE_H_
diff --git a/runtime/vm/compilation_trace.cc b/runtime/vm/compilation_trace.cc
index 412ee128..e1bc89d 100644
--- a/runtime/vm/compilation_trace.cc
+++ b/runtime/vm/compilation_trace.cc
@@ -4,6 +4,7 @@
 
 #include "vm/compilation_trace.h"
 
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/globals.h"
 #include "vm/log.h"
@@ -131,20 +132,21 @@
     }
   }
 
-  // Finally, compile closures in all compiled functions. Don't cache the
-  // length since compiling may append to this list.
-  const GrowableObjectArray& closure_functions = GrowableObjectArray::Handle(
-      zone_, thread_->isolate_group()->object_store()->closure_functions());
-  for (intptr_t i = 0; i < closure_functions.Length(); i++) {
-    function_ ^= closure_functions.At(i);
-    function2_ = function_.parent_function();
+  // Finally, compile closures in all compiled functions. Note: We rely on the
+  // fact that parent functions are visited before children.
+  error_ = Object::null();
+  auto& result = Object::Handle(zone_);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) {
+    function2_ = func.parent_function();
     if (function2_.HasCode()) {
-      error_ = CompileFunction(function_);
-      if (error_.IsError()) {
-        return error_.raw();
+      result = CompileFunction(function_);
+      if (result.IsError()) {
+        error_ = result.raw();
+        return false;  // Stop iteration.
       }
     }
-  }
+    return true;
+  });
 
   return Object::null();
 }
@@ -965,19 +967,7 @@
     // functions in the serialized feedback, so the parent will have already
     // been unoptimized compilated and the child function created and added to
     // ObjectStore::closure_functions_.
-    const GrowableObjectArray& closure_functions = GrowableObjectArray::Handle(
-        zone_, thread_->isolate_group()->object_store()->closure_functions());
-    bool found = false;
-    for (intptr_t i = 0; i < closure_functions.Length(); i++) {
-      func_ ^= closure_functions.At(i);
-      if (func_.Owner() == cls_.raw() && func_.token_pos() == token_pos) {
-        found = true;
-        break;
-      }
-    }
-    if (!found) {
-      func_ = Function::null();
-    }
+    func_ = ClosureFunctionsCache::LookupClosureFunction(cls_, token_pos);
   } else {
     // This leaves unhandled:
     // - kInvokeFieldDispatcher (how to get a valid args descriptor?)
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 4d4fcc5..6b778d7 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -7,6 +7,7 @@
 #include "platform/unicode.h"
 #include "vm/canonical_tables.h"
 #include "vm/class_finalizer.h"
+#include "vm/closure_functions_cache.h"
 #include "vm/code_patcher.h"
 #include "vm/compiler/aot/aot_call_specializer.h"
 #include "vm/compiler/aot/precompiler_tracer.h"
@@ -1548,7 +1549,6 @@
   String& name = String::Handle(Z);
   Function& function = Function::Handle(Z);
   Function& function2 = Function::Handle(Z);
-  GrowableObjectArray& closures = GrowableObjectArray::Handle(Z);
 
   for (intptr_t i = 0; i < libraries_.Length(); i++) {
     lib ^= libraries_.At(i);
@@ -1588,9 +1588,8 @@
     }
   }
 
-  closures = IG->object_store()->closure_functions();
-  for (intptr_t j = 0; j < closures.Length(); j++) {
-    function ^= closures.At(j);
+  auto& parent_function = Function::Handle(Z);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
     bool retain = possibly_retained_functions_.ContainsKey(function);
     if (retain) {
       AddTypesOf(function);
@@ -1601,13 +1600,14 @@
       // It can happen that all uses of a function are inlined, leaving
       // a compiled local function with an uncompiled parent. Retain such
       // parents and their enclosing classes and libraries.
-      function = function.parent_function();
-      while (!function.IsNull()) {
-        AddTypesOf(function);
-        function = function.parent_function();
+      parent_function = function.parent_function();
+      while (!parent_function.IsNull()) {
+        AddTypesOf(parent_function);
+        parent_function = parent_function.parent_function();
       }
     }
-  }
+    return true;  // Continue iteration.
+  });
 }
 
 void Precompiler::FinalizeDispatchTable() {
@@ -1705,7 +1705,6 @@
   Code& code = Code::Handle(Z);
   Object& owner = Object::Handle(Z);
   GrowableObjectArray& retained_functions = GrowableObjectArray::Handle(Z);
-  GrowableObjectArray& closures = GrowableObjectArray::Handle(Z);
 
   auto drop_function = [&](const Function& function) {
     if (function.HasCode()) {
@@ -1783,16 +1782,15 @@
     }
   }
 
-  closures = IG->object_store()->closure_functions();
   retained_functions = GrowableObjectArray::New();
-  for (intptr_t j = 0; j < closures.Length(); j++) {
-    function ^= closures.At(j);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
     if (functions_to_retain_.ContainsKey(function)) {
       retained_functions.Add(function);
     } else {
       drop_function(function);
     }
-  }
+    return true;  // Continue iteration.
+  });
   IG->object_store()->set_closure_functions(retained_functions);
 }
 
diff --git a/runtime/vm/compiler/backend/typed_data_aot_test.cc b/runtime/vm/compiler/backend/typed_data_aot_test.cc
index 8e7ddd1..26f6629 100644
--- a/runtime/vm/compiler/backend/typed_data_aot_test.cc
+++ b/runtime/vm/compiler/backend/typed_data_aot_test.cc
@@ -4,6 +4,7 @@
 
 #include <vector>
 
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/backend/il_printer.h"
 #include "vm/compiler/backend/il_test_helper.h"
 #include "vm/compiler/call_specializer.h"
@@ -472,17 +473,11 @@
   Invoke(root_library, "test");
   const auto& test_function =
       Function::Handle(GetFunction(root_library, "test"));
-  const auto& closures = GrowableObjectArray::Handle(
-      IsolateGroup::Current()->object_store()->closure_functions());
-  auto& function = Function::Handle();
-  for (intptr_t i = closures.Length() - 1; 0 <= i; ++i) {
-    function ^= closures.At(i);
-    if (function.parent_function() == test_function.raw()) {
-      break;
-    }
-    function = Function::null();
-  }
-  RELEASE_ASSERT(!function.IsNull());
+
+  const auto& function = Function::Handle(
+      ClosureFunctionsCache::GetUniqueInnerClosure(test_function));
+  RELEASE_ASSERT(function.IsFunction());
+
   TestPipeline pipeline(function, CompilerPass::kAOT);
   FlowGraph* flow_graph = pipeline.RunPasses({});
 
diff --git a/runtime/vm/compiler/backend/yield_position_test.cc b/runtime/vm/compiler/backend/yield_position_test.cc
index e541f67..338595d 100644
--- a/runtime/vm/compiler/backend/yield_position_test.cc
+++ b/runtime/vm/compiler/backend/yield_position_test.cc
@@ -4,6 +4,7 @@
 
 #include <utility>
 
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/backend/il_test_helper.h"
 #include "vm/compiler/compiler_pass.h"
 #include "vm/object.h"
@@ -78,19 +79,9 @@
       Function::Handle(GetFunction(root_library, "foo"));
 
   // Grab the inner, lazily created, closure from the object store.
-  const auto& closures = GrowableObjectArray::Handle(
-      IsolateGroup::Current()->object_store()->closure_functions());
-  ASSERT(!closures.IsNull());
-  auto& closure = Object::Handle();
-  for (intptr_t i = 0; i < closures.Length(); ++i) {
-    closure = closures.At(i);
-    if (Function::Cast(closure).parent_function() == outer_function.raw()) {
-      break;
-    }
-    closure = Object::null();
-  }
-  RELEASE_ASSERT(closure.IsFunction());
-  const auto& function = Function::Cast(closure);
+  const auto& function = Function::Handle(
+      ClosureFunctionsCache::GetUniqueInnerClosure(outer_function));
+  RELEASE_ASSERT(function.IsFunction());
 
   // Ensure we have 3 different return instructions with yield indices attached
   // to them.
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index def0672..eb94c92 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -4,6 +4,7 @@
 
 #include "vm/compiler/frontend/kernel_binary_flowgraph.h"
 
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/ffi/callback.h"
 #include "vm/compiler/frontend/flow_graph_builder.h"  // For dart::FlowGraphBuilder::SimpleInstanceOfType.
 #include "vm/compiler/frontend/prologue_builder.h"
@@ -4898,16 +4899,16 @@
     SafepointReadRwLocker ml(thread(),
                              thread()->isolate_group()->program_lock());
     // NOTE: This is not TokenPosition in the general sense!
-    function =
-        I->LookupClosureFunction(parsed_function()->function(), position);
+    function = ClosureFunctionsCache::LookupClosureFunctionLocked(
+        parsed_function()->function(), position);
   }
 
   if (function.IsNull()) {
     SafepointWriteRwLocker ml(thread(),
                               thread()->isolate_group()->program_lock());
     // NOTE: This is not TokenPosition in the general sense!
-    function =
-        I->LookupClosureFunction(parsed_function()->function(), position);
+    function = ClosureFunctionsCache::LookupClosureFunctionLocked(
+        parsed_function()->function(), position);
     if (function.IsNull()) {
       for (intptr_t i = 0; i < scopes()->function_scopes.length(); ++i) {
         if (scopes()->function_scopes[i].kernel_offset != offset) {
@@ -4985,7 +4986,7 @@
         signature_type ^= ClassFinalizer::FinalizeType(signature_type);
         function.SetSignatureType(signature_type);
 
-        I->AddClosureFunction(function);
+        ClosureFunctionsCache::AddClosureFunctionLocked(function);
         break;
       }
     }
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index b2f3baf..5fa8d7f 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -6,6 +6,7 @@
 
 #include "include/dart_api.h"
 
+#include "vm/closure_functions_cache.h"
 #include "vm/code_descriptors.h"
 #include "vm/code_patcher.h"
 #include "vm/compiler/api/deopt_id.h"
@@ -1625,7 +1626,6 @@
   CallSiteResetter resetter(zone);
   Class& cls = Class::Handle(zone);
   Array& functions = Array::Handle(zone);
-  GrowableObjectArray& closures = GrowableObjectArray::Handle(zone);
   Function& function = Function::Handle(zone);
   Code& code = Code::Handle(zone);
 
@@ -1633,7 +1633,7 @@
   const intptr_t num_tlc_classes = class_table.NumTopLevelCids();
   // TODO(dartbug.com/36097): Need to stop other mutators running in same IG
   // before deoptimizing the world.
-  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
+  SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
   for (intptr_t i = 1; i < num_classes + num_tlc_classes; i++) {
     const classid_t cid =
         i < num_classes ? i : ClassTable::CidFromTopLevelIndex(i - num_classes);
@@ -1677,11 +1677,7 @@
   }
 
   // Disable optimized closure functions.
-  closures = isolate_group->object_store()->closure_functions();
-  const intptr_t num_closures = closures.Length();
-  for (intptr_t pos = 0; pos < num_closures; pos++) {
-    function ^= closures.At(pos);
-    ASSERT(!function.IsNull());
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
     if (function.HasOptimizedCode()) {
       function.SwitchToUnoptimizedCode();
     }
@@ -1689,7 +1685,8 @@
     if (!code.IsNull()) {
       resetter.ResetSwitchableCalls(code);
     }
-  }
+    return true;  // Continue iteration.
+  });
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 }
 
@@ -2481,36 +2478,24 @@
     TokenPosition end_pos,
     GrowableObjectArray* code_function_list) {
   auto thread = Thread::Current();
-  auto isolate_group = thread->isolate_group();
   auto zone = thread->zone();
-  Class& cls = Class::Handle(zone);
-  Array& functions = Array::Handle(zone);
-  GrowableObjectArray& closures = GrowableObjectArray::Handle(zone);
-  Function& function = Function::Handle(zone);
 
-  closures = isolate_group->object_store()->closure_functions();
-  const intptr_t num_closures = closures.Length();
-  for (intptr_t pos = 0; pos < num_closures; pos++) {
-    function ^= closures.At(pos);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
     ASSERT(!function.IsNull());
     if ((function.token_pos() == start_pos) &&
         (function.end_token_pos() == end_pos) &&
         (function.script() == script.raw())) {
-      if (function.is_debuggable()) {
-        if (function.HasCode()) {
-          code_function_list->Add(function);
-        }
+      if (function.is_debuggable() && function.HasCode()) {
+        code_function_list->Add(function);
       }
-      if (function.HasImplicitClosureFunction()) {
-        function = function.ImplicitClosureFunction();
-        if (function.is_debuggable()) {
-          if (function.HasCode()) {
-            code_function_list->Add(function);
-          }
-        }
-      }
+      ASSERT(!function.HasImplicitClosureFunction());
     }
-  }
+    return true;  // Continue iteration.
+  });
+
+  Class& cls = Class::Handle(zone);
+  Function& function = Function::Handle(zone);
+  Array& functions = Array::Handle(zone);
 
   const ClassTable& class_table = *isolate_->group()->class_table();
   const intptr_t num_classes = class_table.NumCids();
@@ -2610,22 +2595,15 @@
       }
       continue;
     }
-    const GrowableObjectArray& closures = GrowableObjectArray::Handle(
-        zone, isolate_group->object_store()->closure_functions());
-    Array& functions = Array::Handle(zone);
-    Function& function = Function::Handle(zone);
-    Array& fields = Array::Handle(zone);
-    Field& field = Field::Handle(zone);
-    Error& error = Error::Handle(zone);
 
-    const intptr_t num_closures = closures.Length();
-    for (intptr_t i = 0; i < num_closures; i++) {
-      function ^= closures.At(i);
-      if (FunctionOverlaps(function, script, token_pos, last_token_pos)) {
+    ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& fun) {
+      if (FunctionOverlaps(fun, script, token_pos, last_token_pos)) {
         // Select the inner most closure.
-        UpdateBestFit(best_fit, function);
+        UpdateBestFit(best_fit, fun);
       }
-    }
+      return true;  // Continue iteration
+    });
+
     if (!best_fit->IsNull()) {
       // The inner most closure found will be the best fit. Going
       // over class functions below will not help in any further
@@ -2633,6 +2611,12 @@
       return true;
     }
 
+    Array& functions = Array::Handle(zone);
+    Function& function = Function::Handle(zone);
+    Array& fields = Array::Handle(zone);
+    Field& field = Field::Handle(zone);
+    Error& error = Error::Handle(zone);
+
     const ClassTable& class_table = *isolate_->group()->class_table();
     const intptr_t num_classes = class_table.NumCids();
     const intptr_t num_tlc_classes = class_table.NumTopLevelCids();
@@ -3613,8 +3597,8 @@
       // Only set breakpoint when entering async_op the first time.
       // :async_future should be uninitialised at this point:
       if (async_future.IsNull()) {
-        const Function& async_op =
-            Function::Handle(frame->function().GetGeneratedClosure());
+        const Function& async_op = Function::Handle(
+            ClosureFunctionsCache::GetUniqueInnerClosure(frame->function()));
         if (!async_op.IsNull()) {
           SetBreakpointAtAsyncOp(async_op);
           // After setting the breakpoint we stop stepping and continue the
@@ -3830,15 +3814,10 @@
   const TokenPosition& func_start = function.token_pos();
   auto thread = Thread::Current();
   auto zone = thread->zone();
-  auto isolate_group = thread->isolate_group();
   const Script& outer_origin = Script::Handle(zone, function.script());
-  const GrowableObjectArray& closures = GrowableObjectArray::Handle(
-      zone, isolate_group->object_store()->closure_functions());
-  const intptr_t num_closures = closures.Length();
-  Function& closure = Function::Handle(zone);
+
   Function& best_fit = Function::Handle(zone);
-  for (intptr_t i = 0; i < num_closures; i++) {
-    closure ^= closures.At(i);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& closure) {
     const TokenPosition& closure_start = closure.token_pos();
     const TokenPosition& closure_end = closure.end_token_pos();
     // We're only interested in closures that have real ending token positions.
@@ -3850,7 +3829,8 @@
         (closure.script() == outer_origin.raw())) {
       UpdateBestFit(&best_fit, closure);
     }
-  }
+    return true;  // Continue iteration.
+  });
   return best_fit.raw();
 }
 
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index e026393..60ca667 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2328,59 +2328,6 @@
                          reinterpret_cast<uword>(this));
 }
 
-void Isolate::AddClosureFunction(const Function& function) const {
-  ASSERT(!Compiler::IsBackgroundCompilation());
-  GrowableObjectArray& closures =
-      GrowableObjectArray::Handle(group()->object_store()->closure_functions());
-  ASSERT(!closures.IsNull());
-  ASSERT(function.IsNonImplicitClosureFunction());
-  closures.Add(function, Heap::kOld);
-}
-
-// If the linear lookup turns out to be too expensive, the list
-// of closures could be maintained in a hash map, with the key
-// being the token position of the closure. There are almost no
-// collisions with this simple hash value. However, iterating over
-// all closure functions becomes more difficult, especially when
-// the list/map changes while iterating over it.
-FunctionPtr Isolate::LookupClosureFunction(const Function& parent,
-                                           TokenPosition token_pos) const {
-  const GrowableObjectArray& closures =
-      GrowableObjectArray::Handle(group()->object_store()->closure_functions());
-  ASSERT(!closures.IsNull());
-  Function& closure = Function::Handle();
-  intptr_t num_closures = closures.Length();
-  for (intptr_t i = 0; i < num_closures; i++) {
-    closure ^= closures.At(i);
-    if ((closure.token_pos() == token_pos) &&
-        (closure.parent_function() == parent.raw())) {
-      return closure.raw();
-    }
-  }
-  return Function::null();
-}
-
-intptr_t Isolate::FindClosureIndex(const Function& needle) const {
-  const GrowableObjectArray& closures_array =
-      GrowableObjectArray::Handle(group()->object_store()->closure_functions());
-  intptr_t num_closures = closures_array.Length();
-  for (intptr_t i = 0; i < num_closures; i++) {
-    if (closures_array.At(i) == needle.raw()) {
-      return i;
-    }
-  }
-  return -1;
-}
-
-FunctionPtr Isolate::ClosureFunctionFromIndex(intptr_t idx) const {
-  const GrowableObjectArray& closures_array =
-      GrowableObjectArray::Handle(group()->object_store()->closure_functions());
-  if ((idx < 0) || (idx >= closures_array.Length())) {
-    return Function::null();
-  }
-  return Function::RawCast(closures_array.At(idx));
-}
-
 // static
 void Isolate::NotifyLowMemory() {
   Isolate::KillAllIsolates(Isolate::kLowMemoryMsg);
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 1214308..1a84194 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -1317,12 +1317,6 @@
   void PauseEventHandler();
 #endif
 
-  void AddClosureFunction(const Function& function) const;
-  FunctionPtr LookupClosureFunction(const Function& parent,
-                                    TokenPosition token_pos) const;
-  intptr_t FindClosureIndex(const Function& needle) const;
-  FunctionPtr ClosureFunctionFromIndex(intptr_t idx) const;
-
   bool is_service_isolate() const {
     return IsServiceIsolateBit::decode(isolate_flags_);
   }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 57b8bc5..e862b759 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -16,6 +16,7 @@
 #include "vm/bootstrap.h"
 #include "vm/canonical_tables.h"
 #include "vm/class_finalizer.h"
+#include "vm/closure_functions_cache.h"
 #include "vm/code_comments.h"
 #include "vm/code_descriptors.h"
 #include "vm/code_observers.h"
@@ -7029,26 +7030,6 @@
   return DefaultTypeArgumentsKind::kNeedsInstantiation;
 }
 
-FunctionPtr Function::GetGeneratedClosure() const {
-  const auto& closure_functions = GrowableObjectArray::Handle(
-      IsolateGroup::Current()->object_store()->closure_functions());
-  auto& entry = Object::Handle();
-
-  for (auto i = (closure_functions.Length() - 1); i >= 0; i--) {
-    entry = closure_functions.At(i);
-
-    ASSERT(entry.IsFunction());
-
-    const auto& closure_function = Function::Cast(entry);
-    if (closure_function.parent_function() == raw() &&
-        closure_function.is_generated_body()) {
-      return closure_function.raw();
-    }
-  }
-
-  return Function::null();
-}
-
 // Enclosing outermost function of this local function.
 FunctionPtr Function::GetOutermostFunction() const {
   FunctionPtr parent = parent_function();
@@ -13676,24 +13657,18 @@
     }
   }
 
-  // Inner functions get added to the closures array. As part of compilation
-  // more closures can be added to the end of the array. Compile all the
-  // closures until we have reached the end of the "worklist".
   Object& result = Object::Handle(zone);
-  const GrowableObjectArray& closures = GrowableObjectArray::Handle(
-      zone, IsolateGroup::Current()->object_store()->closure_functions());
-  Function& func = Function::Handle(zone);
-  for (int i = 0; i < closures.Length(); i++) {
-    func ^= closures.At(i);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) {
     if (!func.HasCode()) {
       result = Compiler::CompileFunction(thread, func);
       if (result.IsError()) {
-        if (ignore_error) continue;
-        return Error::Cast(result).raw();
+        error = Error::Cast(result).raw();
+        return false;  // Stop iteration.
       }
     }
-  }
-  return Error::null();
+    return true;  // Continue iteration.
+  });
+  return error.raw();
 }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index c89a269..7cf19f6 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2774,11 +2774,6 @@
       DefaultTypeArgumentsKind* kind_out = nullptr) const;
   void set_default_type_arguments(const TypeArguments& value) const;
 
-  // Enclosed generated closure function of this local function.
-  // This will only work after the closure function has been allocated in the
-  // isolate's object_store.
-  FunctionPtr GetGeneratedClosure() const;
-
   // Enclosing outermost function of this local function.
   FunctionPtr GetOutermostFunction() const;
 
diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc
index 9eaf367..b35fb52 100644
--- a/runtime/vm/object_service.cc
+++ b/runtime/vm/object_service.cc
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 #include "vm/canonical_tables.h"
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/assembler/disassembler.h"
 #include "vm/debugger.h"
 #include "vm/object.h"
@@ -236,7 +237,7 @@
   intptr_t id = -1;
   const char* selector = NULL;
   if (f.IsNonImplicitClosureFunction()) {
-    id = Isolate::Current()->FindClosureIndex(f);
+    id = ClosureFunctionsCache::FindClosureIndex(f);
     selector = "closures";
   } else if (f.IsImplicitClosureFunction()) {
     id = cls.FindImplicitClosureFunctionIndex(f);
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index fcd3423..0bd12fe 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -12,6 +12,7 @@
 #include "platform/globals.h"
 
 #include "vm/class_finalizer.h"
+#include "vm/closure_functions_cache.h"
 #include "vm/code_descriptors.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/compiler_state.h"
@@ -3944,7 +3945,6 @@
   const Script& script = Script::Handle();
   const Class& cls = Class::Handle(CreateDummyClass(class_name, script));
   const Array& functions = Array::Handle(Array::New(1));
-  const Isolate* iso = Isolate::Current();
 
   Function& parent = Function::Handle();
   const String& parent_name = String::Handle(Symbols::New(thread, "foo_papa"));
@@ -3962,18 +3962,23 @@
   function = Function::NewClosureFunction(function_name, parent,
                                           TokenPosition::kMinSource);
   // Add closure function to class.
-  iso->AddClosureFunction(function);
+  {
+    SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
+    ClosureFunctionsCache::AddClosureFunctionLocked(function);
+  }
 
   // The closure should return a valid index.
-  intptr_t good_closure_index = iso->FindClosureIndex(function);
+  intptr_t good_closure_index =
+      ClosureFunctionsCache::FindClosureIndex(function);
   EXPECT_GE(good_closure_index, 0);
   // The parent function should return an invalid index.
-  intptr_t bad_closure_index = iso->FindClosureIndex(parent);
+  intptr_t bad_closure_index = ClosureFunctionsCache::FindClosureIndex(parent);
   EXPECT_EQ(bad_closure_index, -1);
 
   // Retrieve closure function via index.
   Function& func_from_index = Function::Handle();
-  func_from_index ^= iso->ClosureFunctionFromIndex(good_closure_index);
+  func_from_index ^=
+      ClosureFunctionsCache::ClosureFunctionFromIndex(good_closure_index);
   // Same closure function.
   EXPECT_EQ(func_from_index.raw(), function.raw());
 }
diff --git a/runtime/vm/program_visitor.cc b/runtime/vm/program_visitor.cc
index 39ea254..09b1598 100644
--- a/runtime/vm/program_visitor.cc
+++ b/runtime/vm/program_visitor.cc
@@ -4,6 +4,7 @@
 
 #include "vm/program_visitor.h"
 
+#include "vm/closure_functions_cache.h"
 #include "vm/code_patcher.h"
 #include "vm/deopt_instructions.h"
 #include "vm/hash_map.h"
@@ -252,17 +253,15 @@
 
   if (visitor->IsFunctionVisitor()) {
     // Function objects not necessarily reachable from classes.
-    auto& function = Function::Handle(zone);
-    const auto& closures =
-        GrowableObjectArray::Handle(zone, object_store->closure_functions());
-    ASSERT(!closures.IsNull());
-    for (intptr_t i = 0; i < closures.Length(); i++) {
-      function ^= closures.At(i);
-      walker.AddToWorklist(function);
-      ASSERT(!function.HasImplicitClosureFunction());
-    }
+    ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& fun) {
+      walker.AddToWorklist(fun);
+      ASSERT(!fun.HasImplicitClosureFunction());
+      return true;  // Continue iteration.
+    });
+
     // TODO(dartbug.com/43049): Use a more general solution and remove manual
     // tracking through object_store->ffi_callback_functions.
+    auto& function = Function::Handle(zone);
     const auto& ffi_callback_entries = GrowableObjectArray::Handle(
         zone, object_store->ffi_callback_functions());
     if (!ffi_callback_entries.IsNull()) {
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 1856c56..8a87782 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -15,6 +15,7 @@
 #include "platform/utils.h"
 #include "vm/base64.h"
 #include "vm/canonical_tables.h"
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/cpu.h"
 #include "vm/dart_api_impl.h"
@@ -1723,7 +1724,6 @@
                                     const Class& klass,
                                     char** parts,
                                     int num_parts) {
-  auto isolate = thread->isolate();
   auto zone = thread->zone();
 
   if (num_parts != 4) {
@@ -1788,7 +1788,7 @@
       return Object::sentinel().raw();
     }
     Function& func = Function::Handle(zone);
-    func = isolate->ClosureFunctionFromIndex(id);
+    func = ClosureFunctionsCache::ClosureFunctionFromIndex(id);
     if (func.IsNull()) {
       return Object::sentinel().raw();
     }
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index 5155d59..aeba553 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -6,6 +6,7 @@
 #include "vm/source_report.h"
 
 #include "vm/bit_vector.h"
+#include "vm/closure_functions_cache.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/isolate.h"
 #include "vm/kernel_loader.h"
@@ -522,16 +523,10 @@
 }
 
 void SourceReport::VisitClosures(JSONArray* jsarr) {
-  const GrowableObjectArray& closures = GrowableObjectArray::Handle(
-      thread()->isolate_group()->object_store()->closure_functions());
-
-  // We need to keep rechecking the length of the closures array, as handling
-  // a closure potentially adds new entries to the end.
-  Function& func = Function::Handle(zone());
-  for (int i = 0; i < closures.Length(); i++) {
-    func ^= closures.At(i);
+  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) {
     VisitFunction(jsarr, func);
-  }
+    return true;  // Continue iteration.
+  });
 }
 
 void SourceReport::PrintJSON(JSONStream* js,
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index 1473064..72c9a91 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -28,6 +28,8 @@
   "class_id.h",
   "class_table.cc",
   "class_table.h",
+  "closure_functions_cache.cc",
+  "closure_functions_cache.h",
   "clustered_snapshot.cc",
   "clustered_snapshot.h",
   "code_comments.cc",
diff --git a/tools/VERSION b/tools/VERSION
index 37672af..09493fa 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 199
+PRERELEASE 200
 PRERELEASE_PATCH 0
\ No newline at end of file