Sketch GC interface (#4682)

This commit is contained in:
Alexander Shabalin
2021-02-08 11:10:22 +03:00
committed by Vasily Levchenko
parent 61d3ae1477
commit d875cb39c9
10 changed files with 436 additions and 147 deletions
@@ -85,6 +85,8 @@ struct ObjHeader {
return hasPointerBits(typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
}
inline bool heap() const { return getPointerBits(typeInfoOrMeta_, OBJECT_TAG_MASK) == 0; }
static MetaObjHeader* createMetaObject(ObjHeader* object);
static void destroyMetaObject(ObjHeader* object);
};
+22
View File
@@ -0,0 +1,22 @@
/*
* 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_H
#define RUNTIME_MM_GC_H
#include "gc/NoOpGC.hpp"
namespace kotlin {
namespace mm {
// TODO: GC should be extracted into a separate module, so that we can do different GCs without
// the need to redo the entire MM. For now changing GCs can be done by modifying `using` below.
using GC = NoOpGC;
} // namespace mm
} // namespace kotlin
#endif // RUNTIME_MM_GC_H
@@ -8,6 +8,7 @@
#include "ObjectFactory.hpp"
#include "GlobalsRegistry.hpp"
#include "GC.hpp"
#include "StableRefRegistry.hpp"
#include "ThreadRegistry.hpp"
#include "Utils.hpp"
@@ -23,7 +24,8 @@ public:
ThreadRegistry& threadRegistry() noexcept { return threadRegistry_; }
GlobalsRegistry& globalsRegistry() noexcept { return globalsRegistry_; }
StableRefRegistry& stableRefRegistry() noexcept { return stableRefRegistry_; }
ObjectFactory& objectFactory() noexcept { return objectFactory_; }
ObjectFactory<GC>& objectFactory() noexcept { return objectFactory_; }
GC& gc() noexcept { return gc_; }
private:
GlobalData();
@@ -34,7 +36,8 @@ private:
ThreadRegistry threadRegistry_;
GlobalsRegistry globalsRegistry_;
StableRefRegistry stableRefRegistry_;
ObjectFactory objectFactory_;
ObjectFactory<GC> objectFactory_;
GC gc_;
};
} // namespace mm
+25 -1
View File
@@ -253,6 +253,11 @@ extern "C" RUNTIME_NOTHROW void GC_CollectorCallback(void* worker) {
// Nothing to do
}
extern "C" void Kotlin_native_internal_GC_collect(ObjHeader*) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->gc().PerformFullGC();
}
extern "C" void Kotlin_native_internal_GC_collectCyclic(ObjHeader*) {
// TODO: Remove when legacy MM is gone.
ThrowIllegalArgumentException();
@@ -289,6 +294,10 @@ extern "C" void Kotlin_Any_share(ObjHeader* thiz) {
// Nothing to do
}
extern "C" RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) {
GetThreadData(memory)->gc().PerformFullGC();
}
extern "C" RUNTIME_NOTHROW bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
// TODO: Remove when legacy MM is gone.
return true;
@@ -366,7 +375,22 @@ extern "C" void AdoptReferenceFromSharedVariable(ObjHeader* object) {
// Nothing to do.
}
void CheckGlobalsAccessible() {
extern "C" void CheckGlobalsAccessible() {
// TODO: Remove when legacy MM is gone.
// Always accessible
}
extern "C" RUNTIME_NOTHROW void Kotlin_mm_safePointFunctionEpilogue() {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->gc().SafePointFunctionEpilogue();
}
extern "C" RUNTIME_NOTHROW void Kotlin_mm_safePointWhileLoopBody() {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->gc().SafePointLoopBody();
}
extern "C" RUNTIME_NOTHROW void Kotlin_mm_safePointExceptionUnwind() {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->gc().SafePointExceptionUnwind();
}
@@ -1,61 +0,0 @@
/*
* Copyright 2010-2020 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 "ObjectFactory.hpp"
#include "Alignment.hpp"
#include "Alloc.h"
#include "GlobalData.hpp"
#include "Types.h"
using namespace kotlin;
ObjHeader* mm::ObjectFactory::ThreadQueue::CreateObject(const TypeInfo* typeInfo) noexcept {
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
size_t allocSize = typeInfo->instanceSize_;
auto& node = producer_.Insert(allocSize);
auto* object = static_cast<ObjHeader*>(node.Data());
object->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
return object;
}
ArrayHeader* mm::ObjectFactory::ThreadQueue::CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept {
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
uint32_t arraySize = static_cast<uint32_t>(-typeInfo->instanceSize_) * count;
// Note: array body is aligned, but for size computation it is enough to align the sum.
size_t allocSize = AlignUp(sizeof(ArrayHeader) + arraySize, kObjectAlignment);
auto& node = producer_.Insert(allocSize);
auto* array = static_cast<ArrayHeader*>(node.Data());
array->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
array->count_ = count;
return array;
}
bool mm::ObjectFactory::Iterator::IsArray() noexcept {
// `ArrayHeader` and `ObjHeader` are kept compatible, so the former can
// be always casted to the other.
auto* object = static_cast<ObjHeader*>((*iterator_).Data());
return object->type_info()->IsArray();
}
ObjHeader* mm::ObjectFactory::Iterator::GetObjHeader() noexcept {
auto* object = static_cast<ObjHeader*>((*iterator_).Data());
RuntimeAssert(!object->type_info()->IsArray(), "Must not be an array");
return object;
}
ArrayHeader* mm::ObjectFactory::Iterator::GetArrayHeader() noexcept {
auto* array = static_cast<ArrayHeader*>((*iterator_).Data());
RuntimeAssert(array->type_info()->IsArray(), "Must be an array");
return array;
}
mm::ObjectFactory::ObjectFactory() noexcept = default;
mm::ObjectFactory::~ObjectFactory() = default;
// static
mm::ObjectFactory& mm::ObjectFactory::Instance() noexcept {
return GlobalData::Instance().objectFactory();
}
@@ -25,12 +25,24 @@ namespace internal {
// A queue that is constructed by collecting subqueues from several `Producer`s.
// This is essentially a heterogeneous `MultiSourceQueue` on top of a singly linked list that
// uses `konanAllocMemory` and `konanFreeMemory`
// uses `Allocator` to allocate and free memory.
// TODO: Consider merging with `MultiSourceQueue` somehow.
template <size_t DataAlignment>
template <size_t DataAlignment, typename Allocator>
class ObjectFactoryStorage : private Pinned {
static_assert(IsValidAlignment(DataAlignment), "DataAlignment is not a valid alignment");
template <typename T>
class Deleter {
public:
void operator()(T* instance) noexcept {
instance->~T();
Allocator::Free(instance);
}
};
template <typename T>
using unique_ptr = std::unique_ptr<T, Deleter<T>>;
public:
// This class does not know its size at compile-time. Does not inherit from `KonanAllocatorAware` because
// in `KonanAllocatorAware::operator new(size_t size, KonanAllocTag)` `size` would be incorrect.
@@ -40,6 +52,13 @@ public:
public:
~Node() = default;
static Node& FromData(void* data) noexcept {
constexpr size_t kDataOffset = DataOffset();
Node* node = reinterpret_cast<Node*>(reinterpret_cast<uintptr_t>(data) - kDataOffset);
RuntimeAssert(node->Data() == data, "Node layout has broken");
return *node;
}
// Note: This can only be trivially destructible data, as nobody can invoke its destructor.
void* Data() noexcept {
constexpr size_t kDataOffset = DataOffset();
@@ -59,37 +78,36 @@ public:
Node() noexcept = default;
static KStdUniquePtr<Node> Create(size_t dataSize) noexcept {
static unique_ptr<Node> Create(Allocator& allocator, size_t dataSize) noexcept {
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
size_t totalAlignment = std::max(alignof(Node), DataAlignment);
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, totalAlignment);
RuntimeAssert(
DataOffset() + dataSize <= totalSize, "totalSize %zu is not enough to fit data %zu at offset %zu", totalSize, dataSize,
DataOffset());
void* ptr = konanAllocAlignedMemory(totalSize, totalAlignment);
void* ptr = allocator.Alloc(totalSize, totalAlignment);
if (!ptr) {
// TODO: Try doing GC first.
konan::consoleErrorf("Out of memory trying to allocate %zu. Aborting.\n", totalSize);
konan::consoleErrorf("Out of memory trying to allocate %zu bytes. Aborting.\n", totalSize);
konan::abort();
}
RuntimeAssert(IsAligned(ptr, totalAlignment), "Allocator returned unaligned to %zu pointer %p", totalAlignment, ptr);
return KStdUniquePtr<Node>(new (ptr) Node());
return unique_ptr<Node>(new (ptr) Node());
}
KStdUniquePtr<Node> next_;
unique_ptr<Node> next_;
// There's some more data of an unknown (at compile-time) size here, but it cannot be represented
// with C++ members.
};
class Producer : private MoveOnly {
public:
explicit Producer(ObjectFactoryStorage& owner) noexcept : owner_(owner) {}
Producer(ObjectFactoryStorage& owner, Allocator allocator) noexcept : owner_(owner), allocator_(std::move(allocator)) {}
~Producer() { Publish(); }
Node& Insert(size_t dataSize) noexcept {
AssertCorrect();
auto node = Node::Create(dataSize);
auto node = Node::Create(allocator_, dataSize);
auto* nodePtr = node.get();
if (!root_) {
root_ = std::move(node);
@@ -159,7 +177,8 @@ public:
}
ObjectFactoryStorage& owner_; // weak
KStdUniquePtr<Node> root_;
Allocator allocator_;
unique_ptr<Node> root_;
Node* last_ = nullptr;
};
@@ -245,35 +264,160 @@ private:
}
}
KStdUniquePtr<Node> root_;
unique_ptr<Node> root_;
Node* last_ = nullptr;
SpinLock mutex_;
};
class SimpleAllocator {
public:
void* Alloc(size_t size, size_t alignment) noexcept { return konanAllocAlignedMemory(size, alignment); }
static void Free(void* instance) noexcept { konanFreeMemory(instance); }
};
template <typename BaseAllocator, typename GC>
class AllocatorWithGC {
public:
AllocatorWithGC(BaseAllocator base, GC& gc) noexcept : base_(std::move(base)), gc_(gc) {}
void* Alloc(size_t size, size_t alignment) noexcept {
gc_.SafePointAllocation(size);
if (void* ptr = base_.Alloc(size, alignment)) {
return ptr;
}
// Tell GC that we failed to allocate, and try one more time.
gc_.OnOOM(size);
return base_.Alloc(size, alignment);
}
static void Free(void* instance) noexcept { BaseAllocator::Free(instance); }
private:
BaseAllocator base_;
GC& gc_;
};
} // namespace internal
template <typename GC>
class ObjectFactory : private Pinned {
using GCObjectData = typename GC::ObjectData;
using GCThreadData = typename GC::ThreadData;
using Allocator = internal::AllocatorWithGC<internal::SimpleAllocator, GCThreadData>;
struct HeapObjHeader {
GCObjectData gcData;
alignas(kObjectAlignment) ObjHeader object;
};
// Needs to be kept compatible with `HeapObjHeader` just like `ArrayHeader` is compatible
// with `ObjHeader`: the former can always be casted to the other.
struct HeapArrayHeader {
GCObjectData gcData;
alignas(kObjectAlignment) ArrayHeader array;
};
public:
using Storage = internal::ObjectFactoryStorage<kObjectAlignment>;
using Storage = internal::ObjectFactoryStorage<kObjectAlignment, Allocator>;
class NodeRef {
public:
explicit NodeRef(typename Storage::Node& node) noexcept : node_(node) {}
static NodeRef From(ObjHeader* object) noexcept {
RuntimeAssert(object->heap(), "Must be a heap object");
auto* heapObject = reinterpret_cast<HeapObjHeader*>(reinterpret_cast<uintptr_t>(object) - offsetof(HeapObjHeader, object));
RuntimeAssert(&heapObject->object == object, "HeapObjHeader layout has broken");
return NodeRef(Storage::Node::FromData(heapObject));
}
static NodeRef From(ArrayHeader* array) noexcept {
// `ArrayHeader` and `ObjHeader` are kept compatible, so the former can
// be always casted to the other.
RuntimeAssert(reinterpret_cast<ObjHeader*>(array)->heap(), "Must be a heap object");
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));
}
NodeRef* operator->() noexcept { return this; }
GCObjectData& GCObjectData() noexcept {
// `HeapArrayHeader` and `HeapObjHeader` are kept compatible, so the former can
// be always casted to the other.
return static_cast<HeapObjHeader*>(node_.Data())->gcData;
}
bool IsArray() const noexcept {
// `HeapArrayHeader` and `HeapObjHeader` are kept compatible, so the former can
// be always casted to the other.
auto* object = &static_cast<HeapObjHeader*>(node_.Data())->object;
return object->type_info()->IsArray();
}
ObjHeader* GetObjHeader() noexcept {
auto* object = &static_cast<HeapObjHeader*>(node_.Data())->object;
RuntimeAssert(!object->type_info()->IsArray(), "Must not be an array");
return object;
}
ArrayHeader* GetArrayHeader() noexcept {
auto* array = &static_cast<HeapArrayHeader*>(node_.Data())->array;
RuntimeAssert(array->type_info()->IsArray(), "Must be an array");
return array;
}
bool operator==(const NodeRef& rhs) const noexcept { return &node_ == &rhs.node_; }
bool operator!=(const NodeRef& rhs) const noexcept { return !(*this == rhs); }
private:
typename Storage::Node& node_;
};
class ThreadQueue : private MoveOnly {
public:
explicit ThreadQueue(ObjectFactory& owner) noexcept : producer_(owner.storage_) {}
ThreadQueue(ObjectFactory& owner, GCThreadData& gc) noexcept :
producer_(owner.storage_, internal::AllocatorWithGC(internal::SimpleAllocator(), gc)) {}
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept;
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept {
RuntimeAssert(!typeInfo->IsArray(), "Must not be an array");
size_t membersSize = typeInfo->instanceSize_ - sizeof(ObjHeader);
size_t allocSize = AlignUp(sizeof(HeapObjHeader) + membersSize, kObjectAlignment);
auto& node = producer_.Insert(allocSize);
auto* heapObject = new (node.Data()) HeapObjHeader();
auto* object = &heapObject->object;
object->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
return object;
}
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept {
RuntimeAssert(typeInfo->IsArray(), "Must be an array");
uint32_t membersSize = static_cast<uint32_t>(-typeInfo->instanceSize_) * count;
// Note: array body is aligned, but for size computation it is enough to align the sum.
size_t allocSize = AlignUp(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
auto& node = producer_.Insert(allocSize);
auto* heapArray = new (node.Data()) HeapArrayHeader();
auto* array = &heapArray->array;
array->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
array->count_ = count;
return array;
}
void Publish() noexcept { producer_.Publish(); }
void ClearForTests() noexcept { producer_.ClearForTests(); }
private:
Storage::Producer producer_;
typename Storage::Producer producer_;
};
class Iterator {
public:
Storage::Node& operator*() noexcept { return *iterator_; }
NodeRef operator*() noexcept { return NodeRef(*iterator_); }
NodeRef operator->() noexcept { return NodeRef(*iterator_); }
Iterator& operator++() noexcept {
++iterator_;
@@ -284,17 +428,12 @@ public:
bool operator!=(const Iterator& rhs) const noexcept { return iterator_ != rhs.iterator_; }
bool IsArray() noexcept;
ObjHeader* GetObjHeader() noexcept;
ArrayHeader* GetArrayHeader() noexcept;
private:
friend class ObjectFactory;
explicit Iterator(Storage::Iterator iterator) noexcept : iterator_(std::move(iterator)) {}
explicit Iterator(typename Storage::Iterator iterator) noexcept : iterator_(std::move(iterator)) {}
Storage::Iterator iterator_;
typename Storage::Iterator iterator_;
};
class Iterable {
@@ -307,13 +446,11 @@ public:
void EraseAndAdvance(Iterator& iterator) noexcept { iter_.EraseAndAdvance(iterator.iterator_); }
private:
Storage::Iterable iter_;
typename Storage::Iterable iter_;
};
ObjectFactory() noexcept;
~ObjectFactory();
static ObjectFactory& Instance() noexcept;
ObjectFactory() noexcept = default;
~ObjectFactory() = default;
Iterable Iter() noexcept { return Iterable(*this); }
@@ -12,17 +12,25 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "GC.hpp"
#include "TestSupport.hpp"
#include "Types.h"
using namespace kotlin;
using testing::_;
namespace {
using SimpleAllocator = mm::internal::SimpleAllocator;
template <size_t DataAlignment>
using ObjectFactoryStorage = mm::internal::ObjectFactoryStorage<DataAlignment>;
using ObjectFactoryStorage = mm::internal::ObjectFactoryStorage<DataAlignment, SimpleAllocator>;
using ObjectFactoryStorageRegular = ObjectFactoryStorage<alignof(void*)>;
namespace {
template <typename Storage>
using Producer = typename Storage::Producer;
template <size_t DataAlignment>
KStdVector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
@@ -76,7 +84,7 @@ TEST(ObjectFactoryStorageTest, Empty) {
TEST(ObjectFactoryStorageTest, DoNotPublish) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<int>(2);
@@ -88,8 +96,8 @@ TEST(ObjectFactoryStorageTest, DoNotPublish) {
TEST(ObjectFactoryStorageTest, Publish) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer1(storage);
ObjectFactoryStorageRegular::Producer producer2(storage);
Producer<ObjectFactoryStorageRegular> producer1(storage, SimpleAllocator());
Producer<ObjectFactoryStorageRegular> producer2(storage, SimpleAllocator());
producer1.Insert<int>(1);
producer1.Insert<int>(2);
@@ -106,7 +114,7 @@ TEST(ObjectFactoryStorageTest, Publish) {
TEST(ObjectFactoryStorageTest, PublishDifferentTypes) {
ObjectFactoryStorage<alignof(MaxAlignedData)> storage;
ObjectFactoryStorage<alignof(MaxAlignedData)>::Producer producer(storage);
Producer<ObjectFactoryStorage<alignof(MaxAlignedData)>> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<size_t>(2);
@@ -139,7 +147,7 @@ TEST(ObjectFactoryStorageTest, PublishDifferentTypes) {
TEST(ObjectFactoryStorageTest, PublishSeveralTimes) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
// Add 2 elements and publish.
producer.Insert<int>(1);
@@ -167,7 +175,7 @@ TEST(ObjectFactoryStorageTest, PublishInDestructor) {
ObjectFactoryStorageRegular storage;
{
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<int>(2);
}
@@ -177,9 +185,22 @@ TEST(ObjectFactoryStorageTest, PublishInDestructor) {
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
}
TEST(ObjectFactoryStorageTest, FindNode) {
ObjectFactoryStorageRegular storage;
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
auto& node1 = producer.Insert<int>(1);
auto& node2 = producer.Insert<int>(2);
producer.Publish();
EXPECT_THAT(&ObjectFactoryStorageRegular::Node::FromData(node1.Data()), &node1);
EXPECT_THAT(&ObjectFactoryStorageRegular::Node::FromData(node2.Data()), &node2);
}
TEST(ObjectFactoryStorageTest, EraseFirst) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<int>(2);
@@ -205,7 +226,7 @@ TEST(ObjectFactoryStorageTest, EraseFirst) {
TEST(ObjectFactoryStorageTest, EraseMiddle) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<int>(2);
@@ -231,7 +252,7 @@ TEST(ObjectFactoryStorageTest, EraseMiddle) {
TEST(ObjectFactoryStorageTest, EraseLast) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<int>(2);
@@ -257,7 +278,7 @@ TEST(ObjectFactoryStorageTest, EraseLast) {
TEST(ObjectFactoryStorageTest, EraseAll) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
producer.Insert<int>(2);
@@ -279,7 +300,7 @@ TEST(ObjectFactoryStorageTest, EraseAll) {
TEST(ObjectFactoryStorageTest, EraseTheOnlyElement) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(1);
@@ -307,7 +328,7 @@ TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
for (int i = 0; i < kThreadCount; ++i) {
expected.push_back(i);
threads.emplace_back([i, &storage, &canStart, &readyCount]() {
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(i);
++readyCount;
while (!canStart) {
@@ -335,7 +356,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
KStdVector<int> expectedBefore;
KStdVector<int> expectedAfter;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
for (int i = 0; i < kStartCount; ++i) {
expectedBefore.push_back(i);
expectedAfter.push_back(i);
@@ -351,7 +372,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
int j = i + kStartCount;
expectedAfter.push_back(j);
threads.emplace_back([j, &storage, &canStart, &startedCount, &readyCount]() {
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(j);
++readyCount;
while (!canStart) {
@@ -393,7 +414,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
constexpr int kThreadCount = kDefaultThreadCount;
KStdVector<int> expectedAfter;
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
for (int i = 0; i < kStartCount; ++i) {
if (i % 2 == 0) {
expectedAfter.push_back(i);
@@ -410,7 +431,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
int j = i + kStartCount;
expectedAfter.push_back(j);
threads.emplace_back([j, &storage, &canStart, &startedCount, &readyCount]() {
ObjectFactoryStorageRegular::Producer producer(storage);
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
producer.Insert<int>(j);
++readyCount;
while (!canStart) {
@@ -446,10 +467,103 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expectedAfter));
}
using mm::ObjectFactory;
using mm::internal::AllocatorWithGC;
namespace {
class MockAllocator {
public:
MOCK_METHOD(void*, Alloc, (size_t, size_t));
};
class MockAllocatorWrapper {
public:
MockAllocator& operator*() { return *mock_; }
void* Alloc(size_t size, size_t alignment) { return mock_->Alloc(size, alignment); }
private:
KStdUniquePtr<testing::StrictMock<MockAllocator>> mock_ = make_unique<testing::StrictMock<MockAllocator>>();
};
class MockGC {
public:
MOCK_METHOD(void, SafePointAllocation, (size_t));
MOCK_METHOD(void, OnOOM, (size_t));
};
} // namespace
TEST(AllocatorWithGCTest, AllocateWithoutOOM) {
constexpr size_t size = 256;
constexpr size_t alignment = 8;
void* nonNull = reinterpret_cast<void*>(1);
MockAllocatorWrapper baseAllocator;
testing::StrictMock<MockGC> gc;
{
testing::InSequence seq;
EXPECT_CALL(gc, SafePointAllocation(size));
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nonNull));
EXPECT_CALL(gc, OnOOM(_)).Times(0);
}
AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
void* ptr = allocator.Alloc(size, alignment);
EXPECT_THAT(ptr, nonNull);
}
TEST(AllocatorWithGCTest, AllocateWithFixableOOM) {
constexpr size_t size = 256;
constexpr size_t alignment = 8;
void* nonNull = reinterpret_cast<void*>(1);
MockAllocatorWrapper baseAllocator;
testing::StrictMock<MockGC> gc;
{
testing::InSequence seq;
EXPECT_CALL(gc, SafePointAllocation(size));
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
EXPECT_CALL(gc, OnOOM(size));
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nonNull));
}
AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
void* ptr = allocator.Alloc(size, alignment);
EXPECT_THAT(ptr, nonNull);
}
TEST(AllocatorWithGCTest, AllocateWithUnfixableOOM) {
constexpr size_t size = 256;
constexpr size_t alignment = 8;
MockAllocatorWrapper baseAllocator;
testing::StrictMock<MockGC> gc;
{
testing::InSequence seq;
EXPECT_CALL(gc, SafePointAllocation(size));
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
EXPECT_CALL(gc, OnOOM(size));
EXPECT_CALL(*baseAllocator, Alloc(size, alignment)).WillOnce(testing::Return(nullptr));
}
AllocatorWithGC<MockAllocatorWrapper, MockGC> allocator(std::move(baseAllocator), gc);
void* ptr = allocator.Alloc(size, alignment);
EXPECT_THAT(ptr, nullptr);
}
namespace {
class GC {
public:
struct ObjectData {
uint32_t flags = 42;
};
class ThreadData {
public:
void SafePointAllocation(size_t size) noexcept {}
void OnOOM(size_t size) noexcept {}
};
};
using ObjectFactory = mm::ObjectFactory<GC>;
KStdUniquePtr<TypeInfo> MakeObjectTypeInfo(int32_t size) {
auto typeInfo = make_unique<TypeInfo>();
typeInfo->typeInfo_ = typeInfo.get();
@@ -468,32 +582,42 @@ KStdUniquePtr<TypeInfo> MakeArrayTypeInfo(int32_t elementSize) {
TEST(ObjectFactoryTest, CreateObject) {
auto typeInfo = MakeObjectTypeInfo(24);
GC::ThreadData gc;
ObjectFactory objectFactory;
ObjectFactory::ThreadQueue threadQueue(objectFactory);
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
auto* object = threadQueue.CreateObject(typeInfo.get());
threadQueue.Publish();
auto node = ObjectFactory::NodeRef::From(object);
EXPECT_FALSE(node.IsArray());
EXPECT_THAT(node.GetObjHeader(), object);
EXPECT_THAT(node.GCObjectData().flags, 42);
auto iter = objectFactory.Iter();
auto it = iter.begin();
EXPECT_FALSE(it.IsArray());
EXPECT_THAT(it.GetObjHeader(), object);
EXPECT_THAT(*it, node);
++it;
EXPECT_THAT(it, iter.end());
}
TEST(ObjectFactoryTest, CreateArray) {
auto typeInfo = MakeArrayTypeInfo(24);
GC::ThreadData gc;
ObjectFactory objectFactory;
ObjectFactory::ThreadQueue threadQueue(objectFactory);
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
auto* array = threadQueue.CreateArray(typeInfo.get(), 3);
threadQueue.Publish();
auto node = ObjectFactory::NodeRef::From(array);
EXPECT_TRUE(node.IsArray());
EXPECT_THAT(node.GetArrayHeader(), array);
EXPECT_THAT(node.GCObjectData().flags, 42);
auto iter = objectFactory.Iter();
auto it = iter.begin();
EXPECT_TRUE(it.IsArray());
EXPECT_THAT(it.GetArrayHeader(), array);
EXPECT_THAT(*it, node);
++it;
EXPECT_THAT(it, iter.end());
}
@@ -501,8 +625,9 @@ TEST(ObjectFactoryTest, CreateArray) {
TEST(ObjectFactoryTest, Erase) {
auto objectTypeInfo = MakeObjectTypeInfo(24);
auto arrayTypeInfo = MakeArrayTypeInfo(24);
GC::ThreadData gc;
ObjectFactory objectFactory;
ObjectFactory::ThreadQueue threadQueue(objectFactory);
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
for (int i = 0; i < 10; ++i) {
threadQueue.CreateObject(objectTypeInfo.get());
@@ -514,7 +639,7 @@ TEST(ObjectFactoryTest, Erase) {
{
auto iter = objectFactory.Iter();
for (auto it = iter.begin(); it != iter.end();) {
if (it.IsArray()) {
if (it->IsArray()) {
iter.EraseAndAdvance(it);
} else {
++it;
@@ -526,7 +651,7 @@ TEST(ObjectFactoryTest, Erase) {
auto iter = objectFactory.Iter();
int count = 0;
for (auto it = iter.begin(); it != iter.end(); ++it, ++count) {
EXPECT_FALSE(it.IsArray());
EXPECT_FALSE(it->IsArray());
}
EXPECT_THAT(count, 10);
}
@@ -544,7 +669,8 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
for (int i = 0; i < kThreadCount; ++i) {
threads.emplace_back([&typeInfo, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
ObjectFactory::ThreadQueue threadQueue(objectFactory);
GC::ThreadData gc;
ObjectFactory::ThreadQueue threadQueue(objectFactory, gc);
auto* object = threadQueue.CreateObject(typeInfo.get());
{
std::lock_guard<std::mutex> guard(expectedMutex);
@@ -567,7 +693,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
auto iter = objectFactory.Iter();
KStdVector<ObjHeader*> actual;
for (auto it = iter.begin(); it != iter.end(); ++it) {
actual.push_back(it.GetObjHeader());
actual.push_back(it->GetObjHeader());
}
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
@@ -26,10 +26,6 @@ void EnsureNeverFrozen(ObjHeader* obj) {
TODO();
}
void Kotlin_native_internal_GC_collect(ObjHeader*) {
TODO();
}
void Kotlin_native_internal_GC_suspend(ObjHeader*) {
TODO();
}
@@ -78,10 +74,6 @@ bool Kotlin_native_internal_GC_getTuneThreshold(ObjHeader*) {
TODO();
}
RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) {
TODO();
}
bool TryAddHeapRef(const ObjHeader* object) {
TODO();
}
@@ -98,16 +90,4 @@ ForeignRefContext InitLocalForeignRef(ObjHeader* object) {
TODO();
}
RUNTIME_NOTHROW void Kotlin_mm_safePointFunctionEpilogue() {
// TODO: Unimplemented
}
RUNTIME_NOTHROW void Kotlin_mm_safePointWhileLoopBody() {
// TODO: Unimplemented
}
RUNTIME_NOTHROW void Kotlin_mm_safePointExceptionUnwind() {
// TODO: Unimplemented
}
} // extern "C"
@@ -9,7 +9,9 @@
#include <atomic>
#include <pthread.h>
#include "GlobalData.hpp"
#include "GlobalsRegistry.hpp"
#include "GC.hpp"
#include "ObjectFactory.hpp"
#include "ShadowStack.hpp"
#include "StableRefRegistry.hpp"
@@ -32,7 +34,8 @@ public:
globalsThreadQueue_(GlobalsRegistry::Instance()),
stableRefThreadQueue_(StableRefRegistry::Instance()),
state_(ThreadState::kRunnable),
objectFactoryThreadQueue_(ObjectFactory::Instance()) {}
gc_(GlobalData::Instance().gc()),
objectFactoryThreadQueue_(GlobalData::Instance().objectFactory(), gc_) {}
~ThreadData() = default;
@@ -48,20 +51,23 @@ public:
ThreadState setState(ThreadState state) noexcept { return state_.exchange(state); }
ObjectFactory::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
ObjectFactory<GC>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
ShadowStack& shadowStack() noexcept { return shadowStack_; }
KStdVector<std::pair<ObjHeader**, ObjHeader*>>& initializingSingletons() noexcept { return initializingSingletons_; }
GC::ThreadData& gc() noexcept { return gc_; }
private:
const pthread_t threadId_;
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
ThreadLocalStorage tls_;
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
std::atomic<ThreadState> state_;
ObjectFactory::ThreadQueue objectFactoryThreadQueue_;
ShadowStack shadowStack_;
GC::ThreadData gc_;
ObjectFactory<GC>::ThreadQueue objectFactoryThreadQueue_;
KStdVector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
};
@@ -0,0 +1,50 @@
/*
* 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_NOOP_GC_H
#define RUNTIME_MM_NOOP_GC_H
#include <cstddef>
#include "Utils.hpp"
namespace kotlin {
namespace mm {
// No-op GC is a GC that does not free memory.
// TODO: It can be made more efficient.
class NoOpGC : private Pinned {
public:
class ObjectData {};
class ThreadData : private Pinned {
public:
using ObjectData = NoOpGC::ObjectData;
explicit ThreadData(NoOpGC& gc) noexcept {}
~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:
};
NoOpGC() noexcept = default;
~NoOpGC() = default;
private:
};
} // namespace mm
} // namespace kotlin
#endif // RUNTIME_MM_NOOP_GC_H