[K/N] Runtime reference accessors

Encapsulate reference-access-related logic,
    such as GC barriers and tsan workarounds
    inside (Direct)RefAccessor classes.
This commit is contained in:
Aleksei.Glushko
2023-10-09 15:23:05 +02:00
committed by Space Team
parent 24becdad7e
commit 7eccfe969c
23 changed files with 409 additions and 268 deletions
@@ -22,9 +22,9 @@ using namespace kotlin;
namespace { namespace {
struct Payload { struct Payload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields = { static constexpr std::array kFields = {
&Payload::field1, &Payload::field1,
@@ -851,8 +851,8 @@ public:
using ObjectFactory = alloc::ObjectFactory<ObjectFactoryTraits>; using ObjectFactory = alloc::ObjectFactory<ObjectFactoryTraits>;
struct Payload { struct Payload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
static constexpr std::array kFields{ static constexpr std::array kFields{
&Payload::field1, &Payload::field1,
@@ -35,8 +35,8 @@ void processFieldInMark(void* state, ObjHeader* field) noexcept {
template <typename Traits> template <typename Traits>
void processObjectInMark(void* state, ObjHeader* object) noexcept { void processObjectInMark(void* state, ObjHeader* object) noexcept {
traverseClassObjectFields(object, [state] (ObjHeader** fieldLocation) noexcept { traverseClassObjectFields(object, [state] (auto fieldAccessor) noexcept {
if (auto field = *fieldLocation) { if (ObjHeader* field = fieldAccessor.direct()) {
processFieldInMark<Traits>(state, field); processFieldInMark<Traits>(state, field);
} }
}); });
@@ -44,8 +44,8 @@ void processObjectInMark(void* state, ObjHeader* object) noexcept {
template <typename Traits> template <typename Traits>
void processArrayInMark(void* state, ArrayHeader* array) noexcept { void processArrayInMark(void* state, ArrayHeader* array) noexcept {
traverseArrayOfObjectsElements(array, [state] (ObjHeader** elemLocation) noexcept { traverseArrayOfObjectsElements(array, [state] (auto elemAccessor) noexcept {
if (auto elem = *elemLocation) { if (ObjHeader* elem = elemAccessor.direct()) {
processFieldInMark<Traits>(state, elem); processFieldInMark<Traits>(state, elem);
} }
}); });
@@ -22,9 +22,9 @@ using namespace kotlin;
namespace { namespace {
struct Payload { struct Payload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields = { static constexpr std::array kFields = {
&Payload::field1, &Payload::field1,
@@ -30,9 +30,9 @@
using namespace kotlin; using namespace kotlin;
struct Payload { struct Payload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields = { static constexpr std::array kFields = {
&Payload::field1, &Payload::field1,
@@ -97,7 +97,7 @@ public:
test_support::ObjectArray<3>& operator*() { return test_support::ObjectArray<3>::FromArrayHeader(location_->array()); } test_support::ObjectArray<3>& operator*() { return test_support::ObjectArray<3>::FromArrayHeader(location_->array()); }
test_support::ObjectArray<3>& operator->() { return test_support::ObjectArray<3>::FromArrayHeader(location_->array()); } test_support::ObjectArray<3>& operator->() { return test_support::ObjectArray<3>::FromArrayHeader(location_->array()); }
ObjHeader*& operator[](size_t index) noexcept { return (**this).elements()[index]; } mm::RefField& operator[](size_t index) noexcept { return (**this).elements()[index]; }
private: private:
ObjHeader* location_ = nullptr; ObjHeader* location_ = nullptr;
@@ -144,7 +144,7 @@ public:
test_support::ObjectArray<3>& operator*() { return test_support::ObjectArray<3>::FromArrayHeader(holder_.obj()->array()); } test_support::ObjectArray<3>& operator*() { return test_support::ObjectArray<3>::FromArrayHeader(holder_.obj()->array()); }
test_support::ObjectArray<3>& operator->() { return test_support::ObjectArray<3>::FromArrayHeader(holder_.obj()->array()); } test_support::ObjectArray<3>& operator->() { return test_support::ObjectArray<3>::FromArrayHeader(holder_.obj()->array()); }
ObjHeader*& operator[](size_t index) noexcept { return (**this).elements()[index]; } mm::RefField& operator[](size_t index) noexcept { return (**this).elements()[index]; }
private: private:
ObjHolder holder_; ObjHolder holder_;
@@ -343,7 +343,7 @@ TYPED_TEST_P(TracingGCTest, FreeObjectWithHoldedWeak) {
RunInNewThread([](mm::ThreadData& threadData) { RunInNewThread([](mm::ThreadData& threadData) {
auto& object1 = AllocateObject(threadData); auto& object1 = AllocateObject(threadData);
StackObjectHolder stack{threadData}; StackObjectHolder stack{threadData};
auto& weak1 = InstallWeakReference(threadData, object1.header(), &stack->field1); auto& weak1 = InstallWeakReference(threadData, object1.header(), stack->field1.ptr());
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1.header(), weak1.header(), stack.header())); ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1.header(), weak1.header(), stack.header()));
ASSERT_THAT(gc::isMarked(object1.header()), false); ASSERT_THAT(gc::isMarked(object1.header()), false);
@@ -31,8 +31,7 @@ namespace {
struct Payload { struct Payload {
int value = 0; int value = 0;
using Field = ObjHeader* Payload::*; static constexpr test_support::NoRefFields<Payload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
using Object = test_support::Object<Payload>; using Object = test_support::Object<Payload>;
@@ -20,8 +20,7 @@ using ::testing::_;
namespace { namespace {
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
class FinalizerHooksTest : public testing::Test { class FinalizerHooksTest : public testing::Test {
@@ -19,8 +19,7 @@ using ::testing::_;
namespace { namespace {
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
class FreezeHooksTest : public testing::Test { class FreezeHooksTest : public testing::Test {
+3 -15
View File
@@ -191,6 +191,7 @@ struct type_layout::descriptor<ArrayBody> {
}; };
}; };
} // namespace kotlin } // namespace kotlin
ALWAYS_INLINE bool isPermanentOrFrozen(const ObjHeader* obj); ALWAYS_INLINE bool isPermanentOrFrozen(const ObjHeader* obj);
@@ -292,10 +293,6 @@ enum class MemoryModel {
// Controls the current memory model, is compile-time constant. // Controls the current memory model, is compile-time constant.
extern const MemoryModel CurrentMemoryModel; extern const MemoryModel CurrentMemoryModel;
// Sets stack location.
void SetStackRef(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTHROW;
// Sets heap location.
void SetHeapRef(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTHROW;
// Zeroes heap location. // Zeroes heap location.
void ZeroHeapRef(ObjHeader** location) RUNTIME_NOTHROW; void ZeroHeapRef(ObjHeader** location) RUNTIME_NOTHROW;
// Zeroes an array. // Zeroes an array.
@@ -315,18 +312,8 @@ OBJ_GETTER(GetAndSetVolatileHeapRef, ObjHeader** location, ObjHeader* newValue)
// Updates heap/static data in one array. // Updates heap/static data in one array.
void UpdateHeapRefsInsideOneArray(const ArrayHeader* array, int fromIndex, int toIndex, int count) RUNTIME_NOTHROW; void UpdateHeapRefsInsideOneArray(const ArrayHeader* array, int fromIndex, int toIndex, int count) RUNTIME_NOTHROW;
// Updates location if it is null, atomically. // Updates location if it is null, atomically.
void UpdateHeapRefIfNull(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTHROW;
// Updates reference in return slot. // Updates reference in return slot.
void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) RUNTIME_NOTHROW; void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) RUNTIME_NOTHROW;
// Compares and swaps reference with taken lock.
OBJ_GETTER(SwapHeapRefLocked,
ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue, int32_t* spinlock,
int32_t* cookie) RUNTIME_NOTHROW;
// Sets reference with taken lock.
void SetHeapRefLocked(ObjHeader** location, ObjHeader* newValue, int32_t* spinlock,
int32_t* cookie) RUNTIME_NOTHROW;
// Reads reference with taken lock.
OBJ_GETTER(ReadHeapRefLocked, ObjHeader** location, int32_t* spinlock, int32_t* cookie) RUNTIME_NOTHROW;
OBJ_GETTER(ReadHeapRefNoLock, ObjHeader* object, int32_t index); OBJ_GETTER(ReadHeapRefNoLock, ObjHeader* object, int32_t index);
// Called on frame enter, if it has object slots. // Called on frame enter, if it has object slots.
void EnterFrame(ObjHeader** start, int parameters, int count) RUNTIME_NOTHROW; void EnterFrame(ObjHeader** start, int parameters, int count) RUNTIME_NOTHROW;
@@ -421,6 +408,7 @@ struct FrameOverlay {
}; };
// Class holding reference to an object, holding object during C++ scope. // Class holding reference to an object, holding object during C++ scope.
// TODO adopt ref accessors
class ObjHolder { class ObjHolder {
public: public:
ObjHolder() : obj_(nullptr) { ObjHolder() : obj_(nullptr) {
@@ -429,7 +417,7 @@ class ObjHolder {
explicit ObjHolder(const ObjHeader* obj) { explicit ObjHolder(const ObjHeader* obj) {
EnterFrame(frame(), 0, sizeof(*this)/sizeof(void*)); EnterFrame(frame(), 0, sizeof(*this)/sizeof(void*));
::SetStackRef(slot(), obj); ::UpdateStackRef(slot(), obj);
} }
~ObjHolder() { ~ObjHolder() {
@@ -11,6 +11,7 @@
#include "KAssert.h" #include "KAssert.h"
#include "Memory.h" #include "Memory.h"
#include "ReferenceOps.hpp"
#include "TypeInfo.h" #include "TypeInfo.h"
#include "Types.h" #include "Types.h"
#include "Utils.hpp" #include "Utils.hpp"
@@ -20,6 +21,12 @@ namespace test_support {
// TODO: Some concepts from here can be used in production code. // TODO: Some concepts from here can be used in production code.
template<typename Host>
using RefFieldPtr = mm::RefField Host::*;
template<typename Host>
using NoRefFields = std::array<RefFieldPtr<Host>, 0>;
class TypeInfoHolder : private Pinned { class TypeInfoHolder : private Pinned {
private: private:
class Builder { class Builder {
@@ -96,6 +103,9 @@ public:
typeInfo_.instanceAlignment_ = builder.instanceAlignment_; typeInfo_.instanceAlignment_ = builder.instanceAlignment_;
} }
template<>
class ArrayBuilder<mm::RefField> : public ArrayBuilder<ObjHeader*> {};
TypeInfo* typeInfo() noexcept { return &typeInfo_; } TypeInfo* typeInfo() noexcept { return &typeInfo_; }
private: private:
@@ -123,9 +133,9 @@ public:
public: public:
FieldIterator(Object& owner, size_t index) noexcept : owner_(owner), index_(index) {} FieldIterator(Object& owner, size_t index) noexcept : owner_(owner), index_(index) {}
ObjHeader*& operator*() noexcept { mm::RefField& operator*() noexcept {
auto* header = &owner_.header_; auto* header = &owner_.header_;
return *reinterpret_cast<ObjHeader**>(reinterpret_cast<uintptr_t>(header) + header->type_info()->objOffsets_[index_]); return *reinterpret_cast<mm::RefField*>(reinterpret_cast<uintptr_t>(header) + header->type_info()->objOffsets_[index_]);
} }
FieldIterator& operator++() noexcept { FieldIterator& operator++() noexcept {
@@ -148,7 +158,7 @@ public:
size_t size() const noexcept { return owner_.header_.type_info()->objOffsetsCount_; } size_t size() const noexcept { return owner_.header_.type_info()->objOffsetsCount_; }
ObjHeader*& operator[](size_t index) noexcept { return *FieldIterator(owner_, index); } mm::RefField& operator[](size_t index) noexcept { return *FieldIterator(owner_, index); }
FieldIterator begin() noexcept { return FieldIterator(owner_, 0); } FieldIterator begin() noexcept { return FieldIterator(owner_, 0); }
FieldIterator end() noexcept { return FieldIterator(owner_, size()); } FieldIterator end() noexcept { return FieldIterator(owner_, size()); }
@@ -190,8 +200,7 @@ TypeInfoHolder::ObjectBuilder<Payload>::ObjectBuilder() noexcept {
char c; char c;
Object<Payload>& object = *reinterpret_cast<Object<Payload>*>(&c); Object<Payload>& object = *reinterpret_cast<Object<Payload>*>(&c);
auto& payload = *object; auto& payload = *object;
using Field = ObjHeader* Payload::*; for (RefFieldPtr<Payload> field : Payload::kFields) {
for (Field field : Payload::kFields) {
auto& actualField = payload.*field; auto& actualField = payload.*field;
objOffsets_.push_back(reinterpret_cast<uintptr_t>(&actualField) - reinterpret_cast<uintptr_t>(object.header())); objOffsets_.push_back(reinterpret_cast<uintptr_t>(&actualField) - reinterpret_cast<uintptr_t>(object.header()));
} }
@@ -237,13 +246,13 @@ private:
} // namespace internal } // namespace internal
template <size_t ElementCount> template <size_t ElementCount>
class ObjectArray : public internal::Array<ObjHeader*, ElementCount> { class ObjectArray : public internal::Array<mm::RefField, ElementCount> {
public: public:
static ObjectArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept { static ObjectArray<ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
return static_cast<ObjectArray<ElementCount>&>(internal::Array<ObjHeader*, ElementCount>::FromArrayHeader(arr)); return static_cast<ObjectArray<ElementCount>&>(internal::Array<mm::RefField, ElementCount>::FromArrayHeader(arr));
} }
ObjectArray() noexcept : internal::Array<ObjHeader*, ElementCount>(theArrayTypeInfo) {} ObjectArray() noexcept : internal::Array<mm::RefField, ElementCount>(theArrayTypeInfo) {}
}; };
template <size_t ElementCount> template <size_t ElementCount>
@@ -350,8 +359,7 @@ struct RegularWeakReferenceImplPayload {
void* weakRef; void* weakRef;
void* referred; void* referred;
using Field = ObjHeader* RegularWeakReferenceImplPayload::*; static constexpr test_support::NoRefFields<RegularWeakReferenceImplPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
extern "C" OBJ_GETTER(Konan_RegularWeakReferenceImpl_get, ObjHeader*); extern "C" OBJ_GETTER(Konan_RegularWeakReferenceImpl_get, ObjHeader*);
@@ -18,9 +18,9 @@ using namespace kotlin;
namespace { namespace {
struct RegularPayload { struct RegularPayload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields{ static constexpr std::array kFields{
&RegularPayload::field1, &RegularPayload::field1,
@@ -31,11 +31,11 @@ struct RegularPayload {
struct IrregularPayload { struct IrregularPayload {
int skipBefore; int skipBefore;
ObjHeader* field1; mm::RefField field1;
int skip; int skip;
ObjHeader* field2; mm::RefField field2;
std::array<int, 10> skipALot; std::array<int, 10> skipALot;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields{ static constexpr std::array kFields{
&IrregularPayload::field1, &IrregularPayload::field1,
@@ -70,8 +70,8 @@ using ObjectTestCases = testing::Types<RegularObjectTestCase, IrregularObjectTes
TYPED_TEST_SUITE(ObjectTestSupportObjectTest, ObjectTestCases, ObjectTestCaseNames); TYPED_TEST_SUITE(ObjectTestSupportObjectTest, ObjectTestCases, ObjectTestCaseNames);
template <typename Payload> template <typename Payload>
std::vector<ObjHeader**> Collect(test_support::Object<Payload>& object) { std::vector<mm::RefField*> Collect(test_support::Object<Payload>& object) {
std::vector<ObjHeader**> result; std::vector<mm::RefField*> result;
for (auto& field : object.fields()) { for (auto& field : object.fields()) {
result.push_back(&field); result.push_back(&field);
} }
@@ -108,9 +108,9 @@ TYPED_TEST(ObjectTestSupportObjectTest, Local) {
EXPECT_THAT(Collect(object), testing::ElementsAre(&object->field1, &object->field2, &object->field3)); EXPECT_THAT(Collect(object), testing::ElementsAre(&object->field1, &object->field2, &object->field3));
EXPECT_THAT(object.fields()[0], nullptr); EXPECT_THAT(object.fields()[0].direct().load(), nullptr);
EXPECT_THAT(object.fields()[1], nullptr); EXPECT_THAT(object.fields()[1].direct().load(), nullptr);
EXPECT_THAT(object.fields()[2], nullptr); EXPECT_THAT(object.fields()[2].direct().load(), nullptr);
auto& recoveredObject = test_support::Object<Payload>::FromObjHeader(object.header()); auto& recoveredObject = test_support::Object<Payload>::FromObjHeader(object.header());
EXPECT_THAT(&recoveredObject, &object); EXPECT_THAT(&recoveredObject, &object);
@@ -149,9 +149,9 @@ TYPED_TEST(ObjectTestSupportObjectTest, Heap) {
EXPECT_THAT(Collect(object), testing::ElementsAre(&object->field1, &object->field2, &object->field3)); EXPECT_THAT(Collect(object), testing::ElementsAre(&object->field1, &object->field2, &object->field3));
EXPECT_THAT(object.fields()[0], nullptr); EXPECT_THAT(object.fields()[0].direct().load(), nullptr);
EXPECT_THAT(object.fields()[1], nullptr); EXPECT_THAT(object.fields()[1].direct().load(), nullptr);
EXPECT_THAT(object.fields()[2], nullptr); EXPECT_THAT(object.fields()[2].direct().load(), nullptr);
}); });
} }
@@ -161,7 +161,7 @@ template <typename Payload>
struct PayloadTraits; struct PayloadTraits;
template <> template <>
struct PayloadTraits<ObjHeader*> { struct PayloadTraits<mm::RefField> {
template <size_t Size> template <size_t Size>
using Array = test_support::ObjectArray<Size>; using Array = test_support::ObjectArray<Size>;
static const TypeInfo* GetTypeInfo() { return theArrayTypeInfo; } static const TypeInfo* GetTypeInfo() { return theArrayTypeInfo; }
@@ -284,8 +284,8 @@ public:
template <typename TestCase> template <typename TestCase>
class ObjectTestSupportArrayTest : public testing::Test {}; class ObjectTestSupportArrayTest : public testing::Test {};
using ArrayTestCases = testing::Types< using ArrayTestCases = testing::Types<
ArrayTestCase<ObjHeader*, 0>, ArrayTestCase<mm::RefField, 0>,
ArrayTestCase<ObjHeader*, 3>, ArrayTestCase<mm::RefField, 3>,
ArrayTestCase<KBoolean, 0>, ArrayTestCase<KBoolean, 0>,
ArrayTestCase<KBoolean, 3>, ArrayTestCase<KBoolean, 3>,
ArrayTestCase<KByte, 0>, ArrayTestCase<KByte, 0>,
@@ -335,7 +335,7 @@ TYPED_TEST(ObjectTestSupportArrayTest, Local) {
for (size_t i = 0; i < size; ++i) { for (size_t i = 0; i < size; ++i) {
auto* element = AddressOfElementAt<Payload>(array.arrayHeader(), i); auto* element = AddressOfElementAt<Payload>(array.arrayHeader(), i);
EXPECT_THAT(&array.elements()[i], element); EXPECT_THAT(&array.elements()[i], element);
EXPECT_THAT(array.elements()[i], Payload{}); EXPECT_TRUE(array.elements()[i] == Payload{});
expected.push_back(element); expected.push_back(element);
} }
@@ -366,7 +366,7 @@ TYPED_TEST(ObjectTestSupportArrayTest, Heap) {
for (size_t i = 0; i < size; ++i) { for (size_t i = 0; i < size; ++i) {
auto* element = AddressOfElementAt<Payload>(array.arrayHeader(), i); auto* element = AddressOfElementAt<Payload>(array.arrayHeader(), i);
EXPECT_THAT(&array.elements()[i], element); EXPECT_THAT(&array.elements()[i], element);
EXPECT_THAT(array.elements()[i], Payload{}); EXPECT_TRUE(array.elements()[i] == Payload{});
expected.push_back(element); expected.push_back(element);
} }
@@ -0,0 +1,205 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#pragma once
#include "Memory.h"
#include "std_support/AtomicRef.hpp"
// Concurrent GC may cause conflicting unordered accesses to references in heap.
// C++ memory model declares that accesses have data race unless they are atomic.
// Thus TSAN would report such non-atomic accesses.
// TODO find out if compiler may take advantage of the theoretical UB here.
//
// However, in practice all the ptr-sized loads and stores are atomic on CPU-level
// even if they are not std::atomic.
// And as far as we aware,
// std::atomic operations are not optimized by LLVM even if they have relaxed memory order.
// So we don't want to compile every heap reference access into std::atomic access.
#define ALWAYS_ATOMIC_REFS __has_feature(thread_sanitizer)
namespace kotlin::mm {
// TODO: Make sure these operations work with any kind of thread stopping: safepoints and signals.
// TODO: Consider adding some kind of an `Object` type (that wraps `ObjHeader*`) which
// will have these operations for a friendlier API.
/**
* Represents direct low-level operations on Koltin references.
* No GC barriers are inserted. Should be used with care!
*/
class DirectRefAccessor {
public:
DirectRefAccessor() = delete;
DirectRefAccessor& operator=(const DirectRefAccessor&) = delete;
explicit DirectRefAccessor(ObjHeader*& fieldRef) noexcept : ref_(fieldRef) {}
explicit DirectRefAccessor(ObjHeader** fieldPtr) noexcept : DirectRefAccessor(*fieldPtr) {}
DirectRefAccessor(const DirectRefAccessor& other) noexcept : DirectRefAccessor(other.ref_) {}
ObjHeader** location() const noexcept { return &ref_; }
ALWAYS_INLINE operator ObjHeader*() const noexcept { return load(); }
ALWAYS_INLINE ObjHeader* operator=(ObjHeader* desired) noexcept { store(desired); return desired; }
ALWAYS_INLINE ObjHeader* load() const noexcept {
#if ALWAYS_ATOMIC_REFS
return loadAtomic(std::memory_order_relaxed);
#else
return ref_;
#endif
}
ALWAYS_INLINE void store(ObjHeader* desired) noexcept {
#if ALWAYS_ATOMIC_REFS
storeAtomic(desired, std::memory_order_relaxed);
#else
ref_ = desired;
#endif
}
ALWAYS_INLINE auto atomic() noexcept {
return std_support::atomic_ref<ObjHeader*>{ref_};
}
ALWAYS_INLINE auto atomic() const noexcept {
return std_support::atomic_ref<ObjHeader*>{ref_};
}
ALWAYS_INLINE ObjHeader* loadAtomic(std::memory_order order) const noexcept {
return atomic().load(order);
}
ALWAYS_INLINE void storeAtomic(ObjHeader* desired, std::memory_order order) noexcept {
atomic().store(desired, order);
}
ALWAYS_INLINE ObjHeader* exchange(ObjHeader* desired, std::memory_order order) noexcept {
return atomic().exchange(desired, order);
}
ALWAYS_INLINE bool compareAndExchange(ObjHeader*& expected, ObjHeader* desired, std::memory_order order) noexcept {
return atomic().compare_exchange_strong(expected, desired, order);
}
private:
ObjHeader*& ref_;
};
/**
* Represents Koltin-level operations on Koltin references.
* With all the necessary GC barriers etc.
* Prefer using aliases below.
*/
template<bool kOnStack>
class RefAccessor {
public:
RefAccessor() = delete;
RefAccessor& operator=(const RefAccessor&) = delete;
explicit RefAccessor(ObjHeader*& fieldRef) noexcept : direct_(fieldRef) {}
explicit RefAccessor(ObjHeader** fieldPtr) noexcept : RefAccessor(*fieldPtr) {}
RefAccessor(const RefAccessor& other) noexcept : direct_(other.direct_) {}
DirectRefAccessor direct() const noexcept { return direct_; }
void beforeLoad() noexcept;
void afterLoad() noexcept;
void beforeStore(ObjHeader* value) noexcept;
void afterStore(ObjHeader* value) noexcept;
ALWAYS_INLINE operator ObjHeader*() noexcept { return load(); }
ALWAYS_INLINE ObjHeader* load() noexcept {
AssertThreadState(ThreadState::kRunnable);
beforeLoad();
auto result = direct_.load();
afterLoad();
return result;
}
ALWAYS_INLINE ObjHeader* loadAtomic(std::memory_order order) noexcept {
AssertThreadState(ThreadState::kRunnable);
beforeLoad();
auto result = direct_.loadAtomic(order);
afterLoad();
return result;
}
ALWAYS_INLINE ObjHeader* operator=(ObjHeader* desired) noexcept { store(desired); return desired; }
ALWAYS_INLINE void store(ObjHeader* desired) noexcept {
AssertThreadState(ThreadState::kRunnable);
beforeStore(desired);
direct_.store(desired);
afterStore(desired);
}
ALWAYS_INLINE void storeAtomic(ObjHeader* desired, std::memory_order order) noexcept {
AssertThreadState(ThreadState::kRunnable);
beforeStore(desired);
direct_.storeAtomic(desired, order);
afterStore(desired);
}
ALWAYS_INLINE ObjHeader* exchange(ObjHeader* desired, std::memory_order order) noexcept {
AssertThreadState(ThreadState::kRunnable);
beforeLoad();
beforeStore(desired);
auto result = direct_.exchange(desired, order);
afterStore(desired);
afterLoad();
return result;
}
ALWAYS_INLINE bool compareAndExchange(ObjHeader*& expected, ObjHeader* desired, std::memory_order order) noexcept {
AssertThreadState(ThreadState::kRunnable);
beforeLoad();
beforeStore(desired);
bool result = direct_.compareAndExchange(expected, desired, order);
afterStore(desired);
afterLoad();
return result;
}
private:
DirectRefAccessor direct_;
};
using RefFieldAccessor = RefAccessor<false>;
using GlobalRefAccessor = RefAccessor<false>;
using StackRefAccessor = RefAccessor<true>;
class RefField : private Pinned {
public:
auto accessor() noexcept {
return mm::RefFieldAccessor(value_);
}
auto direct() noexcept {
return accessor().direct();
}
// FIXME probably most of the uses should instead use accessor
auto ptr() noexcept {
return direct().location();
}
// TODO consider adding other operations
ObjHeader* operator=(ObjHeader* value) noexcept {
accessor() = value;
return value_;
}
bool operator==(const RefField& other) const noexcept {
return value_ == other.value_;
}
bool operator!=(const RefField& other) const noexcept {
return !operator==(other);
}
private:
ObjHeader* value_ = nullptr;
};
OBJ_GETTER(weakRefReadBarrier, std::atomic<ObjHeader*>& referee) noexcept;
}
@@ -16,13 +16,12 @@ using namespace kotlin;
namespace { namespace {
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
struct Payload1 { struct Payload1 {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
static constexpr std::array kFields{ static constexpr std::array kFields{
&Payload1::field1, &Payload1::field1,
@@ -31,8 +30,8 @@ struct Payload1 {
}; };
struct Payload2 { struct Payload2 {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
static constexpr std::array kFields{ static constexpr std::array kFields{
&Payload2::field1, &Payload2::field1,
@@ -19,8 +19,7 @@ using namespace kotlin;
namespace { namespace {
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
class ExtraObjectDataTest : public testing::Test { class ExtraObjectDataTest : public testing::Test {
@@ -29,14 +29,13 @@ struct NoFreezeHook {
}; };
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
struct Payload { struct Payload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields{ static constexpr std::array kFields{
&Payload::field1, &Payload::field1,
@@ -66,7 +65,7 @@ public:
ObjHeader* header() { return object_.header(); } ObjHeader* header() { return object_.header(); }
ObjHeader*& operator[](size_t field) { return object_.fields()[field]; } mm::RefField& operator[](size_t field) { return object_.fields()[field]; }
void MakePermanent() { header()->typeInfoOrMeta_ = setPointerBits(header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER); } void MakePermanent() { header()->typeInfoOrMeta_ = setPointerBits(header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER); }
@@ -89,7 +88,7 @@ public:
ObjHeader* header() { return array_.header(); } ObjHeader* header() { return array_.header(); }
ObjHeader*& operator[](size_t index) { return array_.elements()[index]; } mm::RefField& operator[](size_t index) { return array_.elements()[index]; }
void MakePermanent() { header()->typeInfoOrMeta_ = setPointerBits(header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER); } void MakePermanent() { header()->typeInfoOrMeta_ = setPointerBits(header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER); }
+15 -38
View File
@@ -16,6 +16,7 @@
#include "Natives.h" #include "Natives.h"
#include "ObjectOps.hpp" #include "ObjectOps.hpp"
#include "Porting.h" #include "Porting.h"
#include "ReferenceOps.hpp"
#include "Runtime.h" #include "Runtime.h"
#include "SafePoint.hpp" #include "SafePoint.hpp"
#include "StableRef.hpp" #include "StableRef.hpp"
@@ -144,63 +145,51 @@ extern "C" RUNTIME_NOTHROW void InitAndRegisterGlobal(ObjHeader** location, cons
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(threadData, location); mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(threadData, location);
// Null `initialValue` means that the appropriate value was already set by static initialization. // Null `initialValue` means that the appropriate value was already set by static initialization.
if (initialValue != nullptr) { if (initialValue != nullptr) {
mm::SetHeapRef(location, const_cast<ObjHeader*>(initialValue)); UpdateHeapRef(location, const_cast<ObjHeader*>(initialValue));
} }
} }
extern "C" const MemoryModel CurrentMemoryModel = MemoryModel::kExperimental; extern "C" const MemoryModel CurrentMemoryModel = MemoryModel::kExperimental;
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void SetStackRef(ObjHeader** location, const ObjHeader* object) {
mm::SetStackRef(location, const_cast<ObjHeader*>(object));
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void SetHeapRef(ObjHeader** location, const ObjHeader* object) {
mm::SetHeapRef(location, const_cast<ObjHeader*>(object));
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void ZeroHeapRef(ObjHeader** location) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void ZeroHeapRef(ObjHeader** location) {
mm::SetHeapRef(location, nullptr); mm::RefAccessor<false>{location} = nullptr;
} }
extern "C" RUNTIME_NOTHROW void ZeroArrayRefs(ArrayHeader* array) { extern "C" RUNTIME_NOTHROW void ZeroArrayRefs(ArrayHeader* array) {
for (uint32_t index = 0; index < array->count_; ++index) { for (uint32_t index = 0; index < array->count_; ++index) {
ObjHeader** location = ArrayAddressOfElementAt(array, index); ObjHeader** location = ArrayAddressOfElementAt(array, index);
mm::SetHeapRef(location, nullptr); mm::RefFieldAccessor{location} = nullptr;
} }
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void ZeroStackRef(ObjHeader** location) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void ZeroStackRef(ObjHeader** location) {
mm::SetStackRef(location, nullptr); mm::StackRefAccessor{location} = nullptr;
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateStackRef(ObjHeader** location, const ObjHeader* object) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateStackRef(ObjHeader** location, const ObjHeader* object) {
mm::SetStackRef(location, const_cast<ObjHeader*>(object)); mm::StackRefAccessor{location} = const_cast<ObjHeader*>(object);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateHeapRef(ObjHeader** location, const ObjHeader* object) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateHeapRef(ObjHeader** location, const ObjHeader* object) {
mm::SetHeapRef(location, const_cast<ObjHeader*>(object)); mm::RefAccessor<false>{location} = const_cast<ObjHeader*>(object);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateVolatileHeapRef(ObjHeader** location, const ObjHeader* object) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateVolatileHeapRef(ObjHeader** location, const ObjHeader* object) {
mm::SetHeapRefAtomicSeqCst(location, const_cast<ObjHeader*>(object)); mm::RefAccessor<false>{location}.storeAtomic(const_cast<ObjHeader*>(object), std::memory_order_seq_cst);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(CompareAndSwapVolatileHeapRef, ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(CompareAndSwapVolatileHeapRef, ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue) {
RETURN_RESULT_OF(mm::CompareAndSwapHeapRef, location, expectedValue, newValue); ObjHeader* actual = expectedValue;
mm::RefAccessor<false>{location}.compareAndExchange(actual, newValue, std::memory_order_seq_cst);
RETURN_OBJ(actual);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW bool CompareAndSetVolatileHeapRef(ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW bool CompareAndSetVolatileHeapRef(ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue) {
return mm::CompareAndSetHeapRef(location, expectedValue, newValue); return mm::RefAccessor<false>{location}.compareAndExchange(expectedValue, newValue, std::memory_order_seq_cst);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(GetAndSetVolatileHeapRef, ObjHeader** location, ObjHeader* newValue) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(GetAndSetVolatileHeapRef, ObjHeader** location, ObjHeader* newValue) {
RETURN_RESULT_OF(mm::GetAndSetHeapRef, location, newValue); RETURN_OBJ(mm::RefAccessor<false>{location}.exchange(newValue, std::memory_order_seq_cst));
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateHeapRefIfNull(ObjHeader** location, const ObjHeader* object) {
if (object == nullptr) return;
ObjHeader* result = nullptr; // No need to store this value in a rootset.
mm::CompareAndSwapHeapRef(location, nullptr, const_cast<ObjHeader*>(object), &result);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateHeapRefsInsideOneArray(const ArrayHeader* array, int fromIndex, extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateHeapRefsInsideOneArray(const ArrayHeader* array, int fromIndex,
@@ -209,22 +198,10 @@ extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateHeapRefsInsideOneArray(const
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) { extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) {
mm::SetStackRef(returnSlot, const_cast<ObjHeader*>(object)); UpdateStackRef(returnSlot, object);
} }
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(
SwapHeapRefLocked, ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue, int32_t* spinlock, int32_t* cookie) {
RETURN_RESULT_OF(mm::CompareAndSwapHeapRef, location, expectedValue, newValue);
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void SetHeapRefLocked(
ObjHeader** location, ObjHeader* newValue, int32_t* spinlock, int32_t* cookie) {
mm::SetHeapRefAtomic(location, newValue);
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(ReadHeapRefLocked, ObjHeader** location, int32_t* spinlock, int32_t* cookie) {
RETURN_RESULT_OF(mm::ReadHeapRefAtomic, location);
}
extern "C" OBJ_GETTER(ReadHeapRefNoLock, ObjHeader* object, int32_t index) { extern "C" OBJ_GETTER(ReadHeapRefNoLock, ObjHeader* object, int32_t index) {
// TODO: Remove when legacy MM is gone. // TODO: Remove when legacy MM is gone.
@@ -513,7 +490,7 @@ extern "C" RUNTIME_NOTHROW OBJ_GETTER(AdoptStablePointer, void* pointer) {
AssertThreadState(ThreadState::kRunnable); AssertThreadState(ThreadState::kRunnable);
mm::StableRef stableRef(static_cast<mm::RawSpecialRef*>(pointer)); mm::StableRef stableRef(static_cast<mm::RawSpecialRef*>(pointer));
auto* obj = *stableRef; auto* obj = *stableRef;
mm::SetStackRef(OBJ_RESULT, obj); UpdateStackRef(OBJ_RESULT, obj);
std::move(stableRef).dispose(); std::move(stableRef).dispose();
return obj; return obj;
} }
@@ -11,66 +11,6 @@
using namespace kotlin; using namespace kotlin;
// TODO: Memory barriers.
ALWAYS_INLINE void mm::SetStackRef(ObjHeader** location, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);
*location = value;
}
ALWAYS_INLINE void mm::SetHeapRef(ObjHeader** location, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);
*location = value;
}
#pragma clang diagnostic push
// On 32-bit android arm clang warns of significant performance penalty because of large
// atomic operations. TODO: Consider using alternative ways of ordering memory operations if they
// turn out to be more efficient on these platforms.
#pragma clang diagnostic ignored "-Watomic-alignment"
ALWAYS_INLINE void mm::SetHeapRefAtomic(ObjHeader** location, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);
__atomic_store_n(location, value, __ATOMIC_RELEASE);
}
ALWAYS_INLINE void mm::SetHeapRefAtomicSeqCst(ObjHeader** location, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);
__atomic_store_n(location, value, __ATOMIC_SEQ_CST);
}
ALWAYS_INLINE OBJ_GETTER(mm::ReadHeapRefAtomic, ObjHeader** location) noexcept {
AssertThreadState(ThreadState::kRunnable);
// TODO: Make this work with GCs that can stop thread at any point.
auto result = __atomic_load_n(location, __ATOMIC_ACQUIRE);
RETURN_OBJ(result);
}
ALWAYS_INLINE OBJ_GETTER(mm::CompareAndSwapHeapRef, ObjHeader** location, ObjHeader* expected, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);
// TODO: Make this work with GCs that can stop thread at any point.
ObjHeader* actual = expected;
__atomic_compare_exchange_n(location, &actual, value, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
RETURN_OBJ(actual);
}
ALWAYS_INLINE bool mm::CompareAndSetHeapRef(ObjHeader** location, ObjHeader* expected, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);
// TODO: Make this work with GCs that can stop thread at any point.
ObjHeader* actual = expected;
return __atomic_compare_exchange_n(location, &actual, value, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
ALWAYS_INLINE OBJ_GETTER(mm::GetAndSetHeapRef, ObjHeader** location, ObjHeader* value) noexcept {
AssertThreadState(ThreadState::kRunnable);;
auto *actual = __atomic_exchange_n(location, value, __ATOMIC_SEQ_CST);
RETURN_OBJ(actual);
}
#pragma clang diagnostic pop
OBJ_GETTER(mm::AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept { OBJ_GETTER(mm::AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept {
AssertThreadState(threadData, ThreadState::kRunnable); AssertThreadState(threadData, ThreadState::kRunnable);
// TODO: Make this work with GCs that can stop thread at any point. // TODO: Make this work with GCs that can stop thread at any point.
+1 -14
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * Copyright 2010-2023 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. * that can be found in the LICENSE file.
*/ */
@@ -13,22 +13,9 @@ namespace mm {
class ThreadData; class ThreadData;
// TODO: Make sure these operations work with any kind of thread stopping: safepoints and signals.
// TODO: Consider adding some kind of an `Object` type (that wraps `ObjHeader*`) which
// will have these operations for a friendlier API.
// TODO: `OBJ_GETTER` is used because the returned objects needs to be accessible via the rootset before the function // TODO: `OBJ_GETTER` is used because the returned objects needs to be accessible via the rootset before the function
// returns. If we had a different way to efficiently keep the object in the roots, `OBJ_GETTER` can be removed. // returns. If we had a different way to efficiently keep the object in the roots, `OBJ_GETTER` can be removed.
void SetStackRef(ObjHeader** location, ObjHeader* value) noexcept;
void SetHeapRef(ObjHeader** location, ObjHeader* value) noexcept;
void SetHeapRefAtomic(ObjHeader** location, ObjHeader* value) noexcept;
void SetHeapRefAtomicSeqCst(ObjHeader** location, ObjHeader* value) noexcept;
OBJ_GETTER(ReadHeapRefAtomic, ObjHeader** location) noexcept;
OBJ_GETTER(CompareAndSwapHeapRef, ObjHeader** location, ObjHeader* expected, ObjHeader* value) noexcept;
bool CompareAndSetHeapRef(ObjHeader** location, ObjHeader* expected, ObjHeader* value) noexcept;
OBJ_GETTER(GetAndSetHeapRef, ObjHeader** location, ObjHeader* value) noexcept;
OBJ_GETTER(AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept; OBJ_GETTER(AllocateObject, ThreadData* threadData, const TypeInfo* typeInfo) noexcept;
OBJ_GETTER(AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) noexcept; OBJ_GETTER(AllocateArray, ThreadData* threadData, const TypeInfo* typeInfo, uint32_t elements) noexcept;
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * Copyright 2010-2023 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. * that can be found in the LICENSE file.
*/ */
@@ -10,49 +10,54 @@
#include "Memory.h" #include "Memory.h"
#include "Natives.h" #include "Natives.h"
#include "ReferenceOps.hpp"
#include "Types.h" #include "Types.h"
#include "ObjectOps.hpp"
namespace kotlin { namespace kotlin {
// TODO: Consider an iterator/ranges based approaches for traversals. // TODO: Consider an iterator/ranges based approaches for traversals.
template <typename F> template <typename F>
ALWAYS_INLINE void traverseClassObjectFields(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<ObjHeader**>()))) { ALWAYS_INLINE void traverseClassObjectFields(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<mm::RefFieldAccessor>()))) {
const TypeInfo* typeInfo = object->type_info(); const TypeInfo* typeInfo = object->type_info();
RuntimeAssert(typeInfo != theArrayTypeInfo, "Must not be an array of objects"); RuntimeAssert(typeInfo != theArrayTypeInfo, "Must not be an array of objects");
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
process(reinterpret_cast<ObjHeader**>(reinterpret_cast<uintptr_t>(object) + typeInfo->objOffsets_[index])); auto fieldPtr = reinterpret_cast<ObjHeader**>(reinterpret_cast<uintptr_t>(object) + typeInfo->objOffsets_[index]);
process(mm::RefFieldAccessor(fieldPtr));
} }
} }
template <typename F> template <typename F>
ALWAYS_INLINE void traverseArrayOfObjectsElements(ArrayHeader* array, F process) noexcept(noexcept(process(std::declval<ObjHeader**>()))) { ALWAYS_INLINE void traverseArrayOfObjectsElements(ArrayHeader* array, F process) noexcept(noexcept(process(std::declval<mm::RefFieldAccessor>()))) {
RuntimeAssert(array->type_info() == theArrayTypeInfo, "Must be an array of objects"); RuntimeAssert(array->type_info() == theArrayTypeInfo, "Must be an array of objects");
for (uint32_t index = 0; index < array->count_; index++) { for (uint32_t index = 0; index < array->count_; index++) {
process(ArrayAddressOfElementAt(array, index)); process(mm::RefFieldAccessor(ArrayAddressOfElementAt(array, index)));
} }
} }
template <typename F> template <typename F>
void traverseObjectFields(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<ObjHeader**>()))) { void traverseObjectFields(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<mm::RefFieldAccessor>()))) {
const TypeInfo* typeInfo = object->type_info(); const TypeInfo* typeInfo = object->type_info();
// Only consider arrays of objects, not arrays of primitives. // Only consider arrays of objects, not arrays of primitives.
if (typeInfo != theArrayTypeInfo) { if (typeInfo != theArrayTypeInfo) {
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
process(reinterpret_cast<ObjHeader**>(reinterpret_cast<uintptr_t>(object) + typeInfo->objOffsets_[index])); auto fieldPtr = reinterpret_cast<ObjHeader**>(reinterpret_cast<uintptr_t>(object) + typeInfo->objOffsets_[index]);
process(mm::RefFieldAccessor(fieldPtr));
} }
} else { } else {
ArrayHeader* array = object->array(); ArrayHeader* array = object->array();
for (uint32_t index = 0; index < array->count_; index++) { for (uint32_t index = 0; index < array->count_; index++) {
process(ArrayAddressOfElementAt(array, index)); process(mm::RefFieldAccessor(ArrayAddressOfElementAt(array, index)));
} }
} }
} }
// FIXME explicitly mention no barriers
template <typename F> template <typename F>
void traverseReferredObjects(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<ObjHeader*>()))) { void traverseReferredObjects(ObjHeader* object, F process) noexcept(noexcept(process(std::declval<ObjHeader*>()))) {
traverseObjectFields(object, [&process](ObjHeader** location) noexcept(noexcept(process(std::declval<ObjHeader*>()))) { traverseObjectFields(object, [&process](auto accessor) noexcept(noexcept(process(std::declval<ObjHeader*>()))) {
if (ObjHeader* ref = *location) { if (ObjHeader* ref = accessor.direct()) {
process(ref); process(ref);
} }
}); });
@@ -9,8 +9,10 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "ObjectTestSupport.hpp" #include "ObjectTestSupport.hpp"
#include "ReferenceOps.hpp"
#include "Types.h" #include "Types.h"
#include "Utils.hpp" #include "Utils.hpp"
#include "ObjectOps.hpp"
using namespace kotlin; using namespace kotlin;
@@ -18,25 +20,14 @@ using ::testing::_;
namespace { namespace {
struct CallableWithExceptions {
void operator()(ObjHeader*) noexcept(false) {}
void operator()(ObjHeader**) noexcept(false) {}
};
struct CallableWithoutExceptions {
void operator()(ObjHeader*) noexcept {}
void operator()(ObjHeader**) noexcept {}
};
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
struct Payload { struct Payload {
ObjHeader* field1; mm::RefField field1;
ObjHeader* field2; mm::RefField field2;
ObjHeader* field3; mm::RefField field3;
static constexpr std::array kFields{ static constexpr std::array kFields{
&Payload::field1, &Payload::field1,
@@ -45,9 +36,22 @@ struct Payload {
}; };
}; };
using ProcessFunMock = testing::StrictMock<testing::MockFunction<void(mm::RefFieldAccessor)>>;
MATCHER_P(SameAccessor, accessor, "") {
return arg.direct().location() == accessor.direct().location();
}
} // namespace } // namespace
TEST(ObjectTraversalTest, TraverseFieldsExceptions) { TEST(ObjectTraversalTest, TraverseFieldsExceptions) {
struct CallableWithExceptions {
void operator()(mm::RefFieldAccessor) noexcept(false) {}
};
struct CallableWithoutExceptions {
void operator()(mm::RefFieldAccessor) noexcept {}
};
static_assert( static_assert(
noexcept(traverseObjectFields(std::declval<ObjHeader*>(), std::declval<CallableWithoutExceptions>())), noexcept(traverseObjectFields(std::declval<ObjHeader*>(), std::declval<CallableWithoutExceptions>())),
"Callable is noexcept, so traverse is noexcept"); "Callable is noexcept, so traverse is noexcept");
@@ -59,10 +63,10 @@ TEST(ObjectTraversalTest, TraverseFieldsExceptions) {
TEST(ObjectTraversalTest, TraverseEmptyObjectFields) { TEST(ObjectTraversalTest, TraverseEmptyObjectFields) {
test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()}; test_support::TypeInfoHolder type{test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
test_support::Object<EmptyPayload> object(type.typeInfo()); test_support::Object<EmptyPayload> object(type.typeInfo());
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> process; ProcessFunMock process;
EXPECT_CALL(process, Call(_)).Times(0); EXPECT_CALL(process, Call(_)).Times(0);
traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); traverseObjectFields(object.header(), process.AsStdFunction());
} }
TEST(ObjectTraversalTest, TraverseObjectFields) { TEST(ObjectTraversalTest, TraverseObjectFields) {
@@ -70,14 +74,14 @@ TEST(ObjectTraversalTest, TraverseObjectFields) {
ObjHeader field1; ObjHeader field1;
ObjHeader field3; ObjHeader field3;
test_support::Object<Payload> object(type.typeInfo()); test_support::Object<Payload> object(type.typeInfo());
object->field1 = &field1; object->field1.direct() = &field1;
object->field3 = &field3; object->field3.direct() = &field3;
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> process; ProcessFunMock process;
EXPECT_CALL(process, Call(&object->field1)); EXPECT_CALL(process, Call(SameAccessor(object->field1.accessor())));
EXPECT_CALL(process, Call(&object->field2)); EXPECT_CALL(process, Call(SameAccessor(object->field2.accessor())));
EXPECT_CALL(process, Call(&object->field3)); EXPECT_CALL(process, Call(SameAccessor(object->field3.accessor())));
traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); traverseObjectFields(object.header(), process.AsStdFunction());
} }
TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) { TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) {
@@ -88,16 +92,16 @@ TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) {
ObjHeader field2; ObjHeader field2;
ObjHeader field3; ObjHeader field3;
test_support::Object<Payload> object(type.typeInfo()); test_support::Object<Payload> object(type.typeInfo());
object->field1 = &field1; object->field1.direct() = &field1;
object->field2 = &field2; object->field2.direct() = &field2;
object->field3 = &field3; object->field3.direct() = &field3;
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> process; ProcessFunMock process;
EXPECT_CALL(process, Call(&object->field1)); EXPECT_CALL(process, Call(SameAccessor(object->field1.accessor())));
EXPECT_CALL(process, Call(&object->field2)).WillOnce([]() { throw kException; }); EXPECT_CALL(process, Call(SameAccessor(object->field2.accessor()))).WillOnce([]() { throw kException; });
EXPECT_CALL(process, Call(&object->field3)).Times(0); EXPECT_CALL(process, Call(SameAccessor(object->field3.accessor()))).Times(0);
try { try {
traverseObjectFields(object.header(), [&process](ObjHeader** field) { process.Call(field); }); traverseObjectFields(object.header(), process.AsStdFunction());
} catch (int exception) { } catch (int exception) {
EXPECT_THAT(exception, kException); EXPECT_THAT(exception, kException);
} catch (...) { } catch (...) {
@@ -107,24 +111,24 @@ TEST(ObjectTraversalTest, TraverseObjectFieldsWithException) {
TEST(ObjectTraversalTest, TraverseEmptyArrayFields) { TEST(ObjectTraversalTest, TraverseEmptyArrayFields) {
test_support::ObjectArray<0> array; test_support::ObjectArray<0> array;
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> process; ProcessFunMock process;
EXPECT_CALL(process, Call(_)).Times(0); EXPECT_CALL(process, Call(_)).Times(0);
traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); traverseObjectFields(array.header(), process.AsStdFunction());
} }
TEST(ObjectTraversalTest, TraverseArrayFields) { TEST(ObjectTraversalTest, TraverseArrayFields) {
ObjHeader element1; ObjHeader element1;
ObjHeader element3; ObjHeader element3;
test_support::ObjectArray<3> array; test_support::ObjectArray<3> array;
array.elements()[0] = &element1; array.elements()[0].direct() = &element1;
array.elements()[2] = &element3; array.elements()[2].direct() = &element3;
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> process; ProcessFunMock process;
EXPECT_CALL(process, Call(&array.elements()[0])); EXPECT_CALL(process, Call(SameAccessor(array.elements()[0].accessor())));
EXPECT_CALL(process, Call(&array.elements()[1])); EXPECT_CALL(process, Call(SameAccessor(array.elements()[1].accessor())));
EXPECT_CALL(process, Call(&array.elements()[2])); EXPECT_CALL(process, Call(SameAccessor(array.elements()[2].accessor())));
traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); traverseObjectFields(array.header(), process.AsStdFunction());
} }
TEST(ObjectTraversalTest, TraverseArrayFieldsWithException) { TEST(ObjectTraversalTest, TraverseArrayFieldsWithException) {
@@ -134,16 +138,16 @@ TEST(ObjectTraversalTest, TraverseArrayFieldsWithException) {
ObjHeader element2; ObjHeader element2;
ObjHeader element3; ObjHeader element3;
test_support::ObjectArray<3> array; test_support::ObjectArray<3> array;
array.elements()[0] = &element1; array.elements()[0].direct() = &element1;
array.elements()[1] = &element2; array.elements()[1].direct() = &element2;
array.elements()[2] = &element3; array.elements()[2].direct() = &element3;
testing::StrictMock<testing::MockFunction<void(ObjHeader**)>> process; ProcessFunMock process;
EXPECT_CALL(process, Call(&array.elements()[0])); EXPECT_CALL(process, Call(SameAccessor(array.elements()[0].accessor())));
EXPECT_CALL(process, Call(&array.elements()[1])).WillOnce([]() { throw kException; }); EXPECT_CALL(process, Call(SameAccessor(array.elements()[1].accessor()))).WillOnce([]() { throw kException; });
EXPECT_CALL(process, Call(&array.elements()[2])).Times(0); EXPECT_CALL(process, Call(SameAccessor(array.elements()[2].accessor()))).Times(0);
try { try {
traverseObjectFields(array.header(), [&process](ObjHeader** field) { process.Call(field); }); traverseObjectFields(array.header(), process.AsStdFunction());
} catch (int exception) { } catch (int exception) {
EXPECT_THAT(exception, kException); EXPECT_THAT(exception, kException);
} catch (...) { } catch (...) {
@@ -152,6 +156,13 @@ TEST(ObjectTraversalTest, TraverseArrayFieldsWithException) {
} }
TEST(ObjectTraversalTest, TraverseRefsExceptions) { TEST(ObjectTraversalTest, TraverseRefsExceptions) {
struct CallableWithExceptions {
void operator()(ObjHeader*) noexcept(false) {}
};
struct CallableWithoutExceptions {
void operator()(ObjHeader*) noexcept {}
};
static_assert( static_assert(
noexcept(traverseReferredObjects(std::declval<ObjHeader*>(), std::declval<CallableWithoutExceptions>())), noexcept(traverseReferredObjects(std::declval<ObjHeader*>(), std::declval<CallableWithoutExceptions>())),
"Callable is noexcept, so traverse is noexcept"); "Callable is noexcept, so traverse is noexcept");
@@ -174,8 +185,8 @@ TEST(ObjectTraversalTest, TraverseObjectRefs) {
ObjHeader field1; ObjHeader field1;
ObjHeader field3; ObjHeader field3;
test_support::Object<Payload> object(type.typeInfo()); test_support::Object<Payload> object(type.typeInfo());
object->field1 = &field1; object->field1.direct() = &field1;
object->field3 = &field3; object->field3.direct() = &field3;
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process; testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process;
EXPECT_CALL(process, Call(&field1)); EXPECT_CALL(process, Call(&field1));
@@ -191,9 +202,9 @@ TEST(ObjectTraversalTest, TraverseObjectRefsWithException) {
ObjHeader field2; ObjHeader field2;
ObjHeader field3; ObjHeader field3;
test_support::Object<Payload> object(type.typeInfo()); test_support::Object<Payload> object(type.typeInfo());
object->field1 = &field1; object->field1.direct() = &field1;
object->field2 = &field2; object->field2.direct() = &field2;
object->field3 = &field3; object->field3.direct() = &field3;
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process; testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process;
EXPECT_CALL(process, Call(&field1)); EXPECT_CALL(process, Call(&field1));
@@ -220,8 +231,8 @@ TEST(ObjectTraversalTest, TraverseArrayRefs) {
ObjHeader element1; ObjHeader element1;
ObjHeader element3; ObjHeader element3;
test_support::ObjectArray<3> array; test_support::ObjectArray<3> array;
array.elements()[0] = &element1; array.elements()[0].direct() = &element1;
array.elements()[2] = &element3; array.elements()[2].direct() = &element3;
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process; testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process;
EXPECT_CALL(process, Call(&element1)); EXPECT_CALL(process, Call(&element1));
@@ -236,9 +247,9 @@ TEST(ObjectTraversalTest, TraverseArrayRefsWithException) {
ObjHeader element2; ObjHeader element2;
ObjHeader element3; ObjHeader element3;
test_support::ObjectArray<3> array; test_support::ObjectArray<3> array;
array.elements()[0] = &element1; array.elements()[0].direct() = &element1;
array.elements()[1] = &element2; array.elements()[1].direct() = &element2;
array.elements()[2] = &element3; array.elements()[2].direct() = &element3;
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process; testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> process;
EXPECT_CALL(process, Call(&element1)); EXPECT_CALL(process, Call(&element1));
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2023 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 "ReferenceOps.hpp"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
using namespace kotlin;
// on stack
template<> void mm::RefAccessor<true>::beforeStore(ObjHeader*) noexcept {}
template<> void mm::RefAccessor<true>::afterStore(ObjHeader*) noexcept {}
template<> void mm::RefAccessor<true>::beforeLoad() noexcept {}
template<> void mm::RefAccessor<true>::afterLoad() noexcept {}
// on heap
template<> void mm::RefAccessor<false>::beforeStore(ObjHeader*) noexcept {}
template<> void mm::RefAccessor<false>::afterStore(ObjHeader*) noexcept {}
template<> void mm::RefAccessor<false>::beforeLoad() noexcept {}
template<> void mm::RefAccessor<false>::afterLoad() noexcept {}
ALWAYS_INLINE OBJ_GETTER(mm::weakRefReadBarrier, std::atomic<ObjHeader*>& referee) noexcept {
RETURN_RESULT_OF(kotlin::gc::tryRef, referee);
}
@@ -11,6 +11,7 @@
#include "GC.hpp" #include "GC.hpp"
#include "Memory.h" #include "Memory.h"
#include "RawPtr.hpp" #include "RawPtr.hpp"
#include "ReferenceOps.hpp"
#include "ThreadRegistry.hpp" #include "ThreadRegistry.hpp"
namespace kotlin::mm { namespace kotlin::mm {
@@ -102,7 +103,7 @@ class SpecialRefRegistry : private Pinned {
OBJ_GETTER0(tryRef) noexcept { OBJ_GETTER0(tryRef) noexcept {
AssertThreadState(ThreadState::kRunnable); AssertThreadState(ThreadState::kRunnable);
RETURN_RESULT_OF(kotlin::gc::tryRef, obj_); RETURN_RESULT_OF(mm::weakRefReadBarrier, obj_);
} }
void retainRef() noexcept { void retainRef() noexcept {
@@ -22,12 +22,11 @@ testing::MockFunction<void(KRef)>* kotlin::test_support::internal::Kotlin_runUnh
namespace { namespace {
struct EmptyPayload { struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*; static constexpr kotlin::test_support::NoRefFields<EmptyPayload> kFields{};
static constexpr std::array<Field, 0> kFields{};
}; };
kotlin::test_support::TypeInfoHolder theAnyTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()}; kotlin::test_support::TypeInfoHolder theAnyTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
kotlin::test_support::TypeInfoHolder theArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<ObjHeader*>()}; kotlin::test_support::TypeInfoHolder theArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<kotlin::mm::RefField>()};
kotlin::test_support::TypeInfoHolder theBooleanArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KBoolean>()}; kotlin::test_support::TypeInfoHolder theBooleanArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KBoolean>()};
kotlin::test_support::TypeInfoHolder theByteArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KByte>()}; kotlin::test_support::TypeInfoHolder theByteArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KByte>()};
kotlin::test_support::TypeInfoHolder theCharArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KChar>()}; kotlin::test_support::TypeInfoHolder theCharArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KChar>()};