[K/N] Track stats in Mark
Tracking alive heap set size after Mark GC phase. Merge-request: KT-MR-5179 ^KT-48537
This commit is contained in:
committed by
Space
parent
7206c06d26
commit
c57df1ac40
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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 "Alloc.h"
|
||||||
|
|
||||||
|
namespace kotlin {
|
||||||
|
namespace gc {
|
||||||
|
|
||||||
|
class AlignedAllocator {
|
||||||
|
public:
|
||||||
|
void* Alloc(size_t size, size_t alignment) noexcept { return konanAllocAlignedMemory(size, alignment); }
|
||||||
|
static void Free(void* instance) noexcept { konanFreeMemory(instance); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename BaseAllocator, typename GCThreadData>
|
||||||
|
class AllocatorWithGC {
|
||||||
|
public:
|
||||||
|
AllocatorWithGC(BaseAllocator base, GCThreadData& gc) noexcept : base_(std::move(base)), gc_(gc) {}
|
||||||
|
|
||||||
|
void* Alloc(size_t size, size_t alignment) noexcept {
|
||||||
|
gc_.SafePointAllocation(size);
|
||||||
|
if (void* ptr = base_.Alloc(size, alignment)) {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
// Tell GC that we failed to allocate, and try one more time.
|
||||||
|
gc_.OnOOM(size);
|
||||||
|
return base_.Alloc(size, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Free(void* instance) noexcept { BaseAllocator::Free(instance); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
BaseAllocator base_;
|
||||||
|
GCThreadData& gc_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gc
|
||||||
|
} // namespace kotlin
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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 "Allocator.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class MockAllocator {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(void*, Alloc, (size_t, size_t));
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockAllocatorWrapper {
|
||||||
|
public:
|
||||||
|
MockAllocator& operator*() { return *mock_; }
|
||||||
|
|
||||||
|
void* Alloc(size_t size, size_t alignment) { return mock_->Alloc(size, alignment); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
KStdUniquePtr<testing::StrictMock<MockAllocator>> mock_ = make_unique<testing::StrictMock<MockAllocator>>();
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockGC {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(void, SafePointAllocation, (size_t));
|
||||||
|
MOCK_METHOD(void, OnOOM, (size_t));
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(AllocatorWithGCTest, AllocateWithoutOOM) {
|
||||||
|
constexpr size_t size = 256;
|
||||||
|
constexpr size_t alignment = 8;
|
||||||
|
void* nonNull = reinterpret_cast<void*>(1);
|
||||||
|
MockAllocatorWrapper baseAllocator;
|
||||||
|
testing::StrictMock<MockGC> gc;
|
||||||
|
{
|
||||||
|
testing::InSequence seq;
|
||||||
|
EXPECT_CALL(gc, SafePointAllocation(size));
|
||||||
|
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nonNull));
|
||||||
|
EXPECT_CALL(gc, OnOOM(_)).Times(0);
|
||||||
|
}
|
||||||
|
gc::AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
|
||||||
|
void* ptr = allocator.Alloc(size, alignment);
|
||||||
|
EXPECT_THAT(ptr, nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AllocatorWithGCTest, AllocateWithFixableOOM) {
|
||||||
|
constexpr size_t size = 256;
|
||||||
|
constexpr size_t alignment = 8;
|
||||||
|
void* nonNull = reinterpret_cast<void*>(1);
|
||||||
|
MockAllocatorWrapper baseAllocator;
|
||||||
|
testing::StrictMock<MockGC> gc;
|
||||||
|
{
|
||||||
|
testing::InSequence seq;
|
||||||
|
EXPECT_CALL(gc, SafePointAllocation(size));
|
||||||
|
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
|
||||||
|
EXPECT_CALL(gc, OnOOM(size));
|
||||||
|
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nonNull));
|
||||||
|
}
|
||||||
|
gc::AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
|
||||||
|
void* ptr = allocator.Alloc(size, alignment);
|
||||||
|
EXPECT_THAT(ptr, nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AllocatorWithGCTest, AllocateWithUnfixableOOM) {
|
||||||
|
constexpr size_t size = 256;
|
||||||
|
constexpr size_t alignment = 8;
|
||||||
|
MockAllocatorWrapper baseAllocator;
|
||||||
|
testing::StrictMock<MockGC> gc;
|
||||||
|
{
|
||||||
|
testing::InSequence seq;
|
||||||
|
EXPECT_CALL(gc, SafePointAllocation(size));
|
||||||
|
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
|
||||||
|
EXPECT_CALL(gc, OnOOM(size));
|
||||||
|
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
|
||||||
|
}
|
||||||
|
gc::AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
|
||||||
|
void* ptr = allocator.Alloc(size, alignment);
|
||||||
|
EXPECT_THAT(ptr, nullptr);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ namespace {
|
|||||||
class GCEmptySchedulerData : public gc::GCSchedulerData {
|
class GCEmptySchedulerData : public gc::GCSchedulerData {
|
||||||
void OnSafePoint(gc::GCSchedulerThreadData& threadData) noexcept override {}
|
void OnSafePoint(gc::GCSchedulerThreadData& threadData) noexcept override {}
|
||||||
void OnPerformFullGC() noexcept override {}
|
void OnPerformFullGC() noexcept override {}
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class GCSchedulerDataWithTimer : public gc::GCSchedulerData {
|
class GCSchedulerDataWithTimer : public gc::GCSchedulerData {
|
||||||
@@ -40,6 +41,8 @@ public:
|
|||||||
|
|
||||||
void OnPerformFullGC() noexcept override {}
|
void OnPerformFullGC() noexcept override {}
|
||||||
|
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnTimer() noexcept {
|
void OnTimer() noexcept {
|
||||||
auto allThreadsAreNative = []() {
|
auto allThreadsAreNative = []() {
|
||||||
@@ -83,6 +86,8 @@ public:
|
|||||||
|
|
||||||
void OnPerformFullGC() noexcept override { timeOfLastGcNs_ = currentTimeCallbackNs_(); }
|
void OnPerformFullGC() noexcept override { timeOfLastGcNs_ = currentTimeCallbackNs_(); }
|
||||||
|
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
gc::GCSchedulerConfig& config_;
|
gc::GCSchedulerConfig& config_;
|
||||||
CurrentTimeCallback currentTimeCallbackNs_;
|
CurrentTimeCallback currentTimeCallbackNs_;
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ public:
|
|||||||
|
|
||||||
// Always called by the GC thread.
|
// Always called by the GC thread.
|
||||||
virtual void OnPerformFullGC() noexcept = 0;
|
virtual void OnPerformFullGC() noexcept = 0;
|
||||||
|
|
||||||
|
// Always called by the GC thread.
|
||||||
|
virtual void UpdateAliveSetBytes(size_t bytes) noexcept = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GCSchedulerThreadData {
|
class GCSchedulerThreadData {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "ExtraObjectData.hpp"
|
#include "ExtraObjectData.hpp"
|
||||||
#include "FinalizerHooks.hpp"
|
#include "FinalizerHooks.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
|
#include "ObjectOps.hpp"
|
||||||
#include "ObjectTraversal.hpp"
|
#include "ObjectTraversal.hpp"
|
||||||
#include "Runtime.h"
|
#include "Runtime.h"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
@@ -16,9 +17,19 @@
|
|||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
namespace gc {
|
namespace gc {
|
||||||
|
|
||||||
|
struct MarkStats {
|
||||||
|
// How many objects are alive.
|
||||||
|
size_t aliveHeapSet = 0;
|
||||||
|
// How many objects are alive in bytes. Note: this does not include overhead of malloc/mimalloc itself.
|
||||||
|
size_t aliveHeapSetBytes = 0;
|
||||||
|
// How many times a marked object was found in the mark queue.
|
||||||
|
size_t duplicateEntries = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Because of `graySet` this implementation may allocate heap memory during GC.
|
// TODO: Because of `graySet` this implementation may allocate heap memory during GC.
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void Mark(KStdVector<ObjHeader*> graySet) noexcept {
|
MarkStats Mark(KStdVector<ObjHeader*> graySet) noexcept {
|
||||||
|
MarkStats stats;
|
||||||
while (!graySet.empty()) {
|
while (!graySet.empty()) {
|
||||||
ObjHeader* top = graySet.back();
|
ObjHeader* top = graySet.back();
|
||||||
graySet.pop_back();
|
graySet.pop_back();
|
||||||
@@ -27,8 +38,11 @@ void Mark(KStdVector<ObjHeader*> graySet) noexcept {
|
|||||||
|
|
||||||
if (top->heap()) {
|
if (top->heap()) {
|
||||||
if (!Traits::TryMark(top)) {
|
if (!Traits::TryMark(top)) {
|
||||||
|
++stats.duplicateEntries;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
stats.aliveHeapSet++;
|
||||||
|
stats.aliveHeapSetBytes += mm::GetAllocatedHeapSize(top);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top->heap() || top->local()) {
|
if (top->heap() || top->local()) {
|
||||||
@@ -46,6 +60,7 @@ void Mark(KStdVector<ObjHeader*> graySet) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
|
|||||||
@@ -143,10 +143,10 @@ public:
|
|||||||
return testing::UnorderedElementsAreArray(objects);
|
return testing::UnorderedElementsAreArray(objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mark(std::initializer_list<std::reference_wrapper<BaseObject>> graySet) {
|
gc::MarkStats Mark(std::initializer_list<std::reference_wrapper<BaseObject>> graySet) {
|
||||||
KStdVector<ObjHeader*> objects;
|
KStdVector<ObjHeader*> objects;
|
||||||
for (auto& object : graySet) objects.push_back(object.get().GetObjHeader());
|
for (auto& object : graySet) objects.push_back(object.get().GetObjHeader());
|
||||||
gc::Mark<ScopedMarkTraits>(std::move(objects));
|
return gc::Mark<ScopedMarkTraits>(std::move(objects));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -154,86 +154,118 @@ private:
|
|||||||
ScopedMarkTraits markTraits_;
|
ScopedMarkTraits markTraits_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define EXPECT_MARKED(...) EXPECT_THAT(marked(), MarkedMatcher({__VA_ARGS__}))
|
size_t GetObjectsSize(std::initializer_list<std::reference_wrapper<BaseObject>> objects) {
|
||||||
|
size_t size = 0;
|
||||||
|
for (auto& object : objects) {
|
||||||
|
size += mm::GetAllocatedHeapSize(object.get().GetObjHeader());
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXPECT_MARKED(stats, ...) \
|
||||||
|
do { \
|
||||||
|
std::initializer_list<std::reference_wrapper<BaseObject>> objects = {__VA_ARGS__}; \
|
||||||
|
EXPECT_THAT(stats.aliveHeapSet, objects.size()); \
|
||||||
|
EXPECT_THAT(stats.aliveHeapSetBytes, GetObjectsSize(objects)); \
|
||||||
|
EXPECT_THAT(marked(), MarkedMatcher(objects)); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkNothing) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkNothing) {
|
||||||
Mark({});
|
auto stats = Mark({});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObject) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObject) {
|
||||||
Object object;
|
Object object;
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED(object);
|
EXPECT_MARKED(stats, object);
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArray) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArray) {
|
||||||
ObjectArray array;
|
ObjectArray array;
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array);
|
EXPECT_MARKED(stats, array);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArray) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArray) {
|
||||||
CharArray array;
|
CharArray array;
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array);
|
EXPECT_MARKED(stats, array);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentObject) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentObject) {
|
||||||
Object object{BaseObject::Kind::kPermanent};
|
Object object{BaseObject::Kind::kPermanent};
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentObjectArray) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentObjectArray) {
|
||||||
ObjectArray array{BaseObject::Kind::kPermanent};
|
ObjectArray array{BaseObject::Kind::kPermanent};
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentCharArray) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentCharArray) {
|
||||||
CharArray array{BaseObject::Kind::kPermanent};
|
CharArray array{BaseObject::Kind::kPermanent};
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleStackObject) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleStackObject) {
|
||||||
Object object{BaseObject::Kind::kStackLocal};
|
Object object{BaseObject::Kind::kStackLocal};
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleStackObjectArray) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleStackObjectArray) {
|
||||||
ObjectArray array{BaseObject::Kind::kStackLocal};
|
ObjectArray array{BaseObject::Kind::kStackLocal};
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleStackCharArray) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleStackCharArray) {
|
||||||
CharArray array{BaseObject::Kind::kStackLocal};
|
CharArray array{BaseObject::Kind::kStackLocal};
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -241,18 +273,22 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithInvalidFields) {
|
|||||||
Object object;
|
Object object;
|
||||||
object->field1 = kInitializingSingleton;
|
object->field1 = kInitializingSingleton;
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED(object);
|
EXPECT_MARKED(stats, object);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFields) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFields) {
|
||||||
ObjectArray array;
|
ObjectArray array;
|
||||||
array.elements()[0] = kInitializingSingleton;
|
array.elements()[0] = kInitializingSingleton;
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array);
|
EXPECT_MARKED(stats, array);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeData) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeData) {
|
||||||
@@ -261,36 +297,44 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeData) {
|
|||||||
array.elements()[1] = 'b';
|
array.elements()[1] = 'b';
|
||||||
array.elements()[2] = 'c';
|
array.elements()[2] = 'c';
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array);
|
EXPECT_MARKED(stats, array);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithExtraData) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithExtraData) {
|
||||||
Object object;
|
Object object;
|
||||||
object.InstallExtraData();
|
object.InstallExtraData();
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED(object);
|
EXPECT_MARKED(stats, object);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithExtraData) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithExtraData) {
|
||||||
ObjectArray array;
|
ObjectArray array;
|
||||||
array.InstallExtraData();
|
array.InstallExtraData();
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array);
|
EXPECT_MARKED(stats, array);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithExtraData) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithExtraData) {
|
||||||
CharArray array;
|
CharArray array;
|
||||||
array.InstallExtraData();
|
array.InstallExtraData();
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array);
|
EXPECT_MARKED(stats, array);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithWeakCounter) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithWeakCounter) {
|
||||||
@@ -298,9 +342,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithWeakCounter) {
|
|||||||
Object object;
|
Object object;
|
||||||
object.InstallWeakCounter(weakCounter);
|
object.InstallWeakCounter(weakCounter);
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED(object, weakCounter);
|
EXPECT_MARKED(stats, object, weakCounter);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithWeakCounter) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithWeakCounter) {
|
||||||
@@ -308,9 +354,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithWeakCounter) {
|
|||||||
ObjectArray array;
|
ObjectArray array;
|
||||||
array.InstallWeakCounter(weakCounter);
|
array.InstallWeakCounter(weakCounter);
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array, weakCounter);
|
EXPECT_MARKED(stats, array, weakCounter);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithWeakCounter) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithWeakCounter) {
|
||||||
@@ -318,9 +366,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithWeakCounter) {
|
|||||||
CharArray array;
|
CharArray array;
|
||||||
array.InstallWeakCounter(weakCounter);
|
array.InstallWeakCounter(weakCounter);
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array, weakCounter);
|
EXPECT_MARKED(stats, array, weakCounter);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithInvalidFieldsWithWeakCounter) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithInvalidFieldsWithWeakCounter) {
|
||||||
@@ -329,9 +379,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithInvalidFieldsWithWeakCount
|
|||||||
object->field1 = kInitializingSingleton;
|
object->field1 = kInitializingSingleton;
|
||||||
object.InstallWeakCounter(weakCounter);
|
object.InstallWeakCounter(weakCounter);
|
||||||
|
|
||||||
Mark({object});
|
auto stats = Mark({object});
|
||||||
|
|
||||||
EXPECT_MARKED(object, weakCounter);
|
EXPECT_MARKED(stats, object, weakCounter);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFieldsWithWeakCounter) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFieldsWithWeakCounter) {
|
||||||
@@ -340,9 +392,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFieldsWithWeak
|
|||||||
array.elements()[0] = kInitializingSingleton;
|
array.elements()[0] = kInitializingSingleton;
|
||||||
array.InstallWeakCounter(weakCounter);
|
array.InstallWeakCounter(weakCounter);
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array, weakCounter);
|
EXPECT_MARKED(stats, array, weakCounter);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeDataWithWeakCounter) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeDataWithWeakCounter) {
|
||||||
@@ -353,9 +407,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeDataWithWeakCounter
|
|||||||
array.elements()[2] = 'c';
|
array.elements()[2] = 'c';
|
||||||
array.InstallWeakCounter(weakCounter);
|
array.InstallWeakCounter(weakCounter);
|
||||||
|
|
||||||
Mark({array});
|
auto stats = Mark({array});
|
||||||
|
|
||||||
EXPECT_MARKED(array, weakCounter);
|
EXPECT_MARKED(stats, array, weakCounter);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTree) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkTree) {
|
||||||
@@ -375,11 +431,13 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkTree) {
|
|||||||
root_field3.elements()[1] = root_field3_element2.header();
|
root_field3.elements()[1] = root_field3_element2.header();
|
||||||
root_field3.elements()[2] = root_field3_element3.header();
|
root_field3.elements()[2] = root_field3_element3.header();
|
||||||
|
|
||||||
Mark({root});
|
auto stats = Mark({root});
|
||||||
|
|
||||||
EXPECT_MARKED(
|
EXPECT_MARKED(
|
||||||
root, root_field1, root_field1_field1, root_field1_field2, root_field3, root_field3_element1, root_field3_element2,
|
stats, root, root_field1, root_field1_field1, root_field1_field2, root_field3, root_field3_element1, root_field3_element2,
|
||||||
root_field3_element3);
|
root_field3_element3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentRoot) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentRoot) {
|
||||||
@@ -399,9 +457,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentRoot) {
|
|||||||
root_field3.elements()[1] = root_field3_element2.header();
|
root_field3.elements()[1] = root_field3_element2.header();
|
||||||
root_field3.elements()[2] = root_field3_element3.header();
|
root_field3.elements()[2] = root_field3_element3.header();
|
||||||
|
|
||||||
Mark({root});
|
auto stats = Mark({root});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentMiddle) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentMiddle) {
|
||||||
@@ -421,9 +481,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentMiddle) {
|
|||||||
root_field3.elements()[1] = root_field3_element2.header();
|
root_field3.elements()[1] = root_field3_element2.header();
|
||||||
root_field3.elements()[2] = root_field3_element3.header();
|
root_field3.elements()[2] = root_field3_element3.header();
|
||||||
|
|
||||||
Mark({root});
|
auto stats = Mark({root});
|
||||||
|
|
||||||
EXPECT_MARKED(root, root_field3, root_field3_element1, root_field3_element2, root_field3_element3);
|
EXPECT_MARKED(stats, root, root_field3, root_field3_element1, root_field3_element2, root_field3_element3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentLeaf) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentLeaf) {
|
||||||
@@ -443,9 +505,12 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentLeaf) {
|
|||||||
root_field3.elements()[1] = root_field3_element2.header();
|
root_field3.elements()[1] = root_field3_element2.header();
|
||||||
root_field3.elements()[2] = root_field3_element3.header();
|
root_field3.elements()[2] = root_field3_element3.header();
|
||||||
|
|
||||||
Mark({root});
|
auto stats = Mark({root});
|
||||||
|
|
||||||
EXPECT_MARKED(root, root_field1, root_field1_field2, root_field3, root_field3_element1, root_field3_element2, root_field3_element3);
|
EXPECT_MARKED(
|
||||||
|
stats, root, root_field1, root_field1_field2, root_field3, root_field3_element1, root_field3_element2, root_field3_element3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithStackRoot) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithStackRoot) {
|
||||||
@@ -465,9 +530,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithStackRoot) {
|
|||||||
root_field3.elements()[1] = root_field3_element2.header();
|
root_field3.elements()[1] = root_field3_element2.header();
|
||||||
root_field3.elements()[2] = root_field3_element3.header();
|
root_field3.elements()[2] = root_field3_element3.header();
|
||||||
|
|
||||||
Mark({root, root_field3});
|
auto stats = Mark({root, root_field3});
|
||||||
|
|
||||||
EXPECT_MARKED(root_field3_element1, root_field3_element2);
|
EXPECT_MARKED(stats, root_field3_element1, root_field3_element2);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTree) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTree) {
|
||||||
@@ -478,9 +545,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTree) {
|
|||||||
inner1->field1 = inner2.header();
|
inner1->field1 = inner2.header();
|
||||||
inner2.elements()[0] = root.header();
|
inner2.elements()[0] = root.header();
|
||||||
|
|
||||||
Mark({root});
|
auto stats = Mark({root});
|
||||||
|
|
||||||
EXPECT_MARKED(root, inner1, inner2);
|
EXPECT_MARKED(stats, root, inner1, inner2);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithPermanentRoot) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithPermanentRoot) {
|
||||||
@@ -491,9 +560,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithPermanentRoot) {
|
|||||||
inner1->field1 = inner2.header();
|
inner1->field1 = inner2.header();
|
||||||
inner2.elements()[0] = root.header();
|
inner2.elements()[0] = root.header();
|
||||||
|
|
||||||
Mark({root});
|
auto stats = Mark({root});
|
||||||
|
|
||||||
EXPECT_MARKED();
|
EXPECT_MARKED(stats);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithStackRoot) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithStackRoot) {
|
||||||
@@ -509,9 +580,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithStackRoot) {
|
|||||||
root->field2 = inner3.header();
|
root->field2 = inner3.header();
|
||||||
inner3->field1 = inner2.header();
|
inner3->field1 = inner2.header();
|
||||||
|
|
||||||
Mark({root, inner1, inner2});
|
auto stats = Mark({root, inner1, inner2});
|
||||||
|
|
||||||
EXPECT_MARKED(inner3, inner2_element1);
|
EXPECT_MARKED(stats, inner3, inner2_element1);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -520,9 +593,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkForest) {
|
|||||||
ObjectArray root2;
|
ObjectArray root2;
|
||||||
Object root3;
|
Object root3;
|
||||||
|
|
||||||
Mark({root1, root2, root3});
|
auto stats = Mark({root1, root2, root3});
|
||||||
|
|
||||||
EXPECT_MARKED(root1, root2, root3);
|
EXPECT_MARKED(stats, root1, root2, root3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentFirst) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentFirst) {
|
||||||
@@ -530,9 +605,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentFirst) {
|
|||||||
ObjectArray root2;
|
ObjectArray root2;
|
||||||
Object root3;
|
Object root3;
|
||||||
|
|
||||||
Mark({root1, root2, root3});
|
auto stats = Mark({root1, root2, root3});
|
||||||
|
|
||||||
EXPECT_MARKED(root2, root3);
|
EXPECT_MARKED(stats, root2, root3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentSecond) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentSecond) {
|
||||||
@@ -540,9 +617,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentSecond) {
|
|||||||
ObjectArray root2{BaseObject::Kind::kPermanent};
|
ObjectArray root2{BaseObject::Kind::kPermanent};
|
||||||
Object root3;
|
Object root3;
|
||||||
|
|
||||||
Mark({root1, root2, root3});
|
auto stats = Mark({root1, root2, root3});
|
||||||
|
|
||||||
EXPECT_MARKED(root1, root3);
|
EXPECT_MARKED(stats, root1, root3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentThird) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentThird) {
|
||||||
@@ -550,9 +629,11 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentThird) {
|
|||||||
ObjectArray root2;
|
ObjectArray root2;
|
||||||
Object root3{BaseObject::Kind::kPermanent};
|
Object root3{BaseObject::Kind::kPermanent};
|
||||||
|
|
||||||
Mark({root1, root2, root3});
|
auto stats = Mark({root1, root2, root3});
|
||||||
|
|
||||||
EXPECT_MARKED(root1, root2);
|
EXPECT_MARKED(stats, root1, root2);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithInterconnectedRoots) {
|
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithInterconnectedRoots) {
|
||||||
@@ -564,7 +645,9 @@ TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithInterconnectedRoots) {
|
|||||||
root2.elements()[0] = root3.header();
|
root2.elements()[0] = root3.header();
|
||||||
root3->field1 = root1.header();
|
root3->field1 = root1.header();
|
||||||
|
|
||||||
Mark({root1, root2, root3});
|
auto stats = Mark({root1, root2, root3});
|
||||||
|
|
||||||
EXPECT_MARKED(root1, root2, root3);
|
EXPECT_MARKED(stats, root1, root2, root3);
|
||||||
|
|
||||||
|
EXPECT_THAT(stats.duplicateEntries, 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,12 @@ struct GC {
|
|||||||
State state = State::kUnmarked;
|
State state = State::kUnmarked;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using Allocator = gc::AlignedAllocator;
|
||||||
|
|
||||||
struct ThreadData {
|
struct ThreadData {
|
||||||
void SafePointAllocation(size_t) {}
|
void SafePointAllocation(size_t) {}
|
||||||
void OnOOM(size_t) {}
|
void OnOOM(size_t) {}
|
||||||
|
Allocator CreateAllocator() { return Allocator(); }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "Allocator.hpp"
|
||||||
#include "GCScheduler.hpp"
|
#include "GCScheduler.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
@@ -26,6 +27,8 @@ class NoOpGC : private Pinned {
|
|||||||
public:
|
public:
|
||||||
class ObjectData {};
|
class ObjectData {};
|
||||||
|
|
||||||
|
using Allocator = AlignedAllocator;
|
||||||
|
|
||||||
class ThreadData : private Pinned {
|
class ThreadData : private Pinned {
|
||||||
public:
|
public:
|
||||||
using ObjectData = NoOpGC::ObjectData;
|
using ObjectData = NoOpGC::ObjectData;
|
||||||
@@ -41,6 +44,8 @@ public:
|
|||||||
|
|
||||||
void OnOOM(size_t size) noexcept {}
|
void OnOOM(size_t size) noexcept {}
|
||||||
|
|
||||||
|
Allocator CreateAllocator() noexcept { return Allocator(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -193,9 +193,10 @@ bool gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
|||||||
RuntimeLogInfo(
|
RuntimeLogInfo(
|
||||||
{kTagGC}, "Collected root set of size %zu of which %zu are stable refs in %" PRIu64 " microseconds", graySet.size(),
|
{kTagGC}, "Collected root set of size %zu of which %zu are stable refs in %" PRIu64 " microseconds", graySet.size(),
|
||||||
stableRef, timeRootSetUs - timeSuspendUs);
|
stableRef, timeRootSetUs - timeSuspendUs);
|
||||||
gc::Mark<MarkTraits>(std::move(graySet));
|
auto markStats = gc::Mark<MarkTraits>(std::move(graySet));
|
||||||
auto timeMarkUs = konan::getTimeMicros();
|
auto timeMarkUs = konan::getTimeMicros();
|
||||||
RuntimeLogDebug({kTagGC}, "Marked in %" PRIu64 " microseconds", timeMarkUs - timeRootSetUs);
|
RuntimeLogDebug({kTagGC}, "Marked %zu objects in %" PRIu64 " microseconds. Processed %zu duplicate entries in the gray set", markStats.aliveHeapSet, timeMarkUs - timeRootSetUs, markStats.duplicateEntries);
|
||||||
|
scheduler.gcData().UpdateAliveSetBytes(markStats.aliveHeapSetBytes);
|
||||||
gc::SweepExtraObjects<SweepTraits>(mm::GlobalData::Instance().extraObjectDataFactory());
|
gc::SweepExtraObjects<SweepTraits>(mm::GlobalData::Instance().extraObjectDataFactory());
|
||||||
auto timeSweepExtraObjectsUs = konan::getTimeMicros();
|
auto timeSweepExtraObjectsUs = konan::getTimeMicros();
|
||||||
RuntimeLogDebug({kTagGC}, "Sweeped extra objects in %" PRIu64 " microseconds", timeSweepExtraObjectsUs - timeMarkUs);
|
RuntimeLogDebug({kTagGC}, "Sweeped extra objects in %" PRIu64 " microseconds", timeSweepExtraObjectsUs - timeMarkUs);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "Allocator.hpp"
|
||||||
#include "GCScheduler.hpp"
|
#include "GCScheduler.hpp"
|
||||||
#include "ObjectFactory.hpp"
|
#include "ObjectFactory.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
@@ -48,6 +49,7 @@ public:
|
|||||||
class ThreadData : private Pinned {
|
class ThreadData : private Pinned {
|
||||||
public:
|
public:
|
||||||
using ObjectData = SameThreadMarkAndSweep::ObjectData;
|
using ObjectData = SameThreadMarkAndSweep::ObjectData;
|
||||||
|
using Allocator = AllocatorWithGC<AlignedAllocator, ThreadData>;
|
||||||
|
|
||||||
explicit ThreadData(SameThreadMarkAndSweep& gc, mm::ThreadData& threadData) noexcept : gc_(gc), threadData_(threadData) {}
|
explicit ThreadData(SameThreadMarkAndSweep& gc, mm::ThreadData& threadData) noexcept : gc_(gc), threadData_(threadData) {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
@@ -61,6 +63,8 @@ public:
|
|||||||
|
|
||||||
void OnOOM(size_t size) noexcept;
|
void OnOOM(size_t size) noexcept;
|
||||||
|
|
||||||
|
Allocator CreateAllocator() noexcept { return Allocator(AlignedAllocator(), *this); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SafePointRegular(size_t weight) noexcept;
|
void SafePointRegular(size_t weight) noexcept;
|
||||||
void SafePointSlowPath(SafepointFlag flag) noexcept;
|
void SafePointSlowPath(SafepointFlag flag) noexcept;
|
||||||
@@ -69,6 +73,8 @@ public:
|
|||||||
mm::ThreadData& threadData_;
|
mm::ThreadData& threadData_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using Allocator = ThreadData::Allocator;
|
||||||
|
|
||||||
SameThreadMarkAndSweep() noexcept;
|
SameThreadMarkAndSweep() noexcept;
|
||||||
~SameThreadMarkAndSweep() = default;
|
~SameThreadMarkAndSweep() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ public:
|
|||||||
public:
|
public:
|
||||||
~Node() = default;
|
~Node() = default;
|
||||||
|
|
||||||
|
constexpr static std::pair<size_t, size_t> GetSizeAndAlignmentForDataSize(size_t dataSize) noexcept {
|
||||||
|
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
|
||||||
|
size_t totalAlignment = std::max(alignof(Node), DataAlignment);
|
||||||
|
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, totalAlignment);
|
||||||
|
return std::make_pair(totalSize, totalAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
static Node& FromData(void* data) noexcept {
|
static Node& FromData(void* data) noexcept {
|
||||||
constexpr size_t kDataOffset = DataOffset();
|
constexpr size_t kDataOffset = DataOffset();
|
||||||
Node* node = reinterpret_cast<Node*>(reinterpret_cast<uintptr_t>(data) - kDataOffset);
|
Node* node = reinterpret_cast<Node*>(reinterpret_cast<uintptr_t>(data) - kDataOffset);
|
||||||
@@ -80,9 +87,7 @@ public:
|
|||||||
Node() noexcept = default;
|
Node() noexcept = default;
|
||||||
|
|
||||||
static unique_ptr<Node> Create(Allocator& allocator, size_t dataSize) noexcept {
|
static unique_ptr<Node> Create(Allocator& allocator, size_t dataSize) noexcept {
|
||||||
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
|
auto [totalSize, totalAlignment] = GetSizeAndAlignmentForDataSize(dataSize);
|
||||||
size_t totalAlignment = std::max(alignof(Node), DataAlignment);
|
|
||||||
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, totalAlignment);
|
|
||||||
RuntimeAssert(
|
RuntimeAssert(
|
||||||
DataOffset() + dataSize <= totalSize, "totalSize %zu is not enough to fit data %zu at offset %zu", totalSize, dataSize,
|
DataOffset() + dataSize <= totalSize, "totalSize %zu is not enough to fit data %zu at offset %zu", totalSize, dataSize,
|
||||||
DataOffset());
|
DataOffset());
|
||||||
@@ -392,43 +397,13 @@ private:
|
|||||||
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SimpleAllocator {
|
|
||||||
public:
|
|
||||||
void* Alloc(size_t size, size_t alignment) noexcept { return konanAllocAlignedMemory(size, alignment); }
|
|
||||||
|
|
||||||
static void Free(void* instance) noexcept { konanFreeMemory(instance); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename BaseAllocator, typename GC>
|
|
||||||
class AllocatorWithGC {
|
|
||||||
public:
|
|
||||||
AllocatorWithGC(BaseAllocator base, GC& gc) noexcept : base_(std::move(base)), gc_(gc) {}
|
|
||||||
|
|
||||||
void* Alloc(size_t size, size_t alignment) noexcept {
|
|
||||||
gc_.SafePointAllocation(size);
|
|
||||||
if (void* ptr = base_.Alloc(size, alignment)) {
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
// Tell GC that we failed to allocate, and try one more time.
|
|
||||||
gc_.OnOOM(size);
|
|
||||||
return base_.Alloc(size, alignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Free(void* instance) noexcept { BaseAllocator::Free(instance); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
BaseAllocator base_;
|
|
||||||
GC& gc_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
template <typename GC>
|
template <typename GC>
|
||||||
class ObjectFactory : private Pinned {
|
class ObjectFactory : private Pinned {
|
||||||
using GCObjectData = typename GC::ObjectData;
|
using GCObjectData = typename GC::ObjectData;
|
||||||
using GCThreadData = typename GC::ThreadData;
|
using GCThreadData = typename GC::ThreadData;
|
||||||
|
using Allocator = typename GC::Allocator;
|
||||||
using Allocator = internal::AllocatorWithGC<internal::SimpleAllocator, GCThreadData>;
|
|
||||||
|
|
||||||
struct HeapObjHeader {
|
struct HeapObjHeader {
|
||||||
GCObjectData gcData;
|
GCObjectData gcData;
|
||||||
@@ -523,13 +498,17 @@ public:
|
|||||||
typename Storage::Producer::Iterator iterator_;
|
typename Storage::Producer::Iterator iterator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
ThreadQueue(ObjectFactory& owner, GCThreadData& gc) noexcept :
|
ThreadQueue(ObjectFactory& owner, GCThreadData& gc) noexcept : producer_(owner.storage_, gc.CreateAllocator()) {}
|
||||||
producer_(owner.storage_, internal::AllocatorWithGC(internal::SimpleAllocator(), gc)) {}
|
|
||||||
|
static size_t ObjectAllocatedSize(const TypeInfo* typeInfo) noexcept {
|
||||||
|
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
|
||||||
|
size_t allocSize = ObjectAllocatedDataSize(typeInfo);
|
||||||
|
return Storage::Node::GetSizeAndAlignmentForDataSize(allocSize).first;
|
||||||
|
}
|
||||||
|
|
||||||
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept {
|
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept {
|
||||||
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
|
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
|
||||||
size_t membersSize = typeInfo->instanceSize_ - sizeof(ObjHeader);
|
size_t allocSize = ObjectAllocatedDataSize(typeInfo);
|
||||||
size_t allocSize = AlignUp(sizeof(HeapObjHeader) + membersSize, kObjectAlignment);
|
|
||||||
auto& node = producer_.Insert(allocSize);
|
auto& node = producer_.Insert(allocSize);
|
||||||
auto* heapObject = new (node.Data()) HeapObjHeader();
|
auto* heapObject = new (node.Data()) HeapObjHeader();
|
||||||
auto* object = &heapObject->object;
|
auto* object = &heapObject->object;
|
||||||
@@ -538,11 +517,15 @@ public:
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t ArrayAllocatedSize(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
||||||
|
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
||||||
|
size_t allocSize = ArrayAllocatedDataSize(typeInfo, count);
|
||||||
|
return Storage::Node::GetSizeAndAlignmentForDataSize(allocSize).first;
|
||||||
|
}
|
||||||
|
|
||||||
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
||||||
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
|
||||||
uint32_t membersSize = static_cast<uint32_t>(-typeInfo->instanceSize_) * count;
|
size_t allocSize = ArrayAllocatedDataSize(typeInfo, count);
|
||||||
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
|
||||||
size_t allocSize = AlignUp(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
|
|
||||||
auto& node = producer_.Insert(allocSize);
|
auto& node = producer_.Insert(allocSize);
|
||||||
auto* heapArray = new (node.Data()) HeapArrayHeader();
|
auto* heapArray = new (node.Data()) HeapArrayHeader();
|
||||||
auto* array = &heapArray->array;
|
auto* array = &heapArray->array;
|
||||||
@@ -560,6 +543,17 @@ public:
|
|||||||
void ClearForTests() noexcept { producer_.ClearForTests(); }
|
void ClearForTests() noexcept { producer_.ClearForTests(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static size_t ObjectAllocatedDataSize(const TypeInfo* typeInfo) noexcept {
|
||||||
|
size_t membersSize = typeInfo->instanceSize_ - sizeof(ObjHeader);
|
||||||
|
return AlignUp(sizeof(HeapObjHeader) + membersSize, kObjectAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t ArrayAllocatedDataSize(const TypeInfo* typeInfo, uint32_t count) noexcept {
|
||||||
|
uint32_t membersSize = static_cast<uint32_t>(-typeInfo->instanceSize_) * count;
|
||||||
|
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
||||||
|
return AlignUp(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
typename Storage::Producer producer_;
|
typename Storage::Producer producer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "Allocator.hpp"
|
||||||
#include "FinalizerHooksTestSupport.hpp"
|
#include "FinalizerHooksTestSupport.hpp"
|
||||||
|
#include "ObjectOps.hpp"
|
||||||
#include "ObjectTestSupport.hpp"
|
#include "ObjectTestSupport.hpp"
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
@@ -23,7 +25,7 @@ using testing::_;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using SimpleAllocator = mm::internal::SimpleAllocator;
|
using SimpleAllocator = gc::AlignedAllocator;
|
||||||
|
|
||||||
template <size_t DataAlignment>
|
template <size_t DataAlignment>
|
||||||
using ObjectFactoryStorage = mm::internal::ObjectFactoryStorage<DataAlignment, SimpleAllocator>;
|
using ObjectFactoryStorage = mm::internal::ObjectFactoryStorage<DataAlignment, SimpleAllocator>;
|
||||||
@@ -691,86 +693,61 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
|||||||
EXPECT_THAT(storage.GetSizeUnsafe(), expectedAfter.size());
|
EXPECT_THAT(storage.GetSizeUnsafe(), expectedAfter.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
using mm::internal::AllocatorWithGC;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class MockAllocator {
|
class MockAllocator : private Pinned {
|
||||||
public:
|
public:
|
||||||
|
MockAllocator();
|
||||||
|
~MockAllocator();
|
||||||
|
|
||||||
MOCK_METHOD(void*, Alloc, (size_t, size_t));
|
MOCK_METHOD(void*, Alloc, (size_t, size_t));
|
||||||
|
MOCK_METHOD(void, Free, (void*));
|
||||||
|
|
||||||
|
void* DefaultAlloc(size_t size, size_t alignment) { return konanAllocAlignedMemory(size, alignment); }
|
||||||
|
|
||||||
|
void DefaultFree(void* instance) { konanFreeMemory(instance); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MockAllocatorWrapper {
|
class GlobalMockAllocator {
|
||||||
public:
|
public:
|
||||||
MockAllocator& operator*() { return *mock_; }
|
void* Alloc(size_t size, size_t alignment) {
|
||||||
|
RuntimeAssert(instance_ != nullptr, "Global allocator must be set");
|
||||||
|
return instance_->Alloc(size, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
void* Alloc(size_t size, size_t alignment) { return mock_->Alloc(size, alignment); }
|
static void Free(void* instance) {
|
||||||
|
RuntimeAssert(instance_ != nullptr, "Global allocator must be set");
|
||||||
|
instance_->Free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetMockAllocator(MockAllocator* instance) {
|
||||||
|
RuntimeAssert(instance != nullptr, "Cannot be null");
|
||||||
|
RuntimeAssert(instance_ == nullptr, "No global allocator can be set");
|
||||||
|
instance_ = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ClearMockAllocator(MockAllocator* instance) {
|
||||||
|
RuntimeAssert(instance != nullptr, "Cannot be null");
|
||||||
|
RuntimeAssert(instance_ == instance, "Allocators must match");
|
||||||
|
instance_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KStdUniquePtr<testing::StrictMock<MockAllocator>> mock_ = make_unique<testing::StrictMock<MockAllocator>>();
|
static MockAllocator* instance_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MockGC {
|
MockAllocator::MockAllocator() {
|
||||||
public:
|
GlobalMockAllocator::SetMockAllocator(this);
|
||||||
MOCK_METHOD(void, SafePointAllocation, (size_t));
|
ON_CALL(*this, Alloc(_, _)).WillByDefault([this](size_t size, size_t alignment) { return DefaultAlloc(size, alignment); });
|
||||||
MOCK_METHOD(void, OnOOM, (size_t));
|
ON_CALL(*this, Free(_)).WillByDefault([this](void* instance) { DefaultFree(instance); });
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST(AllocatorWithGCTest, AllocateWithoutOOM) {
|
|
||||||
constexpr size_t size = 256;
|
|
||||||
constexpr size_t alignment = 8;
|
|
||||||
void* nonNull = reinterpret_cast<void*>(1);
|
|
||||||
MockAllocatorWrapper baseAllocator;
|
|
||||||
testing::StrictMock<MockGC> gc;
|
|
||||||
{
|
|
||||||
testing::InSequence seq;
|
|
||||||
EXPECT_CALL(gc, SafePointAllocation(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nonNull));
|
|
||||||
EXPECT_CALL(gc, OnOOM(_)).Times(0);
|
|
||||||
}
|
|
||||||
AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
|
|
||||||
void* ptr = allocator.Alloc(size, alignment);
|
|
||||||
EXPECT_THAT(ptr, nonNull);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AllocatorWithGCTest, AllocateWithFixableOOM) {
|
MockAllocator::~MockAllocator() {
|
||||||
constexpr size_t size = 256;
|
GlobalMockAllocator::ClearMockAllocator(this);
|
||||||
constexpr size_t alignment = 8;
|
|
||||||
void* nonNull = reinterpret_cast<void*>(1);
|
|
||||||
MockAllocatorWrapper baseAllocator;
|
|
||||||
testing::StrictMock<MockGC> gc;
|
|
||||||
{
|
|
||||||
testing::InSequence seq;
|
|
||||||
EXPECT_CALL(gc, SafePointAllocation(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
|
|
||||||
EXPECT_CALL(gc, OnOOM(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nonNull));
|
|
||||||
}
|
|
||||||
AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
|
|
||||||
void* ptr = allocator.Alloc(size, alignment);
|
|
||||||
EXPECT_THAT(ptr, nonNull);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AllocatorWithGCTest, AllocateWithUnfixableOOM) {
|
// static
|
||||||
constexpr size_t size = 256;
|
MockAllocator* GlobalMockAllocator::instance_ = nullptr;
|
||||||
constexpr size_t alignment = 8;
|
|
||||||
MockAllocatorWrapper baseAllocator;
|
|
||||||
testing::StrictMock<MockGC> gc;
|
|
||||||
{
|
|
||||||
testing::InSequence seq;
|
|
||||||
EXPECT_CALL(gc, SafePointAllocation(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
|
|
||||||
EXPECT_CALL(gc, OnOOM(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
|
|
||||||
}
|
|
||||||
AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
|
|
||||||
void* ptr = allocator.Alloc(size, alignment);
|
|
||||||
EXPECT_THAT(ptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class GC {
|
class GC {
|
||||||
public:
|
public:
|
||||||
@@ -778,11 +755,15 @@ public:
|
|||||||
uint32_t flags = 42;
|
uint32_t flags = 42;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using Allocator = GlobalMockAllocator;
|
||||||
|
|
||||||
class ThreadData {
|
class ThreadData {
|
||||||
public:
|
public:
|
||||||
void SafePointAllocation(size_t size) noexcept {}
|
void SafePointAllocation(size_t size) noexcept {}
|
||||||
|
|
||||||
void OnOOM(size_t size) noexcept {}
|
void OnOOM(size_t size) noexcept {}
|
||||||
|
|
||||||
|
Allocator CreateAllocator() noexcept { return Allocator(); }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -801,12 +782,26 @@ struct Payload {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, CreateObject) {
|
TEST(ObjectFactoryTest, CreateObject) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||||
GC::ThreadData gc;
|
GC::ThreadData gc;
|
||||||
ObjectFactory objectFactory;
|
ObjectFactory objectFactory;
|
||||||
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
||||||
|
|
||||||
|
size_t allocSize = 0;
|
||||||
|
void* allocAddress = nullptr;
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).WillOnce([&](size_t size, size_t alignment) {
|
||||||
|
allocSize = size;
|
||||||
|
allocAddress = allocator.DefaultAlloc(size, alignment);
|
||||||
|
return allocAddress;
|
||||||
|
});
|
||||||
auto* object = threadQueue.CreateObject(type.typeInfo());
|
auto* object = threadQueue.CreateObject(type.typeInfo());
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
|
EXPECT_THAT(allocSize, testing::Gt<size_t>(type.typeInfo()->instanceSize_));
|
||||||
|
EXPECT_THAT(allocAddress, testing::Ne(nullptr));
|
||||||
|
EXPECT_THAT(mm::GetAllocatedHeapSize(object), allocSize);
|
||||||
|
|
||||||
threadQueue.Publish();
|
threadQueue.Publish();
|
||||||
|
|
||||||
auto node = ObjectFactory::NodeRef::From(object);
|
auto node = ObjectFactory::NodeRef::From(object);
|
||||||
@@ -819,14 +814,30 @@ TEST(ObjectFactoryTest, CreateObject) {
|
|||||||
EXPECT_THAT(*it, node);
|
EXPECT_THAT(*it, node);
|
||||||
++it;
|
++it;
|
||||||
EXPECT_THAT(it, iter.end());
|
EXPECT_THAT(it, iter.end());
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Free(allocAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, CreateObjectArray) {
|
TEST(ObjectFactoryTest, CreateObjectArray) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
GC::ThreadData gc;
|
GC::ThreadData gc;
|
||||||
ObjectFactory objectFactory;
|
ObjectFactory objectFactory;
|
||||||
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
||||||
|
|
||||||
|
size_t allocSize = 0;
|
||||||
|
void* allocAddress = nullptr;
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).WillOnce([&](size_t size, size_t alignment) {
|
||||||
|
allocSize = size;
|
||||||
|
allocAddress = allocator.DefaultAlloc(size, alignment);
|
||||||
|
return allocAddress;
|
||||||
|
});
|
||||||
auto* array = threadQueue.CreateArray(theArrayTypeInfo, 3);
|
auto* array = threadQueue.CreateArray(theArrayTypeInfo, 3);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
|
EXPECT_THAT(allocSize, testing::Gt<size_t>(-theArrayTypeInfo->instanceSize_ * 3));
|
||||||
|
EXPECT_THAT(allocAddress, testing::Ne(nullptr));
|
||||||
|
EXPECT_THAT(mm::GetAllocatedHeapSize(array->obj()), allocSize);
|
||||||
|
|
||||||
threadQueue.Publish();
|
threadQueue.Publish();
|
||||||
|
|
||||||
auto node = ObjectFactory::NodeRef::From(array);
|
auto node = ObjectFactory::NodeRef::From(array);
|
||||||
@@ -839,14 +850,30 @@ TEST(ObjectFactoryTest, CreateObjectArray) {
|
|||||||
EXPECT_THAT(*it, node);
|
EXPECT_THAT(*it, node);
|
||||||
++it;
|
++it;
|
||||||
EXPECT_THAT(it, iter.end());
|
EXPECT_THAT(it, iter.end());
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Free(allocAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, CreateCharArray) {
|
TEST(ObjectFactoryTest, CreateCharArray) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
GC::ThreadData gc;
|
GC::ThreadData gc;
|
||||||
ObjectFactory objectFactory;
|
ObjectFactory objectFactory;
|
||||||
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
||||||
|
|
||||||
|
size_t allocSize = 0;
|
||||||
|
void* allocAddress = nullptr;
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).WillOnce([&](size_t size, size_t alignment) {
|
||||||
|
allocSize = size;
|
||||||
|
allocAddress = allocator.DefaultAlloc(size, alignment);
|
||||||
|
return allocAddress;
|
||||||
|
});
|
||||||
auto* array = threadQueue.CreateArray(theCharArrayTypeInfo, 3);
|
auto* array = threadQueue.CreateArray(theCharArrayTypeInfo, 3);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
|
EXPECT_THAT(allocSize, testing::Gt<size_t>(-theCharArrayTypeInfo->instanceSize_ * 3));
|
||||||
|
EXPECT_THAT(allocAddress, testing::Ne(nullptr));
|
||||||
|
EXPECT_THAT(mm::GetAllocatedHeapSize(array->obj()), allocSize);
|
||||||
|
|
||||||
threadQueue.Publish();
|
threadQueue.Publish();
|
||||||
|
|
||||||
auto node = ObjectFactory::NodeRef::From(array);
|
auto node = ObjectFactory::NodeRef::From(array);
|
||||||
@@ -859,18 +886,24 @@ TEST(ObjectFactoryTest, CreateCharArray) {
|
|||||||
EXPECT_THAT(*it, node);
|
EXPECT_THAT(*it, node);
|
||||||
++it;
|
++it;
|
||||||
EXPECT_THAT(it, iter.end());
|
EXPECT_THAT(it, iter.end());
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Free(allocAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, Erase) {
|
TEST(ObjectFactoryTest, Erase) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||||
GC::ThreadData gc;
|
GC::ThreadData gc;
|
||||||
ObjectFactory objectFactory;
|
ObjectFactory objectFactory;
|
||||||
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).Times(20);
|
||||||
for (int i = 0; i < 10; ++i) {
|
for (int i = 0; i < 10; ++i) {
|
||||||
threadQueue.CreateObject(objectType.typeInfo());
|
threadQueue.CreateObject(objectType.typeInfo());
|
||||||
threadQueue.CreateArray(theArrayTypeInfo, 3);
|
threadQueue.CreateArray(theArrayTypeInfo, 3);
|
||||||
}
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
|
|
||||||
threadQueue.Publish();
|
threadQueue.Publish();
|
||||||
|
|
||||||
@@ -878,7 +911,9 @@ TEST(ObjectFactoryTest, Erase) {
|
|||||||
auto iter = objectFactory.LockForIter();
|
auto iter = objectFactory.LockForIter();
|
||||||
for (auto it = iter.begin(); it != iter.end();) {
|
for (auto it = iter.begin(); it != iter.end();) {
|
||||||
if (it->IsArray()) {
|
if (it->IsArray()) {
|
||||||
|
EXPECT_CALL(allocator, Free(_));
|
||||||
iter.EraseAndAdvance(it);
|
iter.EraseAndAdvance(it);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -893,19 +928,24 @@ TEST(ObjectFactoryTest, Erase) {
|
|||||||
}
|
}
|
||||||
EXPECT_THAT(count, 10);
|
EXPECT_THAT(count, 10);
|
||||||
}
|
}
|
||||||
|
EXPECT_CALL(allocator, Free(_)).Times(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, Move) {
|
TEST(ObjectFactoryTest, Move) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||||
GC::ThreadData gc;
|
GC::ThreadData gc;
|
||||||
ObjectFactory objectFactory;
|
ObjectFactory objectFactory;
|
||||||
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
||||||
ObjectFactory::FinalizerQueue finalizerQueue;
|
ObjectFactory::FinalizerQueue finalizerQueue;
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).Times(20);
|
||||||
for (int i = 0; i < 10; ++i) {
|
for (int i = 0; i < 10; ++i) {
|
||||||
threadQueue.CreateObject(objectType.typeInfo());
|
threadQueue.CreateObject(objectType.typeInfo());
|
||||||
threadQueue.CreateArray(theArrayTypeInfo, 3);
|
threadQueue.CreateArray(theArrayTypeInfo, 3);
|
||||||
}
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
|
|
||||||
threadQueue.Publish();
|
threadQueue.Publish();
|
||||||
|
|
||||||
@@ -937,9 +977,13 @@ TEST(ObjectFactoryTest, Move) {
|
|||||||
}
|
}
|
||||||
EXPECT_THAT(count, 10);
|
EXPECT_THAT(count, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Free(_)).Times(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, RunFinalizers) {
|
TEST(ObjectFactoryTest, RunFinalizers) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
FinalizerHooksTestSupport finalizerHooks;
|
FinalizerHooksTestSupport finalizerHooks;
|
||||||
|
|
||||||
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>().addFlag(TF_HAS_FINALIZER)};
|
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>().addFlag(TF_HAS_FINALIZER)};
|
||||||
@@ -949,9 +993,11 @@ TEST(ObjectFactoryTest, RunFinalizers) {
|
|||||||
ObjectFactory::FinalizerQueue finalizerQueue;
|
ObjectFactory::FinalizerQueue finalizerQueue;
|
||||||
|
|
||||||
KStdVector<ObjHeader*> objects;
|
KStdVector<ObjHeader*> objects;
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).Times(10);
|
||||||
for (int i = 0; i < 10; ++i) {
|
for (int i = 0; i < 10; ++i) {
|
||||||
objects.push_back(threadQueue.CreateObject(objectType.typeInfo()));
|
objects.push_back(threadQueue.CreateObject(objectType.typeInfo()));
|
||||||
}
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
|
|
||||||
threadQueue.Publish();
|
threadQueue.Publish();
|
||||||
|
|
||||||
@@ -968,9 +1014,12 @@ TEST(ObjectFactoryTest, RunFinalizers) {
|
|||||||
finalizerQueue.Finalize();
|
finalizerQueue.Finalize();
|
||||||
// Hooks called before `FinalizerQueue` destructor.
|
// Hooks called before `FinalizerQueue` destructor.
|
||||||
testing::Mock::VerifyAndClearExpectations(&finalizerHooks.finalizerHook());
|
testing::Mock::VerifyAndClearExpectations(&finalizerHooks.finalizerHook());
|
||||||
|
EXPECT_CALL(allocator, Free(_)).Times(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryTest, ConcurrentPublish) {
|
TEST(ObjectFactoryTest, ConcurrentPublish) {
|
||||||
|
testing::StrictMock<MockAllocator> allocator;
|
||||||
|
|
||||||
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||||
ObjectFactory objectFactory;
|
ObjectFactory objectFactory;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
@@ -980,6 +1029,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
|
|||||||
std::mutex expectedMutex;
|
std::mutex expectedMutex;
|
||||||
KStdVector<ObjHeader*> expected;
|
KStdVector<ObjHeader*> expected;
|
||||||
|
|
||||||
|
EXPECT_CALL(allocator, Alloc(_, _)).Times(kThreadCount);
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
threads.emplace_back([&type, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
|
threads.emplace_back([&type, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
|
||||||
GC::ThreadData gc;
|
GC::ThreadData gc;
|
||||||
@@ -998,6 +1048,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
|
|||||||
|
|
||||||
while (readyCount < kThreadCount) {
|
while (readyCount < kThreadCount) {
|
||||||
}
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&allocator);
|
||||||
canStart = true;
|
canStart = true;
|
||||||
for (auto& t : threads) {
|
for (auto& t : threads) {
|
||||||
t.join();
|
t.join();
|
||||||
@@ -1010,4 +1061,5 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
|
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
|
||||||
|
EXPECT_CALL(allocator, Free(_)).Times(kThreadCount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,3 +69,13 @@ OBJ_GETTER(mm::AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo,
|
|||||||
// `ArrayHeader` and `ObjHeader` are expected to be compatible.
|
// `ArrayHeader` and `ObjHeader` are expected to be compatible.
|
||||||
RETURN_OBJ(reinterpret_cast<ObjHeader*>(array));
|
RETURN_OBJ(reinterpret_cast<ObjHeader*>(array));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t mm::GetAllocatedHeapSize(ObjHeader* object) noexcept {
|
||||||
|
RuntimeAssert(object->heap(), "Object must be a heap object");
|
||||||
|
const auto* typeInfo = object->type_info();
|
||||||
|
if (typeInfo->IsArray()) {
|
||||||
|
return mm::ObjectFactory<gc::GC>::ThreadQueue::ArrayAllocatedSize(typeInfo, object->array()->count_);
|
||||||
|
} else {
|
||||||
|
return mm::ObjectFactory<gc::GC>::ThreadQueue::ObjectAllocatedSize(typeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ OBJ_GETTER(CompareAndSwapHeapRef, ObjHeader** location, ObjHeader* expected, Obj
|
|||||||
OBJ_GETTER(AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept;
|
OBJ_GETTER(AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept;
|
||||||
OBJ_GETTER(AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) noexcept;
|
OBJ_GETTER(AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) noexcept;
|
||||||
|
|
||||||
|
// This does not take into account how much storage did the underlying allocator (malloc/mimalloc) reserved.
|
||||||
|
size_t GetAllocatedHeapSize(ObjHeader* object) noexcept;
|
||||||
|
|
||||||
} // namespace mm
|
} // namespace mm
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user