[K/N] Add assist wait condition

This change lets GCs explicitly state when sweep assist is allowed.
diff --git a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp
index 1a8c345..908e3d8 100644
--- a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp
+++ b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp
@@ -180,6 +180,8 @@
     }
     allocator_.prepareForGC();
 
+    state_.allowAssist(epoch);
+
 #ifndef CUSTOM_ALLOCATOR
     // Taking the locks before the pause is completed. So that any destroying thread
     // would not publish into the global state at an unexpected time.
diff --git a/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp
index e5eb0f5..40b5601 100644
--- a/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp
+++ b/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp
@@ -76,6 +76,10 @@
     return impl_->gc().state().schedule();
 }
 
+void gc::GC::WaitAssist(int64_t epoch) noexcept {
+    impl_->gc().state().waitEpochAssist(epoch);
+}
+
 void gc::GC::WaitFinished(int64_t epoch) noexcept {
     impl_->gc().state().waitEpochFinished(epoch);
 }
diff --git a/kotlin-native/runtime/src/gc/common/cpp/GC.hpp b/kotlin-native/runtime/src/gc/common/cpp/GC.hpp
index 1451238..0092e97 100644
--- a/kotlin-native/runtime/src/gc/common/cpp/GC.hpp
+++ b/kotlin-native/runtime/src/gc/common/cpp/GC.hpp
@@ -76,6 +76,7 @@
 
     // TODO: These should exist only in the scheduler.
     int64_t Schedule() noexcept;
+    void WaitAssist(int64_t epoch) noexcept;
     void WaitFinished(int64_t epoch) noexcept;
     void WaitFinalizers(int64_t epoch) noexcept;
 
diff --git a/kotlin-native/runtime/src/gc/common/cpp/GCState.hpp b/kotlin-native/runtime/src/gc/common/cpp/GCState.hpp
index db2648d..b08d37d 100644
--- a/kotlin-native/runtime/src/gc/common/cpp/GCState.hpp
+++ b/kotlin-native/runtime/src/gc/common/cpp/GCState.hpp
@@ -7,7 +7,6 @@
 
 #include <condition_variable>
 #include <mutex>
-#include <atomic>
 #include <optional>
 
 #include "KAssert.h"
@@ -27,6 +26,7 @@
         std::unique_lock lock(mutex_);
         shutdownFlag_ = true;
         startedEpoch.notify();
+        assistEpoch.notify();
         finishedEpoch.notify();
         scheduledEpoch.notify();
         finalizedEpoch.notify();
@@ -34,10 +34,16 @@
 
     void start(int64_t epoch) { startedEpoch.set(epoch); }
 
+    void allowAssist(int64_t epoch) { assistEpoch.set(epoch); }
+
     void finish(int64_t epoch) { finishedEpoch.set(epoch); }
 
     void finalized(int64_t epoch) { finalizedEpoch.set(epoch); }
 
+    void waitEpochAssist(int64_t epoch) {
+        assistEpoch.wait([this, epoch] { return *assistEpoch >= epoch || shutdownFlag_; });
+    }
+
     void waitEpochFinished(int64_t epoch) {
         finishedEpoch.wait([this, epoch] { return *finishedEpoch >= epoch || shutdownFlag_; });
     }
@@ -88,6 +94,7 @@
     std::mutex mutex_;
     // Use a separate conditional variable for each counter to mitigate a winpthreads bug (see KT-50948 for details).
     ValueWithCondVar<int64_t> startedEpoch{0, mutex_};
+    ValueWithCondVar<int64_t> assistEpoch{0, mutex_};
     ValueWithCondVar<int64_t> finishedEpoch{0, mutex_};
     ValueWithCondVar<int64_t> scheduledEpoch{0, mutex_};
     ValueWithCondVar<int64_t> finalizedEpoch{0, mutex_};
diff --git a/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp
index 3c62fff..d2b9369 100644
--- a/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp
+++ b/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp
@@ -55,6 +55,8 @@
     return 0;
 }
 
+void gc::GC::WaitAssist(int64_t epoch) noexcept {}
+
 void gc::GC::WaitFinished(int64_t epoch) noexcept {}
 
 void gc::GC::WaitFinalizers(int64_t epoch) noexcept {}
diff --git a/kotlin-native/runtime/src/gc/pmcs/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/pmcs/cpp/GCImpl.cpp
index 744a5f5..178ca5a 100644
--- a/kotlin-native/runtime/src/gc/pmcs/cpp/GCImpl.cpp
+++ b/kotlin-native/runtime/src/gc/pmcs/cpp/GCImpl.cpp
@@ -76,6 +76,10 @@
     return impl_->gc().state().schedule();
 }
 
+void gc::GC::WaitAssist(int64_t epoch) noexcept {
+    impl_->gc().state().waitEpochAssist(epoch);
+}
+
 void gc::GC::WaitFinished(int64_t epoch) noexcept {
     impl_->gc().state().waitEpochFinished(epoch);
 }
diff --git a/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp b/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp
index cda9871..588567c 100644
--- a/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp
+++ b/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp
@@ -194,6 +194,8 @@
 
     resumeTheWorld(gcHandle);
 
+    state_.allowAssist(epoch);
+
 #ifndef CUSTOM_ALLOCATOR
     alloc::SweepExtraObjects<alloc::DefaultSweepTraits<alloc::ObjectFactoryImpl>>(gcHandle, *extraObjectFactoryIterable);
     extraObjectFactoryIterable = std::nullopt;
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp
index 0778689..536ba6f 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp
@@ -69,6 +69,10 @@
     return impl_->gc().state().schedule();
 }
 
+void gc::GC::WaitAssist(int64_t epoch) noexcept {
+    impl_->gc().state().waitEpochAssist(epoch);
+}
+
 void gc::GC::WaitFinished(int64_t epoch) noexcept {
     impl_->gc().state().waitEpochFinished(epoch);
 }
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp
index 03f722d..045560b 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp
@@ -78,6 +78,8 @@
     }
     allocator_.prepareForGC();
 
+    state_.allowAssist(epoch);
+
 #ifndef CUSTOM_ALLOCATOR
     // Taking the locks before the pause is completed. So that any destroying thread
     // would not publish into the global state at an unexpected time.
diff --git a/kotlin-native/runtime/src/gcScheduler/common/cpp/MutatorAssists.cpp b/kotlin-native/runtime/src/gcScheduler/common/cpp/MutatorAssists.cpp
index ca03f79..35287f66 100644
--- a/kotlin-native/runtime/src/gcScheduler/common/cpp/MutatorAssists.cpp
+++ b/kotlin-native/runtime/src/gcScheduler/common/cpp/MutatorAssists.cpp
@@ -21,6 +21,7 @@
     {
         std::unique_lock guard(owner_.m_);
         RuntimeLogDebug({kTagGC}, "Thread is assisting for epoch %" PRId64, epoch);
+        mm::GlobalData::Instance().gc().WaitAssist(epoch);
         thread_.allocator().assistGC();
         owner_.cv_.wait(guard, noNeedToWait);
         RuntimeLogDebug({kTagGC}, "Thread has assisted for epoch %" PRId64, epoch);