[K/N] Refactor GC logging and statistics
Same code is now used for GC logging in all gc versions. ^KT-53064
This commit is contained in:
committed by
Space Team
parent
c4e2901a1d
commit
0959255379
@@ -20,6 +20,7 @@
|
|||||||
#include "ThreadSuspension.hpp"
|
#include "ThreadSuspension.hpp"
|
||||||
#include "GCState.hpp"
|
#include "GCState.hpp"
|
||||||
#include "FinalizerProcessor.hpp"
|
#include "FinalizerProcessor.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ namespace {
|
|||||||
[[clang::no_destroy]] std::mutex markingMutex;
|
[[clang::no_destroy]] std::mutex markingMutex;
|
||||||
[[clang::no_destroy]] std::condition_variable markingCondVar;
|
[[clang::no_destroy]] std::condition_variable markingCondVar;
|
||||||
[[clang::no_destroy]] std::atomic<bool> markingRequested = false;
|
[[clang::no_destroy]] std::atomic<bool> markingRequested = false;
|
||||||
|
[[clang::no_destroy]] std::atomic<uint64_t> markingEpoch = 0;
|
||||||
|
|
||||||
struct SweepTraits {
|
struct SweepTraits {
|
||||||
using ObjectFactory = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>;
|
using ObjectFactory = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>;
|
||||||
@@ -72,25 +74,28 @@ void gc::ConcurrentMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
|||||||
|
|
||||||
NO_EXTERNAL_CALLS_CHECK void gc::ConcurrentMarkAndSweep::ThreadData::OnSuspendForGC() noexcept {
|
NO_EXTERNAL_CALLS_CHECK void gc::ConcurrentMarkAndSweep::ThreadData::OnSuspendForGC() noexcept {
|
||||||
std::unique_lock lock(markingMutex);
|
std::unique_lock lock(markingMutex);
|
||||||
if (!markingRequested.load())
|
if (!markingRequested.load()) return;
|
||||||
return;
|
|
||||||
AutoReset scopedAssignMarking(&marking_, true);
|
AutoReset scopedAssignMarking(&marking_, true);
|
||||||
threadData_.Publish();
|
threadData_.Publish();
|
||||||
markingCondVar.wait(lock, []() { return !markingRequested.load(); });
|
markingCondVar.wait(lock, []() { return !markingRequested.load(); });
|
||||||
// // Unlock while marking to allow mutliple threads to mark in parallel.
|
// // Unlock while marking to allow mutliple threads to mark in parallel.
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
RuntimeLogDebug({kTagGC}, "Parallel marking in thread %d", konan::currentThreadId());
|
uint64_t epoch = markingEpoch.load();
|
||||||
|
GCLogDebug(epoch, "Parallel marking in thread %d", konan::currentThreadId());
|
||||||
MarkQueue markQueue;
|
MarkQueue markQueue;
|
||||||
gc::collectRootSetForThread<internal::MarkTraits>(markQueue, threadData_);
|
auto handle = GCHandle::getByEpoch(epoch);
|
||||||
MarkStats stats = gc::Mark<internal::MarkTraits>(markQueue);
|
gc::collectRootSetForThread<internal::MarkTraits>(handle, markQueue, threadData_);
|
||||||
gc_.MergeMarkStats(stats);
|
gc::Mark<internal::MarkTraits>(handle, markQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(
|
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(
|
||||||
mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept :
|
mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept :
|
||||||
objectFactory_(objectFactory),
|
objectFactory_(objectFactory),
|
||||||
gcScheduler_(gcScheduler),
|
gcScheduler_(gcScheduler),
|
||||||
finalizerProcessor_(std_support::make_unique<FinalizerProcessor>([this](int64_t epoch) { state_.finalized(epoch); })) {
|
finalizerProcessor_(std_support::make_unique<FinalizerProcessor>([this](int64_t epoch) {
|
||||||
|
state_.finalized(epoch);
|
||||||
|
GCHandle::getByEpoch(epoch).finalizersDone();
|
||||||
|
})) {
|
||||||
gcScheduler_.SetScheduleGC([this]() NO_INLINE {
|
gcScheduler_.SetScheduleGC([this]() NO_INLINE {
|
||||||
RuntimeLogDebug({kTagGC}, "Scheduling GC by thread %d", konan::currentThreadId());
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by thread %d", konan::currentThreadId());
|
||||||
// This call acquires a lock, so we need to ensure that we're in the safe state.
|
// This call acquires a lock, so we need to ensure that we're in the safe state.
|
||||||
@@ -135,76 +140,46 @@ void gc::ConcurrentMarkAndSweep::SetMarkingBehaviorForTests(MarkingBehavior mark
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
bool gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
||||||
RuntimeLogDebug({kTagGC}, "Attempt to suspend threads by thread %d", konan::currentThreadId());
|
auto gcHandle = GCHandle::create(epoch);
|
||||||
SetMarkingRequested();
|
SetMarkingRequested(epoch);
|
||||||
auto timeStartUs = konan::getTimeMicros();
|
|
||||||
bool didSuspend = mm::RequestThreadsSuspension();
|
bool didSuspend = mm::RequestThreadsSuspension();
|
||||||
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
|
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
|
||||||
RuntimeLogDebug({kTagGC}, "Requested thread suspension by thread %d", konan::currentThreadId());
|
gcHandle.suspensionRequested();
|
||||||
|
|
||||||
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "Concurrent GC must run on unregistered thread");
|
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "Concurrent GC must run on unregistered thread");
|
||||||
WaitForThreadsReadyToMark();
|
WaitForThreadsReadyToMark();
|
||||||
auto timeSuspendUs = konan::getTimeMicros();
|
gcHandle.threadsAreSuspended();
|
||||||
RuntimeLogDebug({kTagGC}, "Suspended all threads in %" PRIu64 " microseconds", timeSuspendUs - timeStartUs);
|
|
||||||
lastGCMarkStats_ = MarkStats();
|
|
||||||
|
|
||||||
auto& scheduler = gcScheduler_;
|
auto& scheduler = gcScheduler_;
|
||||||
scheduler.gcData().OnPerformFullGC();
|
scheduler.gcData().OnPerformFullGC();
|
||||||
|
|
||||||
state_.start(epoch);
|
state_.start(epoch);
|
||||||
RuntimeLogInfo(
|
|
||||||
{kTagGC}, "Started GC epoch %" PRId64 ". Time since last GC %" PRIu64 " microseconds", epoch, timeStartUs - lastGCTimestampUs_);
|
|
||||||
|
|
||||||
CollectRootSetAndStartMarking();
|
CollectRootSetAndStartMarking(gcHandle);
|
||||||
|
|
||||||
// Can be unsafe, because we've stopped the world.
|
// Can be unsafe, because we've stopped the world.
|
||||||
auto objectsCountBefore = objectFactory_.GetSizeUnsafe();
|
gc::Mark<internal::MarkTraits>(gcHandle, markQueue_);
|
||||||
|
|
||||||
auto markStats = gc::Mark<internal::MarkTraits>(markQueue_);
|
|
||||||
MergeMarkStats(markStats);
|
|
||||||
|
|
||||||
RuntimeLogDebug({kTagGC}, "Waiting for marking in threads");
|
|
||||||
mm::WaitForThreadsSuspension();
|
mm::WaitForThreadsSuspension();
|
||||||
auto timeMarkingUs = konan::getTimeMicros();
|
mm::ExtraObjectDataFactory& extraObjectDataFactory = mm::GlobalData::Instance().extraObjectDataFactory();
|
||||||
RuntimeLogInfo({kTagGC}, "Collected root set of size %zu and marked %zu objects in all threads in %" PRIu64 " microseconds", lastGCMarkStats_.rootSetSize, lastGCMarkStats_.aliveHeapSet, timeMarkingUs - timeSuspendUs);
|
auto markStats = gcHandle.getMarked();
|
||||||
|
scheduler.gcData().UpdateAliveSetBytes(markStats.totalObjectsSize);
|
||||||
|
|
||||||
scheduler.gcData().UpdateAliveSetBytes(lastGCMarkStats_.aliveHeapSetBytes);
|
gc::SweepExtraObjects<SweepTraits>(gcHandle, extraObjectDataFactory);
|
||||||
|
|
||||||
gc::SweepExtraObjects<SweepTraits>(mm::GlobalData::Instance().extraObjectDataFactory());
|
|
||||||
auto timeSweepExtraObjectsUs = konan::getTimeMicros();
|
|
||||||
RuntimeLogDebug({kTagGC}, "Sweeped extra objects in %" PRIu64 " microseconds", timeSweepExtraObjectsUs - timeMarkingUs);
|
|
||||||
|
|
||||||
auto objectFactoryIterable = objectFactory_.LockForIter();
|
auto objectFactoryIterable = objectFactory_.LockForIter();
|
||||||
|
|
||||||
mm::ResumeThreads();
|
mm::ResumeThreads();
|
||||||
auto timeResumeUs = konan::getTimeMicros();
|
gcHandle.threadsAreResumed();
|
||||||
|
|
||||||
RuntimeLogInfo(
|
auto finalizerQueue = gc::Sweep<SweepTraits>(gcHandle, objectFactoryIterable);
|
||||||
{kTagGC}, "Resumed threads in %" PRIu64 " microseconds. Total pause for most threads is %" PRIu64 " microseconds",
|
|
||||||
timeResumeUs - timeSweepExtraObjectsUs, timeResumeUs - timeStartUs);
|
|
||||||
|
|
||||||
auto finalizerQueue = gc::Sweep<SweepTraits>(objectFactoryIterable);
|
|
||||||
auto timeSweepUs = konan::getTimeMicros();
|
|
||||||
RuntimeLogDebug({kTagGC}, "Swept in %" PRIu64 " microseconds", timeSweepUs - timeResumeUs);
|
|
||||||
|
|
||||||
kotlin::compactObjectPoolInMainThread();
|
kotlin::compactObjectPoolInMainThread();
|
||||||
|
|
||||||
// Can be unsafe, because we have a lock in objectFactoryIterable
|
|
||||||
auto objectsCountAfter = objectFactory_.GetSizeUnsafe();
|
|
||||||
auto extraObjectsCountAfter = mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
|
||||||
|
|
||||||
auto finalizersCount = finalizerQueue.size();
|
|
||||||
auto collectedCount = objectsCountBefore - objectsCountAfter - finalizersCount;
|
|
||||||
|
|
||||||
state_.finish(epoch);
|
state_.finish(epoch);
|
||||||
|
gcHandle.finalizersScheduled(finalizerQueue.size());
|
||||||
|
gcHandle.finished();
|
||||||
finalizerProcessor_->ScheduleTasks(std::move(finalizerQueue), epoch);
|
finalizerProcessor_->ScheduleTasks(std::move(finalizerQueue), epoch);
|
||||||
|
|
||||||
RuntimeLogInfo(
|
|
||||||
{kTagGC},
|
|
||||||
"Finished GC epoch %" PRId64 ". Collected %zu objects, to be finalized %zu objects, %zu objects and %zd extra data objects remain. Total pause time %" PRIu64
|
|
||||||
" microseconds",
|
|
||||||
epoch, collectedCount, finalizersCount, objectsCountAfter, extraObjectsCountAfter, timeSweepUs - timeStartUs);
|
|
||||||
lastGCTimestampUs_ = timeResumeUs;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,8 +209,9 @@ namespace {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void gc::ConcurrentMarkAndSweep::SetMarkingRequested() noexcept {
|
void gc::ConcurrentMarkAndSweep::SetMarkingRequested(uint64_t epoch) noexcept {
|
||||||
markingRequested = markingBehavior_ == MarkingBehavior::kMarkOwnStack;
|
markingRequested = markingBehavior_ == MarkingBehavior::kMarkOwnStack;
|
||||||
|
markingEpoch = epoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::ConcurrentMarkAndSweep::WaitForThreadsReadyToMark() noexcept {
|
void gc::ConcurrentMarkAndSweep::WaitForThreadsReadyToMark() noexcept {
|
||||||
@@ -244,16 +220,16 @@ void gc::ConcurrentMarkAndSweep::WaitForThreadsReadyToMark() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_EXTERNAL_CALLS_CHECK void gc::ConcurrentMarkAndSweep::CollectRootSetAndStartMarking() noexcept {
|
NO_EXTERNAL_CALLS_CHECK void gc::ConcurrentMarkAndSweep::CollectRootSetAndStartMarking(GCHandle gcHandle) noexcept {
|
||||||
std::unique_lock lock(markingMutex);
|
std::unique_lock lock(markingMutex);
|
||||||
markingRequested = false;
|
markingRequested = false;
|
||||||
gc::collectRootSet<internal::MarkTraits>(
|
gc::collectRootSet<internal::MarkTraits>(
|
||||||
markQueue_, [](mm::ThreadData& thread) { return !thread.gc().impl().gc().marking_.load(); });
|
gcHandle,
|
||||||
|
markQueue_,
|
||||||
|
[](mm::ThreadData& thread) {
|
||||||
|
return !thread.gc().impl().gc().marking_.load();
|
||||||
|
}
|
||||||
|
);
|
||||||
RuntimeLogDebug({kTagGC}, "Requesting marking in threads");
|
RuntimeLogDebug({kTagGC}, "Requesting marking in threads");
|
||||||
markingCondVar.notify_all();
|
markingCondVar.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::ConcurrentMarkAndSweep::MergeMarkStats(gc::MarkStats stats) noexcept {
|
|
||||||
std::unique_lock lock(markingMutex);
|
|
||||||
lastGCMarkStats_.merge(stats);
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "GCState.hpp"
|
#include "GCState.hpp"
|
||||||
#include "std_support/Memory.hpp"
|
#include "std_support/Memory.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
@@ -114,25 +115,22 @@ public:
|
|||||||
void StopFinalizerThreadIfRunning() noexcept;
|
void StopFinalizerThreadIfRunning() noexcept;
|
||||||
bool FinalizersThreadIsRunning() noexcept;
|
bool FinalizersThreadIsRunning() noexcept;
|
||||||
void SetMarkingBehaviorForTests(MarkingBehavior markingBehavior) noexcept;
|
void SetMarkingBehaviorForTests(MarkingBehavior markingBehavior) noexcept;
|
||||||
void SetMarkingRequested() noexcept;
|
void SetMarkingRequested(uint64_t epoch) noexcept;
|
||||||
void WaitForThreadsReadyToMark() noexcept;
|
void WaitForThreadsReadyToMark() noexcept;
|
||||||
void CollectRootSetAndStartMarking() noexcept;
|
void CollectRootSetAndStartMarking(GCHandle gcHandle) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
|
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
|
||||||
bool PerformFullGC(int64_t epoch) noexcept;
|
bool PerformFullGC(int64_t epoch) noexcept;
|
||||||
void MergeMarkStats(MarkStats stats) noexcept;
|
|
||||||
|
|
||||||
mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory_;
|
mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory_;
|
||||||
GCScheduler& gcScheduler_;
|
GCScheduler& gcScheduler_;
|
||||||
|
|
||||||
uint64_t lastGCTimestampUs_ = 0;
|
|
||||||
GCStateHolder state_;
|
GCStateHolder state_;
|
||||||
ScopedThread gcThread_;
|
ScopedThread gcThread_;
|
||||||
std_support::unique_ptr<FinalizerProcessor> finalizerProcessor_;
|
std_support::unique_ptr<FinalizerProcessor> finalizerProcessor_;
|
||||||
|
|
||||||
MarkQueue markQueue_;
|
MarkQueue markQueue_;
|
||||||
MarkStats lastGCMarkStats_;
|
|
||||||
MarkingBehavior markingBehavior_;
|
MarkingBehavior markingBehavior_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1172,7 +1172,7 @@ TEST_P(ConcurrentMarkAndSweepTest, MutatorsCanMarkOwnLocals) {
|
|||||||
|
|
||||||
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
mm::GlobalData::Instance().gc().impl().gc().SetMarkingRequested();
|
mm::GlobalData::Instance().gc().impl().gc().SetMarkingRequested(0);
|
||||||
|
|
||||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||||
gcFutures[i] = mutators[i]
|
gcFutures[i] = mutators[i]
|
||||||
@@ -1181,7 +1181,7 @@ TEST_P(ConcurrentMarkAndSweepTest, MutatorsCanMarkOwnLocals) {
|
|||||||
|
|
||||||
if (GetParam() == gc::ConcurrentMarkAndSweep::kMarkOwnStack) {
|
if (GetParam() == gc::ConcurrentMarkAndSweep::kMarkOwnStack) {
|
||||||
mm::GlobalData::Instance().gc().impl().gc().WaitForThreadsReadyToMark();
|
mm::GlobalData::Instance().gc().impl().gc().WaitForThreadsReadyToMark();
|
||||||
mm::GlobalData::Instance().gc().impl().gc().CollectRootSetAndStartMarking();
|
mm::GlobalData::Instance().gc().impl().gc().CollectRootSetAndStartMarking(gc::GCHandle::createFakeForTests());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "GCImpl.hpp"
|
#include "GCImpl.hpp"
|
||||||
|
|
||||||
#include "GC.hpp"
|
#include "GC.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
#include "MarkAndSweepUtils.hpp"
|
#include "MarkAndSweepUtils.hpp"
|
||||||
#include "ThreadSuspension.hpp"
|
#include "ThreadSuspension.hpp"
|
||||||
#include "std_support/Memory.hpp"
|
#include "std_support/Memory.hpp"
|
||||||
@@ -74,6 +75,20 @@ size_t gc::GC::GetAllocatedHeapSize(ObjHeader* object) noexcept {
|
|||||||
return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
|
return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t gc::GC::GetHeapObjectsCountUnsafe() const noexcept {
|
||||||
|
return impl_->objectFactory().GetObjectsCountUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetTotalHeapObjectsSizeUnsafe() const noexcept {
|
||||||
|
return impl_->objectFactory().GetTotalObjectsSizeUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetExtraObjectsCountUnsafe() const noexcept {
|
||||||
|
return mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetTotalExtraObjectsSizeUnsafe() const noexcept {
|
||||||
|
return mm::GlobalData::Instance().extraObjectDataFactory().GetTotalObjectsSizeUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
||||||
return impl_->gcScheduler().config();
|
return impl_->gcScheduler().config();
|
||||||
}
|
}
|
||||||
@@ -81,6 +96,7 @@ gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
|||||||
void gc::GC::ClearForTests() noexcept {
|
void gc::GC::ClearForTests() noexcept {
|
||||||
impl_->gc().StopFinalizerThreadIfRunning();
|
impl_->gc().StopFinalizerThreadIfRunning();
|
||||||
impl_->objectFactory().ClearForTests();
|
impl_->objectFactory().ClearForTests();
|
||||||
|
GCHandle::ClearForTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {
|
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public:
|
|||||||
|
|
||||||
static size_t GetAllocatedHeapSize(ObjHeader* object) noexcept;
|
static size_t GetAllocatedHeapSize(ObjHeader* object) noexcept;
|
||||||
|
|
||||||
|
size_t GetHeapObjectsCountUnsafe() const noexcept;
|
||||||
|
size_t GetTotalHeapObjectsSizeUnsafe() const noexcept;
|
||||||
|
size_t GetExtraObjectsCountUnsafe() const noexcept;
|
||||||
|
size_t GetTotalExtraObjectsSizeUnsafe() const noexcept;
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gcSchedulerConfig() noexcept;
|
gc::GCSchedulerConfig& gcSchedulerConfig() noexcept;
|
||||||
|
|
||||||
void ClearForTests() noexcept;
|
void ClearForTests() noexcept;
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
|
#include "Mutex.hpp"
|
||||||
|
#include "Porting.h"
|
||||||
|
|
||||||
|
#include "Types.h"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "ThreadData.hpp"
|
||||||
|
#include "std_support/Optional.hpp"
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct MemoryUsageMap {
|
||||||
|
std::optional<kotlin::gc::MemoryUsage> heap;
|
||||||
|
std::optional<kotlin::gc::MemoryUsage> extra;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RootSetStatistics {
|
||||||
|
KLong threadLocalReferences;
|
||||||
|
KLong stackReferences;
|
||||||
|
KLong globalReferences;
|
||||||
|
KLong stableReferences;
|
||||||
|
KLong total() const { return threadLocalReferences + stableReferences + globalReferences + stableReferences; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GCInfo {
|
||||||
|
std::optional<uint64_t> epoch;
|
||||||
|
std::optional<KLong> startTime; // time since process start
|
||||||
|
std::optional<KLong> endTime;
|
||||||
|
std::optional<KLong> pauseStartTime;
|
||||||
|
std::optional<KLong> pauseEndTime;
|
||||||
|
std::optional<KLong> finalizersDoneTime;
|
||||||
|
std::optional<RootSetStatistics> rootSet;
|
||||||
|
std::optional<kotlin::gc::MemoryUsage> markStats;
|
||||||
|
MemoryUsageMap memoryUsageBefore;
|
||||||
|
MemoryUsageMap memoryUsageAfter;
|
||||||
|
};
|
||||||
|
|
||||||
|
GCInfo last;
|
||||||
|
GCInfo current;
|
||||||
|
// This lock can be got by thread in runnable state making parallel mark, so
|
||||||
|
kotlin::SpinLock<kotlin::MutexThreadStateHandling::kIgnore> lock;
|
||||||
|
|
||||||
|
GCInfo* statByEpoch(uint64_t epoch) {
|
||||||
|
if (current.epoch == epoch) return ¤t;
|
||||||
|
if (last.epoch == epoch) return &last;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace kotlin::gc {
|
||||||
|
GCHandle GCHandle::create(uint64_t epoch) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
RuntimeAssert(statByEpoch(epoch) == nullptr, "Starting epoch, which already existed");
|
||||||
|
if (current.epoch) {
|
||||||
|
last = current;
|
||||||
|
current = {};
|
||||||
|
RuntimeLogWarning({kTagGC}, "Starting new GC epoch, while previous is not finished\n");
|
||||||
|
}
|
||||||
|
current.epoch = static_cast<KLong>(epoch);
|
||||||
|
current.startTime = static_cast<KLong>(konan::getTimeNanos());
|
||||||
|
if (last.endTime) {
|
||||||
|
GCLogInfo(epoch, "Started. Time since last GC %" PRIu64 " microseconds.", *current.startTime - *last.endTime);
|
||||||
|
} else {
|
||||||
|
GCLogInfo(epoch, "Started.");
|
||||||
|
}
|
||||||
|
return getByEpoch(epoch);
|
||||||
|
}
|
||||||
|
GCHandle GCHandle::createFakeForTests() { return getByEpoch(std::numeric_limits<uint64_t>::max()); }
|
||||||
|
GCHandle GCHandle::getByEpoch(uint64_t epoch) {
|
||||||
|
return GCHandle{epoch};
|
||||||
|
}
|
||||||
|
void GCHandle::ClearForTests() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
current = {};
|
||||||
|
last = {};
|
||||||
|
}
|
||||||
|
void GCHandle::finished() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->endTime = static_cast<KLong>(konan::getTimeNanos());
|
||||||
|
if (stat->rootSet) {
|
||||||
|
GCLogInfo(
|
||||||
|
epoch_,
|
||||||
|
"Root set: "
|
||||||
|
"%" PRIu64 " thread local references, "
|
||||||
|
"%" PRIu64 " stack references, "
|
||||||
|
"%" PRIu64 " global references, "
|
||||||
|
"%" PRIu64 " stable references. "
|
||||||
|
"In total %" PRIu64 " roots.",
|
||||||
|
stat->rootSet->threadLocalReferences, stat->rootSet->stackReferences, stat->rootSet->globalReferences,
|
||||||
|
stat->rootSet->stableReferences, stat->rootSet->total());
|
||||||
|
}
|
||||||
|
if (stat->startTime) {
|
||||||
|
auto time = (*current.endTime - *current.startTime) / 1000;
|
||||||
|
GCLogInfo(epoch_, "Finished. Total GC epoch time is %" PRId64" microseconds.", time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat == ¤t) {
|
||||||
|
last = current;
|
||||||
|
current = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GCHandle::suspensionRequested() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
GCLogDebug(epoch_, "Requested thread suspension by thread %d", konan::currentThreadId());
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->pauseStartTime = static_cast<KLong>(konan::getTimeNanos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GCHandle::threadsAreSuspended() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
if (stat->pauseStartTime) {
|
||||||
|
auto time = (konan::getTimeNanos() - *stat->pauseStartTime) / 1000;
|
||||||
|
GCLogInfo(epoch_, "Suspended all threads in %" PRIu64 " microseconds", time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GCHandle::threadsAreResumed() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->pauseEndTime = static_cast<KLong>(konan::getTimeNanos());
|
||||||
|
if (stat->pauseStartTime) {
|
||||||
|
auto time = (*stat->pauseEndTime - *stat->pauseStartTime) / 1000;
|
||||||
|
GCLogInfo(epoch_, "Resume all threads. Total pause time is %" PRId64 " microseconds.", time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GCHandle::finalizersDone() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->finalizersDoneTime = static_cast<KLong>(konan::getTimeNanos());
|
||||||
|
if (stat->endTime) {
|
||||||
|
auto time = (*stat->finalizersDoneTime - *stat->endTime) / 1000;
|
||||||
|
GCLogInfo(epoch_, "Finalization is done in %" PRId64 " microseconds after epoch end.", time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GCLogInfo(epoch_, "Finalization is done.");
|
||||||
|
}
|
||||||
|
void GCHandle::finalizersScheduled(uint64_t finalizersCount) {
|
||||||
|
GCLogInfo(epoch_, "Finalization is scheduled for %" PRIu64 " objects.", finalizersCount);
|
||||||
|
}
|
||||||
|
void GCHandle::threadRootSetCollected(mm::ThreadData &threadData, uint64_t threadLocalReferences, uint64_t stackReferences) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
if (!stat->rootSet) {
|
||||||
|
stat->rootSet = RootSetStatistics{0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
stat->rootSet->stackReferences += static_cast<KLong>(stackReferences);
|
||||||
|
stat->rootSet->threadLocalReferences += static_cast<KLong>(threadLocalReferences);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GCHandle::globalRootSetCollected(uint64_t globalReferences, uint64_t stableReferences) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
if (!stat->rootSet) {
|
||||||
|
stat->rootSet = RootSetStatistics{0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
stat->rootSet->globalReferences += static_cast<KLong>(globalReferences);
|
||||||
|
stat->rootSet->stableReferences += static_cast<KLong>(stableReferences);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GCHandle::heapUsageBefore(MemoryUsage usage) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->memoryUsageBefore.heap = usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCHandle::marked(kotlin::gc::MemoryUsage usage) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
if (!stat->markStats) {
|
||||||
|
stat->markStats = MemoryUsage{0, 0};
|
||||||
|
}
|
||||||
|
stat->markStats->totalObjectsSize += usage.totalObjectsSize;
|
||||||
|
stat->markStats->objectsCount += usage.objectsCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryUsage GCHandle::getMarked() {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
if (stat->markStats) {
|
||||||
|
return *stat->markStats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MemoryUsage{0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCHandle::heapUsageAfter(MemoryUsage usage) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->memoryUsageAfter.heap = usage;
|
||||||
|
if (stat->memoryUsageBefore.heap) {
|
||||||
|
GCLogInfo(
|
||||||
|
epoch_,
|
||||||
|
"Collected %" PRId64 " heap objects of total size %" PRId64 " bytes. "
|
||||||
|
"%" PRId64 " heap objects of total size %" PRId64 " bytes are still alive.",
|
||||||
|
stat->memoryUsageBefore.heap->objectsCount - stat->memoryUsageAfter.heap->objectsCount,
|
||||||
|
stat->memoryUsageBefore.heap->totalObjectsSize - stat->memoryUsageAfter.heap->totalObjectsSize,
|
||||||
|
stat->memoryUsageAfter.heap->objectsCount, stat->memoryUsageAfter.heap->totalObjectsSize);
|
||||||
|
}
|
||||||
|
if (stat->markStats) {
|
||||||
|
RuntimeAssert(
|
||||||
|
stat->markStats->objectsCount == usage.objectsCount,
|
||||||
|
"Mismatch in statistics: marked %" PRId64 " objects, while %" PRId64 " is alive after sweep",
|
||||||
|
stat->markStats->objectsCount, usage.objectsCount);
|
||||||
|
RuntimeAssert(
|
||||||
|
stat->markStats->totalObjectsSize == usage.totalObjectsSize,
|
||||||
|
"Mismatch in statistics: total marked size is %" PRId64 " bytes, while %" PRId64 " bytes is alive after sweep",
|
||||||
|
stat->markStats->totalObjectsSize, usage.totalObjectsSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCHandle::extraObjectsUsageBefore(MemoryUsage usage) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->memoryUsageBefore.extra = usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GCHandle::extraObjectsUsageAfter(MemoryUsage usage) {
|
||||||
|
std::lock_guard guard(lock);
|
||||||
|
if (auto* stat = statByEpoch(epoch_)) {
|
||||||
|
stat->memoryUsageAfter.extra = usage;
|
||||||
|
if (stat->memoryUsageBefore.extra) {
|
||||||
|
GCLogInfo(
|
||||||
|
epoch_,
|
||||||
|
"Collected %" PRId64 " extra objects of total size %" PRId64 ". "
|
||||||
|
"%" PRId64 " extra objects of total size %" PRId64 " are still alive.",
|
||||||
|
stat->memoryUsageBefore.extra->objectsCount - stat->memoryUsageAfter.extra->objectsCount,
|
||||||
|
stat->memoryUsageBefore.extra->totalObjectsSize - stat->memoryUsageAfter.extra->totalObjectsSize,
|
||||||
|
stat->memoryUsageAfter.extra->objectsCount, stat->memoryUsageAfter.extra->totalObjectsSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryUsage GCHandle::GCSweepScope::getUsage() {
|
||||||
|
return MemoryUsage{
|
||||||
|
mm::GlobalData::Instance().gc().GetHeapObjectsCountUnsafe(),
|
||||||
|
mm::GlobalData::Instance().gc().GetTotalHeapObjectsSizeUnsafe(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCSweepScope::GCSweepScope(kotlin::gc::GCHandle& handle) :
|
||||||
|
handle_(handle) {
|
||||||
|
handle_.heapUsageBefore(getUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCSweepScope::~GCSweepScope() {
|
||||||
|
GCLogDebug(handle_.getEpoch(), "Swept is done in %" PRIu64 " microseconds.", getStageTime());
|
||||||
|
handle_.heapUsageAfter(getUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryUsage GCHandle::GCSweepExtraObjectsScope::getUsage() {
|
||||||
|
return MemoryUsage{
|
||||||
|
mm::GlobalData::Instance().gc().GetExtraObjectsCountUnsafe(),
|
||||||
|
mm::GlobalData::Instance().gc().GetTotalExtraObjectsSizeUnsafe(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCSweepExtraObjectsScope::GCSweepExtraObjectsScope(kotlin::gc::GCHandle& handle) :
|
||||||
|
handle_(handle) {
|
||||||
|
handle_.extraObjectsUsageBefore(getUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCSweepExtraObjectsScope::~GCSweepExtraObjectsScope() {
|
||||||
|
GCLogDebug(handle_.getEpoch(), "Swept extra objects is done in %" PRIu64 " microseconds", getStageTime());
|
||||||
|
handle_.extraObjectsUsageAfter(getUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCGlobalRootSetScope::GCGlobalRootSetScope(kotlin::gc::GCHandle& handle) : handle_(handle) {}
|
||||||
|
|
||||||
|
GCHandle::GCGlobalRootSetScope::~GCGlobalRootSetScope(){
|
||||||
|
handle_.globalRootSetCollected(globalRoots_, stableRoots_);
|
||||||
|
GCLogDebug(handle_.getEpoch(), "Collected global root set global=%" PRIu64 " stableRef=%" PRIu64 " in %" PRIu64" microseconds.",
|
||||||
|
globalRoots_, stableRoots_, getStageTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCThreadRootSetScope::GCThreadRootSetScope(kotlin::gc::GCHandle& handle, mm::ThreadData& threadData) :
|
||||||
|
handle_(handle), threadData_(threadData) {}
|
||||||
|
|
||||||
|
GCHandle::GCThreadRootSetScope::~GCThreadRootSetScope(){
|
||||||
|
handle_.threadRootSetCollected(threadData_, threadLocalRoots_, stackRoots_);
|
||||||
|
GCLogDebug(handle_.getEpoch(), "Collected root set for thread #%d: stack=%" PRIu64 " tls=%" PRIu64 " in %" PRIu64" microseconds.",
|
||||||
|
threadData_.threadId(), stackRoots_, threadLocalRoots_, getStageTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
GCHandle::GCMarkScope::GCMarkScope(kotlin::gc::GCHandle& handle) : handle_(handle){}
|
||||||
|
|
||||||
|
GCHandle::GCMarkScope::~GCMarkScope() {
|
||||||
|
handle_.marked(MemoryUsage{objectsCount, totalObjectSizeBytes});
|
||||||
|
GCLogDebug(handle_.getEpoch(),
|
||||||
|
"Marked %" PRIu64 " objects in %" PRIu64 " microseconds in thread %d",
|
||||||
|
objectsCount, getStageTime(), konan::currentThreadId());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace kotlin::gc
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include "Common.h"
|
||||||
|
#include "ThreadData.hpp"
|
||||||
|
|
||||||
|
#define GCLogInfo(epoch, format, ...) RuntimeLogInfo({kTagGC}, "Epoch #%" PRIu64 ": " format, epoch, ##__VA_ARGS__)
|
||||||
|
#define GCLogDebug(epoch, format, ...) RuntimeLogDebug({kTagGC}, "Epoch #%" PRIu64 ": " format, epoch, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
namespace kotlin::gc {
|
||||||
|
|
||||||
|
class GCHandle;
|
||||||
|
|
||||||
|
struct MemoryUsage {
|
||||||
|
uint64_t objectsCount;
|
||||||
|
uint64_t totalObjectsSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GCHandle {
|
||||||
|
class GCStageScopeUsTimer {
|
||||||
|
protected:
|
||||||
|
uint64_t startTime_ = konan::getTimeMicros();
|
||||||
|
uint64_t getStageTime() const { return (konan::getTimeMicros() - startTime_); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class GCSweepScope : GCStageScopeUsTimer, Pinned {
|
||||||
|
GCHandle& handle_;
|
||||||
|
MemoryUsage getUsage();
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GCSweepScope(GCHandle& handle);
|
||||||
|
~GCSweepScope();
|
||||||
|
};
|
||||||
|
|
||||||
|
class GCSweepExtraObjectsScope : GCStageScopeUsTimer, Pinned {
|
||||||
|
GCHandle& handle_;
|
||||||
|
MemoryUsage getUsage();
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GCSweepExtraObjectsScope(GCHandle& handle);
|
||||||
|
~GCSweepExtraObjectsScope();
|
||||||
|
};
|
||||||
|
|
||||||
|
class GCGlobalRootSetScope : GCStageScopeUsTimer, Pinned {
|
||||||
|
GCHandle& handle_;
|
||||||
|
uint64_t globalRoots_ = 0;
|
||||||
|
uint64_t stableRoots_ = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GCGlobalRootSetScope(GCHandle& handle);
|
||||||
|
~GCGlobalRootSetScope();
|
||||||
|
void addGlobalRoot() { globalRoots_++; }
|
||||||
|
void addStableRoot() { stableRoots_++; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class GCThreadRootSetScope : GCStageScopeUsTimer, Pinned {
|
||||||
|
GCHandle& handle_;
|
||||||
|
mm::ThreadData& threadData_;
|
||||||
|
uint64_t stackRoots_ = 0;
|
||||||
|
uint64_t threadLocalRoots_ = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GCThreadRootSetScope(GCHandle& handle, mm::ThreadData& threadData);
|
||||||
|
~GCThreadRootSetScope();
|
||||||
|
void addStackRoot() { stackRoots_++; }
|
||||||
|
void addThreadLocalRoot() { threadLocalRoots_++; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class GCMarkScope : GCStageScopeUsTimer, Pinned {
|
||||||
|
GCHandle& handle_;
|
||||||
|
uint64_t objectsCount = 0;
|
||||||
|
uint64_t totalObjectSizeBytes = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GCMarkScope(GCHandle& handle);
|
||||||
|
~GCMarkScope();
|
||||||
|
void addObject(uint64_t objectSize) {
|
||||||
|
totalObjectSizeBytes += objectSize;
|
||||||
|
objectsCount++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t epoch_;
|
||||||
|
explicit GCHandle(uint64_t epoch) : epoch_(epoch) {}
|
||||||
|
|
||||||
|
void threadRootSetCollected(mm::ThreadData& threadData, uint64_t threadLocalReferences, uint64_t stackReferences);
|
||||||
|
void globalRootSetCollected(uint64_t globalReferences, uint64_t stableReferences);
|
||||||
|
void heapUsageBefore(MemoryUsage usage);
|
||||||
|
void heapUsageAfter(MemoryUsage usage);
|
||||||
|
void extraObjectsUsageBefore(MemoryUsage usage);
|
||||||
|
void extraObjectsUsageAfter(MemoryUsage usage);
|
||||||
|
void marked(MemoryUsage usage);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static GCHandle create(uint64_t epoch);
|
||||||
|
static GCHandle createFakeForTests();
|
||||||
|
static GCHandle getByEpoch(uint64_t epoch);
|
||||||
|
static void ClearForTests();
|
||||||
|
|
||||||
|
uint64_t getEpoch() { return epoch_; }
|
||||||
|
void finished();
|
||||||
|
void finalizersDone();
|
||||||
|
void finalizersScheduled(uint64_t finalizersCount);
|
||||||
|
void suspensionRequested();
|
||||||
|
void threadsAreSuspended();
|
||||||
|
void threadsAreResumed();
|
||||||
|
GCSweepScope sweep() { return GCSweepScope(*this); }
|
||||||
|
GCSweepExtraObjectsScope sweepExtraObjects() { return GCSweepExtraObjectsScope(*this); }
|
||||||
|
GCGlobalRootSetScope collectGlobalRoots() { return GCGlobalRootSetScope(*this); }
|
||||||
|
GCThreadRootSetScope collectThreadRoots(mm::ThreadData& threadData) { return GCThreadRootSetScope(*this, threadData); }
|
||||||
|
GCMarkScope mark() { return GCMarkScope(*this); }
|
||||||
|
|
||||||
|
MemoryUsage getMarked();
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "ExtraObjectData.hpp"
|
#include "ExtraObjectData.hpp"
|
||||||
#include "FinalizerHooks.hpp"
|
#include "FinalizerHooks.hpp"
|
||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
#include "Logging.hpp"
|
#include "Logging.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "ObjectOps.hpp"
|
#include "ObjectOps.hpp"
|
||||||
@@ -88,18 +89,15 @@ struct MarkStats {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
MarkStats Mark(typename Traits::MarkQueue& markQueue) noexcept {
|
void Mark(GCHandle handle, typename Traits::MarkQueue& markQueue) noexcept {
|
||||||
MarkStats stats;
|
auto markHandle = handle.mark();
|
||||||
stats.rootSetSize = markQueue.size();
|
|
||||||
auto timeStart = konan::getTimeMicros();
|
|
||||||
while (!Traits::isEmpty(markQueue)) {
|
while (!Traits::isEmpty(markQueue)) {
|
||||||
ObjHeader* top = Traits::dequeue(markQueue);
|
ObjHeader* top = Traits::dequeue(markQueue);
|
||||||
|
|
||||||
RuntimeAssert(!isNullOrMarker(top), "Got invalid reference %p in mark queue", top);
|
RuntimeAssert(!isNullOrMarker(top), "Got invalid reference %p in mark queue", top);
|
||||||
RuntimeAssert(top->heap(), "Got non-heap reference %p in mark queue, permanent=%d stack=%d", top, top->permanent(), top->local());
|
RuntimeAssert(top->heap(), "Got non-heap reference %p in mark queue, permanent=%d stack=%d", top, top->permanent(), top->local());
|
||||||
|
|
||||||
stats.aliveHeapSet++;
|
markHandle.addObject(mm::GetAllocatedHeapSize(top));
|
||||||
stats.aliveHeapSetBytes += mm::GetAllocatedHeapSize(top);
|
|
||||||
|
|
||||||
Traits::processInMark(markQueue, top);
|
Traits::processInMark(markQueue, top);
|
||||||
|
|
||||||
@@ -112,14 +110,12 @@ MarkStats Mark(typename Traits::MarkQueue& markQueue) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto timeEnd = konan::getTimeMicros();
|
|
||||||
RuntimeLogDebug({kTagGC}, "Marked %zu objects in %" PRIu64 " microseconds in thread %d", stats.aliveHeapSet, timeEnd - timeStart, konan::currentThreadId());
|
|
||||||
return stats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void SweepExtraObjects(typename Traits::ExtraObjectsFactory& objectFactory) noexcept {
|
void SweepExtraObjects(GCHandle handle, typename Traits::ExtraObjectsFactory& objectFactory) noexcept {
|
||||||
objectFactory.ProcessDeletions();
|
objectFactory.ProcessDeletions();
|
||||||
|
auto sweepHandle = handle.sweepExtraObjects();
|
||||||
auto iter = objectFactory.LockForIter();
|
auto iter = objectFactory.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
auto &extraObject = *it;
|
auto &extraObject = *it;
|
||||||
@@ -140,8 +136,9 @@ void SweepExtraObjects(typename Traits::ExtraObjectsFactory& objectFactory) noex
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFactory::Iterable& objectFactoryIter) noexcept {
|
typename Traits::ObjectFactory::FinalizerQueue Sweep(GCHandle handle, typename Traits::ObjectFactory::Iterable& objectFactoryIter) noexcept {
|
||||||
typename Traits::ObjectFactory::FinalizerQueue finalizerQueue;
|
typename Traits::ObjectFactory::FinalizerQueue finalizerQueue;
|
||||||
|
auto sweepHandle = handle.sweep();
|
||||||
|
|
||||||
for (auto it = objectFactoryIter.begin(); it != objectFactoryIter.end();) {
|
for (auto it = objectFactoryIter.begin(); it != objectFactoryIter.end();) {
|
||||||
if (Traits::TryResetMark(*it)) {
|
if (Traits::TryResetMark(*it)) {
|
||||||
@@ -160,64 +157,60 @@ typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFact
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFactory& objectFactory) noexcept {
|
typename Traits::ObjectFactory::FinalizerQueue Sweep(GCHandle handle, typename Traits::ObjectFactory& objectFactory) noexcept {
|
||||||
auto iter = objectFactory.LockForIter();
|
auto iter = objectFactory.LockForIter();
|
||||||
return Sweep<Traits>(iter);
|
return Sweep<Traits>(handle, iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void collectRootSetForThread(typename Traits::MarkQueue& markQueue, mm::ThreadData& thread) {
|
void collectRootSetForThread(GCHandle gcHandle, typename Traits::MarkQueue& markQueue, mm::ThreadData& thread) {
|
||||||
|
auto handle = gcHandle.collectThreadRoots(thread);
|
||||||
thread.gc().OnStoppedForGC();
|
thread.gc().OnStoppedForGC();
|
||||||
size_t stack = 0;
|
|
||||||
size_t tls = 0;
|
|
||||||
// TODO: Remove useless mm::ThreadRootSet abstraction.
|
// TODO: Remove useless mm::ThreadRootSet abstraction.
|
||||||
for (auto value : mm::ThreadRootSet(thread)) {
|
for (auto value : mm::ThreadRootSet(thread)) {
|
||||||
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
||||||
switch (value.source) {
|
switch (value.source) {
|
||||||
case mm::ThreadRootSet::Source::kStack:
|
case mm::ThreadRootSet::Source::kStack:
|
||||||
++stack;
|
handle.addStackRoot();
|
||||||
break;
|
break;
|
||||||
case mm::ThreadRootSet::Source::kTLS:
|
case mm::ThreadRootSet::Source::kTLS:
|
||||||
++tls;
|
handle.addThreadLocalRoot();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeLogDebug({kTagGC}, "Collected root set for thread stack=%zu tls=%zu", stack, tls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void collectRootSetGlobals(typename Traits::MarkQueue& markQueue) noexcept {
|
void collectRootSetGlobals(GCHandle gcHandle, typename Traits::MarkQueue& markQueue) noexcept {
|
||||||
|
auto handle = gcHandle.collectGlobalRoots();
|
||||||
mm::StableRefRegistry::Instance().ProcessDeletions();
|
mm::StableRefRegistry::Instance().ProcessDeletions();
|
||||||
size_t global = 0;
|
|
||||||
size_t stableRef = 0;
|
|
||||||
// TODO: Remove useless mm::GlobalRootSet abstraction.
|
// TODO: Remove useless mm::GlobalRootSet abstraction.
|
||||||
for (auto value : mm::GlobalRootSet()) {
|
for (auto value : mm::GlobalRootSet()) {
|
||||||
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
||||||
switch (value.source) {
|
switch (value.source) {
|
||||||
case mm::GlobalRootSet::Source::kGlobal:
|
case mm::GlobalRootSet::Source::kGlobal:
|
||||||
++global;
|
handle.addGlobalRoot();
|
||||||
break;
|
break;
|
||||||
case mm::GlobalRootSet::Source::kStableRef:
|
case mm::GlobalRootSet::Source::kStableRef:
|
||||||
++stableRef;
|
handle.addStableRoot();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeLogDebug({kTagGC}, "Collected global root set global=%zu stableRef=%zu", global, stableRef);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This needs some tests now.
|
// TODO: This needs some tests now.
|
||||||
template <typename Traits, typename F>
|
template <typename Traits, typename F>
|
||||||
void collectRootSet(typename Traits::MarkQueue& markQueue, F&& filter) noexcept {
|
void collectRootSet(GCHandle handle, typename Traits::MarkQueue& markQueue, F&& filter) noexcept {
|
||||||
Traits::clear(markQueue);
|
Traits::clear(markQueue);
|
||||||
for (auto& thread : mm::GlobalData::Instance().threadRegistry().LockForIter()) {
|
for (auto& thread : mm::GlobalData::Instance().threadRegistry().LockForIter()) {
|
||||||
if (!filter(thread))
|
if (!filter(thread))
|
||||||
continue;
|
continue;
|
||||||
thread.Publish();
|
thread.Publish();
|
||||||
collectRootSetForThread<Traits>(markQueue, thread);
|
collectRootSetForThread<Traits>(handle, markQueue, thread);
|
||||||
}
|
}
|
||||||
collectRootSetGlobals<Traits>(markQueue);
|
collectRootSetGlobals<Traits>(handle, markQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace gc
|
} // namespace gc
|
||||||
|
|||||||
@@ -174,13 +174,19 @@ public:
|
|||||||
return testing::UnorderedElementsAreArray(objects);
|
return testing::UnorderedElementsAreArray(objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::MarkStats Mark(std::initializer_list<std::reference_wrapper<BaseObject>> graySet) {
|
gc::MemoryUsage Mark(std::initializer_list<std::reference_wrapper<BaseObject>> graySet) {
|
||||||
std_support::vector<ObjHeader*> objects;
|
std_support::vector<ObjHeader*> objects;
|
||||||
for (auto& object : graySet) ScopedMarkTraits::enqueue(objects, object.get().GetObjHeader());
|
for (auto& object : graySet) ScopedMarkTraits::enqueue(objects, object.get().GetObjHeader());
|
||||||
return gc::Mark<ScopedMarkTraits>(objects);
|
auto handle = gc::GCHandle::create(epoch_++);
|
||||||
|
gc::Mark<ScopedMarkTraits>(handle, objects);
|
||||||
|
handle.finished();
|
||||||
|
return handle.getMarked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~MarkAndSweepUtilsMarkTest() { mm::GlobalData::Instance().gc().ClearForTests(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uint64_t epoch_ = 0;
|
||||||
kotlin::ScopedMemoryInit memoryInit;
|
kotlin::ScopedMemoryInit memoryInit;
|
||||||
ScopedMarkTraits markTraits_;
|
ScopedMarkTraits markTraits_;
|
||||||
};
|
};
|
||||||
@@ -196,8 +202,8 @@ size_t GetObjectsSize(std::initializer_list<std::reference_wrapper<BaseObject>>
|
|||||||
#define EXPECT_MARKED(stats, ...) \
|
#define EXPECT_MARKED(stats, ...) \
|
||||||
do { \
|
do { \
|
||||||
std::initializer_list<std::reference_wrapper<BaseObject>> objects = {__VA_ARGS__}; \
|
std::initializer_list<std::reference_wrapper<BaseObject>> objects = {__VA_ARGS__}; \
|
||||||
EXPECT_THAT(stats.aliveHeapSet, objects.size()); \
|
EXPECT_THAT(stats.objectsCount, objects.size()); \
|
||||||
EXPECT_THAT(stats.aliveHeapSetBytes, GetObjectsSize(objects)); \
|
EXPECT_THAT(stats.totalObjectsSize, GetObjectsSize(objects)); \
|
||||||
EXPECT_THAT(marked(), MarkedMatcher(objects)); \
|
EXPECT_THAT(marked(), MarkedMatcher(objects)); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
|
|||||||
@@ -199,8 +199,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std_support::vector<ObjHeader*> Sweep() {
|
std_support::vector<ObjHeader*> Sweep() {
|
||||||
gc::SweepExtraObjects<SweepTraits>(extraObjectFactory_);
|
gc::SweepExtraObjects<SweepTraits>(gc::GCHandle::getByEpoch(0), extraObjectFactory_);
|
||||||
auto finalizers = gc::Sweep<SweepTraits>(objectFactory_);
|
auto finalizers = gc::Sweep<SweepTraits>(gc::GCHandle::getByEpoch(0), objectFactory_);
|
||||||
std_support::vector<ObjHeader*> objects;
|
std_support::vector<ObjHeader*> objects;
|
||||||
for (auto node : finalizers.IterForTests()) {
|
for (auto node : finalizers.IterForTests()) {
|
||||||
objects.push_back(node.GetObjHeader());
|
objects.push_back(node.GetObjHeader());
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "GC.hpp"
|
#include "GC.hpp"
|
||||||
#include "std_support/Memory.hpp"
|
#include "std_support/Memory.hpp"
|
||||||
|
#include "GlobalData.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -61,12 +63,26 @@ size_t gc::GC::GetAllocatedHeapSize(ObjHeader* object) noexcept {
|
|||||||
return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
|
return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t gc::GC::GetHeapObjectsCountUnsafe() const noexcept {
|
||||||
|
return impl_->objectFactory().GetObjectsCountUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetTotalHeapObjectsSizeUnsafe() const noexcept {
|
||||||
|
return impl_->objectFactory().GetTotalObjectsSizeUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetExtraObjectsCountUnsafe() const noexcept {
|
||||||
|
return mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetTotalExtraObjectsSizeUnsafe() const noexcept {
|
||||||
|
return mm::GlobalData::Instance().extraObjectDataFactory().GetTotalObjectsSizeUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
||||||
return impl_->gcScheduler().config();
|
return impl_->gcScheduler().config();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ClearForTests() noexcept {
|
void gc::GC::ClearForTests() noexcept {
|
||||||
impl_->objectFactory().ClearForTests();
|
impl_->objectFactory().ClearForTests();
|
||||||
|
GCHandle::ClearForTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {}
|
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "GC.hpp"
|
#include "GC.hpp"
|
||||||
#include "MarkAndSweepUtils.hpp"
|
#include "MarkAndSweepUtils.hpp"
|
||||||
#include "std_support/Memory.hpp"
|
#include "std_support/Memory.hpp"
|
||||||
|
#include "GlobalData.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -74,12 +76,26 @@ size_t gc::GC::GetAllocatedHeapSize(ObjHeader* object) noexcept {
|
|||||||
return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
|
return mm::ObjectFactory<GCImpl>::GetAllocatedHeapSize(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t gc::GC::GetHeapObjectsCountUnsafe() const noexcept {
|
||||||
|
return impl_->objectFactory().GetObjectsCountUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetTotalHeapObjectsSizeUnsafe() const noexcept {
|
||||||
|
return impl_->objectFactory().GetTotalObjectsSizeUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetExtraObjectsCountUnsafe() const noexcept {
|
||||||
|
return mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
||||||
|
}
|
||||||
|
size_t gc::GC::GetTotalExtraObjectsSizeUnsafe() const noexcept {
|
||||||
|
return mm::GlobalData::Instance().extraObjectDataFactory().GetTotalObjectsSizeUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
||||||
return impl_->gcScheduler().config();
|
return impl_->gcScheduler().config();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ClearForTests() noexcept {
|
void gc::GC::ClearForTests() noexcept {
|
||||||
impl_->objectFactory().ClearForTests();
|
impl_->objectFactory().ClearForTests();
|
||||||
|
GCHandle::ClearForTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {}
|
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "CompilerConstants.hpp"
|
#include "CompilerConstants.hpp"
|
||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
|
#include "GCStatistics.hpp"
|
||||||
#include "Logging.hpp"
|
#include "Logging.hpp"
|
||||||
#include "MarkAndSweepUtils.hpp"
|
#include "MarkAndSweepUtils.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
@@ -103,7 +104,6 @@ gc::SameThreadMarkAndSweep::SameThreadMarkAndSweep(
|
|||||||
|
|
||||||
bool gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
bool gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
||||||
RuntimeLogDebug({kTagGC}, "Attempt to suspend threads by thread %d", konan::currentThreadId());
|
RuntimeLogDebug({kTagGC}, "Attempt to suspend threads by thread %d", konan::currentThreadId());
|
||||||
auto timeStartUs = konan::getTimeMicros();
|
|
||||||
bool didSuspend = mm::RequestThreadsSuspension();
|
bool didSuspend = mm::RequestThreadsSuspension();
|
||||||
if (!didSuspend) {
|
if (!didSuspend) {
|
||||||
RuntimeLogDebug({kTagGC}, "Failed to suspend threads by thread %d", konan::currentThreadId());
|
RuntimeLogDebug({kTagGC}, "Failed to suspend threads by thread %d", konan::currentThreadId());
|
||||||
@@ -111,8 +111,9 @@ bool gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
|||||||
// TODO: This breaks if suspension is used by something apart from GC.
|
// TODO: This breaks if suspension is used by something apart from GC.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RuntimeLogDebug({kTagGC}, "Requested thread suspension by thread %d", konan::currentThreadId());
|
|
||||||
gSafepointFlag = SafepointFlag::kNeedsSuspend;
|
gSafepointFlag = SafepointFlag::kNeedsSuspend;
|
||||||
|
auto gcHandle = GCHandle::create(epoch_++);
|
||||||
|
gcHandle.suspensionRequested();
|
||||||
|
|
||||||
mm::ObjectFactory<gc::SameThreadMarkAndSweep>::FinalizerQueue finalizerQueue;
|
mm::ObjectFactory<gc::SameThreadMarkAndSweep>::FinalizerQueue finalizerQueue;
|
||||||
{
|
{
|
||||||
@@ -120,55 +121,27 @@ bool gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
|||||||
ThreadStateGuard guard(ThreadState::kNative);
|
ThreadStateGuard guard(ThreadState::kNative);
|
||||||
|
|
||||||
mm::WaitForThreadsSuspension();
|
mm::WaitForThreadsSuspension();
|
||||||
auto timeSuspendUs = konan::getTimeMicros();
|
gcHandle.threadsAreSuspended();
|
||||||
RuntimeLogDebug({kTagGC}, "Suspended all threads in %" PRIu64 " microseconds", timeSuspendUs - timeStartUs);
|
|
||||||
|
|
||||||
auto& scheduler = gcScheduler_;
|
auto& scheduler = gcScheduler_;
|
||||||
scheduler.gcData().OnPerformFullGC();
|
scheduler.gcData().OnPerformFullGC();
|
||||||
|
|
||||||
RuntimeLogInfo(
|
gc::collectRootSet<internal::MarkTraits>(gcHandle, markQueue_, [] (mm::ThreadData&) { return true; });
|
||||||
{kTagGC}, "Started GC epoch %zu. Time since last GC %" PRIu64 " microseconds", epoch_, timeStartUs - lastGCTimestampUs_);
|
auto& extraObjectsDataFactory = mm::GlobalData::Instance().extraObjectDataFactory();
|
||||||
gc::collectRootSet<internal::MarkTraits>(markQueue_, [](mm::ThreadData&) { return true; });
|
|
||||||
auto timeRootSetUs = konan::getTimeMicros();
|
|
||||||
// Can be unsafe, because we've stopped the world.
|
|
||||||
auto objectsCountBefore = objectFactory_.GetSizeUnsafe();
|
|
||||||
|
|
||||||
RuntimeLogInfo(
|
gc::Mark<internal::MarkTraits>(gcHandle, markQueue_);
|
||||||
{kTagGC}, "Collected root set of size %zu in %" PRIu64 " microseconds", markQueue_.size(),
|
auto markStats = gcHandle.getMarked();
|
||||||
timeRootSetUs - timeSuspendUs);
|
scheduler.gcData().UpdateAliveSetBytes(markStats.totalObjectsSize);
|
||||||
auto markStats = gc::Mark<internal::MarkTraits>(markQueue_);
|
gc::SweepExtraObjects<SweepTraits>(gcHandle, extraObjectsDataFactory);
|
||||||
auto timeMarkUs = konan::getTimeMicros();
|
finalizerQueue = gc::Sweep<SweepTraits>(gcHandle, objectFactory_);
|
||||||
RuntimeLogDebug({kTagGC}, "Marked %zu objects in %" PRIu64 " microseconds", markStats.aliveHeapSet, timeMarkUs - timeRootSetUs);
|
|
||||||
scheduler.gcData().UpdateAliveSetBytes(markStats.aliveHeapSetBytes);
|
|
||||||
gc::SweepExtraObjects<SweepTraits>(mm::GlobalData::Instance().extraObjectDataFactory());
|
|
||||||
auto timeSweepExtraObjectsUs = konan::getTimeMicros();
|
|
||||||
RuntimeLogDebug({kTagGC}, "Sweeped extra objects in %" PRIu64 " microseconds", timeSweepExtraObjectsUs - timeMarkUs);
|
|
||||||
finalizerQueue = gc::Sweep<SweepTraits>(objectFactory_);
|
|
||||||
auto timeSweepUs = konan::getTimeMicros();
|
|
||||||
RuntimeLogDebug({kTagGC}, "Sweeped in %" PRIu64 " microseconds", timeSweepUs - timeSweepExtraObjectsUs);
|
|
||||||
|
|
||||||
kotlin::compactObjectPoolInMainThread();
|
kotlin::compactObjectPoolInMainThread();
|
||||||
|
|
||||||
// Can be unsafe, because we've stopped the world.
|
|
||||||
auto objectsCountAfter = objectFactory_.GetSizeUnsafe();
|
|
||||||
auto extraObjectsCountAfter = mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
|
||||||
|
|
||||||
gSafepointFlag = SafepointFlag::kNone;
|
gSafepointFlag = SafepointFlag::kNone;
|
||||||
mm::ResumeThreads();
|
mm::ResumeThreads();
|
||||||
auto timeResumeUs = konan::getTimeMicros();
|
gcHandle.threadsAreResumed();
|
||||||
|
gcHandle.finalizersScheduled(finalizerQueue.size());
|
||||||
RuntimeLogDebug({kTagGC}, "Resumed threads in %" PRIu64 " microseconds.", timeResumeUs - timeSweepUs);
|
gcHandle.finished();
|
||||||
|
|
||||||
auto finalizersCount = finalizerQueue.size();
|
|
||||||
auto collectedCount = objectsCountBefore - objectsCountAfter - finalizersCount;
|
|
||||||
|
|
||||||
RuntimeLogInfo(
|
|
||||||
{kTagGC},
|
|
||||||
"Finished GC epoch %zu. Collected %zu objects, to be finalized %zu objects, %zu objects and %zd extra data objects remain. Total pause time %" PRIu64
|
|
||||||
" microseconds",
|
|
||||||
epoch_, collectedCount, finalizersCount, objectsCountAfter, extraObjectsCountAfter, timeResumeUs - timeStartUs);
|
|
||||||
++epoch_;
|
|
||||||
lastGCTimestampUs_ = timeResumeUs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalizers are run after threads are resumed, because finalizers may request GC themselves, which would
|
// Finalizers are run after threads are resumed, because finalizers may request GC themselves, which would
|
||||||
@@ -177,11 +150,8 @@ bool gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
|||||||
|
|
||||||
// TODO: These will actually need to be run on a separate thread.
|
// TODO: These will actually need to be run on a separate thread.
|
||||||
AssertThreadState(ThreadState::kRunnable);
|
AssertThreadState(ThreadState::kRunnable);
|
||||||
RuntimeLogDebug({kTagGC}, "Starting to run finalizers");
|
|
||||||
auto timeBeforeUs = konan::getTimeMicros();
|
|
||||||
finalizerQueue.Finalize();
|
finalizerQueue.Finalize();
|
||||||
auto timeAfterUs = konan::getTimeMicros();
|
gcHandle.finalizersDone();
|
||||||
RuntimeLogInfo({kTagGC}, "Finished running finalizers in %" PRIu64 " microseconds", timeAfterUs - timeBeforeUs);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,7 @@ private:
|
|||||||
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
|
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
|
||||||
bool PerformFullGC() noexcept;
|
bool PerformFullGC() noexcept;
|
||||||
|
|
||||||
size_t epoch_ = 0;
|
uint64_t epoch_ = 0;
|
||||||
uint64_t lastGCTimestampUs_ = 0;
|
|
||||||
|
|
||||||
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory_;
|
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory_;
|
||||||
GCScheduler& gcScheduler_;
|
GCScheduler& gcScheduler_;
|
||||||
|
|||||||
@@ -3516,6 +3516,9 @@ void Kotlin_native_internal_GC_collect(KRef) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void Kotlin_Internal_GC_GCInfoBuilder_Fill(KRef builder, int id) {
|
||||||
|
}
|
||||||
|
|
||||||
void Kotlin_native_internal_GC_collectCyclic(KRef) {
|
void Kotlin_native_internal_GC_collectCyclic(KRef) {
|
||||||
#if USE_CYCLIC_GC
|
#if USE_CYCLIC_GC
|
||||||
if (g_hasCyclicCollector)
|
if (g_hasCyclicCollector)
|
||||||
@@ -3862,3 +3865,5 @@ void kotlin::StartFinalizerThreadIfNeeded() noexcept {}
|
|||||||
bool kotlin::FinalizersThreadIsRunning() noexcept {
|
bool kotlin::FinalizersThreadIsRunning() noexcept {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,25 +6,15 @@
|
|||||||
#include "Logging.hpp"
|
#include "Logging.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
// TODO: Remove when wasm32 is gone.
|
|
||||||
#include <experimental/optional>
|
|
||||||
namespace std {
|
|
||||||
template <typename T>
|
|
||||||
using optional = std::experimental::optional<T>;
|
|
||||||
inline constexpr auto nullopt = std::experimental::nullopt;
|
|
||||||
} // namespace std
|
|
||||||
#else
|
|
||||||
#error "No <optional>"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Format.h"
|
#include "Format.h"
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
#include "Porting.h"
|
#include "Porting.h"
|
||||||
#include "std_support/Map.hpp"
|
#include "std_support/Map.hpp"
|
||||||
#include "std_support/String.hpp"
|
#include "std_support/String.hpp"
|
||||||
|
#include "std_support/Optional.hpp"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -115,7 +105,7 @@ private:
|
|||||||
|
|
||||||
class StderrLogger : public logging::internal::Logger {
|
class StderrLogger : public logging::internal::Logger {
|
||||||
public:
|
public:
|
||||||
void Log(logging::Level level, std_support::span<const char* const> tags, std::string_view message) const noexcept override {
|
NO_EXTERNAL_CALLS_CHECK void Log(logging::Level level, std_support::span<const char* const> tags, std::string_view message) const noexcept override {
|
||||||
konan::consoleErrorUtf8(message.data(), message.size());
|
konan::consoleErrorUtf8(message.data(), message.size());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
// TODO: Remove when wasm32 is gone.
|
||||||
|
#include <experimental/optional>
|
||||||
|
namespace std {
|
||||||
|
template <typename T>
|
||||||
|
using optional = std::experimental::optional<T>;
|
||||||
|
inline constexpr auto nullopt = std::experimental::nullopt;
|
||||||
|
} // namespace std
|
||||||
|
#else
|
||||||
|
#error "No <optional>"
|
||||||
|
#endif
|
||||||
@@ -25,3 +25,4 @@ Adjustments:
|
|||||||
`std_support::kdelete` as a replacement for operator `delete` for objects created with custom `new`.
|
`std_support::kdelete` as a replacement for operator `delete` for objects created with custom `new`.
|
||||||
* `Deque.hpp`, `ForwardList.hpp`, `List.hpp`, `Map.hpp`, `Set.hpp`, `String.hpp`, `UnorderedMap.hpp`, `UnorderedSet.hpp`, `Vector.hpp` -
|
* `Deque.hpp`, `ForwardList.hpp`, `List.hpp`, `Map.hpp`, `Set.hpp`, `String.hpp`, `UnorderedMap.hpp`, `UnorderedSet.hpp`, `Vector.hpp` -
|
||||||
standard containers and `std_support::string` that default to using `std_support::allocator`.
|
standard containers and `std_support::string` that default to using `std_support::allocator`.
|
||||||
|
* `Optional.hpp` - wrapper choosing correct way to include on different platforms.
|
||||||
@@ -53,6 +53,7 @@ public:
|
|||||||
void ClearForTests() noexcept { extraObjects_.ClearForTests(); }
|
void ClearForTests() noexcept { extraObjects_.ClearForTests(); }
|
||||||
|
|
||||||
size_t GetSizeUnsafe() noexcept { return extraObjects_.GetSizeUnsafe(); }
|
size_t GetSizeUnsafe() noexcept { return extraObjects_.GetSizeUnsafe(); }
|
||||||
|
size_t GetTotalObjectsSizeUnsafe() noexcept { return extraObjects_.GetSizeUnsafe() * sizeof(ExtraObjectData); }
|
||||||
|
|
||||||
// requires LockForIter
|
// requires LockForIter
|
||||||
void EraseAndAdvance(Iterator &it) { extraObjects_.EraseAndAdvance(it); }
|
void EraseAndAdvance(Iterator &it) { extraObjects_.EraseAndAdvance(it); }
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ public:
|
|||||||
|
|
||||||
last_ = nodePtr;
|
last_ = nodePtr;
|
||||||
++size_;
|
++size_;
|
||||||
|
totalObjectsSizeBytes_ += Node::GetSizeForDataSize(dataSize);
|
||||||
RuntimeAssert(root_ != nullptr, "Must not be empty");
|
RuntimeAssert(root_ != nullptr, "Must not be empty");
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
return *nodePtr;
|
return *nodePtr;
|
||||||
@@ -181,7 +182,9 @@ public:
|
|||||||
owner_.last_ = last_;
|
owner_.last_ = last_;
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
owner_.size_ += size_;
|
owner_.size_ += size_;
|
||||||
|
owner_.totalObjectsSizeBytes_ += totalObjectsSizeBytes_;
|
||||||
size_ = 0;
|
size_ = 0;
|
||||||
|
totalObjectsSizeBytes_ = 0;
|
||||||
|
|
||||||
RuntimeAssert(root_ == nullptr, "Must be empty");
|
RuntimeAssert(root_ == nullptr, "Must be empty");
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
@@ -216,6 +219,7 @@ public:
|
|||||||
unique_ptr<Node> root_;
|
unique_ptr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
size_t size_ = 0;
|
size_t size_ = 0;
|
||||||
|
size_t totalObjectsSizeBytes_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
@@ -348,13 +352,13 @@ public:
|
|||||||
Iterator begin() noexcept { return Iterator(nullptr, owner_.root_.get()); }
|
Iterator begin() noexcept { return Iterator(nullptr, owner_.root_.get()); }
|
||||||
Iterator end() noexcept { return Iterator(owner_.last_, nullptr); }
|
Iterator end() noexcept { return Iterator(owner_.last_, nullptr); }
|
||||||
|
|
||||||
void EraseAndAdvance(Iterator& iterator) noexcept {
|
void EraseAndAdvance(Iterator& iterator, size_t objectedSize) noexcept {
|
||||||
auto result = owner_.ExtractUnsafe(iterator.previousNode_);
|
auto result = owner_.ExtractUnsafe(iterator.previousNode_, objectedSize);
|
||||||
iterator.node_ = result.second;
|
iterator.node_ = result.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoveAndAdvance(Consumer& consumer, Iterator& iterator) noexcept {
|
void MoveAndAdvance(Consumer& consumer, Iterator& iterator, size_t objectSize) noexcept {
|
||||||
auto result = owner_.ExtractUnsafe(iterator.previousNode_);
|
auto result = owner_.ExtractUnsafe(iterator.previousNode_, objectSize);
|
||||||
iterator.node_ = result.second;
|
iterator.node_ = result.second;
|
||||||
consumer.Insert(std::move(result.first));
|
consumer.Insert(std::move(result.first));
|
||||||
}
|
}
|
||||||
@@ -373,40 +377,32 @@ public:
|
|||||||
Iterable LockForIter() noexcept { return Iterable(*this); }
|
Iterable LockForIter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
size_t GetSizeUnsafe() const noexcept { return size_; }
|
size_t GetSizeUnsafe() const noexcept { return size_; }
|
||||||
|
size_t GetTotalObjectsSizeUnsafe() const noexcept { return totalObjectsSizeBytes_; }
|
||||||
|
|
||||||
void ClearForTests() {
|
void ClearForTests() {
|
||||||
root_.reset();
|
root_.reset();
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
size_ = 0;
|
size_ = 0;
|
||||||
|
totalObjectsSizeBytes_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Expects `mutex_` to be held by the current thread.
|
// Expects `mutex_` to be held by the current thread.
|
||||||
std::pair<unique_ptr<Node>, Node*> ExtractUnsafe(Node* previousNode) noexcept {
|
std::pair<unique_ptr<Node>, Node*> ExtractUnsafe(Node* previousNode, size_t objectSize) noexcept {
|
||||||
RuntimeAssert(root_ != nullptr, "Must not be empty");
|
RuntimeAssert(root_ != nullptr, "Must not be empty");
|
||||||
AssertCorrectUnsafe();
|
AssertCorrectUnsafe();
|
||||||
|
|
||||||
if (previousNode == nullptr) {
|
unique_ptr<Node> &pointerToNext = (previousNode == nullptr) ? root_ : previousNode->next_;
|
||||||
// Extracting the root.
|
auto node = std::move(pointerToNext);
|
||||||
auto node = std::move(root_);
|
pointerToNext = std::move(node->next_);
|
||||||
root_ = std::move(node->next_);
|
if (!pointerToNext) {
|
||||||
if (!root_) {
|
|
||||||
last_ = nullptr;
|
|
||||||
}
|
|
||||||
--size_;
|
|
||||||
AssertCorrectUnsafe();
|
|
||||||
return {std::move(node), root_.get()};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto node = std::move(previousNode->next_);
|
|
||||||
previousNode->next_ = std::move(node->next_);
|
|
||||||
if (!previousNode->next_) {
|
|
||||||
last_ = previousNode;
|
last_ = previousNode;
|
||||||
}
|
}
|
||||||
--size_;
|
--size_;
|
||||||
|
totalObjectsSizeBytes_ -= objectSize;
|
||||||
|
|
||||||
AssertCorrectUnsafe();
|
AssertCorrectUnsafe();
|
||||||
return {std::move(node), previousNode->next_.get()};
|
return {std::move(node), pointerToNext.get()};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects `mutex_` to be held by the current thread.
|
// Expects `mutex_` to be held by the current thread.
|
||||||
@@ -422,6 +418,7 @@ private:
|
|||||||
unique_ptr<Node> root_;
|
unique_ptr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
size_t size_ = 0;
|
size_t size_ = 0;
|
||||||
|
size_t totalObjectsSizeBytes_ = 0;
|
||||||
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -656,10 +653,12 @@ public:
|
|||||||
Iterator begin() noexcept { return Iterator(iter_.begin()); }
|
Iterator begin() noexcept { return Iterator(iter_.begin()); }
|
||||||
Iterator end() noexcept { return Iterator(iter_.end()); }
|
Iterator end() noexcept { return Iterator(iter_.end()); }
|
||||||
|
|
||||||
void EraseAndAdvance(Iterator& iterator) noexcept { iter_.EraseAndAdvance(iterator.iterator_); }
|
void EraseAndAdvance(Iterator& iterator) noexcept {
|
||||||
|
iter_.EraseAndAdvance(iterator.iterator_, GetAllocatedHeapSize(iterator->GetObjHeader()));
|
||||||
|
}
|
||||||
|
|
||||||
void MoveAndAdvance(FinalizerQueue& queue, Iterator& iterator) noexcept {
|
void MoveAndAdvance(FinalizerQueue& queue, Iterator& iterator) noexcept {
|
||||||
iter_.MoveAndAdvance(queue.consumer_, iterator.iterator_);
|
iter_.MoveAndAdvance(queue.consumer_, iterator.iterator_, GetAllocatedHeapSize(iterator->GetObjHeader()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -672,7 +671,8 @@ public:
|
|||||||
// Lock ObjectFactory for safe iteration.
|
// Lock ObjectFactory for safe iteration.
|
||||||
Iterable LockForIter() noexcept { return Iterable(*this); }
|
Iterable LockForIter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
size_t GetSizeUnsafe() const noexcept { return storage_.GetSizeUnsafe(); }
|
size_t GetObjectsCountUnsafe() const noexcept { return storage_.GetSizeUnsafe(); }
|
||||||
|
size_t GetTotalObjectsSizeUnsafe() const noexcept { return storage_.GetTotalObjectsSizeUnsafe(); }
|
||||||
|
|
||||||
void ClearForTests() { storage_.ClearForTests(); }
|
void ClearForTests() { storage_.ClearForTests(); }
|
||||||
|
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ TEST(ObjectFactoryStorageTest, EraseFirst) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() == 1) {
|
if (it->Data<int>() == 1) {
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -268,7 +268,7 @@ TEST(ObjectFactoryStorageTest, EraseMiddle) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() == 2) {
|
if (it->Data<int>() == 2) {
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ TEST(ObjectFactoryStorageTest, EraseLast) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() == 3) {
|
if (it->Data<int>() == 3) {
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -323,7 +323,7 @@ TEST(ObjectFactoryStorageTest, EraseAll) {
|
|||||||
{
|
{
|
||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +345,7 @@ TEST(ObjectFactoryStorageTest, EraseTheOnlyElement) {
|
|||||||
{
|
{
|
||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
auto it = iter.begin();
|
auto it = iter.begin();
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
EXPECT_THAT(it, iter.end());
|
EXPECT_THAT(it, iter.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +371,7 @@ TEST(ObjectFactoryStorageTest, MoveFirst) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() == 1) {
|
if (it->Data<int>() == 1) {
|
||||||
iter.MoveAndAdvance(consumer, it);
|
iter.MoveAndAdvance(consumer, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -403,7 +403,7 @@ TEST(ObjectFactoryStorageTest, MoveMiddle) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() == 2) {
|
if (it->Data<int>() == 2) {
|
||||||
iter.MoveAndAdvance(consumer, it);
|
iter.MoveAndAdvance(consumer, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -435,7 +435,7 @@ TEST(ObjectFactoryStorageTest, MoveLast) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() == 3) {
|
if (it->Data<int>() == 3) {
|
||||||
iter.MoveAndAdvance(consumer, it);
|
iter.MoveAndAdvance(consumer, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ TEST(ObjectFactoryStorageTest, MoveAll) {
|
|||||||
{
|
{
|
||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
iter.MoveAndAdvance(consumer, it);
|
iter.MoveAndAdvance(consumer, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ TEST(ObjectFactoryStorageTest, MergeWith) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() % 2 == 0) {
|
if (it->Data<int>() % 2 == 0) {
|
||||||
iter.MoveAndAdvance(consumer1, it);
|
iter.MoveAndAdvance(consumer1, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -508,7 +508,7 @@ TEST(ObjectFactoryStorageTest, MergeWith) {
|
|||||||
{
|
{
|
||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
iter.MoveAndAdvance(consumer2, it);
|
iter.MoveAndAdvance(consumer2, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +577,7 @@ TEST(ObjectFactoryStorageTest, MoveTheOnlyElement) {
|
|||||||
{
|
{
|
||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
auto it = iter.begin();
|
auto it = iter.begin();
|
||||||
iter.MoveAndAdvance(consumer, it);
|
iter.MoveAndAdvance(consumer, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
EXPECT_THAT(it, iter.end());
|
EXPECT_THAT(it, iter.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,8 +612,8 @@ TEST(ObjectFactoryStorageTest, MoveAndErase) {
|
|||||||
auto iter = storage.LockForIter();
|
auto iter = storage.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
++it;
|
++it;
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
iter.MoveAndAdvance(consumer, it);
|
iter.MoveAndAdvance(consumer, it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +759,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
|||||||
|
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->Data<int>() % 2 != 0) {
|
if (it->Data<int>() % 2 != 0) {
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it, ObjectFactoryStorageRegular::Node::GetSizeForDataSize(sizeof(int)));
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ NO_EXTERNAL_CALLS_CHECK bool kotlin::mm::RequestThreadsSuspension() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void kotlin::mm::WaitForThreadsSuspension() noexcept {
|
void kotlin::mm::WaitForThreadsSuspension() noexcept {
|
||||||
// Spin wating for threads to suspend. Ignore Native threads.
|
// Spin waiting for threads to suspend. Ignore Native threads.
|
||||||
while(!allThreads(isSuspendedOrNative)) {
|
while(!allThreads(isSuspendedOrNative)) {
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user