Weak references. (#1459)

This commit is contained in:
Nikolay Igotti
2018-04-04 14:35:06 +03:00
committed by GitHub
parent bd506c527a
commit 21079ae644
7 changed files with 296 additions and 30 deletions
+5
View File
@@ -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")
}
+51 -30
View File
@@ -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)
}
+8
View File
@@ -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.
+85
View File
@@ -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"
+52
View File
@@ -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)