[vm/concurrency] Move background compiler related state from `Isolate` to `IsolateGroup`

As part of making the compiler independent of `Isolate` we have to move
various state from `Isolate` to `IsolateGroup`. This CL moves background
compiler and related state.

One background compiler will be shared for all isolates within a group.

Issue https://github.com/dart-lang/sdk/issues/36097
Fixes https://github.com/dart-lang/sdk/issues/44938

TEST=Mainly refactoring, stress tests for --enable-isolate-groups with JIT will come in the future.

Change-Id: I66e863651d0b37c431ce1864e505b5413850cbde
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/183401
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
index 269b771..80f8741 100644
--- a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
@@ -111,7 +111,7 @@
     Map arguments = event['args'];
     expect(arguments, isA<Map>());
     expect(arguments['isolateGroupId'], isA<String>());
-    if (event['cat'] != 'GC') {
+    if (!const ['GC', 'Compiler', 'CompilerVerbose'].contains(event['cat'])) {
       expect(arguments['isolateId'], isA<String>());
     }
   }
diff --git a/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
index 8dc5899..1be667e 100644
--- a/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
@@ -111,7 +111,7 @@
     Map arguments = event['args'];
     expect(arguments, isA<Map>());
     expect(arguments['isolateGroupId'], isA<String>());
-    if (event['cat'] != 'GC') {
+    if (!const ['GC', 'Compiler', 'CompilerVerbose'].contains(event['cat'])) {
       expect(arguments['isolateId'], isA<String>());
     }
   }
diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc
index 0dfec5c..95ae6e3 100644
--- a/runtime/vm/class_finalizer.cc
+++ b/runtime/vm/class_finalizer.cc
@@ -1309,7 +1309,6 @@
 void ClassFinalizer::SortClasses() {
   auto T = Thread::Current();
   auto Z = T->zone();
-  auto I = T->isolate();
   auto IG = T->isolate_group();
 
   // Prevent background compiler from adding deferred classes or canonicalizing
@@ -1384,11 +1383,11 @@
   ASSERT(next_new_cid == num_cids);
   RemapClassIds(old_to_new_cid.get());
   RehashTypes();         // Types use cid's as part of their hashes.
-  I->RehashConstants();  // Const objects use cid's as part of their hashes.
+  IG->RehashConstants();  // Const objects use cid's as part of their hashes.
 
   // Ensure any newly spawned isolate will apply this permutation map right
   // after kernel loading.
-  I->group()->source()->cid_permutation_map = std::move(old_to_new_cid);
+  IG->source()->cid_permutation_map = std::move(old_to_new_cid);
 }
 
 class CidRewriteVisitor : public ObjectVisitor {
@@ -1551,7 +1550,6 @@
 void ClassFinalizer::RehashTypes() {
   auto T = Thread::Current();
   auto Z = T->zone();
-  auto I = T->isolate();
   auto IG = T->isolate_group();
 
   // Clear all cached hash values.
@@ -1635,7 +1633,7 @@
 
   // The canonical constant tables use canonical hashcodes which can change
   // due to cid-renumbering.
-  I->RehashConstants();
+  IG->RehashConstants();
 
   dict_size = Utils::RoundUpToPowerOfTwo(typeargs.Length() * 4 / 3);
   CanonicalTypeArgumentsSet typeargs_table(
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index 45bced2..897e24a 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -6963,7 +6963,7 @@
 
 #if defined(DEBUG)
   isolate()->ValidateClassTable();
-  isolate()->ValidateConstants();
+  isolate_group()->ValidateConstants();
 #endif  // DEBUG
 
 #if defined(DART_PRECOMPILER)
diff --git a/runtime/vm/compiler/backend/constant_propagator.h b/runtime/vm/compiler/backend/constant_propagator.h
index 36368d0..f2a6929 100644
--- a/runtime/vm/compiler/backend/constant_propagator.h
+++ b/runtime/vm/compiler/backend/constant_propagator.h
@@ -88,8 +88,6 @@
   // thus should not be stored anywhere.
   PhiInfo* GetPhiInfo(PhiInstr* phi);
 
-  Isolate* isolate() const { return graph_->isolate(); }
-
   FlowGraph* graph_;
 
   // Sentinels for unknown constant and non-constant values.
diff --git a/runtime/vm/compiler/backend/flow_graph.h b/runtime/vm/compiler/backend/flow_graph.h
index 8a8bb32..09e3caf 100644
--- a/runtime/vm/compiler/backend/flow_graph.h
+++ b/runtime/vm/compiler/backend/flow_graph.h
@@ -238,7 +238,6 @@
 
   Thread* thread() const { return thread_; }
   Zone* zone() const { return thread()->zone(); }
-  Isolate* isolate() const { return thread()->isolate(); }
   IsolateGroup* isolate_group() const { return thread()->isolate_group(); }
 
   intptr_t max_block_id() const { return max_block_id_; }
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 7005c7b..6fac0ad 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -300,9 +300,9 @@
 
 bool FlowGraphCompiler::ForceSlowPathForStackOverflow() const {
 #if !defined(PRODUCT)
-  if ((FLAG_stacktrace_every > 0) || (FLAG_deoptimize_every > 0) ||
-      (FLAG_gc_every > 0) ||
-      (isolate()->reload_every_n_stack_overflow_checks() > 0)) {
+  if (FLAG_stacktrace_every > 0 || FLAG_deoptimize_every > 0 ||
+      FLAG_gc_every > 0 ||
+      (isolate_group()->reload_every_n_stack_overflow_checks() > 0)) {
     if (!IsolateGroup::IsSystemIsolateGroup(isolate_group())) {
       return true;
     }
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index 7a145d1..f3f422e 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -945,7 +945,6 @@
   }
 
   Thread* thread() const { return thread_; }
-  Isolate* isolate() const { return thread_->isolate(); }
   IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
   Zone* zone() const { return zone_; }
 
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index acb987e..459aec0 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -83,7 +83,6 @@
 
 // Quick access to the current zone.
 #define Z (zone())
-#define I (isolate())
 
 #define TRACE_INLINING(statement)                                              \
   do {                                                                         \
@@ -608,7 +607,7 @@
 
   TargetEntryInstr* BuildDecisionGraph();
 
-  Isolate* isolate() const;
+  IsolateGroup* isolate_group() const;
   Zone* zone() const;
   intptr_t AllocateBlockId() const;
   inline bool trace_inlining() const;
@@ -762,7 +761,6 @@
   FlowGraph* caller_graph() const { return caller_graph_; }
 
   Thread* thread() const { return caller_graph_->thread(); }
-  Isolate* isolate() const { return caller_graph_->isolate(); }
   Zone* zone() const { return caller_graph_->zone(); }
 
   bool trace_inlining() const { return inliner_->trace_inlining(); }
@@ -1663,8 +1661,8 @@
       exit_collector_(new (Z) InlineExitCollector(owner->caller_graph(), call)),
       caller_function_(caller_function) {}
 
-Isolate* PolymorphicInliner::isolate() const {
-  return owner_->caller_graph()->isolate();
+IsolateGroup* PolymorphicInliner::isolate_group() const {
+  return owner_->caller_graph()->isolate_group();
 }
 
 Zone* PolymorphicInliner::zone() const {
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.h b/runtime/vm/compiler/backend/redundancy_elimination.h
index ab09270..877223b 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.h
+++ b/runtime/vm/compiler/backend/redundancy_elimination.h
@@ -72,7 +72,6 @@
 
   void EliminateAllocation(Definition* alloc);
 
-  Isolate* isolate() const { return flow_graph_->isolate(); }
   Zone* zone() const { return flow_graph_->zone(); }
 
   FlowGraph* flow_graph_;
diff --git a/runtime/vm/compiler/call_specializer.h b/runtime/vm/compiler/call_specializer.h
index ccad4cb..c0ad62d 100644
--- a/runtime/vm/compiler/call_specializer.h
+++ b/runtime/vm/compiler/call_specializer.h
@@ -74,7 +74,6 @@
 
  protected:
   Thread* thread() const { return flow_graph_->thread(); }
-  Isolate* isolate() const { return flow_graph_->isolate(); }
   IsolateGroup* isolate_group() const { return flow_graph_->isolate_group(); }
   Zone* zone() const { return flow_graph_->zone(); }
   const Function& function() const { return flow_graph_->function(); }
diff --git a/runtime/vm/compiler/frontend/flow_graph_builder.h b/runtime/vm/compiler/frontend/flow_graph_builder.h
index f2d5364..25d74d6 100644
--- a/runtime/vm/compiler/frontend/flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/flow_graph_builder.h
@@ -73,7 +73,6 @@
                           Instruction** last_instruction,
                           intptr_t try_index);
 
-  Isolate* isolate() const { return caller_graph_->isolate(); }
   Zone* zone() const { return caller_graph_->zone(); }
 
   FlowGraph* caller_graph_;
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index eeb7cd7..92c90ce 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -323,6 +323,7 @@
   intptr_t osr_id() const { return osr_id_; }
   Thread* thread() const { return thread_; }
   Isolate* isolate() const { return thread_->isolate(); }
+  IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
   CodePtr FinalizeCompilation(compiler::Assembler* assembler,
                               FlowGraphCompiler* graph_compiler,
                               FlowGraph* flow_graph);
@@ -1110,8 +1111,8 @@
   DISALLOW_COPY_AND_ASSIGN(BackgroundCompilerTask);
 };
 
-BackgroundCompiler::BackgroundCompiler(Isolate* isolate)
-    : isolate_(isolate),
+BackgroundCompiler::BackgroundCompiler(IsolateGroup* isolate_group)
+    : isolate_group_(isolate_group),
       queue_monitor_(),
       function_queue_(new BackgroundCompilationQueue()),
       done_monitor_(),
@@ -1125,10 +1126,11 @@
 }
 
 void BackgroundCompiler::Run() {
-  while (running_) {
+  while (true) {
     // Maybe something is already in the queue, check first before waiting
     // to be notified.
-    bool result = Thread::EnterIsolateAsHelper(isolate_, Thread::kCompilerTask);
+    bool result = Thread::EnterIsolateGroupAsHelper(
+        isolate_group_, Thread::kCompilerTask, /*bypass_safepoint=*/false);
     ASSERT(result);
     {
       Thread* thread = Thread::Current();
@@ -1173,13 +1175,16 @@
         }
       }
     }
-    Thread::ExitIsolateAsHelper();
+    Thread::ExitIsolateGroupAsHelper(/*bypass_safepoint=*/false);
     {
       // Wait to be notified when the work queue is not empty.
       MonitorLocker ml(&queue_monitor_);
       while (function_queue()->IsEmpty() && running_) {
         ml.Wait();
       }
+      if (!running_) {
+        break;
+      }
     }
   }  // while running
 
@@ -1187,7 +1192,7 @@
     // Notify that the thread is done.
     MonitorLocker ml_done(&done_monitor_);
     done_ = true;
-    ml_done.Notify();
+    ml_done.NotifyAll();
   }
 }
 
@@ -1229,7 +1234,7 @@
 
 void BackgroundCompiler::Stop() {
   Thread* thread = Thread::Current();
-  ASSERT(thread->IsMutatorThread());
+  ASSERT(thread->isolate() == nullptr || thread->IsMutatorThread());
   ASSERT(!thread->IsAtSafepoint());
 
   SafepointMonitorLocker ml_done(&done_monitor_);
diff --git a/runtime/vm/compiler/jit/compiler.h b/runtime/vm/compiler/jit/compiler.h
index 5ed55be..456c70b 100644
--- a/runtime/vm/compiler/jit/compiler.h
+++ b/runtime/vm/compiler/jit/compiler.h
@@ -119,12 +119,11 @@
 // No OSR compilation in the background compiler.
 class BackgroundCompiler {
  public:
-  explicit BackgroundCompiler(Isolate* isolate);
+  explicit BackgroundCompiler(IsolateGroup* isolate_group);
   virtual ~BackgroundCompiler();
 
-  static void Stop(Isolate* isolate) {
-    ASSERT(Thread::Current()->IsMutatorThread());
-    isolate->background_compiler()->Stop();
+  static void Stop(IsolateGroup* isolate_group) {
+    isolate_group->background_compiler()->Stop();
   }
 
   // Enqueues a function to be compiled in the background.
@@ -148,7 +147,7 @@
   void Disable();
   bool IsRunning() { return !done_; }
 
-  Isolate* isolate_;
+  IsolateGroup* isolate_group_;
 
   Monitor queue_monitor_;  // Controls access to the queue.
   BackgroundCompilationQueue* function_queue_;
@@ -165,13 +164,15 @@
 class NoBackgroundCompilerScope : public StackResource {
  public:
   explicit NoBackgroundCompilerScope(Thread* thread)
-      : StackResource(thread), isolate_(thread->isolate()) {
-    isolate_->background_compiler()->Disable();
+      : StackResource(thread), isolate_group_(thread->isolate_group()) {
+    isolate_group_->background_compiler()->Disable();
   }
-  ~NoBackgroundCompilerScope() { isolate_->background_compiler()->Enable(); }
+  ~NoBackgroundCompilerScope() {
+    isolate_group_->background_compiler()->Enable();
+  }
 
  private:
-  Isolate* isolate_;
+  IsolateGroup* isolate_group_;
 };
 
 }  // namespace dart
diff --git a/runtime/vm/compiler_test.cc b/runtime/vm/compiler_test.cc
index d5d9a5e..49df77f 100644
--- a/runtime/vm/compiler_test.cc
+++ b/runtime/vm/compiler_test.cc
@@ -88,8 +88,8 @@
   // Constant in product mode.
   FLAG_background_compilation = true;
 #endif
-  Isolate* isolate = thread->isolate();
-  isolate->background_compiler()->EnqueueCompilation(func);
+  auto isolate_group = thread->isolate_group();
+  isolate_group->background_compiler()->EnqueueCompilation(func);
   Monitor* m = new Monitor();
   {
     SafepointMonitorLocker ml(m);
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 96d9b1b..21d315c 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1435,7 +1435,7 @@
 
 DART_EXPORT void Dart_ShutdownIsolate() {
   Thread* T = Thread::Current();
-  Isolate* I = T->isolate();
+  auto I = T->isolate();
   CHECK_ISOLATE(I);
 
   // The Thread structure is disassociated from the isolate, we do the
@@ -1461,7 +1461,7 @@
     StackZone zone(T);
     HandleScope handle_scope(T);
 #if defined(DEBUG)
-    I->ValidateConstants();
+    T->isolate_group()->ValidateConstants();
 #endif
     Dart::RunShutdownCallback();
   }
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 61be4a2..acf1abd 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -3506,9 +3506,23 @@
 }
 
 bool Debugger::IsDebugging(Thread* thread, const Function& func) {
-  Debugger* debugger = thread->isolate()->debugger();
-  return debugger->IsStepping() ||
-         debugger->HasBreakpoint(func, thread->zone());
+  // TODO(dartbug.com/36097): We might need to adjust this once we start adding
+  // debugging support to --enable-isolate-groups.
+  auto isolate_group = thread->isolate_group();
+
+  bool has_breakpoint = false;
+  bool is_single_stepping = false;
+  isolate_group->ForEachIsolate(
+      [&](Isolate* isolate) {
+        if (isolate->debugger()->IsStepping()) {
+          is_single_stepping = true;
+        }
+        if (isolate->debugger()->HasBreakpoint(func, thread->zone())) {
+          has_breakpoint = true;
+        }
+      },
+      thread->IsAtSafepoint());
+  return has_breakpoint || is_single_stepping;
 }
 
 void Debugger::SignalPausedEvent(ActivationFrame* top_frame, Breakpoint* bpt) {
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 5fff56a..3799f2c 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -342,6 +342,7 @@
       random_(),
 #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
       last_reload_timestamp_(OS::GetCurrentTimeMillis()),
+      reload_every_n_stack_overflow_checks_(FLAG_reload_every),
 #endif
       source_(std::move(source)),
       api_state_(new ApiState()),
@@ -353,6 +354,9 @@
       heap_(nullptr),
       saved_unlinked_calls_(Array::null()),
       initial_field_table_(new FieldTable(/*isolate=*/nullptr)),
+#if !defined(DART_PRECOMPILED_RUNTIME)
+      background_compiler_(new BackgroundCompiler(this)),
+#endif
       symbols_lock_(new SafepointRwLock()),
       type_canonicalization_mutex_(
           NOT_IN_PRODUCT("IsolateGroup::type_canonicalization_mutex_")),
@@ -850,11 +854,15 @@
 
 NoOOBMessageScope::NoOOBMessageScope(Thread* thread)
     : ThreadStackResource(thread) {
-  thread->DeferOOBMessageInterrupts();
+  if (thread->isolate() != nullptr) {
+    thread->DeferOOBMessageInterrupts();
+  }
 }
 
 NoOOBMessageScope::~NoOOBMessageScope() {
-  thread()->RestoreOOBMessageInterrupts();
+  if (thread()->isolate() != nullptr) {
+    thread()->RestoreOOBMessageInterrupts();
+  }
 }
 
 NoReloadScope::NoReloadScope(IsolateGroup* isolate_group, Thread* thread)
@@ -934,7 +942,7 @@
   }
 }
 
-void Isolate::RehashConstants() {
+void IsolateGroup::RehashConstants() {
   Thread* thread = Thread::Current();
   StackZone stack_zone(thread);
   Zone* zone = stack_zone.GetZone();
@@ -942,10 +950,10 @@
   thread->heap()->ResetCanonicalHashTable();
 
   Class& cls = Class::Handle(zone);
-  intptr_t top = group()->class_table()->NumCids();
+  intptr_t top = class_table()->NumCids();
   for (intptr_t cid = kInstanceCid; cid < top; cid++) {
-    if (!group()->class_table()->IsValidIndex(cid) ||
-        !group()->class_table()->HasValidClassAt(cid)) {
+    if (!class_table()->IsValidIndex(cid) ||
+        !class_table()->HasValidClassAt(cid)) {
       continue;
     }
     if ((cid == kTypeArgumentsCid) || IsStringClassId(cid)) {
@@ -953,27 +961,27 @@
       // that aren't based on address.
       continue;
     }
-    cls = group()->class_table()->At(cid);
+    cls = class_table()->At(cid);
     cls.RehashConstants(zone);
   }
 }
 
 #if defined(DEBUG)
-void Isolate::ValidateConstants() {
+void IsolateGroup::ValidateConstants() {
   if (FLAG_precompiled_mode) {
     // TODO(27003)
     return;
   }
   // Issue(https://dartbug.com/44862): Figure out why hot-reload causes
   // existence of non-canonical constants.
-  if (group()->HasAttemptedReload()) {
+  if (HasAttemptedReload()) {
     return;
   }
 
   // Verify that all canonical instances are correctly setup in the
   // corresponding canonical tables.
   NoBackgroundCompilerScope no_bg_compiler(Thread::Current());
-  group()->heap()->CollectAllGarbage();
+  heap()->CollectAllGarbage();
   Thread* thread = Thread::Current();
   HeapIterationScope iteration(thread);
   VerifyCanonicalVisitor check_canonical(thread);
@@ -1705,7 +1713,6 @@
   metric_##variable##_(),
       ISOLATE_METRIC_LIST(ISOLATE_METRIC_CONSTRUCTORS)
 #undef ISOLATE_METRIC_CONSTRUCTORS
-          reload_every_n_stack_overflow_checks_(FLAG_reload_every),
 #endif  // !defined(PRODUCT)
       start_time_micros_(OS::GetCurrentMonotonicMicros()),
       on_shutdown_callback_(Isolate::ShutdownCallback()),
@@ -1734,8 +1741,6 @@
         "which violates the Dart standard.\n"
         "         See dartbug.com/30524 for more information.\n");
   }
-
-  NOT_IN_PRECOMPILED(background_compiler_ = new BackgroundCompiler(this));
 }
 
 #undef REUSABLE_HANDLE_SCOPE_INIT
@@ -1747,9 +1752,6 @@
   // RELEASE_ASSERT(program_reload_context_ == NULL);
 #endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
 
-  delete background_compiler_;
-  background_compiler_ = nullptr;
-
 #if !defined(PRODUCT)
   delete debugger_;
   debugger_ = nullptr;
@@ -1845,6 +1847,10 @@
   result->set_pause_capability(result->random()->NextUInt64());
   result->set_terminate_capability(result->random()->NextUInt64());
 
+#if !defined(PRODUCT)
+  result->debugger_ = new Debugger(result);
+#endif
+
   // Now we register the isolate in the group. From this point on any GC would
   // traverse the isolate roots (before this point, the roots are only pointing
   // to vm-isolate objects, e.g. null)
@@ -1860,9 +1866,6 @@
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
   }
 
-#if !defined(PRODUCT)
-  result->debugger_ = new Debugger(result);
-#endif
   if (FLAG_trace_isolates) {
     if (name_prefix == nullptr || strcmp(name_prefix, "vm-isolate") != 0) {
       OS::PrintErr(
@@ -2424,12 +2427,13 @@
 }
 
 #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
-void Isolate::MaybeIncreaseReloadEveryNStackOverflowChecks() {
+void IsolateGroup::MaybeIncreaseReloadEveryNStackOverflowChecks() {
   if (FLAG_reload_every_back_off) {
     if (reload_every_n_stack_overflow_checks_ < 5000) {
       reload_every_n_stack_overflow_checks_ += 99;
     } else {
-      reload_every_n_stack_overflow_checks_ *= 2;
+      const auto old_value = reload_every_n_stack_overflow_checks_;
+      reload_every_n_stack_overflow_checks_ = old_value * old_value;
     }
     // Cap the value.
     if (reload_every_n_stack_overflow_checks_ > 1000000) {
@@ -2449,12 +2453,8 @@
 }
 
 void Isolate::Shutdown() {
-  ASSERT(this == Isolate::Current());
-  NOT_IN_PRECOMPILED(BackgroundCompiler::Stop(this));
-  NOT_IN_PRECOMPILED(delete background_compiler_);
-  background_compiler_ = nullptr;
-
   Thread* thread = Thread::Current();
+  ASSERT(this == thread->isolate());
 
   // Don't allow anymore dart code to execution on this isolate.
   thread->ClearStackLimit();
@@ -2535,6 +2535,15 @@
   const bool shutdown_group =
       isolate_group->UnregisterIsolateDecrementCount(isolate);
   if (shutdown_group) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    if (!is_vm_isolate) {
+      Thread::EnterIsolateGroupAsHelper(isolate_group, Thread::kUnknownTask,
+                                        /*bypass_safepoint=*/false);
+      BackgroundCompiler::Stop(isolate_group);
+      Thread::ExitIsolateGroupAsHelper(/*bypass_safepoint=*/false);
+    }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
     // The "vm-isolate" does not have a thread pool.
     ASSERT(is_vm_isolate == (isolate_group->thread_pool() == nullptr));
     if (is_vm_isolate ||
@@ -2612,10 +2621,6 @@
       reinterpret_cast<ObjectPtr*>(&registered_service_extension_handlers_));
 #endif  // !defined(PRODUCT)
 
-  if (background_compiler() != nullptr) {
-    background_compiler()->VisitPointers(visitor);
-  }
-
 #if !defined(PRODUCT)
   // Visit objects in the debugger.
   if (debugger() != nullptr) {
@@ -2789,6 +2794,8 @@
   // is guarded with a monitor. This means that we can visit it only
   // when at safepoint or the field_list_mutex_ lock has been taken.
   visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&boxed_field_list_));
+
+  NOT_IN_PRECOMPILED(background_compiler()->VisitPointers(visitor));
 }
 
 void IsolateGroup::VisitStackPointers(ObjectPointerVisitor* visitor,
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index fefa565..1ec4245 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -341,6 +341,11 @@
                Dart_IsolateFlags api_flags);
   ~IsolateGroup();
 
+  void RehashConstants();
+#if defined(DEBUG)
+  void ValidateConstants();
+#endif
+
   IsolateGroupSource* source() const { return source_.get(); }
   std::shared_ptr<IsolateGroupSource> shareable_source() const {
     return source_;
@@ -352,6 +357,14 @@
 
   Heap* heap() const { return heap_.get(); }
 
+  BackgroundCompiler* background_compiler() const {
+#if defined(DART_PRECOMPILED_RUNTIME)
+    return nullptr;
+#else
+    return background_compiler_.get();
+#endif
+  }
+
   IdleTimeHandler* idle_time_handler() { return &idle_time_handler_; }
 
   // Returns true if this is the first isolate registered.
@@ -488,6 +501,10 @@
     isolate_group_flags_ =
         HasAttemptedReloadBit::update(value, isolate_group_flags_);
   }
+  void MaybeIncreaseReloadEveryNStackOverflowChecks();
+  intptr_t reload_every_n_stack_overflow_checks() const {
+    return reload_every_n_stack_overflow_checks_;
+  }
 #else
   bool HasAttemptedReload() const { return false; }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
@@ -827,6 +844,8 @@
   std::shared_ptr<IsolateGroupReloadContext> group_reload_context_;
   RelaxedAtomic<intptr_t> no_reload_scope_depth_ =
       0;  // we can only reload when this is 0.
+  // Per-isolate-group copy of FLAG_reload_every.
+  RelaxedAtomic<intptr_t> reload_every_n_stack_overflow_checks_;
 #endif
 
 #define ISOLATE_METRIC_VARIABLE(type, variable, name, unit)                    \
@@ -865,6 +884,8 @@
   std::shared_ptr<FieldTable> initial_field_table_;
   uint32_t isolate_group_flags_ = 0;
 
+  NOT_IN_PRECOMPILED(std::unique_ptr<BackgroundCompiler> background_compiler_);
+
   std::unique_ptr<SafepointRwLock> symbols_lock_;
   Mutex type_canonicalization_mutex_;
   Mutex type_arguments_canonicalization_mutex_;
@@ -962,11 +983,6 @@
   void ValidateClassTable();
 #endif
 
-  void RehashConstants();
-#if defined(DEBUG)
-  void ValidateConstants();
-#endif
-
   ThreadRegistry* thread_registry() const { return group()->thread_registry(); }
 
   SafepointHandler* safepoint_handler() const {
@@ -1225,10 +1241,6 @@
     deopt_context_ = value;
   }
 
-  BackgroundCompiler* background_compiler() const {
-    return background_compiler_;
-  }
-
   intptr_t BlockClassFinalization() {
     ASSERT(defer_finalization_count_ >= 0);
     return defer_finalization_count_++;
@@ -1425,20 +1437,12 @@
     return IsolateGroup::IsSystemIsolateGroup(isolate->group());
   }
 
-#if !defined(PRODUCT)
-  intptr_t reload_every_n_stack_overflow_checks() const {
-    return reload_every_n_stack_overflow_checks_;
-  }
-#endif  // !defined(PRODUCT)
-
   HandlerInfoCache* handler_info_cache() { return &handler_info_cache_; }
 
   CatchEntryMovesCache* catch_entry_moves_cache() {
     return &catch_entry_moves_cache_;
   }
 
-  void MaybeIncreaseReloadEveryNStackOverflowChecks();
-
   // The weak table used in the snapshot writer for the purpose of fast message
   // sending.
   WeakTable* forward_table_new() { return forward_table_new_.get(); }
@@ -1589,8 +1593,6 @@
 
   uint32_t isolate_flags_ = 0;
 
-  BackgroundCompiler* background_compiler_ = nullptr;
-
 // Fields that aren't needed in a product build go here with boolean flags at
 // the top.
 #if !defined(PRODUCT)
@@ -1618,8 +1620,6 @@
   ISOLATE_METRIC_LIST(ISOLATE_METRIC_VARIABLE);
 #undef ISOLATE_METRIC_VARIABLE
 
-  // Per-isolate copy of FLAG_reload_every.
-  intptr_t reload_every_n_stack_overflow_checks_;
   ProgramReloadContext* program_reload_context_ = nullptr;
   // Ring buffer of objects assigned an id.
   ObjectIdRing* object_id_ring_ = nullptr;
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index f5b4498..2a13446 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -664,11 +664,7 @@
       [&](Isolate* isolate) { number_of_isolates++; });
 
   // Disable the background compiler while we are performing the reload.
-  ForEachIsolate([&](Isolate* isolate) {
-    // TODO(dartbug.com/36097): Once the BG compiler moves from Isolate to
-    // IsolateGroup this scope should cover most of this function.
-    NoBackgroundCompilerScope stop_bg_compiler(isolate->mutator_thread());
-  });
+  NoBackgroundCompilerScope stop_bg_compiler(thread);
 
   // Wait for any concurrent marking tasks to finish and turn off the
   // concurrent marker during reload as we might be allocating new instances
@@ -1705,11 +1701,11 @@
   // content may have changed from fields being added or removed.
   {
     TIMELINE_SCOPE(RehashConstants);
-    I->RehashConstants();
+    IG->RehashConstants();
   }
 
 #ifdef DEBUG
-  I->ValidateConstants();
+  IG->ValidateConstants();
 #endif
 
   if (FLAG_identity_reload) {
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 4b38596..740793b 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -2614,7 +2614,7 @@
 class WriteBarrierUpdateVisitor : public ObjectPointerVisitor {
  public:
   explicit WriteBarrierUpdateVisitor(Thread* thread, ObjectPtr obj)
-      : ObjectPointerVisitor(thread->isolate()->group()),
+      : ObjectPointerVisitor(thread->isolate_group()),
         thread_(thread),
         old_obj_(obj) {
     ASSERT(old_obj_->IsOldObject());
@@ -6695,8 +6695,19 @@
 #if defined(PRODUCT)
   return false;
 #else
-  Thread* thread = Thread::Current();
-  return thread->isolate()->debugger()->HasBreakpoint(*this, thread->zone());
+  // TODO(dartbug.com/36097): We might need to adjust this once we start adding
+  // debugging support to --enable-isolate-groups.
+  auto thread = Thread::Current();
+  auto zone = thread->zone();
+  auto isolate_group = thread->isolate_group();
+
+  bool has_breakpoint = false;
+  isolate_group->ForEachIsolate([&](Isolate* isolate) {
+    if (isolate->debugger()->HasBreakpoint(*this, zone)) {
+      has_breakpoint = true;
+    }
+  });
+  return has_breakpoint;
 #endif
 }
 
@@ -7742,12 +7753,9 @@
     return CompilerState::Current().is_aot();
   }
 
-#if !defined(PRODUCT)
-  Thread* thread = Thread::Current();
-  if (thread->isolate()->debugger()->HasBreakpoint(*this, thread->zone())) {
+  if (HasBreakpoint()) {
     return false;
   }
-#endif  // !defined(PRODUCT)
 
   return is_inlinable() && !is_external() && !is_generated_body();
 }
@@ -10312,8 +10320,7 @@
   result.set_has_pragma(false);
   result.set_static_type_exactness_state(
       StaticTypeExactnessState::NotTracking());
-  auto isolate = Isolate::Current();
-  auto isolate_group = isolate->group();
+  auto isolate_group = IsolateGroup::Current();
 
 // Use field guards if they are enabled and the isolate has never reloaded.
 // TODO(johnmccutchan): The reload case assumes the worst case (everything is
@@ -16255,7 +16262,17 @@
 #if defined(PRODUCT)
   return false;
 #else
-  return Isolate::Current()->debugger()->HasBreakpoint(*this);
+  // TODO(dartbug.com/36097): We might need to adjust this once we start adding
+  // debugging support to --enable-isolate-groups.
+  auto isolate_group = Thread::Current()->isolate_group();
+
+  bool has_breakpoint = false;
+  isolate_group->ForEachIsolate([&](Isolate* isolate) {
+    if (isolate->debugger()->HasBreakpoint(*this)) {
+      has_breakpoint = true;
+    }
+  });
+  return has_breakpoint;
 #endif
 }
 
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 2c78a12..e70b4fe 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -2481,7 +2481,7 @@
   bool do_reload = false;
   bool do_gc = false;
   const intptr_t isolate_reload_every =
-      isolate->reload_every_n_stack_overflow_checks();
+      isolate->group()->reload_every_n_stack_overflow_checks();
   if ((FLAG_deoptimize_every > 0) || (FLAG_stacktrace_every > 0) ||
       (FLAG_gc_every > 0) || (isolate_reload_every > 0)) {
     if (!Isolate::IsSystemIsolate(isolate)) {
@@ -2548,7 +2548,7 @@
   if (do_reload) {
     JSONStream js;
     // Maybe adjust the rate of future reloads.
-    isolate->MaybeIncreaseReloadEveryNStackOverflowChecks();
+    isolate->group()->MaybeIncreaseReloadEveryNStackOverflowChecks();
 
     const char* script_uri;
     {
@@ -2773,15 +2773,15 @@
   ASSERT(function.HasCode());
 
   if (Compiler::CanOptimizeFunction(thread, function)) {
+    auto isolate_group = thread->isolate_group();
     if (FLAG_background_compilation) {
       if (function.is_background_optimizable() &&
-          isolate->background_compiler()->EnqueueCompilation(function)) {
+          isolate_group->background_compiler()->EnqueueCompilation(function)) {
         // Reduce the chance of triggering a compilation while the function is
         // being compiled in the background. INT32_MIN should ensure that it
         // takes long time to trigger a compilation.
         // Note that the background compilation queue rejects duplicate entries.
         function.SetUsageCounter(INT32_MIN);
-
         // Continue in the same code.
         arguments.SetReturn(function);
         return;