diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 4fc9d4d1088..f910f011398 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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'] diff --git a/backend.native/tests/runtime/memory/weak0.kt b/backend.native/tests/runtime/memory/weak0.kt new file mode 100644 index 00000000000..011f661a305 --- /dev/null +++ b/backend.native/tests/runtime/memory/weak0.kt @@ -0,0 +1,35 @@ +package runtime.memory.weak0 + +import kotlin.test.* +import konan.ref.* + +data class Data(val s: String) + +fun localWeak(): WeakReference { + val x = Data("Hello") + val weak = WeakReference(x) + + println(weak.get()) + return weak +} + +fun multiWeak(): Array> { + 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") +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 3d914e8caeb..01bc0b3f2c7 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -464,7 +464,7 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) { namespace { template -void traverseContainerObjectFields(ContainerHeader* container, func process) { +inline void traverseContainerObjectFields(ContainerHeader* container, func process) { ObjHeader* obj = reinterpret_cast(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 -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(location)); + *const_cast(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(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(reinterpret_cast(&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(location) = object; + } +#else + AddRef(object); + auto old = __sync_val_compare_and_swap(location, nullptr, const_cast(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) } diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 5e84fe1b70c..390e8185edc 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -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. diff --git a/runtime/src/main/cpp/Weak.cpp b/runtime/src/main/cpp/Weak.cpp new file mode 100644 index 00000000000..43e04241156 --- /dev/null +++ b/runtime/src/main/cpp/Weak.cpp @@ -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(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(reinterpret_cast(counter + 1) + referredOffset); +#if KONAN_NO_THREADS + RETURN_OBJ(*referredAddress); +#else + int32_t* lockAddress = reinterpret_cast(reinterpret_cast(counter + 1) + lockOffset); + // Spinlock. + lock(lockAddress); + ObjHolder holder(*referredAddress); + unlock(lockAddress); + RETURN_OBJ(holder.obj()); +#endif +} + +void WeakReferenceCounterClear(ObjHeader* counter) { + ObjHeader** referredAddress = reinterpret_cast(reinterpret_cast(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(reinterpret_cast(counter + 1) + lockOffset); + // Spinlock. + lock(lockAddress); + *referredAddress = nullptr; + unlock(lockAddress); +#endif +} + +} // extern "C" diff --git a/runtime/src/main/kotlin/konan/ref/Weak.kt b/runtime/src/main/kotlin/konan/ref/Weak.kt new file mode 100644 index 00000000000..198dbe0b435 --- /dev/null +++ b/runtime/src/main/kotlin/konan/ref/Weak.kt @@ -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 { + /** + * 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 WeakReference.get() = pointer?.get() as T? diff --git a/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt b/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt new file mode 100644 index 00000000000..1d072e40268 --- /dev/null +++ b/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt @@ -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) +