Sketch mm::ObjectFactory (#4583)
This commit is contained in:
committed by
Nikolay Krasko
parent
d239ff7f5a
commit
72fc5f5aee
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user