Object freezing API (#1390)

This commit is contained in:
Nikolay Igotti
2018-03-14 11:51:48 +03:00
committed by GitHub
parent 610c9b5278
commit a6fa19a7fd
12 changed files with 413 additions and 50 deletions
@@ -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") }
@@ -477,8 +477,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
override fun visitConstructor(declaration: IrConstructor) {
context.log{"visitConstructor : ${ir2string(declaration)}"}
if (declaration.constructedClass.isIntrinsic) {
// Do not generate any ctors for intrinsic classes.
if (declaration.descriptor.containingDeclaration.defaultType.isValueType()) {
// Do not generate any ctors for value types.
return
}
@@ -692,6 +692,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------//
override fun visitProperty(declaration: IrProperty) {
val container = declaration.descriptor.containingDeclaration
// For value types with real backing field there's no point to generate an accessor.
if (container is ClassDescriptor && container.defaultType.isValueType() && declaration.backingField != null)
return
declaration.getter?.acceptVoid(this)
declaration.setter?.acceptVoid(this)
declaration.backingField?.acceptVoid(this)
@@ -1330,9 +1334,11 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
private fun evaluateSetField(value: IrSetField): LLVMValueRef {
context.log{"evaluateSetField : ${ir2string(value)}"}
val valueToAssign = evaluateExpression(value.value)
if (value.descriptor.dispatchReceiverParameter != null) {
val thisPtr = evaluateExpression(value.receiver!!)
functionGenerationContext.call(context.llvm.mutationCheck,
listOf(functionGenerationContext.bitcast(codegen.kObjHeaderPtr, thisPtr)),
Lifetime.IRRELEVANT, ExceptionHandler.Caller)
functionGenerationContext.storeAny(valueToAssign, fieldPtrOfClass(thisPtr, value.symbol.owner))
}
else {
@@ -2424,7 +2430,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
private fun call(descriptor: FunctionDescriptor, function: LLVMValueRef, args: List<LLVMValueRef>,
resultLifetime: Lifetime): LLVMValueRef {
val result = call(function, args, resultLifetime)
if (descriptor.returnType?.isNothing() == true) {
if (descriptor.returnType.isNothing()) {
functionGenerationContext.unreachable()
}
@@ -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<elementType>:
val type = arrayListClass.defaultType.replace(listOf(elementType))
val arrayListFqName = arrayListClass.fqNameSafe
val arrayListFields = mapOf(
"$arrayListFqName.array" to array,
@@ -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
)
@@ -277,7 +277,8 @@ private fun IrElement.innerEndOffset(descriptor: DeclarationDescriptorWithSource
descriptor.endOffset ?: this.endOffset
inline fun <reified T> 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 {
+16
View File
@@ -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 = "<fun:C><fun:C1>\n<fun:C><fun:C3>\n"
source = "codegen/basics/superFunCall.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")
}
@@ -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")
}
}
+158 -26
View File
@@ -19,10 +19,6 @@
#include <cstddef> // for offsetof
#ifndef KONAN_NO_THREADS
#include <pthread.h>
#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 <bool Atomic>
inline void IncrementRC(ContainerHeader* container) {
container->incRefCount();
container->incRefCount<Atomic>();
}
template <bool Atomic>
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
if (container->decRefCount() == 0) {
if (container->decRefCount<Atomic>() == 0) {
FreeContainer(container);
}
}
@@ -515,15 +517,19 @@ inline uint32_t freeableSize(MemoryState* state) {
return state->toFree->size();
}
template <bool Atomic>
inline void IncrementRC(ContainerHeader* container) {
container->incRefCount();
container->incRefCount<Atomic>();
container->setColor(CONTAINER_TAG_GC_BLACK);
}
template <bool Atomic>
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
if (container->decRefCount() == 0) {
if (container->decRefCount<Atomic>() == 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<false>();
MarkGray<useColor>(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<false>();
if (useColor) {
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
ScanBlack<useColor>(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<false>(header);
break;
case CONTAINER_TAG_FROZEN:
IncrementRC<true>(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<false>(header, useCycleCollector);
break;
case CONTAINER_TAG_FROZEN:
DecrementRC<true>(header, useCycleCollector);
break;
default:
RuntimeAssert(false, "unknown container type");
@@ -1127,7 +1140,7 @@ void UpdateRef(ObjHeader** location, const ObjHeader* object) {
AddRef(object);
}
*const_cast<const ObjHeader**>(location) = object;
if (old > reinterpret_cast<ObjHeader*>(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<false>(container);
auto bad = hasExternalRefs(container, &visited);
ScanBlack<false>(container);
container->incRefCount();
if (bad) return false;
if (!container->permanentOrFrozen()) {
container->decRefCount<false>();
MarkGray<false>(container);
auto bad = hasExternalRefs(container, &visited);
ScanBlack<false>(container);
container->incRefCount<false>();
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<ContainerHeader*> 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<ContainerHeader*>(reinterpret_cast<uintptr_t>(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"
+56 -10
View File
@@ -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 <bool Atomic>
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 <bool Atomic>
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
+11 -2
View File
@@ -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"
@@ -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> 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()