[K/N] concurrent weak sweep ^KT-57772

This commit is contained in:
Aleksei.Glushko
2023-04-26 11:57:28 +02:00
committed by Space Team
parent 09ca335b7e
commit 96b44e0ad8
20 changed files with 393 additions and 136 deletions
@@ -39,6 +39,8 @@ object BinaryOptions : BinaryOptionRegistry() {
val gcMarkSingleThreaded by booleanOption() val gcMarkSingleThreaded by booleanOption()
val concurrentWeakSweep by booleanOption()
val linkRuntime by option<RuntimeLinkageStrategyBinaryOption>() val linkRuntime by option<RuntimeLinkageStrategyBinaryOption>()
val bundleId by stringOption() val bundleId by stringOption()
@@ -143,7 +143,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
} }
val gcMarkSingleThreaded: Boolean val gcMarkSingleThreaded: Boolean
get() = configuration.get(BinaryOptions.gcMarkSingleThreaded) == true get() = configuration.get(BinaryOptions.gcMarkSingleThreaded) ?: false
val concurrentWeakSweep: Boolean
get() = configuration.get(BinaryOptions.concurrentWeakSweep) ?: false
val irVerificationMode: IrVerificationMode val irVerificationMode: IrVerificationMode
get() = configuration.getNotNull(KonanConfigKeys.VERIFY_IR) get() = configuration.getNotNull(KonanConfigKeys.VERIFY_IR)
@@ -2998,6 +2998,7 @@ internal fun NativeGenerationState.generateRuntimeConstantsModule() : LLVMModule
setRuntimeConstGlobal("Kotlin_runtimeLogs", runtimeLogs) setRuntimeConstGlobal("Kotlin_runtimeLogs", runtimeLogs)
setRuntimeConstGlobal("Kotlin_freezingEnabled", llvm.constInt32(if (config.freezing.enableFreezeAtRuntime) 1 else 0)) setRuntimeConstGlobal("Kotlin_freezingEnabled", llvm.constInt32(if (config.freezing.enableFreezeAtRuntime) 1 else 0))
setRuntimeConstGlobal("Kotlin_freezingChecksEnabled", llvm.constInt32(if (config.freezing.enableFreezeChecks) 1 else 0)) setRuntimeConstGlobal("Kotlin_freezingChecksEnabled", llvm.constInt32(if (config.freezing.enableFreezeChecks) 1 else 0))
setRuntimeConstGlobal("Kotlin_concurrentWeakSweep", llvm.constInt32(if (context.config.concurrentWeakSweep) 1 else 0))
return llvmModule return llvmModule
} }
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "Barriers.hpp"
#include <algorithm>
#include <atomic>
#include "GCImpl.hpp"
#include "SafePoint.hpp"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
using namespace kotlin;
namespace {
std::atomic<ObjHeader* (*)(ObjHeader*)> weakRefBarrier = nullptr;
ObjHeader* weakRefBarrierImpl(ObjHeader* weakReferee) noexcept {
if (!weakReferee) return nullptr;
// When weak ref barriers are enabled, marked state cannot change and the
// object cannot be deleted.
if (!gc::isMarked(weakReferee)) {
return nullptr;
}
return weakReferee;
}
NO_INLINE ObjHeader* weakRefReadSlowPath(std::atomic<ObjHeader*>& weakReferee) noexcept {
// reread an action to avoid register pollution outside the function
auto barrier = weakRefBarrier.load(std::memory_order_seq_cst);
auto* weak = weakReferee.load(std::memory_order_relaxed);
return barrier ? barrier(weak) : weak;
}
void waitForThreadsToReachCheckpoint() {
// Reset checkpoint on all threads.
for (auto& thr : mm::ThreadRegistry::Instance().LockForIter()) {
thr.gc().impl().gc().barriers().resetCheckpoint();
}
mm::SafePointActivator safePointActivator;
// Disable new threads coming and going.
auto threads = mm::ThreadRegistry::Instance().LockForIter();
// And wait for all threads to either have passed safepoint or to be in the native state.
// Either of these mean that none of them are inside a weak reference accessing code.
while (!std::all_of(threads.begin(), threads.end(), [](mm::ThreadData& thread) noexcept {
return thread.gc().impl().gc().barriers().visitedCheckpoint() || thread.suspensionData().suspendedOrNative();
})) {
std::this_thread::yield();
}
}
} // namespace
void gc::BarriersThreadData::onCheckpoint() noexcept {
visitedCheckpoint_.store(true, std::memory_order_release);
}
void gc::BarriersThreadData::resetCheckpoint() noexcept {
visitedCheckpoint_.store(false, std::memory_order_release);
}
bool gc::BarriersThreadData::visitedCheckpoint() const noexcept {
return visitedCheckpoint_.load(std::memory_order_acquire);
}
void gc::EnableWeakRefBarriers() noexcept {
weakRefBarrier.store(weakRefBarrierImpl, std::memory_order_seq_cst);
}
void gc::DisableWeakRefBarriers() noexcept {
weakRefBarrier.store(nullptr, std::memory_order_seq_cst);
waitForThreadsToReachCheckpoint();
}
OBJ_GETTER(kotlin::gc::WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept {
// TODO: Make this work with GCs that can stop thread at any point.
if (!compiler::concurrentWeakSweep()) {
RETURN_OBJ(weakReferee.load(std::memory_order_relaxed));
}
// Copying the scheme from SafePoint.cpp: branch + indirect call.
auto barrier = weakRefBarrier.load(std::memory_order_relaxed);
ObjHeader* result;
if (__builtin_expect(barrier != nullptr, false)) {
result = weakRefReadSlowPath(weakReferee);
} else {
result = weakReferee.load(std::memory_order_relaxed);
}
RETURN_OBJ(result);
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#pragma once
#include <atomic>
#include <optional>
#include "Memory.h"
#include "Utils.hpp"
namespace kotlin::gc {
class BarriersThreadData : private Pinned {
public:
void onCheckpoint() noexcept;
void resetCheckpoint() noexcept;
bool visitedCheckpoint() const noexcept;
private:
std::atomic<bool> visitedCheckpoint_ = false;
};
// Must be called during STW.
void EnableWeakRefBarriers() noexcept;
// Must be called outside STW.
void DisableWeakRefBarriers() noexcept;
OBJ_GETTER(WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept;
} // namespace kotlin::gc
@@ -184,25 +184,38 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
auto markStats = gcHandle.getMarked(); auto markStats = gcHandle.getMarked();
scheduler.gcData().UpdateAliveSetBytes(markStats.markedSizeBytes); scheduler.gcData().UpdateAliveSetBytes(markStats.markedSizeBytes);
gc::processWeaks<ProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
// Taking the locks before the pause is completed. So that any destroying thread // Taking the locks before the pause is completed. So that any destroying thread
// would not publish into the global state at an unexpected time. // would not publish into the global state at an unexpected time.
std::optional extraObjectFactoryIterable = mm::GlobalData::Instance().extraObjectDataFactory().LockForIter(); std::optional extraObjectFactoryIterable = mm::GlobalData::Instance().extraObjectDataFactory().LockForIter();
std::optional objectFactoryIterable = objectFactory_.LockForIter(); std::optional objectFactoryIterable = objectFactory_.LockForIter();
mm::ResumeThreads(); #endif
gcHandle.threadsAreResumed();
if (compiler::concurrentWeakSweep()) {
// Expected to happen inside STW.
gc::EnableWeakRefBarriers();
mm::ResumeThreads();
gcHandle.threadsAreResumed();
}
gc::processWeaks<ProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
if (compiler::concurrentWeakSweep()) {
// Expected to happen outside STW.
gc::DisableWeakRefBarriers();
} else {
mm::ResumeThreads();
gcHandle.threadsAreResumed();
}
#ifndef CUSTOM_ALLOCATOR
gc::SweepExtraObjects<SweepTraits>(gcHandle, *extraObjectFactoryIterable); gc::SweepExtraObjects<SweepTraits>(gcHandle, *extraObjectFactoryIterable);
extraObjectFactoryIterable = std::nullopt; extraObjectFactoryIterable = std::nullopt;
auto finalizerQueue = gc::Sweep<SweepTraits>(gcHandle, *objectFactoryIterable); auto finalizerQueue = gc::Sweep<SweepTraits>(gcHandle, *objectFactoryIterable);
objectFactoryIterable = std::nullopt; objectFactoryIterable = std::nullopt;
kotlin::compactObjectPoolInMainThread(); kotlin::compactObjectPoolInMainThread();
#else #else
mm::ResumeThreads();
gcHandle.threadsAreResumed();
// also sweeps extraObjects // also sweeps extraObjects
auto finalizerQueue = heap_.Sweep(gcHandle); auto finalizerQueue = heap_.Sweep(gcHandle);
#endif #endif
@@ -9,6 +9,7 @@
#include <cstddef> #include <cstddef>
#include "Allocator.hpp" #include "Allocator.hpp"
#include "Barriers.hpp"
#include "FinalizerProcessor.hpp" #include "FinalizerProcessor.hpp"
#include "GCScheduler.hpp" #include "GCScheduler.hpp"
#include "GCState.hpp" #include "GCState.hpp"
@@ -91,14 +92,19 @@ public:
void OnSuspendForGC() noexcept; void OnSuspendForGC() noexcept;
void safePoint() noexcept { barriers_.onCheckpoint(); }
Allocator CreateAllocator() noexcept { return Allocator(gc::Allocator(), *this); } Allocator CreateAllocator() noexcept { return Allocator(gc::Allocator(), *this); }
BarriersThreadData& barriers() noexcept { return barriers_; }
private: private:
friend ConcurrentMarkAndSweep; friend ConcurrentMarkAndSweep;
ConcurrentMarkAndSweep& gc_; ConcurrentMarkAndSweep& gc_;
mm::ThreadData& threadData_; mm::ThreadData& threadData_;
gcScheduler::GCSchedulerThreadData& gcScheduler_; gcScheduler::GCSchedulerThreadData& gcScheduler_;
std::atomic<bool> marking_; std::atomic<bool> marking_;
BarriersThreadData barriers_;
}; };
using Allocator = ThreadData::Allocator; using Allocator = ThreadData::Allocator;
@@ -130,7 +136,8 @@ public:
alloc::Heap& heap() noexcept { return heap_; } alloc::Heap& heap() noexcept { return heap_; }
#endif #endif
void Schedule() noexcept { state_.schedule(); } int64_t Schedule() noexcept { return state_.schedule(); }
void WaitFinalized(int64_t epoch) noexcept { state_.waitEpochFinalized(epoch); }
private: private:
void PerformFullGC(int64_t epoch) noexcept; void PerformFullGC(int64_t epoch) noexcept;
@@ -728,17 +728,19 @@ TEST_P(ConcurrentMarkAndSweepTest, MultipleMutatorsCollect) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); std::atomic<bool> gcDone = false;
for (auto& mutator : mutators) {
// Spin until thread suspension is requested. gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
while (!mm::IsThreadSuspensionRequested()) { while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
}));
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { mm::GlobalData::Instance().gc().ScheduleAndWaitFullGCWithFinalizers();
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { mm::safePoint(threadData); }); gcDone.store(true, std::memory_order_relaxed);
}
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
@@ -783,15 +785,15 @@ TEST_P(ConcurrentMarkAndSweepTest, MultipleMutatorsAllCollect) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
for (int i = 0; i < kDefaultThreadCount; ++i) { for (auto& mutator : mutators) {
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { gcFutures.emplace_back(mutator.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
// If GC starts before all thread executed line above, two gc will be run // If GC starts before all thread executed line above, two gc will be run
// So we are temporary switch threads to native state and then return them back after all GC runs are done // So we temporary switch threads to native state and then return them back after all GC runs are done
SwitchThreadState(mm::GetMemoryState(), kotlin::ThreadState::kNative); SwitchThreadState(mm::GetMemoryState(), kotlin::ThreadState::kNative);
}); }));
} }
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
@@ -846,34 +848,33 @@ TEST_P(ConcurrentMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRe
mutator.AddStackRoot(locals[i]); mutator.AddStackRoot(locals[i]);
}; };
mutators[0]
.Execute([expandRootSet, allocateInHeap](mm::ThreadData& threadData, Mutator& mutator) {
allocateInHeap(threadData, mutator, 0);
expandRootSet(threadData, mutator, 0);
})
.wait();
// Allocate everything in heap before scheduling the GC. // Allocate everything in heap before scheduling the GC.
for (int i = 1; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators[i] mutators[i]
.Execute([allocateInHeap, i](mm::ThreadData& threadData, Mutator& mutator) { allocateInHeap(threadData, mutator, i); }) .Execute([allocateInHeap, i](mm::ThreadData& threadData, Mutator& mutator) { allocateInHeap(threadData, mutator, i); })
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); auto epoch = mm::GlobalData::Instance().gc().Schedule();
std::atomic<bool> gcDone = false;
// Spin until thread suspension is requested. // Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) { while (!mm::IsThreadSuspensionRequested()) {
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
gcFutures[i] = mutators[i].Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { gcFutures.emplace_back(mutators[i].Execute([&gcDone, i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) {
expandRootSet(threadData, mutator, i); expandRootSet(threadData, mutator, i);
mm::safePoint(threadData); while (!gcDone.load(std::memory_order_relaxed)) {
}); mm::safePoint(threadData);
}
}));
} }
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
} }
@@ -924,17 +925,19 @@ TEST_P(ConcurrentMarkAndSweepTest, CrossThreadReference) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); std::atomic<bool> gcDone = false;
for (auto& mutator : mutators) {
// Spin until thread suspension is requested. gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
while (!mm::IsThreadSuspensionRequested()) { while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
}));
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { mm::GlobalData::Instance().gc().ScheduleAndWaitFullGCWithFinalizers();
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { mm::safePoint(threadData); }); gcDone.store(true, std::memory_order_relaxed);
}
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
@@ -984,24 +987,26 @@ TEST_P(ConcurrentMarkAndSweepTest, MultipleMutatorsWeaks) {
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait(); mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
auto epoch = mm::GlobalData::Instance().gc().Schedule();
gcFutures[0] = mutators[0].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) { std::atomic<bool> gcDone = false;
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
EXPECT_THAT(weak->get(), nullptr);
});
// Spin until thread suspension is requested. // Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) { while (!mm::IsThreadSuspensionRequested()) {
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { for (auto& mutator : mutators) {
gcFutures[i] = mutators[i].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) { gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
mm::safePoint(threadData); while (!gcDone.load(std::memory_order_relaxed)) {
EXPECT_THAT(weak->get(), nullptr); mm::safePoint(threadData);
}); EXPECT_THAT(weak->get(), nullptr);
}
}));
} }
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
} }
@@ -1036,9 +1041,9 @@ TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
auto epoch = mm::GlobalData::Instance().gc().Schedule();
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); std::atomic<bool> gcDone = false;
// Spin until thread suspension is requested. // Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) { while (!mm::IsThreadSuspensionRequested()) {
@@ -1049,14 +1054,27 @@ TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
std_support::vector<std::future<void>> attachFutures(kDefaultThreadCount); std_support::vector<std::future<void>> attachFutures(kDefaultThreadCount);
for (int i = 0; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
attachFutures[i] = newMutators[i].Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i + kDefaultThreadCount); }); attachFutures[i] = newMutators[i].Execute([&gcDone, i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) {
expandRootSet(threadData, mutator, i + kDefaultThreadCount);
while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
});
} }
// All the other threads are stopping at safe points. // All the other threads are stopping at safe points.
for (int i = 1; i < kDefaultThreadCount; ++i) { for (auto& mutator : mutators) {
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { mm::safePoint(threadData); }); gcFutures.emplace_back(mutator.Execute([&gcDone](mm::ThreadData& threadData, Mutator& mutator) {
while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
}));
} }
// Wait for the GC to be done.
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
// GC will be completed first // GC will be completed first
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
@@ -8,6 +8,7 @@
#include "GC.hpp" #include "GC.hpp"
#include "GCStatistics.hpp" #include "GCStatistics.hpp"
#include "MarkAndSweepUtils.hpp" #include "MarkAndSweepUtils.hpp"
#include "ObjectOps.hpp"
#include "ThreadSuspension.hpp" #include "ThreadSuspension.hpp"
#include "std_support/Memory.hpp" #include "std_support/Memory.hpp"
@@ -62,6 +63,10 @@ void gc::GC::ThreadData::OnSuspendForGC() noexcept {
impl_->gc().OnSuspendForGC(); impl_->gc().OnSuspendForGC();
} }
void gc::GC::ThreadData::safePoint() noexcept {
impl_->gc().safePoint();
}
gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {} gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {}
gc::GC::~GC() = default; gc::GC::~GC() = default;
@@ -114,6 +119,19 @@ ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noe
gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field); gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field);
} }
void gc::GC::Schedule() noexcept { int64_t gc::GC::Schedule() noexcept {
impl_->gc().Schedule(); return impl_->gc().Schedule();
}
void gc::GC::WaitFinalizers(int64_t epoch) noexcept {
impl_->gc().WaitFinalized(epoch);
}
bool gc::isMarked(ObjHeader* object) noexcept {
auto& objectData = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>::NodeRef::From(object).ObjectData();
return objectData.marked();
}
ALWAYS_INLINE OBJ_GETTER(gc::tryRef, std::atomic<ObjHeader*>& object) noexcept {
RETURN_RESULT_OF(gc::WeakRefRead, object);
} }
+11 -2
View File
@@ -5,6 +5,8 @@
#pragma once #pragma once
#include <atomic>
#include "GCScheduler.hpp" #include "GCScheduler.hpp"
#include "Memory.h" #include "Memory.h"
#include "Types.h" #include "Types.h"
@@ -44,6 +46,8 @@ public:
void OnSuspendForGC() noexcept; void OnSuspendForGC() noexcept;
void safePoint() noexcept;
private: private:
std_support::unique_ptr<Impl> impl_; std_support::unique_ptr<Impl> impl_;
}; };
@@ -67,13 +71,18 @@ public:
static void processArrayInMark(void* state, ArrayHeader* array) noexcept; static void processArrayInMark(void* state, ArrayHeader* array) noexcept;
static void processFieldInMark(void* state, ObjHeader* field) noexcept; static void processFieldInMark(void* state, ObjHeader* field) noexcept;
// TODO: This should be moved into the scheduler. // TODO: These should be moved into the scheduler.
void Schedule() noexcept; int64_t Schedule() noexcept;
void WaitFinalizers(int64_t epoch) noexcept;
void ScheduleAndWaitFullGCWithFinalizers() noexcept { WaitFinalizers(Schedule()); }
private: private:
std_support::unique_ptr<Impl> impl_; std_support::unique_ptr<Impl> impl_;
}; };
bool isMarked(ObjHeader* object) noexcept;
OBJ_GETTER(tryRef, std::atomic<ObjHeader*>& object) noexcept;
inline constexpr bool kSupportsMultipleMutators = true; inline constexpr bool kSupportsMultipleMutators = true;
} // namespace gc } // namespace gc
@@ -220,7 +220,7 @@ template <typename Traits>
void processWeaks(GCHandle gcHandle, mm::SpecialRefRegistry& registry) noexcept { void processWeaks(GCHandle gcHandle, mm::SpecialRefRegistry& registry) noexcept {
auto handle = gcHandle.processWeaks(); auto handle = gcHandle.processWeaks();
for (auto& object : registry.lockForIter()) { for (auto& object : registry.lockForIter()) {
auto* obj = object; auto* obj = object.load(std::memory_order_relaxed);
if (!obj) { if (!obj) {
// We already processed it at some point. // We already processed it at some point.
handle.addUndisposed(); handle.addUndisposed();
@@ -233,7 +233,7 @@ void processWeaks(GCHandle gcHandle, mm::SpecialRefRegistry& registry) noexcept
continue; continue;
} }
// Object is not alive. Clear it out. // Object is not alive. Clear it out.
object = nullptr; object.store(nullptr, std::memory_order_relaxed);
handle.addNulled(); handle.addNulled();
} }
} }
@@ -9,6 +9,7 @@
#include "std_support/Memory.hpp" #include "std_support/Memory.hpp"
#include "GlobalData.hpp" #include "GlobalData.hpp"
#include "GCStatistics.hpp" #include "GCStatistics.hpp"
#include "ObjectOps.hpp"
using namespace kotlin; using namespace kotlin;
@@ -47,6 +48,8 @@ ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeI
void gc::GC::ThreadData::OnSuspendForGC() noexcept { } void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
void gc::GC::ThreadData::safePoint() noexcept {}
gc::GC::GC(gcScheduler::GCScheduler&) noexcept : impl_(std_support::make_unique<Impl>()) {} gc::GC::GC(gcScheduler::GCScheduler&) noexcept : impl_(std_support::make_unique<Impl>()) {}
gc::GC::~GC() = default; gc::GC::~GC() = default;
@@ -82,4 +85,16 @@ ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) n
// static // static
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {} ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {}
void gc::GC::Schedule() noexcept {} int64_t gc::GC::Schedule() noexcept {
return 0;
}
void gc::GC::WaitFinalizers(int64_t epoch) noexcept {}
bool gc::isMarked(ObjHeader* object) noexcept {
RuntimeAssert(false, "Should not reach here");
return true;
}
ALWAYS_INLINE OBJ_GETTER(gc::tryRef, std::atomic<ObjHeader*>& object) noexcept {
RETURN_OBJ(object.load(std::memory_order_relaxed));
}
@@ -10,6 +10,7 @@
#include "std_support/Memory.hpp" #include "std_support/Memory.hpp"
#include "GlobalData.hpp" #include "GlobalData.hpp"
#include "GCStatistics.hpp" #include "GCStatistics.hpp"
#include "ObjectOps.hpp"
using namespace kotlin; using namespace kotlin;
@@ -48,6 +49,8 @@ ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeI
void gc::GC::ThreadData::OnSuspendForGC() noexcept { } void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
void gc::GC::ThreadData::safePoint() noexcept {}
gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {} gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {}
gc::GC::~GC() = default; gc::GC::~GC() = default;
@@ -94,6 +97,19 @@ ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noe
gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field); gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field);
} }
void gc::GC::Schedule() noexcept { int64_t gc::GC::Schedule() noexcept {
impl_->gc().Schedule(); return impl_->gc().Schedule();
}
void gc::GC::WaitFinalizers(int64_t epoch) noexcept {
impl_->gc().WaitFinalized(epoch);
}
bool gc::isMarked(ObjHeader* object) noexcept {
auto& objectData = mm::ObjectFactory<gc::SameThreadMarkAndSweep>::NodeRef::From(object).ObjectData();
return objectData.marked();
}
ALWAYS_INLINE OBJ_GETTER(gc::tryRef, std::atomic<ObjHeader*>& object) noexcept {
RETURN_OBJ(object.load(std::memory_order_relaxed));
} }
@@ -102,7 +102,8 @@ public:
void StopFinalizerThreadIfRunning() noexcept; void StopFinalizerThreadIfRunning() noexcept;
bool FinalizersThreadIsRunning() noexcept; bool FinalizersThreadIsRunning() noexcept;
void Schedule() noexcept { state_.schedule(); } int64_t Schedule() noexcept { return state_.schedule(); }
void WaitFinalized(int64_t epoch) noexcept { state_.waitEpochFinalized(epoch); }
private: private:
void PerformFullGC(int64_t epoch) noexcept; void PerformFullGC(int64_t epoch) noexcept;
@@ -724,18 +724,19 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsCollect) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
gcFutures[0] = mutators[0].Execute( std::atomic<bool> gcDone = false;
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); for (auto& mutator : mutators) {
gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
// Spin until thread suspension is requested. while (!gcDone.load(std::memory_order_relaxed)) {
while (!mm::IsThreadSuspensionRequested()) { mm::safePoint(threadData);
}
}));
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { mm::GlobalData::Instance().gc().ScheduleAndWaitFullGCWithFinalizers();
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { mm::safePoint(threadData); }); gcDone.store(true, std::memory_order_relaxed);
}
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
@@ -780,15 +781,15 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsAllCollect) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
for (int i = 0; i < kDefaultThreadCount; ++i) { for (auto& mutator : mutators) {
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { gcFutures.emplace_back(mutator.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
// If GC starts before all thread executed line above, two gc will be run // If GC starts before all thread executed line above, two gc will be run
// So we are temporary switch threads to native state and then return them back after all GC runs are done // So we temporary switch threads to native state and then return them back after all GC runs are done
SwitchThreadState(mm::GetMemoryState(), kotlin::ThreadState::kNative); SwitchThreadState(mm::GetMemoryState(), kotlin::ThreadState::kNative);
}); }));
} }
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
@@ -843,35 +844,33 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRe
mutator.AddStackRoot(locals[i]); mutator.AddStackRoot(locals[i]);
}; };
mutators[0]
.Execute([expandRootSet, allocateInHeap](mm::ThreadData& threadData, Mutator& mutator) {
allocateInHeap(threadData, mutator, 0);
expandRootSet(threadData, mutator, 0);
})
.wait();
// Allocate everything in heap before scheduling the GC. // Allocate everything in heap before scheduling the GC.
for (int i = 1; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators[i] mutators[i]
.Execute([allocateInHeap, i](mm::ThreadData& threadData, Mutator& mutator) { allocateInHeap(threadData, mutator, i); }) .Execute([allocateInHeap, i](mm::ThreadData& threadData, Mutator& mutator) { allocateInHeap(threadData, mutator, i); })
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
gcFutures[0] = mutators[0].Execute( auto epoch = mm::GlobalData::Instance().gc().Schedule();
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); std::atomic<bool> gcDone = false;
// Spin until thread suspension is requested. // Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) { while (!mm::IsThreadSuspensionRequested()) {
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
gcFutures[i] = mutators[i].Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { gcFutures.emplace_back(mutators[i].Execute([&gcDone, i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) {
expandRootSet(threadData, mutator, i); expandRootSet(threadData, mutator, i);
mm::safePoint(threadData); while (!gcDone.load(std::memory_order_relaxed)) {
}); mm::safePoint(threadData);
}
}));
} }
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
} }
@@ -922,18 +921,19 @@ TEST_F(SameThreadMarkAndSweepTest, CrossThreadReference) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
gcFutures[0] = mutators[0].Execute( std::atomic<bool> gcDone = false;
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); }); for (auto& mutator : mutators) {
gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
// Spin until thread suspension is requested. while (!gcDone.load(std::memory_order_relaxed)) {
while (!mm::IsThreadSuspensionRequested()) { mm::safePoint(threadData);
}
}));
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { mm::GlobalData::Instance().gc().ScheduleAndWaitFullGCWithFinalizers();
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { mm::safePoint(threadData); }); gcDone.store(true, std::memory_order_relaxed);
}
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
@@ -983,24 +983,26 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsWeaks) {
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait(); mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
auto epoch = mm::GlobalData::Instance().gc().Schedule();
gcFutures[0] = mutators[0].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) { std::atomic<bool> gcDone = false;
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
EXPECT_THAT(weak->get(), nullptr);
});
// Spin until thread suspension is requested. // Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) { while (!mm::IsThreadSuspensionRequested()) {
} }
for (int i = 1; i < kDefaultThreadCount; ++i) { for (auto& mutator : mutators) {
gcFutures[i] = mutators[i].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) { gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
mm::safePoint(threadData); while (!gcDone.load(std::memory_order_relaxed)) {
EXPECT_THAT(weak->get(), nullptr); mm::safePoint(threadData);
}); EXPECT_THAT(weak->get(), nullptr);
}
}));
} }
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
} }
@@ -1035,10 +1037,9 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
.wait(); .wait();
} }
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount); std_support::vector<std::future<void>> gcFutures;
auto epoch = mm::GlobalData::Instance().gc().Schedule();
gcFutures[0] = mutators[0].Execute( std::atomic<bool> gcDone = false;
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
// Spin until thread suspension is requested. // Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) { while (!mm::IsThreadSuspensionRequested()) {
@@ -1049,14 +1050,27 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
std_support::vector<std::future<void>> attachFutures(kDefaultThreadCount); std_support::vector<std::future<void>> attachFutures(kDefaultThreadCount);
for (int i = 0; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
attachFutures[i] = newMutators[i].Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i + kDefaultThreadCount); }); attachFutures[i] = newMutators[i].Execute([&gcDone, i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) {
expandRootSet(threadData, mutator, i + kDefaultThreadCount);
while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
});
} }
// All the other threads are stopping at safe points. // All the other threads are stopping at safe points.
for (int i = 1; i < kDefaultThreadCount; ++i) { for (auto& mutator : mutators) {
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { mm::safePoint(threadData); }); gcFutures.emplace_back(mutator.Execute([&gcDone](mm::ThreadData& threadData, Mutator& mutator) {
while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
}));
} }
// Wait for the GC to be done.
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
// GC will be completed first // GC will be completed first
for (auto& future : gcFutures) { for (auto& future : gcFutures) {
future.wait(); future.wait();
@@ -37,6 +37,7 @@ extern "C" const int32_t Kotlin_needDebugInfo;
extern "C" const int32_t Kotlin_runtimeAssertsMode; extern "C" const int32_t Kotlin_runtimeAssertsMode;
extern "C" const int32_t Kotlin_disableMmap; extern "C" const int32_t Kotlin_disableMmap;
extern "C" const char* const Kotlin_runtimeLogs; extern "C" const char* const Kotlin_runtimeLogs;
extern "C" const int32_t Kotlin_concurrentWeakSweep;
extern "C" const int32_t Kotlin_freezingEnabled; extern "C" const int32_t Kotlin_freezingEnabled;
extern "C" const int32_t Kotlin_freezingChecksEnabled; extern "C" const int32_t Kotlin_freezingChecksEnabled;
@@ -98,6 +99,10 @@ ALWAYS_INLINE inline bool freezingChecksEnabled() noexcept {
return Kotlin_freezingChecksEnabled != 0; return Kotlin_freezingChecksEnabled != 0;
} }
ALWAYS_INLINE inline bool concurrentWeakSweep() noexcept {
return Kotlin_concurrentWeakSweep != 0;
}
WorkerExceptionHandling workerExceptionHandling() noexcept; WorkerExceptionHandling workerExceptionHandling() noexcept;
DestroyRuntimeMode destroyRuntimeMode() noexcept; DestroyRuntimeMode destroyRuntimeMode() noexcept;
bool gcMarkSingleThreaded() noexcept; bool gcMarkSingleThreaded() noexcept;
@@ -26,6 +26,7 @@ void safePointActionImpl(mm::ThreadData& threadData) noexcept {
AutoReset guard(&recursion, true); AutoReset guard(&recursion, true);
mm::GlobalData::Instance().gcScheduler().safePoint(); mm::GlobalData::Instance().gcScheduler().safePoint();
threadData.gc().safePoint();
threadData.suspensionData().suspendIfRequested(); threadData.suspensionData().suspendIfRequested();
} }
@@ -67,7 +67,7 @@ mm::SpecialRefRegistry::Node* mm::SpecialRefRegistry::nextRoot(Node* current) no
auto [candidatePrev, candidateNext] = eraseFromRoots(current, candidate); auto [candidatePrev, candidateNext] = eraseFromRoots(current, candidate);
// We removed candidate. But should we have? // We removed candidate. But should we have?
if (candidate->rc_.load(std::memory_order_relaxed) > 0) { if (candidate->rc_.load(std::memory_order_relaxed) > 0) {
RuntimeAssert(candidate->obj_ != nullptr, "candidate cannot have a null obj_"); RuntimeAssert(candidate->obj_.load(std::memory_order_relaxed) != nullptr, "candidate cannot have a null obj_");
// Ooops. Let's put it back. Okay to put into the head. // Ooops. Let's put it back. Okay to put into the head.
insertIntoRootsHead(*candidate); insertIntoRootsHead(*candidate);
} }
@@ -7,6 +7,7 @@
#include <atomic> #include <atomic>
#include "GC.hpp"
#include "Memory.h" #include "Memory.h"
#include "RawPtr.hpp" #include "RawPtr.hpp"
#include "ThreadRegistry.hpp" #include "ThreadRegistry.hpp"
@@ -77,13 +78,14 @@ class SpecialRefRegistry : private Pinned {
auto rc = rc_.exchange(disposedMarker, std::memory_order_release); auto rc = rc_.exchange(disposedMarker, std::memory_order_release);
if (compiler::runtimeAssertsEnabled()) { if (compiler::runtimeAssertsEnabled()) {
if (rc > 0) { if (rc > 0) {
auto* obj = obj_.load(std::memory_order_relaxed);
// In objc export if ObjCClass extends from KtClass // In objc export if ObjCClass extends from KtClass
// doing retain+autorelease inside [ObjCClass dealloc] will cause // doing retain+autorelease inside [ObjCClass dealloc] will cause
// this->dispose() be called after this->retain() but before // this->dispose() be called after this->retain() but before
// subsequent this->release(). // subsequent this->release().
// However, since this happens in dealloc, the stored object must // However, since this happens in dealloc, the stored object must
// have been cleared already. // have been cleared already.
RuntimeAssert(obj_ == nullptr, "Disposing StableRef@%p with rc %d and uncleaned object %p", this, rc, obj_); RuntimeAssert(obj == nullptr, "Disposing StableRef@%p with rc %d and uncleaned object %p", this, rc, obj);
} }
RuntimeAssert(rc >= 0, "Disposing StableRef@%p with rc %d", this, rc); RuntimeAssert(rc >= 0, "Disposing StableRef@%p with rc %d", this, rc);
} }
@@ -95,13 +97,12 @@ class SpecialRefRegistry : private Pinned {
auto rc = rc_.load(std::memory_order_relaxed); auto rc = rc_.load(std::memory_order_relaxed);
RuntimeAssert(rc >= 0, "Dereferencing StableRef@%p with rc %d", this, rc); RuntimeAssert(rc >= 0, "Dereferencing StableRef@%p with rc %d", this, rc);
} }
return obj_; return obj_.load(std::memory_order_relaxed);
} }
OBJ_GETTER0(tryRef) noexcept { OBJ_GETTER0(tryRef) noexcept {
AssertThreadState(ThreadState::kRunnable); AssertThreadState(ThreadState::kRunnable);
// TODO: Weak read barrier with CMS. RETURN_RESULT_OF(kotlin::gc::tryRef, obj_);
RETURN_OBJ(obj_);
} }
void retainRef() noexcept { void retainRef() noexcept {
@@ -112,7 +113,7 @@ class SpecialRefRegistry : private Pinned {
position_ == std_support::list<Node>::iterator{}, position_ == std_support::list<Node>::iterator{},
"Retaining StableRef@%p with fast deletion optimization is disallowed", this); "Retaining StableRef@%p with fast deletion optimization is disallowed", this);
if (!obj_) { if (!obj_.load(std::memory_order_relaxed)) {
// In objc export if ObjCClass extends from KtClass // In objc export if ObjCClass extends from KtClass
// calling retain inside [ObjCClass dealloc] will cause // calling retain inside [ObjCClass dealloc] will cause
// node.retainRef() be called after node.obj_ was cleared but // node.retainRef() be called after node.obj_ was cleared but
@@ -161,7 +162,8 @@ class SpecialRefRegistry : private Pinned {
// be nulled, and disable the barriers when the phase is completed. // be nulled, and disable the barriers when the phase is completed.
// Synchronization between GC and mutators happens via enabling/disabling // Synchronization between GC and mutators happens via enabling/disabling
// the barriers. // the barriers.
ObjHeader* obj_ = nullptr; // TODO: Try to handle it atomically only when the GC is in progress.
std::atomic<ObjHeader*> obj_ = nullptr;
// Only ever updated using relaxed memory ordering. Any synchronization // Only ever updated using relaxed memory ordering. Any synchronization
// with nextRoot_ is achieved via acquire-release of nextRoot_. // with nextRoot_ is achieved via acquire-release of nextRoot_.
std::atomic<Rc> rc_ = 0; // After dispose() will be disposedMarker. std::atomic<Rc> rc_ = 0; // After dispose() will be disposedMarker.
@@ -244,7 +246,7 @@ public:
ObjHeader* operator*() const noexcept { ObjHeader* operator*() const noexcept {
// Ignoring rc here. If someone nulls out rc during root // Ignoring rc here. If someone nulls out rc during root
// scanning, it's okay to be conservative and still make it a root. // scanning, it's okay to be conservative and still make it a root.
return node_->obj_; return node_->obj_.load(std::memory_order_relaxed);
} }
RootsIterator& operator++() noexcept { RootsIterator& operator++() noexcept {
@@ -281,7 +283,7 @@ public:
class Iterator { class Iterator {
public: public:
ObjHeader*& operator*() noexcept { return iterator_->obj_; } std::atomic<ObjHeader*>& operator*() noexcept { return iterator_->obj_; }
Iterator& operator++() noexcept { Iterator& operator++() noexcept {
iterator_ = owner_->findAliveNode(std::next(iterator_)); iterator_ = owner_->findAliveNode(std::next(iterator_));
@@ -74,6 +74,7 @@ extern const int32_t Kotlin_disableMmap = 1;
extern const int32_t Kotlin_disableMmap = 0; extern const int32_t Kotlin_disableMmap = 0;
#endif #endif
extern const char* const Kotlin_runtimeLogs = nullptr; extern const char* const Kotlin_runtimeLogs = nullptr;
extern const int32_t Kotlin_concurrentWeakSweep = 1;
extern const int32_t Kotlin_freezingChecksEnabled = 1; extern const int32_t Kotlin_freezingChecksEnabled = 1;
extern const int32_t Kotlin_freezingEnabled = 1; extern const int32_t Kotlin_freezingEnabled = 1;