diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index cf211a89eb0..35b59cc8888 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -46,6 +46,7 @@ #include "MemoryPrivate.hpp" #include "Mutex.hpp" #include "Natives.h" +#include "ObjectTraversal.hpp" #include "Porting.h" #include "Runtime.h" #include "Utils.hpp" @@ -977,31 +978,6 @@ inline container_size_t objectSize(const ObjHeader* obj) { return alignUp(size, kObjectAlignment); } -template -inline void traverseObjectFields(ObjHeader* obj, func process) { - const TypeInfo* typeInfo = obj->type_info(); - if (typeInfo != theArrayTypeInfo) { - for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { - ObjHeader** location = reinterpret_cast( - reinterpret_cast(obj) + typeInfo->objOffsets_[index]); - process(location); - } - } else { - ArrayHeader* array = obj->array(); - for (uint32_t index = 0; index < array->count_; index++) { - process(ArrayAddressOfElementAt(array, index)); - } - } -} - -template -inline void traverseReferredObjects(ObjHeader* obj, func process) { - traverseObjectFields(obj, [process](ObjHeader** location) { - ObjHeader* ref = *location; - if (ref != nullptr) process(ref); - }); -} - template inline void traverseContainerObjects(ContainerHeader* container, func process) { RuntimeAssert(!isAggregatingFrozenContainer(container), "Must not be called on such containers"); @@ -1016,7 +992,7 @@ inline void traverseContainerObjects(ContainerHeader* container, func process) { template inline void traverseContainerObjectFields(ContainerHeader* container, func process) { traverseContainerObjects(container, [process](ObjHeader* obj) { - traverseObjectFields(obj, process); + kotlin::traverseObjectFields(obj, process); }); } @@ -2870,7 +2846,7 @@ void runFreezeHooksRecursive(ObjHeader* root) { kotlin::RunFreezeHooks(obj); - traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) { + kotlin::traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) { auto wasNotSeenYet = seen.insert(field).second; // Only iterating on unseen objects which containers will get frozen by freezeCyclic or freezeAcyclic. if (wasNotSeenYet && canFreeze(containerFor(field))) { @@ -2997,7 +2973,7 @@ CycleDetectorRootset CycleDetector::collectRootset() { continue; rootset.roots.push_back(candidate); rootset.heldRefs.emplace_back(candidate); - traverseReferredObjects(candidate, [&rootset, candidate](KRef field) { + kotlin::traverseReferredObjects(candidate, [&rootset, candidate](KRef field) { rootset.rootToFields[candidate].push_back(field); // TODO: There's currently a race here: // some other thread might null this field and destroy it in GC before @@ -3022,7 +2998,7 @@ KStdVector findCycleWithDFS(KRef root, const CycleDetectorRootset& rootset return; } - traverseReferredObjects(obj, process); + kotlin::traverseReferredObjects(obj, process); }; KStdVector> toVisit; diff --git a/kotlin-native/runtime/src/main/cpp/ObjectTraversal.hpp b/kotlin-native/runtime/src/main/cpp/ObjectTraversal.hpp new file mode 100644 index 00000000000..9b99a67574f --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ObjectTraversal.hpp @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef RUNTIME_OBJECT_TRAVERSAL_H +#define RUNTIME_OBJECT_TRAVERSAL_H + +#include + +#include "Memory.h" +#include "Natives.h" +#include "Types.h" + +namespace kotlin { + +// TODO: Consider an iterator/ranges based approaches for traversals. + +template +void traverseObjectFields(ObjHeader* object, F process) noexcept(noexcept(process(std::declval()))) { + const TypeInfo* typeInfo = object->type_info(); + // Only consider arrays of objects, not arrays of primitives. + if (typeInfo != theArrayTypeInfo) { + for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { + process(reinterpret_cast(reinterpret_cast(object) + typeInfo->objOffsets_[index])); + } + } else { + ArrayHeader* array = object->array(); + for (uint32_t index = 0; index < array->count_; index++) { + process(ArrayAddressOfElementAt(array, index)); + } + } +} + +template +void traverseReferredObjects(ObjHeader* object, F process) noexcept(noexcept(process(std::declval()))) { + traverseObjectFields(object, [&process](ObjHeader** location) noexcept(noexcept(process(std::declval()))) { + if (ObjHeader* ref = *location) { + process(ref); + } + }); +} + +} // namespace kotlin + +#endif // RUNTIME_OBJECT_TRAVERSAL_H diff --git a/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp b/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp new file mode 100644 index 00000000000..87f94a787ef --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ObjectTraversalTest.cpp @@ -0,0 +1,271 @@ +/* + * 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 "ObjectTraversal.hpp" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "Types.h" +#include "Utils.hpp" + +using namespace kotlin; + +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) {} +}; + +struct CallableWithoutExceptions { + void operator()(ObjHeader*) noexcept {} + void operator()(ObjHeader**) noexcept {} +}; + +} // namespace + +TEST(ObjectTraversalTest, TraverseFieldsExceptions) { + static_assert( + noexcept(traverseObjectFields(std::declval(), std::declval())), + "Callable is noexcept, so traverse is noexcept"); + static_assert( + !noexcept(traverseObjectFields(std::declval(), std::declval())), + "Callable is noexcept(false), so traverse is noexcept(false)"); +} + +TEST(ObjectTraversalTest, TraverseEmptyObjectFields) { + Object<0> object; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(_)).Times(0); + traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseObjectFields) { + ObjHeader field1; + ObjHeader field3; + Object<3> object; + object[0] = &field1; + object[2] = &field3; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(&object[0])); + EXPECT_CALL(process, Call(&object[1])); + EXPECT_CALL(process, Call(&object[2])); + traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) { + constexpr int kException = 1; + + ObjHeader field1; + ObjHeader field2; + ObjHeader field3; + Object<3> object; + object[0] = &field1; + object[1] = &field2; + object[2] = &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); + try { + traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); + } catch (int exception) { + EXPECT_THAT(exception, kException); + } catch (...) { + EXPECT_TRUE(false); + } +} + +TEST(ObjectTraversalTest, TraverseEmptyArrayFields) { + Array<0> array; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(_)).Times(0); + traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseArrayFields) { + ObjHeader element1; + ObjHeader element3; + Array<3> array; + array[0] = &element1; + array[2] = &element3; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(&array[0])); + EXPECT_CALL(process, Call(&array[1])); + EXPECT_CALL(process, Call(&array[2])); + traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseArrayFieldsWithException) { + constexpr int kException = 1; + + ObjHeader element1; + ObjHeader element2; + ObjHeader element3; + Array<3> array; + array[0] = &element1; + array[1] = &element2; + array[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); + try { + traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); + } catch (int exception) { + EXPECT_THAT(exception, kException); + } catch (...) { + EXPECT_TRUE(false); + } +} + +TEST(ObjectTraversalTest, TraverseRefsExceptions) { + static_assert( + noexcept(traverseReferredObjects(std::declval(), std::declval())), + "Callable is noexcept, so traverse is noexcept"); + static_assert( + !noexcept(traverseReferredObjects(std::declval(), std::declval())), + "Callable is noexcept(false), so traverse is noexcept(false)"); +} + +TEST(ObjectTraversalTest, TraverseEmptyObjectRefs) { + Object<0> object; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(_)).Times(0); + traverseReferredObjects(object.header(), [&process](ObjHeader* field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseObjectRefs) { + ObjHeader field1; + ObjHeader field3; + Object<3> object; + object[0] = &field1; + object[2] = &field3; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(&field1)); + EXPECT_CALL(process, Call(&field3)); + traverseReferredObjects(object.header(), [&process](ObjHeader* field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseObjectRefsWithException) { + constexpr int kException = 1; + + ObjHeader field1; + ObjHeader field2; + ObjHeader field3; + Object<3> object; + object[0] = &field1; + object[1] = &field2; + object[2] = &field3; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(&field1)); + EXPECT_CALL(process, Call(&field2)).WillOnce([]() { throw kException; }); + EXPECT_CALL(process, Call(&field3)).Times(0); + try { + traverseReferredObjects(object.header(), [&process](ObjHeader* field) { process.Call(field); }); + } catch (int exception) { + EXPECT_THAT(exception, kException); + } catch (...) { + EXPECT_TRUE(false); + } +} + +TEST(ObjectTraversalTest, TraverseEmptyArrayRefs) { + Array<0> array; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(_)).Times(0); + traverseReferredObjects(array.header(), [&process](ObjHeader* field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseArrayRefs) { + ObjHeader element1; + ObjHeader element3; + Array<3> array; + array[0] = &element1; + array[2] = &element3; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(&element1)); + EXPECT_CALL(process, Call(&element3)); + traverseReferredObjects(array.header(), [&process](ObjHeader* field) { process.Call(field); }); +} + +TEST(ObjectTraversalTest, TraverseArrayRefsWithException) { + constexpr int kException = 1; + + ObjHeader element1; + ObjHeader element2; + ObjHeader element3; + Array<3> array; + array[0] = &element1; + array[1] = &element2; + array[2] = &element3; + testing::StrictMock> process; + + EXPECT_CALL(process, Call(&element1)); + EXPECT_CALL(process, Call(&element2)).WillOnce([]() { throw kException; }); + EXPECT_CALL(process, Call(&element3)).Times(0); + try { + traverseReferredObjects(array.header(), [&process](ObjHeader* field) { process.Call(field); }); + } catch (int exception) { + EXPECT_THAT(exception, kException); + } catch (...) { + EXPECT_TRUE(false); + } +} diff --git a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp index 05bab08ed91..7de71d7e71d 100644 --- a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp +++ b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp @@ -9,28 +9,38 @@ namespace { -TypeInfo theAnyTypeInfoImpl = {}; -TypeInfo theArrayTypeInfoImpl = {}; -TypeInfo theBooleanArrayTypeInfoImpl = {}; -TypeInfo theByteArrayTypeInfoImpl = {}; -TypeInfo theCharArrayTypeInfoImpl = {}; -TypeInfo theDoubleArrayTypeInfoImpl = {}; -TypeInfo theFloatArrayTypeInfoImpl = {}; -TypeInfo theForeignObjCObjectTypeInfoImpl = {}; -TypeInfo theFreezableAtomicReferenceTypeInfoImpl = {}; -TypeInfo theIntArrayTypeInfoImpl = {}; -TypeInfo theLongArrayTypeInfoImpl = {}; -TypeInfo theNativePtrArrayTypeInfoImpl = {}; -TypeInfo theObjCObjectWrapperTypeInfoImpl = {}; -TypeInfo theOpaqueFunctionTypeInfoImpl = {}; -TypeInfo theShortArrayTypeInfoImpl = {}; -TypeInfo theStringTypeInfoImpl = {}; -TypeInfo theThrowableTypeInfoImpl = {}; -TypeInfo theUnitTypeInfoImpl = {}; -TypeInfo theWorkerBoundReferenceTypeInfoImpl = {}; -TypeInfo theCleanerImplTypeInfoImpl = {}; +class TypeInfoImpl { +public: + TypeInfoImpl() { type_.typeInfo_ = &type_; } -ArrayHeader theEmptyStringImpl = { &theStringTypeInfoImpl, /* element count */ 0 }; + TypeInfo* type() { return &type_; } + +private: + TypeInfo type_; +}; + +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; + +ArrayHeader theEmptyStringImpl = {theStringTypeInfoImpl.type(), /* element count */ 0}; template struct KBox { @@ -48,28 +58,28 @@ extern "C" { // Set to 1 to enable runtime assertions. extern const int KonanNeedDebugInfo = 1; -extern const TypeInfo* theAnyTypeInfo = &theAnyTypeInfoImpl; -extern const TypeInfo* theArrayTypeInfo = &theArrayTypeInfoImpl; -extern const TypeInfo* theBooleanArrayTypeInfo = &theBooleanArrayTypeInfoImpl; -extern const TypeInfo* theByteArrayTypeInfo = &theByteArrayTypeInfoImpl; -extern const TypeInfo* theCharArrayTypeInfo = &theCharArrayTypeInfoImpl; -extern const TypeInfo* theDoubleArrayTypeInfo = &theDoubleArrayTypeInfoImpl; -extern const TypeInfo* theFloatArrayTypeInfo = &theFloatArrayTypeInfoImpl; -extern const TypeInfo* theForeignObjCObjectTypeInfo = &theForeignObjCObjectTypeInfoImpl; -extern const TypeInfo* theFreezableAtomicReferenceTypeInfo = &theFreezableAtomicReferenceTypeInfoImpl; -extern const TypeInfo* theIntArrayTypeInfo = &theIntArrayTypeInfoImpl; -extern const TypeInfo* theLongArrayTypeInfo = &theLongArrayTypeInfoImpl; -extern const TypeInfo* theNativePtrArrayTypeInfo = &theNativePtrArrayTypeInfoImpl; -extern const TypeInfo* theObjCObjectWrapperTypeInfo = &theObjCObjectWrapperTypeInfoImpl; -extern const TypeInfo* theOpaqueFunctionTypeInfo = &theOpaqueFunctionTypeInfoImpl; -extern const TypeInfo* theShortArrayTypeInfo = &theShortArrayTypeInfoImpl; -extern const TypeInfo* theStringTypeInfo = &theStringTypeInfoImpl; -extern const TypeInfo* theThrowableTypeInfo = &theThrowableTypeInfoImpl; -extern const TypeInfo* theUnitTypeInfo = &theUnitTypeInfoImpl; -extern const TypeInfo* theWorkerBoundReferenceTypeInfo = &theWorkerBoundReferenceTypeInfoImpl; -extern const TypeInfo* theCleanerImplTypeInfo = &theCleanerImplTypeInfoImpl; +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 ArrayHeader theEmptyArray = { &theArrayTypeInfoImpl, /* element count */0 }; +extern const ArrayHeader theEmptyArray = {theArrayTypeInfoImpl.type(), /* element count */ 0}; OBJ_GETTER0(TheEmptyString) { RETURN_OBJ(theEmptyStringImpl.obj());