[K/N] Use intrusive list for mark queue ^KT-51436

Merge-request: KT-MR-5912
Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
Alexander Shabalin
2022-04-14 08:19:05 +00:00
committed by Space
parent 231fe42c0a
commit 46fc71ef1e
9 changed files with 98 additions and 37 deletions
@@ -36,16 +36,17 @@ struct MarkTraits {
} }
static ObjHeader* dequeue(MarkQueue& queue) noexcept { static ObjHeader* dequeue(MarkQueue& queue) noexcept {
auto top = queue.back(); auto& top = queue.front();
queue.pop_back(); queue.pop_front();
return top; auto node = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>::NodeRef::From(top);
return node->GetObjHeader();
} }
static void enqueue(MarkQueue& queue, ObjHeader* object) noexcept { static void enqueue(MarkQueue& queue, ObjHeader* object) noexcept {
auto& objectData = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>::NodeRef::From(object).ObjectData(); auto& objectData = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>::NodeRef::From(object).ObjectData();
if (objectData.color() == gc::ConcurrentMarkAndSweep::ObjectData::Color::kBlack) return; if (objectData.color() == gc::ConcurrentMarkAndSweep::ObjectData::Color::kBlack) return;
objectData.setColor(gc::ConcurrentMarkAndSweep::ObjectData::Color::kBlack); objectData.setColor(gc::ConcurrentMarkAndSweep::ObjectData::Color::kBlack);
queue.push_back(object); queue.push_front(objectData);
} }
}; };
@@ -96,7 +97,6 @@ gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(
objectFactory_(objectFactory), objectFactory_(objectFactory),
gcScheduler_(gcScheduler), gcScheduler_(gcScheduler),
finalizerProcessor_(make_unique<FinalizerProcessor>([this](int64_t epoch) { state_.finalized(epoch); })) { finalizerProcessor_(make_unique<FinalizerProcessor>([this](int64_t epoch) { state_.finalized(epoch); })) {
markQueue_.reserve(1000);
gcScheduler_.SetScheduleGC([this]() NO_INLINE { gcScheduler_.SetScheduleGC([this]() NO_INLINE {
RuntimeLogDebug({kTagGC}, "Scheduling GC by thread %d", konan::currentThreadId()); RuntimeLogDebug({kTagGC}, "Scheduling GC by thread %d", konan::currentThreadId());
// This call acquires a lock, so we need to ensure that we're in the safe state. // This call acquires a lock, so we need to ensure that we're in the safe state.
@@ -9,6 +9,7 @@
#include "Allocator.hpp" #include "Allocator.hpp"
#include "GCScheduler.hpp" #include "GCScheduler.hpp"
#include "IntrusiveList.hpp"
#include "ObjectFactory.hpp" #include "ObjectFactory.hpp"
#include "ScopedThread.hpp" #include "ScopedThread.hpp"
#include "Types.h" #include "Types.h"
@@ -29,24 +30,39 @@ class FinalizerProcessor;
// TODO: Also make mark concurrent. // TODO: Also make mark concurrent.
class ConcurrentMarkAndSweep : private Pinned { class ConcurrentMarkAndSweep : private Pinned {
public: public:
// This implementation of mark queue allocates memory during collection.
using MarkQueue = KStdVector<ObjHeader*>;
class ObjectData { class ObjectData {
static inline constexpr unsigned colorMask = (1 << 1) - 1;
public: public:
enum class Color { enum class Color : unsigned {
kWhite = 0, // Initial color at the start of collection cycles. Objects with this color at the end of GC cycle are collected. 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. // All new objects are allocated with this color.
kBlack, // Objects encountered during mark phase. kBlack, // Objects encountered during mark phase.
}; };
Color color() const noexcept { return color_; } Color color() const noexcept { return static_cast<Color>(getPointerBits(next_, colorMask)); }
void setColor(Color color) noexcept { color_ = color; } void setColor(Color color) noexcept { next_ = setPointerBits(clearPointerBits(next_, colorMask), static_cast<unsigned>(color)); }
ObjectData* next() const noexcept { return clearPointerBits(next_, colorMask); }
void setNext(ObjectData* next) noexcept {
RuntimeAssert(!hasPointerBits(next, colorMask), "next must be untagged: %p", next);
auto bits = getPointerBits(next_, colorMask);
next_ = setPointerBits(next, bits);
}
private: private:
Color color_ = Color::kWhite; // Color is encoded in low bits.
ObjectData* next_ = nullptr;
}; };
struct MarkQueueTraits {
static ObjectData* next(const ObjectData& value) noexcept { return value.next(); }
static void setNext(ObjectData& value, ObjectData* next) noexcept { value.setNext(next); }
};
using MarkQueue = intrusive_forward_list<ObjectData, MarkQueueTraits>;
class ThreadData : private Pinned { class ThreadData : private Pinned {
public: public:
using ObjectData = ConcurrentMarkAndSweep::ObjectData; using ObjectData = ConcurrentMarkAndSweep::ObjectData;
@@ -34,16 +34,17 @@ struct MarkTraits {
} }
static ObjHeader* dequeue(MarkQueue& queue) noexcept { static ObjHeader* dequeue(MarkQueue& queue) noexcept {
auto top = queue.back(); auto& top = queue.front();
queue.pop_back(); queue.pop_front();
return top; auto node = mm::ObjectFactory<gc::SameThreadMarkAndSweep>::NodeRef::From(top);
return node->GetObjHeader();
} }
static void enqueue(MarkQueue& queue, ObjHeader* object) noexcept { static void enqueue(MarkQueue& queue, ObjHeader* object) noexcept {
auto& objectData = mm::ObjectFactory<gc::SameThreadMarkAndSweep>::NodeRef::From(object).ObjectData(); auto& objectData = mm::ObjectFactory<gc::SameThreadMarkAndSweep>::NodeRef::From(object).ObjectData();
if (objectData.color() == gc::SameThreadMarkAndSweep::ObjectData::Color::kBlack) return; if (objectData.color() == gc::SameThreadMarkAndSweep::ObjectData::Color::kBlack) return;
objectData.setColor(gc::SameThreadMarkAndSweep::ObjectData::Color::kBlack); objectData.setColor(gc::SameThreadMarkAndSweep::ObjectData::Color::kBlack);
queue.push_back(object); queue.push_front(objectData);
} }
}; };
@@ -115,7 +116,6 @@ NO_INLINE void gc::SameThreadMarkAndSweep::ThreadData::SafePointSlowPath(Safepoi
gc::SameThreadMarkAndSweep::SameThreadMarkAndSweep( gc::SameThreadMarkAndSweep::SameThreadMarkAndSweep(
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept : mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept :
objectFactory_(objectFactory), gcScheduler_(gcScheduler) { objectFactory_(objectFactory), gcScheduler_(gcScheduler) {
markQueue_.reserve(1000);
gcScheduler_.SetScheduleGC([]() { gcScheduler_.SetScheduleGC([]() {
// TODO: CMS is also responsible for avoiding scheduling while GC hasn't started running. // TODO: CMS is also responsible for avoiding scheduling while GC hasn't started running.
// Investigate, if it's possible to move this logic into the scheduler. // Investigate, if it's possible to move this logic into the scheduler.
@@ -10,6 +10,7 @@
#include "Allocator.hpp" #include "Allocator.hpp"
#include "GCScheduler.hpp" #include "GCScheduler.hpp"
#include "IntrusiveList.hpp"
#include "ObjectFactory.hpp" #include "ObjectFactory.hpp"
#include "Types.h" #include "Types.h"
#include "Utils.hpp" #include "Utils.hpp"
@@ -25,9 +26,6 @@ namespace gc {
// Stop-the-world Mark-and-Sweep that runs on mutator threads. Can support targets that do not have threads. // Stop-the-world Mark-and-Sweep that runs on mutator threads. Can support targets that do not have threads.
class SameThreadMarkAndSweep : private Pinned { class SameThreadMarkAndSweep : private Pinned {
public: public:
// This implementation of mark queue allocates memory during collection.
using MarkQueue = KStdVector<ObjHeader*>;
enum class SafepointFlag { enum class SafepointFlag {
kNone, kNone,
kNeedsSuspend, kNeedsSuspend,
@@ -35,20 +33,38 @@ public:
}; };
class ObjectData { class ObjectData {
static inline constexpr unsigned colorMask = (1 << 1) - 1;
public: public:
enum class Color { enum class Color : unsigned {
kWhite = 0, // Initial color at the start of collection cycles. Objects with this color at the end of GC cycle are collected. 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. // All new objects are allocated with this color.
kBlack, // Objects encountered during mark phase. kBlack, // Objects encountered during mark phase.
}; };
Color color() const noexcept { return color_; } Color color() const noexcept { return static_cast<Color>(getPointerBits(next_, colorMask)); }
void setColor(Color color) noexcept { color_ = color; } void setColor(Color color) noexcept { next_ = setPointerBits(clearPointerBits(next_, colorMask), static_cast<unsigned>(color)); }
ObjectData* next() const noexcept { return clearPointerBits(next_, colorMask); }
void setNext(ObjectData* next) noexcept {
RuntimeAssert(!hasPointerBits(next, colorMask), "next must be untagged: %p", next);
auto bits = getPointerBits(next_, colorMask);
next_ = setPointerBits(next, bits);
}
private: private:
Color color_ = Color::kWhite; // Color is encoded in low bits.
ObjectData* next_ = nullptr;
}; };
struct MarkQueueTraits {
static ObjectData* next(const ObjectData& value) noexcept { return value.next(); }
static void setNext(ObjectData& value, ObjectData* next) noexcept { value.setNext(next); }
};
using MarkQueue = intrusive_forward_list<ObjectData, MarkQueueTraits>;
class ThreadData : private Pinned { class ThreadData : private Pinned {
public: public:
using ObjectData = SameThreadMarkAndSweep::ObjectData; using ObjectData = SameThreadMarkAndSweep::ObjectData;
@@ -12,6 +12,7 @@
#include "Mutex.hpp" #include "Mutex.hpp"
#include "Types.h" #include "Types.h"
#include "Utils.hpp"
namespace kotlin { namespace kotlin {
@@ -34,7 +35,7 @@ public:
static Node& fromValue(T& t) noexcept { static Node& fromValue(T& t) noexcept {
static_assert(std::is_base_of_v<Pinned, T>, "fromValue function only makes sense for non-movable object"); static_assert(std::is_base_of_v<Pinned, T>, "fromValue function only makes sense for non-movable object");
return *reinterpret_cast<Node*>(reinterpret_cast<uintptr_t>(&t) - offsetof(Node, value_)); return ownerOf(Node, value_, t);
} }
private: private:
@@ -80,6 +80,8 @@ private:
size_t CombineHash(size_t seed, size_t value); size_t CombineHash(size_t seed, size_t value);
#define ownerOf(type, field, ref) *reinterpret_cast<type*>(reinterpret_cast<char*>(&ref) - offsetof(type, field))
} // namespace kotlin } // namespace kotlin
#endif // RUNTIME_UTILS_H #endif // RUNTIME_UTILS_H
@@ -7,6 +7,7 @@
#include <type_traits> #include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
using namespace kotlin; using namespace kotlin;
@@ -48,3 +49,27 @@ TEST(UtilsTest, PinnedImpl) {
static_assert(!std::is_move_assignable_v<PinnedImpl>, "Must not be move assignable"); static_assert(!std::is_move_assignable_v<PinnedImpl>, "Must not be move assignable");
static_assert(sizeof(PinnedImpl) == sizeof(A), "Must not increase size"); static_assert(sizeof(PinnedImpl) == sizeof(A), "Must not increase size");
} }
namespace {
class Container {
public:
static Container& fromX(int32_t& x) { return ownerOf(Container, x_, x); }
static Container& fromY(void*& y) { return ownerOf(Container, y_, y); }
int32_t& x() { return x_; }
void*& y() { return y_; }
private:
int32_t x_ = 0;
void* y_ = nullptr;
};
} // namespace
TEST(UtilsTest, OwnerOf) {
Container c;
EXPECT_THAT(&c, &Container::fromX(c.x()));
EXPECT_THAT(&c, &Container::fromY(c.y()));
}
@@ -453,18 +453,19 @@ public:
static NodeRef From(ObjHeader* object) noexcept { static NodeRef From(ObjHeader* object) noexcept {
RuntimeAssert(object->heap(), "Must be a heap object"); RuntimeAssert(object->heap(), "Must be a heap object");
auto* heapObject = reinterpret_cast<HeapObjHeader*>(reinterpret_cast<uintptr_t>(object) - offsetof(HeapObjHeader, object)); auto& heapObject = ownerOf(HeapObjHeader, object, *object);
RuntimeAssert(&heapObject->object == object, "HeapObjHeader layout has broken"); return NodeRef(Storage::Node::FromData(&heapObject));
return NodeRef(Storage::Node::FromData(heapObject));
} }
static NodeRef From(ArrayHeader* array) noexcept { static NodeRef From(ArrayHeader* array) noexcept {
// `ArrayHeader` and `ObjHeader` are kept compatible, so the former can RuntimeAssert(array->obj()->heap(), "Must be a heap object");
// be always casted to the other. auto& heapArray = ownerOf(HeapArrayHeader, array, *array);
RuntimeAssert(reinterpret_cast<ObjHeader*>(array)->heap(), "Must be a heap object"); return NodeRef(Storage::Node::FromData(&heapArray));
auto* heapArray = reinterpret_cast<HeapArrayHeader*>(reinterpret_cast<uintptr_t>(array) - offsetof(HeapArrayHeader, array)); }
RuntimeAssert(&heapArray->array == array, "HeapArrayHeader layout has broken");
return NodeRef(Storage::Node::FromData(heapArray)); static NodeRef From(ObjectData& objectData) noexcept {
auto& heapObject = ownerOf(HeapObjHeader, gcData, objectData);
return NodeRef(Storage::Node::FromData(&heapObject));
} }
NodeRef* operator->() noexcept { return this; } NodeRef* operator->() noexcept { return this; }
@@ -869,7 +869,7 @@ TEST(ObjectFactoryTest, CreateObject) {
testing::Mock::VerifyAndClearExpectations(&allocator); testing::Mock::VerifyAndClearExpectations(&allocator);
EXPECT_THAT(allocSize, testing::Gt<size_t>(type.typeInfo()->instanceSize_)); EXPECT_THAT(allocSize, testing::Gt<size_t>(type.typeInfo()->instanceSize_));
EXPECT_THAT(allocAddress, testing::Ne(nullptr)); EXPECT_THAT(allocAddress, testing::Ne(nullptr));
EXPECT_THAT(mm::GetAllocatedHeapSize(object), allocSize); EXPECT_THAT(ObjectFactory::GetAllocatedHeapSize(object), allocSize);
EXPECT_THAT(object->type_info(), type.typeInfo()); EXPECT_THAT(object->type_info(), type.typeInfo());
threadQueue.Publish(); threadQueue.Publish();
@@ -904,7 +904,7 @@ TEST(ObjectFactoryTest, CreateObjectArray) {
testing::Mock::VerifyAndClearExpectations(&allocator); testing::Mock::VerifyAndClearExpectations(&allocator);
EXPECT_THAT(allocSize, testing::Gt<size_t>(-theArrayTypeInfo->instanceSize_ * 3)); EXPECT_THAT(allocSize, testing::Gt<size_t>(-theArrayTypeInfo->instanceSize_ * 3));
EXPECT_THAT(allocAddress, testing::Ne(nullptr)); EXPECT_THAT(allocAddress, testing::Ne(nullptr));
EXPECT_THAT(mm::GetAllocatedHeapSize(array->obj()), allocSize); EXPECT_THAT(ObjectFactory::GetAllocatedHeapSize(array->obj()), allocSize);
EXPECT_THAT(array->type_info(), theArrayTypeInfo); EXPECT_THAT(array->type_info(), theArrayTypeInfo);
threadQueue.Publish(); threadQueue.Publish();
@@ -939,7 +939,7 @@ TEST(ObjectFactoryTest, CreateCharArray) {
testing::Mock::VerifyAndClearExpectations(&allocator); testing::Mock::VerifyAndClearExpectations(&allocator);
EXPECT_THAT(allocSize, testing::Gt<size_t>(-theCharArrayTypeInfo->instanceSize_ * 3)); EXPECT_THAT(allocSize, testing::Gt<size_t>(-theCharArrayTypeInfo->instanceSize_ * 3));
EXPECT_THAT(allocAddress, testing::Ne(nullptr)); EXPECT_THAT(allocAddress, testing::Ne(nullptr));
EXPECT_THAT(mm::GetAllocatedHeapSize(array->obj()), allocSize); EXPECT_THAT(ObjectFactory::GetAllocatedHeapSize(array->obj()), allocSize);
EXPECT_THAT(array->type_info(), theCharArrayTypeInfo); EXPECT_THAT(array->type_info(), theCharArrayTypeInfo);
threadQueue.Publish(); threadQueue.Publish();