Add integration tests for custom allocator and GCs
diff --git a/kotlin-native/runtime/build.gradle.kts b/kotlin-native/runtime/build.gradle.kts
index def32fc..2916138 100644
--- a/kotlin-native/runtime/build.gradle.kts
+++ b/kotlin-native/runtime/build.gradle.kts
@@ -165,9 +165,6 @@
             }
 
             compilerArgs.add("-DCUSTOM_ALLOCATOR")
-
-            // Directly depends on cms which is only supported with threads.
-            onlyIf { target.supportsThreads() }
         }
 
         module("opt_alloc") {
@@ -196,6 +193,7 @@
 
             onlyIf { target.supportsCoreSymbolication() }
         }
+
         module("source_info_libbacktrace") {
             srcRoot.set(layout.projectDirectory.dir("src/source_info/libbacktrace"))
             headersDirs.from(files("src/main/cpp", "src/libbacktrace/c/include"))
@@ -372,6 +370,11 @@
             testedModules.addAll("main", "experimental_memory_manager", "common_gc", "same_thread_ms_gc", "std_alloc", "objc")
         }
 
+        testsGroup("experimentalMM_custom_alloc_runtime_tests") {
+            testedModules.addAll("experimental_memory_manager_custom", "same_thread_ms_gc_custom")
+            testSupportModules.addAll("main", "common_gc", "custom_alloc", "objc")
+        }
+
         testsGroup("experimentalMM_cms_mimalloc_runtime_tests") {
             testedModules.addAll("main", "experimental_memory_manager", "common_gc", "concurrent_ms_gc", "mimalloc", "opt_alloc", "objc")
         }
@@ -380,6 +383,11 @@
             testedModules.addAll("main", "experimental_memory_manager", "common_gc", "concurrent_ms_gc", "std_alloc", "objc")
         }
 
+        testsGroup("experimentalMM_cms_custom_alloc_runtime_tests") {
+            testedModules.addAll("experimental_memory_manager_custom", "concurrent_ms_gc_custom")
+            testSupportModules.addAll("main", "common_gc", "custom_alloc", "objc")
+        }
+
         testsGroup("experimentalMM_noop_mimalloc_runtime_tests") {
             testedModules.addAll("main", "experimental_memory_manager", "common_gc", "noop_gc", "mimalloc", "opt_alloc", "objc")
         }
@@ -387,6 +395,11 @@
         testsGroup("experimentalMM_noop_std_alloc_runtime_tests") {
             testedModules.addAll("main", "experimental_memory_manager", "common_gc", "noop_gc", "std_alloc", "objc")
         }
+
+        testsGroup("experimentalMM_noop_custom_alloc_runtime_tests") {
+            testedModules.addAll("experimental_memory_manager_custom", "noop_gc_custom")
+            testSupportModules.addAll("main", "common_gc", "custom_alloc", "objc")
+        }
     }
 }
 
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/AtomicStack.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/AtomicStack.hpp
index 7db4f43..26f94d0 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/AtomicStack.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/AtomicStack.hpp
@@ -10,6 +10,7 @@
 
 #include "KAssert.h"
 #include "Utils.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -82,6 +83,17 @@
         RuntimeAssert(isEmpty(), "AtomicStack must be empty on destruction");
     }
 
+    // Test method
+    std_support::vector<T*> GetElements() {
+        std_support::vector<T*> elements;
+        T* elm = stack_.load();
+        while (elm) {
+            elements.push_back(elm);
+            elm = elm->next_;
+        }
+        return elements;
+    }
+
 private:
     std::atomic<T*> stack_{nullptr};
 };
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/CustomAllocator.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/CustomAllocator.hpp
index 947b5ce..212852a 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/CustomAllocator.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/CustomAllocator.hpp
@@ -37,6 +37,10 @@
     FinalizerQueue ExtractFinalizerQueue() noexcept;
 
     static size_t GetAllocatedHeapSize(ObjHeader* object) noexcept;
+    
+    Heap& heap() noexcept {
+        return heap_;
+    }
 
 private:
     uint8_t* Allocate(uint64_t cellCount) noexcept;
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.cpp b/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.cpp
index d8356e1..30fedd7 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.cpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.cpp
@@ -86,4 +86,22 @@
     return nextFree_.first > 0 || nextFree_.last < end_;
 }
 
+std_support::vector<uint8_t*> FixedBlockPage::GetAllocatedBlocks() noexcept {
+    std_support::vector<uint8_t*> allocated;
+    CustomAllocInfo("FixedBlockPage(%p)::Sweep()", this);
+    FixedCellRange nextFree = nextFree_; // Accessing the previous free list structure.
+    for (uint32_t cell = 0 ; cell < end_ ; cell += blockSize_) {
+        for (; cell < nextFree.first ; cell += blockSize_) {
+            allocated.push_back(cells_[cell].data);
+        }
+        if (nextFree.last >= end_) {
+            break;
+        }
+        cell = nextFree.last;
+        nextFree = cells_[cell].nextFree;
+    }
+    return allocated;
+}
+
+
 } // namespace kotlin::alloc
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.hpp
index 80df4bc..ca760bf 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPage.hpp
@@ -12,6 +12,7 @@
 #include "AtomicStack.hpp"
 #include "ExtraObjectPage.hpp"
 #include "GCStatistics.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -43,6 +44,9 @@
 
     bool Sweep(GCSweepScope& sweepHandle, FinalizerQueue& finalizerQueue) noexcept;
 
+    // Testing method
+    std_support::vector<uint8_t*> GetAllocatedBlocks() noexcept;
+
 private:
     explicit FixedBlockPage(uint32_t blockSize) noexcept;
 
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPageTest.cpp b/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPageTest.cpp
index ef483f0..0f23388 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPageTest.cpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/FixedBlockPageTest.cpp
@@ -150,6 +150,15 @@
                 }
             }
             EXPECT_EQ(page->Sweep(gcScope, finalizerQueue), !live.empty());
+            uint8_t* prev = nullptr;
+            uint32_t allocCount = 0;
+            for (auto* obj : page->GetAllocatedBlocks()) {
+                EXPECT_LT(prev, obj);
+                prev = obj;
+                ++allocCount;
+                EXPECT_NE(live.find(obj), live.end());
+            }
+            EXPECT_EQ(allocCount, live.size());
         }
         while ((ptr = alloc(page, size))) live.insert(ptr);
         EXPECT_EQ(live.size(), BLOCK_COUNT);
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/Heap.cpp b/kotlin-native/runtime/src/custom_alloc/cpp/Heap.cpp
index 35ab3df..15095f9 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/Heap.cpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/Heap.cpp
@@ -14,8 +14,12 @@
 #include "CustomAllocConstants.hpp"
 #include "AtomicStack.hpp"
 #include "CustomLogging.hpp"
+#include "ExtraObjectData.hpp"
 #include "ExtraObjectPage.hpp"
+#include "GCApi.hpp"
+#include "Memory.h"
 #include "ThreadRegistry.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -31,6 +35,7 @@
 
 FinalizerQueue Heap::Sweep(gc::GCHandle gcHandle) noexcept {
     FinalizerQueue finalizerQueue;
+    CustomAllocDebug("Heap: before sweep FinalizerQueue size == %zu", finalizerQueue.size());
     CustomAllocDebug("Heap::Sweep()");
     {
         auto sweepHandle = gcHandle.sweep();
@@ -40,6 +45,7 @@
         nextFitPages_.Sweep(sweepHandle, finalizerQueue);
         singleObjectPages_.SweepAndFree(sweepHandle, finalizerQueue);
     }
+    CustomAllocDebug("Heap: before extra sweep FinalizerQueue size == %zu", finalizerQueue.size());
     {
         auto sweepHandle = gcHandle.sweepExtraObjects();
         extraObjectPages_.Sweep(sweepHandle, finalizerQueue);
@@ -68,4 +74,41 @@
     return extraObjectPages_.GetPage(0, finalizerQueue);
 }
 
+std_support::vector<ObjHeader*> Heap::GetAllocatedObjects() noexcept {
+    std_support::vector<ObjHeader*> allocated;
+    for (int blockSize = 0; blockSize <= FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE; ++blockSize) {
+        for (auto* page : fixedBlockPages_[blockSize].GetPages()) {
+            for (auto* block : page->GetAllocatedBlocks()) {
+                allocated.push_back(reinterpret_cast<ObjHeader*>(block + gcDataSize));
+            }
+        }
+    }
+    for (auto* page : nextFitPages_.GetPages()) {
+        for (auto* block : page->GetAllocatedBlocks()) {
+            allocated.push_back(reinterpret_cast<ObjHeader*>(block + gcDataSize));
+        }
+    }
+    for (auto* page : singleObjectPages_.GetPages()) {
+        for (auto* block : page->GetAllocatedBlocks()) {
+            allocated.push_back(reinterpret_cast<ObjHeader*>(block + gcDataSize));
+        }
+    }
+    std_support::vector<ObjHeader*> unfinalized;
+    for (auto* block: allocated) {
+        if (!block->has_meta_object() || !mm::ExtraObjectData::Get(block)->getFlag(mm::ExtraObjectData::FLAGS_FINALIZED)) {
+            unfinalized.push_back(block);
+        }
+    }
+    return unfinalized;
+}
+
+void Heap::ClearForTests() noexcept {
+    for (int blockSize = 0; blockSize <= FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE; ++blockSize) {
+        fixedBlockPages_[blockSize].ClearForTests();
+    }
+    nextFitPages_.ClearForTests();
+    singleObjectPages_.ClearForTests();
+    extraObjectPages_.ClearForTests();
+}
+
 } // namespace kotlin::alloc
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/Heap.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/Heap.hpp
index a3c206c..d2fdf4f 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/Heap.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/Heap.hpp
@@ -13,6 +13,7 @@
 #include "CustomAllocConstants.hpp"
 #include "ExtraObjectPage.hpp"
 #include "GCStatistics.hpp"
+#include "Memory.h"
 #include "SingleObjectPage.hpp"
 #include "NextFitPage.hpp"
 #include "PageStore.hpp"
@@ -35,6 +36,10 @@
     SingleObjectPage* GetSingleObjectPage(uint64_t cellCount, FinalizerQueue& finalizerQueue) noexcept;
     ExtraObjectPage* GetExtraObjectPage(FinalizerQueue& finalizerQueue) noexcept;
 
+    // Test method
+    std_support::vector<ObjHeader*> GetAllocatedObjects() noexcept;
+    void ClearForTests() noexcept;
+
 private:
     PageStore<FixedBlockPage> fixedBlockPages_[FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 1];
     PageStore<NextFitPage> nextFitPages_;
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.cpp b/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.cpp
index fb08351..89bed47 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.cpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.cpp
@@ -11,6 +11,7 @@
 #include "CustomLogging.hpp"
 #include "CustomAllocConstants.hpp"
 #include "GCApi.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -104,4 +105,15 @@
     }
 }
 
+std_support::vector<uint8_t*> NextFitPage::GetAllocatedBlocks() noexcept {
+    std_support::vector<uint8_t*> allocated;
+    Cell* end = cells_ + NEXT_FIT_PAGE_CELL_COUNT;
+    for (Cell* block = cells_ + 1; block != end; block = block->Next()) {
+        if (block->isAllocated_) {
+            allocated.push_back(block->data_);
+        }
+    }
+    return allocated;
+}
+
 } // namespace kotlin::alloc
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.hpp
index 085ac5b..e8bd30f 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPage.hpp
@@ -13,6 +13,7 @@
 #include "Cell.hpp"
 #include "ExtraObjectPage.hpp"
 #include "GCStatistics.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -34,6 +35,9 @@
     // Testing method
     bool CheckInvariants() noexcept;
 
+    // Testing method
+    std_support::vector<uint8_t*> GetAllocatedBlocks() noexcept;
+
 private:
     explicit NextFitPage(uint32_t cellCount) noexcept;
 
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPageTest.cpp b/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPageTest.cpp
index 88c3dfe..1190a10 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPageTest.cpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/NextFitPageTest.cpp
@@ -109,9 +109,7 @@
         std::minstd_rand r(seed);
         NextFitPage* page = NextFitPage::Create(MIN_BLOCK_SIZE);
         int unmarked = 0;
-        while (true) {
-            uint8_t* ptr = alloc(page, MIN_BLOCK_SIZE);
-            if (ptr == nullptr) break;
+        while (uint8_t* ptr = alloc(page, MIN_BLOCK_SIZE)) {
             if (r() & 1) {
                 mark(ptr);
             } else {
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/PageStore.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/PageStore.hpp
index 831b7df..bf989d4 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/PageStore.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/PageStore.hpp
@@ -7,10 +7,12 @@
 #define CUSTOM_ALLOC_CPP_PAGESTORE_HPP_
 
 #include <atomic>
+#include <cstdint>
 
 #include "AtomicStack.hpp"
 #include "ExtraObjectPage.hpp"
 #include "GCStatistics.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -78,6 +80,8 @@
     }
 
 private:
+    friend class Heap;
+
     T* SweepSingle(GCSweepScope& sweepHandle, T* page, AtomicStack<T>& from, AtomicStack<T>& to, FinalizerQueue& finalizerQueue) noexcept {
         if (!page) {
             return nullptr;
@@ -92,6 +96,22 @@
         return nullptr;
     }
 
+    // Testing method
+    std_support::vector<T*> GetPages() noexcept {
+        std_support::vector<T*> pages;
+        for (T* page : ready_.GetElements()) pages.push_back(page);
+        for (T* page : used_.GetElements()) pages.push_back(page);
+        for (T* page : unswept_.GetElements()) pages.push_back(page);
+        return pages;
+    }
+
+    void ClearForTests() noexcept {
+        while (T* page = empty_.Pop()) page->Destroy();
+        while (T* page = ready_.Pop()) page->Destroy();
+        while (T* page = used_.Pop()) page->Destroy();
+        while (T* page = unswept_.Pop()) page->Destroy();
+    }
+
     AtomicStack<T> empty_;
     AtomicStack<T> ready_;
     AtomicStack<T> used_;
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.cpp b/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.cpp
index e38d2d4..69998c2 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.cpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.cpp
@@ -11,6 +11,7 @@
 #include "CustomLogging.hpp"
 #include "CustomAllocConstants.hpp"
 #include "GCApi.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -46,4 +47,12 @@
     return false;
 }
 
+std_support::vector<uint8_t*> SingleObjectPage::GetAllocatedBlocks() noexcept {
+    std_support::vector<uint8_t*> allocated;
+    if (isAllocated_) {
+        allocated.push_back(data_);
+    }
+    return allocated;
+}
+
 } // namespace kotlin::alloc
diff --git a/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.hpp b/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.hpp
index 51646a7..41dade4 100644
--- a/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.hpp
+++ b/kotlin-native/runtime/src/custom_alloc/cpp/SingleObjectPage.hpp
@@ -12,6 +12,7 @@
 #include "AtomicStack.hpp"
 #include "ExtraObjectPage.hpp"
 #include "GCStatistics.hpp"
+#include "std_support/Vector.hpp"
 
 namespace kotlin::alloc {
 
@@ -33,9 +34,13 @@
 
 private:
     friend class AtomicStack<SingleObjectPage>;
+    friend class Heap;
 
     explicit SingleObjectPage(size_t size) noexcept;
 
+    // Testing method
+    std_support::vector<uint8_t*> GetAllocatedBlocks() noexcept;
+
     SingleObjectPage* next_;
     bool isAllocated_ = false;
     size_t size_;
diff --git a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweepTest.cpp b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweepTest.cpp
index ee93875..cb73a63 100644
--- a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweepTest.cpp
+++ b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweepTest.cpp
@@ -178,6 +178,9 @@
 }
 
 std_support::vector<ObjHeader*> Alive(mm::ThreadData& threadData) {
+#ifdef CUSTOM_ALLOCATOR
+    return threadData.gc().impl().alloc().heap().GetAllocatedObjects();
+#else
     std_support::vector<ObjHeader*> objects;
     for (auto node : threadData.gc().impl().objectFactoryThreadQueue()) {
         objects.push_back(node.GetObjHeader());
@@ -186,6 +189,7 @@
         objects.push_back(node.GetObjHeader());
     }
     return objects;
+#endif
 }
 
 bool IsMarked(ObjHeader* objHeader) {
@@ -214,7 +218,9 @@
     ~ConcurrentMarkAndSweepTest() {
         mm::GlobalsRegistry::Instance().ClearForTests();
         mm::SpecialRefRegistry::instance().clearForTests();
+#ifndef CUSTOM_ALLOCATOR
         mm::GlobalData::Instance().extraObjectDataFactory().ClearForTests();
+#endif
         mm::GlobalData::Instance().gc().ClearForTests();
     }
 
@@ -1012,6 +1018,8 @@
     }
 }
 
+// Custom allocator does not have a notion of objects alive only for some thread
+#ifndef CUSTOM_ALLOCATOR
 TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
     std_support::vector<Mutator> mutators(kDefaultThreadCount);
     std_support::vector<ObjHeader*> globals(2 * kDefaultThreadCount);
@@ -1092,7 +1100,7 @@
         EXPECT_THAT(newMutators[i].Alive(), testing::UnorderedElementsAreArray(aliveForThisThread));
     }
 }
-
+#endif // CUSTOM_ALLOCATOR
 
 TEST_P(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
     std_support::vector<Mutator> mutators(2);
diff --git a/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessorTest.cpp b/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessorTest.cpp
index 50ed078..1def417 100644
--- a/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessorTest.cpp
+++ b/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessorTest.cpp
@@ -27,6 +27,8 @@
 
 // These tests can only work if `GC` is `ConcurrentMarkAndSweep`.
 
+// custom allocator uses its own finalizer processor
+#ifndef CUSTOM_ALLOCATOR
 namespace {
 
 struct Payload {
@@ -144,4 +146,4 @@
         ASSERT_EQ(threadsCount(), 1);
     });
 }
-
+#endif // CUSTOM_ALLOCATOR
diff --git a/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp
index d505fe7..314fcaa 100644
--- a/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp
+++ b/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp
@@ -5,7 +5,6 @@
 
 #include "GCImpl.hpp"
 
-#include "Common.h"
 #include "ConcurrentMarkAndSweep.hpp"
 #include "GC.hpp"
 #include "GCStatistics.hpp"
@@ -57,6 +56,8 @@
 void gc::GC::ThreadData::ClearForTests() noexcept {
 #ifndef CUSTOM_ALLOCATOR
     impl_->objectFactoryThreadQueue().ClearForTests();
+#else
+    impl_->alloc().PrepareForGC();
 #endif
 }
 
@@ -115,6 +116,8 @@
     impl_->gc().StopFinalizerThreadIfRunning();
 #ifndef CUSTOM_ALLOCATOR
     impl_->objectFactory().ClearForTests();
+#else
+    impl_->gc().heap().ClearForTests();
 #endif
     GCHandle::ClearForTests();
 }
diff --git a/kotlin-native/runtime/src/gc/cms/cpp/GCImplTestSupport.cpp b/kotlin-native/runtime/src/gc/cms/cpp/GCImplTestSupport.cpp
index 3c84c32..131801a4 100644
--- a/kotlin-native/runtime/src/gc/cms/cpp/GCImplTestSupport.cpp
+++ b/kotlin-native/runtime/src/gc/cms/cpp/GCImplTestSupport.cpp
@@ -26,6 +26,10 @@
 } // namespace
 
 void gc::AssertClear(GC& gc) noexcept {
+#ifdef CUSTOM_ALLOCATOR
+    auto objects = gc.impl().gc().heap().GetAllocatedObjects();
+#else
     auto objects = gc.impl().objectFactory().LockForIter();
+#endif
     EXPECT_THAT(collectCopy(objects), testing::UnorderedElementsAre());
 }
diff --git a/kotlin-native/runtime/src/gc/noop/cpp/GCImplTestSupport.cpp b/kotlin-native/runtime/src/gc/noop/cpp/GCImplTestSupport.cpp
index 1fab0db..131801a4 100644
--- a/kotlin-native/runtime/src/gc/noop/cpp/GCImplTestSupport.cpp
+++ b/kotlin-native/runtime/src/gc/noop/cpp/GCImplTestSupport.cpp
@@ -26,8 +26,10 @@
 } // namespace
 
 void gc::AssertClear(GC& gc) noexcept {
-#ifndef CUSTOM_ALLOCATOR
+#ifdef CUSTOM_ALLOCATOR
+    auto objects = gc.impl().gc().heap().GetAllocatedObjects();
+#else
     auto objects = gc.impl().objectFactory().LockForIter();
-    EXPECT_THAT(collectCopy(objects), testing::UnorderedElementsAre());
 #endif
+    EXPECT_THAT(collectCopy(objects), testing::UnorderedElementsAre());
 }
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp
index 81eb0c3..a952533 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp
@@ -59,6 +59,8 @@
 void gc::GC::ThreadData::ClearForTests() noexcept {
 #ifndef CUSTOM_ALLOCATOR
     impl_->objectFactoryThreadQueue().ClearForTests();
+#else
+    impl_->alloc().PrepareForGC();
 #endif
 }
 
@@ -96,10 +98,10 @@
 
 // static
 size_t gc::GC::GetAllocatedHeapSize(ObjHeader* object) noexcept {
-#ifndef CUSTOM_ALLOCATOR
-    return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
-#else
+#ifdef CUSTOM_ALLOCATOR
     return alloc::CustomAllocator::GetAllocatedHeapSize(object);
+#else
+    return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
 #endif
 }
 
@@ -114,6 +116,8 @@
 void gc::GC::ClearForTests() noexcept {
 #ifndef CUSTOM_ALLOCATOR
     impl_->objectFactory().ClearForTests();
+#else
+    impl_->gc().heap().ClearForTests();
 #endif
     GCHandle::ClearForTests();
 }
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/GCImplTestSupport.cpp b/kotlin-native/runtime/src/gc/stms/cpp/GCImplTestSupport.cpp
index 3c84c32..131801a4 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/GCImplTestSupport.cpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/GCImplTestSupport.cpp
@@ -26,6 +26,10 @@
 } // namespace
 
 void gc::AssertClear(GC& gc) noexcept {
+#ifdef CUSTOM_ALLOCATOR
+    auto objects = gc.impl().gc().heap().GetAllocatedObjects();
+#else
     auto objects = gc.impl().objectFactory().LockForIter();
+#endif
     EXPECT_THAT(collectCopy(objects), testing::UnorderedElementsAre());
 }
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp
index 29b6cb7..3294c67 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.cpp
@@ -188,8 +188,7 @@
 #ifndef CUSTOM_ALLOCATOR
     finalizerQueue.Finalize();
 #else
-    alloc::ExtraObjectCell* cell;
-    while ((cell = finalizerQueue.Pop())) {
+    while (alloc::ExtraObjectCell* cell = finalizerQueue.Pop()) {
         RunFinalizers(cell->Data()->GetBaseObject());
     }
 #endif
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp
index 71bcbdd..69bb82d 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp
@@ -42,7 +42,6 @@
     class ObjectData {
     public:
         bool tryMark() noexcept {
-            printf("WTF?!?!?\n"); fflush(stdout);
             bool result = trySetNext(reinterpret_cast<ObjectData*>(1));
             RuntimeLogDebug({"gc"}, "tryMark %p = %d", this, result);
             return result;
diff --git a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweepTest.cpp b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweepTest.cpp
index 5bd2ba6..b8140f8 100644
--- a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweepTest.cpp
+++ b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweepTest.cpp
@@ -179,6 +179,9 @@
 }
 
 std_support::vector<ObjHeader*> Alive(mm::ThreadData& threadData) {
+#ifdef CUSTOM_ALLOCATOR
+    return threadData.gc().impl().alloc().heap().GetAllocatedObjects();
+#else
     std_support::vector<ObjHeader*> objects;
     for (auto node : threadData.gc().impl().objectFactoryThreadQueue()) {
         objects.push_back(node.GetObjHeader());
@@ -187,6 +190,7 @@
         objects.push_back(node.GetObjHeader());
     }
     return objects;
+#endif
 }
 
 bool IsMarked(ObjHeader* objHeader) {
@@ -210,8 +214,12 @@
     ~SameThreadMarkAndSweepTest() {
         mm::GlobalsRegistry::Instance().ClearForTests();
         mm::SpecialRefRegistry::instance().clearForTests();
+#ifndef CUSTOM_ALLOCATOR
         mm::GlobalData::Instance().extraObjectDataFactory().ClearForTests();
         mm::GlobalData::Instance().gc().impl().objectFactory().ClearForTests();
+#else
+        mm::GlobalData::Instance().gc().impl().gc().heap().ClearForTests();
+#endif
     }
 
     testing::MockFunction<void(ObjHeader*)>& finalizerHook() { return finalizerHooks_.finalizerHook(); }
@@ -996,6 +1004,8 @@
     }
 }
 
+// Custom allocator does not have a notion of objects alive only for some thread
+#ifndef CUSTOM_ALLOCATOR
 TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
     std_support::vector<Mutator> mutators(kDefaultThreadCount);
     std_support::vector<ObjHeader*> globals(2 * kDefaultThreadCount);
@@ -1076,6 +1086,7 @@
         EXPECT_THAT(newMutators[i].Alive(), testing::UnorderedElementsAreArray(aliveForThisThread));
     }
 }
+#endif
 
 
 TEST_F(SameThreadMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataFactory.cpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataFactory.cpp
index 41a3e24..dd7d146 100644
--- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataFactory.cpp
+++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataFactory.cpp
@@ -10,6 +10,7 @@
 
 using namespace kotlin;
 
+#ifndef CUSTOM_ALLOCATOR
 // static
 mm::ExtraObjectDataFactory& mm::ExtraObjectDataFactory::Instance() noexcept {
     return GlobalData::Instance().extraObjectDataFactory();
@@ -41,3 +42,4 @@
 
 mm::ExtraObjectDataFactory::ExtraObjectDataFactory() = default;
 mm::ExtraObjectDataFactory::~ExtraObjectDataFactory() = default;
+#endif
diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp
index 806cd4c..0053207 100644
--- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp
+++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp
@@ -29,7 +29,9 @@
 
     ~ExtraObjectDataTest() {
         mm::GlobalsRegistry::Instance().ClearForTests();
+#ifndef CUSTOM_ALLOCATOR
         mm::GlobalData::Instance().extraObjectDataFactory().ClearForTests();
+#endif
         mm::GlobalData::Instance().gc().ClearForTests();
     }
 };
diff --git a/kotlin-native/runtime/src/mm/cpp/GlobalData.hpp b/kotlin-native/runtime/src/mm/cpp/GlobalData.hpp
index b6d7b25..45c48f9 100644
--- a/kotlin-native/runtime/src/mm/cpp/GlobalData.hpp
+++ b/kotlin-native/runtime/src/mm/cpp/GlobalData.hpp
@@ -27,7 +27,9 @@
     ThreadRegistry& threadRegistry() noexcept { return threadRegistry_; }
     GlobalsRegistry& globalsRegistry() noexcept { return globalsRegistry_; }
     SpecialRefRegistry& specialRefRegistry() noexcept { return specialRefRegistry_; }
+#ifndef CUSTOM_ALLOCATOR
     ExtraObjectDataFactory& extraObjectDataFactory() noexcept { return extraObjectDataFactory_; }
+#endif
     gc::GC& gc() noexcept { return gc_; }
     AppStateTracking& appStateTracking() noexcept { return appStateTracking_; }
 
@@ -42,7 +44,9 @@
     AppStateTracking appStateTracking_;
     GlobalsRegistry globalsRegistry_;
     SpecialRefRegistry specialRefRegistry_;
+#ifndef CUSTOM_ALLOCATOR
     ExtraObjectDataFactory extraObjectDataFactory_;
+#endif
     gc::GC gc_;
 };
 
diff --git a/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp b/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp
index 36ec7b6..a795beb 100644
--- a/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp
+++ b/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp
@@ -21,6 +21,8 @@
 #include "std_support/CStdlib.hpp"
 #include "std_support/Vector.hpp"
 
+// ObjectFactory is not used by custom allocator
+#ifndef CUSTOM_ALLOCATOR
 using namespace kotlin;
 
 using testing::_;
@@ -1130,3 +1132,4 @@
     EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
     EXPECT_CALL(allocator, Free(_, _)).Times(kThreadCount);
 }
+#endif
diff --git a/kotlin-native/runtime/src/mm/cpp/TestSupport.cpp b/kotlin-native/runtime/src/mm/cpp/TestSupport.cpp
index 6a041a3..2c8ebbf 100644
--- a/kotlin-native/runtime/src/mm/cpp/TestSupport.cpp
+++ b/kotlin-native/runtime/src/mm/cpp/TestSupport.cpp
@@ -45,12 +45,15 @@
 extern "C" void Kotlin_TestSupport_AssertClearGlobalState() {
     // Validate that global registries are empty.
     auto globals = mm::GlobalsRegistry::Instance().LockForIter();
-    auto extraObjects = mm::GlobalData::Instance().extraObjectDataFactory().LockForIter();
     auto specialRefs = mm::SpecialRefRegistry::instance().lockForIter();
     auto threads = mm::ThreadRegistry::Instance().LockForIter();
 
-    EXPECT_THAT(collectCopy(globals), testing::UnorderedElementsAre());
+#ifndef CUSTOM_ALLOCATOR
+    auto extraObjects = mm::GlobalData::Instance().extraObjectDataFactory().LockForIter();
     EXPECT_THAT(collectPointers(extraObjects), testing::UnorderedElementsAre());
+#endif
+
+    EXPECT_THAT(collectCopy(globals), testing::UnorderedElementsAre());
     EXPECT_THAT(collectPointers(specialRefs), testing::UnorderedElementsAre());
     EXPECT_THAT(collectPointers(threads), testing::UnorderedElementsAre());
     gc::AssertClear(mm::GlobalData::Instance().gc());
diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp b/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp
index 1ad8101..22dba9a 100644
--- a/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp
+++ b/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp
@@ -34,7 +34,9 @@
         threadId_(threadId),
         globalsThreadQueue_(GlobalsRegistry::Instance()),
         specialRefRegistry_(SpecialRefRegistry::instance()),
+#ifndef CUSTOM_ALLOCATOR
         extraObjectDataThreadQueue_(ExtraObjectDataFactory::Instance()),
+#endif
         gc_(GlobalData::Instance().gc(), *this),
         suspensionData_(ThreadState::kNative, *this) {}
 
@@ -48,7 +50,9 @@
 
     SpecialRefRegistry::ThreadQueue& specialRefRegistry() noexcept { return specialRefRegistry_; }
 
+#ifndef CUSTOM_ALLOCATOR
     ExtraObjectDataFactory::ThreadQueue& extraObjectDataThreadQueue() noexcept { return extraObjectDataThreadQueue_; }
+#endif
 
     ThreadState state() noexcept { return suspensionData_.state(); }
 
@@ -66,14 +70,18 @@
         // TODO: These use separate locks, which is inefficient.
         globalsThreadQueue_.Publish();
         specialRefRegistry_.publish();
+#ifndef CUSTOM_ALLOCATOR
         extraObjectDataThreadQueue_.Publish();
+#endif
         gc_.Publish();
     }
 
     void ClearForTests() noexcept {
         globalsThreadQueue_.ClearForTests();
         specialRefRegistry_.clearForTests();
+#ifndef CUSTOM_ALLOCATOR
         extraObjectDataThreadQueue_.ClearForTests();
+#endif
         gc_.ClearForTests();
     }
 
@@ -82,7 +90,9 @@
     GlobalsRegistry::ThreadQueue globalsThreadQueue_;
     ThreadLocalStorage tls_;
     SpecialRefRegistry::ThreadQueue specialRefRegistry_;
+#ifndef CUSTOM_ALLOCATOR
     ExtraObjectDataFactory::ThreadQueue extraObjectDataThreadQueue_;
+#endif
     ShadowStack shadowStack_;
     gc::GC::ThreadData gc_;
     std_support::vector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;