Sketch mm::ObjectFactory (#4583)

This commit is contained in:
Alexander Shabalin
2020-12-21 19:12:21 +03:00
committed by Nikolay Krasko
parent d239ff7f5a
commit 72fc5f5aee
11 changed files with 1181 additions and 12 deletions
@@ -0,0 +1,39 @@
/*
* 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.
*/
#ifndef RUNTIME_ALIGNMENT_H
#define RUNTIME_ALIGNMENT_H
#include <cstddef>
#include <cstdint>
namespace kotlin {
constexpr size_t kObjectAlignment = 8;
constexpr inline size_t AlignUp(size_t size, size_t alignment) {
return (size + alignment - 1) & ~(alignment - 1);
}
inline void* AlignUp(void* ptr, size_t alignment) {
static_assert(sizeof(void*) == sizeof(size_t), "size_t size must be equal to pointer size for this to work");
return reinterpret_cast<void*>(AlignUp(reinterpret_cast<size_t>(ptr), alignment));
}
constexpr inline bool IsValidAlignment(size_t alignment) {
return alignment != 0 && (alignment & (alignment - 1)) == 0;
}
constexpr inline bool IsAligned(size_t size, size_t alignment) {
return size % alignment == 0;
}
inline bool IsAligned(void* ptr, size_t alignment) {
return reinterpret_cast<uintptr_t>(ptr) % alignment == 0;
}
} // namespace kotlin
#endif // RUNTIME_ALIGNMENT_H
@@ -0,0 +1,157 @@
/*
* 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 "Alignment.hpp"
#include <cstddef>
#include <tuple>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "Types.h"
using namespace kotlin;
namespace {
template <typename... Args>
class NamedTestWithParam : public testing::TestWithParam<std::tuple<const char*, Args...>> {
public:
using Param = std::tuple<const char*, Args...>;
static std::string Print(const testing::TestParamInfo<Param>& info) { return std::string(std::get<0>(info.param)); }
template <size_t I>
static const typename std::tuple_element<I + 1, Param>::type& Get() {
const auto& param = testing::TestWithParam<Param>::GetParam();
return std::get<I + 1>(param);
}
};
#define INSTANTIATE_NAMED_TEST(testName, ...) INSTANTIATE_TEST_SUITE_P(, testName, testing::Values(__VA_ARGS__), &testName::Print)
} // namespace
using IsValidAlignmentTest = NamedTestWithParam<size_t, bool>;
TEST_P(IsValidAlignmentTest, Test) {
const auto& alignment = Get<0>();
const auto& expected = Get<1>();
EXPECT_THAT(IsValidAlignment(alignment), expected);
}
INSTANTIATE_NAMED_TEST(
IsValidAlignmentTest,
std::make_tuple("0", 0, false),
std::make_tuple("1", 1, true),
std::make_tuple("2", 2, true),
std::make_tuple("3", 3, false),
std::make_tuple("4", 4, true),
std::make_tuple("5", 5, false),
std::make_tuple("6", 6, false),
std::make_tuple("7", 7, false),
std::make_tuple("8", 8, true),
std::make_tuple("9", 9, false),
std::make_tuple("10", 10, false),
std::make_tuple("11", 11, false),
std::make_tuple("12", 12, false),
std::make_tuple("13", 13, false),
std::make_tuple("14", 14, false),
std::make_tuple("15", 15, false),
std::make_tuple("16", 16, true),
std::make_tuple("int", alignof(int), true),
std::make_tuple("ptr", alignof(void*), true),
std::make_tuple("max", alignof(std::max_align_t), true));
using IsAlignedSizeTest = NamedTestWithParam<size_t, size_t, bool>;
TEST_P(IsAlignedSizeTest, Test) {
const auto& size = Get<0>();
const auto& alignment = Get<1>();
const auto& expected = Get<2>();
EXPECT_THAT(IsAligned(size, alignment), expected);
}
INSTANTIATE_NAMED_TEST(
IsAlignedSizeTest,
std::make_tuple("1_1", 1, 1, true),
std::make_tuple("2_1", 2, 1, true),
std::make_tuple("3_1", 3, 1, true),
std::make_tuple("4_1", 4, 1, true),
std::make_tuple("1_2", 1, 2, false),
std::make_tuple("2_2", 2, 2, true),
std::make_tuple("3_2", 3, 2, false),
std::make_tuple("4_2", 4, 2, true));
using IsAlignedPointerTest = NamedTestWithParam<uintptr_t, size_t, bool>;
TEST_P(IsAlignedPointerTest, Test) {
const auto& ptr = Get<0>();
const auto& alignment = Get<1>();
const auto& expected = Get<2>();
EXPECT_THAT(IsAligned(reinterpret_cast<void*>(ptr), alignment), expected);
}
INSTANTIATE_NAMED_TEST(
IsAlignedPointerTest,
std::make_tuple("0_1", 0, 1, true),
std::make_tuple("1_1", 1, 1, true),
std::make_tuple("2_1", 2, 1, true),
std::make_tuple("3_1", 3, 1, true),
std::make_tuple("4_1", 4, 1, true),
std::make_tuple("0_2", 0, 2, true),
std::make_tuple("1_2", 1, 2, false),
std::make_tuple("2_2", 2, 2, true),
std::make_tuple("3_2", 3, 2, false),
std::make_tuple("4_2", 4, 2, true));
using AlignUpSizeTest = NamedTestWithParam<size_t, size_t, size_t>;
TEST_P(AlignUpSizeTest, Test) {
const auto& size = Get<0>();
const auto& alignment = Get<1>();
const auto& expected = Get<2>();
EXPECT_THAT(AlignUp(size, alignment), expected);
}
INSTANTIATE_NAMED_TEST(
AlignUpSizeTest,
std::make_tuple("1_1", 1, 1, 1),
std::make_tuple("2_1", 2, 1, 2),
std::make_tuple("3_1", 3, 1, 3),
std::make_tuple("4_1", 4, 1, 4),
std::make_tuple("1_2", 1, 2, 2),
std::make_tuple("2_2", 2, 2, 2),
std::make_tuple("3_2", 3, 2, 4),
std::make_tuple("4_2", 4, 2, 4));
using AlignUpPointerTest = NamedTestWithParam<uintptr_t, size_t, uintptr_t>;
TEST_P(AlignUpPointerTest, Test) {
const auto& ptr = Get<0>();
const auto& alignment = Get<1>();
const auto& expected = Get<2>();
EXPECT_THAT(AlignUp(reinterpret_cast<void*>(ptr), alignment), reinterpret_cast<void*>(expected));
}
INSTANTIATE_NAMED_TEST(
AlignUpPointerTest,
std::make_tuple("0_1", 0, 1, 0),
std::make_tuple("1_1", 1, 1, 1),
std::make_tuple("2_1", 2, 1, 2),
std::make_tuple("3_1", 3, 1, 3),
std::make_tuple("4_1", 4, 1, 4),
std::make_tuple("0_2", 0, 2, 0),
std::make_tuple("1_2", 1, 2, 2),
std::make_tuple("2_2", 2, 2, 2),
std::make_tuple("3_2", 3, 2, 4),
std::make_tuple("4_2", 4, 2, 4));
TEST(AlignmentTest, ObjectAlignment) {
static_assert(IsValidAlignment(kObjectAlignment), "kObjectAlignment must be a valid alignment");
static_assert(kObjectAlignment % alignof(KLong) == 0, "");
static_assert(kObjectAlignment % alignof(KDouble) == 0, "");
}
@@ -29,6 +29,10 @@ inline void* konanAllocMemory(size_t size) {
return konan::calloc(1, size);
}
inline void* konanAllocAlignedMemory(size_t size, size_t alignment) {
return konan::calloc_aligned(1, size, alignment);
}
inline void konanFreeMemory(void* memory) {
konan::free(memory);
}
@@ -157,6 +157,8 @@ struct TypeInfo {
inline VTableElement* vtable() {
return reinterpret_cast<VTableElement*>(this + 1);
}
inline bool IsArray() const { return instanceSize_ < 0; }
#endif
};
@@ -6,6 +6,7 @@
#ifndef RUNTIME_MM_GLOBAL_DATA_H
#define RUNTIME_MM_GLOBAL_DATA_H
#include "ObjectFactory.hpp"
#include "GlobalsRegistry.hpp"
#include "StableRefRegistry.hpp"
#include "ThreadRegistry.hpp"
@@ -19,9 +20,10 @@ class GlobalData : private Pinned {
public:
static GlobalData& Instance() noexcept { return instance_; }
ThreadRegistry& threadRegistry() { return threadRegistry_; }
GlobalsRegistry& globalsRegistry() { return globalsRegistry_; }
StableRefRegistry& stableRefRegistry() { return stableRefRegistry_; }
ThreadRegistry& threadRegistry() noexcept { return threadRegistry_; }
GlobalsRegistry& globalsRegistry() noexcept { return globalsRegistry_; }
StableRefRegistry& stableRefRegistry() noexcept { return stableRefRegistry_; }
ObjectFactory& objectFactory() noexcept { return objectFactory_; }
private:
GlobalData();
@@ -32,6 +34,7 @@ private:
ThreadRegistry threadRegistry_;
GlobalsRegistry globalsRegistry_;
StableRefRegistry stableRefRegistry_;
ObjectFactory objectFactory_;
};
} // namespace mm
@@ -5,6 +5,7 @@
#include "Memory.h"
#include "Exceptions.h"
#include "GlobalsRegistry.hpp"
#include "KAssert.h"
#include "Porting.h"
@@ -74,6 +75,22 @@ extern "C" void RestoreMemory(MemoryState*) {
// TODO: Remove when legacy MM is gone.
}
extern "C" RUNTIME_NOTHROW OBJ_GETTER(AllocInstance, const TypeInfo* typeInfo) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
auto* object = threadData->objectFactoryThreadQueue().CreateObject(typeInfo);
RETURN_OBJ(object);
}
extern "C" OBJ_GETTER(AllocArrayInstance, const TypeInfo* typeInfo, int32_t elements) {
if (elements < 0) {
ThrowIllegalArgumentException();
}
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
auto* array = threadData->objectFactoryThreadQueue().CreateArray(typeInfo, static_cast<uint32_t>(elements));
// `ArrayHeader` and `ObjHeader` are expected to be compatible.
RETURN_OBJ(reinterpret_cast<ObjHeader*>(array));
}
extern "C" OBJ_GETTER(InitSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
// TODO: This should only be called if singleton is actually created here. It's possible that the
@@ -0,0 +1,61 @@
/*
* 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();
}
@@ -0,0 +1,316 @@
/*
* 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.
*/
#ifndef RUNTIME_MM_OBJECT_FACTORY_H
#define RUNTIME_MM_OBJECT_FACTORY_H
#include <algorithm>
#include <memory>
#include <mutex>
#include "Alignment.hpp"
#include "Alloc.h"
#include "CppSupport.hpp"
#include "Memory.h"
#include "Mutex.hpp"
#include "Utils.hpp"
namespace kotlin {
namespace mm {
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`
// TODO: Consider merging with `MultiSourceQueue` somehow.
template <size_t DataAlignment>
class ObjectFactoryStorage : private Pinned {
static_assert(IsValidAlignment(DataAlignment), "DataAlignment is not a valid alignment");
public:
// This class does not know its size at compile-time.
class Node : private Pinned {
constexpr static size_t DataOffset() noexcept { return AlignUp(sizeof(Node), DataAlignment); }
public:
~Node() = default;
static void operator delete(void* ptr) noexcept { konanFreeMemory(ptr); }
// Note: This can only be trivially destructible data, as nobody can invoke its destructor.
void* Data() noexcept {
constexpr size_t kDataOffset = DataOffset();
void* ptr = reinterpret_cast<uint8_t*>(this) + kDataOffset;
RuntimeAssert(IsAligned(ptr, DataAlignment), "Data=%p is not aligned to %zu", ptr, DataAlignment);
return ptr;
}
// It's a caller responsibility to know if the underlying data is `T`.
template <typename T>
T& Data() noexcept {
return *static_cast<T*>(Data());
}
private:
friend class ObjectFactoryStorage;
Node() noexcept = default;
static void* operator new(size_t size, 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);
if (!ptr) {
// TODO: Try doing GC first.
konan::consoleErrorf("Out of memory trying to allocate %zu. Aborting.\n", totalSize);
konan::abort();
}
RuntimeAssert(IsAligned(ptr, totalAlignment), "Allocator returned unaligned to %zu pointer %p", totalAlignment, ptr);
return ptr;
}
std::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() { Publish(); }
Node& Insert(size_t dataSize) noexcept {
AssertCorrect();
auto* nodePtr = new (dataSize) Node();
std::unique_ptr<Node> node(nodePtr);
if (!root_) {
RuntimeAssert(last_ == nullptr, "Unsynchronized root_ and last_");
root_ = std::move(node);
} else {
RuntimeAssert(last_ != nullptr, "Unsynchronized root_ and last_");
last_->next_ = std::move(node);
}
last_ = nodePtr;
RuntimeAssert(root_ != nullptr, "Must not be empty");
AssertCorrect();
return *nodePtr;
}
template <typename T, typename... Args>
Node& Insert(Args&&... args) noexcept {
static_assert(alignof(T) <= DataAlignment, "Cannot insert type with alignment bigger than DataAlignment");
static_assert(std_support::is_trivially_destructible_v<T>, "Type must be trivially destructible");
auto& node = Insert(sizeof(T));
new (node.Data()) T(std::forward<Args>(args)...);
return node;
}
// Merge `this` queue with owning `ObjectFactoryStorage`.
// `this` will have empty queue after the call.
// This call is performed without heap allocations. TODO: Test that no allocations are happening.
void Publish() noexcept {
AssertCorrect();
if (!root_) {
return;
}
std::lock_guard<SpinLock> guard(owner_.mutex_);
owner_.AssertCorrectUnsafe();
if (!owner_.root_) {
owner_.root_ = std::move(root_);
} else {
owner_.last_->next_ = std::move(root_);
}
owner_.last_ = last_;
last_ = nullptr;
RuntimeAssert(root_ == nullptr, "Must be empty");
AssertCorrect();
RuntimeAssert(owner_.root_ != nullptr, "Must not be empty");
owner_.AssertCorrectUnsafe();
}
private:
friend class ObjectFactoryStorage;
ALWAYS_INLINE void AssertCorrect() const noexcept {
if (root_ == nullptr) {
RuntimeAssert(last_ == nullptr, "last_ must be null");
} else {
RuntimeAssert(last_ != nullptr, "last_ must not be null");
RuntimeAssert(last_->next_ == nullptr, "last_ must not have next");
}
}
ObjectFactoryStorage& owner_; // weak
std::unique_ptr<Node> root_;
Node* last_ = nullptr;
};
class Iterator {
public:
Node& operator*() noexcept { return *node_; }
Node* operator->() noexcept { return node_; }
Iterator& operator++() noexcept {
previousNode_ = node_;
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 ObjectFactoryStorage;
Iterator(Node* previousNode, Node* node) noexcept : previousNode_(previousNode), node_(node) {}
Node* previousNode_; // Kept for `Iterable::EraseAndAdvance`.
Node* node_;
};
class Iterable : private MoveOnly {
public:
explicit Iterable(ObjectFactoryStorage& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {}
Iterator begin() noexcept { return Iterator(nullptr, owner_.root_.get()); }
Iterator end() noexcept { return Iterator(owner_.last_, nullptr); }
void EraseAndAdvance(Iterator& iterator) noexcept { iterator.node_ = owner_.EraseUnsafe(iterator.previousNode_); }
private:
ObjectFactoryStorage& owner_; // weak
std::unique_lock<SpinLock> guard_;
};
// Lock `ObjectFactoryStorage` for safe iteration.
Iterable Iter() noexcept { return Iterable(*this); }
private:
// Expects `mutex_` to be held by the current thread.
Node* EraseUnsafe(Node* previousNode) noexcept {
RuntimeAssert(root_ != nullptr, "Must not be empty");
AssertCorrectUnsafe();
if (previousNode == nullptr) {
// Deleting the root.
root_ = std::move(root_->next_);
if (!root_) {
last_ = nullptr;
}
AssertCorrectUnsafe();
return root_.get();
}
auto node = std::move(previousNode->next_);
previousNode->next_ = std::move(node->next_);
if (!previousNode->next_) {
last_ = previousNode;
}
AssertCorrectUnsafe();
return previousNode->next_.get();
}
// Expects `mutex_` to be held by the current thread.
ALWAYS_INLINE void AssertCorrectUnsafe() const noexcept {
if (root_ == nullptr) {
RuntimeAssert(last_ == nullptr, "last_ must be null");
} else {
RuntimeAssert(last_ != nullptr, "last_ must not be null");
RuntimeAssert(last_->next_ == nullptr, "last_ must not have next");
}
}
std::unique_ptr<Node> root_;
Node* last_ = nullptr;
SpinLock mutex_;
};
} // namespace internal
class ObjectFactory : private Pinned {
public:
using Storage = internal::ObjectFactoryStorage<kObjectAlignment>;
class ThreadQueue : private MoveOnly {
public:
explicit ThreadQueue(ObjectFactory& owner) noexcept : producer_(owner.storage_) {}
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t count) noexcept;
void Publish() noexcept { producer_.Publish(); }
private:
Storage::Producer producer_;
};
class Iterator {
public:
Storage::Node& operator*() noexcept { return *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_; }
bool IsArray() noexcept;
ObjHeader* GetObjHeader() noexcept;
ArrayHeader* GetArrayHeader() noexcept;
private:
friend class ObjectFactory;
explicit Iterator(Storage::Iterator iterator) noexcept : iterator_(std::move(iterator)) {}
Storage::Iterator iterator_;
};
class Iterable {
public:
Iterable(ObjectFactory& owner) noexcept : iter_(owner.storage_.Iter()) {}
Iterator begin() noexcept { return Iterator(iter_.begin()); }
Iterator end() noexcept { return Iterator(iter_.end()); }
void EraseAndAdvance(Iterator& iterator) noexcept { iter_.EraseAndAdvance(iterator.iterator_); }
private:
Storage::Iterable iter_;
};
ObjectFactory() noexcept;
~ObjectFactory();
static ObjectFactory& Instance() noexcept;
Iterable Iter() noexcept { return Iterable(*this); }
private:
Storage storage_;
};
} // namespace mm
} // namespace kotlin
#endif // RUNTIME_MM_OBJECT_FACTORY_H
@@ -0,0 +1,573 @@
/*
* 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 <atomic>
#include <thread>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "CppSupport.hpp"
#include "TestSupport.hpp"
using namespace kotlin;
template <size_t DataAlignment>
using ObjectFactoryStorage = mm::internal::ObjectFactoryStorage<DataAlignment>;
using ObjectFactoryStorageRegular = ObjectFactoryStorage<alignof(void*)>;
namespace {
template <size_t DataAlignment>
std::vector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
std::vector<void*> result;
for (auto& node : storage.Iter()) {
result.push_back(node.Data());
}
return result;
}
template <typename T, size_t DataAlignment>
std::vector<T> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
std::vector<T> result;
for (auto& node : storage.Iter()) {
result.push_back(*static_cast<T*>(node.Data()));
}
return result;
}
struct MoveOnlyImpl : private MoveOnly {
MoveOnlyImpl(int value1, int value2) : value1(value1), value2(value2) {}
int value1;
int value2;
};
struct PinnedImpl : private Pinned {
PinnedImpl(int value1, int value2, int value3) : value1(value1), value2(value2), value3(value3) {}
int value1;
int value2;
int value3;
};
struct MaxAlignedData {
explicit MaxAlignedData(int value) : value(value) {}
std::max_align_t padding;
int value;
};
} // namespace
TEST(ObjectFactoryStorageTest, Empty) {
ObjectFactoryStorageRegular storage;
auto actual = Collect(storage);
EXPECT_THAT(actual, testing::IsEmpty());
}
TEST(ObjectFactoryStorageTest, DoNotPublish) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<int>(2);
auto actual = Collect(storage);
EXPECT_THAT(actual, testing::IsEmpty());
}
TEST(ObjectFactoryStorageTest, Publish) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer1(storage);
ObjectFactoryStorageRegular::Producer producer2(storage);
producer1.Insert<int>(1);
producer1.Insert<int>(2);
producer2.Insert<int>(10);
producer2.Insert<int>(20);
producer1.Publish();
producer2.Publish();
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::ElementsAre(1, 2, 10, 20));
}
TEST(ObjectFactoryStorageTest, PublishDifferentTypes) {
ObjectFactoryStorage<alignof(MaxAlignedData)> storage;
ObjectFactoryStorage<alignof(MaxAlignedData)>::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<size_t>(2);
producer.Insert<MoveOnlyImpl>(3, 4);
producer.Insert<PinnedImpl>(5, 6, 7);
producer.Insert<MaxAlignedData>(8);
producer.Publish();
auto actual = storage.Iter();
auto it = actual.begin();
EXPECT_THAT(it->Data<int>(), 1);
++it;
EXPECT_THAT(it->Data<size_t>(), 2);
++it;
auto& moveOnly = it->Data<MoveOnlyImpl>();
EXPECT_THAT(moveOnly.value1, 3);
EXPECT_THAT(moveOnly.value2, 4);
++it;
auto& pinned = it->Data<PinnedImpl>();
EXPECT_THAT(pinned.value1, 5);
EXPECT_THAT(pinned.value2, 6);
EXPECT_THAT(pinned.value3, 7);
++it;
auto& maxAlign = it->Data<MaxAlignedData>();
EXPECT_THAT(maxAlign.value, 8);
++it;
EXPECT_THAT(it, actual.end());
}
TEST(ObjectFactoryStorageTest, PublishSeveralTimes) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
// Add 2 elements and publish.
producer.Insert<int>(1);
producer.Insert<int>(2);
producer.Publish();
// Add another element and publish.
producer.Insert<int>(3);
producer.Publish();
// Publish without adding elements.
producer.Publish();
// Add yet another two elements and publish.
producer.Insert<int>(4);
producer.Insert<int>(5);
producer.Publish();
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::ElementsAre(1, 2, 3, 4, 5));
}
TEST(ObjectFactoryStorageTest, PublishInDestructor) {
ObjectFactoryStorageRegular storage;
{
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<int>(2);
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
}
TEST(ObjectFactoryStorageTest, EraseFirst) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<int>(2);
producer.Insert<int>(3);
producer.Publish();
{
auto iter = storage.Iter();
for (auto it = iter.begin(); it != iter.end();) {
if (it->Data<int>() == 1) {
iter.EraseAndAdvance(it);
} else {
++it;
}
}
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::ElementsAre(2, 3));
}
TEST(ObjectFactoryStorageTest, EraseMiddle) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<int>(2);
producer.Insert<int>(3);
producer.Publish();
{
auto iter = storage.Iter();
for (auto it = iter.begin(); it != iter.end();) {
if (it->Data<int>() == 2) {
iter.EraseAndAdvance(it);
} else {
++it;
}
}
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::ElementsAre(1, 3));
}
TEST(ObjectFactoryStorageTest, EraseLast) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<int>(2);
producer.Insert<int>(3);
producer.Publish();
{
auto iter = storage.Iter();
for (auto it = iter.begin(); it != iter.end();) {
if (it->Data<int>() == 3) {
iter.EraseAndAdvance(it);
} else {
++it;
}
}
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
}
TEST(ObjectFactoryStorageTest, EraseAll) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Insert<int>(2);
producer.Insert<int>(3);
producer.Publish();
{
auto iter = storage.Iter();
for (auto it = iter.begin(); it != iter.end();) {
iter.EraseAndAdvance(it);
}
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::IsEmpty());
}
TEST(ObjectFactoryStorageTest, EraseTheOnlyElement) {
ObjectFactoryStorageRegular storage;
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(1);
producer.Publish();
{
auto iter = storage.Iter();
auto it = iter.begin();
iter.EraseAndAdvance(it);
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::IsEmpty());
}
TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
ObjectFactoryStorageRegular storage;
constexpr int kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
std::vector<int> expected;
for (int i = 0; i < kThreadCount; ++i) {
expected.push_back(i);
threads.emplace_back([i, &storage, &canStart, &readyCount]() {
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(i);
++readyCount;
while (!canStart) {
}
producer.Publish();
});
}
while (readyCount < kThreadCount) {
}
canStart = true;
for (auto& t : threads) {
t.join();
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
}
TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
ObjectFactoryStorageRegular storage;
constexpr int kStartCount = 50;
constexpr int kThreadCount = kDefaultThreadCount;
std::vector<int> expectedBefore;
std::vector<int> expectedAfter;
ObjectFactoryStorageRegular::Producer producer(storage);
for (int i = 0; i < kStartCount; ++i) {
expectedBefore.push_back(i);
expectedAfter.push_back(i);
producer.Insert<int>(i);
}
producer.Publish();
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
int j = i + kStartCount;
expectedAfter.push_back(j);
threads.emplace_back([j, &storage, &canStart, &startedCount, &readyCount]() {
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(j);
++readyCount;
while (!canStart) {
}
++startedCount;
producer.Publish();
});
}
std::vector<int> actualBefore;
{
auto iter = storage.Iter();
while (readyCount < kThreadCount) {
}
canStart = true;
while (startedCount < kThreadCount) {
}
for (auto& node : iter) {
int element = *static_cast<int*>(node.Data());
actualBefore.push_back(element);
}
}
for (auto& t : threads) {
t.join();
}
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
auto actualAfter = Collect<int>(storage);
EXPECT_THAT(actualAfter, testing::UnorderedElementsAreArray(expectedAfter));
}
TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
ObjectFactoryStorageRegular storage;
constexpr int kStartCount = 50;
constexpr int kThreadCount = kDefaultThreadCount;
std::vector<int> expectedAfter;
ObjectFactoryStorageRegular::Producer producer(storage);
for (int i = 0; i < kStartCount; ++i) {
if (i % 2 == 0) {
expectedAfter.push_back(i);
}
producer.Insert<int>(i);
}
producer.Publish();
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
int j = i + kStartCount;
expectedAfter.push_back(j);
threads.emplace_back([j, &storage, &canStart, &startedCount, &readyCount]() {
ObjectFactoryStorageRegular::Producer producer(storage);
producer.Insert<int>(j);
++readyCount;
while (!canStart) {
}
++startedCount;
producer.Publish();
});
}
{
auto iter = storage.Iter();
while (readyCount < kThreadCount) {
}
canStart = true;
while (startedCount < kThreadCount) {
}
for (auto it = iter.begin(); it != iter.end();) {
if (it->Data<int>() % 2 != 0) {
iter.EraseAndAdvance(it);
} else {
++it;
}
}
}
for (auto& t : threads) {
t.join();
}
auto actual = Collect<int>(storage);
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expectedAfter));
}
using mm::ObjectFactory;
namespace {
std::unique_ptr<TypeInfo> MakeObjectTypeInfo(int32_t size) {
auto typeInfo = std_support::make_unique<TypeInfo>();
typeInfo->typeInfo_ = typeInfo.get();
typeInfo->instanceSize_ = size;
return typeInfo;
}
std::unique_ptr<TypeInfo> MakeArrayTypeInfo(int32_t elementSize) {
auto typeInfo = std_support::make_unique<TypeInfo>();
typeInfo->typeInfo_ = typeInfo.get();
typeInfo->instanceSize_ = -elementSize;
return typeInfo;
}
} // namespace
TEST(ObjectFactoryTest, CreateObject) {
auto typeInfo = MakeObjectTypeInfo(24);
ObjectFactory objectFactory;
ObjectFactory::ThreadQueue threadQueue(objectFactory);
auto* object = threadQueue.CreateObject(typeInfo.get());
threadQueue.Publish();
auto iter = objectFactory.Iter();
auto it = iter.begin();
EXPECT_FALSE(it.IsArray());
EXPECT_THAT(it.GetObjHeader(), object);
++it;
EXPECT_THAT(it, iter.end());
}
TEST(ObjectFactoryTest, CreateArray) {
auto typeInfo = MakeArrayTypeInfo(24);
ObjectFactory objectFactory;
ObjectFactory::ThreadQueue threadQueue(objectFactory);
auto* array = threadQueue.CreateArray(typeInfo.get(), 3);
threadQueue.Publish();
auto iter = objectFactory.Iter();
auto it = iter.begin();
EXPECT_TRUE(it.IsArray());
EXPECT_THAT(it.GetArrayHeader(), array);
++it;
EXPECT_THAT(it, iter.end());
}
TEST(ObjectFactoryTest, Erase) {
auto objectTypeInfo = MakeObjectTypeInfo(24);
auto arrayTypeInfo = MakeArrayTypeInfo(24);
ObjectFactory objectFactory;
ObjectFactory::ThreadQueue threadQueue(objectFactory);
for (int i = 0; i < 10; ++i) {
threadQueue.CreateObject(objectTypeInfo.get());
threadQueue.CreateArray(arrayTypeInfo.get(), 3);
}
threadQueue.Publish();
{
auto iter = objectFactory.Iter();
for (auto it = iter.begin(); it != iter.end();) {
if (it.IsArray()) {
iter.EraseAndAdvance(it);
} else {
++it;
}
}
}
{
auto iter = objectFactory.Iter();
int count = 0;
for (auto it = iter.begin(); it != iter.end(); ++it, ++count) {
EXPECT_FALSE(it.IsArray());
}
EXPECT_THAT(count, 10);
}
}
TEST(ObjectFactoryTest, ConcurrentPublish) {
auto typeInfo = MakeObjectTypeInfo(24);
ObjectFactory objectFactory;
constexpr int kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
std::mutex expectedMutex;
std::vector<ObjHeader*> expected;
for (int i = 0; i < kThreadCount; ++i) {
threads.emplace_back([&typeInfo, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
ObjectFactory::ThreadQueue threadQueue(objectFactory);
auto* object = threadQueue.CreateObject(typeInfo.get());
{
std::lock_guard<std::mutex> guard(expectedMutex);
expected.push_back(object);
}
++readyCount;
while (!canStart) {
}
threadQueue.Publish();
});
}
while (readyCount < kThreadCount) {
}
canStart = true;
for (auto& t : threads) {
t.join();
}
auto iter = objectFactory.Iter();
std::vector<ObjHeader*> actual;
for (auto it = iter.begin(); it != iter.end(); ++it) {
actual.push_back(it.GetObjHeader());
}
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
}
@@ -45,14 +45,6 @@ static void destroyMetaObject(TypeInfo** location) {
extern "C" {
RUNTIME_NOTHROW OBJ_GETTER(AllocInstance, const TypeInfo* type_info) {
TODO();
}
OBJ_GETTER(AllocArrayInstance, const TypeInfo* type_info, int32_t elements) {
TODO();
}
OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
TODO();
}
@@ -9,6 +9,7 @@
#include <atomic>
#include <pthread.h>
#include "ObjectFactory.hpp"
#include "GlobalsRegistry.hpp"
#include "StableRefRegistry.hpp"
#include "ThreadLocalStorage.hpp"
@@ -26,7 +27,8 @@ public:
threadId_(threadId),
globalsThreadQueue_(GlobalsRegistry::Instance()),
stableRefThreadQueue_(StableRefRegistry::Instance()),
state_(ThreadState::kRunnable) {}
state_(ThreadState::kRunnable),
objectFactoryThreadQueue_(ObjectFactory::Instance()) {}
~ThreadData() = default;
@@ -42,12 +44,15 @@ public:
ThreadState setState(ThreadState state) noexcept { return state_.exchange(state); }
ObjectFactory::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
private:
const pthread_t threadId_;
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
ThreadLocalStorage tls_;
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
std::atomic<ThreadState> state_;
ObjectFactory::ThreadQueue objectFactoryThreadQueue_;
};
} // namespace mm