[K/N] Refactor allocators ^KT-52130

Merge-request: KT-MR-6135
Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
Alexander Shabalin
2022-04-27 14:00:27 +00:00
committed by Space
parent a05bb3c77c
commit 663e843544
49 changed files with 1345 additions and 500 deletions
+3 -3
View File
@@ -5,7 +5,7 @@
## Headers
* Headers should live in the same folder with it's implementation counterpart (if there's one) **TODO**: This does not work with multiple implementations of a single header.
* Headers should use header guards
* Headers can use either `#pragma once` or header guards.
* Headers that are designed to be included into both `.cpp`/`.mm` and `.c`/`.m` should have extension `.h`
* Headers that are designed to be included into `.cpp`/`.mm` only should have extension `.hpp`
@@ -23,8 +23,8 @@
* 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.
* Use `std_support::*` containers and smart pointers instead of `std::*` ones. The former ones default to runtime-specific allocator.
* Use `new (std_support::kalloc) T(...)` (defined in `std_support/New.hpp` instead of `new T(...)` and `std_support::kdelete(ptr)` instead of `delete ptr`. The former ones use runtime-specific allocator.
## Naming
@@ -53,6 +53,7 @@
#include "Utils.hpp"
#include "WorkerBoundReference.h"
#include "Weak.h"
#include "std_support/New.hpp"
#ifdef KONAN_OBJC_INTEROP
#include "ObjCMMAPI.h"
@@ -75,6 +76,8 @@
#include <algorithm>
#endif
using namespace kotlin;
namespace {
ALWAYS_INLINE bool IsStrictMemoryModel() noexcept {
@@ -196,7 +199,7 @@ struct CycleDetectorRootset {
KStdVector<ScopedRefHolder> heldRefs;
};
class CycleDetector : private kotlin::Pinned, public KonanAllocatorAware {
class CycleDetector : private kotlin::Pinned {
public:
static void insertCandidateIfNeeded(KRef object) {
if (canBeACandidate(object))
@@ -216,7 +219,7 @@ class CycleDetector : private kotlin::Pinned, public KonanAllocatorAware {
static CycleDetector& instance() {
// Only store a pointer to CycleDetector in .bss
static CycleDetector* result = new CycleDetector();
static CycleDetector* result = new (std_support::kalloc) CycleDetector();
return *result;
}
+24 -129
View File
@@ -1,29 +1,32 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright 2010-2022 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_ALLOC_H
#define RUNTIME_ALLOC_H
#include <stddef.h>
#include <stdlib.h>
#pragma once
#include <cstddef>
#include <new>
#include <utility>
#include "Porting.h"
#include "std_support/CStdlib.hpp"
#include "std_support/New.hpp"
namespace konan {
inline void* calloc(size_t count, size_t size) {
return kotlin::std_support::calloc(count, size);
}
inline void* calloc_aligned(size_t count, size_t size, size_t alignment) {
return kotlin::std_support::aligned_calloc(alignment, count, size);
}
inline void free(void* ptr) {
kotlin::std_support::free(ptr);
}
} // namespace konan
inline void* konanAllocMemory(size_t size) {
return konan::calloc(1, size);
@@ -44,7 +47,7 @@ inline T* konanAllocArray(size_t length) {
template <typename T, typename ...A>
inline T* konanConstructInstance(A&& ...args) {
return new (konanAllocMemory(sizeof(T))) T(::std::forward<A>(args)...);
return new (kotlin::std_support::kalloc) T(std::forward<A>(args)...);
}
template <typename T, typename ...A>
@@ -54,113 +57,5 @@ inline T* konanConstructSizedInstance(size_t size, A&& ...args) {
template <typename T>
inline void konanDestructInstance(T* instance) {
instance->~T();
konanFreeMemory(instance);
kotlin::std_support::kdelete(instance);
}
template <class T> class KonanAllocator {
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
KonanAllocator() {}
KonanAllocator(const KonanAllocator&) {}
pointer allocate(size_type n, const void * = 0) {
return reinterpret_cast<T*>(konanAllocMemory(n * sizeof(T)));
}
void deallocate(void* p, size_type) {
if (p != nullptr) konanFreeMemory(p);
}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const { return &x; }
KonanAllocator<T>& operator=(const KonanAllocator&) { return *this; }
void construct(pointer p, const T& val) { new ((T*) p) T(val); }
// C++-11 wants that.
template <class U, class ...A>
void construct(U* const p, A&& ...args) {
new (p) U(::std::forward<A>(args)...);
}
void destroy(pointer p) { p->~T(); }
size_type max_size() const { return size_t(-1); }
template <class U>
struct rebind { typedef KonanAllocator<U> other; };
template <class U>
KonanAllocator(const KonanAllocator<U>&) {}
template <class U>
KonanAllocator& operator=(const KonanAllocator<U>&) { return *this; }
};
template <class T, class U>
bool operator==(
KonanAllocator<T> const&, KonanAllocator<U> const&) noexcept {
return true;
}
template <class T, class U>
bool operator!=(
KonanAllocator<T> const& x, KonanAllocator<U> const& y) noexcept {
return !(x == y);
}
template <class T>
class KonanDeleter {
public:
KonanDeleter() = default;
// This is needed for `KStdUniquePtr` covariance: if `U*` converts to `T*` then `KStdUniquePtr<U>` converts to `KStdUniquePtr<T>`.
template <class U, std::enable_if_t<std::is_convertible_v<U*, T*>, std::nullptr_t> = nullptr>
KonanDeleter(KonanDeleter<U>) {}
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
@@ -1,133 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "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_VARIABLE 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_VARIABLE 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();
}
TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) {
constexpr size_t kCount = 5;
// TODO: Consider removing support for placement new[] altogether, since there's no
// portable way to know needed storage size ahead of time.
alignas(A) std::array<uint8_t, sizeof(A) * kCount + sizeof(size_t)> 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();
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <mutex>
#include <optional>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "Utils.hpp"
#include "std_support/CStdlib.hpp"
#include "std_support/Map.hpp"
namespace kotlin::test_support {
class MockAllocatorCore : private Pinned {
public:
MOCK_METHOD(void*, allocate, (std::size_t), (noexcept));
MOCK_METHOD(void, deallocate, (void*, std::size_t), (noexcept));
};
class SpyAllocatorCore : private Pinned {
public:
SpyAllocatorCore() noexcept {
ON_CALL(*this, allocate(testing::_)).WillByDefault([](std::size_t size) { return std_support::malloc(size); });
ON_CALL(*this, deallocate(testing::_, testing::_)).WillByDefault([](void* ptr, std::size_t size) { std_support::free(ptr); });
}
MOCK_METHOD(void*, allocate, (std::size_t), (noexcept));
MOCK_METHOD(void, deallocate, (void*, std::size_t), (noexcept));
};
template <typename T, typename Core>
struct Allocator {
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::false_type;
explicit Allocator(Core& core) : core_(&core) {}
Allocator(const Allocator&) noexcept = default;
template <typename U>
Allocator(const Allocator<U, Core>& other) noexcept : core_(other.core_) {}
template <typename U>
Allocator& operator=(const Allocator<U, Core>& other) noexcept {
core_ = other.core_;
}
T* allocate(std::size_t n) noexcept { return static_cast<T*>(core_->allocate(sizeof(T) * n)); }
void deallocate(T* p, std::size_t n) noexcept { core_->deallocate(p, sizeof(T) * n); }
Core* core_;
};
template <typename T, typename Core>
auto MakeAllocator(Core& core) {
return Allocator<T, Core>(core);
}
template <typename T, typename U, typename Core>
bool operator==(const Allocator<T, Core>& lhs, const Allocator<U, Core>& rhs) noexcept {
return lhs.core_ == rhs.core_;
}
template <typename T, typename U, typename Core>
bool operator!=(const Allocator<T, Core>& lhs, const Allocator<U, Core>& rhs) noexcept {
return !(lhs == rhs);
}
} // namespace kotlin::test_support
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2022 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 "AllocatorTestSupport.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using namespace kotlin;
namespace {
struct EmptyClass {};
struct Class {
int32_t x;
};
static_assert(sizeof(Class) > sizeof(EmptyClass));
} // namespace
TEST(AllocatorTestSupportTest, MockAllocate) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
auto allocator = test_support::MakeAllocator<Class>(allocatorCore);
auto* expectedPtr = reinterpret_cast<Class*>(13);
EXPECT_CALL(allocatorCore, allocate(2 * sizeof(Class))).WillOnce(testing::Return(expectedPtr));
auto* ptr = std::allocator_traits<decltype(allocator)>::allocate(allocator, 2);
EXPECT_THAT(ptr, expectedPtr);
}
TEST(AllocatorTestSupportTest, MockDeallocate) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
auto allocator = test_support::MakeAllocator<Class>(allocatorCore);
auto* ptr = reinterpret_cast<Class*>(13);
EXPECT_CALL(allocatorCore, deallocate(ptr, 2 * sizeof(Class)));
std::allocator_traits<decltype(allocator)>::deallocate(allocator, ptr, 2);
}
TEST(AllocatorTestSupportTest, MockAdjustType) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
auto initial = test_support::MakeAllocator<EmptyClass>(allocatorCore);
using Allocator = std::allocator_traits<decltype(initial)>::template rebind_alloc<Class>;
using Traits = std::allocator_traits<decltype(initial)>::template rebind_traits<Class>;
Allocator allocator = Allocator(initial);
auto* expectedPtr = reinterpret_cast<Class*>(13);
EXPECT_CALL(allocatorCore, allocate(2 * sizeof(Class))).WillOnce(testing::Return(expectedPtr));
auto* ptr = Traits::allocate(allocator, 2);
EXPECT_THAT(ptr, expectedPtr);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
EXPECT_CALL(allocatorCore, deallocate(ptr, 2 * sizeof(Class)));
Traits::deallocate(allocator, ptr, 2);
}
TEST(AllocatorTestSupportTest, Spy) {
test_support::SpyAllocatorCore allocatorCore;
auto allocator = test_support::MakeAllocator<Class>(allocatorCore);
EXPECT_CALL(allocatorCore, allocate(2 * sizeof(Class)));
auto* ptr1 = std::allocator_traits<decltype(allocator)>::allocate(allocator, 2);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
using Allocator = std::allocator_traits<decltype(allocator)>::template rebind_alloc<EmptyClass>;
using Traits = std::allocator_traits<decltype(allocator)>::template rebind_traits<EmptyClass>;
Allocator b = Allocator(allocator);
EXPECT_CALL(allocatorCore, allocate(2 * sizeof(EmptyClass)));
auto* ptr2 = Traits::allocate(b, 2);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
EXPECT_CALL(allocatorCore, deallocate(ptr1, 2 * sizeof(Class)));
std::allocator_traits<decltype(allocator)>::deallocate(allocator, ptr1, 2);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
EXPECT_CALL(allocatorCore, deallocate(ptr2, 2 * sizeof(EmptyClass)));
Traits::deallocate(b, ptr2, 2);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
}
@@ -1,17 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#ifndef RUNTIME_CPP_SUPPORT_H
#define RUNTIME_CPP_SUPPORT_H
// A collection of backported utilities from future C++ versions.
namespace kotlin {
namespace std_support {
} // namespace std_support
} // namespace kotlin
#endif // RUNTIME_CPP_SUPPORT_H
@@ -26,6 +26,7 @@
#include "ExecFormat.h"
#include "Memory.h"
#include "Mutex.hpp"
#include "Porting.h"
#include "Types.h"
#include "Utils.hpp"
#include "ObjCExceptions.h"
@@ -16,6 +16,7 @@
#include "ExecFormat.h"
#include "Porting.h"
#include "Types.h"
#if USE_ELF_SYMBOLS
+1 -1
View File
@@ -8,7 +8,7 @@
#include <cstdarg>
#include "cpp_support/Span.hpp"
#include "std_support/Span.hpp"
namespace kotlin {
@@ -10,7 +10,7 @@
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "cpp_support/Span.hpp"
#include "std_support/Span.hpp"
using namespace kotlin;
@@ -8,8 +8,9 @@
#include <array>
#include <cstdarg>
#include "cpp_support/Span.hpp"
#include "std_support/Span.hpp"
#include "Format.h"
#include "Porting.h"
#include "StackTrace.hpp"
using namespace kotlin;
@@ -22,6 +22,7 @@ inline constexpr auto nullopt = std::experimental::nullopt;
#include "Format.h"
#include "KAssert.h"
#include "Porting.h"
using namespace kotlin;
@@ -22,7 +22,7 @@ using string_view = std::experimental::string_view;
#endif
#include "CompilerConstants.hpp"
#include "cpp_support/Span.hpp"
#include "std_support/Span.hpp"
#include "Types.h"
namespace kotlin {
@@ -13,18 +13,25 @@
#include "Mutex.hpp"
#include "Types.h"
#include "Utils.hpp"
#include "std_support/List.hpp"
#include "std_support/Memory.hpp"
namespace kotlin {
// A queue that is constructed by collecting subqueues from several `Producer`s.
template <typename T, typename Mutex>
template <typename T, typename Mutex, typename Allocator = std_support::allocator<T>>
class MultiSourceQueue {
// Using `std_support::list` as it allows to implement `Collect` without memory allocations,
// which is important for GC mark phase.
template <typename U>
using List = std_support::list<U, typename std::allocator_traits<Allocator>::template rebind_alloc<U>>;
public:
class Producer;
// TODO: Consider switching from `KStdList` to `SingleLockList` to hide the constructor
// and to not store the iterator.
class Node : private Pinned, public KonanAllocatorAware {
class Node : private Pinned {
public:
Node(Producer* owner, const T& value) noexcept : value_(value), owner_(owner) {}
@@ -43,12 +50,13 @@ public:
T value_;
std::atomic<Producer*> owner_; // `nullptr` signifies that `MultiSourceQueue` owns it.
typename KStdList<Node>::iterator position_;
typename List<Node>::iterator position_;
};
class Producer {
public:
explicit Producer(MultiSourceQueue& owner) noexcept : owner_(owner) {}
explicit Producer(MultiSourceQueue& owner) noexcept :
owner_(owner), queue_(owner.queue_.get_allocator()), deletionQueue_(owner.deletionQueue_.get_allocator()) {}
~Producer() { Publish(); }
@@ -84,7 +92,9 @@ public:
node.owner_ = nullptr;
}
std::lock_guard<Mutex> guard(owner_.mutex_);
RuntimeAssert(owner_.queue_.get_allocator() == queue_.get_allocator(), "queue_ allocators must match");
owner_.queue_.splice(owner_.queue_.end(), queue_);
RuntimeAssert(owner_.deletionQueue_.get_allocator() == deletionQueue_.get_allocator(), "deletionQueue_ allocators must match");
owner_.deletionQueue_.splice(owner_.deletionQueue_.end(), deletionQueue_);
}
@@ -95,8 +105,8 @@ public:
private:
MultiSourceQueue& owner_; // weak
KStdList<Node> queue_;
KStdList<Node*> deletionQueue_;
List<Node> queue_;
List<Node*> deletionQueue_;
};
class Iterator {
@@ -115,9 +125,9 @@ public:
private:
friend class MultiSourceQueue;
explicit Iterator(const typename KStdList<Node>::iterator& position) noexcept : position_(position) {}
explicit Iterator(const typename List<Node>::iterator& position) noexcept : position_(position) {}
typename KStdList<Node>::iterator position_;
typename List<Node>::iterator position_;
};
class Iterable : MoveOnly {
@@ -134,6 +144,8 @@ public:
std::unique_lock<Mutex> guard_;
};
explicit MultiSourceQueue(const Allocator& allocator = Allocator()) noexcept : queue_(allocator), deletionQueue_(allocator) {}
// Lock `MultiSourceQueue` for safe iteration. If element was scheduled for deletion,
// it'll still be iterated. Use `ApplyDeletions` to remove those elements.
Iterable LockForIter() noexcept { return Iterable(*this); }
@@ -141,7 +153,7 @@ public:
// Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published.
void ApplyDeletions() noexcept {
std::lock_guard<Mutex> guard(mutex_);
KStdList<Node*> remainingDeletions;
List<Node*> remainingDeletions(deletionQueue_.get_allocator());
auto it = deletionQueue_.begin();
while (it != deletionQueue_.end()) {
@@ -176,10 +188,8 @@ public:
}
private:
// Using `KStdList` as it allows to implement `Collect` without memory allocations,
// which is important for GC mark phase.
KStdList<Node> queue_;
KStdList<Node*> deletionQueue_;
List<Node> queue_;
List<Node*> deletionQueue_;
Mutex mutex_;
};
@@ -10,12 +10,15 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "AllocatorTestSupport.hpp"
#include "ScopedThread.hpp"
#include "TestSupport.hpp"
#include "Types.h"
using namespace kotlin;
using ::testing::_;
namespace {
template <typename T, typename Mutex>
@@ -312,3 +315,42 @@ TEST(MultiSourceQueueTest, ConcurrentPublishAndApplyDeletions) {
auto actual = Collect(queue);
EXPECT_THAT(actual, testing::IsEmpty());
}
TEST(MultiSourceQueueTest, CustomAllocator) {
testing::StrictMock<test_support::SpyAllocatorCore> allocatorCore;
auto allocator = test_support::MakeAllocator<int>(allocatorCore);
using Queue = MultiSourceQueue<int, SpinLock<MutexThreadStateHandling::kIgnore>, decltype(allocator)>;
Queue queue(allocator);
Queue::Producer producer1(queue);
Queue::Producer producer2(queue);
EXPECT_CALL(allocatorCore, allocate(_)).Times(5);
auto* node11 = producer1.Insert(1);
auto* node12 = producer1.Insert(2);
auto* node21 = producer2.Insert(10);
auto* node22 = producer2.Insert(20);
auto* node23 = producer2.Insert(30);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
EXPECT_CALL(allocatorCore, deallocate(_, _));
producer2.Erase(node22);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
producer1.Publish();
producer2.Publish();
EXPECT_CALL(allocatorCore, allocate(_)).Times(4);
producer1.Erase(node11);
producer1.Erase(node23);
producer2.Erase(node12);
producer2.Erase(node21);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
producer1.Publish();
producer2.Publish();
EXPECT_CALL(allocatorCore, deallocate(_, _)).Times(8);
queue.ApplyDeletions();
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
}
@@ -25,6 +25,7 @@
#include "KString.h"
#include "StackTrace.hpp"
#include "Memory.h"
#include "Porting.h"
#include "Natives.h"
#include "Types.h"
+5 -97
View File
@@ -36,6 +36,9 @@
#include "CompilerConstants.hpp"
#include "Porting.h"
#include "KAssert.h"
#include "std_support/CStdlib.hpp"
using namespace kotlin;
#if KONAN_WASM || KONAN_ZEPHYR
extern "C" RUNTIME_NORETURN void Konan_abort(const char*);
@@ -205,7 +208,7 @@ static void onThreadExitCallback(void* value) {
while (record != nullptr) {
record->destructor(record->destructorParameter);
auto next = record->next;
free(record);
std_support::free(record);
record = next;
}
}
@@ -242,7 +245,7 @@ void onThreadExit(void (*destructor)(void*), void* destructorParameter) {
#else // !KONAN_NO_THREADS
// We cannot use pthread_cleanup_push() as it is lexical scope bound.
pthread_once(&terminationKeyOnceControl, onThreadExitInit);
DestructorRecord* destructorRecord = (DestructorRecord*)calloc(1, sizeof(DestructorRecord));
DestructorRecord* destructorRecord = (DestructorRecord*)std_support::calloc(1, sizeof(DestructorRecord));
destructorRecord->destructor = destructor;
destructorRecord->destructorParameter = destructorParameter;
destructorRecord->next =
@@ -332,35 +335,6 @@ size_t strnlen(const char* buffer, size_t maxSize) {
return ::strnlen(buffer, maxSize);
}
// Memory operations.
#if KONAN_INTERNAL_DLMALLOC
extern "C" void* dlcalloc(size_t, size_t);
extern "C" void dlfree(void*);
#define calloc_impl dlcalloc
#define free_impl dlfree
#define calloc_aligned_impl(count, size, alignment) dlcalloc(count, size)
#else
extern "C" void* konan_calloc_impl(size_t, size_t);
extern "C" void konan_free_impl(void*);
extern "C" void* konan_calloc_aligned_impl(size_t count, size_t size, size_t alignment);
#define calloc_impl konan_calloc_impl
#define free_impl konan_free_impl
#define calloc_aligned_impl konan_calloc_aligned_impl
#endif
void* calloc(size_t count, size_t size) {
return calloc_impl(count, size);
}
void* calloc_aligned(size_t count, size_t size, size_t alignment) {
return calloc_aligned_impl(count, size, alignment);
}
void free(void* pointer) {
free_impl(pointer);
}
#if KONAN_INTERNAL_NOW
#ifdef KONAN_ZEPHYR
@@ -406,72 +380,6 @@ uint64_t getTimeMicros() {
}
#endif
#if KONAN_INTERNAL_DLMALLOC
// This function is being called when memory allocator needs more RAM.
#if KONAN_WASM
namespace {
constexpr uint32_t MFAIL = ~(uint32_t)0;
constexpr uint32_t WASM_PAGESIZE_EXPONENT = 16;
constexpr uint32_t WASM_PAGESIZE = 1u << WASM_PAGESIZE_EXPONENT;
constexpr uint32_t WASM_PAGEMASK = WASM_PAGESIZE-1;
uint32_t pageAlign(int32_t value) {
return (value + WASM_PAGEMASK) & ~ (WASM_PAGEMASK);
}
uint32_t inBytes(uint32_t pageCount) {
return pageCount << WASM_PAGESIZE_EXPONENT;
}
uint32_t inPages(uint32_t value) {
return value >> WASM_PAGESIZE_EXPONENT;
}
extern "C" void Konan_notify_memory_grow();
uint32_t memorySize() {
return __builtin_wasm_memory_size(0);
}
int32_t growMemory(uint32_t delta) {
int32_t oldLength = __builtin_wasm_memory_grow(0, delta);
Konan_notify_memory_grow();
return oldLength;
}
}
void* moreCore(int32_t delta) {
uint32_t top = inBytes(memorySize());
if (delta > 0) {
if (growMemory(inPages(pageAlign(delta))) == 0) {
return (void *) MFAIL;
}
} else if (delta < 0) {
return (void *) MFAIL;
}
return (void *) top;
}
// dlmalloc() wants to know the page size.
long getpagesize() {
return WASM_PAGESIZE;
}
#else
void* moreCore(int size) {
return sbrk(size);
}
long getpagesize() {
return sysconf(_SC_PAGESIZE);
}
#endif
#endif
} // namespace konan
extern "C" {
@@ -79,11 +79,6 @@ void *memset(void *b, int c, size_t len);
#endif
}
// Memory operations.
void* calloc(size_t count, size_t size);
void* calloc_aligned(size_t count, size_t size, size_t alignment);
void free(void* ptr);
// Time operations.
uint64_t getTimeMillis();
uint64_t getTimeMicros();
@@ -12,41 +12,36 @@
#include <mutex>
#include <type_traits>
#include "Alloc.h"
#include "Mutex.hpp"
#include "Types.h"
#include "Utils.hpp"
#include "std_support/Memory.hpp"
namespace kotlin {
// TODO: Consider different locking mechanisms.
template <typename Value, typename Mutex>
template <typename Value, typename Mutex, typename Allocator = std_support::allocator<Value>>
class SingleLockList : private Pinned {
public:
class Node;
private:
class NodeDeleter {
public:
void operator()(Node* node) const { delete node; }
};
using NodeOwner = std::unique_ptr<Node, NodeDeleter>;
using NodeAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<Node>;
using NodeOwner = std_support::unique_ptr<Node, std_support::allocator_deleter<Node, NodeAllocator>>;
public:
class Node : private Pinned, public KonanAllocatorAware {
// TODO: Maybe just hide `Node` altogether?
class Node : private Pinned {
public:
template <typename... Args>
explicit Node(const Allocator& allocator, Args&&... args) noexcept :
value_(std::forward<Args>(args)...), next_(std_support::nullptr_unique<Node>(allocator)) {}
Value* Get() noexcept { return &value_; }
private:
friend class SingleLockList;
template <typename... Args>
Node(Args&&... args) noexcept : value_(std::forward<Args>(args)...) {}
// Make sure `Node` can only be deleted by `SingleLockList` itself.
~Node() = default;
Value value_;
NodeOwner next_;
Node* previous_ = nullptr; // weak
@@ -90,6 +85,11 @@ public:
std::unique_lock<Mutex> guard_;
};
SingleLockList() noexcept = default;
explicit SingleLockList(const Allocator& allocator) noexcept :
allocator_(allocator), root_(std_support::nullptr_unique<Node, NodeAllocator>(allocator)) {}
~SingleLockList() {
AssertCorrectUnsafe();
// Make sure not to blow up the stack by nested `~Node` calls.
@@ -102,12 +102,12 @@ public:
// 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);
auto node = std_support::allocate_unique<Node>(allocator_, allocator_, std::forward<Args>(args)...);
auto* nodePtr = node.get();
std::lock_guard<Mutex> guard(mutex_);
AssertCorrectUnsafe();
if (root_) {
root_->previous_ = node.get();
root_->previous_ = nodePtr;
} else {
last_ = nodePtr;
}
@@ -125,7 +125,8 @@ public:
last_ = node->previous_;
}
if (root_.get() == node) {
root_ = std::move(node->next_);
auto next = std::move(node->next_);
root_ = std::move(next);
if (root_) {
root_->previous_ = nullptr;
}
@@ -165,6 +166,7 @@ private:
}
}
[[no_unique_address]] NodeAllocator allocator_;
NodeOwner root_;
Node* last_ = nullptr;
Mutex mutex_;
@@ -11,12 +11,15 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "AllocatorTestSupport.hpp"
#include "ScopedThread.hpp"
#include "TestSupport.hpp"
#include "Types.h"
using namespace kotlin;
using ::testing::_;
namespace {
using IntList = SingleLockList<int, SpinLock<MutexThreadStateHandling::kIgnore>>;
@@ -417,3 +420,26 @@ TEST(SingleLockListTest, Destructor) {
}
}
}
TEST(SingleLockListTest, CustomAllocator) {
testing::StrictMock<test_support::SpyAllocatorCore> allocatorCore;
auto allocator = test_support::MakeAllocator<int>(allocatorCore);
SingleLockList<int, SpinLock<MutexThreadStateHandling::kIgnore>, decltype(allocator)> list(allocator);
EXPECT_CALL(allocatorCore, allocate(_)).Times(3);
auto* node1 = list.Emplace(1);
auto* node2 = list.Emplace(2);
auto* node3 = list.Emplace(3);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
{
testing::InSequence seq;
EXPECT_CALL(allocatorCore, deallocate(node1, _));
EXPECT_CALL(allocatorCore, deallocate(node2, _));
EXPECT_CALL(allocatorCore, deallocate(node3, _));
}
list.Erase(node1);
list.Erase(node2);
list.Erase(node3);
testing::Mock::VerifyAndClearExpectations(&allocatorCore);
}
@@ -168,7 +168,7 @@ NO_INLINE size_t kotlin::internal::GetCurrentStackTrace(size_t skipFrames, std_s
#if ! KONAN_NO_BACKTRACE
#include <cstdarg>
#include <cstring>
#include "cpp_support/Span.hpp"
#include "std_support/Span.hpp"
#include "Format.h"
#if __has_include("dlfcn.h")
@@ -6,7 +6,7 @@
#ifndef RUNTIME_STACK_TRACE_H
#define RUNTIME_STACK_TRACE_H
#include "cpp_support/Span.hpp"
#include "std_support/Span.hpp"
#include "Memory.h"
#include "Types.h"
+28 -45
View File
@@ -19,25 +19,19 @@
#include <stdlib.h>
#if (KONAN_WASM || KONAN_ZEPHYR) && !defined(assert)
// assert() is needed by STLport.
#define assert(cond) if (!(cond)) abort()
#endif
#include <deque>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "Alloc.h"
#include "Common.h"
#include "Memory.h"
#include "TypeInfo.h"
#include "std_support/Deque.hpp"
#include "std_support/List.hpp"
#include "std_support/Map.hpp"
#include "std_support/Memory.hpp"
#include "std_support/Set.hpp"
#include "std_support/String.hpp"
#include "std_support/UnorderedMap.hpp"
#include "std_support/UnorderedSet.hpp"
#include "std_support/Vector.hpp"
// Note that almost all types are signed.
typedef bool KBoolean;
@@ -61,36 +55,25 @@ typedef ObjHeader* KRef;
typedef const ObjHeader* KConstRef;
typedef const ArrayHeader* KString;
// TODO: Consider moving these into `kotlin::std_support` namespace keeping STL names.
// Definitions of STL classes used inside Konan runtime.
typedef std::basic_string<char, std::char_traits<char>,
KonanAllocator<char>> KStdString;
template<class Value>
using KStdDeque = std::deque<Value, KonanAllocator<Value>>;
template<class Key, class Value>
using KStdUnorderedMap = std::unordered_map<Key, Value,
std::hash<Key>, std::equal_to<Key>,
KonanAllocator<std::pair<const Key, Value>>>;
template<class Value>
using KStdUnorderedSet = std::unordered_set<Value,
std::hash<Value>, std::equal_to<Value>,
KonanAllocator<Value>>;
template<class Value, class Compare = std::less<Value>>
using KStdOrderedMultiset = std::multiset<Value, Compare, KonanAllocator<Value>>;
template<class Key, class Value, class Compare = std::less<Key>>
using KStdOrderedMap = std::map<Key, Value, Compare, KonanAllocator<std::pair<const Key, Value>>>;
template<class Value>
using KStdVector = std::vector<Value, KonanAllocator<Value>>;
template<class 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)...));
}
// TODO: Remove these typedefs. Use std_support directly everywhere.
using KStdString = kotlin::std_support::string;
template <typename Value>
using KStdDeque = kotlin::std_support::deque<Value>;
template <typename Key, typename Value>
using KStdUnorderedMap = kotlin::std_support::unordered_map<Key, Value>;
template <typename Value>
using KStdUnorderedSet = kotlin::std_support::unordered_set<Value>;
template <typename Value, typename Compare = std::less<Value>>
using KStdOrderedMultiset = kotlin::std_support::multiset<Value, Compare>;
template <typename Key, typename Value>
using KStdOrderedMap = kotlin::std_support::map<Key, Value>;
template <typename Value>
using KStdVector = kotlin::std_support::vector<Value>;
template <typename Value>
using KStdList = kotlin::std_support::list<Value>;
template <typename Value>
using KStdUniquePtr = kotlin::std_support::unique_ptr<Value>;
using kotlin::std_support::make_unique;
#ifdef __cplusplus
extern "C" {
@@ -23,6 +23,7 @@
#include "../Exceptions.h"
#include "../KString.h"
#include "../Natives.h"
#include "../Porting.h"
#include "../utf8.h"
#include "../KotlinMath.h"
#include "../ReturnSlot.h"
@@ -23,6 +23,7 @@
#include "../Exceptions.h"
#include "../KString.h"
#include "../Natives.h"
#include "../Porting.h"
#include "../utf8.h"
#if defined(LINUX) || defined(FREEBSD) || defined(MACOSX) || defined(ZOS) || defined(AIX)
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2022 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 "std_support/CStdlib.hpp"
#include <cstdint>
#include <unistd.h>
using namespace kotlin;
#if KONAN_INTERNAL_DLMALLOC
extern "C" void* dlmalloc(size_t);
extern "C" void* dlcalloc(size_t, size_t);
extern "C" void* dlrealloc(void*, size_t);
extern "C" void dlfree(void*);
#define malloc_impl dlmalloc
#define aligned_alloc_impl(alignment, size) dlmalloc(size)
#define calloc_impl dlcalloc
#define aligned_calloc_impl(alignment, num, size) dlcalloc(num, size)
#define realloc_impl dlrealloc
#define free_impl dlfree
#else
extern "C" void* konan_malloc_impl(size_t);
extern "C" void* konan_aligned_alloc_impl(size_t, size_t);
extern "C" void* konan_calloc_impl(size_t, size_t);
extern "C" void* konan_aligned_calloc_impl(size_t, size_t, size_t);
extern "C" void* konan_realloc_impl(void*, size_t);
extern "C" void konan_free_impl(void*);
#define malloc_impl konan_malloc_impl
#define aligned_alloc_impl konan_aligned_alloc_impl
#define calloc_impl konan_calloc_impl
#define aligned_calloc_impl konan_aligned_calloc_impl
#define realloc_impl konan_realloc_impl
#define free_impl konan_free_impl
#endif
void* std_support::malloc(std::size_t size) noexcept {
return malloc_impl(size);
}
void* std_support::aligned_alloc(std::size_t alignment, std::size_t size) noexcept {
return aligned_alloc_impl(alignment, size);
}
void* std_support::calloc(std::size_t num, std::size_t size) noexcept {
return calloc_impl(num, size);
}
void* std_support::aligned_calloc(std::size_t alignment, std::size_t num, std::size_t size) noexcept {
return aligned_calloc_impl(alignment, num, size);
}
void* std_support::realloc(void* ptr, std::size_t size) noexcept {
return realloc_impl(ptr, size);
}
void std_support::free(void* ptr) noexcept {
return free_impl(ptr);
}
namespace konan {
#if KONAN_INTERNAL_DLMALLOC
// This function is being called when memory allocator needs more RAM.
#if KONAN_WASM
namespace {
constexpr uint32_t MFAIL = ~(uint32_t)0;
constexpr uint32_t WASM_PAGESIZE_EXPONENT = 16;
constexpr uint32_t WASM_PAGESIZE = 1u << WASM_PAGESIZE_EXPONENT;
constexpr uint32_t WASM_PAGEMASK = WASM_PAGESIZE - 1;
uint32_t pageAlign(int32_t value) {
return (value + WASM_PAGEMASK) & ~(WASM_PAGEMASK);
}
uint32_t inBytes(uint32_t pageCount) {
return pageCount << WASM_PAGESIZE_EXPONENT;
}
uint32_t inPages(uint32_t value) {
return value >> WASM_PAGESIZE_EXPONENT;
}
extern "C" void Konan_notify_memory_grow();
uint32_t memorySize() {
return __builtin_wasm_memory_size(0);
}
int32_t growMemory(uint32_t delta) {
int32_t oldLength = __builtin_wasm_memory_grow(0, delta);
Konan_notify_memory_grow();
return oldLength;
}
} // namespace
void* moreCore(int32_t delta) {
uint32_t top = inBytes(memorySize());
if (delta > 0) {
if (growMemory(inPages(pageAlign(delta))) == 0) {
return (void*)MFAIL;
}
} else if (delta < 0) {
return (void*)MFAIL;
}
return (void*)top;
}
// dlmalloc() wants to know the page size.
long getpagesize() {
return WASM_PAGESIZE;
}
#else
void* moreCore(int size) {
return sbrk(size);
}
long getpagesize() {
return sysconf(_SC_PAGESIZE);
}
#endif
#endif
} // namespace konan
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <cstddef>
namespace kotlin::std_support {
void* malloc(std::size_t size) noexcept;
void* aligned_alloc(std::size_t alignment, std::size_t size) noexcept;
void* calloc(std::size_t num, std::size_t size) noexcept;
void* aligned_calloc(std::size_t alignment, std::size_t num, std::size_t size) noexcept;
void* realloc(void* ptr, std::size_t size) noexcept;
void free(void* ptr) noexcept;
} // namespace kotlin::std_support
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <deque>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename T, typename Allocator = allocator<T>>
using deque = std::deque<T, Allocator>;
} // namespace kotlin::std_support
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <forward_list>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename T, typename Allocator = allocator<T>>
using forward_list = std::forward_list<T, Allocator>;
} // namespace kotlin::std_support
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <list>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename T, typename Allocator = allocator<T>>
using list = std::list<T, Allocator>;
} // namespace kotlin::std_support
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <map>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename Key, typename T, typename Compare = std::less<Key>, typename Allocator = allocator<std::pair<const Key, T>>>
using map = std::map<Key, T, Compare, Allocator>;
template <typename Key, typename T, typename Compare = std::less<Key>, typename Allocator = allocator<std::pair<const Key, T>>>
using multimap = std::multimap<Key, T, Compare, Allocator>;
} // namespace kotlin::std_support
@@ -0,0 +1,150 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <cstddef>
#include <memory>
#include <type_traits>
#include "std_support/CStdlib.hpp"
namespace kotlin::std_support {
// Default allocator for Kotlin.
// TODO: Consider overriding global operator new and operator delete instead. However, make sure this does
// not extend over to interop.
template <typename T>
struct allocator {
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
allocator() noexcept = default;
allocator(const allocator&) noexcept = default;
template <typename U>
allocator(const allocator<U>&) noexcept {}
// TODO: maybe malloc, actually?
T* allocate(std::size_t n) noexcept { return static_cast<T*>(std_support::calloc(n, sizeof(T))); }
void deallocate(T* p, std::size_t n) noexcept { std_support::free(p); }
};
template <typename T, typename U>
bool operator==(const allocator<T>&, const allocator<U>&) noexcept {
return true;
}
template <typename T, typename U>
bool operator!=(const allocator<T>&, const allocator<U>&) noexcept {
return false;
}
template <typename T, typename Allocator, typename... Args>
T* allocator_new(const Allocator& allocator, Args&&... args) {
static_assert(!std::is_array_v<T>, "T cannot be an array");
using TAllocatorTraits = typename std::allocator_traits<Allocator>::template rebind_traits<T>;
using TAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
auto a = TAllocator(allocator);
T* ptr = TAllocatorTraits::allocate(a, 1);
#if KONAN_NO_EXCEPTIONS
TAllocatorTraits::construct(a, ptr, std::forward<Args>(args)...);
return ptr;
#else
try {
TAllocatorTraits::construct(a, ptr, std::forward<Args>(args)...);
return ptr;
} catch (...) {
TAllocatorTraits::deallocate(a, ptr, 1);
throw;
}
#endif
}
template <typename T, typename Allocator>
void allocator_delete(const Allocator& allocator, T* ptr) noexcept {
static_assert(!std::is_array_v<T>, "T cannot be an array");
using TAllocatorTraits = typename std::allocator_traits<Allocator>::template rebind_traits<T>;
using TAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
auto a = TAllocator(allocator);
TAllocatorTraits::destroy(a, ptr);
TAllocatorTraits::deallocate(a, ptr, 1);
}
template <typename T, typename Allocator>
class allocator_deleter {
static_assert(!std::is_array_v<T>, "T cannot be an array");
template <typename A, typename V>
using Rebind = typename std::allocator_traits<A>::template rebind_alloc<V>;
template <typename A1, typename A2>
static inline constexpr bool allocatorsCompatible = std::is_same_v<Rebind<A1, T>, Rebind<A2, T>>;
public:
allocator_deleter() noexcept = default;
explicit allocator_deleter(const Allocator& allocator) noexcept : allocator(allocator) {}
template <
typename U,
typename Other,
typename = std::enable_if_t<std::is_convertible_v<U*, T*> && allocatorsCompatible<Allocator, Other>>>
allocator_deleter(const allocator_deleter<U, Other>& rhs) noexcept : allocator(rhs.allocator) {}
void operator()(T* ptr) noexcept { allocator_delete(allocator, ptr); }
[[no_unique_address]] Allocator allocator;
};
template <typename T, typename Allocator, typename... Args>
auto allocate_unique(const Allocator& allocator, Args&&... args) {
static_assert(!std::is_array_v<T>, "T cannot be an array");
using TAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
using TDeleter = allocator_deleter<T, TAllocator>;
return std::unique_ptr<T, TDeleter>(allocator_new<T>(allocator, std::forward<Args>(args)...), TDeleter(allocator));
}
template <typename T>
using default_delete = allocator_deleter<T, allocator<T>>;
template <typename T, typename Deleter = default_delete<T>>
using unique_ptr = std::unique_ptr<T, Deleter>;
template <typename T, typename... Args>
auto make_unique(Args&&... args) {
static_assert(!std::is_array_v<T>, "T cannot be an array");
return allocate_unique<T>(allocator<T>(), std::forward<Args>(args)...);
}
template <typename T, typename... Args>
auto make_shared(Args&&... args) {
static_assert(!std::is_array_v<T>, "T cannot be an array");
return std::allocate_shared<T>(allocator<T>(), std::forward<Args>(args)...);
}
template <typename T, typename Allocator>
auto nullptr_unique(const Allocator& allocator = Allocator()) noexcept {
static_assert(!std::is_array_v<T>, "T cannot be an array");
using TAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
using TDeleter = allocator_deleter<T, TAllocator>;
return std::unique_ptr<T, TDeleter>(nullptr, TDeleter(allocator));
}
} // namespace kotlin::std_support
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2022 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 "std_support/New.hpp"
#include "std_support/CStdlib.hpp"
using namespace kotlin;
// TODO: Maybe malloc instead of calloc?
void* operator new(std::size_t count, kotlin::std_support::kalloc_t) noexcept {
return std_support::calloc(1, count);
}
void* operator new[](std::size_t count, kotlin::std_support::kalloc_t) noexcept {
return std_support::calloc(1, count);
}
void operator delete(void* ptr, kotlin::std_support::kalloc_t) noexcept {
std_support::free(ptr);
}
void operator delete[](void* ptr, kotlin::std_support::kalloc_t) noexcept {
std_support::free(ptr);
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <new>
namespace kotlin::std_support {
struct kalloc_t {};
inline constexpr kalloc_t kalloc = kotlin::std_support::kalloc_t{};
} // namespace kotlin::std_support
// TODO: Add align_val_t overloads once we make sure all targets support aligned allocation.
// (also requires removing `-fno-aligned-allocation` compiler flag).
void* operator new(std::size_t count, kotlin::std_support::kalloc_t) noexcept;
void* operator new[](std::size_t count, kotlin::std_support::kalloc_t) noexcept;
void operator delete(void* ptr, kotlin::std_support::kalloc_t) noexcept;
void operator delete[](void* ptr, kotlin::std_support::kalloc_t) noexcept;
namespace kotlin::std_support {
template <typename T>
void kdelete(T* ptr) noexcept {
ptr->~T();
::operator delete(ptr, kalloc);
}
} // namespace kotlin::std_support
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2022 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 "std_support/New.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using namespace kotlin;
namespace {
class Class {
public:
explicit Class(int32_t x = 17) : x_(x) {}
int32_t x() const { return x_; }
private:
int32_t x_;
};
class ClassThrows {
public:
explicit ClassThrows(int32_t x = 17) : x_(x) { throw 13; }
int32_t x() const { return x_; }
private:
int32_t x_;
};
} // namespace
TEST(NewTest, NewDelete) {
Class* ptr = new (std_support::kalloc) Class(42);
EXPECT_THAT(ptr->x(), 42);
std_support::kdelete(ptr);
}
TEST(NewTest, NewDeleteArray) {
Class* ptr = new (std_support::kalloc) Class[13];
EXPECT_THAT(ptr[3].x(), 17);
std_support::kdelete(ptr);
}
TEST(NewTest, NewThrows) {
EXPECT_THROW(new (std_support::kalloc) ClassThrows(42), int);
}
TEST(NewTest, NewThrowsArray) {
EXPECT_THROW(new (std_support::kalloc) ClassThrows[13], int);
}
@@ -0,0 +1,27 @@
## Support for C++ standard library.
This contains backported utilities from future standards, implementation of not-yet-standardized proposals, and adjustments of standardized utilities to be used within Kotlin/Native runtime.
Everything here lives in `kotlin::std_support` namespace.
Backporting from C++20:
* `Span.hpp` - [`std::span`](https://en.cppreference.com/w/cpp/container/span)
Proposals:
* `Memory.hpp` - [`p0211r3`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0211r3.html)
Adjustments:
* `CStdlib.hpp` -
`std_support::malloc`, `std_support::aligned_alloc`, `std_support::calloc`, `std_support::realloc`, `std_support::free` that use custom allocation scheme,
`std_support::aligned_calloc` as a version of `calloc` that allows changing alignment.
* `Memory.hpp` -
`std_support::allocator` using `std_support::calloc`/`std_support::free`,
`std_support::default_delete` that uses `std_support::free`,
`std_support::unique_ptr` that uses `std_support::default_delete`,
`std_support::make_unique` and `std_support::make_shared` that use `std_support::allocator`,
`std_support::nullptr_unique` - `nullptr` replacement for `unique_ptr` that takes an allocator.
* `New.hpp` -
custom operator `new` with `std_support::kalloc` marker argument that delegates to `std_support` allocator,
`std_support::kdelete` as a replacement for operator `delete` for objects created with custom `new`.
* `Deque.hpp`, `ForwardList.hpp`, `List.hpp`, `Map.hpp`, `Set.hpp`, `String.hpp`, `UnorderedMap.hpp`, `UnorderedSet.hpp`, `Vector.hpp` -
standard containers and `std_support::string` that default to using `std_support::allocator`.
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <set>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename Key, typename Compare = std::less<Key>, typename Allocator = allocator<Key>>
using set = std::set<Key, Compare, Allocator>;
template <typename Key, typename Compare = std::less<Key>, typename Allocator = allocator<Key>>
using multiset = std::multiset<Key, Compare, Allocator>;
} // namespace kotlin::std_support
@@ -13,8 +13,6 @@
#include "KAssert.h"
// Modelling https://en.cppreference.com/w/cpp/container/span from C++20.
namespace kotlin {
namespace std_support {
@@ -0,0 +1,315 @@
/*
* Copyright 2010-2022 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 "std_support/Memory.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "AllocatorTestSupport.hpp"
#include "KAssert.h"
#include "Utils.hpp"
using namespace kotlin;
namespace {
class EmptyClass {};
class Class {
public:
explicit Class(int32_t x) : x_(x) {}
int32_t x() const { return x_; }
private:
int32_t x_;
};
static_assert(sizeof(Class) > sizeof(EmptyClass));
class ClassThrows {
public:
explicit ClassThrows(int32_t x) : x_(x) { throw 13; }
int32_t x() const { return x_; }
private:
int32_t x_;
};
class DerivedClass : public Class {
public:
DerivedClass(int32_t x, int32_t y) : Class(x), y_(y) {}
int32_t y() const { return y_; }
private:
int32_t y_;
};
class MockClass : private Pinned {
public:
class Mocker : private Pinned {
public:
Mocker() noexcept {
RuntimeAssert(instance_ == nullptr, "Only one MockClass::Mocker at a time allowed");
instance_ = this;
}
~Mocker() {
RuntimeAssert(instance_ == this, "MockClass::Mocker::instance_ is broken.");
instance_ = nullptr;
}
MOCK_METHOD(void, ctor, (MockClass*, int));
MOCK_METHOD(void, dtor, (MockClass*), (noexcept));
private:
friend class MockClass;
static Mocker* instance_;
};
explicit MockClass(int x) { Mocker::instance_->ctor(this, x); }
~MockClass() noexcept { Mocker::instance_->dtor(this); }
int32_t x() const { return x_; }
private:
int32_t x_;
};
static_assert(sizeof(MockClass) > sizeof(EmptyClass));
// static
MockClass::Mocker* MockClass::Mocker::instance_ = nullptr;
} // namespace
TEST(StdSupportMemoryTest, Allocator) {
using Allocator = std_support::allocator<Class>;
using Traits = std::allocator_traits<Allocator>;
Allocator allocator;
Class* ptr = Traits::allocate(allocator, 1);
new (ptr) Class(42);
EXPECT_THAT(ptr->x(), 42);
Traits::deallocate(allocator, ptr, 1);
}
TEST(StdSupportMemoryTest, AllocatorFromWrongClass) {
using WrongClassAllocator = std_support::allocator<EmptyClass>;
WrongClassAllocator base;
using Allocator = typename std::allocator_traits<WrongClassAllocator>::template rebind_alloc<Class>;
using Traits = typename std::allocator_traits<WrongClassAllocator>::template rebind_traits<Class>;
Allocator allocator = Allocator(base);
Class* ptr = Traits::allocate(allocator, 1);
new (ptr) Class(42);
EXPECT_THAT(ptr->x(), 42);
Traits::deallocate(allocator, ptr, 1);
}
TEST(StdSupportMemoryTest, MakeUnique) {
auto ptr = std_support::make_unique<Class>(42);
EXPECT_THAT(ptr->x(), 42);
}
TEST(StdSupportMemoryTest, MakeUniqueThrows) {
EXPECT_THROW(std_support::make_unique<ClassThrows>(42), int);
}
TEST(StdSupportMemoryTest, MakeShared) {
auto ptr = std_support::make_shared<Class>(42);
EXPECT_THAT(ptr->x(), 42);
}
TEST(StdSupportMemoryTest, MakeSharedThrows) {
EXPECT_THROW(std_support::make_shared<ClassThrows>(42), int);
}
TEST(StdSupportMemoryTest, AllocatorNew) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(allocatorCore, allocate(sizeof(MockClass))).WillOnce(testing::Return(expectedPtr));
EXPECT_CALL(mocker, ctor(expectedPtr, 42));
}
auto* ptr = std_support::allocator_new<MockClass>(test_support::MakeAllocator<MockClass>(allocatorCore), 42);
EXPECT_THAT(ptr, expectedPtr);
}
TEST(StdSupportMemoryTest, AllocatorNewThrows) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(allocatorCore, allocate(sizeof(MockClass))).WillOnce(testing::Return(expectedPtr));
EXPECT_CALL(mocker, ctor(expectedPtr, 42)).WillOnce([] { throw 17; });
EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass)));
}
EXPECT_THROW(std_support::allocator_new<MockClass>(test_support::MakeAllocator<MockClass>(allocatorCore), 42), int);
}
TEST(StdSupportMemoryTest, AllocatorNewWrongType) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(allocatorCore, allocate(sizeof(MockClass))).WillOnce(testing::Return(expectedPtr));
EXPECT_CALL(mocker, ctor(expectedPtr, 42));
}
auto* ptr = std_support::allocator_new<MockClass>(test_support::MakeAllocator<EmptyClass>(allocatorCore), 42);
EXPECT_THAT(ptr, expectedPtr);
}
TEST(StdSupportMemoryTest, AllocatorDelete) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(mocker, dtor(expectedPtr));
EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass)));
}
std_support::allocator_delete(test_support::MakeAllocator<MockClass>(allocatorCore), expectedPtr);
}
TEST(StdSupportMemoryTest, AllocatorDeleteWrongType) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(mocker, dtor(expectedPtr));
EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass)));
}
std_support::allocator_delete(test_support::MakeAllocator<EmptyClass>(allocatorCore), expectedPtr);
}
TEST(StdSupportMemoryTest, AllocateUnique) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(allocatorCore, allocate(sizeof(MockClass))).WillOnce(testing::Return(expectedPtr));
EXPECT_CALL(mocker, ctor(expectedPtr, 42));
}
auto ptr = std_support::allocate_unique<MockClass>(test_support::MakeAllocator<MockClass>(allocatorCore), 42);
EXPECT_THAT(ptr.get(), expectedPtr);
{
testing::InSequence s;
EXPECT_CALL(mocker, dtor(expectedPtr));
EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass)));
}
ptr.reset();
}
TEST(StdSupportMemoryTest, AllocateUniqueThrows) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(allocatorCore, allocate(sizeof(MockClass))).WillOnce(testing::Return(expectedPtr));
EXPECT_CALL(mocker, ctor(expectedPtr, 42)).WillOnce([] { throw 17; });
EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass)));
}
EXPECT_THROW(std_support::allocate_unique<MockClass>(test_support::MakeAllocator<MockClass>(allocatorCore), 42), int);
}
TEST(StdSupportMemoryTest, AllocateUniqueWrongType) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
testing::StrictMock<MockClass::Mocker> mocker;
MockClass* expectedPtr = reinterpret_cast<MockClass*>(13);
{
testing::InSequence s;
EXPECT_CALL(allocatorCore, allocate(sizeof(MockClass))).WillOnce(testing::Return(expectedPtr));
EXPECT_CALL(mocker, ctor(expectedPtr, 42));
}
auto ptr = std_support::allocate_unique<MockClass>(test_support::MakeAllocator<EmptyClass>(allocatorCore), 42);
EXPECT_THAT(ptr.get(), expectedPtr);
{
testing::InSequence s;
EXPECT_CALL(mocker, dtor(expectedPtr));
EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass)));
}
ptr.reset();
}
template <typename T, typename Allocator>
using UniquePtr = std_support::unique_ptr<T, std_support::allocator_deleter<T, Allocator>>;
TEST(StdSupportMemoryTest, UniquePtrConversions) {
static_assert(std::is_convertible_v<std_support::unique_ptr<DerivedClass>, std_support::unique_ptr<Class>>);
static_assert(!std::is_convertible_v<std_support::unique_ptr<Class>, std_support::unique_ptr<DerivedClass>>);
static_assert(!std::is_convertible_v<std_support::unique_ptr<Class>, std_support::unique_ptr<int>>);
static_assert(!std::is_convertible_v<std_support::unique_ptr<int>, std_support::unique_ptr<Class>>);
static_assert(!std::is_assignable_v<std_support::unique_ptr<DerivedClass>, std_support::unique_ptr<Class>>);
static_assert(std::is_assignable_v<std_support::unique_ptr<Class>, std_support::unique_ptr<DerivedClass>>);
static_assert(!std::is_assignable_v<std_support::unique_ptr<Class>, std_support::unique_ptr<int>>);
static_assert(!std::is_assignable_v<std_support::unique_ptr<int>, std_support::unique_ptr<Class>>);
using AllocatorClass = test_support::Allocator<Class, test_support::MockAllocatorCore>;
using AllocatorDerivedClass = test_support::Allocator<DerivedClass, test_support::MockAllocatorCore>;
using AllocatorInt = test_support::Allocator<int, test_support::MockAllocatorCore>;
static_assert(std::is_convertible_v<UniquePtr<DerivedClass, AllocatorClass>, UniquePtr<Class, AllocatorClass>>);
static_assert(std::is_convertible_v<UniquePtr<DerivedClass, AllocatorDerivedClass>, UniquePtr<Class, AllocatorClass>>);
static_assert(std::is_convertible_v<UniquePtr<DerivedClass, AllocatorClass>, UniquePtr<Class, AllocatorDerivedClass>>);
static_assert(std::is_convertible_v<UniquePtr<DerivedClass, AllocatorDerivedClass>, UniquePtr<Class, AllocatorDerivedClass>>);
static_assert(!std::is_convertible_v<UniquePtr<Class, AllocatorClass>, UniquePtr<DerivedClass, AllocatorClass>>);
static_assert(!std::is_convertible_v<UniquePtr<Class, AllocatorDerivedClass>, UniquePtr<DerivedClass, AllocatorClass>>);
static_assert(!std::is_convertible_v<UniquePtr<Class, AllocatorClass>, UniquePtr<DerivedClass, AllocatorDerivedClass>>);
static_assert(!std::is_convertible_v<UniquePtr<Class, AllocatorDerivedClass>, UniquePtr<DerivedClass, AllocatorDerivedClass>>);
static_assert(!std::is_convertible_v<UniquePtr<Class, AllocatorClass>, UniquePtr<int, AllocatorInt>>);
static_assert(!std::is_convertible_v<UniquePtr<Class, AllocatorDerivedClass>, UniquePtr<int, AllocatorInt>>);
static_assert(!std::is_convertible_v<UniquePtr<int, AllocatorInt>, UniquePtr<Class, AllocatorClass>>);
static_assert(!std::is_convertible_v<UniquePtr<int, AllocatorInt>, UniquePtr<DerivedClass, AllocatorDerivedClass>>);
static_assert(!std::is_assignable_v<UniquePtr<DerivedClass, AllocatorClass>, UniquePtr<Class, AllocatorClass>>);
static_assert(!std::is_assignable_v<UniquePtr<DerivedClass, AllocatorDerivedClass>, UniquePtr<Class, AllocatorClass>>);
static_assert(!std::is_assignable_v<UniquePtr<DerivedClass, AllocatorClass>, UniquePtr<Class, AllocatorDerivedClass>>);
static_assert(!std::is_assignable_v<UniquePtr<DerivedClass, AllocatorDerivedClass>, UniquePtr<Class, AllocatorDerivedClass>>);
static_assert(std::is_assignable_v<UniquePtr<Class, AllocatorClass>, UniquePtr<DerivedClass, AllocatorClass>>);
static_assert(std::is_assignable_v<UniquePtr<Class, AllocatorDerivedClass>, UniquePtr<DerivedClass, AllocatorClass>>);
static_assert(std::is_assignable_v<UniquePtr<Class, AllocatorClass>, UniquePtr<DerivedClass, AllocatorDerivedClass>>);
static_assert(std::is_assignable_v<UniquePtr<Class, AllocatorDerivedClass>, UniquePtr<DerivedClass, AllocatorDerivedClass>>);
static_assert(!std::is_assignable_v<UniquePtr<Class, AllocatorClass>, UniquePtr<int, AllocatorInt>>);
static_assert(!std::is_assignable_v<UniquePtr<Class, AllocatorDerivedClass>, UniquePtr<int, AllocatorInt>>);
static_assert(!std::is_assignable_v<UniquePtr<int, AllocatorInt>, UniquePtr<Class, AllocatorClass>>);
static_assert(!std::is_assignable_v<UniquePtr<int, AllocatorInt>, UniquePtr<DerivedClass, AllocatorDerivedClass>>);
}
TEST(StdSupportMemoryTest, NullptrUnique) {
testing::StrictMock<test_support::MockAllocatorCore> allocatorCore;
auto allocator = test_support::MakeAllocator<int>(allocatorCore);
std_support::unique_ptr<int, std_support::allocator_deleter<int, decltype(allocator)>> ptr =
std_support::nullptr_unique<int>(allocator);
EXPECT_THAT(ptr.get(), nullptr);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <string>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename CharT, typename Traits = std::char_traits<CharT>, typename Allocator = allocator<CharT>>
using basic_string = std::basic_string<CharT, Traits, Allocator>;
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
using u16string = basic_string<char16_t>;
using u32string = basic_string<char32_t>;
} // namespace kotlin::std_support
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <unordered_map>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <
typename Key,
typename T,
typename Hash = std::hash<Key>,
typename KeyEqual = std::equal_to<Key>,
typename Allocator = allocator<std::pair<const Key, T>>>
using unordered_map = std::unordered_map<Key, T, Hash, KeyEqual, Allocator>;
template <
typename Key,
typename T,
typename Hash = std::hash<Key>,
typename KeyEqual = std::equal_to<Key>,
typename Allocator = allocator<std::pair<const Key, T>>>
using unordered_multimap = std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>;
} // namespace kotlin::std_support
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <unordered_set>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename Key, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>, typename Allocator = allocator<Key>>
using unordered_set = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
template <typename Key, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>, typename Allocator = allocator<Key>>
using unordered_multiset = std::unordered_multiset<Key, Hash, KeyEqual, Allocator>;
} // namespace kotlin::std_support
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.
*/
#pragma once
#include <vector>
#include "std_support/Memory.hpp"
namespace kotlin::std_support {
template <typename T, typename Allocator = allocator<T>>
using vector = std::vector<T, Allocator>;
} // namespace kotlin::std_support
@@ -21,7 +21,7 @@ namespace kotlin {
namespace mm {
// Optional data that's lazily allocated only for objects that need it.
class ExtraObjectData : private Pinned, public KonanAllocatorAware {
class ExtraObjectData : private Pinned {
public:
// flags are stored as single atomic uint32, values are bit numbers in that uint32
enum Flags : uint32_t {
@@ -16,6 +16,7 @@
#include "FinalizerHooks.hpp"
#include "Memory.h"
#include "Mutex.hpp"
#include "Porting.h"
#include "Types.h"
#include "Utils.hpp"
@@ -45,8 +46,7 @@ class ObjectFactoryStorage : private Pinned {
using unique_ptr = std::unique_ptr<T, Deleter<T>>;
public:
// This class does not know its size at compile-time. Does not inherit from `KonanAllocatorAware` because
// in `KonanAllocatorAware::operator new(size_t size, KonanAllocTag)` `size` would be incorrect.
// 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); }
@@ -1,24 +1,39 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* Copyright 2010-2022 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 <stdlib.h>
#include <stdio.h>
#include <cstddef>
extern "C" {
void* mi_calloc(size_t, size_t);
void mi_free(void*);
void* mi_malloc(size_t size);
void* mi_malloc_aligned(size_t size, size_t alignment);
void* mi_calloc(size_t count, size_t size);
void* mi_calloc_aligned(size_t count, size_t size, size_t alignment);
void* konan_calloc_impl(size_t n_elements, size_t elem_size) {
return mi_calloc(n_elements, elem_size);
void* mi_realloc(void* ptr, size_t size);
void mi_free(void* ptr);
}
void* konan_calloc_aligned_impl(size_t count, size_t size, size_t alignment) {
return mi_calloc_aligned(count, size, alignment);
extern "C" void* konan_malloc_impl(size_t size) {
return mi_malloc(size);
}
void konan_free_impl (void* mem) {
mi_free(mem);
extern "C" void* konan_aligned_alloc_impl(size_t alignment, size_t size) {
return mi_malloc_aligned(size, alignment);
}
extern "C" void* konan_calloc_impl(size_t num, size_t size) {
return mi_calloc(num, size);
}
extern "C" void* konan_aligned_calloc_impl(size_t alignment, size_t num, size_t size) {
return mi_calloc_aligned(num, size, alignment);
}
extern "C" void* konan_realloc_impl(void* ptr, size_t size) {
return mi_realloc(ptr, size);
}
extern "C" void konan_free_impl(void* ptr) {
return mi_free(ptr);
}
} // extern "C"
@@ -1,23 +1,30 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* Copyright 2010-2022 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 <stdlib.h>
#include <stdio.h>
extern "C" {
// Memory operations.
void* konan_calloc_impl(size_t n_elements, size_t elem_size) {
return calloc(n_elements, elem_size);
#include <cstdlib>
extern "C" void* konan_malloc_impl(size_t size) {
return ::malloc(size);
}
void* konan_calloc_aligned_impl(size_t count, size_t size, size_t alignment) {
// alignment is not supported by std alloc - use mimalloc
return calloc(count, size);
extern "C" void* konan_aligned_alloc_impl(size_t alignment, size_t size) {
return ::malloc(size);
}
void konan_free_impl (void* mem) {
free(mem);
}
extern "C" void* konan_calloc_impl(size_t num, size_t size) {
return ::calloc(num, size);
}
extern "C" void* konan_aligned_calloc_impl(size_t alignment, size_t num, size_t size) {
return ::calloc(num, size);
}
extern "C" void* konan_realloc_impl(void* ptr, size_t size) {
return ::realloc(ptr, size);
}
extern "C" void konan_free_impl(void* ptr) {
return ::free(ptr);
}