diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 885b7e7d19e..9ac188e0060 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -395,8 +395,6 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { private fun importRtFunction(name: String) = importFunction(name, runtime.llvmModule) - var globalInitIndex:Int = 0 - val allocInstanceFunction = importRtFunction("AllocInstance") val allocArrayFunction = importRtFunction("AllocArrayInstance") val initInstanceFunction = importRtFunction("InitInstance") @@ -412,6 +410,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val throwExceptionFunction = importRtFunction("ThrowException") val appendToInitalizersTail = importRtFunction("AppendToInitializersTail") val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded") + val mutationCheck = importRtFunction("MutationCheck") val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") } val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 4708624a179..14735df40c9 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -477,8 +477,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map, resultLifetime: Lifetime): LLVMValueRef { val result = call(function, args, resultLifetime) - if (descriptor.returnType?.isNothing() == true) { + if (descriptor.returnType.isNothing()) { functionGenerationContext.unreachable() } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt index f9e794589b2..c792655b110 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt @@ -111,9 +111,6 @@ private fun StaticData.getArrayListClass(): ClassDescriptor { internal fun StaticData.createArrayList(elementType: TypeProjection, array: ConstPointer, length: Int): ConstPointer { val arrayListClass = context.ir.symbols.arrayList.owner - // type is ArrayList: - val type = arrayListClass.defaultType.replace(listOf(elementType)) - val arrayListFqName = arrayListClass.fqNameSafe val arrayListFields = mapOf( "$arrayListFqName.array" to array, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index 9cf73f17075..5d221e3e06d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -597,7 +597,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag symbolTable.mapClass(owner), vtableIndex, arguments, - symbolTable.mapType(callee.returnType!!), + symbolTable.mapType(callee.returnType), value ) } @@ -606,7 +606,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag DataFlowIR.Node.StaticCall( symbolTable.mapFunction(actualCallee), arguments, - symbolTable.mapType(actualCallee.returnType!!), + symbolTable.mapType(actualCallee.returnType), actualCallee.dispatchReceiverParameter?.let { symbolTable.mapType(it.type) }, value ) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index 74862c87041..e26ef5942ba 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -277,7 +277,8 @@ private fun IrElement.innerEndOffset(descriptor: DeclarationDescriptorWithSource descriptor.endOffset ?: this.endOffset inline fun stub(name: String): T { - return Proxy.newProxyInstance(T::class.java.classLoader, arrayOf(T::class.java)) { proxy, method, methodArgs -> + return Proxy.newProxyInstance(T::class.java.classLoader, arrayOf(T::class.java)) { + _ /* proxy */, method, _ /* methodArgs */ -> if (method.name == "toString" && method.parameterCount == 0) { "${T::class.simpleName} stub for $name" } else { diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index a255c48903a..2cdb823b354 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -610,6 +610,22 @@ task worker8(type: RunKonanTest) { source = "runtime/workers/worker8.kt" } +task freeze0(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // No workers on WASM. + goldValue = "frozen bit is true\n" + + "Worker: SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\n" + + "Main: SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\n" + + "OK\n" + source = "runtime/workers/freeze0.kt" +} + +task freeze1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // No exceptions on WASM. + goldValue = "OK, cannot freeze cyclic\n"+ + "OK, cannot mutate frozen\n" + source = "runtime/workers/freeze1.kt" +} + task superFunCall(type: RunKonanTest) { goldValue = "\n\n" source = "codegen/basics/superFunCall.kt" diff --git a/backend.native/tests/runtime/workers/freeze0.kt b/backend.native/tests/runtime/workers/freeze0.kt new file mode 100644 index 00000000000..2360525dcf9 --- /dev/null +++ b/backend.native/tests/runtime/workers/freeze0.kt @@ -0,0 +1,43 @@ +/* + * 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 runtime.workers.freeze0 + +import kotlin.test.* + +import konan.worker.* + +data class SharedDataMember(val double: Double) + +data class SharedData(val string: String, val int: Int, val member: SharedDataMember) + +@Test fun runTest() { + val worker = startWorker() + // Create immutable shared data. + val immutable = SharedData("Hello", 10, SharedDataMember(0.1)).freeze() + println("frozen bit is ${immutable.isFrozen}") + + val future = worker.schedule(TransferMode.CHECKED, { immutable } ) { + input -> + println("Worker: $input") + input + } + future.consume { + result -> println("Main: $result") + } + worker.requestTermination().consume { _ -> } + println("OK") +} \ No newline at end of file diff --git a/backend.native/tests/runtime/workers/freeze1.kt b/backend.native/tests/runtime/workers/freeze1.kt new file mode 100644 index 00000000000..fad2cfbb146 --- /dev/null +++ b/backend.native/tests/runtime/workers/freeze1.kt @@ -0,0 +1,61 @@ +/* + * 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 runtime.workers.freeze1 + +import kotlin.test.* + +import konan.worker.* + +data class Node(var previous: Node?, var data: Int) + +fun makeCycle(count: Int): Node { + val first = Node(null, 0) + var current = first + for (index in 1 .. count - 1) { + current = Node(current, index) + } + first.previous = current + return first +} + +data class Node2(var leaf1: Node2?, var leaf2: Node2?) + +fun makeDiamond(): Node2 { + val bottom = Node2(null, null) + val mid1prime = Node2(bottom, null) + val mid1 = Node2(mid1prime, null) + val mid2 = Node2(bottom, null) + return Node2(mid1, mid2) +} + +@Test fun runTest() { + try { + makeCycle(10).freeze() + } catch (e: FreezingException) { + println("OK, cannot freeze cyclic") + } + + // Must be able to freeze diamond shaped graph. + val diamond = makeDiamond().freeze() + + val immutable = Node(null, 4).freeze() + try { + immutable.data = 42 + } catch (e: InvalidMutabilityException) { + println("OK, cannot mutate frozen") + } +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index a66a6f17669..50772e0bd86 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -19,10 +19,6 @@ #include // for offsetof -#ifndef KONAN_NO_THREADS -#include -#endif - #include "Alloc.h" #include "Assert.h" #include "Exceptions.h" @@ -31,7 +27,6 @@ #include "Natives.h" #include "Porting.h" - // If garbage collection algorithm for cyclic garbage to be used. // We are using the Bacon's algorithm for GC, see // http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf. @@ -43,6 +38,7 @@ // Auto-adjust GC thresholds. #define GC_ERGONOMICS 1 +// TODO: ensure it it read-only. ContainerHeader ObjHeader::theStaticObjectsContainer = { CONTAINER_TAG_PERMANENT | CONTAINER_TAG_INCREMENT }; @@ -419,9 +415,13 @@ inline bool isRefCounted(KConstRef object) { } // namespace extern "C" { + void objc_release(void* ptr); void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj); -} +RUNTIME_NORETURN void ThrowFreezingException(); +RUNTIME_NORETURN void ThrowInvalidMutabilityException(); + +} // extern "C" inline void runDeallocationHooks(ObjHeader* obj) { #if KONAN_OBJC_INTEROP @@ -499,12 +499,14 @@ inline void scheduleDestroyContainer( #if !USE_GC +template inline void IncrementRC(ContainerHeader* container) { - container->incRefCount(); + container->incRefCount(); } +template inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) { - if (container->decRefCount() == 0) { + if (container->decRefCount() == 0) { FreeContainer(container); } } @@ -515,15 +517,19 @@ inline uint32_t freeableSize(MemoryState* state) { return state->toFree->size(); } +template inline void IncrementRC(ContainerHeader* container) { - container->incRefCount(); + container->incRefCount(); container->setColor(CONTAINER_TAG_GC_BLACK); } +template inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) { - if (container->decRefCount() == 0) { + if (container->decRefCount() == 0) { FreeContainer(container); - } else if (useCycleCollector) { // Possible root. + } else if (!Atomic && useCycleCollector) { // Possible root. + // Do not use cycle collector for frozen objects, as we already detected possible cycles during + // freezing. if (container->color() != CONTAINER_TAG_GC_PURPLE) { container->setColor(CONTAINER_TAG_GC_PURPLE); if (!container->buffered()) { @@ -618,8 +624,9 @@ void MarkGray(ContainerHeader* container) { traverseContainerReferredObjects(container, [](ObjHeader* ref) { auto childContainer = ref->container(); RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered"); - if (!childContainer->permanent()) { - childContainer->decRefCount(); + + if (!childContainer->permanentOrFrozen()) { + childContainer->decRefCount(); MarkGray(childContainer); } }); @@ -637,8 +644,8 @@ void ScanBlack(ContainerHeader* container) { traverseContainerReferredObjects(container, [](ObjHeader* ref) { auto childContainer = ref->container(); RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered"); - if (!childContainer->permanent()) { - childContainer->incRefCount(); + if (!childContainer->permanentOrFrozen()) { + childContainer->incRefCount(); if (useColor) { if (childContainer->color() != CONTAINER_TAG_GC_BLACK) ScanBlack(childContainer); @@ -701,7 +708,7 @@ void Scan(ContainerHeader* container) { traverseContainerReferredObjects(container, [](ObjHeader* ref) { auto childContainer = ref->container(); RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered"); - if (!childContainer->permanent()) { + if (!childContainer->permanentOrFrozen()) { Scan(childContainer); } }); @@ -730,7 +737,10 @@ inline void AddRef(ContainerHeader* header) { case CONTAINER_TAG_PERMANENT: break; case CONTAINER_TAG_NORMAL: - IncrementRC(header); + IncrementRC(header); + break; + case CONTAINER_TAG_FROZEN: + IncrementRC(header); break; default: RuntimeAssert(false, "unknown container type"); @@ -746,7 +756,10 @@ inline void Release(ContainerHeader* header, bool useCycleCollector) { case CONTAINER_TAG_STACK: break; case CONTAINER_TAG_NORMAL: - DecrementRC(header, useCycleCollector); + DecrementRC(header, useCycleCollector); + break; + case CONTAINER_TAG_FROZEN: + DecrementRC(header, useCycleCollector); break; default: RuntimeAssert(false, "unknown container type"); @@ -1127,7 +1140,7 @@ void UpdateRef(ObjHeader** location, const ObjHeader* object) { AddRef(object); } *const_cast(location) = object; - if (old > reinterpret_cast(1)) { + if (old != nullptr) { ReleaseRef(old); } } @@ -1300,7 +1313,7 @@ bool hasExternalRefs(ContainerHeader* container, ContainerHeaderSet* visited) { bool result = container->refCount() != 0; traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) { auto child = ref->container(); - if (!child->permanent() && (visited->find(child) == visited->end())) { + if (!child->permanentOrFrozen() && (visited->find(child) == visited->end())) { result |= hasExternalRefs(child, visited); } }); @@ -1314,18 +1327,26 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) { auto state = memoryState; auto container = root->container(); + if (container->frozen()) + // We assume, that frozen objects can be safely passed and are already removed + // GC candidate list. + return true; + ContainerHeaderSet visited; if (!checked) { hasExternalRefs(container, &visited); } else { - container->decRefCount(); - MarkGray(container); - auto bad = hasExternalRefs(container, &visited); - ScanBlack(container); - container->incRefCount(); - if (bad) return false; + if (!container->permanentOrFrozen()) { + container->decRefCount(); + MarkGray(container); + auto bad = hasExternalRefs(container, &visited); + ScanBlack(container); + container->incRefCount(); + if (bad) return false; + } } + // TODO: not very effecient traversal. for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) { auto container = *it; if (visited.find(container) != visited.end()) { @@ -1339,4 +1360,115 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) { return true; } +/** + * Do DFS cycle detection with three colors: + * - 'marked' bit as BLACK marker (object and its descendants processed) + * - 'seen' bit as GRAY marker (object is being processed) + * - not 'marked' and not 'seen' as WHITE marker (object is unprocessed) + * When we see GREY during DFS, it means we see cycle. + */ +void depthFirstTraversal(ContainerHeader* container, bool* hasCycles) { + if (*hasCycles) return; + // Mark GRAY. + container->setSeen(); + traverseContainerObjectFields(container, [&hasCycles](ObjHeader** location) { + ObjHeader* obj = *location; + if (obj != nullptr) { + ContainerHeader* objContainer = obj->container(); + if (!objContainer->permanentOrFrozen()) { + // Marked GREY, there's cycle. + if (objContainer->seen()) *hasCycles = true; + + // Go deeper if WHITE. + if (!objContainer->seen() && !objContainer->marked()) { + depthFirstTraversal(objContainer, hasCycles); + } + } + } + }); + // Mark BLACK. + container->resetSeen(); + container->mark(); +} + +/** + * Theory of operations. + * + * Kotlin/Native supports object graph freezing, allowing to make certain subgraph immutable and thus + * suitable for safe sharing amongs multiple concurrent executors. This operation recursively operates + * on all objects reachable from the given object, and marks them as frozen. In frozen state object's + * fields cannot be modified, and so, lifetime of frozen objects correlates. Practically, it means + * that lifetimes of all strongly connected components are fully controlled by incoming reference + * counters, and so if we place all members of strongly connected component to the single container + * it could be correctly released by just atomic decrement on reference counter, without additional + * cycle collector run. + * So during subgraph freezing operation, we perform the following steps: + * - run Kosoraju-Sharir algorithm to find strongly connected components + * - put all objects in each strongly connected component into an artificial container + * (we assume that they all were in single element containers initially), single-object + * components remain in the same container + * - artifical container sums up outer reference counters of all its objects (i.e. + * incoming references from the same strongly connected component are not counted) + * - mark all object's headers as frozen + * + * Further reference counting on frozen objects is performed with the atomic operations, and so frozen + * references could be passed accross multiple threads. + */ +void FreezeSubgraph(ObjHeader* root) { + // TODO: for now, we just check that passed object graph has no cycles, and throw an exception, + // if it does. Next version will run Kosoraju-Sharir if cycles are found. + ContainerHeader* rootContainer = root->container(); + if (rootContainer->permanentOrFrozen()) return; + + // Do DFS cycle detection. + bool hasCycles = false; + depthFirstTraversal(rootContainer, &hasCycles); + + // Now unmark all marked objects, and freeze them, if no cycles detected. + KStdDeque stack; + stack.push_back(rootContainer); + while (!stack.empty()) { + ContainerHeader* current = stack.front(); + stack.pop_front(); + current->unMark(); + current->resetSeen(); + + if (!hasCycles) { + current->resetBuffered(); + current->setColor(CONTAINER_TAG_GC_BLACK); + // Note, that once object is frozen, it could be concurrently accessed, so + // color and similar attributes shall not be used. + current->freeze(); + } + traverseContainerObjectFields(current, [&hasCycles, &stack](ObjHeader** location) { + ObjHeader* obj = *location; + if (obj != nullptr) { + ContainerHeader* objContainer = obj->container(); + if (!objContainer->permanentOrFrozen() && objContainer->marked()) + stack.push_back(objContainer); + } + }); + } + + // Now remove frozen objects from the toFree list. + // TODO: optimize it by keeping ignored (i.e. freshly frozen) objects in the set, + // and use it when analyzing toFree during collection. + auto state = memoryState; + for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) { + auto container = *it; + if (container->frozen()) { + *it = reinterpret_cast(reinterpret_cast(container) | 1); + } + } + + // For now, just throw an exception here. + if (hasCycles) ThrowFreezingException(); +} + +// This function is called from field mutators to check if object's header is frozen. +// If object is frozen, an exception is thrown. +void MutationCheck(ObjHeader* obj) { + if (obj->container()->frozen()) ThrowInvalidMutabilityException(); +} + } // extern "C" diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index d552db9520c..95de79cbd0e 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. + * 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. @@ -21,7 +21,6 @@ #include "Common.h" #include "TypeInfo.h" -// Must fit in two bits. typedef enum { // Those bit masks are applied to refCount_ field. // Container is normal thread local container. @@ -43,17 +42,19 @@ typedef enum { // Those bit masks are applied to objectCount_ field. // Shift to get actual object count. - CONTAINER_TAG_GC_SHIFT = 4, + CONTAINER_TAG_GC_SHIFT = 5, CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT, - // Color of a container. - CONTAINER_TAG_GC_COLOR_MASK = ((CONTAINER_TAG_GC_INCREMENT >> 2) - 1), + // Color mask of a container. + CONTAINER_TAG_GC_COLOR_MASK = (1 << 2) - 1, // Colors. CONTAINER_TAG_GC_BLACK = 0, CONTAINER_TAG_GC_GRAY = 1, CONTAINER_TAG_GC_WHITE = 2, CONTAINER_TAG_GC_PURPLE = 3, - CONTAINER_TAG_GC_MARKED = 4, - CONTAINER_TAG_GC_BUFFERED = 8 + // Individual state bits used during GC and freezing. + CONTAINER_TAG_GC_MARKED = 1 << 2, + CONTAINER_TAG_GC_BUFFERED = 1 << 3, + CONTAINER_TAG_GC_SEEN = 1 << 4 } ContainerTag; typedef uint32_t container_offset_t; @@ -71,17 +72,47 @@ struct ContainerHeader { return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT; } + inline bool frozen() const { + return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_FROZEN; + } + + inline void freeze() { + refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_FROZEN; + } + + inline bool permanentOrFrozen() const { + return tag() == CONTAINER_TAG_PERMANENT || tag() == CONTAINER_TAG_FROZEN; + } + + inline bool stack() const { + return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK; + } + inline unsigned refCount() const { return refCount_ >> CONTAINER_TAG_SHIFT; } + template inline void incRefCount() { +#ifdef KONAN_NO_THREADS refCount_ += CONTAINER_TAG_INCREMENT; +#else + if (Atomic) + __sync_add_and_fetch(&refCount_, CONTAINER_TAG_INCREMENT); + else + refCount_ += CONTAINER_TAG_INCREMENT; +#endif } + template inline int decRefCount() { - refCount_ -= CONTAINER_TAG_INCREMENT; - return refCount_ >> CONTAINER_TAG_SHIFT; +#ifdef KONAN_NO_THREADS + int value = refCount_ -= CONTAINER_TAG_INCREMENT; +#else + int value = Atomic ? + __sync_sub_and_fetch(&refCount_, CONTAINER_TAG_INCREMENT) : refCount_ -= CONTAINER_TAG_INCREMENT; +#endif + return value >> CONTAINER_TAG_SHIFT; } inline unsigned tag() const { @@ -131,6 +162,18 @@ struct ContainerHeader { inline void unMark() { objectCount_ &= ~CONTAINER_TAG_GC_MARKED; } + + inline bool seen() const { + return (objectCount_ & CONTAINER_TAG_GC_SEEN) != 0; + } + + inline void setSeen() { + objectCount_ |= CONTAINER_TAG_GC_SEEN; + } + + inline void resetSeen() { + objectCount_ &= ~CONTAINER_TAG_GC_SEEN; + } }; struct ArrayHeader; @@ -413,7 +456,10 @@ void DisposeStablePointer(void* pointer) RUNTIME_NOTHROW; OBJ_GETTER(DerefStablePointer, void*) RUNTIME_NOTHROW; // Move stable pointer ownership. OBJ_GETTER(AdoptStablePointer, void*) RUNTIME_NOTHROW; - +// Check mutability state. +void MutationCheck(ObjHeader* obj); +// Freeze object subgraph. +void FreezeSubgraph(ObjHeader* root); #ifdef __cplusplus } #endif diff --git a/runtime/src/main/cpp/Worker.cpp b/runtime/src/main/cpp/Worker.cpp index cdf1caf2f94..1e0a58de8aa 100644 --- a/runtime/src/main/cpp/Worker.cpp +++ b/runtime/src/main/cpp/Worker.cpp @@ -38,8 +38,8 @@ extern "C" { -void ThrowWorkerInvalidState(); -void ThrowWorkerUnsupported(); +RUNTIME_NORETURN void ThrowWorkerInvalidState(); +RUNTIME_NORETURN void ThrowWorkerUnsupported(); OBJ_GETTER(WorkerLaunchpad, KRef); } // extern "C" @@ -571,4 +571,13 @@ KNativePtr Kotlin_Worker_detachObjectGraphInternal(KInt transferMode, KRef produ return detachObjectGraphInternal(transferMode, producer); } +void Kotlin_Worker_freezeInternal(KRef object) { + if (object != nullptr) + FreezeSubgraph(object); +} + +KBoolean Kotlin_Worker_isFrozenInternal(KRef object) { + return object == nullptr || object->container()->frozen(); +} + } // extern "C" diff --git a/runtime/src/main/kotlin/konan/worker/Freezing.kt b/runtime/src/main/kotlin/konan/worker/Freezing.kt new file mode 100644 index 00000000000..40e14cd90d4 --- /dev/null +++ b/runtime/src/main/kotlin/konan/worker/Freezing.kt @@ -0,0 +1,53 @@ +/* + * 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.worker + +import konan.internal.ExportForCppRuntime + +/** + * Exception thrown whenever freezing is not possible. + */ +public class FreezingException() : RuntimeException() + +/** + * Exception thrown whenever we attempt to mutate frozen objects. + */ +public class InvalidMutabilityException() : RuntimeException() + +/** + * Freezes object subgraph reachable from this object. Frozen objects can be freely + * shared between threads/workers. + */ +fun T.freeze(): T { + freezeInternal(this) + return this +} + +val Any?.isFrozen + get() = isFrozenInternal(this) + +@SymbolName("Kotlin_Worker_freezeInternal") +internal external fun freezeInternal(it: Any?) + +@SymbolName("Kotlin_Worker_isFrozenInternal") +internal external fun isFrozenInternal(it: Any?): Boolean + +@ExportForCppRuntime +internal fun ThrowFreezingException(): Nothing = throw FreezingException() + +@ExportForCppRuntime +internal fun ThrowInvalidMutabilityException(): Nothing = throw InvalidMutabilityException()