Object freezing API (#1390)
This commit is contained in:
+1
-2
@@ -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") }
|
||||
|
||||
+10
-4
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -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,
|
||||
|
||||
+2
-2
@@ -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
|
||||
)
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user