Weak references. (#1459)
This commit is contained in:
@@ -2076,6 +2076,11 @@ task memory_escape2(type: RunKonanTest) {
|
||||
source = "runtime/memory/escape2.kt"
|
||||
}
|
||||
|
||||
task memory_weak0(type: RunKonanTest) {
|
||||
goldValue = "Data(s=Hello)\nnull\nOK\n"
|
||||
source = "runtime/memory/weak0.kt"
|
||||
}
|
||||
|
||||
task mpp1(type: RunStandaloneKonanTest) {
|
||||
source = "codegen/mpp/mpp1.kt"
|
||||
flags = ['-tr', '-Xmulti-platform']
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package runtime.memory.weak0
|
||||
|
||||
import kotlin.test.*
|
||||
import konan.ref.*
|
||||
|
||||
data class Data(val s: String)
|
||||
|
||||
fun localWeak(): WeakReference<Data> {
|
||||
val x = Data("Hello")
|
||||
val weak = WeakReference(x)
|
||||
|
||||
println(weak.get())
|
||||
return weak
|
||||
}
|
||||
|
||||
fun multiWeak(): Array<WeakReference<Data>> {
|
||||
val x = Data("Hello")
|
||||
val weaks = Array(100, { WeakReference(x) } )
|
||||
weaks.forEach {
|
||||
it -> if (it.get()?.s != "Hello") throw Error("bad reference")
|
||||
}
|
||||
return weaks
|
||||
}
|
||||
|
||||
@Test fun runTest() {
|
||||
val weak = localWeak()
|
||||
val value = weak.get()
|
||||
println(value?.toString())
|
||||
|
||||
val weaks = multiWeak()
|
||||
weaks.forEach {
|
||||
it -> if (it.get()?.s != null) throw Error("not null")
|
||||
}
|
||||
println("OK")
|
||||
}
|
||||
@@ -464,7 +464,7 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) {
|
||||
namespace {
|
||||
|
||||
template<typename func>
|
||||
void traverseContainerObjectFields(ContainerHeader* container, func process) {
|
||||
inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
|
||||
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
|
||||
for (int object = 0; object < container->objectCount(); object++) {
|
||||
const TypeInfo* typeInfo = obj->type_info();
|
||||
@@ -485,7 +485,7 @@ void traverseContainerObjectFields(ContainerHeader* container, func process) {
|
||||
}
|
||||
|
||||
template<typename func>
|
||||
void traverseContainerReferredObjects(ContainerHeader* container, func process) {
|
||||
inline void traverseContainerReferredObjects(ContainerHeader* container, func process) {
|
||||
traverseContainerObjectFields(container, [process](ObjHeader** location) {
|
||||
ObjHeader* ref = *location;
|
||||
if (ref != nullptr) process(ref);
|
||||
@@ -510,9 +510,6 @@ inline void processFinalizerQueue(MemoryState* state) {
|
||||
#if TRACE_MEMORY
|
||||
state->containers->erase(container);
|
||||
#endif
|
||||
if (!isAggregatingFrozenContainer(container))
|
||||
runDeallocationHooks(container);
|
||||
|
||||
CONTAINER_DESTROY_EVENT(state, container)
|
||||
konanFreeMemory(container);
|
||||
atomicAdd(&allocCount, -1);
|
||||
@@ -835,8 +832,12 @@ MetaObjHeader* ObjHeader::createMetaObject(TypeInfo** location) {
|
||||
}
|
||||
|
||||
void ObjHeader::destroyMetaObject(TypeInfo** location) {
|
||||
TypeInfo* meta = *location;
|
||||
*location = nullptr;
|
||||
MetaObjHeader* meta = *(reinterpret_cast<MetaObjHeader**>(location));
|
||||
*const_cast<const TypeInfo**>(location) = meta->typeInfo_;
|
||||
if (meta->counter_ != nullptr) {
|
||||
WeakReferenceCounterClear(meta->counter_);
|
||||
UpdateRef(&meta->counter_, nullptr);
|
||||
}
|
||||
konanFreeMemory(meta);
|
||||
}
|
||||
|
||||
@@ -890,29 +891,29 @@ void FreeAggregatingFrozenContainer(ContainerHeader* container) {
|
||||
MEMORY_LOG("Freeing subcontainers done\n");
|
||||
}
|
||||
|
||||
void FreeContainer(ContainerHeader* header) {
|
||||
RuntimeAssert(!header->permanent(), "this kind of container shalln't be freed");
|
||||
void FreeContainer(ContainerHeader* container) {
|
||||
RuntimeAssert(!container->permanent(), "this kind of container shalln't be freed");
|
||||
auto state = memoryState;
|
||||
|
||||
CONTAINER_FREE_EVENT(state, header)
|
||||
CONTAINER_FREE_EVENT(state, container)
|
||||
|
||||
if (isAggregatingFrozenContainer(header)) {
|
||||
FreeAggregatingFrozenContainer(header);
|
||||
if (isAggregatingFrozenContainer(container)) {
|
||||
FreeAggregatingFrozenContainer(container);
|
||||
return;
|
||||
} else {
|
||||
runDeallocationHooks(container);
|
||||
}
|
||||
|
||||
// Now let's clean all object's fields in this container.
|
||||
traverseContainerObjectFields(header, [](ObjHeader** location) {
|
||||
traverseContainerObjectFields(container, [](ObjHeader** location) {
|
||||
UpdateRef(location, nullptr);
|
||||
});
|
||||
|
||||
// And release underlying memory.
|
||||
if (!isFreeable(header)) {
|
||||
runDeallocationHooks(header);
|
||||
} else {
|
||||
header->setColor(CONTAINER_TAG_GC_BLACK);
|
||||
if (!header->buffered())
|
||||
scheduleDestroyContainer(state, header, false);
|
||||
if (isFreeable(container)) {
|
||||
container->setColor(CONTAINER_TAG_GC_BLACK);
|
||||
if (!container->buffered())
|
||||
scheduleDestroyContainer(state, container, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1232,7 +1233,8 @@ void* GetReservedObjectTail(ObjHeader* obj) {
|
||||
void SetRef(ObjHeader** location, const ObjHeader* object) {
|
||||
MEMORY_LOG("SetRef *%p: %p\n", location, object)
|
||||
*const_cast<const ObjHeader**>(location) = object;
|
||||
AddRef(object);
|
||||
if (object != nullptr)
|
||||
AddRef(object);
|
||||
}
|
||||
|
||||
ObjHeader** GetReturnSlotIfArena(ObjHeader** returnSlot, ObjHeader** localSlot) {
|
||||
@@ -1248,16 +1250,6 @@ ObjHeader** GetParamSlotIfArena(ObjHeader* param, ObjHeader** localSlot) {
|
||||
return reinterpret_cast<ObjHeader**>(reinterpret_cast<uintptr_t>(&chunk->arena) | ARENA_BIT);
|
||||
}
|
||||
|
||||
void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) {
|
||||
if (isArenaSlot(returnSlot)) {
|
||||
// Not a subject of reference counting.
|
||||
if (object == nullptr || !isRefCounted(object)) return;
|
||||
auto arena = initedArena(asArenaSlot(returnSlot));
|
||||
returnSlot = arena->getSlot();
|
||||
}
|
||||
UpdateRef(returnSlot, object);
|
||||
}
|
||||
|
||||
void UpdateRef(ObjHeader** location, const ObjHeader* object) {
|
||||
RuntimeAssert(!isArenaSlot(location), "must not be a slot");
|
||||
ObjHeader* old = *location;
|
||||
@@ -1273,6 +1265,35 @@ void UpdateRef(ObjHeader** location, const ObjHeader* object) {
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) {
|
||||
if (isArenaSlot(returnSlot)) {
|
||||
// Not a subject of reference counting.
|
||||
if (object == nullptr || !isRefCounted(object)) return;
|
||||
auto arena = initedArena(asArenaSlot(returnSlot));
|
||||
returnSlot = arena->getSlot();
|
||||
}
|
||||
UpdateRef(returnSlot, object);
|
||||
}
|
||||
|
||||
void UpdateRefIfNull(ObjHeader** location, const ObjHeader* object) {
|
||||
if (object != nullptr) {
|
||||
#if KONAN_NO_THREADS
|
||||
ObjHeader* old = *location;
|
||||
if (old == nullptr) {
|
||||
AddRef(object);
|
||||
*const_cast<const ObjHeader**>(location) = object;
|
||||
}
|
||||
#else
|
||||
AddRef(object);
|
||||
auto old = __sync_val_compare_and_swap(location, nullptr, const_cast<ObjHeader*>(object));
|
||||
if (old != nullptr) {
|
||||
// Failed to store, was not null.
|
||||
ReleaseRef(object);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void EnterFrame(ObjHeader** start, int parameters, int count) {
|
||||
MEMORY_LOG("EnterFrame %p .. %p\n", start, start + count + parameters)
|
||||
}
|
||||
|
||||
@@ -242,6 +242,8 @@ struct ArrayHeader {
|
||||
struct MetaObjHeader {
|
||||
// Pointer to the type info. Must be first, to match ArrayHeader and ObjHeader layout.
|
||||
const TypeInfo* typeInfo_;
|
||||
// Strong reference to counter object.
|
||||
ObjHeader* counter_;
|
||||
};
|
||||
|
||||
inline uint32_t ArrayDataSizeBytes(const ArrayHeader* obj) {
|
||||
@@ -394,6 +396,10 @@ bool HasReservedObjectTail(ObjHeader* obj) RUNTIME_NOTHROW;
|
||||
// Returns the pointer to the reserved space, `HasReservedObjectTail(obj)` must be true.
|
||||
void* GetReservedObjectTail(ObjHeader* obj) RUNTIME_NOTHROW;
|
||||
|
||||
// Weak reference operations.
|
||||
// Atomically clears counter object reference.
|
||||
void WeakReferenceCounterClear(ObjHeader* counter);
|
||||
|
||||
//
|
||||
// Object reference management.
|
||||
//
|
||||
@@ -420,6 +426,8 @@ void* GetReservedObjectTail(ObjHeader* obj) RUNTIME_NOTHROW;
|
||||
void SetRef(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTHROW;
|
||||
// Updates location.
|
||||
void UpdateRef(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTHROW;
|
||||
// Updates location if it is null, atomically.
|
||||
void UpdateRefIfNull(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTHROW;
|
||||
// Updates reference in return slot.
|
||||
void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) RUNTIME_NOTHROW;
|
||||
// Optimization: release all references in range.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 "Memory.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO: an ugly hack with fixed offsets.
|
||||
constexpr int referredOffset = 0;
|
||||
constexpr int lockOffset = sizeof(void*);
|
||||
|
||||
#if !KONAN_NO_THREADS
|
||||
|
||||
inline void lock(int32_t* address) {
|
||||
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*);
|
||||
|
||||
// See Weak.kt for implementation details.
|
||||
// Retrieve link on the counter object.
|
||||
OBJ_GETTER(Konan_getWeakReferenceCounter, ObjHeader* referred) {
|
||||
MetaObjHeader* meta = referred->meta_object();
|
||||
if (meta->counter_ == nullptr) {
|
||||
ObjHolder counterHolder;
|
||||
// Cast unneeded, just to emphasize we store an object reference as void*.
|
||||
ObjHeader* counter = makeWeakReferenceCounter(reinterpret_cast<void*>(referred), counterHolder.slot());
|
||||
UpdateRefIfNull(&meta->counter_, counter);
|
||||
}
|
||||
RETURN_OBJ(meta->counter_);
|
||||
}
|
||||
|
||||
// Materialize a weak reference to either null or the real reference.
|
||||
OBJ_GETTER(Konan_WeakReferenceCounter_get, ObjHeader* counter) {
|
||||
ObjHeader** referredAddress = reinterpret_cast<ObjHeader**>(reinterpret_cast<char*>(counter + 1) + referredOffset);
|
||||
#if KONAN_NO_THREADS
|
||||
RETURN_OBJ(*referredAddress);
|
||||
#else
|
||||
int32_t* lockAddress = reinterpret_cast<int32_t*>(reinterpret_cast<char*>(counter + 1) + lockOffset);
|
||||
// Spinlock.
|
||||
lock(lockAddress);
|
||||
ObjHolder holder(*referredAddress);
|
||||
unlock(lockAddress);
|
||||
RETURN_OBJ(holder.obj());
|
||||
#endif
|
||||
}
|
||||
|
||||
void WeakReferenceCounterClear(ObjHeader* counter) {
|
||||
ObjHeader** referredAddress = reinterpret_cast<ObjHeader**>(reinterpret_cast<char*>(counter + 1) + referredOffset);
|
||||
// Note, that we don't do UpdateRef here, as reference is weak.
|
||||
#if KONAN_NO_THREADS
|
||||
*referredAddress = nullptr;
|
||||
#else
|
||||
int32_t* lockAddress = reinterpret_cast<int32_t*>(reinterpret_cast<char*>(counter + 1) + lockOffset);
|
||||
// Spinlock.
|
||||
lock(lockAddress);
|
||||
*referredAddress = nullptr;
|
||||
unlock(lockAddress);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package konan.ref
|
||||
|
||||
/**
|
||||
* Class WeakReference encapsulates weak reference to an object, which could be used to either
|
||||
* retrieve a strong reference to an object, or return null, if object was already destoyed by
|
||||
* the memory manager.
|
||||
*/
|
||||
class WeakReference<T> {
|
||||
/**
|
||||
* Creates a weak reference object pointing to an object. Weak reference doesn't prevent
|
||||
* removing object, and is nullified once object is collected.
|
||||
*/
|
||||
constructor(referred: T) {
|
||||
if (referred == null) throw Error("Weak reference to null?")
|
||||
pointer = getWeakReferenceCounter(referred)
|
||||
}
|
||||
|
||||
/**
|
||||
* Backing store for the object pointer, inaccessible directly.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal var pointer: WeakReferenceCounter?
|
||||
|
||||
/**
|
||||
* Clears reference to an object.
|
||||
*/
|
||||
public fun clear() {
|
||||
pointer = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either reference to an object or null, if it was collected.
|
||||
*/
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun <reified T> WeakReference<T>.get() = pointer?.get() as T?
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package konan.ref
|
||||
|
||||
import kotlinx.cinterop.COpaquePointer
|
||||
import konan.internal.ExportForCppRuntime
|
||||
|
||||
/**
|
||||
* Theory of operations:
|
||||
*
|
||||
* Weak references in Kotlin/Native are implemented in the following way. Whenever weak reference to an
|
||||
* object is created, we atomically modify type info pointer in the object to point into a metaobject.
|
||||
* This metaobject contains a strong reference to the counter object (instance of WeakReferenceCounter class).
|
||||
* Every other weak reference contains a strong reference to the counter object.
|
||||
*
|
||||
* [weak1] [weak2]
|
||||
* \ /
|
||||
* V V
|
||||
* .......[Counter] <----
|
||||
* . |
|
||||
* . |
|
||||
* ->[Object] -> [Meta]-
|
||||
*
|
||||
* References from weak reference objects to the counter and from the metaobject to the counter are strong,
|
||||
* and from the counter to the object is nullably weak. So whenever an object dies, if it has a metaobject,
|
||||
* it is traversed to find a counter object, and atomically nullify reference to the object. Afterward, all attempts
|
||||
* to get the object would yield null.
|
||||
*/
|
||||
|
||||
// Clear holding the counter object, which refers to the actual object.
|
||||
internal class WeakReferenceCounter(var referred: COpaquePointer?) {
|
||||
// Spinlock, potentially taken when materializing or removing 'referred' object.
|
||||
var lock: Int = 0
|
||||
|
||||
@SymbolName("Konan_WeakReferenceCounter_get")
|
||||
internal external fun get(): Any?
|
||||
}
|
||||
|
||||
// Get a counter from non-null object.
|
||||
@SymbolName("Konan_getWeakReferenceCounter")
|
||||
external internal fun getWeakReferenceCounter(referent: Any): WeakReferenceCounter
|
||||
|
||||
// Create a counter object.
|
||||
@ExportForCppRuntime
|
||||
internal fun makeWeakReferenceCounter(referred: COpaquePointer) = WeakReferenceCounter(referred)
|
||||
|
||||
Reference in New Issue
Block a user