diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index a268488147c..af07fd42df4 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -3327,6 +3327,10 @@ void RestoreMemory(MemoryState* memoryState) { ::memoryState = memoryState; } +void ClearMemoryForTests(MemoryState*) { + // Nothing to do, DeinitMemory will do the job. +} + OBJ_GETTER(AllocInstanceStrict, const TypeInfo* type_info) { RETURN_RESULT_OF(allocInstance, type_info); } diff --git a/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp index 274b4b37b17..459326ec9e0 100644 --- a/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp @@ -10,6 +10,7 @@ #include "FinalizerHooksTestSupport.hpp" #include "Memory.h" +#include "ObjectTestSupport.hpp" using namespace kotlin; @@ -17,6 +18,11 @@ using ::testing::_; namespace { +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; +}; + class FinalizerHooksTest : public testing::Test { public: testing::MockFunction& finalizerHook() { return finalizerHooks_.finalizerHook(); } @@ -28,55 +34,51 @@ private: } // namespace TEST_F(FinalizerHooksTest, TypeWithFinalizerHookWithoutExtra) { - TypeInfo type; - type.typeInfo_ = &type; - type.flags_ |= TF_HAS_FINALIZER; - ObjHeader obj = {&type}; - ASSERT_FALSE(obj.has_meta_object()); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder().addFlag(TF_HAS_FINALIZER)}; + test_support::Object object(type.typeInfo()); + ObjHeader* obj = object.header(); + ASSERT_FALSE(obj->has_meta_object()); - EXPECT_TRUE(HasFinalizers(&obj)); - EXPECT_CALL(finalizerHook(), Call(&obj)); - RunFinalizers(&obj); - EXPECT_FALSE(obj.has_meta_object()); + EXPECT_TRUE(HasFinalizers(obj)); + EXPECT_CALL(finalizerHook(), Call(obj)); + RunFinalizers(obj); + EXPECT_FALSE(obj->has_meta_object()); } TEST_F(FinalizerHooksTest, TypeWithFinalizerHookWithExtra) { - TypeInfo type; - type.typeInfo_ = &type; - type.flags_ |= TF_HAS_FINALIZER; - ObjHeader obj = {&type}; - ObjHeader::createMetaObject(&obj); - ASSERT_TRUE(obj.has_meta_object()); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder().addFlag(TF_HAS_FINALIZER)}; + test_support::Object object(type.typeInfo()); + ObjHeader* obj = object.header(); + ObjHeader::createMetaObject(obj); + ASSERT_TRUE(obj->has_meta_object()); - EXPECT_TRUE(HasFinalizers(&obj)); - EXPECT_CALL(finalizerHook(), Call(&obj)); - RunFinalizers(&obj); - EXPECT_FALSE(obj.has_meta_object()); + EXPECT_TRUE(HasFinalizers(obj)); + EXPECT_CALL(finalizerHook(), Call(obj)); + RunFinalizers(obj); + EXPECT_FALSE(obj->has_meta_object()); } TEST_F(FinalizerHooksTest, TypeWithoutFinalizerHookWithoutExtra) { - TypeInfo type; - type.typeInfo_ = &type; - type.flags_ &= ~TF_HAS_FINALIZER; - ObjHeader obj = {&type}; - ASSERT_FALSE(obj.has_meta_object()); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); + ObjHeader* obj = object.header(); + ASSERT_FALSE(obj->has_meta_object()); - EXPECT_FALSE(HasFinalizers(&obj)); + EXPECT_FALSE(HasFinalizers(obj)); EXPECT_CALL(finalizerHook(), Call(_)).Times(0); - RunFinalizers(&obj); - EXPECT_FALSE(obj.has_meta_object()); + RunFinalizers(obj); + EXPECT_FALSE(obj->has_meta_object()); } TEST_F(FinalizerHooksTest, TypeWithoutFinalizerHookWithExtra) { - TypeInfo type; - type.typeInfo_ = &type; - type.flags_ &= ~TF_HAS_FINALIZER; - ObjHeader obj = {&type}; - ObjHeader::createMetaObject(&obj); - ASSERT_TRUE(obj.has_meta_object()); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); + ObjHeader* obj = object.header(); + ObjHeader::createMetaObject(obj); + ASSERT_TRUE(obj->has_meta_object()); - EXPECT_TRUE(HasFinalizers(&obj)); + EXPECT_TRUE(HasFinalizers(obj)); EXPECT_CALL(finalizerHook(), Call(_)).Times(0); - RunFinalizers(&obj); - EXPECT_FALSE(obj.has_meta_object()); + RunFinalizers(obj); + EXPECT_FALSE(obj->has_meta_object()); } diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp b/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp index 36042aa7bb4..11193ae2b70 100644 --- a/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp @@ -10,6 +10,7 @@ #include "FreezeHooksTestSupport.hpp" #include "Memory.h" +#include "ObjectTestSupport.hpp" using namespace kotlin; @@ -17,6 +18,11 @@ using ::testing::_; namespace { +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; +}; + class FreezeHooksTest : public testing::Test { public: testing::MockFunction& freezeHook() { return freezeHooks_.freezeHook(); } @@ -28,19 +34,17 @@ private: } // namespace TEST_F(FreezeHooksTest, TypeWithFreezeHook) { - TypeInfo type; - type.typeInfo_ = &type; - type.flags_ |= TF_HAS_FREEZE_HOOK; - ObjHeader obj = {&type}; - EXPECT_CALL(freezeHook(), Call(&obj)); - RunFreezeHooks(&obj); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder().addFlag(TF_HAS_FREEZE_HOOK)}; + test_support::Object object(type.typeInfo()); + ObjHeader* obj = object.header(); + EXPECT_CALL(freezeHook(), Call(obj)); + RunFreezeHooks(obj); } TEST_F(FreezeHooksTest, TypeWithoutFreezeHook) { - TypeInfo type; - type.typeInfo_ = &type; - type.flags_ &= ~TF_HAS_FREEZE_HOOK; - ObjHeader obj = {&type}; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); + ObjHeader* obj = object.header(); EXPECT_CALL(freezeHook(), Call(_)).Times(0); - RunFreezeHooks(&obj); + RunFreezeHooks(obj); } diff --git a/kotlin-native/runtime/src/main/cpp/Memory.h b/kotlin-native/runtime/src/main/cpp/Memory.h index 1f5bbc5afe6..06efe223594 100644 --- a/kotlin-native/runtime/src/main/cpp/Memory.h +++ b/kotlin-native/runtime/src/main/cpp/Memory.h @@ -138,6 +138,7 @@ struct MemoryState; MemoryState* InitMemory(bool firstRuntime); void DeinitMemory(MemoryState*, bool destroyRuntime); void RestoreMemory(MemoryState*); +void ClearMemoryForTests(MemoryState*); // // Object allocation. diff --git a/kotlin-native/runtime/src/main/cpp/ObjectTestSupport.hpp b/kotlin-native/runtime/src/main/cpp/ObjectTestSupport.hpp new file mode 100644 index 00000000000..7a4c54e9e47 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ObjectTestSupport.hpp @@ -0,0 +1,276 @@ +/* + * Copyright 2010-2021 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 "KAssert.h" +#include "Memory.h" +#include "TypeInfo.h" +#include "Types.h" +#include "Utils.hpp" + +namespace kotlin { +namespace test_support { + +// TODO: Some concepts from here can be used in production code. + +class TypeInfoHolder : private Pinned { +private: + class Builder { + protected: + friend class TypeInfoHolder; + + virtual ~Builder() = default; + + int32_t instanceSize_ = 0; + KStdVector objOffsets_; + int32_t flags_ = 0; + }; + +public: + template + class ObjectBuilder : public Builder { + public: + ObjectBuilder() noexcept; + + ObjectBuilder&& addFlag(Konan_TypeFlags flag) noexcept { + flags_ |= flag; + return std::move(*this); + } + + ObjectBuilder&& removeFlag(Konan_TypeFlags flag) noexcept { + flags_ &= ~flag; + return std::move(*this); + } + }; + + template + class ArrayBuilder : public Builder { + public: + ArrayBuilder() noexcept { instanceSize_ = -static_cast(sizeof(Payload)); } + + ArrayBuilder&& addFlag(Konan_TypeFlags flag) noexcept { + flags_ |= flag; + return std::move(*this); + } + + ArrayBuilder&& removeFlag(Konan_TypeFlags flag) noexcept { + flags_ &= ~flag; + return std::move(*this); + } + }; + + explicit TypeInfoHolder(Builder&& builder) noexcept { + typeInfo_.typeInfo_ = &typeInfo_; + typeInfo_.instanceSize_ = builder.instanceSize_; + objOffsets_ = std::move(builder.objOffsets_); + typeInfo_.objOffsets_ = objOffsets_.data(); + if (&typeInfo_ == theArrayTypeInfo) { + // Following RTTIGenerator.kt + typeInfo_.objOffsetsCount_ = 1; + } else { + typeInfo_.objOffsetsCount_ = objOffsets_.size(); + } + typeInfo_.flags_ = builder.flags_; + } + + TypeInfo* typeInfo() noexcept { return &typeInfo_; } + +private: + TypeInfo typeInfo_{}; + KStdVector objOffsets_; +}; + +template +class Object : private Pinned { +public: + class FieldIterator { + public: + FieldIterator(Object& owner, size_t index) noexcept : owner_(owner), index_(index) {} + + ObjHeader*& operator*() noexcept { + auto* header = &owner_.header_; + return *reinterpret_cast(reinterpret_cast(header) + header->type_info()->objOffsets_[index_]); + } + + FieldIterator& operator++() noexcept { + ++index_; + return *this; + } + + bool operator==(const FieldIterator& rhs) const noexcept { return &owner_ == &rhs.owner_ && index_ == rhs.index_; } + + bool operator!=(const FieldIterator& rhs) const noexcept { return !(*this == rhs); } + + private: + Object& owner_; + size_t index_; + }; + + class FieldIterable { + public: + explicit FieldIterable(Object& owner) noexcept : owner_(owner) {} + + size_t size() const noexcept { return owner_.header_.type_info()->objOffsetsCount_; } + + ObjHeader*& operator[](size_t index) noexcept { return *FieldIterator(owner_, index); } + + FieldIterator begin() noexcept { return FieldIterator(owner_, 0); } + FieldIterator end() noexcept { return FieldIterator(owner_, size()); } + + private: + Object& owner_; + }; + + static Object& FromObjHeader(ObjHeader* obj) noexcept { + static_assert(std::is_trivially_destructible_v, "Object destructor is not guaranteed to be called."); + RuntimeAssert( + TypeInfoHolder{TypeInfoHolder::ObjectBuilder()}.typeInfo()->IsLayoutCompatible(obj->type_info()), + "getting object from incompatible ObjHeader"); + auto& object = *reinterpret_cast*>(obj); + RuntimeAssert(object.header() == obj, "Object layout is broken"); + return object; + } + + explicit Object(const TypeInfo* typeInfo) noexcept { + static_assert(std::is_trivially_destructible_v, "Object destructor is not guaranteed to be called."); + RuntimeAssert( + TypeInfoHolder{TypeInfoHolder::ObjectBuilder()}.typeInfo()->IsLayoutCompatible(typeInfo), + "constructing object from incompatible type info"); + header_.typeInfoOrMeta_ = const_cast(typeInfo); + } + + ObjHeader* header() noexcept { return &header_; } + + Payload& operator*() noexcept { return payload_; } + Payload* operator->() noexcept { return &payload_; } + + FieldIterable fields() noexcept { return FieldIterable(*this); } + +private: + ObjHeader header_; + Payload payload_{}; +}; + +template +TypeInfoHolder::ObjectBuilder::ObjectBuilder() noexcept { + instanceSize_ = sizeof(Object); + char c; + Object& object = *reinterpret_cast*>(&c); + auto& payload = *object; + using Field = ObjHeader* Payload::*; + for (Field field : Payload::kFields) { + auto& actualField = payload.*field; + objOffsets_.push_back(reinterpret_cast(&actualField) - reinterpret_cast(object.header())); + } +} + +namespace internal { + +// Array types are predetermined, use one of the subclasses below. +template +class Array : private Pinned { +public: + static Array& FromArrayHeader(ArrayHeader* arr) noexcept { + static_assert(std::is_trivially_destructible_v, "Array destructor is not guaranteed to be called."); + RuntimeAssert( + TypeInfoHolder{TypeInfoHolder::ArrayBuilder()}.typeInfo()->IsLayoutCompatible(arr->type_info()), + "getting array from incompatible ArrayHeader"); + RuntimeAssert(arr->count_ == ElementCount, "getting array from ArrayHeader with different element count"); + auto& array = *reinterpret_cast*>(arr); + RuntimeAssert(array.arrayHeader() == arr, "Array layout is broken"); + return array; + } + + explicit Array(const TypeInfo* typeInfo) noexcept { + static_assert(std::is_trivially_destructible_v, "Array destructor is not guaranteed to be called."); + RuntimeAssert( + TypeInfoHolder{TypeInfoHolder::ArrayBuilder()}.typeInfo()->IsLayoutCompatible(typeInfo), + "constructing array from incompatible type info"); + header_.typeInfoOrMeta_ = const_cast(typeInfo); + header_.count_ = ElementCount; + } + + ObjHeader* header() noexcept { return header_.obj(); } + ArrayHeader* arrayHeader() noexcept { return &header_; } + + std::array& elements() noexcept { return elements_; } + +private: + ArrayHeader header_; + std::array elements_{}; +}; + +} // namespace internal + +template +class ObjectArray : public internal::Array { +public: + ObjectArray() noexcept : internal::Array(theArrayTypeInfo) {} +}; + +template +class BooleanArray : public internal::Array { +public: + BooleanArray() noexcept : internal::Array(theBooleanArrayTypeInfo) {} +}; + +template +class ByteArray : public internal::Array { +public: + ByteArray() noexcept : internal::Array(theByteArrayTypeInfo) {} +}; + +template +class CharArray : public internal::Array { +public: + CharArray() noexcept : internal::Array(theCharArrayTypeInfo) {} +}; + +template +class DoubleArray : public internal::Array { +public: + DoubleArray() noexcept : internal::Array(theDoubleArrayTypeInfo) {} +}; + +template +class FloatArray : public internal::Array { +public: + FloatArray() noexcept : internal::Array(theFloatArrayTypeInfo) {} +}; + +template +class IntArray : public internal::Array { +public: + IntArray() noexcept : internal::Array(theIntArrayTypeInfo) {} +}; + +template +class LongArray : public internal::Array { +public: + LongArray() noexcept : internal::Array(theLongArrayTypeInfo) {} +}; + +template +class NativePtrArray : public internal::Array { +public: + NativePtrArray() noexcept : internal::Array(theNativePtrArrayTypeInfo) {} +}; + +template +class ShortArray : public internal::Array { +public: + ShortArray() noexcept : internal::Array(theShortArrayTypeInfo) {} +}; + +template +class String : public internal::Array { +public: + String() noexcept : internal::Array(theStringTypeInfo) {} +}; + +} // namespace test_support +} // namespace kotlin diff --git a/kotlin-native/runtime/src/main/cpp/ObjectTestSupportTest.cpp b/kotlin-native/runtime/src/main/cpp/ObjectTestSupportTest.cpp new file mode 100644 index 00000000000..209c08816ba --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ObjectTestSupportTest.cpp @@ -0,0 +1,385 @@ +/* + * Copyright 2010-2021 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 "ObjectTestSupport.hpp" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "Natives.h" + +using namespace kotlin; + +namespace { + +struct RegularPayload { + ObjHeader* field1; + ObjHeader* field2; + ObjHeader* field3; + + static constexpr std::array kFields{ + &RegularPayload::field1, + &RegularPayload::field2, + &RegularPayload::field3, + }; +}; + +struct IrregularPayload { + int skipBefore; + ObjHeader* field1; + int skip; + ObjHeader* field2; + std::array skipALot; + ObjHeader* field3; + + static constexpr std::array kFields{ + &IrregularPayload::field1, + &IrregularPayload::field2, + &IrregularPayload::field3, + }; +}; + +struct RegularObjectTestCase { + using Payload = RegularPayload; + + static constexpr const char* name = "RegularPayload"; +}; + +struct IrregularObjectTestCase { + using Payload = IrregularPayload; + + static constexpr const char* name = "IrregularPayload"; +}; + +class ObjectTestCaseNames { +public: + template + static std::string GetName(int i) { + return T::name; + } +}; + +template +class ObjectTestSupportObjectTest : public testing::Test {}; +using ObjectTestCases = testing::Types; +TYPED_TEST_SUITE(ObjectTestSupportObjectTest, ObjectTestCases, ObjectTestCaseNames); + +// TODO: Replace with common implementation. +template +void RunInNewThread(F f) { + std::thread([&f]() { + auto* memory = InitMemory(false); + f(); + ClearMemoryForTests(memory); + DeinitMemory(memory, false); + }).join(); +} + +template +KStdVector Collect(test_support::Object& object) { + KStdVector result; + for (auto& field : object.fields()) { + result.push_back(&field); + } + return result; +} + +} // namespace + +TYPED_TEST(ObjectTestSupportObjectTest, Local) { + using Payload = typename TypeParam::Payload; + + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + + test_support::Object object(type.typeInfo()); + EXPECT_THAT(object.header()->type_info(), type.typeInfo()); + + EXPECT_THAT(object.header()->type_info()->objOffsetsCount_, 3); + + EXPECT_THAT( + reinterpret_cast(&object->field1), + reinterpret_cast(object.header()) + object.header()->type_info()->objOffsets_[0]); + EXPECT_THAT( + reinterpret_cast(&object->field2), + reinterpret_cast(object.header()) + object.header()->type_info()->objOffsets_[1]); + EXPECT_THAT( + reinterpret_cast(&object->field3), + reinterpret_cast(object.header()) + object.header()->type_info()->objOffsets_[2]); + + EXPECT_THAT(object.fields().size(), 3); + + EXPECT_THAT(&object.fields()[0], &object->field1); + EXPECT_THAT(&object.fields()[1], &object->field2); + EXPECT_THAT(&object.fields()[2], &object->field3); + + EXPECT_THAT(Collect(object), testing::ElementsAre(&object->field1, &object->field2, &object->field3)); + + EXPECT_THAT(object.fields()[0], nullptr); + EXPECT_THAT(object.fields()[1], nullptr); + EXPECT_THAT(object.fields()[2], nullptr); + + auto& recoveredObject = test_support::Object::FromObjHeader(object.header()); + EXPECT_THAT(&recoveredObject, &object); +} + +TYPED_TEST(ObjectTestSupportObjectTest, Heap) { + using Payload = typename TypeParam::Payload; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + + RunInNewThread([&type]() { + ObjHolder resultHolder; + ObjHeader* result = AllocInstance(type.typeInfo(), resultHolder.slot()); + ASSERT_THAT(result, testing::Ne(nullptr)); + + auto& object = test_support::Object::FromObjHeader(result); + EXPECT_THAT(object.header(), result); + EXPECT_THAT(object.header()->type_info(), type.typeInfo()); + + EXPECT_THAT(object.header()->type_info()->objOffsetsCount_, 3); + + EXPECT_THAT( + reinterpret_cast(&object->field1), + reinterpret_cast(object.header()) + object.header()->type_info()->objOffsets_[0]); + EXPECT_THAT( + reinterpret_cast(&object->field2), + reinterpret_cast(object.header()) + object.header()->type_info()->objOffsets_[1]); + EXPECT_THAT( + reinterpret_cast(&object->field3), + reinterpret_cast(object.header()) + object.header()->type_info()->objOffsets_[2]); + + EXPECT_THAT(object.fields().size(), 3); + + EXPECT_THAT(&object.fields()[0], &object->field1); + EXPECT_THAT(&object.fields()[1], &object->field2); + EXPECT_THAT(&object.fields()[2], &object->field3); + + EXPECT_THAT(Collect(object), testing::ElementsAre(&object->field1, &object->field2, &object->field3)); + + EXPECT_THAT(object.fields()[0], nullptr); + EXPECT_THAT(object.fields()[1], nullptr); + EXPECT_THAT(object.fields()[2], nullptr); + }); +} + +namespace { + +template +struct PayloadTraits; + +template <> +struct PayloadTraits { + template + using Array = test_support::ObjectArray; + static const TypeInfo* GetTypeInfo() { return theArrayTypeInfo; } + static constexpr const char* name = "Array"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::BooleanArray; + static const TypeInfo* GetTypeInfo() { return theBooleanArrayTypeInfo; } + static constexpr const char* name = "BooleanArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::ByteArray; + static const TypeInfo* GetTypeInfo() { return theByteArrayTypeInfo; } + static constexpr const char* name = "ByteArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::CharArray; + static const TypeInfo* GetTypeInfo() { return theCharArrayTypeInfo; } + static constexpr const char* name = "CharArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::DoubleArray; + static const TypeInfo* GetTypeInfo() { return theDoubleArrayTypeInfo; } + static constexpr const char* name = "DoubleArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::FloatArray; + static const TypeInfo* GetTypeInfo() { return theFloatArrayTypeInfo; } + static constexpr const char* name = "FloatArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::IntArray; + static const TypeInfo* GetTypeInfo() { return theIntArrayTypeInfo; } + static constexpr const char* name = "IntArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::LongArray; + static const TypeInfo* GetTypeInfo() { return theLongArrayTypeInfo; } + static constexpr const char* name = "LongArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::NativePtrArray; + static const TypeInfo* GetTypeInfo() { return theNativePtrArrayTypeInfo; } + static constexpr const char* name = "NativePtrArray"; +}; + +template <> +struct PayloadTraits { + template + using Array = test_support::ShortArray; + static const TypeInfo* GetTypeInfo() { return theShortArrayTypeInfo; } + static constexpr const char* name = "ShortArray"; +}; + +template +struct SizeTraits; + +template <> +struct SizeTraits<0> { + static constexpr const char* name = "Empty"; +}; + +template <> +struct SizeTraits<3> { + static constexpr const char* name = ""; +}; + +template +struct ArrayTestCase { + using Payload = T; + using Array = typename PayloadTraits::template Array; + + static constexpr size_t size = Size; + static const TypeInfo* GetTypeInfo() { return PayloadTraits::GetTypeInfo(); } + static std::string GetName() { return std::string(SizeTraits::name) + std::string(PayloadTraits::name); } +}; + +template +struct StringTestCase { + using Payload = KChar; + using Array = test_support::String; + + static constexpr size_t size = Size; + static const TypeInfo* GetTypeInfo() { return theStringTypeInfo; } + static std::string GetName() { return std::string(SizeTraits::name) + std::string("String"); } +}; + +class ArrayTestCaseNames { +public: + template + static std::string GetName(int i) { + return T::GetName(); + } +}; + +template +class ObjectTestSupportArrayTest : public testing::Test {}; +using ArrayTestCases = testing::Types< + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + ArrayTestCase, + StringTestCase<0>, + StringTestCase<3>>; +TYPED_TEST_SUITE(ObjectTestSupportArrayTest, ArrayTestCases, ArrayTestCaseNames); + +template +KStdVector Collect(test_support::internal::Array& array) { + KStdVector result; + for (auto& element : array.elements()) { + result.push_back(&element); + } + return result; +} + +} // namespace + +TYPED_TEST(ObjectTestSupportArrayTest, Local) { + using Payload = typename TypeParam::Payload; + using Array = typename TypeParam::Array; + const auto typeInfo = TypeParam::GetTypeInfo(); + constexpr auto size = TypeParam::size; + + Array array; + + EXPECT_THAT(array.header()->type_info(), typeInfo); + EXPECT_THAT(array.arrayHeader()->count_, size); + EXPECT_THAT(array.elements().size(), size); + + KStdVector expected; + for (size_t i = 0; i < size; ++i) { + auto* element = AddressOfElementAt(array.arrayHeader(), i); + EXPECT_THAT(&array.elements()[i], element); + EXPECT_THAT(array.elements()[i], Payload{}); + expected.push_back(element); + } + + EXPECT_THAT(Collect(array), testing::ElementsAreArray(expected)); + + auto& recoveredArray = Array::FromArrayHeader(array.arrayHeader()); + EXPECT_THAT(&recoveredArray, &array); +} + +TYPED_TEST(ObjectTestSupportArrayTest, Heap) { + using Payload = typename TypeParam::Payload; + using Array = typename TypeParam::Array; + const auto typeInfo = TypeParam::GetTypeInfo(); + constexpr auto size = TypeParam::size; + + RunInNewThread([typeInfo]() { + ObjHolder resultHolder; + ObjHeader* result = AllocArrayInstance(typeInfo, size, resultHolder.slot()); + ASSERT_THAT(result, testing::Ne(nullptr)); + + auto& array = Array::FromArrayHeader(result->array()); + EXPECT_THAT(array.header(), result); + EXPECT_THAT(array.header()->type_info(), typeInfo); + EXPECT_THAT(array.arrayHeader()->count_, size); + EXPECT_THAT(array.elements().size(), size); + + KStdVector expected; + for (size_t i = 0; i < size; ++i) { + auto* element = AddressOfElementAt(array.arrayHeader(), i); + EXPECT_THAT(&array.elements()[i], element); + EXPECT_THAT(array.elements()[i], Payload{}); + expected.push_back(element); + } + + EXPECT_THAT(Collect(array), testing::ElementsAreArray(expected)); + }); +} diff --git a/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp b/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp index 87f94a787ef..903d776f3cb 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp @@ -8,6 +8,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ObjectTestSupport.hpp" #include "Types.h" #include "Utils.hpp" @@ -17,47 +18,6 @@ using ::testing::_; namespace { -template -class Object : private Pinned { -public: - Object() { - header_.typeInfoOrMeta_ = &type_; - type_.typeInfo_ = &type_; - type_.objOffsetsCount_ = Count; - type_.objOffsets_ = fieldOffsets_.data(); - for (size_t i = 0; i < Count; ++i) { - fieldOffsets_[i] = reinterpret_cast(&fields_[i]) - reinterpret_cast(&header_); - } - } - - ObjHeader* header() { return &header_; } - - ObjHeader*& operator[](size_t index) { return fields_[index]; } - -private: - ObjHeader header_; - TypeInfo type_; - std::array fieldOffsets_; - std::array fields_{}; -}; - -template -class Array : private Pinned { -public: - Array() { - header_.typeInfoOrMeta_ = const_cast(theArrayTypeInfo); - header_.count_ = Count; - } - - ObjHeader* header() { return header_.obj(); } - - ObjHeader*& operator[](size_t index) { return fields_[index]; } - -private: - ArrayHeader header_; - std::array fields_{}; -}; - struct CallableWithExceptions { void operator()(ObjHeader*) noexcept(false) {} void operator()(ObjHeader**) noexcept(false) {} @@ -68,6 +28,23 @@ struct CallableWithoutExceptions { void operator()(ObjHeader**) noexcept {} }; +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; +}; + +struct Payload { + ObjHeader* field1; + ObjHeader* field2; + ObjHeader* field3; + + static constexpr std::array kFields{ + &Payload::field1, + &Payload::field2, + &Payload::field3, + }; +}; + } // namespace TEST(ObjectTraversalTest, TraverseFieldsExceptions) { @@ -80,7 +57,8 @@ TEST(ObjectTraversalTest, TraverseFieldsExceptions) { } TEST(ObjectTraversalTest, TraverseEmptyObjectFields) { - Object<0> object; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); testing::StrictMock> process; EXPECT_CALL(process, Call(_)).Times(0); @@ -88,34 +66,36 @@ TEST(ObjectTraversalTest, TraverseEmptyObjectFields) { } TEST(ObjectTraversalTest, TraverseObjectFields) { + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; ObjHeader field1; ObjHeader field3; - Object<3> object; - object[0] = &field1; - object[2] = &field3; + test_support::Object object(type.typeInfo()); + object->field1 = &field1; + object->field3 = &field3; testing::StrictMock> process; - EXPECT_CALL(process, Call(&object[0])); - EXPECT_CALL(process, Call(&object[1])); - EXPECT_CALL(process, Call(&object[2])); + EXPECT_CALL(process, Call(&object->field1)); + EXPECT_CALL(process, Call(&object->field2)); + EXPECT_CALL(process, Call(&object->field3)); traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); } TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) { constexpr int kException = 1; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; ObjHeader field1; ObjHeader field2; ObjHeader field3; - Object<3> object; - object[0] = &field1; - object[1] = &field2; - object[2] = &field3; + test_support::Object object(type.typeInfo()); + object->field1 = &field1; + object->field2 = &field2; + object->field3 = &field3; testing::StrictMock> process; - EXPECT_CALL(process, Call(&object[0])); - EXPECT_CALL(process, Call(&object[1])).WillOnce([]() { throw kException; }); - EXPECT_CALL(process, Call(&object[2])).Times(0); + EXPECT_CALL(process, Call(&object->field1)); + EXPECT_CALL(process, Call(&object->field2)).WillOnce([]() { throw kException; }); + EXPECT_CALL(process, Call(&object->field3)).Times(0); try { traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); } catch (int exception) { @@ -126,7 +106,7 @@ TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) { } TEST(ObjectTraversalTest, TraverseEmptyArrayFields) { - Array<0> array; + test_support::ObjectArray<0> array; testing::StrictMock> process; EXPECT_CALL(process, Call(_)).Times(0); @@ -136,14 +116,14 @@ TEST(ObjectTraversalTest, TraverseEmptyArrayFields) { TEST(ObjectTraversalTest, TraverseArrayFields) { ObjHeader element1; ObjHeader element3; - Array<3> array; - array[0] = &element1; - array[2] = &element3; + test_support::ObjectArray<3> array; + array.elements()[0] = &element1; + array.elements()[2] = &element3; testing::StrictMock> process; - EXPECT_CALL(process, Call(&array[0])); - EXPECT_CALL(process, Call(&array[1])); - EXPECT_CALL(process, Call(&array[2])); + EXPECT_CALL(process, Call(&array.elements()[0])); + EXPECT_CALL(process, Call(&array.elements()[1])); + EXPECT_CALL(process, Call(&array.elements()[2])); traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); } @@ -153,15 +133,15 @@ TEST(ObjectTraversalTest, TraverseArrayFieldsWithException) { ObjHeader element1; ObjHeader element2; ObjHeader element3; - Array<3> array; - array[0] = &element1; - array[1] = &element2; - array[2] = &element3; + test_support::ObjectArray<3> array; + array.elements()[0] = &element1; + array.elements()[1] = &element2; + array.elements()[2] = &element3; testing::StrictMock> process; - EXPECT_CALL(process, Call(&array[0])); - EXPECT_CALL(process, Call(&array[1])).WillOnce([]() { throw kException; }); - EXPECT_CALL(process, Call(&array[2])).Times(0); + EXPECT_CALL(process, Call(&array.elements()[0])); + EXPECT_CALL(process, Call(&array.elements()[1])).WillOnce([]() { throw kException; }); + EXPECT_CALL(process, Call(&array.elements()[2])).Times(0); try { traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); } catch (int exception) { @@ -181,7 +161,8 @@ TEST(ObjectTraversalTest, TraverseRefsExceptions) { } TEST(ObjectTraversalTest, TraverseEmptyObjectRefs) { - Object<0> object; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); testing::StrictMock> process; EXPECT_CALL(process, Call(_)).Times(0); @@ -189,11 +170,12 @@ TEST(ObjectTraversalTest, TraverseEmptyObjectRefs) { } TEST(ObjectTraversalTest, TraverseObjectRefs) { + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; ObjHeader field1; ObjHeader field3; - Object<3> object; - object[0] = &field1; - object[2] = &field3; + test_support::Object object(type.typeInfo()); + object->field1 = &field1; + object->field3 = &field3; testing::StrictMock> process; EXPECT_CALL(process, Call(&field1)); @@ -204,13 +186,14 @@ TEST(ObjectTraversalTest, TraverseObjectRefs) { TEST(ObjectTraversalTest, TraverseObjectRefsWithException) { constexpr int kException = 1; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; ObjHeader field1; ObjHeader field2; ObjHeader field3; - Object<3> object; - object[0] = &field1; - object[1] = &field2; - object[2] = &field3; + test_support::Object object(type.typeInfo()); + object->field1 = &field1; + object->field2 = &field2; + object->field3 = &field3; testing::StrictMock> process; EXPECT_CALL(process, Call(&field1)); @@ -226,7 +209,7 @@ TEST(ObjectTraversalTest, TraverseObjectRefsWithException) { } TEST(ObjectTraversalTest, TraverseEmptyArrayRefs) { - Array<0> array; + test_support::ObjectArray<0> array; testing::StrictMock> process; EXPECT_CALL(process, Call(_)).Times(0); @@ -236,9 +219,9 @@ TEST(ObjectTraversalTest, TraverseEmptyArrayRefs) { TEST(ObjectTraversalTest, TraverseArrayRefs) { ObjHeader element1; ObjHeader element3; - Array<3> array; - array[0] = &element1; - array[2] = &element3; + test_support::ObjectArray<3> array; + array.elements()[0] = &element1; + array.elements()[2] = &element3; testing::StrictMock> process; EXPECT_CALL(process, Call(&element1)); @@ -252,10 +235,10 @@ TEST(ObjectTraversalTest, TraverseArrayRefsWithException) { ObjHeader element1; ObjHeader element2; ObjHeader element3; - Array<3> array; - array[0] = &element1; - array[1] = &element2; - array[2] = &element3; + test_support::ObjectArray<3> array; + array.elements()[0] = &element1; + array.elements()[1] = &element2; + array.elements()[2] = &element3; testing::StrictMock> process; EXPECT_CALL(process, Call(&element1)); diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfo.h b/kotlin-native/runtime/src/main/cpp/TypeInfo.h index 69200b72a79..de2d9a9bf1e 100644 --- a/kotlin-native/runtime/src/main/cpp/TypeInfo.h +++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.h @@ -17,6 +17,7 @@ #ifndef RUNTIME_TYPEINFO_H #define RUNTIME_TYPEINFO_H +#include #include #include "Common.h" @@ -160,6 +161,18 @@ struct TypeInfo { } inline bool IsArray() const { return instanceSize_ < 0; } + + bool IsLayoutCompatible(const TypeInfo* rhs) const noexcept { + // TODO: Use debug info if it's present? + // This automatically checks array vs object discrepancy. + if (instanceSize_ != rhs->instanceSize_) return false; + if (!IsArray()) { + if (!std::equal(objOffsets_, objOffsets_ + objOffsetsCount_, rhs->objOffsets_, rhs->objOffsets_ + rhs->objOffsetsCount_)) { + return false; + } + } + return true; + } #endif }; diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfoTest.cpp b/kotlin-native/runtime/src/main/cpp/TypeInfoTest.cpp new file mode 100644 index 00000000000..da376b6c0fa --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/TypeInfoTest.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2021 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 "TypeInfo.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "ObjectTestSupport.hpp" +#include "Types.h" + +using namespace kotlin; + +namespace { + +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; +}; + +struct Payload1 { + ObjHeader* field1; + ObjHeader* field2; + + static constexpr std::array kFields{ + &Payload1::field1, + &Payload1::field2, + }; +}; + +struct Payload2 { + ObjHeader* field1; + ObjHeader* field2; + + static constexpr std::array kFields{ + &Payload2::field1, + &Payload2::field2, + }; +}; + +test_support::TypeInfoHolder emptyObjectTypeHolder{test_support::TypeInfoHolder::ObjectBuilder()}; +test_support::TypeInfoHolder object1TypeHolder{test_support::TypeInfoHolder::ObjectBuilder()}; +test_support::TypeInfoHolder object2TypeHolder{test_support::TypeInfoHolder::ObjectBuilder()}; + +const TypeInfo* emptyObjectType = emptyObjectTypeHolder.typeInfo(); +const TypeInfo* object1Type = object1TypeHolder.typeInfo(); +const TypeInfo* object2Type = object2TypeHolder.typeInfo(); + +using LayoutCompatibleTestParam = std::tuple; + +class LayoutCompatibleTest : public testing::TestWithParam { +public: + static std::string Print(const testing::TestParamInfo& param) { return std::get<3>(param.param); } + + const TypeInfo* lhsType() { return std::get<0>(GetParam()); } + const TypeInfo* rhsType() { return std::get<1>(GetParam()); } + bool expectCompatible() { return std::get<2>(GetParam()); } +}; + +INSTANTIATE_TEST_SUITE_P( + , + LayoutCompatibleTest, + testing::Values( + std::make_tuple(emptyObjectType, emptyObjectType, true, "empty_empty"), + std::make_tuple(emptyObjectType, object1Type, false, "empty_obj1"), + std::make_tuple(emptyObjectType, object2Type, false, "empty_obj2"), + std::make_tuple(emptyObjectType, theArrayTypeInfo, false, "empty_arr"), + std::make_tuple(emptyObjectType, theCharArrayTypeInfo, false, "empty_charArr"), + + std::make_tuple(object1Type, emptyObjectType, false, "obj1_empty"), + std::make_tuple(object1Type, object1Type, true, "obj1_obj1"), + std::make_tuple(object1Type, object2Type, true, "obj1_obj2"), + std::make_tuple(object1Type, theArrayTypeInfo, false, "obj1_arr"), + std::make_tuple(object1Type, theCharArrayTypeInfo, false, "obj1_charArr"), + + std::make_tuple(object2Type, emptyObjectType, false, "obj2_empty"), + std::make_tuple(object2Type, object1Type, true, "obj2_obj1"), + std::make_tuple(object2Type, object2Type, true, "obj2_obj2"), + std::make_tuple(object2Type, theArrayTypeInfo, false, "obj2_arr"), + std::make_tuple(object2Type, theCharArrayTypeInfo, false, "obj2_charArr"), + + std::make_tuple(theArrayTypeInfo, emptyObjectType, false, "arr_empty"), + std::make_tuple(theArrayTypeInfo, object1Type, false, "arr_obj1"), + std::make_tuple(theArrayTypeInfo, object2Type, false, "arr_obj2"), + std::make_tuple(theArrayTypeInfo, theArrayTypeInfo, true, "arr_arr"), + std::make_tuple(theArrayTypeInfo, theCharArrayTypeInfo, false, "arr_charArr"), + + std::make_tuple(theCharArrayTypeInfo, emptyObjectType, false, "charArr_empty"), + std::make_tuple(theCharArrayTypeInfo, object1Type, false, "charArr_obj1"), + std::make_tuple(theCharArrayTypeInfo, object2Type, false, "charArr_obj2"), + std::make_tuple(theCharArrayTypeInfo, theArrayTypeInfo, false, "charArr_arr"), + std::make_tuple(theCharArrayTypeInfo, theCharArrayTypeInfo, true, "charArr_charArr")), + &LayoutCompatibleTest::Print); + +} // namespace + +TEST_P(LayoutCompatibleTest, IsLayoutCompatible) { + EXPECT_THAT(lhsType()->IsLayoutCompatible(rhsType()), expectCompatible()); +} diff --git a/kotlin-native/runtime/src/mm/cpp/ExceptionObjHolderTest.cpp b/kotlin-native/runtime/src/mm/cpp/ExceptionObjHolderTest.cpp index 30e5a919c23..6f7d7d62f02 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExceptionObjHolderTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ExceptionObjHolderTest.cpp @@ -19,11 +19,6 @@ namespace { class ExceptionObjHolderTest : public ::testing::Test { public: - ~ExceptionObjHolderTest() { - auto& stableRefs = mm::StableRefRegistry::Instance(); - stableRefs.ClearForTests(); - } - static KStdVector Collect(mm::ThreadData& threadData) { auto& stableRefs = mm::StableRefRegistry::Instance(); stableRefs.ProcessThread(&threadData); diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp index 594c47e0305..e17344a2f18 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp @@ -11,35 +11,42 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ObjectTestSupport.hpp" #include "TestSupport.hpp" using namespace kotlin; +namespace { + +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; +}; + +} // namespace + TEST(ExtraObjectDataTest, Install) { - TypeInfo typeInfo; - typeInfo.typeInfo_ = &typeInfo; - ObjHeader object; - object.typeInfoOrMeta_ = &typeInfo; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); + auto* typeInfo = object.header()->type_info(); - ASSERT_FALSE(object.has_meta_object()); + ASSERT_FALSE(object.header()->has_meta_object()); - auto& extraData = mm::ExtraObjectData::Install(&object); + auto& extraData = mm::ExtraObjectData::Install(object.header()); - EXPECT_TRUE(object.has_meta_object()); - EXPECT_THAT(object.meta_object(), extraData.AsMetaObjHeader()); - EXPECT_THAT(object.type_info(), &typeInfo); + EXPECT_TRUE(object.header()->has_meta_object()); + EXPECT_THAT(object.header()->meta_object(), extraData.AsMetaObjHeader()); + EXPECT_THAT(object.header()->type_info(), typeInfo); - mm::ExtraObjectData::Uninstall(&object); + mm::ExtraObjectData::Uninstall(object.header()); - EXPECT_FALSE(object.has_meta_object()); - EXPECT_THAT(object.type_info(), &typeInfo); + EXPECT_FALSE(object.header()->has_meta_object()); + EXPECT_THAT(object.header()->type_info(), typeInfo); } TEST(ExtraObjectDataTest, ConcurrentInstall) { - TypeInfo typeInfo; - typeInfo.typeInfo_ = &typeInfo; - ObjHeader object; - object.typeInfoOrMeta_ = &typeInfo; + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; + test_support::Object object(type.typeInfo()); constexpr int kThreadCount = kDefaultThreadCount; @@ -53,7 +60,7 @@ TEST(ExtraObjectDataTest, ConcurrentInstall) { ++readyCount; while (!canStart) { } - auto& extraData = mm::ExtraObjectData::Install(&object); + auto& extraData = mm::ExtraObjectData::Install(object.header()); actual[i] = &extraData; }); } @@ -70,5 +77,5 @@ TEST(ExtraObjectDataTest, ConcurrentInstall) { EXPECT_THAT(actual, testing::ElementsAreArray(expected)); - mm::ExtraObjectData::Uninstall(&object); + mm::ExtraObjectData::Uninstall(object.header()); } diff --git a/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp b/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp index f63e08f4bf9..f0ed3ccc44a 100644 --- a/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp @@ -11,6 +11,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ObjectTestSupport.hpp" #include "TestSupport.hpp" #include "ThreadData.hpp" #include "Types.h" @@ -21,12 +22,14 @@ using testing::_; namespace { +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; +}; + class InitSingletonTest : public testing::Test { public: InitSingletonTest() { - typeInfo_.typeInfo_ = &typeInfo_; - typeInfo_.instanceSize_ = sizeof(ObjHeader); - globalConstructor_ = &constructor_; for (auto& threadData : threadDatas_) { @@ -38,8 +41,7 @@ public: globalConstructor_ = nullptr; // Make sure to clean everything allocated by the tests. for (auto& threadData : threadDatas_) { - threadData->objectFactoryThreadQueue().ClearForTests(); - threadData->globalsThreadQueue().ClearForTests(); + threadData->ClearForTests(); } } @@ -48,18 +50,18 @@ public: testing::MockFunction& constructor() { return constructor_; } OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, size_t threadIndex) { - RETURN_RESULT_OF(mm::InitThreadLocalSingleton, threadDatas_[threadIndex].get(), location, &typeInfo_, constructorImpl); + RETURN_RESULT_OF(mm::InitThreadLocalSingleton, threadDatas_[threadIndex].get(), location, type_.typeInfo(), constructorImpl); } OBJ_GETTER(InitSingleton, ObjHeader** location, size_t threadIndex) { - RETURN_RESULT_OF(mm::InitSingleton, threadDatas_[threadIndex].get(), location, &typeInfo_, constructorImpl); + RETURN_RESULT_OF(mm::InitSingleton, threadDatas_[threadIndex].get(), location, type_.typeInfo(), constructorImpl); } private: testing::StrictMock> constructor_; // TODO: It makes sense to somehow abstract `ThreadData` stuff away. Allocation in this case. std::array, kDefaultThreadCount> threadDatas_; - TypeInfo typeInfo_; // Only used for allocator calls, uninteresting for these tests. + test_support::TypeInfoHolder type_{test_support::TypeInfoHolder::ObjectBuilder()}; static testing::MockFunction* globalConstructor_; diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index 37994c8e530..8662afe2073 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -116,6 +116,11 @@ extern "C" void RestoreMemory(MemoryState*) { // TODO: Remove when legacy MM is gone. } +extern "C" void ClearMemoryForTests(MemoryState* state) { + auto* threadData = FromMemoryState(state)->Get(); + threadData->ClearForTests(); +} + extern "C" RUNTIME_NOTHROW OBJ_GETTER(AllocInstance, const TypeInfo* typeInfo) { auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData(); RETURN_RESULT_OF(mm::AllocateObject, threadData, typeInfo); diff --git a/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp b/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp index 4f1f3f5ff79..3129b78a304 100644 --- a/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp @@ -13,6 +13,7 @@ #include "gtest/gtest.h" #include "GC.hpp" +#include "ObjectTestSupport.hpp" #include "TestSupport.hpp" #include "Types.h" @@ -745,29 +746,25 @@ public: using ObjectFactory = mm::ObjectFactory; -KStdUniquePtr MakeObjectTypeInfo(int32_t size) { - auto typeInfo = make_unique(); - typeInfo->typeInfo_ = typeInfo.get(); - typeInfo->instanceSize_ = size; - return typeInfo; -} +struct Payload { + ObjHeader* field1; + ObjHeader* field2; -KStdUniquePtr MakeArrayTypeInfo(int32_t elementSize) { - auto typeInfo = make_unique(); - typeInfo->typeInfo_ = typeInfo.get(); - typeInfo->instanceSize_ = -elementSize; - return typeInfo; -} + static constexpr std::array kFields{ + &Payload::field1, + &Payload::field2, + }; +}; } // namespace TEST(ObjectFactoryTest, CreateObject) { - auto typeInfo = MakeObjectTypeInfo(24); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; GC::ThreadData gc; ObjectFactory objectFactory; ObjectFactory::ThreadQueue threadQueue(objectFactory, gc); - auto* object = threadQueue.CreateObject(typeInfo.get()); + auto* object = threadQueue.CreateObject(type.typeInfo()); threadQueue.Publish(); auto node = ObjectFactory::NodeRef::From(object); @@ -782,13 +779,32 @@ TEST(ObjectFactoryTest, CreateObject) { EXPECT_THAT(it, iter.end()); } -TEST(ObjectFactoryTest, CreateArray) { - auto typeInfo = MakeArrayTypeInfo(24); +TEST(ObjectFactoryTest, CreateObjectArray) { GC::ThreadData gc; ObjectFactory objectFactory; ObjectFactory::ThreadQueue threadQueue(objectFactory, gc); - auto* array = threadQueue.CreateArray(typeInfo.get(), 3); + auto* array = threadQueue.CreateArray(theArrayTypeInfo, 3); + threadQueue.Publish(); + + auto node = ObjectFactory::NodeRef::From(array); + EXPECT_TRUE(node.IsArray()); + EXPECT_THAT(node.GetArrayHeader(), array); + EXPECT_THAT(node.GCObjectData().flags, 42); + + auto iter = objectFactory.Iter(); + auto it = iter.begin(); + EXPECT_THAT(*it, node); + ++it; + EXPECT_THAT(it, iter.end()); +} + +TEST(ObjectFactoryTest, CreateCharArray) { + GC::ThreadData gc; + ObjectFactory objectFactory; + ObjectFactory::ThreadQueue threadQueue(objectFactory, gc); + + auto* array = threadQueue.CreateArray(theCharArrayTypeInfo, 3); threadQueue.Publish(); auto node = ObjectFactory::NodeRef::From(array); @@ -804,15 +820,14 @@ TEST(ObjectFactoryTest, CreateArray) { } TEST(ObjectFactoryTest, Erase) { - auto objectTypeInfo = MakeObjectTypeInfo(24); - auto arrayTypeInfo = MakeArrayTypeInfo(24); + test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder()}; GC::ThreadData gc; ObjectFactory objectFactory; ObjectFactory::ThreadQueue threadQueue(objectFactory, gc); for (int i = 0; i < 10; ++i) { - threadQueue.CreateObject(objectTypeInfo.get()); - threadQueue.CreateArray(arrayTypeInfo.get(), 3); + threadQueue.CreateObject(objectType.typeInfo()); + threadQueue.CreateArray(theArrayTypeInfo, 3); } threadQueue.Publish(); @@ -839,16 +854,15 @@ TEST(ObjectFactoryTest, Erase) { } TEST(ObjectFactoryTest, Move) { - auto objectTypeInfo = MakeObjectTypeInfo(24); - auto arrayTypeInfo = MakeArrayTypeInfo(24); + test_support::TypeInfoHolder objectType{test_support::TypeInfoHolder::ObjectBuilder()}; GC::ThreadData gc; ObjectFactory objectFactory; ObjectFactory::ThreadQueue threadQueue(objectFactory, gc); ObjectFactory::FinalizerQueue finalizerQueue; for (int i = 0; i < 10; ++i) { - threadQueue.CreateObject(objectTypeInfo.get()); - threadQueue.CreateArray(arrayTypeInfo.get(), 3); + threadQueue.CreateObject(objectType.typeInfo()); + threadQueue.CreateArray(theArrayTypeInfo, 3); } threadQueue.Publish(); @@ -883,7 +897,7 @@ TEST(ObjectFactoryTest, Move) { } TEST(ObjectFactoryTest, ConcurrentPublish) { - auto typeInfo = MakeObjectTypeInfo(24); + test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder()}; ObjectFactory objectFactory; constexpr int kThreadCount = kDefaultThreadCount; std::atomic canStart(false); @@ -893,10 +907,10 @@ TEST(ObjectFactoryTest, ConcurrentPublish) { KStdVector expected; for (int i = 0; i < kThreadCount; ++i) { - threads.emplace_back([&typeInfo, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() { + threads.emplace_back([&type, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() { GC::ThreadData gc; ObjectFactory::ThreadQueue threadQueue(objectFactory, gc); - auto* object = threadQueue.CreateObject(typeInfo.get()); + auto* object = threadQueue.CreateObject(type.typeInfo()); { std::lock_guard guard(expectedMutex); expected.push_back(object); diff --git a/kotlin-native/runtime/src/mm/cpp/TestSupport.hpp b/kotlin-native/runtime/src/mm/cpp/TestSupport.hpp index 62800541de8..9937a45bad9 100644 --- a/kotlin-native/runtime/src/mm/cpp/TestSupport.hpp +++ b/kotlin-native/runtime/src/mm/cpp/TestSupport.hpp @@ -29,6 +29,8 @@ void RunInNewThread(F f) { } registration; f(registration.threadData()); + + registration.threadData().ClearForTests(); }).join(); } diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp b/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp index 6685edc9be0..864688d308a 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp @@ -66,6 +66,12 @@ public: objectFactoryThreadQueue_.Publish(); } + void ClearForTests() noexcept { + globalsThreadQueue_.ClearForTests(); + stableRefThreadQueue_.ClearForTests(); + objectFactoryThreadQueue_.ClearForTests(); + } + private: const pthread_t threadId_; GlobalsRegistry::ThreadQueue globalsThreadQueue_; diff --git a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp index 7de71d7e71d..50c367bac55 100644 --- a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp +++ b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp @@ -5,42 +5,42 @@ #include "TestSupportCompilerGenerated.hpp" +#include "ObjectTestSupport.hpp" #include "Types.h" namespace { -class TypeInfoImpl { -public: - TypeInfoImpl() { type_.typeInfo_ = &type_; } - - TypeInfo* type() { return &type_; } - -private: - TypeInfo type_; +struct EmptyPayload { + using Field = ObjHeader* EmptyPayload::*; + static constexpr std::array kFields{}; }; -TypeInfoImpl theAnyTypeInfoImpl; -TypeInfoImpl theArrayTypeInfoImpl; -TypeInfoImpl theBooleanArrayTypeInfoImpl; -TypeInfoImpl theByteArrayTypeInfoImpl; -TypeInfoImpl theCharArrayTypeInfoImpl; -TypeInfoImpl theDoubleArrayTypeInfoImpl; -TypeInfoImpl theFloatArrayTypeInfoImpl; -TypeInfoImpl theForeignObjCObjectTypeInfoImpl; -TypeInfoImpl theFreezableAtomicReferenceTypeInfoImpl; -TypeInfoImpl theIntArrayTypeInfoImpl; -TypeInfoImpl theLongArrayTypeInfoImpl; -TypeInfoImpl theNativePtrArrayTypeInfoImpl; -TypeInfoImpl theObjCObjectWrapperTypeInfoImpl; -TypeInfoImpl theOpaqueFunctionTypeInfoImpl; -TypeInfoImpl theShortArrayTypeInfoImpl; -TypeInfoImpl theStringTypeInfoImpl; -TypeInfoImpl theThrowableTypeInfoImpl; -TypeInfoImpl theUnitTypeInfoImpl; -TypeInfoImpl theWorkerBoundReferenceTypeInfoImpl; -TypeInfoImpl theCleanerImplTypeInfoImpl; +kotlin::test_support::TypeInfoHolder theAnyTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theBooleanArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theByteArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theCharArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theDoubleArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theFloatArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theForeignObjCObjectTypeInfoHolder{ + kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theFreezableAtomicReferenceTypeInfoHolder{ + kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theIntArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theLongArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theNativePtrArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theObjCObjectWrapperTypeInfoHolder{ + kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theOpaqueFunctionTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theShortArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theStringTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder()}; +kotlin::test_support::TypeInfoHolder theThrowableTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theUnitTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theWorkerBoundReferenceTypeInfoHolder{ + kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; +kotlin::test_support::TypeInfoHolder theCleanerImplTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder()}; -ArrayHeader theEmptyStringImpl = {theStringTypeInfoImpl.type(), /* element count */ 0}; +ArrayHeader theEmptyStringImpl = {theStringTypeInfoHolder.typeInfo(), /* element count */ 0}; template struct KBox { @@ -58,28 +58,28 @@ extern "C" { // Set to 1 to enable runtime assertions. extern const int KonanNeedDebugInfo = 1; -extern const TypeInfo* theAnyTypeInfo = theAnyTypeInfoImpl.type(); -extern const TypeInfo* theArrayTypeInfo = theArrayTypeInfoImpl.type(); -extern const TypeInfo* theBooleanArrayTypeInfo = theBooleanArrayTypeInfoImpl.type(); -extern const TypeInfo* theByteArrayTypeInfo = theByteArrayTypeInfoImpl.type(); -extern const TypeInfo* theCharArrayTypeInfo = theCharArrayTypeInfoImpl.type(); -extern const TypeInfo* theDoubleArrayTypeInfo = theDoubleArrayTypeInfoImpl.type(); -extern const TypeInfo* theFloatArrayTypeInfo = theFloatArrayTypeInfoImpl.type(); -extern const TypeInfo* theForeignObjCObjectTypeInfo = theForeignObjCObjectTypeInfoImpl.type(); -extern const TypeInfo* theFreezableAtomicReferenceTypeInfo = theFreezableAtomicReferenceTypeInfoImpl.type(); -extern const TypeInfo* theIntArrayTypeInfo = theIntArrayTypeInfoImpl.type(); -extern const TypeInfo* theLongArrayTypeInfo = theLongArrayTypeInfoImpl.type(); -extern const TypeInfo* theNativePtrArrayTypeInfo = theNativePtrArrayTypeInfoImpl.type(); -extern const TypeInfo* theObjCObjectWrapperTypeInfo = theObjCObjectWrapperTypeInfoImpl.type(); -extern const TypeInfo* theOpaqueFunctionTypeInfo = theOpaqueFunctionTypeInfoImpl.type(); -extern const TypeInfo* theShortArrayTypeInfo = theShortArrayTypeInfoImpl.type(); -extern const TypeInfo* theStringTypeInfo = theStringTypeInfoImpl.type(); -extern const TypeInfo* theThrowableTypeInfo = theThrowableTypeInfoImpl.type(); -extern const TypeInfo* theUnitTypeInfo = theUnitTypeInfoImpl.type(); -extern const TypeInfo* theWorkerBoundReferenceTypeInfo = theWorkerBoundReferenceTypeInfoImpl.type(); -extern const TypeInfo* theCleanerImplTypeInfo = theCleanerImplTypeInfoImpl.type(); +extern const TypeInfo* theAnyTypeInfo = theAnyTypeInfoHolder.typeInfo(); +extern const TypeInfo* theArrayTypeInfo = theArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theBooleanArrayTypeInfo = theBooleanArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theByteArrayTypeInfo = theByteArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theCharArrayTypeInfo = theCharArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theDoubleArrayTypeInfo = theDoubleArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theFloatArrayTypeInfo = theFloatArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theForeignObjCObjectTypeInfo = theForeignObjCObjectTypeInfoHolder.typeInfo(); +extern const TypeInfo* theFreezableAtomicReferenceTypeInfo = theFreezableAtomicReferenceTypeInfoHolder.typeInfo(); +extern const TypeInfo* theIntArrayTypeInfo = theIntArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theLongArrayTypeInfo = theLongArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theNativePtrArrayTypeInfo = theNativePtrArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theObjCObjectWrapperTypeInfo = theObjCObjectWrapperTypeInfoHolder.typeInfo(); +extern const TypeInfo* theOpaqueFunctionTypeInfo = theOpaqueFunctionTypeInfoHolder.typeInfo(); +extern const TypeInfo* theShortArrayTypeInfo = theShortArrayTypeInfoHolder.typeInfo(); +extern const TypeInfo* theStringTypeInfo = theStringTypeInfoHolder.typeInfo(); +extern const TypeInfo* theThrowableTypeInfo = theThrowableTypeInfoHolder.typeInfo(); +extern const TypeInfo* theUnitTypeInfo = theUnitTypeInfoHolder.typeInfo(); +extern const TypeInfo* theWorkerBoundReferenceTypeInfo = theWorkerBoundReferenceTypeInfoHolder.typeInfo(); +extern const TypeInfo* theCleanerImplTypeInfo = theCleanerImplTypeInfoHolder.typeInfo(); -extern const ArrayHeader theEmptyArray = {theArrayTypeInfoImpl.type(), /* element count */ 0}; +extern const ArrayHeader theEmptyArray = {theArrayTypeInfoHolder.typeInfo(), /* element count */ 0}; OBJ_GETTER0(TheEmptyString) { RETURN_OBJ(theEmptyStringImpl.obj());