Extract object traversals (#4724)

(cherry picked from commit c3131ea2f51c31881ea068f28035f4d015daf609)
This commit is contained in:
Alexander Shabalin
2021-03-02 12:47:14 +03:00
committed by Space
parent 248e340cd9
commit 27e4b21020
4 changed files with 374 additions and 71 deletions
@@ -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 <typename func>
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<ObjHeader**>(
reinterpret_cast<uintptr_t>(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 <typename func>
inline void traverseReferredObjects(ObjHeader* obj, func process) {
traverseObjectFields(obj, [process](ObjHeader** location) {
ObjHeader* ref = *location;
if (ref != nullptr) process(ref);
});
}
template <typename func>
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 <typename func>
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<KRef> findCycleWithDFS(KRef root, const CycleDetectorRootset& rootset
return;
}
traverseReferredObjects(obj, process);
kotlin::traverseReferredObjects(obj, process);
};
KStdVector<KStdVector<KRef>> toVisit;
@@ -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 <type_traits>
#include "Memory.h"
#include "Natives.h"
#include "Types.h"
namespace kotlin {
// TODO: Consider an iterator/ranges based approaches for traversals.
template <typename F>
void traverseObjectFields(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<ObjHeader**>()))) {
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<ObjHeader**>(reinterpret_cast<uintptr_t>(object) + typeInfo->objOffsets_[index]));
}
} else {
ArrayHeader* array = object->array();
for (uint32_t index = 0; index < array->count_; index++) {
process(ArrayAddressOfElementAt(array, index));
}
}
}
template <typename F>
void traverseReferredObjects(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<ObjHeader*>()))) {
traverseObjectFields(object, [&process](ObjHeader** location) noexcept(noexcept(process(std::declval<ObjHeader*>()))) {
if (ObjHeader* ref = *location) {
process(ref);
}
});
}
} // namespace kotlin
#endif // RUNTIME_OBJECT_TRAVERSAL_H
@@ -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 <size_t Count>
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<uintptr_t>(&fields_[i]) - reinterpret_cast<uintptr_t>(&header_);
}
}
ObjHeader* header() { return &header_; }
ObjHeader*& operator[](size_t index) { return fields_[index]; }
private:
ObjHeader header_;
TypeInfo type_;
std::array<int32_t, Count> fieldOffsets_;
std::array<ObjHeader*, Count> fields_{};
};
template <size_t Count>
class Array : private Pinned {
public:
Array() {
header_.typeInfoOrMeta_ = const_cast<TypeInfo*>(theArrayTypeInfo);
header_.count_ = Count;
}
ObjHeader* header() { return header_.obj(); }
ObjHeader*& operator[](size_t index) { return fields_[index]; }
private:
ArrayHeader header_;
std::array<ObjHeader*, Count> 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<ObjHeader*>(), std::declval<CallableWithoutExceptions>())),
"Callable is noexcept, so traverse is noexcept");
static_assert(
!noexcept(traverseObjectFields(std::declval<ObjHeader*>(), std::declval<CallableWithExceptions>())),
"Callable is noexcept(false), so traverse is noexcept(false)");
}
TEST(ObjectTraversalTest, TraverseEmptyObjectFields) {
Object<0> object;
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> 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<testing::MockFunction<void(ObjHeader**)>> 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<testing::MockFunction<void(ObjHeader**)>> 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<testing::MockFunction<void(ObjHeader**)>> 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<testing::MockFunction<void(ObjHeader**)>> 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<testing::MockFunction<void(ObjHeader**)>> 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<ObjHeader*>(), std::declval<CallableWithoutExceptions>())),
"Callable is noexcept, so traverse is noexcept");
static_assert(
!noexcept(traverseReferredObjects(std::declval<ObjHeader*>(), std::declval<CallableWithExceptions>())),
"Callable is noexcept(false), so traverse is noexcept(false)");
}
TEST(ObjectTraversalTest, TraverseEmptyObjectRefs) {
Object<0> object;
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> 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<testing::MockFunction<void(ObjHeader*)>> 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<testing::MockFunction<void(ObjHeader*)>> 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<testing::MockFunction<void(ObjHeader*)>> 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<testing::MockFunction<void(ObjHeader*)>> 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<testing::MockFunction<void(ObjHeader*)>> 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);
}
}
@@ -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 <class T>
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());