Use Alloc.h everywhere (#4611)
This commit is contained in:
committed by
Nikolay Krasko
parent
37bcd0894f
commit
d57802633c
@@ -21,6 +21,10 @@
|
|||||||
* Put implementation details inside `.h`/`.hpp` into a nested `namespace internal` (e.g. implementation details of module `mm` go into `namespace kotlin { namespace mm { namespace internal { ... } } }`)
|
* Put implementation details inside `.h`/`.hpp` into a nested `namespace internal` (e.g. implementation details of module `mm` go into `namespace kotlin { namespace mm { namespace internal { ... } } }`)
|
||||||
* Put implementation details inside `.cpp`/`.mm` into a global anonymous `namespace`
|
* Put implementation details inside `.cpp`/`.mm` into a global anonymous `namespace`
|
||||||
* For `extern "C"` declarations emulate namespaces with `Kotlin_[module_name]_` prefixes.
|
* For `extern "C"` declarations emulate namespaces with `Kotlin_[module_name]_` prefixes.
|
||||||
|
* To mark type as move-only, privately inherit from `kotlin::MoveOnly`
|
||||||
|
* To mark type unmovable and uncopyable, privately inherit from `kotlin::Pinned`
|
||||||
|
* All heap-allocated classes should publicly inherit from `KonanAllocatorAware`
|
||||||
|
* Use `KStd*` containers and smart pointers instead of `std::*` ones.
|
||||||
|
|
||||||
## Naming
|
## Naming
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ struct CycleDetectorRootset {
|
|||||||
KStdVector<ScopedRefHolder> heldRefs;
|
KStdVector<ScopedRefHolder> heldRefs;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CycleDetector : private kotlin::Pinned {
|
class CycleDetector : private kotlin::Pinned, public KonanAllocatorAware {
|
||||||
public:
|
public:
|
||||||
static void insertCandidateIfNeeded(KRef object) {
|
static void insertCandidateIfNeeded(KRef object) {
|
||||||
if (canBeACandidate(object))
|
if (canBeACandidate(object))
|
||||||
|
|||||||
@@ -119,4 +119,42 @@ bool operator!=(
|
|||||||
return !(x == y);
|
return !(x == y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class KonanDeleter {
|
||||||
|
public:
|
||||||
|
void operator()(T* instance) noexcept { konanDestructInstance(instance); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Force a class to be heap-allocated using `konanAllocMemory`. Does not prevent stack allocation, or
|
||||||
|
// allocation as part of another object.
|
||||||
|
// Usage:
|
||||||
|
// class A : public KonanAllocatorAware {
|
||||||
|
// ...
|
||||||
|
// };
|
||||||
|
class KonanAllocatorAware {
|
||||||
|
public:
|
||||||
|
static void* operator new(size_t count) noexcept { return konanAllocMemory(count); }
|
||||||
|
static void* operator new[](size_t count) noexcept { return konanAllocMemory(count); }
|
||||||
|
|
||||||
|
static void* operator new(size_t count, void* ptr) noexcept { return ptr; }
|
||||||
|
static void* operator new[](size_t count, void* ptr) noexcept { return ptr; }
|
||||||
|
|
||||||
|
static void operator delete(void* ptr) noexcept { konanFreeMemory(ptr); }
|
||||||
|
static void operator delete[](void* ptr) noexcept { konanFreeMemory(ptr); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Hide constructors, assignments and destructor to discourage operating on instance of `KonanAllocatorAware`
|
||||||
|
KonanAllocatorAware() = default;
|
||||||
|
|
||||||
|
KonanAllocatorAware(const KonanAllocatorAware&) = default;
|
||||||
|
KonanAllocatorAware(KonanAllocatorAware&&) = default;
|
||||||
|
|
||||||
|
KonanAllocatorAware& operator=(const KonanAllocatorAware&) = default;
|
||||||
|
KonanAllocatorAware& operator=(KonanAllocatorAware&&) = default;
|
||||||
|
|
||||||
|
// Not virtual by design. Since this class hides this destructor, no one can destroy an
|
||||||
|
// instance of `KonanAllocatorAware` directly, so this destructor is never called in a virtual manner.
|
||||||
|
~KonanAllocatorAware() = default;
|
||||||
|
};
|
||||||
|
|
||||||
#endif // RUNTIME_ALLOC_H
|
#endif // RUNTIME_ALLOC_H
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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 "Alloc.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class A : public KonanAllocatorAware {
|
||||||
|
public:
|
||||||
|
using DestructorHook = testing::StrictMock<testing::MockFunction<void(int)>>;
|
||||||
|
|
||||||
|
static thread_local DestructorHook* destructorHook;
|
||||||
|
|
||||||
|
explicit A(int value = -1) : value_(value) {}
|
||||||
|
|
||||||
|
~A() { destructorHook->Call(value_); }
|
||||||
|
|
||||||
|
int value() const { return value_; }
|
||||||
|
|
||||||
|
bool operator==(const A& rhs) const { return value_ == rhs.value_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// static
|
||||||
|
thread_local A::DestructorHook* A::destructorHook = nullptr;
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
explicit B(int value) : a(value) {}
|
||||||
|
|
||||||
|
A a;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class KonanAllocatorAwareTest : public testing::Test {
|
||||||
|
public:
|
||||||
|
KStdUniquePtr<A::DestructorHook> destructorHook;
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
Test::SetUp();
|
||||||
|
|
||||||
|
destructorHook = make_unique<A::DestructorHook>();
|
||||||
|
A::destructorHook = destructorHook.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
A::destructorHook = nullptr;
|
||||||
|
destructorHook.reset();
|
||||||
|
|
||||||
|
Test::TearDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(KonanAllocatorAwareTest, AllocatedOnStack) {
|
||||||
|
A a(42);
|
||||||
|
EXPECT_THAT(a.value(), 42);
|
||||||
|
EXPECT_CALL(*destructorHook, Call(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(KonanAllocatorAwareTest, AllocatedInAnotherObject) {
|
||||||
|
// We do not control how `B` is allocated.
|
||||||
|
B* b = new B(42);
|
||||||
|
EXPECT_THAT(b->a.value(), 42);
|
||||||
|
EXPECT_CALL(*destructorHook, Call(42));
|
||||||
|
delete b;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(KonanAllocatorAwareTest, AllocatedByItself) {
|
||||||
|
A* a = new A(42);
|
||||||
|
EXPECT_THAT(a->value(), 42);
|
||||||
|
EXPECT_CALL(*destructorHook, Call(42));
|
||||||
|
delete a;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(KonanAllocatorAwareTest, AllocateArray) {
|
||||||
|
constexpr size_t kCount = 5;
|
||||||
|
A* as = new A[kCount];
|
||||||
|
|
||||||
|
std::vector<int> actual;
|
||||||
|
for (A* a = as; a != as + kCount; ++a) {
|
||||||
|
actual.push_back(a->value());
|
||||||
|
}
|
||||||
|
std::array<int, kCount> expected;
|
||||||
|
for (int& element : expected) {
|
||||||
|
element = -1;
|
||||||
|
}
|
||||||
|
EXPECT_THAT(actual, testing::ElementsAreArray(expected));
|
||||||
|
|
||||||
|
EXPECT_CALL(*destructorHook, Call(-1)).Times(kCount);
|
||||||
|
delete[] as;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(KonanAllocatorAwareTest, PlacementAllocated) {
|
||||||
|
std::array<uint8_t, sizeof(A)> buffer;
|
||||||
|
A* a = new (buffer.data()) A(42);
|
||||||
|
EXPECT_THAT(a->value(), 42);
|
||||||
|
EXPECT_CALL(*destructorHook, Call(42));
|
||||||
|
a->~A();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(destructorHook.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) {
|
||||||
|
constexpr size_t kCount = 5;
|
||||||
|
std::array<uint8_t, sizeof(A) * kCount> buffer;
|
||||||
|
A* as = new (buffer.data()) A[kCount];
|
||||||
|
|
||||||
|
std::vector<int> actual;
|
||||||
|
for (A* a = as; a != as + kCount; ++a) {
|
||||||
|
actual.push_back(a->value());
|
||||||
|
}
|
||||||
|
std::array<int, kCount> expected;
|
||||||
|
for (int& element : expected) {
|
||||||
|
element = -1;
|
||||||
|
}
|
||||||
|
EXPECT_THAT(actual, testing::ElementsAreArray(expected));
|
||||||
|
|
||||||
|
EXPECT_CALL(*destructorHook, Call(-1)).Times(kCount);
|
||||||
|
for (A* a = as; a != as + kCount; ++a) {
|
||||||
|
a->~A();
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(destructorHook.get());
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "Cleaner.h"
|
#include "Cleaner.h"
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
@@ -14,6 +13,7 @@
|
|||||||
#include "Atomic.h"
|
#include "Atomic.h"
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
#include "TestSupportCompilerGenerated.hpp"
|
#include "TestSupportCompilerGenerated.hpp"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
using testing::_;
|
using testing::_;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ TEST(CleanerTest, ConcurrentCreation) {
|
|||||||
|
|
||||||
int startedThreads = 0;
|
int startedThreads = 0;
|
||||||
bool allowRunning = false;
|
bool allowRunning = false;
|
||||||
std::vector<std::future<KInt>> futures;
|
KStdVector<std::future<KInt>> futures;
|
||||||
for (int i = 0; i < threadCount; ++i) {
|
for (int i = 0; i < threadCount; ++i) {
|
||||||
auto future = std::async(std::launch::async, [&startedThreads, &allowRunning]() {
|
auto future = std::async(std::launch::async, [&startedThreads, &allowRunning]() {
|
||||||
atomicAdd(&startedThreads, 1);
|
atomicAdd(&startedThreads, 1);
|
||||||
@@ -44,7 +44,7 @@ TEST(CleanerTest, ConcurrentCreation) {
|
|||||||
while (atomicGet(&startedThreads) != threadCount) {
|
while (atomicGet(&startedThreads) != threadCount) {
|
||||||
}
|
}
|
||||||
atomicSet(&allowRunning, true);
|
atomicSet(&allowRunning, true);
|
||||||
std::vector<KInt> values;
|
KStdVector<KInt> values;
|
||||||
for (auto& future : futures) {
|
for (auto& future : futures) {
|
||||||
values.push_back(future.get());
|
values.push_back(future.get());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#define RUNTIME_CPP_SUPPORT_H
|
#define RUNTIME_CPP_SUPPORT_H
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// A collection of backported utilities from future C++ versions.
|
// A collection of backported utilities from future C++ versions.
|
||||||
|
|
||||||
@@ -16,11 +15,6 @@ namespace std_support {
|
|||||||
|
|
||||||
////////////////////////// C++14 //////////////////////////
|
////////////////////////// C++14 //////////////////////////
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
|
||||||
std::unique_ptr<T> make_unique(Args&&... args) {
|
|
||||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using make_unsigned_t = typename std::make_unsigned<T>::type;
|
using make_unsigned_t = typename std::make_unsigned<T>::type;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
@@ -20,9 +21,9 @@ class MultiSourceQueue {
|
|||||||
public:
|
public:
|
||||||
class Producer;
|
class Producer;
|
||||||
|
|
||||||
// TODO: Consider switching from `std::list` to `SingleLockList` to hide the constructor
|
// TODO: Consider switching from `KStdList` to `SingleLockList` to hide the constructor
|
||||||
// and to not store the iterator.
|
// and to not store the iterator.
|
||||||
class Node : private Pinned {
|
class Node : private Pinned, public KonanAllocatorAware {
|
||||||
public:
|
public:
|
||||||
Node(const T& value, Producer* owner) noexcept : value_(value), owner_(owner) {}
|
Node(const T& value, Producer* owner) noexcept : value_(value), owner_(owner) {}
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ public:
|
|||||||
|
|
||||||
T value_;
|
T value_;
|
||||||
std::atomic<Producer*> owner_; // `nullptr` signifies that `MultiSourceQueue` owns it.
|
std::atomic<Producer*> owner_; // `nullptr` signifies that `MultiSourceQueue` owns it.
|
||||||
typename std::list<Node>::iterator position_;
|
typename KStdList<Node>::iterator position_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Producer {
|
class Producer {
|
||||||
@@ -72,8 +73,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
MultiSourceQueue& owner_; // weak
|
MultiSourceQueue& owner_; // weak
|
||||||
std::list<Node> queue_;
|
KStdList<Node> queue_;
|
||||||
std::list<Node*> deletionQueue_;
|
KStdList<Node*> deletionQueue_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
@@ -92,9 +93,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
friend class MultiSourceQueue;
|
friend class MultiSourceQueue;
|
||||||
|
|
||||||
explicit Iterator(const typename std::list<Node>::iterator& position) noexcept : position_(position) {}
|
explicit Iterator(const typename KStdList<Node>::iterator& position) noexcept : position_(position) {}
|
||||||
|
|
||||||
typename std::list<Node>::iterator position_;
|
typename KStdList<Node>::iterator position_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Iterable : MoveOnly {
|
class Iterable : MoveOnly {
|
||||||
@@ -118,7 +119,7 @@ public:
|
|||||||
// Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published.
|
// Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published.
|
||||||
void ApplyDeletions() noexcept {
|
void ApplyDeletions() noexcept {
|
||||||
std::lock_guard<SpinLock> guard(mutex_);
|
std::lock_guard<SpinLock> guard(mutex_);
|
||||||
std::list<Node*> remainingDeletions;
|
KStdList<Node*> remainingDeletions;
|
||||||
|
|
||||||
auto it = deletionQueue_.begin();
|
auto it = deletionQueue_.begin();
|
||||||
while (it != deletionQueue_.end()) {
|
while (it != deletionQueue_.end()) {
|
||||||
@@ -139,10 +140,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Using `std::list` as it allows to implement `Collect` without memory allocations,
|
// Using `KStdList` as it allows to implement `Collect` without memory allocations,
|
||||||
// which is important for GC mark phase.
|
// which is important for GC mark phase.
|
||||||
std::list<Node> queue_;
|
KStdList<Node> queue_;
|
||||||
std::list<Node*> deletionQueue_;
|
KStdList<Node*> deletionQueue_;
|
||||||
SpinLock mutex_;
|
SpinLock mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,15 @@
|
|||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::vector<T> Collect(MultiSourceQueue<T>& queue) {
|
KStdVector<T> Collect(MultiSourceQueue<T>& queue) {
|
||||||
std::vector<T> result;
|
KStdVector<T> result;
|
||||||
for (const auto& element : queue.Iter()) {
|
for (const auto& element : queue.Iter()) {
|
||||||
result.push_back(element);
|
result.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -192,8 +193,8 @@ TEST(MultiSourceQueueTest, ConcurrentPublish) {
|
|||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
std::vector<int> expected;
|
KStdVector<int> expected;
|
||||||
|
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
expected.push_back(i);
|
expected.push_back(i);
|
||||||
@@ -223,8 +224,8 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) {
|
|||||||
constexpr int kStartCount = 50;
|
constexpr int kStartCount = 50;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
|
|
||||||
std::vector<int> expectedBefore;
|
KStdVector<int> expectedBefore;
|
||||||
std::vector<int> expectedAfter;
|
KStdVector<int> expectedAfter;
|
||||||
IntQueue::Producer producer(queue);
|
IntQueue::Producer producer(queue);
|
||||||
for (int i = 0; i < kStartCount; ++i) {
|
for (int i = 0; i < kStartCount; ++i) {
|
||||||
expectedBefore.push_back(i);
|
expectedBefore.push_back(i);
|
||||||
@@ -236,7 +237,7 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) {
|
|||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::atomic<int> startedCount(0);
|
std::atomic<int> startedCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
int j = i + kStartCount;
|
int j = i + kStartCount;
|
||||||
expectedAfter.push_back(j);
|
expectedAfter.push_back(j);
|
||||||
@@ -251,7 +252,7 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> actualBefore;
|
KStdVector<int> actualBefore;
|
||||||
{
|
{
|
||||||
auto iter = queue.Iter();
|
auto iter = queue.Iter();
|
||||||
while (readyCount < kThreadCount) {
|
while (readyCount < kThreadCount) {
|
||||||
@@ -282,7 +283,7 @@ TEST(MultiSourceQueueTest, ConcurrentPublishAndApplyDeletions) {
|
|||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::atomic<int> startedCount(0);
|
std::atomic<int> startedCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
threads.emplace_back([&queue, i, &canStart, &readyCount, &startedCount]() {
|
threads.emplace_back([&queue, i, &canStart, &readyCount, &startedCount]() {
|
||||||
IntQueue::Producer producer(queue);
|
IntQueue::Producer producer(queue);
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "CppSupport.hpp"
|
#include "Alloc.h"
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
@@ -20,29 +21,43 @@ namespace kotlin {
|
|||||||
template <typename Value, typename Mutex = SpinLock>
|
template <typename Value, typename Mutex = SpinLock>
|
||||||
class SingleLockList : private Pinned {
|
class SingleLockList : private Pinned {
|
||||||
public:
|
public:
|
||||||
class Node : Pinned {
|
class Node;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class NodeDeleter {
|
||||||
public:
|
public:
|
||||||
Value* Get() noexcept { return &value; }
|
void operator()(Node* node) const { delete node; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using NodeOwner = std::unique_ptr<Node, NodeDeleter>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Node : private Pinned, public KonanAllocatorAware {
|
||||||
|
public:
|
||||||
|
Value* Get() noexcept { return &value_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class SingleLockList;
|
friend class SingleLockList;
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
Node(Args... args) noexcept : value(args...) {}
|
Node(Args&&... args) noexcept : value_(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
Value value;
|
// Make sure `Node` can only be deleted by `SingleLockList` itself.
|
||||||
std::unique_ptr<Node> next;
|
~Node() = default;
|
||||||
Node* previous = nullptr; // weak
|
|
||||||
|
Value value_;
|
||||||
|
NodeOwner next_;
|
||||||
|
Node* previous_ = nullptr; // weak
|
||||||
};
|
};
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
public:
|
public:
|
||||||
explicit Iterator(Node* node) noexcept : node_(node) {}
|
explicit Iterator(Node* node) noexcept : node_(node) {}
|
||||||
|
|
||||||
Value& operator*() noexcept { return node_->value; }
|
Value& operator*() noexcept { return node_->value_; }
|
||||||
|
|
||||||
Iterator& operator++() noexcept {
|
Iterator& operator++() noexcept {
|
||||||
node_ = node_->next.get();
|
node_ = node_->next_.get();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,36 +82,56 @@ public:
|
|||||||
std::unique_lock<Mutex> guard_;
|
std::unique_lock<Mutex> guard_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
~SingleLockList() {
|
||||||
Node* Emplace(Args... args) noexcept {
|
AssertCorrectUnsafe();
|
||||||
auto* nodePtr = new Node(args...);
|
// Make sure not to blow up the stack by nested `~Node` calls.
|
||||||
std::unique_ptr<Node> node(nodePtr);
|
for (auto node = std::move(root_); node != nullptr; node = std::move(node->next_)) {
|
||||||
std::lock_guard<Mutex> guard(mutex_);
|
|
||||||
if (root_) {
|
|
||||||
root_->previous = node.get();
|
|
||||||
}
|
}
|
||||||
node->next = std::move(root_);
|
last_ = nullptr;
|
||||||
|
AssertCorrectUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider making `Emplace` append to `last_`.
|
||||||
|
template <typename... Args>
|
||||||
|
Node* Emplace(Args&&... args) noexcept {
|
||||||
|
auto* nodePtr = new Node(std::forward<Args>(args)...);
|
||||||
|
NodeOwner node(nodePtr);
|
||||||
|
std::lock_guard<Mutex> guard(mutex_);
|
||||||
|
AssertCorrectUnsafe();
|
||||||
|
if (root_) {
|
||||||
|
root_->previous_ = node.get();
|
||||||
|
} else {
|
||||||
|
last_ = nodePtr;
|
||||||
|
}
|
||||||
|
node->next_ = std::move(root_);
|
||||||
root_ = std::move(node);
|
root_ = std::move(node);
|
||||||
|
AssertCorrectUnsafe();
|
||||||
return nodePtr;
|
return nodePtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using `node` including its referred `Value` after `Erase` is undefined behaviour.
|
// Using `node` including its referred `Value` after `Erase` is undefined behaviour.
|
||||||
void Erase(Node* node) noexcept {
|
void Erase(Node* node) noexcept {
|
||||||
std::lock_guard<Mutex> guard(mutex_);
|
std::lock_guard<Mutex> guard(mutex_);
|
||||||
|
AssertCorrectUnsafe();
|
||||||
|
if (last_ == node) {
|
||||||
|
last_ = node->previous_;
|
||||||
|
}
|
||||||
if (root_.get() == node) {
|
if (root_.get() == node) {
|
||||||
root_ = std::move(node->next);
|
root_ = std::move(node->next_);
|
||||||
if (root_) {
|
if (root_) {
|
||||||
root_->previous = nullptr;
|
root_->previous_ = nullptr;
|
||||||
}
|
}
|
||||||
|
AssertCorrectUnsafe();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto* previous = node->previous;
|
auto* previous = node->previous_;
|
||||||
RuntimeAssert(previous != nullptr, "Only the root node doesn't have the previous node");
|
RuntimeAssert(previous != nullptr, "Only the root node doesn't have the previous node");
|
||||||
auto ownedNode = std::move(previous->next);
|
auto ownedNode = std::move(previous->next_);
|
||||||
previous->next = std::move(node->next);
|
previous->next_ = std::move(node->next_);
|
||||||
if (auto& next = previous->next) {
|
if (auto& next = previous->next_) {
|
||||||
next->previous = previous;
|
next->previous_ = previous;
|
||||||
}
|
}
|
||||||
|
AssertCorrectUnsafe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned value locks `this` to perform safe iteration. `this` unlocks when
|
// Returned value locks `this` to perform safe iteration. `this` unlocks when
|
||||||
@@ -109,7 +144,19 @@ public:
|
|||||||
Iterable Iter() noexcept { return Iterable(this); }
|
Iterable Iter() noexcept { return Iterable(this); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Node> root_;
|
// 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(root_->previous_ == nullptr, "root_ must not have previous_");
|
||||||
|
RuntimeAssert(last_ != nullptr, "last_ must not be null");
|
||||||
|
RuntimeAssert(last_->next_ == nullptr, "last_ must not have next_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeOwner root_;
|
||||||
|
Node* last_ = nullptr;
|
||||||
Mutex mutex_;
|
Mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,14 @@
|
|||||||
#include "SingleLockList.hpp"
|
#include "SingleLockList.hpp"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <deque>
|
#include <functional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ TEST(SingleLockListTest, EmplaceAndIter) {
|
|||||||
list.Emplace(kSecond);
|
list.Emplace(kSecond);
|
||||||
list.Emplace(kThird);
|
list.Emplace(kThird);
|
||||||
|
|
||||||
std::vector<int> actual;
|
KStdVector<int> actual;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actual.push_back(element);
|
actual.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ TEST(SingleLockListTest, EmplaceEraseAndIter) {
|
|||||||
list.Emplace(kThird);
|
list.Emplace(kThird);
|
||||||
list.Erase(secondNode);
|
list.Erase(secondNode);
|
||||||
|
|
||||||
std::vector<int> actual;
|
KStdVector<int> actual;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actual.push_back(element);
|
actual.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ TEST(SingleLockListTest, EmplaceEraseAndIter) {
|
|||||||
TEST(SingleLockListTest, IterEmpty) {
|
TEST(SingleLockListTest, IterEmpty) {
|
||||||
IntList list;
|
IntList list;
|
||||||
|
|
||||||
std::vector<int> actual;
|
KStdVector<int> actual;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actual.push_back(element);
|
actual.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ TEST(SingleLockListTest, EraseToEmptyEmplaceAndIter) {
|
|||||||
list.Emplace(kThird);
|
list.Emplace(kThird);
|
||||||
list.Emplace(kFourth);
|
list.Emplace(kFourth);
|
||||||
|
|
||||||
std::vector<int> actual;
|
KStdVector<int> actual;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actual.push_back(element);
|
actual.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -110,8 +111,8 @@ TEST(SingleLockListTest, ConcurrentEmplace) {
|
|||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
std::vector<int> expected;
|
KStdVector<int> expected;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
expected.push_back(i);
|
expected.push_back(i);
|
||||||
threads.emplace_back([i, &list, &canStart, &readyCount]() {
|
threads.emplace_back([i, &list, &canStart, &readyCount]() {
|
||||||
@@ -129,7 +130,7 @@ TEST(SingleLockListTest, ConcurrentEmplace) {
|
|||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> actual;
|
KStdVector<int> actual;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actual.push_back(element);
|
actual.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -140,14 +141,14 @@ TEST(SingleLockListTest, ConcurrentEmplace) {
|
|||||||
TEST(SingleLockListTest, ConcurrentErase) {
|
TEST(SingleLockListTest, ConcurrentErase) {
|
||||||
IntList list;
|
IntList list;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
std::vector<IntList::Node*> items;
|
KStdVector<IntList::Node*> items;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
items.push_back(list.Emplace(i));
|
items.push_back(list.Emplace(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (auto* item : items) {
|
for (auto* item : items) {
|
||||||
threads.emplace_back([item, &list, &canStart, &readyCount]() {
|
threads.emplace_back([item, &list, &canStart, &readyCount]() {
|
||||||
++readyCount;
|
++readyCount;
|
||||||
@@ -164,7 +165,7 @@ TEST(SingleLockListTest, ConcurrentErase) {
|
|||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> actual;
|
KStdVector<int> actual;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actual.push_back(element);
|
actual.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -177,8 +178,8 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
|
|||||||
constexpr int kStartCount = 50;
|
constexpr int kStartCount = 50;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
|
|
||||||
std::deque<int> expectedBefore;
|
KStdDeque<int> expectedBefore;
|
||||||
std::vector<int> expectedAfter;
|
KStdVector<int> expectedAfter;
|
||||||
for (int i = 0; i < kStartCount; ++i) {
|
for (int i = 0; i < kStartCount; ++i) {
|
||||||
expectedBefore.push_front(i);
|
expectedBefore.push_front(i);
|
||||||
expectedAfter.push_back(i);
|
expectedAfter.push_back(i);
|
||||||
@@ -187,7 +188,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
|
|||||||
|
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> startedCount(0);
|
std::atomic<int> startedCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
int j = i + kStartCount;
|
int j = i + kStartCount;
|
||||||
expectedAfter.push_back(j);
|
expectedAfter.push_back(j);
|
||||||
@@ -199,7 +200,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> actualBefore;
|
KStdVector<int> actualBefore;
|
||||||
{
|
{
|
||||||
auto iter = list.Iter();
|
auto iter = list.Iter();
|
||||||
canStart = true;
|
canStart = true;
|
||||||
@@ -217,7 +218,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
|
|||||||
|
|
||||||
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
|
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
|
||||||
|
|
||||||
std::vector<int> actualAfter;
|
KStdVector<int> actualAfter;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actualAfter.push_back(element);
|
actualAfter.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -229,8 +230,8 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
|
|||||||
IntList list;
|
IntList list;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
|
|
||||||
std::deque<int> expectedBefore;
|
KStdDeque<int> expectedBefore;
|
||||||
std::vector<IntList::Node*> items;
|
KStdVector<IntList::Node*> items;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
expectedBefore.push_front(i);
|
expectedBefore.push_front(i);
|
||||||
items.push_back(list.Emplace(i));
|
items.push_back(list.Emplace(i));
|
||||||
@@ -238,7 +239,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
|
|||||||
|
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> startedCount(0);
|
std::atomic<int> startedCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (auto* item : items) {
|
for (auto* item : items) {
|
||||||
threads.emplace_back([item, &list, &canStart, &startedCount]() {
|
threads.emplace_back([item, &list, &canStart, &startedCount]() {
|
||||||
while (!canStart) {
|
while (!canStart) {
|
||||||
@@ -248,7 +249,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> actualBefore;
|
KStdVector<int> actualBefore;
|
||||||
{
|
{
|
||||||
auto iter = list.Iter();
|
auto iter = list.Iter();
|
||||||
canStart = true;
|
canStart = true;
|
||||||
@@ -266,7 +267,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
|
|||||||
|
|
||||||
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
|
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
|
||||||
|
|
||||||
std::vector<int> actualAfter;
|
KStdVector<int> actualAfter;
|
||||||
for (int element : list.Iter()) {
|
for (int element : list.Iter()) {
|
||||||
actualAfter.push_back(element);
|
actualAfter.push_back(element);
|
||||||
}
|
}
|
||||||
@@ -298,10 +299,48 @@ TEST(SingleLockListTest, PinnedType) {
|
|||||||
|
|
||||||
list.Erase(itemNode);
|
list.Erase(itemNode);
|
||||||
|
|
||||||
std::vector<PinnedType*> actualAfter;
|
KStdVector<PinnedType*> actualAfter;
|
||||||
for (auto& element : list.Iter()) {
|
for (auto& element : list.Iter()) {
|
||||||
actualAfter.push_back(&element);
|
actualAfter.push_back(&element);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_THAT(actualAfter, testing::IsEmpty());
|
EXPECT_THAT(actualAfter, testing::IsEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class WithDestructorHook;
|
||||||
|
|
||||||
|
using DestructorHook = void(WithDestructorHook*);
|
||||||
|
|
||||||
|
class WithDestructorHook : private Pinned {
|
||||||
|
public:
|
||||||
|
explicit WithDestructorHook(std::function<DestructorHook> hook) : hook_(std::move(hook)) {}
|
||||||
|
|
||||||
|
~WithDestructorHook() { hook_(this); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<DestructorHook> hook_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(SingleLockListTest, Destructor) {
|
||||||
|
testing::StrictMock<testing::MockFunction<DestructorHook>> hook;
|
||||||
|
{
|
||||||
|
SingleLockList<WithDestructorHook> list;
|
||||||
|
auto* first = list.Emplace(hook.AsStdFunction())->Get();
|
||||||
|
auto* second = list.Emplace(hook.AsStdFunction())->Get();
|
||||||
|
auto* third = list.Emplace(hook.AsStdFunction())->Get();
|
||||||
|
{
|
||||||
|
testing::InSequence seq;
|
||||||
|
// `list` is `third`->`second`->`first`. If destruction
|
||||||
|
// were to cause recursion, the order of destructors
|
||||||
|
// would've been backwards.
|
||||||
|
EXPECT_CALL(hook, Call(third));
|
||||||
|
EXPECT_CALL(hook, Call(second));
|
||||||
|
EXPECT_CALL(hook, Call(first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClear(&hook);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "CppSupport.hpp"
|
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ public:
|
|||||||
explicit ScopedStrictMockFunction(Mock** globalMockLocation) : globalMockLocation_(globalMockLocation) {
|
explicit ScopedStrictMockFunction(Mock** globalMockLocation) : globalMockLocation_(globalMockLocation) {
|
||||||
RuntimeCheck(globalMockLocation != nullptr, "ScopedStrictMockFunction needs non-null global mock location");
|
RuntimeCheck(globalMockLocation != nullptr, "ScopedStrictMockFunction needs non-null global mock location");
|
||||||
RuntimeCheck(*globalMockLocation == nullptr, "ScopedStrictMockFunction needs null global mock");
|
RuntimeCheck(*globalMockLocation == nullptr, "ScopedStrictMockFunction needs null global mock");
|
||||||
mock_ = kotlin::std_support::make_unique<Mock>();
|
mock_ = make_unique<Mock>();
|
||||||
*globalMockLocation_ = mock_.get();
|
*globalMockLocation_ = mock_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
// Can be null if moved-out of.
|
// Can be null if moved-out of.
|
||||||
Mock** globalMockLocation_;
|
Mock** globalMockLocation_;
|
||||||
std::unique_ptr<Mock> mock_;
|
KStdUniquePtr<Mock> mock_;
|
||||||
};
|
};
|
||||||
|
|
||||||
ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock();
|
ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -60,6 +61,8 @@ typedef ObjHeader* KRef;
|
|||||||
typedef const ObjHeader* KConstRef;
|
typedef const ObjHeader* KConstRef;
|
||||||
typedef const ArrayHeader* KString;
|
typedef const ArrayHeader* KString;
|
||||||
|
|
||||||
|
// TODO: Consider moving these into `kotlin::std_support` namespace keeping STL names.
|
||||||
|
|
||||||
// Definitions of STL classes used inside Konan runtime.
|
// Definitions of STL classes used inside Konan runtime.
|
||||||
typedef std::basic_string<char, std::char_traits<char>,
|
typedef std::basic_string<char, std::char_traits<char>,
|
||||||
KonanAllocator<char>> KStdString;
|
KonanAllocator<char>> KStdString;
|
||||||
@@ -81,6 +84,13 @@ template<class Value>
|
|||||||
using KStdVector = std::vector<Value, KonanAllocator<Value>>;
|
using KStdVector = std::vector<Value, KonanAllocator<Value>>;
|
||||||
template<class Value>
|
template<class Value>
|
||||||
using KStdList = std::list<Value, KonanAllocator<Value>>;
|
using KStdList = std::list<Value, KonanAllocator<Value>>;
|
||||||
|
template <class Value>
|
||||||
|
using KStdUniquePtr = std::unique_ptr<Value, KonanDeleter<Value>>;
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
KStdUniquePtr<T> make_unique(Args&&... args) noexcept {
|
||||||
|
return KStdUniquePtr<T>(konanConstructInstance<T>(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|||||||
@@ -476,7 +476,7 @@ class State {
|
|||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void waitNativeWorkersTerminationUnlocked(bool checkLeaks, F waitForWorker) {
|
void waitNativeWorkersTerminationUnlocked(bool checkLeaks, F waitForWorker) {
|
||||||
std::vector<std::pair<KInt, pthread_t>> workersToWait;
|
KStdVector<std::pair<KInt, pthread_t>> workersToWait;
|
||||||
{
|
{
|
||||||
Locker locker(&lock_);
|
Locker locker(&lock_);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "Alloc.h"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "TypeInfo.h"
|
#include "TypeInfo.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
@@ -17,7 +18,7 @@ namespace kotlin {
|
|||||||
namespace mm {
|
namespace mm {
|
||||||
|
|
||||||
// Optional data that's lazily allocated only for objects that need it.
|
// Optional data that's lazily allocated only for objects that need it.
|
||||||
class ExtraObjectData : private Pinned {
|
class ExtraObjectData : private Pinned, public KonanAllocatorAware {
|
||||||
public:
|
public:
|
||||||
MetaObjHeader* AsMetaObjHeader() noexcept { return reinterpret_cast<MetaObjHeader*>(this); }
|
MetaObjHeader* AsMetaObjHeader() noexcept { return reinterpret_cast<MetaObjHeader*>(this); }
|
||||||
static ExtraObjectData& FromMetaObjHeader(MetaObjHeader* header) noexcept { return *reinterpret_cast<ExtraObjectData*>(header); }
|
static ExtraObjectData& FromMetaObjHeader(MetaObjHeader* header) noexcept { return *reinterpret_cast<ExtraObjectData*>(header); }
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public:
|
|||||||
void ProcessThread(mm::ThreadData* threadData) noexcept;
|
void ProcessThread(mm::ThreadData* threadData) noexcept;
|
||||||
|
|
||||||
// Lock registry for safe iteration.
|
// Lock registry for safe iteration.
|
||||||
// TODO: Iteration over `globals_` will be slow, because it's `std::list` collected at different times from
|
// TODO: Iteration over `globals_` will be slow, because it's `KStdList` collected at different times from
|
||||||
// different threads, and so the nodes are all over the memory. Use metrics to understand how
|
// different threads, and so the nodes are all over the memory. Use metrics to understand how
|
||||||
// much of a problem is it.
|
// much of a problem is it.
|
||||||
Iterable Iter() noexcept { return globals_.Iter(); }
|
Iterable Iter() noexcept { return globals_.Iter(); }
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "CppSupport.hpp"
|
#include "CppSupport.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
@@ -31,15 +32,14 @@ class ObjectFactoryStorage : private Pinned {
|
|||||||
static_assert(IsValidAlignment(DataAlignment), "DataAlignment is not a valid alignment");
|
static_assert(IsValidAlignment(DataAlignment), "DataAlignment is not a valid alignment");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// This class does not know its size at compile-time.
|
// 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.
|
||||||
class Node : private Pinned {
|
class Node : private Pinned {
|
||||||
constexpr static size_t DataOffset() noexcept { return AlignUp(sizeof(Node), DataAlignment); }
|
constexpr static size_t DataOffset() noexcept { return AlignUp(sizeof(Node), DataAlignment); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~Node() = default;
|
~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.
|
// Note: This can only be trivially destructible data, as nobody can invoke its destructor.
|
||||||
void* Data() noexcept {
|
void* Data() noexcept {
|
||||||
constexpr size_t kDataOffset = DataOffset();
|
constexpr size_t kDataOffset = DataOffset();
|
||||||
@@ -59,7 +59,7 @@ public:
|
|||||||
|
|
||||||
Node() noexcept = default;
|
Node() noexcept = default;
|
||||||
|
|
||||||
static void* operator new(size_t size, size_t dataSize) noexcept {
|
static KStdUniquePtr<Node> Create(size_t dataSize) noexcept {
|
||||||
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
|
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
|
||||||
size_t totalAlignment = std::max(alignof(Node), DataAlignment);
|
size_t totalAlignment = std::max(alignof(Node), DataAlignment);
|
||||||
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, totalAlignment);
|
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, totalAlignment);
|
||||||
@@ -73,10 +73,10 @@ public:
|
|||||||
konan::abort();
|
konan::abort();
|
||||||
}
|
}
|
||||||
RuntimeAssert(IsAligned(ptr, totalAlignment), "Allocator returned unaligned to %zu pointer %p", totalAlignment, ptr);
|
RuntimeAssert(IsAligned(ptr, totalAlignment), "Allocator returned unaligned to %zu pointer %p", totalAlignment, ptr);
|
||||||
return ptr;
|
return KStdUniquePtr<Node>(new (ptr) Node());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Node> next_;
|
KStdUniquePtr<Node> next_;
|
||||||
// There's some more data of an unknown (at compile-time) size here, but it cannot be represented
|
// There's some more data of an unknown (at compile-time) size here, but it cannot be represented
|
||||||
// with C++ members.
|
// with C++ members.
|
||||||
};
|
};
|
||||||
@@ -89,13 +89,11 @@ public:
|
|||||||
|
|
||||||
Node& Insert(size_t dataSize) noexcept {
|
Node& Insert(size_t dataSize) noexcept {
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
auto* nodePtr = new (dataSize) Node();
|
auto node = Node::Create(dataSize);
|
||||||
std::unique_ptr<Node> node(nodePtr);
|
auto* nodePtr = node.get();
|
||||||
if (!root_) {
|
if (!root_) {
|
||||||
RuntimeAssert(last_ == nullptr, "Unsynchronized root_ and last_");
|
|
||||||
root_ = std::move(node);
|
root_ = std::move(node);
|
||||||
} else {
|
} else {
|
||||||
RuntimeAssert(last_ != nullptr, "Unsynchronized root_ and last_");
|
|
||||||
last_->next_ = std::move(node);
|
last_->next_ = std::move(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +153,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjectFactoryStorage& owner_; // weak
|
ObjectFactoryStorage& owner_; // weak
|
||||||
std::unique_ptr<Node> root_;
|
KStdUniquePtr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,6 +195,11 @@ public:
|
|||||||
std::unique_lock<SpinLock> guard_;
|
std::unique_lock<SpinLock> guard_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
~ObjectFactoryStorage() {
|
||||||
|
// Make sure not to blow up the stack by nested `~Node` calls.
|
||||||
|
for (auto node = std::move(root_); node != nullptr; node = std::move(node->next_)) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Lock `ObjectFactoryStorage` for safe iteration.
|
// Lock `ObjectFactoryStorage` for safe iteration.
|
||||||
Iterable Iter() noexcept { return Iterable(*this); }
|
Iterable Iter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
@@ -236,7 +239,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Node> root_;
|
KStdUniquePtr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
SpinLock mutex_;
|
SpinLock mutex_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "CppSupport.hpp"
|
#include "CppSupport.hpp"
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -24,8 +25,8 @@ using ObjectFactoryStorageRegular = ObjectFactoryStorage<alignof(void*)>;
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <size_t DataAlignment>
|
template <size_t DataAlignment>
|
||||||
std::vector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
|
KStdVector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
|
||||||
std::vector<void*> result;
|
KStdVector<void*> result;
|
||||||
for (auto& node : storage.Iter()) {
|
for (auto& node : storage.Iter()) {
|
||||||
result.push_back(node.Data());
|
result.push_back(node.Data());
|
||||||
}
|
}
|
||||||
@@ -33,8 +34,8 @@ std::vector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, size_t DataAlignment>
|
template <typename T, size_t DataAlignment>
|
||||||
std::vector<T> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
|
KStdVector<T> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
|
||||||
std::vector<T> result;
|
KStdVector<T> result;
|
||||||
for (auto& node : storage.Iter()) {
|
for (auto& node : storage.Iter()) {
|
||||||
result.push_back(*static_cast<T*>(node.Data()));
|
result.push_back(*static_cast<T*>(node.Data()));
|
||||||
}
|
}
|
||||||
@@ -300,8 +301,8 @@ TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
|
|||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
std::vector<int> expected;
|
KStdVector<int> expected;
|
||||||
|
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
expected.push_back(i);
|
expected.push_back(i);
|
||||||
@@ -332,8 +333,8 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
|
|||||||
constexpr int kStartCount = 50;
|
constexpr int kStartCount = 50;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
|
|
||||||
std::vector<int> expectedBefore;
|
KStdVector<int> expectedBefore;
|
||||||
std::vector<int> expectedAfter;
|
KStdVector<int> expectedAfter;
|
||||||
ObjectFactoryStorageRegular::Producer producer(storage);
|
ObjectFactoryStorageRegular::Producer producer(storage);
|
||||||
for (int i = 0; i < kStartCount; ++i) {
|
for (int i = 0; i < kStartCount; ++i) {
|
||||||
expectedBefore.push_back(i);
|
expectedBefore.push_back(i);
|
||||||
@@ -345,7 +346,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
|
|||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::atomic<int> startedCount(0);
|
std::atomic<int> startedCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
int j = i + kStartCount;
|
int j = i + kStartCount;
|
||||||
expectedAfter.push_back(j);
|
expectedAfter.push_back(j);
|
||||||
@@ -360,7 +361,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> actualBefore;
|
KStdVector<int> actualBefore;
|
||||||
{
|
{
|
||||||
auto iter = storage.Iter();
|
auto iter = storage.Iter();
|
||||||
while (readyCount < kThreadCount) {
|
while (readyCount < kThreadCount) {
|
||||||
@@ -391,7 +392,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
|||||||
constexpr int kStartCount = 50;
|
constexpr int kStartCount = 50;
|
||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
|
|
||||||
std::vector<int> expectedAfter;
|
KStdVector<int> expectedAfter;
|
||||||
ObjectFactoryStorageRegular::Producer producer(storage);
|
ObjectFactoryStorageRegular::Producer producer(storage);
|
||||||
for (int i = 0; i < kStartCount; ++i) {
|
for (int i = 0; i < kStartCount; ++i) {
|
||||||
if (i % 2 == 0) {
|
if (i % 2 == 0) {
|
||||||
@@ -404,7 +405,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
|||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::atomic<int> startedCount(0);
|
std::atomic<int> startedCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
int j = i + kStartCount;
|
int j = i + kStartCount;
|
||||||
expectedAfter.push_back(j);
|
expectedAfter.push_back(j);
|
||||||
@@ -449,15 +450,15 @@ using mm::ObjectFactory;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::unique_ptr<TypeInfo> MakeObjectTypeInfo(int32_t size) {
|
KStdUniquePtr<TypeInfo> MakeObjectTypeInfo(int32_t size) {
|
||||||
auto typeInfo = std_support::make_unique<TypeInfo>();
|
auto typeInfo = make_unique<TypeInfo>();
|
||||||
typeInfo->typeInfo_ = typeInfo.get();
|
typeInfo->typeInfo_ = typeInfo.get();
|
||||||
typeInfo->instanceSize_ = size;
|
typeInfo->instanceSize_ = size;
|
||||||
return typeInfo;
|
return typeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<TypeInfo> MakeArrayTypeInfo(int32_t elementSize) {
|
KStdUniquePtr<TypeInfo> MakeArrayTypeInfo(int32_t elementSize) {
|
||||||
auto typeInfo = std_support::make_unique<TypeInfo>();
|
auto typeInfo = make_unique<TypeInfo>();
|
||||||
typeInfo->typeInfo_ = typeInfo.get();
|
typeInfo->typeInfo_ = typeInfo.get();
|
||||||
typeInfo->instanceSize_ = -elementSize;
|
typeInfo->instanceSize_ = -elementSize;
|
||||||
return typeInfo;
|
return typeInfo;
|
||||||
@@ -537,9 +538,9 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
|
|||||||
constexpr int kThreadCount = kDefaultThreadCount;
|
constexpr int kThreadCount = kDefaultThreadCount;
|
||||||
std::atomic<bool> canStart(false);
|
std::atomic<bool> canStart(false);
|
||||||
std::atomic<int> readyCount(0);
|
std::atomic<int> readyCount(0);
|
||||||
std::vector<std::thread> threads;
|
KStdVector<std::thread> threads;
|
||||||
std::mutex expectedMutex;
|
std::mutex expectedMutex;
|
||||||
std::vector<ObjHeader*> expected;
|
KStdVector<ObjHeader*> expected;
|
||||||
|
|
||||||
for (int i = 0; i < kThreadCount; ++i) {
|
for (int i = 0; i < kThreadCount; ++i) {
|
||||||
threads.emplace_back([&typeInfo, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
|
threads.emplace_back([&typeInfo, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
|
||||||
@@ -564,7 +565,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto iter = objectFactory.Iter();
|
auto iter = objectFactory.Iter();
|
||||||
std::vector<ObjHeader*> actual;
|
KStdVector<ObjHeader*> actual;
|
||||||
for (auto it = iter.begin(); it != iter.end(); ++it) {
|
for (auto it = iter.begin(); it != iter.end(); ++it) {
|
||||||
actual.push_back(it.GetObjHeader());
|
actual.push_back(it.GetObjHeader());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public:
|
|||||||
void ProcessDeletions() noexcept;
|
void ProcessDeletions() noexcept;
|
||||||
|
|
||||||
// Lock registry for safe iteration.
|
// Lock registry for safe iteration.
|
||||||
// TODO: Iteration over `stableRefs_` will be slow, because it's `std::list` collected at different times from
|
// TODO: Iteration over `stableRefs_` will be slow, because it's `KStdList` collected at different times from
|
||||||
// different threads, and so the nodes are all over the memory. Use metrics to understand how
|
// different threads, and so the nodes are all over the memory. Use metrics to understand how
|
||||||
// much of a problem is it.
|
// much of a problem is it.
|
||||||
Iterable Iter() noexcept { return stableRefs_.Iter(); }
|
Iterable Iter() noexcept { return stableRefs_.Iter(); }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
@@ -22,7 +23,7 @@ public:
|
|||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
public:
|
public:
|
||||||
explicit Iterator(std::vector<ObjHeader*>::iterator iterator) : iterator_(iterator) {}
|
explicit Iterator(KStdVector<ObjHeader*>::iterator iterator) : iterator_(iterator) {}
|
||||||
|
|
||||||
ObjHeader** operator*() noexcept { return &*iterator_; }
|
ObjHeader** operator*() noexcept { return &*iterator_; }
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ public:
|
|||||||
bool operator!=(const Iterator& rhs) const noexcept { return iterator_ != rhs.iterator_; }
|
bool operator!=(const Iterator& rhs) const noexcept { return iterator_ != rhs.iterator_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<ObjHeader*>::iterator iterator_;
|
KStdVector<ObjHeader*>::iterator iterator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add TLS record. Can only be called before `Commit`.
|
// Add TLS record. Can only be called before `Commit`.
|
||||||
@@ -64,9 +65,9 @@ private:
|
|||||||
|
|
||||||
ObjHeader** Lookup(Entry entry, int index) noexcept;
|
ObjHeader** Lookup(Entry entry, int index) noexcept;
|
||||||
|
|
||||||
std::vector<ObjHeader*> storage_;
|
KStdVector<ObjHeader*> storage_;
|
||||||
// TODO: `std::unordered_map` is probably the wrong container here.
|
// TODO: `KStdUnorderedMap` is probably the wrong container here.
|
||||||
std::unordered_map<Key, Entry> map_;
|
KStdUnorderedMap<Key, Entry> map_;
|
||||||
State state_ = State::kBuilding;
|
State state_ = State::kBuilding;
|
||||||
int size_ = 0; // Only used in `State::kBuilding`
|
int size_ = 0; // Only used in `State::kBuilding`
|
||||||
std::pair<Key, Entry> lastKeyAndEntry_;
|
std::pair<Key, Entry> lastKeyAndEntry_;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -54,12 +56,12 @@ TEST(ThreadLocalStorageTest, Iterate) {
|
|||||||
tls.AddRecord(&key2, 2);
|
tls.AddRecord(&key2, 2);
|
||||||
tls.Commit();
|
tls.Commit();
|
||||||
|
|
||||||
std::vector<ObjHeader**> expected;
|
KStdVector<ObjHeader**> expected;
|
||||||
expected.push_back(tls.Lookup(&key1, 0));
|
expected.push_back(tls.Lookup(&key1, 0));
|
||||||
expected.push_back(tls.Lookup(&key2, 0));
|
expected.push_back(tls.Lookup(&key2, 0));
|
||||||
expected.push_back(tls.Lookup(&key2, 1));
|
expected.push_back(tls.Lookup(&key2, 1));
|
||||||
|
|
||||||
std::vector<ObjHeader**> actual;
|
KStdVector<ObjHeader**> actual;
|
||||||
for (auto item : tls) {
|
for (auto item : tls) {
|
||||||
actual.push_back(item);
|
actual.push_back(item);
|
||||||
}
|
}
|
||||||
@@ -78,12 +80,12 @@ TEST(ThreadLocalStorageTest, AddRecordEmpty) {
|
|||||||
tls.AddRecord(&key3, 2);
|
tls.AddRecord(&key3, 2);
|
||||||
tls.Commit();
|
tls.Commit();
|
||||||
|
|
||||||
std::vector<ObjHeader**> expected;
|
KStdVector<ObjHeader**> expected;
|
||||||
expected.push_back(tls.Lookup(&key1, 0));
|
expected.push_back(tls.Lookup(&key1, 0));
|
||||||
expected.push_back(tls.Lookup(&key3, 0));
|
expected.push_back(tls.Lookup(&key3, 0));
|
||||||
expected.push_back(tls.Lookup(&key3, 1));
|
expected.push_back(tls.Lookup(&key3, 1));
|
||||||
|
|
||||||
std::vector<ObjHeader**> actual;
|
KStdVector<ObjHeader**> actual;
|
||||||
for (auto item : tls) {
|
for (auto item : tls) {
|
||||||
actual.push_back(item);
|
actual.push_back(item);
|
||||||
}
|
}
|
||||||
@@ -99,10 +101,10 @@ TEST(ThreadLocalStorageTest, AddRecordSameSize) {
|
|||||||
tls.AddRecord(&key1, 1);
|
tls.AddRecord(&key1, 1);
|
||||||
tls.Commit();
|
tls.Commit();
|
||||||
|
|
||||||
std::vector<ObjHeader**> expected;
|
KStdVector<ObjHeader**> expected;
|
||||||
expected.push_back(tls.Lookup(&key1, 0));
|
expected.push_back(tls.Lookup(&key1, 0));
|
||||||
|
|
||||||
std::vector<ObjHeader**> actual;
|
KStdVector<ObjHeader**> actual;
|
||||||
for (auto item : tls) {
|
for (auto item : tls) {
|
||||||
actual.push_back(item);
|
actual.push_back(item);
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,7 @@ TEST(ThreadLocalStorageTest, NoRecords) {
|
|||||||
|
|
||||||
tls.Commit();
|
tls.Commit();
|
||||||
|
|
||||||
std::vector<ObjHeader**> actual;
|
KStdVector<ObjHeader**> actual;
|
||||||
for (auto item : tls) {
|
for (auto item : tls) {
|
||||||
actual.push_back(item);
|
actual.push_back(item);
|
||||||
}
|
}
|
||||||
@@ -130,7 +132,7 @@ TEST(ThreadLocalStorageTest, ClearEmpty) {
|
|||||||
|
|
||||||
tls.Clear();
|
tls.Clear();
|
||||||
|
|
||||||
std::vector<ObjHeader**> actual;
|
KStdVector<ObjHeader**> actual;
|
||||||
for (auto item : tls) {
|
for (auto item : tls) {
|
||||||
actual.push_back(item);
|
actual.push_back(item);
|
||||||
}
|
}
|
||||||
@@ -147,7 +149,7 @@ TEST(ThreadLocalStorageTest, ClearNonEmpty) {
|
|||||||
|
|
||||||
tls.Clear();
|
tls.Clear();
|
||||||
|
|
||||||
std::vector<ObjHeader**> actual;
|
KStdVector<ObjHeader**> actual;
|
||||||
for (auto item : tls) {
|
for (auto item : tls) {
|
||||||
actual.push_back(item);
|
actual.push_back(item);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user