diff --git a/kotlin-native/codestyle/cpp/README.md b/kotlin-native/codestyle/cpp/README.md index bcefcadee6e..887aece4850 100644 --- a/kotlin-native/codestyle/cpp/README.md +++ b/kotlin-native/codestyle/cpp/README.md @@ -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 diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index 6bc933bd30a..4615d30e53b 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -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 #endif +using namespace kotlin; + namespace { ALWAYS_INLINE bool IsStrictMemoryModel() noexcept { @@ -196,7 +199,7 @@ struct CycleDetectorRootset { KStdVector 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; } diff --git a/kotlin-native/runtime/src/main/cpp/Alloc.h b/kotlin-native/runtime/src/main/cpp/Alloc.h index ccfa9fb2ba0..1df67de8036 100644 --- a/kotlin-native/runtime/src/main/cpp/Alloc.h +++ b/kotlin-native/runtime/src/main/cpp/Alloc.h @@ -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 -#include +#pragma once +#include #include #include -#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 inline T* konanConstructInstance(A&& ...args) { - return new (konanAllocMemory(sizeof(T))) T(::std::forward(args)...); + return new (kotlin::std_support::kalloc) T(std::forward(args)...); } template @@ -54,113 +57,5 @@ inline T* konanConstructSizedInstance(size_t size, A&& ...args) { template inline void konanDestructInstance(T* instance) { - instance->~T(); - konanFreeMemory(instance); + kotlin::std_support::kdelete(instance); } - -template 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(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& operator=(const KonanAllocator&) { return *this; } - - void construct(pointer p, const T& val) { new ((T*) p) T(val); } - - // C++-11 wants that. - template - void construct(U* const p, A&& ...args) { - new (p) U(::std::forward(args)...); - } - - void destroy(pointer p) { p->~T(); } - - size_type max_size() const { return size_t(-1); } - - template - struct rebind { typedef KonanAllocator other; }; - - template - KonanAllocator(const KonanAllocator&) {} - - template - KonanAllocator& operator=(const KonanAllocator&) { return *this; } -}; - -template -bool operator==( - KonanAllocator const&, KonanAllocator const&) noexcept { - return true; -} - -template -bool operator!=( - KonanAllocator const& x, KonanAllocator const& y) noexcept { - return !(x == y); -} - -template -class KonanDeleter { -public: - KonanDeleter() = default; - - // This is needed for `KStdUniquePtr` covariance: if `U*` converts to `T*` then `KStdUniquePtr` converts to `KStdUniquePtr`. - template , std::nullptr_t> = nullptr> - KonanDeleter(KonanDeleter) {} - - 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 diff --git a/kotlin-native/runtime/src/main/cpp/AllocTest.cpp b/kotlin-native/runtime/src/main/cpp/AllocTest.cpp deleted file mode 100644 index 4b61017925a..00000000000 --- a/kotlin-native/runtime/src/main/cpp/AllocTest.cpp +++ /dev/null @@ -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 - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include "Types.h" - -namespace { - -class A : public KonanAllocatorAware { -public: - using DestructorHook = testing::StrictMock>; - - 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 destructorHook; - - void SetUp() override { - Test::SetUp(); - - destructorHook = make_unique(); - 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 actual; - for (A* a = as; a != as + kCount; ++a) { - actual.push_back(a->value()); - } - std::array 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 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 buffer; - A* as = new (buffer.data()) A[kCount]; - - std::vector actual; - for (A* a = as; a != as + kCount; ++a) { - actual.push_back(a->value()); - } - std::array 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(); - } -} diff --git a/kotlin-native/runtime/src/main/cpp/AllocatorTestSupport.hpp b/kotlin-native/runtime/src/main/cpp/AllocatorTestSupport.hpp new file mode 100644 index 00000000000..52a256ee2df --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/AllocatorTestSupport.hpp @@ -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 +#include + +#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 +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 + Allocator(const Allocator& other) noexcept : core_(other.core_) {} + + template + Allocator& operator=(const Allocator& other) noexcept { + core_ = other.core_; + } + + T* allocate(std::size_t n) noexcept { return static_cast(core_->allocate(sizeof(T) * n)); } + + void deallocate(T* p, std::size_t n) noexcept { core_->deallocate(p, sizeof(T) * n); } + + Core* core_; +}; + +template +auto MakeAllocator(Core& core) { + return Allocator(core); +} + +template +bool operator==(const Allocator& lhs, const Allocator& rhs) noexcept { + return lhs.core_ == rhs.core_; +} + +template +bool operator!=(const Allocator& lhs, const Allocator& rhs) noexcept { + return !(lhs == rhs); +} + +} // namespace kotlin::test_support diff --git a/kotlin-native/runtime/src/main/cpp/AllocatorTestSupportTest.cpp b/kotlin-native/runtime/src/main/cpp/AllocatorTestSupportTest.cpp new file mode 100644 index 00000000000..ceb04f88b23 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/AllocatorTestSupportTest.cpp @@ -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 allocatorCore; + auto allocator = test_support::MakeAllocator(allocatorCore); + + auto* expectedPtr = reinterpret_cast(13); + EXPECT_CALL(allocatorCore, allocate(2 * sizeof(Class))).WillOnce(testing::Return(expectedPtr)); + auto* ptr = std::allocator_traits::allocate(allocator, 2); + EXPECT_THAT(ptr, expectedPtr); +} + +TEST(AllocatorTestSupportTest, MockDeallocate) { + testing::StrictMock allocatorCore; + auto allocator = test_support::MakeAllocator(allocatorCore); + + auto* ptr = reinterpret_cast(13); + EXPECT_CALL(allocatorCore, deallocate(ptr, 2 * sizeof(Class))); + std::allocator_traits::deallocate(allocator, ptr, 2); +} + +TEST(AllocatorTestSupportTest, MockAdjustType) { + testing::StrictMock allocatorCore; + auto initial = test_support::MakeAllocator(allocatorCore); + + using Allocator = std::allocator_traits::template rebind_alloc; + using Traits = std::allocator_traits::template rebind_traits; + Allocator allocator = Allocator(initial); + + auto* expectedPtr = reinterpret_cast(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(allocatorCore); + + EXPECT_CALL(allocatorCore, allocate(2 * sizeof(Class))); + auto* ptr1 = std::allocator_traits::allocate(allocator, 2); + testing::Mock::VerifyAndClearExpectations(&allocatorCore); + + using Allocator = std::allocator_traits::template rebind_alloc; + using Traits = std::allocator_traits::template rebind_traits; + 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::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); +} diff --git a/kotlin-native/runtime/src/main/cpp/CppSupport.hpp b/kotlin-native/runtime/src/main/cpp/CppSupport.hpp deleted file mode 100644 index 68c7bfb8c21..00000000000 --- a/kotlin-native/runtime/src/main/cpp/CppSupport.hpp +++ /dev/null @@ -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 diff --git a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp b/kotlin-native/runtime/src/main/cpp/Exceptions.cpp index 550a592ab86..ccbd3426e12 100644 --- a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp +++ b/kotlin-native/runtime/src/main/cpp/Exceptions.cpp @@ -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" diff --git a/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp b/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp index ffd045919cf..80afd8ef8e2 100644 --- a/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp +++ b/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp @@ -16,6 +16,7 @@ #include "ExecFormat.h" +#include "Porting.h" #include "Types.h" #if USE_ELF_SYMBOLS diff --git a/kotlin-native/runtime/src/main/cpp/Format.h b/kotlin-native/runtime/src/main/cpp/Format.h index db615a25c55..9a84c7cbe48 100644 --- a/kotlin-native/runtime/src/main/cpp/Format.h +++ b/kotlin-native/runtime/src/main/cpp/Format.h @@ -8,7 +8,7 @@ #include -#include "cpp_support/Span.hpp" +#include "std_support/Span.hpp" namespace kotlin { diff --git a/kotlin-native/runtime/src/main/cpp/FormatTest.cpp b/kotlin-native/runtime/src/main/cpp/FormatTest.cpp index 8a6230ee239..469da91cf13 100644 --- a/kotlin-native/runtime/src/main/cpp/FormatTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/FormatTest.cpp @@ -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; diff --git a/kotlin-native/runtime/src/main/cpp/KAssert.cpp b/kotlin-native/runtime/src/main/cpp/KAssert.cpp index d7c1a8f57c9..a8a77d21866 100644 --- a/kotlin-native/runtime/src/main/cpp/KAssert.cpp +++ b/kotlin-native/runtime/src/main/cpp/KAssert.cpp @@ -8,8 +8,9 @@ #include #include -#include "cpp_support/Span.hpp" +#include "std_support/Span.hpp" #include "Format.h" +#include "Porting.h" #include "StackTrace.hpp" using namespace kotlin; diff --git a/kotlin-native/runtime/src/main/cpp/Logging.cpp b/kotlin-native/runtime/src/main/cpp/Logging.cpp index 319327a4ecf..10fc6e331a1 100644 --- a/kotlin-native/runtime/src/main/cpp/Logging.cpp +++ b/kotlin-native/runtime/src/main/cpp/Logging.cpp @@ -22,6 +22,7 @@ inline constexpr auto nullopt = std::experimental::nullopt; #include "Format.h" #include "KAssert.h" +#include "Porting.h" using namespace kotlin; diff --git a/kotlin-native/runtime/src/main/cpp/Logging.hpp b/kotlin-native/runtime/src/main/cpp/Logging.hpp index 95c2c8de680..8b4c230837b 100644 --- a/kotlin-native/runtime/src/main/cpp/Logging.hpp +++ b/kotlin-native/runtime/src/main/cpp/Logging.hpp @@ -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 { diff --git a/kotlin-native/runtime/src/main/cpp/MultiSourceQueue.hpp b/kotlin-native/runtime/src/main/cpp/MultiSourceQueue.hpp index d8483efb33b..a1964f525e4 100644 --- a/kotlin-native/runtime/src/main/cpp/MultiSourceQueue.hpp +++ b/kotlin-native/runtime/src/main/cpp/MultiSourceQueue.hpp @@ -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 +template > class MultiSourceQueue { + // Using `std_support::list` as it allows to implement `Collect` without memory allocations, + // which is important for GC mark phase. + template + using List = std_support::list::template rebind_alloc>; + 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 owner_; // `nullptr` signifies that `MultiSourceQueue` owns it. - typename KStdList::iterator position_; + typename List::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 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 queue_; - KStdList deletionQueue_; + List queue_; + List deletionQueue_; }; class Iterator { @@ -115,9 +125,9 @@ public: private: friend class MultiSourceQueue; - explicit Iterator(const typename KStdList::iterator& position) noexcept : position_(position) {} + explicit Iterator(const typename List::iterator& position) noexcept : position_(position) {} - typename KStdList::iterator position_; + typename List::iterator position_; }; class Iterable : MoveOnly { @@ -134,6 +144,8 @@ public: std::unique_lock 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 guard(mutex_); - KStdList remainingDeletions; + List 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 queue_; - KStdList deletionQueue_; + List queue_; + List deletionQueue_; Mutex mutex_; }; diff --git a/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp b/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp index c16c00778ef..1ad1efb0ab7 100644 --- a/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp @@ -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 @@ -312,3 +315,42 @@ TEST(MultiSourceQueueTest, ConcurrentPublishAndApplyDeletions) { auto actual = Collect(queue); EXPECT_THAT(actual, testing::IsEmpty()); } + +TEST(MultiSourceQueueTest, CustomAllocator) { + testing::StrictMock allocatorCore; + auto allocator = test_support::MakeAllocator(allocatorCore); + + using Queue = MultiSourceQueue, 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); +} diff --git a/kotlin-native/runtime/src/main/cpp/Natives.cpp b/kotlin-native/runtime/src/main/cpp/Natives.cpp index 3d5616d7d6c..e4a1e8174f6 100644 --- a/kotlin-native/runtime/src/main/cpp/Natives.cpp +++ b/kotlin-native/runtime/src/main/cpp/Natives.cpp @@ -25,6 +25,7 @@ #include "KString.h" #include "StackTrace.hpp" #include "Memory.h" +#include "Porting.h" #include "Natives.h" #include "Types.h" diff --git a/kotlin-native/runtime/src/main/cpp/Porting.cpp b/kotlin-native/runtime/src/main/cpp/Porting.cpp index e6480310555..ca786849c4e 100644 --- a/kotlin-native/runtime/src/main/cpp/Porting.cpp +++ b/kotlin-native/runtime/src/main/cpp/Porting.cpp @@ -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" { diff --git a/kotlin-native/runtime/src/main/cpp/Porting.h b/kotlin-native/runtime/src/main/cpp/Porting.h index d7e0f54f883..e20fda1222b 100644 --- a/kotlin-native/runtime/src/main/cpp/Porting.h +++ b/kotlin-native/runtime/src/main/cpp/Porting.h @@ -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(); diff --git a/kotlin-native/runtime/src/main/cpp/SingleLockList.hpp b/kotlin-native/runtime/src/main/cpp/SingleLockList.hpp index a1c9f041b71..8d8f71c6542 100644 --- a/kotlin-native/runtime/src/main/cpp/SingleLockList.hpp +++ b/kotlin-native/runtime/src/main/cpp/SingleLockList.hpp @@ -12,41 +12,36 @@ #include #include -#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 +template > class SingleLockList : private Pinned { public: class Node; private: - class NodeDeleter { - public: - void operator()(Node* node) const { delete node; } - }; - - using NodeOwner = std::unique_ptr; + using NodeAllocator = typename std::allocator_traits::template rebind_alloc; + using NodeOwner = std_support::unique_ptr>; public: - class Node : private Pinned, public KonanAllocatorAware { + // TODO: Maybe just hide `Node` altogether? + class Node : private Pinned { public: + template + explicit Node(const Allocator& allocator, Args&&... args) noexcept : + value_(std::forward(args)...), next_(std_support::nullptr_unique(allocator)) {} + Value* Get() noexcept { return &value_; } private: friend class SingleLockList; - template - Node(Args&&... args) noexcept : value_(std::forward(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 guard_; }; + SingleLockList() noexcept = default; + + explicit SingleLockList(const Allocator& allocator) noexcept : + allocator_(allocator), root_(std_support::nullptr_unique(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 Node* Emplace(Args&&... args) noexcept { - auto* nodePtr = new Node(std::forward(args)...); - NodeOwner node(nodePtr); + auto node = std_support::allocate_unique(allocator_, allocator_, std::forward(args)...); + auto* nodePtr = node.get(); std::lock_guard 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_; diff --git a/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp b/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp index 2b2f691d303..3702b43302c 100644 --- a/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp @@ -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>; @@ -417,3 +420,26 @@ TEST(SingleLockListTest, Destructor) { } } } + +TEST(SingleLockListTest, CustomAllocator) { + testing::StrictMock allocatorCore; + auto allocator = test_support::MakeAllocator(allocatorCore); + SingleLockList, 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); +} diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp index 3547f2aa050..ffd97c7eff1 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp @@ -168,7 +168,7 @@ NO_INLINE size_t kotlin::internal::GetCurrentStackTrace(size_t skipFrames, std_s #if ! KONAN_NO_BACKTRACE #include #include -#include "cpp_support/Span.hpp" +#include "std_support/Span.hpp" #include "Format.h" #if __has_include("dlfcn.h") diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.hpp b/kotlin-native/runtime/src/main/cpp/StackTrace.hpp index c8636031816..bc4e62e00e2 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTrace.hpp +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.hpp @@ -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" diff --git a/kotlin-native/runtime/src/main/cpp/Types.h b/kotlin-native/runtime/src/main/cpp/Types.h index 51d09cb4adc..1bafd39e9de 100644 --- a/kotlin-native/runtime/src/main/cpp/Types.h +++ b/kotlin-native/runtime/src/main/cpp/Types.h @@ -19,25 +19,19 @@ #include -#if (KONAN_WASM || KONAN_ZEPHYR) && !defined(assert) -// assert() is needed by STLport. -#define assert(cond) if (!(cond)) abort() -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - #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, - KonanAllocator> KStdString; -template -using KStdDeque = std::deque>; -template -using KStdUnorderedMap = std::unordered_map, std::equal_to, - KonanAllocator>>; -template -using KStdUnorderedSet = std::unordered_set, std::equal_to, - KonanAllocator>; -template> -using KStdOrderedMultiset = std::multiset>; -template> -using KStdOrderedMap = std::map>>; -template -using KStdVector = std::vector>; -template -using KStdList = std::list>; -template -using KStdUniquePtr = std::unique_ptr>; - -template -KStdUniquePtr make_unique(Args&&... args) noexcept { - return KStdUniquePtr(konanConstructInstance(std::forward(args)...)); -} +// TODO: Remove these typedefs. Use std_support directly everywhere. +using KStdString = kotlin::std_support::string; +template +using KStdDeque = kotlin::std_support::deque; +template +using KStdUnorderedMap = kotlin::std_support::unordered_map; +template +using KStdUnorderedSet = kotlin::std_support::unordered_set; +template > +using KStdOrderedMultiset = kotlin::std_support::multiset; +template +using KStdOrderedMap = kotlin::std_support::map; +template +using KStdVector = kotlin::std_support::vector; +template +using KStdList = kotlin::std_support::list; +template +using KStdUniquePtr = kotlin::std_support::unique_ptr; +using kotlin::std_support::make_unique; #ifdef __cplusplus extern "C" { diff --git a/kotlin-native/runtime/src/main/cpp/dtoa/dblparse.cpp b/kotlin-native/runtime/src/main/cpp/dtoa/dblparse.cpp index e3d2a5a1968..f92376588ae 100644 --- a/kotlin-native/runtime/src/main/cpp/dtoa/dblparse.cpp +++ b/kotlin-native/runtime/src/main/cpp/dtoa/dblparse.cpp @@ -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" diff --git a/kotlin-native/runtime/src/main/cpp/dtoa/fltparse.cpp b/kotlin-native/runtime/src/main/cpp/dtoa/fltparse.cpp index e27da3758ce..4a9e1a4f2bd 100644 --- a/kotlin-native/runtime/src/main/cpp/dtoa/fltparse.cpp +++ b/kotlin-native/runtime/src/main/cpp/dtoa/fltparse.cpp @@ -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) diff --git a/kotlin-native/runtime/src/main/cpp/std_support/CStdlib.cpp b/kotlin-native/runtime/src/main/cpp/std_support/CStdlib.cpp new file mode 100644 index 00000000000..7a85ce708f8 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/CStdlib.cpp @@ -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 +#include + +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 diff --git a/kotlin-native/runtime/src/main/cpp/std_support/CStdlib.hpp b/kotlin-native/runtime/src/main/cpp/std_support/CStdlib.hpp new file mode 100644 index 00000000000..69b4d58b90f --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/CStdlib.hpp @@ -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 + +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 diff --git a/kotlin-native/runtime/src/main/cpp/std_support/Deque.hpp b/kotlin-native/runtime/src/main/cpp/std_support/Deque.hpp new file mode 100644 index 00000000000..e3134ac1acc --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/Deque.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template > +using deque = std::deque; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/ForwardList.hpp b/kotlin-native/runtime/src/main/cpp/std_support/ForwardList.hpp new file mode 100644 index 00000000000..24c9aeb76a8 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/ForwardList.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template > +using forward_list = std::forward_list; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/List.hpp b/kotlin-native/runtime/src/main/cpp/std_support/List.hpp new file mode 100644 index 00000000000..a4c1fbc6d3b --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/List.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template > +using list = std::list; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/Map.hpp b/kotlin-native/runtime/src/main/cpp/std_support/Map.hpp new file mode 100644 index 00000000000..68696164dba --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/Map.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template , typename Allocator = allocator>> +using map = std::map; + +template , typename Allocator = allocator>> +using multimap = std::multimap; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/Memory.hpp b/kotlin-native/runtime/src/main/cpp/std_support/Memory.hpp new file mode 100644 index 00000000000..f93f098d4be --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/Memory.hpp @@ -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 +#include +#include + +#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 +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 + allocator(const allocator&) noexcept {} + + // TODO: maybe malloc, actually? + T* allocate(std::size_t n) noexcept { return static_cast(std_support::calloc(n, sizeof(T))); } + + void deallocate(T* p, std::size_t n) noexcept { std_support::free(p); } +}; + +template +bool operator==(const allocator&, const allocator&) noexcept { + return true; +} + +template +bool operator!=(const allocator&, const allocator&) noexcept { + return false; +} + +template +T* allocator_new(const Allocator& allocator, Args&&... args) { + static_assert(!std::is_array_v, "T cannot be an array"); + + using TAllocatorTraits = typename std::allocator_traits::template rebind_traits; + using TAllocator = typename std::allocator_traits::template rebind_alloc; + + auto a = TAllocator(allocator); + T* ptr = TAllocatorTraits::allocate(a, 1); + +#if KONAN_NO_EXCEPTIONS + TAllocatorTraits::construct(a, ptr, std::forward(args)...); + return ptr; +#else + try { + TAllocatorTraits::construct(a, ptr, std::forward(args)...); + return ptr; + } catch (...) { + TAllocatorTraits::deallocate(a, ptr, 1); + throw; + } +#endif +} + +template +void allocator_delete(const Allocator& allocator, T* ptr) noexcept { + static_assert(!std::is_array_v, "T cannot be an array"); + + using TAllocatorTraits = typename std::allocator_traits::template rebind_traits; + using TAllocator = typename std::allocator_traits::template rebind_alloc; + + auto a = TAllocator(allocator); + TAllocatorTraits::destroy(a, ptr); + TAllocatorTraits::deallocate(a, ptr, 1); +} + +template +class allocator_deleter { + static_assert(!std::is_array_v, "T cannot be an array"); + + template + using Rebind = typename std::allocator_traits::template rebind_alloc; + + template + static inline constexpr bool allocatorsCompatible = std::is_same_v, Rebind>; + +public: + allocator_deleter() noexcept = default; + + explicit allocator_deleter(const Allocator& allocator) noexcept : allocator(allocator) {} + + template < + typename U, + typename Other, + typename = std::enable_if_t && allocatorsCompatible>> + allocator_deleter(const allocator_deleter& rhs) noexcept : allocator(rhs.allocator) {} + + void operator()(T* ptr) noexcept { allocator_delete(allocator, ptr); } + + [[no_unique_address]] Allocator allocator; +}; + +template +auto allocate_unique(const Allocator& allocator, Args&&... args) { + static_assert(!std::is_array_v, "T cannot be an array"); + + using TAllocator = typename std::allocator_traits::template rebind_alloc; + using TDeleter = allocator_deleter; + return std::unique_ptr(allocator_new(allocator, std::forward(args)...), TDeleter(allocator)); +} + +template +using default_delete = allocator_deleter>; + +template > +using unique_ptr = std::unique_ptr; + +template +auto make_unique(Args&&... args) { + static_assert(!std::is_array_v, "T cannot be an array"); + + return allocate_unique(allocator(), std::forward(args)...); +} + +template +auto make_shared(Args&&... args) { + static_assert(!std::is_array_v, "T cannot be an array"); + + return std::allocate_shared(allocator(), std::forward(args)...); +} + +template +auto nullptr_unique(const Allocator& allocator = Allocator()) noexcept { + static_assert(!std::is_array_v, "T cannot be an array"); + + using TAllocator = typename std::allocator_traits::template rebind_alloc; + using TDeleter = allocator_deleter; + return std::unique_ptr(nullptr, TDeleter(allocator)); +} + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/New.cpp b/kotlin-native/runtime/src/main/cpp/std_support/New.cpp new file mode 100644 index 00000000000..76a46272477 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/New.cpp @@ -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); +} diff --git a/kotlin-native/runtime/src/main/cpp/std_support/New.hpp b/kotlin-native/runtime/src/main/cpp/std_support/New.hpp new file mode 100644 index 00000000000..37326f0ac73 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/New.hpp @@ -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 + +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 +void kdelete(T* ptr) noexcept { + ptr->~T(); + ::operator delete(ptr, kalloc); +} + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/NewTest.cpp b/kotlin-native/runtime/src/main/cpp/std_support/NewTest.cpp new file mode 100644 index 00000000000..9bd0fa2695c --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/NewTest.cpp @@ -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); +} diff --git a/kotlin-native/runtime/src/main/cpp/std_support/README.md b/kotlin-native/runtime/src/main/cpp/std_support/README.md new file mode 100644 index 00000000000..f0ec90d7e0e --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/README.md @@ -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`. diff --git a/kotlin-native/runtime/src/main/cpp/std_support/Set.hpp b/kotlin-native/runtime/src/main/cpp/std_support/Set.hpp new file mode 100644 index 00000000000..c67732cc48f --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/Set.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template , typename Allocator = allocator> +using set = std::set; + +template , typename Allocator = allocator> +using multiset = std::multiset; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/cpp_support/Span.hpp b/kotlin-native/runtime/src/main/cpp/std_support/Span.hpp similarity index 99% rename from kotlin-native/runtime/src/main/cpp/cpp_support/Span.hpp rename to kotlin-native/runtime/src/main/cpp/std_support/Span.hpp index d2b13d1b2f0..a3e57e0f525 100644 --- a/kotlin-native/runtime/src/main/cpp/cpp_support/Span.hpp +++ b/kotlin-native/runtime/src/main/cpp/std_support/Span.hpp @@ -13,8 +13,6 @@ #include "KAssert.h" -// Modelling https://en.cppreference.com/w/cpp/container/span from C++20. - namespace kotlin { namespace std_support { diff --git a/kotlin-native/runtime/src/main/cpp/cpp_support/SpanTest.cpp b/kotlin-native/runtime/src/main/cpp/std_support/SpanTest.cpp similarity index 100% rename from kotlin-native/runtime/src/main/cpp/cpp_support/SpanTest.cpp rename to kotlin-native/runtime/src/main/cpp/std_support/SpanTest.cpp diff --git a/kotlin-native/runtime/src/main/cpp/std_support/StdSupportMemoryTest.cpp b/kotlin-native/runtime/src/main/cpp/std_support/StdSupportMemoryTest.cpp new file mode 100644 index 00000000000..8581cd41595 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/StdSupportMemoryTest.cpp @@ -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; + using Traits = std::allocator_traits; + 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; + WrongClassAllocator base; + using Allocator = typename std::allocator_traits::template rebind_alloc; + using Traits = typename std::allocator_traits::template rebind_traits; + 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(42); + EXPECT_THAT(ptr->x(), 42); +} + +TEST(StdSupportMemoryTest, MakeUniqueThrows) { + EXPECT_THROW(std_support::make_unique(42), int); +} + +TEST(StdSupportMemoryTest, MakeShared) { + auto ptr = std_support::make_shared(42); + EXPECT_THAT(ptr->x(), 42); +} + +TEST(StdSupportMemoryTest, MakeSharedThrows) { + EXPECT_THROW(std_support::make_shared(42), int); +} + +TEST(StdSupportMemoryTest, AllocatorNew) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(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(test_support::MakeAllocator(allocatorCore), 42); + EXPECT_THAT(ptr, expectedPtr); +} + +TEST(StdSupportMemoryTest, AllocatorNewThrows) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(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(test_support::MakeAllocator(allocatorCore), 42), int); +} + +TEST(StdSupportMemoryTest, AllocatorNewWrongType) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(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(test_support::MakeAllocator(allocatorCore), 42); + EXPECT_THAT(ptr, expectedPtr); +} + +TEST(StdSupportMemoryTest, AllocatorDelete) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(13); + + { + testing::InSequence s; + EXPECT_CALL(mocker, dtor(expectedPtr)); + EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass))); + } + std_support::allocator_delete(test_support::MakeAllocator(allocatorCore), expectedPtr); +} + +TEST(StdSupportMemoryTest, AllocatorDeleteWrongType) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(13); + + { + testing::InSequence s; + EXPECT_CALL(mocker, dtor(expectedPtr)); + EXPECT_CALL(allocatorCore, deallocate(expectedPtr, sizeof(MockClass))); + } + std_support::allocator_delete(test_support::MakeAllocator(allocatorCore), expectedPtr); +} + +TEST(StdSupportMemoryTest, AllocateUnique) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(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(test_support::MakeAllocator(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 allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(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(test_support::MakeAllocator(allocatorCore), 42), int); +} + +TEST(StdSupportMemoryTest, AllocateUniqueWrongType) { + testing::StrictMock allocatorCore; + testing::StrictMock mocker; + + MockClass* expectedPtr = reinterpret_cast(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(test_support::MakeAllocator(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 +using UniquePtr = std_support::unique_ptr>; + +TEST(StdSupportMemoryTest, UniquePtrConversions) { + static_assert(std::is_convertible_v, std_support::unique_ptr>); + static_assert(!std::is_convertible_v, std_support::unique_ptr>); + static_assert(!std::is_convertible_v, std_support::unique_ptr>); + static_assert(!std::is_convertible_v, std_support::unique_ptr>); + + static_assert(!std::is_assignable_v, std_support::unique_ptr>); + static_assert(std::is_assignable_v, std_support::unique_ptr>); + static_assert(!std::is_assignable_v, std_support::unique_ptr>); + static_assert(!std::is_assignable_v, std_support::unique_ptr>); + + using AllocatorClass = test_support::Allocator; + using AllocatorDerivedClass = test_support::Allocator; + using AllocatorInt = test_support::Allocator; + + static_assert(std::is_convertible_v, UniquePtr>); + static_assert(std::is_convertible_v, UniquePtr>); + static_assert(std::is_convertible_v, UniquePtr>); + static_assert(std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + static_assert(!std::is_convertible_v, UniquePtr>); + + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(std::is_assignable_v, UniquePtr>); + static_assert(std::is_assignable_v, UniquePtr>); + static_assert(std::is_assignable_v, UniquePtr>); + static_assert(std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); + static_assert(!std::is_assignable_v, UniquePtr>); +} + +TEST(StdSupportMemoryTest, NullptrUnique) { + testing::StrictMock allocatorCore; + auto allocator = test_support::MakeAllocator(allocatorCore); + + std_support::unique_ptr> ptr = + std_support::nullptr_unique(allocator); + EXPECT_THAT(ptr.get(), nullptr); +} diff --git a/kotlin-native/runtime/src/main/cpp/std_support/String.hpp b/kotlin-native/runtime/src/main/cpp/std_support/String.hpp new file mode 100644 index 00000000000..8404013932f --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/String.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template , typename Allocator = allocator> +using basic_string = std::basic_string; + +using string = basic_string; +using wstring = basic_string; +using u16string = basic_string; +using u32string = basic_string; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/UnorderedMap.hpp b/kotlin-native/runtime/src/main/cpp/std_support/UnorderedMap.hpp new file mode 100644 index 00000000000..2e166dd6c16 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/UnorderedMap.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template < + typename Key, + typename T, + typename Hash = std::hash, + typename KeyEqual = std::equal_to, + typename Allocator = allocator>> +using unordered_map = std::unordered_map; + +template < + typename Key, + typename T, + typename Hash = std::hash, + typename KeyEqual = std::equal_to, + typename Allocator = allocator>> +using unordered_multimap = std::unordered_multimap; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/UnorderedSet.hpp b/kotlin-native/runtime/src/main/cpp/std_support/UnorderedSet.hpp new file mode 100644 index 00000000000..ce49fbfacb0 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/UnorderedSet.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template , typename KeyEqual = std::equal_to, typename Allocator = allocator> +using unordered_set = std::unordered_set; + +template , typename KeyEqual = std::equal_to, typename Allocator = allocator> +using unordered_multiset = std::unordered_multiset; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/main/cpp/std_support/Vector.hpp b/kotlin-native/runtime/src/main/cpp/std_support/Vector.hpp new file mode 100644 index 00000000000..c528fc45d40 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/std_support/Vector.hpp @@ -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 + +#include "std_support/Memory.hpp" + +namespace kotlin::std_support { + +template > +using vector = std::vector; + +} // namespace kotlin::std_support diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp index 0714954a888..0d0e585bf6c 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp +++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp @@ -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 { diff --git a/kotlin-native/runtime/src/mm/cpp/ObjectFactory.hpp b/kotlin-native/runtime/src/mm/cpp/ObjectFactory.hpp index cf17a1b795a..2774d160461 100644 --- a/kotlin-native/runtime/src/mm/cpp/ObjectFactory.hpp +++ b/kotlin-native/runtime/src/mm/cpp/ObjectFactory.hpp @@ -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>; 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); } diff --git a/kotlin-native/runtime/src/opt_alloc/cpp/AllocImpl.cpp b/kotlin-native/runtime/src/opt_alloc/cpp/AllocImpl.cpp index c44d7a52fb7..00a36bb5811 100644 --- a/kotlin-native/runtime/src/opt_alloc/cpp/AllocImpl.cpp +++ b/kotlin-native/runtime/src/opt_alloc/cpp/AllocImpl.cpp @@ -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 -#include + +#include 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" diff --git a/kotlin-native/runtime/src/std_alloc/cpp/AllocImpl.cpp b/kotlin-native/runtime/src/std_alloc/cpp/AllocImpl.cpp index c3a165e44d6..88d0cf9282e 100644 --- a/kotlin-native/runtime/src/std_alloc/cpp/AllocImpl.cpp +++ b/kotlin-native/runtime/src/std_alloc/cpp/AllocImpl.cpp @@ -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 -#include -extern "C" { -// Memory operations. -void* konan_calloc_impl(size_t n_elements, size_t elem_size) { - return calloc(n_elements, elem_size); +#include + +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); +}