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:
Alexander Shabalin
2021-04-02 12:13:44 +00:00
committed by Space
parent e7102cd63f
commit 71397a06fb
16 changed files with 2161 additions and 22 deletions
@@ -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;
};
+2 -1
View File
@@ -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_;
+6 -1
View File
@@ -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);
});
}