[K/N] Split Weak and MemorySharedRefs between mm implementations ^KT-56233

* A separate RegularWeakReferenceImpl for the new mm. Name is chosen for
  better consistency with other WeakReferenceImpl implementations.
* KRefSharedHolder and BackRefFromAssociatedObject implementations are
  independent between the legacy and the new mm.
* Better support for WeakReference in runtime unit tests.
This commit is contained in:
Alexander Shabalin
2023-03-31 14:39:00 +02:00
committed by Space Team
parent 87da670319
commit 1f1b26f2a5
26 changed files with 738 additions and 571 deletions
+8 -19
View File
@@ -149,10 +149,17 @@ ALWAYS_INLINE inline bool isNullOrMarker(const ObjHeader* obj) noexcept {
return reinterpret_cast<uintptr_t>(obj) <= 1;
}
class ForeignRefManager;
struct FrameOverlay;
// Legacy MM only:
class ForeignRefManager;
typedef ForeignRefManager* ForeignRefContext;
namespace kotlin::mm {
// New MM only:
struct RawSpecialRef;
} // namespace kotlin::mm
#ifdef __cplusplus
extern "C" {
#endif
@@ -337,24 +344,6 @@ bool Kotlin_Any_isShareable(ObjHeader* thiz);
void Kotlin_Any_share(ObjHeader* thiz);
void PerformFullGC(MemoryState* memory) RUNTIME_NOTHROW;
// Only for legacy
bool TryAddHeapRef(const ObjHeader* object);
void ReleaseHeapRefNoCollect(const ObjHeader* object) RUNTIME_NOTHROW;
// Only for experimental
OBJ_GETTER(TryRef, ObjHeader* object) RUNTIME_NOTHROW;
ForeignRefContext InitLocalForeignRef(ObjHeader* object);
ForeignRefContext InitForeignRef(ObjHeader* object);
void DeinitForeignRef(ObjHeader* object, ForeignRefContext context);
bool IsForeignRefAccessible(ObjHeader* object, ForeignRefContext context);
// Should be used when reference is read from a possibly shared variable,
// and there's nothing else keeping the object alive.
void AdoptReferenceFromSharedVariable(ObjHeader* object);
void CheckGlobalsAccessible();
// Sets state of the current thread to NATIVE (used by the new MM).
@@ -3,9 +3,8 @@
* that can be found in the LICENSE file.
*/
#include "Exceptions.h"
#include "MemorySharedRefs.hpp"
#include "Runtime.h"
#include "Types.h"
extern "C" {
@@ -13,217 +12,11 @@ extern "C" {
OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo, KConstNativePtr address);
} // extern "C"
namespace {
inline bool isForeignRefAccessible(ObjHeader* object, ForeignRefContext context) {
// If runtime has not been initialized on this thread, then the object is either unowned or shared.
// In the former case initialized runtime is required to throw exceptions
// in the latter case -- to provide proper execution context for caller.
// TODO: this probably can't be called in uninitialized state in the new MM.
Kotlin_initRuntimeIfNeeded();
return IsForeignRefAccessible(object, context);
}
RUNTIME_NORETURN inline void throwIllegalSharingException(ObjHeader* object) {
// TODO: add some info about the context.
// Note: retrieving 'type_info()' is supposed to be correct even for unowned object.
ThrowIllegalObjectSharingException(object->type_info(), object);
}
RUNTIME_NORETURN inline void terminateWithIllegalSharingException(ObjHeader* object) {
#if KONAN_NO_EXCEPTIONS
// This will terminate.
throwIllegalSharingException(object);
#else
try {
throwIllegalSharingException(object);
} catch (...) {
// A trick to terminate with unhandled exception. This will print a stack trace
// and write to iOS crash log.
std::terminate();
}
#endif
}
template <ErrorPolicy errorPolicy>
bool ensureRefAccessible(ObjHeader* object, ForeignRefContext context) {
static_assert(errorPolicy != ErrorPolicy::kIgnore, "Must've been handled by specialization");
if (isForeignRefAccessible(object, context)) {
return true;
}
switch (errorPolicy) {
case ErrorPolicy::kDefaultValue:
return false;
case ErrorPolicy::kThrow:
throwIllegalSharingException(object);
case ErrorPolicy::kTerminate:
terminateWithIllegalSharingException(object);
}
}
template <>
bool ensureRefAccessible<ErrorPolicy::kIgnore>(ObjHeader* object, ForeignRefContext context) {
return true;
}
} // namespace
void KRefSharedHolder::initLocal(ObjHeader* obj) {
RuntimeAssert(obj != nullptr, "must not be null");
context_ = InitLocalForeignRef(obj);
obj_ = obj;
}
void KRefSharedHolder::init(ObjHeader* obj) {
RuntimeAssert(obj != nullptr, "must not be null");
context_ = InitForeignRef(obj);
obj_ = obj;
}
template <ErrorPolicy errorPolicy>
ObjHeader* KRefSharedHolder::ref() const {
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
if (!ensureRefAccessible<errorPolicy>(obj_, context_)) {
return nullptr;
}
AdoptReferenceFromSharedVariable(obj_);
return obj_;
}
template ObjHeader* KRefSharedHolder::ref<ErrorPolicy::kDefaultValue>() const;
template ObjHeader* KRefSharedHolder::ref<ErrorPolicy::kThrow>() const;
template ObjHeader* KRefSharedHolder::ref<ErrorPolicy::kTerminate>() const;
void KRefSharedHolder::dispose() const {
if (obj_ == nullptr) {
// To handle the case when it is not initialized. See [KotlinMutableSet/Dictionary dealloc].
return;
}
DeinitForeignRef(obj_, context_);
}
OBJ_GETTER0(KRefSharedHolder::describe) const {
// Note: retrieving 'type_info()' is supposed to be correct even for unowned object.
RETURN_RESULT_OF(DescribeObjectForDebugging, obj_->type_info(), obj_);
}
void BackRefFromAssociatedObject::initAndAddRef(ObjHeader* obj) {
RuntimeAssert(obj != nullptr, "must not be null");
obj_ = obj;
// Generally a specialized addRef below:
context_ = InitForeignRef(obj);
refCount = 1;
}
template <ErrorPolicy errorPolicy>
void BackRefFromAssociatedObject::addRef() {
static_assert(errorPolicy != ErrorPolicy::kDefaultValue, "Cannot use default return value here");
// Can be called both from Native state (if ObjC or Swift code adds RC)
// and from Runnable state (Kotlin_ObjCExport_refToObjC).
if (atomicAdd(&refCount, 1) == 1) {
if (obj_ == nullptr) return; // E.g. after [detach].
kotlin::CalledFromNativeGuard guard(/* reentrant */ true);
// There are no references to the associated object itself, so Kotlin object is being passed from Kotlin,
// and it is owned therefore.
ensureRefAccessible<errorPolicy>(obj_, context_); // TODO: consider removing explicit verification.
// Foreign reference has already been deinitialized (see [releaseRef]).
// Create a new one:
context_ = InitForeignRef(obj_);
}
}
template void BackRefFromAssociatedObject::addRef<ErrorPolicy::kThrow>();
template void BackRefFromAssociatedObject::addRef<ErrorPolicy::kTerminate>();
template <ErrorPolicy errorPolicy>
bool BackRefFromAssociatedObject::tryAddRef() {
static_assert(errorPolicy != ErrorPolicy::kDefaultValue, "Cannot use default return value here");
kotlin::CalledFromNativeGuard guard;
if (obj_ == nullptr) return false; // E.g. after [detach].
if (CurrentMemoryModel == MemoryModel::kExperimental) {
ObjHolder holder;
ObjHeader* obj = TryRef(obj_, holder.slot());
// Failed to lock weak reference.
if (obj == nullptr) return false;
RuntimeAssert(obj == obj_, "Mismatched locked weak. obj=%p obj_=%p", obj, obj_);
// TODO: This is a very weird way to ask for "unsafe" addRef.
addRef<ErrorPolicy::kIgnore>();
return true;
} else {
// Suboptimal but simple:
ensureRefAccessible<errorPolicy>(obj_, context_);
ObjHeader* obj = obj_;
if (!TryAddHeapRef(obj)) return false;
RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above");
// TODO: This is a very weird way to ask for "unsafe" addRef.
addRef<ErrorPolicy::kIgnore>();
ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef.
// TODO: consider optimizing for non-shared objects.
return true;
}
}
template bool BackRefFromAssociatedObject::tryAddRef<ErrorPolicy::kThrow>();
template bool BackRefFromAssociatedObject::tryAddRef<ErrorPolicy::kTerminate>();
void BackRefFromAssociatedObject::releaseRef() {
ForeignRefContext context = context_;
if (atomicAdd(&refCount, -1) == 0) {
if (obj_ == nullptr) return; // E.g. after [detach].
kotlin::CalledFromNativeGuard guard;
// Note: by this moment "subsequent" addRef may have already happened and patched context_.
// So use the value loaded before refCount update:
DeinitForeignRef(obj_, context);
// From this moment [context] is generally a dangling pointer.
// This is handled in [IsForeignRefAccessible] and [addRef].
// TODO: This probably isn't fine in new MM. Make sure it works.
}
}
void BackRefFromAssociatedObject::detach() {
RuntimeAssert(atomicGet(&refCount) == 0, "unexpected refCount");
obj_ = nullptr; // Handled in addRef/tryAddRef/releaseRef/ref.
}
ALWAYS_INLINE void BackRefFromAssociatedObject::assertDetached() {
RuntimeAssert(obj_ == nullptr, "Expecting this=%p to be detached, but found obj_=%p", this, obj_);
}
template <ErrorPolicy errorPolicy>
ObjHeader* BackRefFromAssociatedObject::ref() const {
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
RuntimeAssert(obj_ != nullptr, "no valid Kotlin object found");
if (!ensureRefAccessible<errorPolicy>(obj_, context_)) {
return nullptr;
}
AdoptReferenceFromSharedVariable(obj_);
return obj_;
}
template ObjHeader* BackRefFromAssociatedObject::ref<ErrorPolicy::kDefaultValue>() const;
template ObjHeader* BackRefFromAssociatedObject::ref<ErrorPolicy::kThrow>() const;
template ObjHeader* BackRefFromAssociatedObject::ref<ErrorPolicy::kTerminate>() const;
extern "C" {
RUNTIME_NOTHROW void KRefSharedHolder_initLocal(KRefSharedHolder* holder, ObjHeader* obj) {
holder->initLocal(obj);
@@ -233,8 +26,8 @@ RUNTIME_NOTHROW void KRefSharedHolder_init(KRefSharedHolder* holder, ObjHeader*
holder->init(obj);
}
RUNTIME_NOTHROW void KRefSharedHolder_dispose(const KRefSharedHolder* holder) {
holder->dispose();
RUNTIME_NOTHROW void KRefSharedHolder_dispose(KRefSharedHolder* holder) {
holder->dispose();
}
RUNTIME_NOTHROW ObjHeader* KRefSharedHolder_ref(const KRefSharedHolder* holder) {
@@ -28,9 +28,9 @@ class KRefSharedHolder {
template <ErrorPolicy errorPolicy>
ObjHeader* ref() const;
void dispose() const;
void dispose();
void disposeFromNative() const {
void disposeFromNative() {
kotlin::CalledFromNativeGuard guard;
dispose();
}
@@ -78,7 +78,7 @@ static_assert(
extern "C" {
RUNTIME_NOTHROW void KRefSharedHolder_initLocal(KRefSharedHolder* holder, ObjHeader* obj);
RUNTIME_NOTHROW void KRefSharedHolder_init(KRefSharedHolder* holder, ObjHeader* obj);
RUNTIME_NOTHROW void KRefSharedHolder_dispose(const KRefSharedHolder* holder);
RUNTIME_NOTHROW void KRefSharedHolder_dispose(KRefSharedHolder* holder);
RUNTIME_NOTHROW ObjHeader* KRefSharedHolder_ref(const KRefSharedHolder* holder);
} // extern "C"
@@ -101,8 +101,21 @@ private:
std_support::vector<int32_t> objOffsets_;
};
class Any : private Pinned {
public:
ObjHeader* header() noexcept { return &header_; }
void installMetaObject() noexcept { (void)header()->meta_object(); }
protected:
Any() noexcept = default;
~Any() = default;
ObjHeader header_;
};
template <typename Payload>
class Object : private Pinned {
class Object : public Any {
public:
class FieldIterator {
public:
@@ -160,15 +173,12 @@ public:
header_.typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
}
ObjHeader* header() noexcept { return &header_; }
Payload& operator*() noexcept { return payload_; }
Payload* operator->() noexcept { return &payload_; }
FieldIterable fields() noexcept { return FieldIterable(*this); }
private:
ObjHeader header_;
Payload payload_{};
};
@@ -191,7 +201,7 @@ namespace internal {
// Array types are predetermined, use one of the subclasses below.
template <typename Payload, size_t ElementCount>
class Array : private Pinned {
class Array : public Any {
public:
static Array<Payload, ElementCount>& FromArrayHeader(ArrayHeader* arr) noexcept {
static_assert(std::is_trivially_destructible_v<Array>, "Array destructor is not guaranteed to be called.");
@@ -210,17 +220,16 @@ public:
TypeInfoHolder{TypeInfoHolder::ArrayBuilder<Payload>()}.typeInfo()->IsLayoutCompatible(typeInfo),
"constructing array from incompatible type info");
header_.typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
header_.count_ = ElementCount;
count_ = ElementCount;
}
ObjHeader* header() noexcept { return header_.obj(); }
ArrayHeader* arrayHeader() noexcept { return &header_; }
ArrayHeader* arrayHeader() noexcept { return header()->array(); }
std::array<Payload, ElementCount>& elements() noexcept { return elements_; }
private:
ArrayHeader header_;
std::array<Payload, ElementCount> elements_{};
uint32_t count_;
alignas(ArrayHeader) std::array<Payload, ElementCount> elements_{};
};
} // namespace internal
@@ -335,5 +344,31 @@ public:
String() noexcept : internal::Array<KChar, ElementCount>(theStringTypeInfo) {}
};
struct RegularWeakReferenceImplPayload {
void* referred;
using Field = ObjHeader* RegularWeakReferenceImplPayload::*;
static constexpr std::array<Field, 0> kFields{};
};
extern "C" OBJ_GETTER(Konan_RegularWeakReferenceImpl_get, ObjHeader*);
class RegularWeakReferenceImpl : public Object<RegularWeakReferenceImplPayload> {
public:
static RegularWeakReferenceImpl& FromObjHeader(ObjHeader* obj) noexcept {
RuntimeAssert(obj->type_info() == theRegularWeakReferenceImplTypeInfo, "Invalid type");
return static_cast<RegularWeakReferenceImpl&>(Object::FromObjHeader(obj));
}
RegularWeakReferenceImpl() noexcept : Object(theRegularWeakReferenceImplTypeInfo) {}
OBJ_GETTER0(get) noexcept { RETURN_RESULT_OF(Konan_RegularWeakReferenceImpl_get, header()); }
ObjHeader* get() noexcept {
ObjHeader* result;
return get(&result);
}
};
} // namespace test_support
} // namespace kotlin
@@ -70,6 +70,7 @@ extern const TypeInfo* theThrowableTypeInfo;
extern const TypeInfo* theUnitTypeInfo;
extern const TypeInfo* theWorkerBoundReferenceTypeInfo;
extern const TypeInfo* theCleanerImplTypeInfo;
extern const TypeInfo* theRegularWeakReferenceImplTypeInfo;
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
KBoolean IsInstanceOfClassFast(const ObjHeader* obj, int32_t lo, int32_t hi) RUNTIME_PURE;
-110
View File
@@ -1,110 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Weak.h"
#include "Memory.h"
#include "Types.h"
namespace {
// TODO: an ugly hack with fixed layout.
struct WeakReferenceCounter {
ObjHeader header;
KRef referred;
KInt lock;
KInt cookie;
};
inline WeakReferenceCounter* asWeakReferenceCounter(ObjHeader* obj) {
return reinterpret_cast<WeakReferenceCounter*>(obj);
}
#if !KONAN_NO_THREADS
inline void lock(int32_t* address) {
RuntimeAssert(*address == 0 || *address == 1, "Incorrect lock state");
while (__sync_val_compare_and_swap(address, 0, 1) == 1);
}
inline void unlock(int32_t* address) {
int old = __sync_val_compare_and_swap(address, 1, 0);
RuntimeAssert(old == 1, "Incorrect lock state");
}
#endif
} // namespace
extern "C" {
OBJ_GETTER(makeWeakReferenceCounter, void*);
OBJ_GETTER(makeObjCWeakReferenceImpl, void*);
OBJ_GETTER(makePermanentWeakReferenceImpl, ObjHeader*);
// See Weak.kt for implementation details.
// Retrieve link on the counter object.
OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
if (referred->permanent()) {
RETURN_RESULT_OF(makePermanentWeakReferenceImpl, referred);
}
#if KONAN_OBJC_INTEROP
if (IsInstance(referred, theObjCObjectWrapperTypeInfo)) {
RETURN_RESULT_OF(makeObjCWeakReferenceImpl, referred->GetAssociatedObject());
}
#endif // KONAN_OBJC_INTEROP
ObjHeader* weakCounter = referred->GetWeakCounter();
if (weakCounter == nullptr) {
ObjHolder counterHolder;
// Cast unneeded, just to emphasize we store an object reference as void*.
ObjHeader* counter = makeWeakReferenceCounter(reinterpret_cast<void*>(referred), counterHolder.slot());
weakCounter = referred->GetOrSetWeakCounter(counter);
}
RETURN_OBJ(weakCounter);
}
// Materialize a weak reference to either null or the real reference.
OBJ_GETTER(Konan_WeakReferenceCounter_get, ObjHeader* counter) {
ObjHeader** referredAddress = &asWeakReferenceCounter(counter)->referred;
#if KONAN_NO_THREADS
RETURN_OBJ(*referredAddress);
#else
auto* weakCounter = asWeakReferenceCounter(counter);
RETURN_RESULT_OF(ReadHeapRefLocked, referredAddress, &weakCounter->lock, &weakCounter->cookie);
#endif
}
ALWAYS_INLINE ObjHeader* UnsafeWeakReferenceCounterGet(ObjHeader* counter) {
return asWeakReferenceCounter(counter)->referred;
}
void WeakReferenceCounterClear(ObjHeader* counter) {
ObjHeader** referredAddress = &asWeakReferenceCounter(counter)->referred;
// Note, that we don't do UpdateRef here, as reference is weak.
#if KONAN_NO_THREADS
*referredAddress = nullptr;
#else
int32_t* lockAddress = &asWeakReferenceCounter(counter)->lock;
// Spinlock.
lock(lockAddress);
*referredAddress = nullptr;
unlock(lockAddress);
#endif
}
} // extern "C"
-19
View File
@@ -1,19 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#ifndef RUNTIME_WEAK_H
#define RUNTIME_WEAK_H
#include "Memory.h"
extern "C" {
// Atomically clears counter object reference.
void WeakReferenceCounterClear(ObjHeader* counter);
ObjHeader* UnsafeWeakReferenceCounterGet(ObjHeader* counter);
} // extern "C"
#endif // RUNTIME_WEAK_H