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) {
|
task freeze6(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // No exceptions on WASM.
|
enabled = (project.testTarget != 'wasm32') // No exceptions on WASM.
|
||||||
!isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
goldValue = "OK\nOK\n"
|
goldValue = "OK\nOK\n"
|
||||||
source = "runtime/workers/freeze6.kt"
|
source = "runtime/workers/freeze6.kt"
|
||||||
}
|
}
|
||||||
@@ -1057,13 +1056,11 @@ task lazy1(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("lazy2") {
|
standaloneTest("lazy2") {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
goldValue = "123\nOK\n"
|
goldValue = "123\nOK\n"
|
||||||
source = "runtime/workers/lazy2.kt"
|
source = "runtime/workers/lazy2.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("lazy3") {
|
standaloneTest("lazy3") {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
source = "runtime/workers/lazy3.kt"
|
source = "runtime/workers/lazy3.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2876,13 +2873,11 @@ task memory_escape1(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task memory_cycles0(type: KonanLocalTest) {
|
task memory_cycles0(type: KonanLocalTest) {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
goldValue = "42\n"
|
goldValue = "42\n"
|
||||||
source = "runtime/memory/cycles0.kt"
|
source = "runtime/memory/cycles0.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task memory_cycles1(type: KonanLocalTest) {
|
task memory_cycles1(type: KonanLocalTest) {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
source = "runtime/memory/cycles1.kt"
|
source = "runtime/memory/cycles1.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2896,7 +2891,6 @@ task memory_escape2(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task memory_weak0(type: KonanLocalTest) {
|
task memory_weak0(type: KonanLocalTest) {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
goldValue = "Data(s=Hello)\nnull\nOK\n"
|
goldValue = "Data(s=Hello)\nnull\nOK\n"
|
||||||
source = "runtime/memory/weak0.kt"
|
source = "runtime/memory/weak0.kt"
|
||||||
}
|
}
|
||||||
@@ -2907,7 +2901,6 @@ task memory_weak1(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("memory_only_gc") {
|
standaloneTest("memory_only_gc") {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
source = "runtime/memory/only_gc.kt"
|
source = "runtime/memory/only_gc.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4207,7 +4200,6 @@ standaloneTest("interop_opengl_teapot") {
|
|||||||
|
|
||||||
if (PlatformInfo.isAppleTarget(project)) {
|
if (PlatformInfo.isAppleTarget(project)) {
|
||||||
interopTest("interop_objc_smoke") {
|
interopTest("interop_objc_smoke") {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have a GC yet.
|
|
||||||
goldValue = "84\nFoo\nDeallocated\n" +
|
goldValue = "84\nFoo\nDeallocated\n" +
|
||||||
"Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
"Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
||||||
"true\ntrue\n" +
|
"true\ntrue\n" +
|
||||||
@@ -4242,7 +4234,7 @@ if (PlatformInfo.isAppleTarget(project)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interopTestMultifile("interop_objc_tests") {
|
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/"
|
source = "interop/objc/tests/"
|
||||||
interop = 'objcTests'
|
interop = 'objcTests'
|
||||||
flags = ['-tr', '-e', 'main']
|
flags = ['-tr', '-e', 'main']
|
||||||
@@ -4492,7 +4484,7 @@ dynamicTest("interop_kt42397") {
|
|||||||
|
|
||||||
dynamicTest("interop_cleaners_main_thread") {
|
dynamicTest("interop_cleaners_main_thread") {
|
||||||
disabled = (project.target.name != project.hostName) ||
|
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"
|
source = "interop/cleaners/cleaners.kt"
|
||||||
cSource = "$projectDir/interop/cleaners/main_thread.cpp"
|
cSource = "$projectDir/interop/cleaners/main_thread.cpp"
|
||||||
clangTool = "clang++"
|
clangTool = "clang++"
|
||||||
@@ -4826,7 +4818,6 @@ if (isAppleTarget(project)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frameworkTest("testStdlibFramework") {
|
frameworkTest("testStdlibFramework") {
|
||||||
enabled = !isExperimentalMM // Experimental MM does not have GC yet.
|
|
||||||
framework('Stdlib') {
|
framework('Stdlib') {
|
||||||
sources = ['framework/stdlib']
|
sources = ['framework/stdlib']
|
||||||
bitcode = true
|
bitcode = true
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ struct ObjHeader;
|
|||||||
|
|
||||||
namespace kotlin {
|
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;
|
bool HasFinalizers(ObjHeader* object) noexcept;
|
||||||
void RunFinalizers(ObjHeader* object) noexcept;
|
void RunFinalizers(ObjHeader* object) noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -209,66 +209,110 @@ private:
|
|||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class ObjectArray : public internal::Array<ObjHeader*, ElementCount> {
|
class ObjectArray : public internal::Array<ObjHeader*, ElementCount> {
|
||||||
public:
|
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) {}
|
ObjectArray() noexcept : internal::Array<ObjHeader*, ElementCount>(theArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class BooleanArray : public internal::Array<KBoolean, ElementCount> {
|
class BooleanArray : public internal::Array<KBoolean, ElementCount> {
|
||||||
public:
|
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) {}
|
BooleanArray() noexcept : internal::Array<KBoolean, ElementCount>(theBooleanArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class ByteArray : public internal::Array<KByte, ElementCount> {
|
class ByteArray : public internal::Array<KByte, ElementCount> {
|
||||||
public:
|
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) {}
|
ByteArray() noexcept : internal::Array<KByte, ElementCount>(theByteArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class CharArray : public internal::Array<KChar, ElementCount> {
|
class CharArray : public internal::Array<KChar, ElementCount> {
|
||||||
public:
|
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) {}
|
CharArray() noexcept : internal::Array<KChar, ElementCount>(theCharArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class DoubleArray : public internal::Array<KDouble, ElementCount> {
|
class DoubleArray : public internal::Array<KDouble, ElementCount> {
|
||||||
public:
|
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) {}
|
DoubleArray() noexcept : internal::Array<KDouble, ElementCount>(theDoubleArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class FloatArray : public internal::Array<KFloat, ElementCount> {
|
class FloatArray : public internal::Array<KFloat, ElementCount> {
|
||||||
public:
|
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) {}
|
FloatArray() noexcept : internal::Array<KFloat, ElementCount>(theFloatArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class IntArray : public internal::Array<KInt, ElementCount> {
|
class IntArray : public internal::Array<KInt, ElementCount> {
|
||||||
public:
|
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) {}
|
IntArray() noexcept : internal::Array<KInt, ElementCount>(theIntArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class LongArray : public internal::Array<KLong, ElementCount> {
|
class LongArray : public internal::Array<KLong, ElementCount> {
|
||||||
public:
|
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) {}
|
LongArray() noexcept : internal::Array<KLong, ElementCount>(theLongArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class NativePtrArray : public internal::Array<KNativePtr, ElementCount> {
|
class NativePtrArray : public internal::Array<KNativePtr, ElementCount> {
|
||||||
public:
|
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) {}
|
NativePtrArray() noexcept : internal::Array<KNativePtr, ElementCount>(theNativePtrArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class ShortArray : public internal::Array<KShort, ElementCount> {
|
class ShortArray : public internal::Array<KShort, ElementCount> {
|
||||||
public:
|
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) {}
|
ShortArray() noexcept : internal::Array<KShort, ElementCount>(theShortArrayTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t ElementCount>
|
template <size_t ElementCount>
|
||||||
class String : public internal::Array<KChar, ElementCount> {
|
class String : public internal::Array<KChar, ElementCount> {
|
||||||
public:
|
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) {}
|
String() noexcept : internal::Array<KChar, ElementCount>(theStringTypeInfo) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "ExtraObjectData.hpp"
|
#include "ExtraObjectData.hpp"
|
||||||
|
|
||||||
|
#include "ObjectOps.hpp"
|
||||||
#include "PointerBits.h"
|
#include "PointerBits.h"
|
||||||
#include "Weak.h"
|
#include "Weak.h"
|
||||||
|
|
||||||
@@ -64,11 +65,19 @@ void mm::ExtraObjectData::Uninstall(ObjHeader* object) noexcept {
|
|||||||
delete &data;
|
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() {
|
mm::ExtraObjectData::~ExtraObjectData() {
|
||||||
if (weakReferenceCounter_) {
|
RuntimeAssert(!HasWeakReferenceCounter(), "Object must have cleared weak references");
|
||||||
WeakReferenceCounterClear(weakReferenceCounter_);
|
|
||||||
ZeroHeapRef(&weakReferenceCounter_);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef KONAN_OBJC_INTEROP
|
#ifdef KONAN_OBJC_INTEROP
|
||||||
Kotlin_ObjCExport_releaseAssociatedObject(associatedObject_);
|
Kotlin_ObjCExport_releaseAssociatedObject(associatedObject_);
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public:
|
|||||||
|
|
||||||
std::atomic<Flags>& flags() noexcept { return flags_; }
|
std::atomic<Flags>& flags() noexcept { return flags_; }
|
||||||
|
|
||||||
|
bool HasWeakReferenceCounter() noexcept;
|
||||||
|
void ClearWeakReferenceCounter() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ExtraObjectData(const TypeInfo* typeInfo) noexcept : typeInfo_(typeInfo) {}
|
explicit ExtraObjectData(const TypeInfo* typeInfo) noexcept : typeInfo_(typeInfo) {}
|
||||||
~ExtraObjectData();
|
~ExtraObjectData();
|
||||||
@@ -60,7 +63,6 @@ private:
|
|||||||
void* associatedObject_ = nullptr;
|
void* associatedObject_ = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// TODO: Need to respect when marking.
|
|
||||||
ObjHeader* weakReferenceCounter_ = nullptr;
|
ObjHeader* weakReferenceCounter_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#ifndef RUNTIME_MM_GC_H
|
#ifndef RUNTIME_MM_GC_H
|
||||||
#define RUNTIME_MM_GC_H
|
#define RUNTIME_MM_GC_H
|
||||||
|
|
||||||
|
#include "gc/SingleThreadMarkAndSweep.hpp"
|
||||||
#include "gc/NoOpGC.hpp"
|
#include "gc/NoOpGC.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
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
|
// 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.
|
// 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 mm
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ public:
|
|||||||
// much of a problem is it.
|
// much of a problem is it.
|
||||||
Iterable Iter() noexcept { return globals_.Iter(); }
|
Iterable Iter() noexcept { return globals_.Iter(); }
|
||||||
|
|
||||||
|
void ClearForTests() { globals_.ClearForTests(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// TODO: Add-only MultiSourceQueue can be made more efficient. Measure, if it's a problem.
|
// TODO: Add-only MultiSourceQueue can be made more efficient. Measure, if it's a problem.
|
||||||
MultiSourceQueue<ObjHeader**> globals_;
|
MultiSourceQueue<ObjHeader**> globals_;
|
||||||
|
|||||||
@@ -92,7 +92,12 @@ extern "C" MemoryState* InitMemory(bool firstRuntime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void DeinitMemory(MemoryState* state, bool destroyRuntime) {
|
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*) {
|
extern "C" void RestoreMemory(MemoryState*) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "Alignment.hpp"
|
#include "Alignment.hpp"
|
||||||
#include "Alloc.h"
|
#include "Alloc.h"
|
||||||
|
#include "FinalizerHooks.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
@@ -101,6 +102,26 @@ public:
|
|||||||
|
|
||||||
class Producer : private MoveOnly {
|
class Producer : private MoveOnly {
|
||||||
public:
|
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(ObjectFactoryStorage& owner, Allocator allocator) noexcept : owner_(owner), allocator_(std::move(allocator)) {}
|
||||||
|
|
||||||
~Producer() { Publish(); }
|
~Producer() { Publish(); }
|
||||||
@@ -158,6 +179,9 @@ public:
|
|||||||
owner_.AssertCorrectUnsafe();
|
owner_.AssertCorrectUnsafe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iterator begin() noexcept { return Iterator(root_.get()); }
|
||||||
|
Iterator end() noexcept { return Iterator(nullptr); }
|
||||||
|
|
||||||
void ClearForTests() noexcept {
|
void ClearForTests() noexcept {
|
||||||
// Since it's only for tests, no need to worry about stack overflows.
|
// Since it's only for tests, no need to worry about stack overflows.
|
||||||
root_.reset();
|
root_.reset();
|
||||||
@@ -302,6 +326,11 @@ public:
|
|||||||
// Lock `ObjectFactoryStorage` for safe iteration.
|
// Lock `ObjectFactoryStorage` for safe iteration.
|
||||||
Iterable Iter() noexcept { return Iterable(*this); }
|
Iterable Iter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
|
void ClearForTests() {
|
||||||
|
root_.reset();
|
||||||
|
last_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Expects `mutex_` to be held by the current thread.
|
// Expects `mutex_` to be held by the current thread.
|
||||||
std::pair<unique_ptr<Node>, Node*> ExtractUnsafe(Node* previousNode) noexcept {
|
std::pair<unique_ptr<Node>, Node*> ExtractUnsafe(Node* previousNode) noexcept {
|
||||||
@@ -454,6 +483,27 @@ public:
|
|||||||
|
|
||||||
class ThreadQueue : private MoveOnly {
|
class ThreadQueue : private MoveOnly {
|
||||||
public:
|
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 :
|
ThreadQueue(ObjectFactory& owner, GCThreadData& gc) noexcept :
|
||||||
producer_(owner.storage_, internal::AllocatorWithGC(internal::SimpleAllocator(), gc)) {}
|
producer_(owner.storage_, internal::AllocatorWithGC(internal::SimpleAllocator(), gc)) {}
|
||||||
|
|
||||||
@@ -483,6 +533,9 @@ public:
|
|||||||
|
|
||||||
void Publish() noexcept { producer_.Publish(); }
|
void Publish() noexcept { producer_.Publish(); }
|
||||||
|
|
||||||
|
Iterator begin() noexcept { return Iterator(producer_.begin()); }
|
||||||
|
Iterator end() noexcept { return Iterator(producer_.end()); }
|
||||||
|
|
||||||
void ClearForTests() noexcept { producer_.ClearForTests(); }
|
void ClearForTests() noexcept { producer_.ClearForTests(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -534,8 +587,27 @@ public:
|
|||||||
typename Storage::Consumer::Iterator iterator_;
|
typename Storage::Consumer::Iterator iterator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
Iterator begin() noexcept { return Iterator(consumer_.begin()); }
|
class Iterable {
|
||||||
Iterator end() noexcept { return Iterator(consumer_.end()); }
|
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:
|
private:
|
||||||
friend class ObjectFactory;
|
friend class ObjectFactory;
|
||||||
@@ -565,6 +637,8 @@ public:
|
|||||||
|
|
||||||
Iterable Iter() noexcept { return Iterable(*this); }
|
Iterable Iter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
|
void ClearForTests() { storage_.ClearForTests(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Storage storage_;
|
Storage storage_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "FinalizerHooksTestSupport.hpp"
|
||||||
#include "GC.hpp"
|
#include "GC.hpp"
|
||||||
#include "ObjectTestSupport.hpp"
|
#include "ObjectTestSupport.hpp"
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
@@ -889,13 +890,45 @@ TEST(ObjectFactoryTest, Move) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
int count = 0;
|
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_TRUE(it->IsArray());
|
||||||
}
|
}
|
||||||
EXPECT_THAT(count, 10);
|
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(ObjectFactoryTest, ConcurrentPublish) {
|
||||||
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||||
ObjectFactory objectFactory;
|
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