Add Stop-the-World Mark & Sweep GC for single-threaded programs.
GC implementation for testing purposes * Only works for a single mutator * Runs on the same thread as the mutator * Runs every nth checkpoint and after every m bytes are allocated * Runs finalizers after sweeping on the mutator thread.
This commit is contained in:
committed by
Space
parent
e7102cd63f
commit
71397a06fb
@@ -1024,8 +1024,7 @@ task freeze5(type: KonanLocalTest) {
|
||||
}
|
||||
|
||||
task freeze6(type: KonanLocalTest) {
|
||||
enabled = (project.testTarget != 'wasm32') && // No exceptions on WASM.
|
||||
!isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
enabled = (project.testTarget != 'wasm32') // No exceptions on WASM.
|
||||
goldValue = "OK\nOK\n"
|
||||
source = "runtime/workers/freeze6.kt"
|
||||
}
|
||||
@@ -1057,13 +1056,11 @@ task lazy1(type: KonanLocalTest) {
|
||||
}
|
||||
|
||||
standaloneTest("lazy2") {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
goldValue = "123\nOK\n"
|
||||
source = "runtime/workers/lazy2.kt"
|
||||
}
|
||||
|
||||
standaloneTest("lazy3") {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
source = "runtime/workers/lazy3.kt"
|
||||
}
|
||||
|
||||
@@ -2876,13 +2873,11 @@ task memory_escape1(type: KonanLocalTest) {
|
||||
}
|
||||
|
||||
task memory_cycles0(type: KonanLocalTest) {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
goldValue = "42\n"
|
||||
source = "runtime/memory/cycles0.kt"
|
||||
}
|
||||
|
||||
task memory_cycles1(type: KonanLocalTest) {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
source = "runtime/memory/cycles1.kt"
|
||||
}
|
||||
|
||||
@@ -2896,7 +2891,6 @@ task memory_escape2(type: KonanLocalTest) {
|
||||
}
|
||||
|
||||
task memory_weak0(type: KonanLocalTest) {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
goldValue = "Data(s=Hello)\nnull\nOK\n"
|
||||
source = "runtime/memory/weak0.kt"
|
||||
}
|
||||
@@ -2907,7 +2901,6 @@ task memory_weak1(type: KonanLocalTest) {
|
||||
}
|
||||
|
||||
standaloneTest("memory_only_gc") {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
source = "runtime/memory/only_gc.kt"
|
||||
}
|
||||
|
||||
@@ -4207,7 +4200,6 @@ standaloneTest("interop_opengl_teapot") {
|
||||
|
||||
if (PlatformInfo.isAppleTarget(project)) {
|
||||
interopTest("interop_objc_smoke") {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
goldValue = "84\nFoo\nDeallocated\n" +
|
||||
"Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
||||
"true\ntrue\n" +
|
||||
@@ -4242,7 +4234,7 @@ if (PlatformInfo.isAppleTarget(project)) {
|
||||
}
|
||||
|
||||
interopTestMultifile("interop_objc_tests") {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
enabled = !isExperimentalMM // Experimental MM does not support Obj-C weaks yet.
|
||||
source = "interop/objc/tests/"
|
||||
interop = 'objcTests'
|
||||
flags = ['-tr', '-e', 'main']
|
||||
@@ -4492,7 +4484,7 @@ dynamicTest("interop_kt42397") {
|
||||
|
||||
dynamicTest("interop_cleaners_main_thread") {
|
||||
disabled = (project.target.name != project.hostName) ||
|
||||
isExperimentalMM // Experimental MM does not have a GC yet.
|
||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
||||
source = "interop/cleaners/cleaners.kt"
|
||||
cSource = "$projectDir/interop/cleaners/main_thread.cpp"
|
||||
clangTool = "clang++"
|
||||
@@ -4826,7 +4818,6 @@ if (isAppleTarget(project)) {
|
||||
}
|
||||
|
||||
frameworkTest("testStdlibFramework") {
|
||||
enabled = !isExperimentalMM // Experimental MM does not have GC yet.
|
||||
framework('Stdlib') {
|
||||
sources = ['framework/stdlib']
|
||||
bitcode = true
|
||||
|
||||
@@ -10,6 +10,9 @@ struct ObjHeader;
|
||||
|
||||
namespace kotlin {
|
||||
|
||||
// Note: when finalizer is run, object's `ObjHeader*` fields might already be freed,
|
||||
// finalizer must never try to reference them.
|
||||
|
||||
bool HasFinalizers(ObjHeader* object) noexcept;
|
||||
void RunFinalizers(ObjHeader* object) noexcept;
|
||||
|
||||
|
||||
@@ -209,66 +209,110 @@ private:
|
||||
template <size_t ElementCount>
|
||||
class ObjectArray : public internal::Array<ObjHeader*, ElementCount> {
|
||||
public:
|
||||
static ObjectArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<ObjectArray<ElementCount>&>(internal::Array<ObjHeader*, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
ObjectArray() noexcept : internal::Array<ObjHeader*, ElementCount>(theArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class BooleanArray : public internal::Array<KBoolean, ElementCount> {
|
||||
public:
|
||||
static BooleanArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<BooleanArray<ElementCount>&>(internal::Array<KBoolean, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
BooleanArray() noexcept : internal::Array<KBoolean, ElementCount>(theBooleanArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class ByteArray : public internal::Array<KByte, ElementCount> {
|
||||
public:
|
||||
static ByteArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<ByteArray<ElementCount>&>(internal::Array<KByte, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
ByteArray() noexcept : internal::Array<KByte, ElementCount>(theByteArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class CharArray : public internal::Array<KChar, ElementCount> {
|
||||
public:
|
||||
static CharArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<CharArray<ElementCount>&>(internal::Array<KChar, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
CharArray() noexcept : internal::Array<KChar, ElementCount>(theCharArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class DoubleArray : public internal::Array<KDouble, ElementCount> {
|
||||
public:
|
||||
static DoubleArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<DoubleArray<ElementCount>&>(internal::Array<KDouble, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
DoubleArray() noexcept : internal::Array<KDouble, ElementCount>(theDoubleArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class FloatArray : public internal::Array<KFloat, ElementCount> {
|
||||
public:
|
||||
static FloatArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<FloatArray<ElementCount>&>(internal::Array<KFloat, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
FloatArray() noexcept : internal::Array<KFloat, ElementCount>(theFloatArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class IntArray : public internal::Array<KInt, ElementCount> {
|
||||
public:
|
||||
static IntArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<IntArray<ElementCount>&>(internal::Array<KInt, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
IntArray() noexcept : internal::Array<KInt, ElementCount>(theIntArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class LongArray : public internal::Array<KLong, ElementCount> {
|
||||
public:
|
||||
static LongArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<LongArray<ElementCount>&>(internal::Array<KLong, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
LongArray() noexcept : internal::Array<KLong, ElementCount>(theLongArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class NativePtrArray : public internal::Array<KNativePtr, ElementCount> {
|
||||
public:
|
||||
static NativePtrArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<NativePtrArray<ElementCount>&>(internal::Array<KNativePtr, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
NativePtrArray() noexcept : internal::Array<KNativePtr, ElementCount>(theNativePtrArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class ShortArray : public internal::Array<KShort, ElementCount> {
|
||||
public:
|
||||
static ShortArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<ShortArray<ElementCount>&>(internal::Array<KShort, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
ShortArray() noexcept : internal::Array<KShort, ElementCount>(theShortArrayTypeInfo) {}
|
||||
};
|
||||
|
||||
template <size_t ElementCount>
|
||||
class String : public internal::Array<KChar, ElementCount> {
|
||||
public:
|
||||
static String<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
|
||||
return static_cast<String<ElementCount>&>(internal::Array<KChar, ElementCount>::FromArrayHeader(arr));
|
||||
}
|
||||
|
||||
String() noexcept : internal::Array<KChar, ElementCount>(theStringTypeInfo) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "ExtraObjectData.hpp"
|
||||
|
||||
#include "ObjectOps.hpp"
|
||||
#include "PointerBits.h"
|
||||
#include "Weak.h"
|
||||
|
||||
@@ -64,11 +65,19 @@ void mm::ExtraObjectData::Uninstall(ObjHeader* object) noexcept {
|
||||
delete &data;
|
||||
}
|
||||
|
||||
bool mm::ExtraObjectData::HasWeakReferenceCounter() noexcept {
|
||||
return weakReferenceCounter_ != nullptr;
|
||||
}
|
||||
|
||||
void mm::ExtraObjectData::ClearWeakReferenceCounter() noexcept {
|
||||
if (!HasWeakReferenceCounter()) return;
|
||||
|
||||
WeakReferenceCounterClear(weakReferenceCounter_);
|
||||
mm::SetHeapRef(&weakReferenceCounter_, nullptr);
|
||||
}
|
||||
|
||||
mm::ExtraObjectData::~ExtraObjectData() {
|
||||
if (weakReferenceCounter_) {
|
||||
WeakReferenceCounterClear(weakReferenceCounter_);
|
||||
ZeroHeapRef(&weakReferenceCounter_);
|
||||
}
|
||||
RuntimeAssert(!HasWeakReferenceCounter(), "Object must have cleared weak references");
|
||||
|
||||
#ifdef KONAN_OBJC_INTEROP
|
||||
Kotlin_ObjCExport_releaseAssociatedObject(associatedObject_);
|
||||
|
||||
@@ -47,6 +47,9 @@ public:
|
||||
|
||||
std::atomic<Flags>& flags() noexcept { return flags_; }
|
||||
|
||||
bool HasWeakReferenceCounter() noexcept;
|
||||
void ClearWeakReferenceCounter() noexcept;
|
||||
|
||||
private:
|
||||
explicit ExtraObjectData(const TypeInfo* typeInfo) noexcept : typeInfo_(typeInfo) {}
|
||||
~ExtraObjectData();
|
||||
@@ -60,7 +63,6 @@ private:
|
||||
void* associatedObject_ = nullptr;
|
||||
#endif
|
||||
|
||||
// TODO: Need to respect when marking.
|
||||
ObjHeader* weakReferenceCounter_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef RUNTIME_MM_GC_H
|
||||
#define RUNTIME_MM_GC_H
|
||||
|
||||
#include "gc/SingleThreadMarkAndSweep.hpp"
|
||||
#include "gc/NoOpGC.hpp"
|
||||
|
||||
namespace kotlin {
|
||||
@@ -14,7 +15,7 @@ namespace mm {
|
||||
// TODO: GC should be extracted into a separate module, so that we can do different GCs without
|
||||
// the need to redo the entire MM. For now changing GCs can be done by modifying `using` below.
|
||||
|
||||
using GC = NoOpGC;
|
||||
using GC = SingleThreadMarkAndSweep;
|
||||
|
||||
} // namespace mm
|
||||
} // namespace kotlin
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
// much of a problem is it.
|
||||
Iterable Iter() noexcept { return globals_.Iter(); }
|
||||
|
||||
void ClearForTests() { globals_.ClearForTests(); }
|
||||
|
||||
private:
|
||||
// TODO: Add-only MultiSourceQueue can be made more efficient. Measure, if it's a problem.
|
||||
MultiSourceQueue<ObjHeader**> globals_;
|
||||
|
||||
@@ -92,7 +92,12 @@ extern "C" MemoryState* InitMemory(bool firstRuntime) {
|
||||
}
|
||||
|
||||
extern "C" void DeinitMemory(MemoryState* state, bool destroyRuntime) {
|
||||
mm::ThreadRegistry::Instance().Unregister(mm::FromMemoryState(state));
|
||||
auto* node = mm::FromMemoryState(state);
|
||||
if (destroyRuntime) {
|
||||
node->Get()->gc().PerformFullGC();
|
||||
// TODO: Also make sure that finalizers are run.
|
||||
}
|
||||
mm::ThreadRegistry::Instance().Unregister(node);
|
||||
}
|
||||
|
||||
extern "C" void RestoreMemory(MemoryState*) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "Alignment.hpp"
|
||||
#include "Alloc.h"
|
||||
#include "FinalizerHooks.hpp"
|
||||
#include "Memory.h"
|
||||
#include "Mutex.hpp"
|
||||
#include "Types.h"
|
||||
@@ -101,6 +102,26 @@ public:
|
||||
|
||||
class Producer : private MoveOnly {
|
||||
public:
|
||||
class Iterator {
|
||||
public:
|
||||
Node& operator*() noexcept { return *node_; }
|
||||
Node* operator->() noexcept { return node_; }
|
||||
|
||||
Iterator& operator++() noexcept {
|
||||
node_ = node_->next_.get();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator& rhs) const noexcept { return node_ == rhs.node_; }
|
||||
bool operator!=(const Iterator& rhs) const noexcept { return node_ != rhs.node_; }
|
||||
|
||||
private:
|
||||
friend class Producer;
|
||||
explicit Iterator(Node* node) noexcept : node_(node) {}
|
||||
|
||||
Node* node_;
|
||||
};
|
||||
|
||||
Producer(ObjectFactoryStorage& owner, Allocator allocator) noexcept : owner_(owner), allocator_(std::move(allocator)) {}
|
||||
|
||||
~Producer() { Publish(); }
|
||||
@@ -158,6 +179,9 @@ public:
|
||||
owner_.AssertCorrectUnsafe();
|
||||
}
|
||||
|
||||
Iterator begin() noexcept { return Iterator(root_.get()); }
|
||||
Iterator end() noexcept { return Iterator(nullptr); }
|
||||
|
||||
void ClearForTests() noexcept {
|
||||
// Since it's only for tests, no need to worry about stack overflows.
|
||||
root_.reset();
|
||||
@@ -302,6 +326,11 @@ public:
|
||||
// Lock `ObjectFactoryStorage` for safe iteration.
|
||||
Iterable Iter() noexcept { return Iterable(*this); }
|
||||
|
||||
void ClearForTests() {
|
||||
root_.reset();
|
||||
last_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
// Expects `mutex_` to be held by the current thread.
|
||||
std::pair<unique_ptr<Node>, Node*> ExtractUnsafe(Node* previousNode) noexcept {
|
||||
@@ -454,6 +483,27 @@ public:
|
||||
|
||||
class ThreadQueue : private MoveOnly {
|
||||
public:
|
||||
class Iterator {
|
||||
public:
|
||||
NodeRef operator*() noexcept { return NodeRef(*iterator_); }
|
||||
NodeRef operator->() noexcept { return NodeRef(*iterator_); }
|
||||
|
||||
Iterator& operator++() noexcept {
|
||||
++iterator_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator& rhs) const noexcept { return iterator_ == rhs.iterator_; }
|
||||
bool operator!=(const Iterator& rhs) const noexcept { return iterator_ != rhs.iterator_; }
|
||||
|
||||
private:
|
||||
friend class ObjectFactory;
|
||||
|
||||
explicit Iterator(typename Storage::Producer::Iterator iterator) noexcept : iterator_(std::move(iterator)) {}
|
||||
|
||||
typename Storage::Producer::Iterator iterator_;
|
||||
};
|
||||
|
||||
ThreadQueue(ObjectFactory& owner, GCThreadData& gc) noexcept :
|
||||
producer_(owner.storage_, internal::AllocatorWithGC(internal::SimpleAllocator(), gc)) {}
|
||||
|
||||
@@ -483,6 +533,9 @@ public:
|
||||
|
||||
void Publish() noexcept { producer_.Publish(); }
|
||||
|
||||
Iterator begin() noexcept { return Iterator(producer_.begin()); }
|
||||
Iterator end() noexcept { return Iterator(producer_.end()); }
|
||||
|
||||
void ClearForTests() noexcept { producer_.ClearForTests(); }
|
||||
|
||||
private:
|
||||
@@ -534,8 +587,27 @@ public:
|
||||
typename Storage::Consumer::Iterator iterator_;
|
||||
};
|
||||
|
||||
Iterator begin() noexcept { return Iterator(consumer_.begin()); }
|
||||
Iterator end() noexcept { return Iterator(consumer_.end()); }
|
||||
class Iterable {
|
||||
public:
|
||||
Iterator begin() noexcept { return Iterator(owner_.consumer_.begin()); }
|
||||
Iterator end() noexcept { return Iterator(owner_.consumer_.end()); }
|
||||
|
||||
private:
|
||||
friend class FinalizerQueue;
|
||||
|
||||
explicit Iterable(FinalizerQueue& owner) : owner_(owner) {}
|
||||
|
||||
FinalizerQueue& owner_;
|
||||
};
|
||||
|
||||
// TODO: Consider running it in the destructor instead.
|
||||
void Finalize() noexcept {
|
||||
for (auto node : Iterable(*this)) {
|
||||
RunFinalizers(node->IsArray() ? node->GetArrayHeader()->obj() : node->GetObjHeader());
|
||||
}
|
||||
}
|
||||
|
||||
Iterable IterForTests() noexcept { return Iterable(*this); }
|
||||
|
||||
private:
|
||||
friend class ObjectFactory;
|
||||
@@ -565,6 +637,8 @@ public:
|
||||
|
||||
Iterable Iter() noexcept { return Iterable(*this); }
|
||||
|
||||
void ClearForTests() { storage_.ClearForTests(); }
|
||||
|
||||
private:
|
||||
Storage storage_;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "FinalizerHooksTestSupport.hpp"
|
||||
#include "GC.hpp"
|
||||
#include "ObjectTestSupport.hpp"
|
||||
#include "TestSupport.hpp"
|
||||
@@ -889,13 +890,45 @@ TEST(ObjectFactoryTest, Move) {
|
||||
|
||||
{
|
||||
int count = 0;
|
||||
for (auto it = finalizerQueue.begin(); it != finalizerQueue.end(); ++it, ++count) {
|
||||
auto iter = finalizerQueue.IterForTests();
|
||||
for (auto it = iter.begin(); it != iter.end(); ++it, ++count) {
|
||||
EXPECT_TRUE(it->IsArray());
|
||||
}
|
||||
EXPECT_THAT(count, 10);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ObjectFactoryTest, RunFinalizers) {
|
||||
FinalizerHooksTestSupport finalizerHooks;
|
||||
|
||||
test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder<Payload>().addFlag(TF_HAS_FINALIZER)};
|
||||
GC::ThreadData gc;
|
||||
ObjectFactory objectFactory;
|
||||
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
|
||||
ObjectFactory::FinalizerQueue finalizerQueue;
|
||||
|
||||
KStdVector<ObjHeader*> objects;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
objects.push_back(threadQueue.CreateObject(objectType.typeInfo()));
|
||||
}
|
||||
|
||||
threadQueue.Publish();
|
||||
|
||||
{
|
||||
auto iter = objectFactory.Iter();
|
||||
for (auto it = iter.begin(); it != iter.end();) {
|
||||
iter.MoveAndAdvance(finalizerQueue, it);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& object : objects) {
|
||||
EXPECT_CALL(finalizerHooks.finalizerHook(), Call(object));
|
||||
}
|
||||
finalizerQueue.Finalize();
|
||||
// Hooks called before `FinalizerQueue` destructor.
|
||||
testing::Mock::VerifyAndClearExpectations(&finalizerHooks.finalizerHook());
|
||||
}
|
||||
|
||||
TEST(ObjectFactoryTest, ConcurrentPublish) {
|
||||
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||
ObjectFactory objectFactory;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_MM_MARK_AND_SWEEP_UTILS_H
|
||||
#define RUNTIME_MM_MARK_AND_SWEEP_UTILS_H
|
||||
|
||||
#include "../ExtraObjectData.hpp"
|
||||
#include "FinalizerHooks.hpp"
|
||||
#include "Memory.h"
|
||||
#include "ObjectTraversal.hpp"
|
||||
#include "Runtime.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace kotlin {
|
||||
namespace mm {
|
||||
|
||||
// TODO: Because of `graySet` this implementation may allocate heap memory during GC.
|
||||
template <typename Traits>
|
||||
void Mark(KStdVector<ObjHeader*> graySet) noexcept {
|
||||
while (!graySet.empty()) {
|
||||
ObjHeader* top = graySet.back();
|
||||
graySet.pop_back();
|
||||
|
||||
RuntimeAssert(!isNullOrMarker(top), "Got invalid reference %p in gray set", top);
|
||||
RuntimeAssert(!top->local(), "TODO: Stack objects are not supported yet, top=%p", top);
|
||||
|
||||
if (top->heap()) {
|
||||
if (!Traits::TryMark(top)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!top->permanent()) {
|
||||
traverseReferredObjects(top, [&graySet](ObjHeader* field) noexcept {
|
||||
if (!isNullOrMarker(field) && !field->permanent() && !Traits::IsMarked(field)) {
|
||||
graySet.push_back(field);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (auto* extraObjectData = mm::ExtraObjectData::Get(top)) {
|
||||
auto* weakCounter = *extraObjectData->GetWeakCounterLocation();
|
||||
if (!isNullOrMarker(weakCounter)) {
|
||||
graySet.push_back(weakCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFactory& objectFactory) noexcept {
|
||||
typename Traits::ObjectFactory::FinalizerQueue finalizerQueue;
|
||||
|
||||
auto iter = objectFactory.Iter();
|
||||
for (auto it = iter.begin(); it != iter.end();) {
|
||||
if (Traits::TryResetMark(*it)) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
auto* objHeader = it->IsArray() ? it->GetArrayHeader()->obj() : it->GetObjHeader();
|
||||
if (auto* extraObject = mm::ExtraObjectData::Get(objHeader)) {
|
||||
extraObject->ClearWeakReferenceCounter();
|
||||
}
|
||||
if (HasFinalizers(objHeader)) {
|
||||
iter.MoveAndAdvance(finalizerQueue, it);
|
||||
} else {
|
||||
iter.EraseAndAdvance(it);
|
||||
}
|
||||
}
|
||||
|
||||
return finalizerQueue;
|
||||
}
|
||||
|
||||
} // namespace mm
|
||||
} // namespace kotlin
|
||||
|
||||
#endif // RUNTIME_MM_MARK_AND_SWEEP_UTILS_H
|
||||
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* 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 "MarkAndSweepUtils.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "FinalizerHooks.hpp"
|
||||
#include "ObjectTestSupport.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Payload {
|
||||
ObjHeader* field1;
|
||||
ObjHeader* field2;
|
||||
ObjHeader* field3;
|
||||
|
||||
static constexpr std::array kFields = {
|
||||
&Payload::field1,
|
||||
&Payload::field2,
|
||||
&Payload::field3,
|
||||
};
|
||||
};
|
||||
|
||||
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||
|
||||
// TODO: This base might belong in `test_support`
|
||||
class BaseObject {
|
||||
public:
|
||||
enum class Kind {
|
||||
kPermanent,
|
||||
kHeapLike // Treated as heap object for the purposes of the test.
|
||||
};
|
||||
|
||||
virtual ObjHeader* GetObjHeader() = 0;
|
||||
|
||||
void InstallExtraData() { mm::ExtraObjectData::Install(GetObjHeader()); }
|
||||
|
||||
void InstallWeakCounter(BaseObject& counter) {
|
||||
auto& extraObjectData = mm::ExtraObjectData::GetOrInstall(GetObjHeader());
|
||||
*extraObjectData.GetWeakCounterLocation() = counter.GetObjHeader();
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetKind(Kind kind) {
|
||||
switch (kind) {
|
||||
case Kind::kPermanent:
|
||||
GetObjHeader()->typeInfoOrMeta_ = setPointerBits(GetObjHeader()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||
RuntimeAssert(GetObjHeader()->permanent(), "Must be permanent");
|
||||
break;
|
||||
case Kind::kHeapLike:
|
||||
RuntimeAssert(GetObjHeader()->heap(), "Must be heap");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
if (auto* extraObjectData = mm::ExtraObjectData::Get(GetObjHeader())) {
|
||||
extraObjectData->ClearWeakReferenceCounter();
|
||||
}
|
||||
RunFinalizers(GetObjHeader());
|
||||
}
|
||||
};
|
||||
|
||||
class Object : public BaseObject, public test_support::Object<Payload> {
|
||||
public:
|
||||
explicit Object(Kind kind = Kind::kHeapLike) : test_support::Object<Payload>(typeHolder.typeInfo()) { SetKind(kind); }
|
||||
|
||||
~Object() { Finalize(); }
|
||||
|
||||
ObjHeader* GetObjHeader() override { return header(); }
|
||||
};
|
||||
|
||||
class ObjectArray : public BaseObject, public test_support::ObjectArray<3> {
|
||||
public:
|
||||
explicit ObjectArray(Kind kind = Kind::kHeapLike) : test_support::ObjectArray<3>() { SetKind(kind); }
|
||||
|
||||
~ObjectArray() { Finalize(); }
|
||||
|
||||
ObjHeader* GetObjHeader() override { return header(); }
|
||||
};
|
||||
|
||||
class CharArray : public BaseObject, public test_support::CharArray<3> {
|
||||
public:
|
||||
explicit CharArray(Kind kind = Kind::kHeapLike) : test_support::CharArray<3>() { SetKind(kind); }
|
||||
|
||||
~CharArray() { Finalize(); }
|
||||
|
||||
ObjHeader* GetObjHeader() override { return header(); }
|
||||
};
|
||||
|
||||
class ScopedMarkTraits : private Pinned {
|
||||
public:
|
||||
ScopedMarkTraits() {
|
||||
RuntimeAssert(instance_ == nullptr, "Only one ScopedMarkTraits is allowed");
|
||||
instance_ = this;
|
||||
}
|
||||
|
||||
~ScopedMarkTraits() {
|
||||
RuntimeAssert(instance_ == this, "ScopedMarkTraits instance broke");
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
const KStdUnorderedSet<ObjHeader*>& marked() const { return marked_; }
|
||||
|
||||
static bool TryMark(ObjHeader* object) noexcept { return instance_->marked_.insert(object).second; }
|
||||
static bool IsMarked(ObjHeader* object) noexcept { return instance_->marked_.find(object) != instance_->marked_.end(); }
|
||||
|
||||
private:
|
||||
static ScopedMarkTraits* instance_;
|
||||
|
||||
KStdUnorderedSet<ObjHeader*> marked_;
|
||||
};
|
||||
|
||||
// static
|
||||
ScopedMarkTraits* ScopedMarkTraits::instance_ = nullptr;
|
||||
|
||||
class MarkAndSweepUtilsMarkTest : public ::testing::Test {
|
||||
public:
|
||||
const KStdUnorderedSet<ObjHeader*>& marked() const { return markTraits_.marked(); }
|
||||
|
||||
auto MarkedMatcher(std::initializer_list<std::reference_wrapper<BaseObject>> expected) {
|
||||
KStdVector<ObjHeader*> objects;
|
||||
for (auto& object : expected) {
|
||||
objects.push_back(object.get().GetObjHeader());
|
||||
}
|
||||
return testing::UnorderedElementsAreArray(objects);
|
||||
}
|
||||
|
||||
void Mark(std::initializer_list<std::reference_wrapper<BaseObject>> graySet) {
|
||||
KStdVector<ObjHeader*> objects;
|
||||
for (auto& object : graySet) objects.push_back(object.get().GetObjHeader());
|
||||
mm::Mark<ScopedMarkTraits>(std::move(objects));
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedMarkTraits markTraits_;
|
||||
};
|
||||
|
||||
#define EXPECT_MARKED(...) EXPECT_THAT(marked(), MarkedMatcher({__VA_ARGS__}))
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkNothing) {
|
||||
Mark({});
|
||||
|
||||
EXPECT_MARKED();
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObject) {
|
||||
Object object;
|
||||
|
||||
Mark({object});
|
||||
|
||||
EXPECT_MARKED(object);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArray) {
|
||||
ObjectArray array;
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArray) {
|
||||
CharArray array;
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentObject) {
|
||||
Object object{BaseObject::Kind::kPermanent};
|
||||
|
||||
Mark({object});
|
||||
|
||||
EXPECT_MARKED();
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentObjectArray) {
|
||||
ObjectArray array{BaseObject::Kind::kPermanent};
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED();
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSinglePermanentCharArray) {
|
||||
CharArray array{BaseObject::Kind::kPermanent};
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED();
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithInvalidFields) {
|
||||
Object object;
|
||||
object->field1 = kInitializingSingleton;
|
||||
|
||||
Mark({object});
|
||||
|
||||
EXPECT_MARKED(object);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFields) {
|
||||
ObjectArray array;
|
||||
array.elements()[0] = kInitializingSingleton;
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeData) {
|
||||
CharArray array;
|
||||
array.elements()[0] = 'a';
|
||||
array.elements()[1] = 'b';
|
||||
array.elements()[2] = 'c';
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithExtraData) {
|
||||
Object object;
|
||||
object.InstallExtraData();
|
||||
|
||||
Mark({object});
|
||||
|
||||
EXPECT_MARKED(object);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithExtraData) {
|
||||
ObjectArray array;
|
||||
array.InstallExtraData();
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithExtraData) {
|
||||
CharArray array;
|
||||
array.InstallExtraData();
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithWeakCounter) {
|
||||
Object weakCounter;
|
||||
Object object;
|
||||
object.InstallWeakCounter(weakCounter);
|
||||
|
||||
Mark({object});
|
||||
|
||||
EXPECT_MARKED(object, weakCounter);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithWeakCounter) {
|
||||
Object weakCounter;
|
||||
ObjectArray array;
|
||||
array.InstallWeakCounter(weakCounter);
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array, weakCounter);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithWeakCounter) {
|
||||
Object weakCounter;
|
||||
CharArray array;
|
||||
array.InstallWeakCounter(weakCounter);
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array, weakCounter);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectWithInvalidFieldsWithWeakCounter) {
|
||||
Object weakCounter;
|
||||
Object object;
|
||||
object->field1 = kInitializingSingleton;
|
||||
object.InstallWeakCounter(weakCounter);
|
||||
|
||||
Mark({object});
|
||||
|
||||
EXPECT_MARKED(object, weakCounter);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleObjectArrayWithInvalidFieldsWithWeakCounter) {
|
||||
Object weakCounter;
|
||||
ObjectArray array;
|
||||
array.elements()[0] = kInitializingSingleton;
|
||||
array.InstallWeakCounter(weakCounter);
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array, weakCounter);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkSingleCharArrayWithSomeDataWithWeakCounter) {
|
||||
Object weakCounter;
|
||||
CharArray array;
|
||||
array.elements()[0] = 'a';
|
||||
array.elements()[1] = 'b';
|
||||
array.elements()[2] = 'c';
|
||||
array.InstallWeakCounter(weakCounter);
|
||||
|
||||
Mark({array});
|
||||
|
||||
EXPECT_MARKED(array, weakCounter);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTree) {
|
||||
Object root;
|
||||
Object root_field1;
|
||||
Object root_field1_field1;
|
||||
Object root_field1_field2;
|
||||
ObjectArray root_field3;
|
||||
Object root_field3_element1;
|
||||
ObjectArray root_field3_element2;
|
||||
CharArray root_field3_element3;
|
||||
root->field1 = root_field1.header();
|
||||
root_field1->field1 = root_field1_field1.header();
|
||||
root_field1->field2 = root_field1_field2.header();
|
||||
root->field3 = root_field3.header();
|
||||
root_field3.elements()[0] = root_field3_element1.header();
|
||||
root_field3.elements()[1] = root_field3_element2.header();
|
||||
root_field3.elements()[2] = root_field3_element3.header();
|
||||
|
||||
Mark({root});
|
||||
|
||||
EXPECT_MARKED(
|
||||
root, root_field1, root_field1_field1, root_field1_field2, root_field3, root_field3_element1, root_field3_element2,
|
||||
root_field3_element3);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentRoot) {
|
||||
Object root{BaseObject::Kind::kPermanent};
|
||||
Object root_field1{BaseObject::Kind::kPermanent};
|
||||
Object root_field1_field1{BaseObject::Kind::kPermanent};
|
||||
Object root_field1_field2{BaseObject::Kind::kPermanent};
|
||||
ObjectArray root_field3{BaseObject::Kind::kPermanent};
|
||||
Object root_field3_element1{BaseObject::Kind::kPermanent};
|
||||
ObjectArray root_field3_element2{BaseObject::Kind::kPermanent};
|
||||
CharArray root_field3_element3{BaseObject::Kind::kPermanent};
|
||||
root->field1 = root_field1.header();
|
||||
root_field1->field1 = root_field1_field1.header();
|
||||
root_field1->field2 = root_field1_field2.header();
|
||||
root->field3 = root_field3.header();
|
||||
root_field3.elements()[0] = root_field3_element1.header();
|
||||
root_field3.elements()[1] = root_field3_element2.header();
|
||||
root_field3.elements()[2] = root_field3_element3.header();
|
||||
|
||||
Mark({root});
|
||||
|
||||
EXPECT_MARKED();
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentMiddle) {
|
||||
Object root;
|
||||
Object root_field1{BaseObject::Kind::kPermanent};
|
||||
Object root_field1_field1{BaseObject::Kind::kPermanent};
|
||||
Object root_field1_field2{BaseObject::Kind::kPermanent};
|
||||
ObjectArray root_field3;
|
||||
Object root_field3_element1;
|
||||
ObjectArray root_field3_element2;
|
||||
CharArray root_field3_element3;
|
||||
root->field1 = root_field1.header();
|
||||
root_field1->field1 = root_field1_field1.header();
|
||||
root_field1->field2 = root_field1_field2.header();
|
||||
root->field3 = root_field3.header();
|
||||
root_field3.elements()[0] = root_field3_element1.header();
|
||||
root_field3.elements()[1] = root_field3_element2.header();
|
||||
root_field3.elements()[2] = root_field3_element3.header();
|
||||
|
||||
Mark({root});
|
||||
|
||||
EXPECT_MARKED(root, root_field3, root_field3_element1, root_field3_element2, root_field3_element3);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkTreeWithPermanentLeaf) {
|
||||
Object root;
|
||||
Object root_field1;
|
||||
Object root_field1_field1{BaseObject::Kind::kPermanent};
|
||||
Object root_field1_field2;
|
||||
ObjectArray root_field3;
|
||||
Object root_field3_element1;
|
||||
ObjectArray root_field3_element2;
|
||||
CharArray root_field3_element3;
|
||||
root->field1 = root_field1.header();
|
||||
root_field1->field1 = root_field1_field1.header();
|
||||
root_field1->field2 = root_field1_field2.header();
|
||||
root->field3 = root_field3.header();
|
||||
root_field3.elements()[0] = root_field3_element1.header();
|
||||
root_field3.elements()[1] = root_field3_element2.header();
|
||||
root_field3.elements()[2] = root_field3_element3.header();
|
||||
|
||||
Mark({root});
|
||||
|
||||
EXPECT_MARKED(root, root_field1, root_field1_field2, root_field3, root_field3_element1, root_field3_element2, root_field3_element3);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTree) {
|
||||
Object root;
|
||||
Object inner1;
|
||||
ObjectArray inner2;
|
||||
root->field1 = inner1.header();
|
||||
inner1->field1 = inner2.header();
|
||||
inner2.elements()[0] = root.header();
|
||||
|
||||
Mark({root});
|
||||
|
||||
EXPECT_MARKED(root, inner1, inner2);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkRecursiveTreeWithPermanentRoot) {
|
||||
Object root{BaseObject::Kind::kPermanent};
|
||||
Object inner1{BaseObject::Kind::kPermanent};
|
||||
ObjectArray inner2{BaseObject::Kind::kPermanent};
|
||||
root->field1 = inner1.header();
|
||||
inner1->field1 = inner2.header();
|
||||
inner2.elements()[0] = root.header();
|
||||
|
||||
Mark({root});
|
||||
|
||||
EXPECT_MARKED();
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForest) {
|
||||
Object root1;
|
||||
ObjectArray root2;
|
||||
Object root3;
|
||||
|
||||
Mark({root1, root2, root3});
|
||||
|
||||
EXPECT_MARKED(root1, root2, root3);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentFirst) {
|
||||
Object root1{BaseObject::Kind::kPermanent};
|
||||
ObjectArray root2;
|
||||
Object root3;
|
||||
|
||||
Mark({root1, root2, root3});
|
||||
|
||||
EXPECT_MARKED(root2, root3);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentSecond) {
|
||||
Object root1;
|
||||
ObjectArray root2{BaseObject::Kind::kPermanent};
|
||||
Object root3;
|
||||
|
||||
Mark({root1, root2, root3});
|
||||
|
||||
EXPECT_MARKED(root1, root3);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithPermanentThird) {
|
||||
Object root1;
|
||||
ObjectArray root2;
|
||||
Object root3{BaseObject::Kind::kPermanent};
|
||||
|
||||
Mark({root1, root2, root3});
|
||||
|
||||
EXPECT_MARKED(root1, root2);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsMarkTest, MarkForestWithInterconnectedRoots) {
|
||||
Object root1;
|
||||
ObjectArray root2;
|
||||
Object root3;
|
||||
|
||||
root1->field1 = root2.header();
|
||||
root2.elements()[0] = root3.header();
|
||||
root3->field1 = root1.header();
|
||||
|
||||
Mark({root1, root2, root3});
|
||||
|
||||
EXPECT_MARKED(root1, root2, root3);
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* 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 "MarkAndSweepUtils.hpp"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "../ObjectFactory.hpp"
|
||||
#include "FinalizerHooksTestSupport.hpp"
|
||||
#include "ObjectTestSupport.hpp"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Payload {
|
||||
ObjHeader* field1;
|
||||
ObjHeader* field2;
|
||||
ObjHeader* field3;
|
||||
|
||||
static constexpr std::array kFields = {
|
||||
&Payload::field1,
|
||||
&Payload::field2,
|
||||
&Payload::field3,
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: This should go into test support for weak references.
|
||||
struct WeakCounterPayload {
|
||||
void* referred;
|
||||
KInt lock;
|
||||
KInt cookie;
|
||||
|
||||
static constexpr std::array<ObjHeader * WeakCounterPayload::*, 0> kFields{};
|
||||
};
|
||||
|
||||
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||
test_support::TypeInfoHolder typeHolderWithFinalizer{test_support::TypeInfoHolder::ObjectBuilder<Payload>().addFlag(TF_HAS_FINALIZER)};
|
||||
test_support::TypeInfoHolder typeHolderWeakCounter{test_support::TypeInfoHolder::ObjectBuilder<WeakCounterPayload>()};
|
||||
|
||||
struct GC {
|
||||
struct ObjectData {
|
||||
enum class State {
|
||||
kUnmarked,
|
||||
kMarked,
|
||||
kMarkReset,
|
||||
};
|
||||
State state = State::kUnmarked;
|
||||
};
|
||||
|
||||
struct ThreadData {
|
||||
void SafePointAllocation(size_t) {}
|
||||
void OnOOM(size_t) {}
|
||||
};
|
||||
};
|
||||
|
||||
using ObjectFactory = mm::ObjectFactory<GC>;
|
||||
|
||||
class Object : public test_support::Object<Payload> {
|
||||
public:
|
||||
// No way to directly create or destroy it.
|
||||
Object() = delete;
|
||||
~Object() = delete;
|
||||
|
||||
static Object& FromObjHeader(ObjHeader* obj) { return static_cast<Object&>(test_support::Object<Payload>::FromObjHeader(obj)); }
|
||||
|
||||
void InstallExtraData() { mm::ExtraObjectData::Install(header()); }
|
||||
|
||||
bool HasWeakCounter() {
|
||||
if (auto* extraObjectData = mm::ExtraObjectData::Get(header())) {
|
||||
return extraObjectData->HasWeakReferenceCounter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mark() { objectData().state = GC::ObjectData::State::kMarked; }
|
||||
|
||||
GC::ObjectData::State state() { return objectData().state; }
|
||||
|
||||
private:
|
||||
GC::ObjectData& objectData() { return ObjectFactory::NodeRef::From(header()).GCObjectData(); }
|
||||
};
|
||||
|
||||
class ObjectArray : public test_support::ObjectArray<3> {
|
||||
public:
|
||||
// No way to directly create or destroy it.
|
||||
ObjectArray() = delete;
|
||||
~ObjectArray() = delete;
|
||||
|
||||
static ObjectArray& FromArrayHeader(ArrayHeader* array) {
|
||||
return static_cast<ObjectArray&>(test_support::ObjectArray<3>::FromArrayHeader(array));
|
||||
}
|
||||
|
||||
void InstallExtraData() { mm::ExtraObjectData::Install(header()); }
|
||||
|
||||
bool HasWeakCounter() {
|
||||
if (auto* extraObjectData = mm::ExtraObjectData::Get(header())) {
|
||||
return extraObjectData->HasWeakReferenceCounter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mark() { objectData().state = GC::ObjectData::State::kMarked; }
|
||||
|
||||
GC::ObjectData::State state() { return objectData().state; }
|
||||
|
||||
private:
|
||||
GC::ObjectData& objectData() { return ObjectFactory::NodeRef::From(header()).GCObjectData(); }
|
||||
};
|
||||
|
||||
class CharArray : public test_support::CharArray<3> {
|
||||
public:
|
||||
// No way to directly create or destroy it.
|
||||
CharArray() = delete;
|
||||
~CharArray() = delete;
|
||||
|
||||
static CharArray& FromArrayHeader(ArrayHeader* array) {
|
||||
return static_cast<CharArray&>(test_support::CharArray<3>::FromArrayHeader(array));
|
||||
}
|
||||
|
||||
void InstallExtraData() { mm::ExtraObjectData::Install(header()); }
|
||||
|
||||
bool HasWeakCounter() {
|
||||
if (auto* extraObjectData = mm::ExtraObjectData::Get(header())) {
|
||||
return extraObjectData->HasWeakReferenceCounter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mark() { objectData().state = GC::ObjectData::State::kMarked; }
|
||||
|
||||
GC::ObjectData::State state() { return objectData().state; }
|
||||
|
||||
private:
|
||||
GC::ObjectData& objectData() { return ObjectFactory::NodeRef::From(header()).GCObjectData(); }
|
||||
};
|
||||
|
||||
using WeakCounter = test_support::Object<WeakCounterPayload>;
|
||||
|
||||
void MarkWeakCounter(WeakCounter& counter) {
|
||||
ObjectFactory::NodeRef::From(counter.header()).GCObjectData().state = GC::ObjectData::State::kMarked;
|
||||
}
|
||||
|
||||
GC::ObjectData::State GetWeakCounterState(WeakCounter& counter) {
|
||||
return ObjectFactory::NodeRef::From(counter.header()).GCObjectData().state;
|
||||
}
|
||||
|
||||
struct SweepTraits {
|
||||
using ObjectFactory = ObjectFactory;
|
||||
|
||||
static bool TryResetMark(ObjectFactory::NodeRef node) {
|
||||
GC::ObjectData& objectData = node.GCObjectData();
|
||||
switch (objectData.state) {
|
||||
case GC::ObjectData::State::kUnmarked:
|
||||
return false;
|
||||
case GC::ObjectData::State::kMarked:
|
||||
objectData.state = GC::ObjectData::State::kMarkReset;
|
||||
return true;
|
||||
case GC::ObjectData::State::kMarkReset:
|
||||
RuntimeFail("Trying to reset mark twice.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MarkAndSweepUtilsSweepTest : public ::testing::Test {
|
||||
public:
|
||||
~MarkAndSweepUtilsSweepTest() override {
|
||||
for (auto& finalizerQueue : finalizers_) {
|
||||
finalizerQueue.Finalize();
|
||||
}
|
||||
testing::Mock::VerifyAndClear(&finalizerHook());
|
||||
// TODO: Figure out a better way to clear up the stuff.
|
||||
EXPECT_CALL(finalizerHook(), Call(testing::_)).Times(testing::AnyNumber());
|
||||
for (auto node : objectFactory_.Iter()) {
|
||||
auto* obj = node->IsArray() ? node->GetArrayHeader()->obj() : node->GetObjHeader();
|
||||
if (auto* extraObject = mm::ExtraObjectData::Get(obj)) {
|
||||
extraObject->ClearWeakReferenceCounter();
|
||||
}
|
||||
RunFinalizers(obj);
|
||||
}
|
||||
}
|
||||
|
||||
KStdVector<ObjHeader*> Sweep() {
|
||||
auto finalizers = mm::Sweep<SweepTraits>(objectFactory_);
|
||||
KStdVector<ObjHeader*> objects;
|
||||
for (auto node : finalizers.IterForTests()) {
|
||||
objects.push_back(node.IsArray() ? node.GetArrayHeader()->obj() : node.GetObjHeader());
|
||||
}
|
||||
finalizers_.push_back(std::move(finalizers));
|
||||
return objects;
|
||||
}
|
||||
|
||||
KStdVector<ObjHeader*> Alive() {
|
||||
KStdVector<ObjHeader*> objects;
|
||||
for (auto node : objectFactory_.Iter()) {
|
||||
objects.push_back(node.IsArray() ? node.GetArrayHeader()->obj() : node.GetObjHeader());
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
Object& AllocateObject(const TypeInfo* typeInfo = typeHolder.typeInfo()) {
|
||||
auto* object = objectFactoryThreadQueue_.CreateObject(typeInfo);
|
||||
objectFactoryThreadQueue_.Publish();
|
||||
return Object::FromObjHeader(object);
|
||||
}
|
||||
|
||||
ObjectArray& AllocateObjectArray() {
|
||||
auto* array = objectFactoryThreadQueue_.CreateArray(theArrayTypeInfo, 3);
|
||||
objectFactoryThreadQueue_.Publish();
|
||||
return ObjectArray::FromArrayHeader(array);
|
||||
}
|
||||
|
||||
CharArray& AllocateCharArray() {
|
||||
auto* array = objectFactoryThreadQueue_.CreateArray(theCharArrayTypeInfo, 3);
|
||||
objectFactoryThreadQueue_.Publish();
|
||||
return CharArray::FromArrayHeader(array);
|
||||
}
|
||||
|
||||
WeakCounter& InstallWeakCounter(ObjHeader* objHeader) {
|
||||
auto* weakCounterHeader = objectFactoryThreadQueue_.CreateObject(typeHolderWeakCounter.typeInfo());
|
||||
objectFactoryThreadQueue_.Publish();
|
||||
auto& weakCounter = WeakCounter::FromObjHeader(weakCounterHeader);
|
||||
auto& extraObjectData = mm::ExtraObjectData::GetOrInstall(objHeader);
|
||||
*extraObjectData.GetWeakCounterLocation() = weakCounter.header();
|
||||
weakCounter->referred = objHeader;
|
||||
return weakCounter;
|
||||
}
|
||||
|
||||
testing::MockFunction<void(ObjHeader*)>& finalizerHook() { return finalizerHooks_.finalizerHook(); }
|
||||
|
||||
private:
|
||||
FinalizerHooksTestSupport finalizerHooks_;
|
||||
GC::ThreadData gcThreadData_;
|
||||
ObjectFactory objectFactory_;
|
||||
ObjectFactory::ThreadQueue objectFactoryThreadQueue_{objectFactory_, gcThreadData_};
|
||||
KStdVector<ObjectFactory::FinalizerQueue> finalizers_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepEmpty) {
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObject) {
|
||||
auto& object = AllocateObject();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObjectArray) {
|
||||
auto& array = AllocateObjectArray();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleCharArray) {
|
||||
auto& array = AllocateCharArray();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObject) {
|
||||
auto& object = AllocateObject();
|
||||
object.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObjectArray) {
|
||||
auto& array = AllocateObjectArray();
|
||||
array.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedCharArray) {
|
||||
auto& array = AllocateCharArray();
|
||||
array.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObjectWithExtraData) {
|
||||
auto& object = AllocateObject();
|
||||
object.InstallExtraData();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(object.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kUnmarked);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObjectArrayWithExtraData) {
|
||||
auto& array = AllocateObjectArray();
|
||||
array.InstallExtraData();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kUnmarked);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleCharArrayWithExtraData) {
|
||||
auto& array = AllocateCharArray();
|
||||
array.InstallExtraData();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kUnmarked);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObjectWithExtraData) {
|
||||
auto& object = AllocateObject();
|
||||
object.InstallExtraData();
|
||||
object.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObjectArrayWithExtraData) {
|
||||
auto& array = AllocateObjectArray();
|
||||
array.InstallExtraData();
|
||||
array.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedCharArrayWithExtraData) {
|
||||
auto& array = AllocateCharArray();
|
||||
array.InstallExtraData();
|
||||
array.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObjectWithFinalizerHook) {
|
||||
auto& object = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(object.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kUnmarked);
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object.header()));
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObjectWithFinalizerHook) {
|
||||
auto& object = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
object.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object.header()));
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kMarkReset);
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObjectWithWeakCounter) {
|
||||
auto& object = AllocateObject();
|
||||
auto& weakCounter = InstallWeakCounter(object.header());
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header(), weakCounter.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(object.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kUnmarked);
|
||||
EXPECT_FALSE(object.HasWeakCounter());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleObjectArrayWithWeakCounter) {
|
||||
auto& array = AllocateObjectArray();
|
||||
auto& weakCounter = InstallWeakCounter(array.header());
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header(), weakCounter.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kUnmarked);
|
||||
EXPECT_FALSE(array.HasWeakCounter());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleCharArrayWithWeakCounter) {
|
||||
auto& array = AllocateCharArray();
|
||||
auto& weakCounter = InstallWeakCounter(array.header());
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header(), weakCounter.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(array.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kUnmarked);
|
||||
EXPECT_FALSE(array.HasWeakCounter());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObjectWithWeakCounter) {
|
||||
auto& object = AllocateObject();
|
||||
auto& weakCounter = InstallWeakCounter(object.header());
|
||||
object.Mark();
|
||||
MarkWeakCounter(weakCounter);
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object.header(), weakCounter.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object.header(), weakCounter.header()));
|
||||
EXPECT_THAT(object.state(), GC::ObjectData::State::kMarkReset);
|
||||
EXPECT_THAT(GetWeakCounterState(weakCounter), GC::ObjectData::State::kMarkReset);
|
||||
EXPECT_TRUE(object.HasWeakCounter());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedObjectArrayWithWeakCounter) {
|
||||
auto& array = AllocateObjectArray();
|
||||
auto& weakCounter = InstallWeakCounter(array.header());
|
||||
array.Mark();
|
||||
MarkWeakCounter(weakCounter);
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header(), weakCounter.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(array.header(), weakCounter.header()));
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kMarkReset);
|
||||
EXPECT_THAT(GetWeakCounterState(weakCounter), GC::ObjectData::State::kMarkReset);
|
||||
EXPECT_TRUE(array.HasWeakCounter());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepSingleMarkedCharArrayWithWeakCounter) {
|
||||
auto& array = AllocateCharArray();
|
||||
auto& weakCounter = InstallWeakCounter(array.header());
|
||||
array.Mark();
|
||||
MarkWeakCounter(weakCounter);
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(array.header(), weakCounter.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(array.header(), weakCounter.header()));
|
||||
EXPECT_THAT(array.state(), GC::ObjectData::State::kMarkReset);
|
||||
EXPECT_THAT(GetWeakCounterState(weakCounter), GC::ObjectData::State::kMarkReset);
|
||||
EXPECT_TRUE(array.HasWeakCounter());
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepObjects) {
|
||||
auto& object1 = AllocateObject();
|
||||
auto& object2 = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
auto& object3 = AllocateObject();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object1.header(), object2.header(), object3.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(object2.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre());
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object2.header()));
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepObjectsMarkAll) {
|
||||
auto& object1 = AllocateObject();
|
||||
object1.Mark();
|
||||
auto& object2 = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
object2.Mark();
|
||||
auto& object3 = AllocateObject();
|
||||
object3.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object1.header(), object2.header(), object3.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object1.header(), object2.header(), object3.header()));
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepObjectsMarkFirst) {
|
||||
auto& object1 = AllocateObject();
|
||||
object1.Mark();
|
||||
auto& object2 = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
auto& object3 = AllocateObject();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object1.header(), object2.header(), object3.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(object2.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object1.header()));
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object2.header()));
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepObjectsMarkSecond) {
|
||||
auto& object1 = AllocateObject();
|
||||
auto& object2 = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
object2.Mark();
|
||||
auto& object3 = AllocateObject();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object1.header(), object2.header(), object3.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre());
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object2.header()));
|
||||
}
|
||||
|
||||
TEST_F(MarkAndSweepUtilsSweepTest, SweepObjectsMarkThird) {
|
||||
auto& object1 = AllocateObject();
|
||||
auto& object2 = AllocateObject(typeHolderWithFinalizer.typeInfo());
|
||||
auto& object3 = AllocateObject();
|
||||
object3.Mark();
|
||||
ASSERT_THAT(Alive(), testing::UnorderedElementsAre(object1.header(), object2.header(), object3.header()));
|
||||
|
||||
auto finalizers = Sweep();
|
||||
|
||||
EXPECT_THAT(finalizers, testing::UnorderedElementsAre(object2.header()));
|
||||
EXPECT_THAT(Alive(), testing::UnorderedElementsAre(object3.header()));
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object2.header()));
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 "SingleThreadMarkAndSweep.hpp"
|
||||
|
||||
#include "../GlobalData.hpp"
|
||||
#include "../RootSet.hpp"
|
||||
#include "../ThreadData.hpp"
|
||||
#include "../ThreadRegistry.hpp"
|
||||
#include "MarkAndSweepUtils.hpp"
|
||||
#include "Memory.h"
|
||||
#include "Runtime.h"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
namespace {
|
||||
|
||||
struct MarkTraits {
|
||||
static bool IsMarked(ObjHeader* object) noexcept {
|
||||
auto& objectData = mm::ObjectFactory<mm::SingleThreadMarkAndSweep>::NodeRef::From(object).GCObjectData();
|
||||
return objectData.color() == mm::SingleThreadMarkAndSweep::ObjectData::Color::kBlack;
|
||||
}
|
||||
|
||||
static bool TryMark(ObjHeader* object) noexcept {
|
||||
auto& objectData = mm::ObjectFactory<mm::SingleThreadMarkAndSweep>::NodeRef::From(object).GCObjectData();
|
||||
if (objectData.color() == mm::SingleThreadMarkAndSweep::ObjectData::Color::kBlack) return false;
|
||||
objectData.setColor(mm::SingleThreadMarkAndSweep::ObjectData::Color::kBlack);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
struct SweepTraits {
|
||||
using ObjectFactory = mm::ObjectFactory<mm::SingleThreadMarkAndSweep>;
|
||||
|
||||
static bool TryResetMark(ObjectFactory::NodeRef node) noexcept {
|
||||
auto& objectData = node.GCObjectData();
|
||||
if (objectData.color() == mm::SingleThreadMarkAndSweep::ObjectData::Color::kWhite) return false;
|
||||
objectData.setColor(mm::SingleThreadMarkAndSweep::ObjectData::Color::kWhite);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct FinalizeTraits {
|
||||
using ObjectFactory = mm::ObjectFactory<mm::SingleThreadMarkAndSweep>;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::ThreadData::SafePointFunctionEpilogue() noexcept {
|
||||
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
|
||||
PerformFullGC();
|
||||
}
|
||||
++safePointsCounter_;
|
||||
}
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::ThreadData::SafePointLoopBody() noexcept {
|
||||
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
|
||||
PerformFullGC();
|
||||
}
|
||||
++safePointsCounter_;
|
||||
}
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::ThreadData::SafePointExceptionUnwind() noexcept {
|
||||
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
|
||||
PerformFullGC();
|
||||
}
|
||||
++safePointsCounter_;
|
||||
}
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
|
||||
size_t allocationOverhead =
|
||||
gc_.GetAllocationThresholdBytes() == 0 ? allocatedBytes_ : allocatedBytes_ % gc_.GetAllocationThresholdBytes();
|
||||
if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
|
||||
PerformFullGC();
|
||||
}
|
||||
allocatedBytes_ += size;
|
||||
}
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
||||
gc_.PerformFullGC();
|
||||
}
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
||||
PerformFullGC();
|
||||
}
|
||||
|
||||
void mm::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
|
||||
RuntimeAssert(running_ == false, "Cannot have been called during another collection");
|
||||
running_ = true;
|
||||
|
||||
KStdVector<ObjHeader*> graySet;
|
||||
for (auto& thread : mm::GlobalData::Instance().threadRegistry().Iter()) {
|
||||
thread.Publish();
|
||||
for (auto* object : mm::ThreadRootSet(thread)) {
|
||||
if (!isNullOrMarker(object)) {
|
||||
graySet.push_back(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
mm::StableRefRegistry::Instance().ProcessDeletions();
|
||||
for (auto* object : mm::GlobalRootSet()) {
|
||||
if (!isNullOrMarker(object)) {
|
||||
graySet.push_back(object);
|
||||
}
|
||||
}
|
||||
|
||||
mm::Mark<MarkTraits>(std::move(graySet));
|
||||
auto finalizerQueue = mm::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
|
||||
|
||||
running_ = false;
|
||||
|
||||
// TODO: These will actually need to be run on a separate thread.
|
||||
// TODO: This probably should check for the existence of runtime itself, but unit tests initialize only memory.
|
||||
RuntimeAssert(mm::ThreadRegistry::Instance().CurrentThreadData() != nullptr, "Finalizers need a Kotlin runtime");
|
||||
finalizerQueue.Finalize();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_MM_GC_SINGLE_THREAD_MARK_AND_SWEEP_H
|
||||
#define RUNTIME_MM_GC_SINGLE_THREAD_MARK_AND_SWEEP_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "Types.h"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace kotlin {
|
||||
namespace mm {
|
||||
|
||||
// Stop-the-world Mark-and-Sweep for a single mutator
|
||||
class SingleThreadMarkAndSweep : private Pinned {
|
||||
public:
|
||||
class ObjectData {
|
||||
public:
|
||||
enum class Color {
|
||||
kWhite = 0, // Initial color at the start of collection cycles. Objects with this color at the end of GC cycle are collected.
|
||||
// All new objects are allocated with this color.
|
||||
kBlack, // Objects encountered during mark phase.
|
||||
};
|
||||
|
||||
Color color() const noexcept { return color_; }
|
||||
void setColor(Color color) noexcept { color_ = color; }
|
||||
|
||||
private:
|
||||
Color color_ = Color::kWhite;
|
||||
};
|
||||
|
||||
class ThreadData : private Pinned {
|
||||
public:
|
||||
using ObjectData = SingleThreadMarkAndSweep::ObjectData;
|
||||
|
||||
explicit ThreadData(SingleThreadMarkAndSweep& gc) noexcept : gc_(gc) {}
|
||||
~ThreadData() = default;
|
||||
|
||||
void SafePointFunctionEpilogue() noexcept;
|
||||
void SafePointLoopBody() noexcept;
|
||||
void SafePointExceptionUnwind() noexcept;
|
||||
void SafePointAllocation(size_t size) noexcept;
|
||||
|
||||
void PerformFullGC() noexcept;
|
||||
|
||||
void OnOOM(size_t size) noexcept;
|
||||
|
||||
private:
|
||||
SingleThreadMarkAndSweep& gc_;
|
||||
size_t allocatedBytes_ = 0;
|
||||
size_t safePointsCounter_ = 0;
|
||||
};
|
||||
|
||||
SingleThreadMarkAndSweep() noexcept {}
|
||||
~SingleThreadMarkAndSweep() = default;
|
||||
|
||||
void SetThreshold(size_t value) noexcept { threshold_ = value; }
|
||||
size_t GetThreshold() noexcept { return threshold_; }
|
||||
|
||||
void SetAllocationThresholdBytes(size_t value) noexcept { allocationThresholdBytes_ = value; }
|
||||
size_t GetAllocationThresholdBytes() noexcept { return allocationThresholdBytes_; }
|
||||
|
||||
private:
|
||||
void PerformFullGC() noexcept;
|
||||
|
||||
bool running_ = false;
|
||||
|
||||
size_t threshold_ = 1000;
|
||||
size_t allocationThresholdBytes_ = 10000;
|
||||
};
|
||||
|
||||
} // namespace mm
|
||||
} // namespace kotlin
|
||||
|
||||
#endif // RUNTIME_MM_GC_SINGLE_THREAD_MARK_AND_SWEEP_H
|
||||
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* 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 "SingleThreadMarkAndSweep.hpp"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "../ExtraObjectData.hpp"
|
||||
#include "../GlobalData.hpp"
|
||||
#include "../ObjectOps.hpp"
|
||||
#include "../TestSupport.hpp"
|
||||
#include "FinalizerHooksTestSupport.hpp"
|
||||
#include "ObjectTestSupport.hpp"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
// These tests can only work if `GC` is `SingleThreadMarkAndSweep`.
|
||||
// TODO: Extracting GC into a separate module will help with this.
|
||||
|
||||
namespace {
|
||||
|
||||
struct Payload {
|
||||
ObjHeader* field1;
|
||||
ObjHeader* field2;
|
||||
ObjHeader* field3;
|
||||
|
||||
static constexpr std::array kFields = {
|
||||
&Payload::field1,
|
||||
&Payload::field2,
|
||||
&Payload::field3,
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: This should go into test support for weak references.
|
||||
struct WeakCounterPayload {
|
||||
void* referred;
|
||||
KInt lock;
|
||||
KInt cookie;
|
||||
|
||||
static constexpr std::array<ObjHeader * WeakCounterPayload::*, 0> kFields{};
|
||||
};
|
||||
|
||||
using WeakCounter = test_support::Object<WeakCounterPayload>;
|
||||
|
||||
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||
test_support::TypeInfoHolder typeHolderWithFinalizer{test_support::TypeInfoHolder::ObjectBuilder<Payload>().addFlag(TF_HAS_FINALIZER)};
|
||||
test_support::TypeInfoHolder typeHolderWeakCounter{test_support::TypeInfoHolder::ObjectBuilder<WeakCounterPayload>()};
|
||||
|
||||
// TODO: Clean GlobalObjectHolder after it's gone.
|
||||
class GlobalObjectHolder : private Pinned {
|
||||
public:
|
||||
explicit GlobalObjectHolder(mm::ThreadData& threadData) {
|
||||
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(&threadData, &location_);
|
||||
mm::AllocateObject(&threadData, typeHolder.typeInfo(), &location_);
|
||||
}
|
||||
|
||||
ObjHeader* header() { return location_; }
|
||||
|
||||
test_support::Object<Payload>& operator*() { return test_support::Object<Payload>::FromObjHeader(location_); }
|
||||
test_support::Object<Payload>& operator->() { return test_support::Object<Payload>::FromObjHeader(location_); }
|
||||
|
||||
private:
|
||||
ObjHeader* location_;
|
||||
};
|
||||
|
||||
// TODO: Clean GlobalPermanentObjectHolder after it's gone.
|
||||
class GlobalPermanentObjectHolder : private Pinned {
|
||||
public:
|
||||
explicit GlobalPermanentObjectHolder(mm::ThreadData& threadData) {
|
||||
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(&threadData, &global_);
|
||||
global_->typeInfoOrMeta_ = setPointerBits(global_->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||
RuntimeAssert(global_->permanent(), "Must be permanent");
|
||||
}
|
||||
|
||||
ObjHeader* header() { return global_; }
|
||||
|
||||
test_support::Object<Payload>& operator*() { return object_; }
|
||||
test_support::Object<Payload>& operator->() { return object_; }
|
||||
|
||||
private:
|
||||
test_support::Object<Payload> object_{typeHolder.typeInfo()};
|
||||
ObjHeader* global_{object_.header()};
|
||||
};
|
||||
|
||||
// TODO: Clean GlobalObjectArrayHolder after it's gone.
|
||||
class GlobalObjectArrayHolder : private Pinned {
|
||||
public:
|
||||
explicit GlobalObjectArrayHolder(mm::ThreadData& threadData) {
|
||||
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(&threadData, &location_);
|
||||
mm::AllocateArray(&threadData, theArrayTypeInfo, 3, &location_);
|
||||
}
|
||||
|
||||
ObjHeader* header() { return location_; }
|
||||
|
||||
test_support::ObjectArray<3>& operator*() { return test_support::ObjectArray<3>::FromArrayHeader(location_->array()); }
|
||||
test_support::ObjectArray<3>& operator->() { return test_support::ObjectArray<3>::FromArrayHeader(location_->array()); }
|
||||
|
||||
ObjHeader*& operator[](size_t index) noexcept { return (**this).elements()[index]; }
|
||||
|
||||
private:
|
||||
ObjHeader* location_;
|
||||
};
|
||||
|
||||
// TODO: Clean GlobalCharArrayHolder after it's gone.
|
||||
class GlobalCharArrayHolder : private Pinned {
|
||||
public:
|
||||
explicit GlobalCharArrayHolder(mm::ThreadData& threadData) {
|
||||
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(&threadData, &location_);
|
||||
mm::AllocateArray(&threadData, theCharArrayTypeInfo, 3, &location_);
|
||||
}
|
||||
|
||||
ObjHeader* header() { return location_; }
|
||||
|
||||
test_support::CharArray<3>& operator*() { return test_support::CharArray<3>::FromArrayHeader(location_->array()); }
|
||||
test_support::CharArray<3>& operator->() { return test_support::CharArray<3>::FromArrayHeader(location_->array()); }
|
||||
|
||||
private:
|
||||
ObjHeader* location_;
|
||||
};
|
||||
|
||||
class StackObjectHolder : private Pinned {
|
||||
public:
|
||||
explicit StackObjectHolder(mm::ThreadData& threadData) { mm::AllocateObject(&threadData, typeHolder.typeInfo(), holder_.slot()); }
|
||||
explicit StackObjectHolder(test_support::Object<Payload>& object) : holder_(object.header()) {}
|
||||
|
||||
ObjHeader* header() { return holder_.obj(); }
|
||||
|
||||
test_support::Object<Payload>& operator*() { return test_support::Object<Payload>::FromObjHeader(holder_.obj()); }
|
||||
test_support::Object<Payload>& operator->() { return test_support::Object<Payload>::FromObjHeader(holder_.obj()); }
|
||||
|
||||
private:
|
||||
ObjHolder holder_;
|
||||
};
|
||||
|
||||
class StackObjectArrayHolder : private Pinned {
|
||||
public:
|
||||
explicit StackObjectArrayHolder(mm::ThreadData& threadData) { mm::AllocateArray(&threadData, theArrayTypeInfo, 3, holder_.slot()); }
|
||||
|
||||
ObjHeader* header() { return holder_.obj(); }
|
||||
|
||||
test_support::ObjectArray<3>& operator*() { return test_support::ObjectArray<3>::FromArrayHeader(holder_.obj()->array()); }
|
||||
test_support::ObjectArray<3>& operator->() { return test_support::ObjectArray<3>::FromArrayHeader(holder_.obj()->array()); }
|
||||
|
||||
ObjHeader*& operator[](size_t index) noexcept { return (**this).elements()[index]; }
|
||||
|
||||
private:
|
||||
ObjHolder holder_;
|
||||
};
|
||||
|
||||
class StackCharArrayHolder : private Pinned {
|
||||
public:
|
||||
explicit StackCharArrayHolder(mm::ThreadData& threadData) { mm::AllocateArray(&threadData, theCharArrayTypeInfo, 3, holder_.slot()); }
|
||||
|
||||
ObjHeader* header() { return holder_.obj(); }
|
||||
|
||||
test_support::CharArray<3>& operator*() { return test_support::CharArray<3>::FromArrayHeader(holder_.obj()->array()); }
|
||||
test_support::CharArray<3>& operator->() { return test_support::CharArray<3>::FromArrayHeader(holder_.obj()->array()); }
|
||||
|
||||
private:
|
||||
ObjHolder holder_;
|
||||
};
|
||||
|
||||
test_support::Object<Payload>& AllocateObject(mm::ThreadData& threadData) {
|
||||
ObjHolder holder;
|
||||
mm::AllocateObject(&threadData, typeHolder.typeInfo(), holder.slot());
|
||||
return test_support::Object<Payload>::FromObjHeader(holder.obj());
|
||||
}
|
||||
|
||||
test_support::Object<Payload>& AllocateObjectWithFinalizer(mm::ThreadData& threadData) {
|
||||
ObjHolder holder;
|
||||
mm::AllocateObject(&threadData, typeHolderWithFinalizer.typeInfo(), holder.slot());
|
||||
return test_support::Object<Payload>::FromObjHeader(holder.obj());
|
||||
}
|
||||
|
||||
KStdVector<ObjHeader*> Alive(mm::ThreadData& threadData) {
|
||||
KStdVector<ObjHeader*> objects;
|
||||
for (auto node : threadData.objectFactoryThreadQueue()) {
|
||||
objects.push_back(node.IsArray() ? node.GetArrayHeader()->obj() : node.GetObjHeader());
|
||||
}
|
||||
for (auto node : mm::GlobalData::Instance().objectFactory().Iter()) {
|
||||
objects.push_back(node.IsArray() ? node.GetArrayHeader()->obj() : node.GetObjHeader());
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
using Color = mm::SingleThreadMarkAndSweep::ObjectData::Color;
|
||||
|
||||
Color GetColor(ObjHeader* objHeader) {
|
||||
auto nodeRef = mm::ObjectFactory<mm::SingleThreadMarkAndSweep>::NodeRef::From(objHeader);
|
||||
return nodeRef.GCObjectData().color();
|
||||
}
|
||||
|
||||
WeakCounter& InstallWeakCounter(mm::ThreadData& threadData, ObjHeader* objHeader, ObjHeader** location) {
|
||||
mm::AllocateObject(&threadData, typeHolderWeakCounter.typeInfo(), location);
|
||||
auto& weakCounter = WeakCounter::FromObjHeader(*location);
|
||||
auto& extraObjectData = mm::ExtraObjectData::GetOrInstall(objHeader);
|
||||
*extraObjectData.GetWeakCounterLocation() = weakCounter.header();
|
||||
weakCounter->referred = objHeader;
|
||||
return weakCounter;
|
||||
}
|
||||
|
||||
class SingleThreadMarkAndSweepTest : public testing::Test {
|
||||
public:
|
||||
~SingleThreadMarkAndSweepTest() {
|
||||
mm::GlobalsRegistry::Instance().ClearForTests();
|
||||
mm::GlobalData::Instance().objectFactory().ClearForTests();
|
||||
}
|
||||
|
||||
testing::MockFunction<void(ObjHeader*)>& finalizerHook() { return finalizerHooks_.finalizerHook(); }
|
||||
|
||||
private:
|
||||
FinalizerHooksTestSupport finalizerHooks_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, RootSet) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global1{threadData};
|
||||
GlobalObjectArrayHolder global2{threadData};
|
||||
GlobalCharArrayHolder global3{threadData};
|
||||
StackObjectHolder stack1{threadData};
|
||||
StackObjectArrayHolder stack2{threadData};
|
||||
StackCharArrayHolder stack3{threadData};
|
||||
|
||||
ASSERT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global1.header(), global2.header(), global3.header(), stack1.header(), stack2.header(), stack3.header()));
|
||||
ASSERT_THAT(GetColor(global1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(global3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global1.header(), global2.header(), global3.header(), stack1.header(), stack2.header(), stack3.header()));
|
||||
EXPECT_THAT(GetColor(global1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(global3.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, InterconnectedRootSet) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global1{threadData};
|
||||
GlobalObjectArrayHolder global2{threadData};
|
||||
GlobalCharArrayHolder global3{threadData};
|
||||
StackObjectHolder stack1{threadData};
|
||||
StackObjectArrayHolder stack2{threadData};
|
||||
StackCharArrayHolder stack3{threadData};
|
||||
|
||||
global1->field1 = stack1.header();
|
||||
global1->field2 = global1.header();
|
||||
global1->field3 = global2.header();
|
||||
global2[0] = global1.header();
|
||||
global2[1] = global3.header();
|
||||
stack1->field1 = global1.header();
|
||||
stack1->field2 = stack1.header();
|
||||
stack1->field3 = stack2.header();
|
||||
stack2[0] = stack1.header();
|
||||
stack2[1] = stack3.header();
|
||||
|
||||
ASSERT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global1.header(), global2.header(), global3.header(), stack1.header(), stack2.header(), stack3.header()));
|
||||
ASSERT_THAT(GetColor(global1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(global3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global1.header(), global2.header(), global3.header(), stack1.header(), stack2.header(), stack3.header()));
|
||||
EXPECT_THAT(GetColor(global1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(global3.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, FreeObjects) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
auto& object2 = AllocateObject(threadData);
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1.header(), object2.header()));
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, FreeObjectsWithFinalizers) {
|
||||
RunInNewThread([this](mm::ThreadData& threadData) {
|
||||
auto& object1 = AllocateObjectWithFinalizer(threadData);
|
||||
auto& object2 = AllocateObjectWithFinalizer(threadData);
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1.header(), object2.header()));
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object1.header()));
|
||||
EXPECT_CALL(finalizerHook(), Call(object2.header()));
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, FreeObjectWithFreeWeak) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
auto& weak1 = ([&threadData, &object1]() -> WeakCounter& {
|
||||
ObjHolder holder;
|
||||
return InstallWeakCounter(threadData, object1.header(), holder.slot());
|
||||
})();
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1.header(), weak1.header()));
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
ASSERT_THAT(weak1->referred, object1.header());
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, FreeObjectWithHoldedWeak) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
StackObjectHolder stack{threadData};
|
||||
auto& weak1 = InstallWeakCounter(threadData, object1.header(), &stack->field1);
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1.header(), weak1.header(), stack.header()));
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
ASSERT_THAT(weak1->referred, object1.header());
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(weak1.header(), stack.header()));
|
||||
EXPECT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
EXPECT_THAT(weak1->referred, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, ObjectReferencedFromRootSet) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global{threadData};
|
||||
StackObjectHolder stack{threadData};
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
auto& object2 = AllocateObject(threadData);
|
||||
auto& object3 = AllocateObject(threadData);
|
||||
auto& object4 = AllocateObject(threadData);
|
||||
|
||||
global->field1 = object1.header();
|
||||
object1->field1 = object2.header();
|
||||
stack->field1 = object3.header();
|
||||
object3->field1 = object4.header();
|
||||
|
||||
ASSERT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header()));
|
||||
ASSERT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, ObjectsWithCycles) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global{threadData};
|
||||
StackObjectHolder stack{threadData};
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
auto& object2 = AllocateObject(threadData);
|
||||
auto& object3 = AllocateObject(threadData);
|
||||
auto& object4 = AllocateObject(threadData);
|
||||
auto& object5 = AllocateObject(threadData);
|
||||
auto& object6 = AllocateObject(threadData);
|
||||
|
||||
global->field1 = object1.header();
|
||||
object1->field1 = object2.header();
|
||||
object2->field1 = object1.header();
|
||||
stack->field1 = object3.header();
|
||||
object3->field1 = object4.header();
|
||||
object4->field1 = object3.header();
|
||||
object5->field1 = object6.header();
|
||||
object6->field1 = object5.header();
|
||||
|
||||
ASSERT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header(),
|
||||
object5.header(), object6.header()));
|
||||
ASSERT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, ObjectsWithCyclesAndFinalizers) {
|
||||
RunInNewThread([this](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global{threadData};
|
||||
StackObjectHolder stack{threadData};
|
||||
auto& object1 = AllocateObjectWithFinalizer(threadData);
|
||||
auto& object2 = AllocateObjectWithFinalizer(threadData);
|
||||
auto& object3 = AllocateObjectWithFinalizer(threadData);
|
||||
auto& object4 = AllocateObjectWithFinalizer(threadData);
|
||||
auto& object5 = AllocateObjectWithFinalizer(threadData);
|
||||
auto& object6 = AllocateObjectWithFinalizer(threadData);
|
||||
|
||||
global->field1 = object1.header();
|
||||
object1->field1 = object2.header();
|
||||
object2->field1 = object1.header();
|
||||
stack->field1 = object3.header();
|
||||
object3->field1 = object4.header();
|
||||
object4->field1 = object3.header();
|
||||
object5->field1 = object6.header();
|
||||
object6->field1 = object5.header();
|
||||
|
||||
ASSERT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header(),
|
||||
object5.header(), object6.header()));
|
||||
ASSERT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object5.header()));
|
||||
EXPECT_CALL(finalizerHook(), Call(object6.header()));
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, ObjectsWithCyclesIntoRootSet) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global{threadData};
|
||||
StackObjectHolder stack{threadData};
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
auto& object2 = AllocateObject(threadData);
|
||||
|
||||
global->field1 = object1.header();
|
||||
object1->field1 = global.header();
|
||||
stack->field1 = object2.header();
|
||||
object2->field1 = stack.header();
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), stack.header(), object1.header(), object2.header()));
|
||||
ASSERT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), stack.header(), object1.header(), object2.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, RunGCTwice) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global{threadData};
|
||||
StackObjectHolder stack{threadData};
|
||||
auto& object1 = AllocateObject(threadData);
|
||||
auto& object2 = AllocateObject(threadData);
|
||||
auto& object3 = AllocateObject(threadData);
|
||||
auto& object4 = AllocateObject(threadData);
|
||||
auto& object5 = AllocateObject(threadData);
|
||||
auto& object6 = AllocateObject(threadData);
|
||||
|
||||
global->field1 = object1.header();
|
||||
object1->field1 = object2.header();
|
||||
object2->field1 = object1.header();
|
||||
stack->field1 = object3.header();
|
||||
object3->field1 = object4.header();
|
||||
object4->field1 = object3.header();
|
||||
object5->field1 = object6.header();
|
||||
object6->field1 = object5.header();
|
||||
|
||||
ASSERT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header(),
|
||||
object5.header(), object6.header()));
|
||||
ASSERT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
testing::UnorderedElementsAre(
|
||||
global.header(), stack.header(), object1.header(), object2.header(), object3.header(), object4.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(stack.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, PermanentObjects) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalPermanentObjectHolder global1{threadData};
|
||||
GlobalObjectHolder global2{threadData};
|
||||
test_support::Object<Payload> permanentObject{typeHolder.typeInfo()};
|
||||
permanentObject.header()->typeInfoOrMeta_ = setPointerBits(permanentObject.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||
RuntimeAssert(permanentObject.header()->permanent(), "Must be permanent");
|
||||
|
||||
global1->field1 = permanentObject.header();
|
||||
global2->field1 = global1.header();
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(global2.header()));
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global2.header()));
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SingleThreadMarkAndSweepTest, SameObjectInRootSet) {
|
||||
RunInNewThread([](mm::ThreadData& threadData) {
|
||||
GlobalObjectHolder global{threadData};
|
||||
StackObjectHolder stack(*global);
|
||||
auto& object = AllocateObject(threadData);
|
||||
|
||||
global->field1 = object.header();
|
||||
|
||||
ASSERT_THAT(global.header(), stack.header());
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), object.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), object.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user