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)
|
private fun importRtFunction(name: String) = importFunction(name, runtime.llvmModule)
|
||||||
|
|
||||||
var globalInitIndex:Int = 0
|
|
||||||
|
|
||||||
val allocInstanceFunction = importRtFunction("AllocInstance")
|
val allocInstanceFunction = importRtFunction("AllocInstance")
|
||||||
val allocArrayFunction = importRtFunction("AllocArrayInstance")
|
val allocArrayFunction = importRtFunction("AllocArrayInstance")
|
||||||
val initInstanceFunction = importRtFunction("InitInstance")
|
val initInstanceFunction = importRtFunction("InitInstance")
|
||||||
@@ -412,6 +410,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
|||||||
val throwExceptionFunction = importRtFunction("ThrowException")
|
val throwExceptionFunction = importRtFunction("ThrowException")
|
||||||
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
||||||
val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded")
|
val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded")
|
||||||
|
val mutationCheck = importRtFunction("MutationCheck")
|
||||||
|
|
||||||
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
||||||
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
|
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) {
|
override fun visitConstructor(declaration: IrConstructor) {
|
||||||
context.log{"visitConstructor : ${ir2string(declaration)}"}
|
context.log{"visitConstructor : ${ir2string(declaration)}"}
|
||||||
if (declaration.constructedClass.isIntrinsic) {
|
if (declaration.descriptor.containingDeclaration.defaultType.isValueType()) {
|
||||||
// Do not generate any ctors for intrinsic classes.
|
// Do not generate any ctors for value types.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,6 +692,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
//-------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------//
|
||||||
|
|
||||||
override fun visitProperty(declaration: IrProperty) {
|
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.getter?.acceptVoid(this)
|
||||||
declaration.setter?.acceptVoid(this)
|
declaration.setter?.acceptVoid(this)
|
||||||
declaration.backingField?.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 {
|
private fun evaluateSetField(value: IrSetField): LLVMValueRef {
|
||||||
context.log{"evaluateSetField : ${ir2string(value)}"}
|
context.log{"evaluateSetField : ${ir2string(value)}"}
|
||||||
val valueToAssign = evaluateExpression(value.value)
|
val valueToAssign = evaluateExpression(value.value)
|
||||||
|
|
||||||
if (value.descriptor.dispatchReceiverParameter != null) {
|
if (value.descriptor.dispatchReceiverParameter != null) {
|
||||||
val thisPtr = evaluateExpression(value.receiver!!)
|
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))
|
functionGenerationContext.storeAny(valueToAssign, fieldPtrOfClass(thisPtr, value.symbol.owner))
|
||||||
}
|
}
|
||||||
else {
|
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>,
|
private fun call(descriptor: FunctionDescriptor, function: LLVMValueRef, args: List<LLVMValueRef>,
|
||||||
resultLifetime: Lifetime): LLVMValueRef {
|
resultLifetime: Lifetime): LLVMValueRef {
|
||||||
val result = call(function, args, resultLifetime)
|
val result = call(function, args, resultLifetime)
|
||||||
if (descriptor.returnType?.isNothing() == true) {
|
if (descriptor.returnType.isNothing()) {
|
||||||
functionGenerationContext.unreachable()
|
functionGenerationContext.unreachable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-3
@@ -111,9 +111,6 @@ private fun StaticData.getArrayListClass(): ClassDescriptor {
|
|||||||
internal fun StaticData.createArrayList(elementType: TypeProjection, array: ConstPointer, length: Int): ConstPointer {
|
internal fun StaticData.createArrayList(elementType: TypeProjection, array: ConstPointer, length: Int): ConstPointer {
|
||||||
val arrayListClass = context.ir.symbols.arrayList.owner
|
val arrayListClass = context.ir.symbols.arrayList.owner
|
||||||
|
|
||||||
// type is ArrayList<elementType>:
|
|
||||||
val type = arrayListClass.defaultType.replace(listOf(elementType))
|
|
||||||
|
|
||||||
val arrayListFqName = arrayListClass.fqNameSafe
|
val arrayListFqName = arrayListClass.fqNameSafe
|
||||||
val arrayListFields = mapOf(
|
val arrayListFields = mapOf(
|
||||||
"$arrayListFqName.array" to array,
|
"$arrayListFqName.array" to array,
|
||||||
|
|||||||
+2
-2
@@ -597,7 +597,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
symbolTable.mapClass(owner),
|
symbolTable.mapClass(owner),
|
||||||
vtableIndex,
|
vtableIndex,
|
||||||
arguments,
|
arguments,
|
||||||
symbolTable.mapType(callee.returnType!!),
|
symbolTable.mapType(callee.returnType),
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -606,7 +606,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
DataFlowIR.Node.StaticCall(
|
DataFlowIR.Node.StaticCall(
|
||||||
symbolTable.mapFunction(actualCallee),
|
symbolTable.mapFunction(actualCallee),
|
||||||
arguments,
|
arguments,
|
||||||
symbolTable.mapType(actualCallee.returnType!!),
|
symbolTable.mapType(actualCallee.returnType),
|
||||||
actualCallee.dispatchReceiverParameter?.let { symbolTable.mapType(it.type) },
|
actualCallee.dispatchReceiverParameter?.let { symbolTable.mapType(it.type) },
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-1
@@ -277,7 +277,8 @@ private fun IrElement.innerEndOffset(descriptor: DeclarationDescriptorWithSource
|
|||||||
descriptor.endOffset ?: this.endOffset
|
descriptor.endOffset ?: this.endOffset
|
||||||
|
|
||||||
inline fun <reified T> stub(name: String): T {
|
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) {
|
if (method.name == "toString" && method.parameterCount == 0) {
|
||||||
"${T::class.simpleName} stub for $name"
|
"${T::class.simpleName} stub for $name"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -610,6 +610,22 @@ task worker8(type: RunKonanTest) {
|
|||||||
source = "runtime/workers/worker8.kt"
|
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) {
|
task superFunCall(type: RunKonanTest) {
|
||||||
goldValue = "<fun:C><fun:C1>\n<fun:C><fun:C3>\n"
|
goldValue = "<fun:C><fun:C1>\n<fun:C><fun:C3>\n"
|
||||||
source = "codegen/basics/superFunCall.kt"
|
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
|
#include <cstddef> // for offsetof
|
||||||
|
|
||||||
#ifndef KONAN_NO_THREADS
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Alloc.h"
|
#include "Alloc.h"
|
||||||
#include "Assert.h"
|
#include "Assert.h"
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
@@ -31,7 +27,6 @@
|
|||||||
#include "Natives.h"
|
#include "Natives.h"
|
||||||
#include "Porting.h"
|
#include "Porting.h"
|
||||||
|
|
||||||
|
|
||||||
// If garbage collection algorithm for cyclic garbage to be used.
|
// If garbage collection algorithm for cyclic garbage to be used.
|
||||||
// We are using the Bacon's algorithm for GC, see
|
// We are using the Bacon's algorithm for GC, see
|
||||||
// http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf.
|
// http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf.
|
||||||
@@ -43,6 +38,7 @@
|
|||||||
// Auto-adjust GC thresholds.
|
// Auto-adjust GC thresholds.
|
||||||
#define GC_ERGONOMICS 1
|
#define GC_ERGONOMICS 1
|
||||||
|
|
||||||
|
// TODO: ensure it it read-only.
|
||||||
ContainerHeader ObjHeader::theStaticObjectsContainer = {
|
ContainerHeader ObjHeader::theStaticObjectsContainer = {
|
||||||
CONTAINER_TAG_PERMANENT | CONTAINER_TAG_INCREMENT
|
CONTAINER_TAG_PERMANENT | CONTAINER_TAG_INCREMENT
|
||||||
};
|
};
|
||||||
@@ -419,9 +415,13 @@ inline bool isRefCounted(KConstRef object) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
void objc_release(void* ptr);
|
void objc_release(void* ptr);
|
||||||
void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj);
|
void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj);
|
||||||
}
|
RUNTIME_NORETURN void ThrowFreezingException();
|
||||||
|
RUNTIME_NORETURN void ThrowInvalidMutabilityException();
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
inline void runDeallocationHooks(ObjHeader* obj) {
|
inline void runDeallocationHooks(ObjHeader* obj) {
|
||||||
#if KONAN_OBJC_INTEROP
|
#if KONAN_OBJC_INTEROP
|
||||||
@@ -499,12 +499,14 @@ inline void scheduleDestroyContainer(
|
|||||||
|
|
||||||
#if !USE_GC
|
#if !USE_GC
|
||||||
|
|
||||||
|
template <bool Atomic>
|
||||||
inline void IncrementRC(ContainerHeader* container) {
|
inline void IncrementRC(ContainerHeader* container) {
|
||||||
container->incRefCount();
|
container->incRefCount<Atomic>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool Atomic>
|
||||||
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
|
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
|
||||||
if (container->decRefCount() == 0) {
|
if (container->decRefCount<Atomic>() == 0) {
|
||||||
FreeContainer(container);
|
FreeContainer(container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,15 +517,19 @@ inline uint32_t freeableSize(MemoryState* state) {
|
|||||||
return state->toFree->size();
|
return state->toFree->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool Atomic>
|
||||||
inline void IncrementRC(ContainerHeader* container) {
|
inline void IncrementRC(ContainerHeader* container) {
|
||||||
container->incRefCount();
|
container->incRefCount<Atomic>();
|
||||||
container->setColor(CONTAINER_TAG_GC_BLACK);
|
container->setColor(CONTAINER_TAG_GC_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool Atomic>
|
||||||
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
|
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
|
||||||
if (container->decRefCount() == 0) {
|
if (container->decRefCount<Atomic>() == 0) {
|
||||||
FreeContainer(container);
|
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) {
|
if (container->color() != CONTAINER_TAG_GC_PURPLE) {
|
||||||
container->setColor(CONTAINER_TAG_GC_PURPLE);
|
container->setColor(CONTAINER_TAG_GC_PURPLE);
|
||||||
if (!container->buffered()) {
|
if (!container->buffered()) {
|
||||||
@@ -618,8 +624,9 @@ void MarkGray(ContainerHeader* container) {
|
|||||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
if (!childContainer->permanent()) {
|
|
||||||
childContainer->decRefCount();
|
if (!childContainer->permanentOrFrozen()) {
|
||||||
|
childContainer->decRefCount<false>();
|
||||||
MarkGray<useColor>(childContainer);
|
MarkGray<useColor>(childContainer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -637,8 +644,8 @@ void ScanBlack(ContainerHeader* container) {
|
|||||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
if (!childContainer->permanent()) {
|
if (!childContainer->permanentOrFrozen()) {
|
||||||
childContainer->incRefCount();
|
childContainer->incRefCount<false>();
|
||||||
if (useColor) {
|
if (useColor) {
|
||||||
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
||||||
ScanBlack<useColor>(childContainer);
|
ScanBlack<useColor>(childContainer);
|
||||||
@@ -701,7 +708,7 @@ void Scan(ContainerHeader* container) {
|
|||||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
if (!childContainer->permanent()) {
|
if (!childContainer->permanentOrFrozen()) {
|
||||||
Scan(childContainer);
|
Scan(childContainer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -730,7 +737,10 @@ inline void AddRef(ContainerHeader* header) {
|
|||||||
case CONTAINER_TAG_PERMANENT:
|
case CONTAINER_TAG_PERMANENT:
|
||||||
break;
|
break;
|
||||||
case CONTAINER_TAG_NORMAL:
|
case CONTAINER_TAG_NORMAL:
|
||||||
IncrementRC(header);
|
IncrementRC<false>(header);
|
||||||
|
break;
|
||||||
|
case CONTAINER_TAG_FROZEN:
|
||||||
|
IncrementRC<true>(header);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
RuntimeAssert(false, "unknown container type");
|
RuntimeAssert(false, "unknown container type");
|
||||||
@@ -746,7 +756,10 @@ inline void Release(ContainerHeader* header, bool useCycleCollector) {
|
|||||||
case CONTAINER_TAG_STACK:
|
case CONTAINER_TAG_STACK:
|
||||||
break;
|
break;
|
||||||
case CONTAINER_TAG_NORMAL:
|
case CONTAINER_TAG_NORMAL:
|
||||||
DecrementRC(header, useCycleCollector);
|
DecrementRC<false>(header, useCycleCollector);
|
||||||
|
break;
|
||||||
|
case CONTAINER_TAG_FROZEN:
|
||||||
|
DecrementRC<true>(header, useCycleCollector);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
RuntimeAssert(false, "unknown container type");
|
RuntimeAssert(false, "unknown container type");
|
||||||
@@ -1127,7 +1140,7 @@ void UpdateRef(ObjHeader** location, const ObjHeader* object) {
|
|||||||
AddRef(object);
|
AddRef(object);
|
||||||
}
|
}
|
||||||
*const_cast<const ObjHeader**>(location) = object;
|
*const_cast<const ObjHeader**>(location) = object;
|
||||||
if (old > reinterpret_cast<ObjHeader*>(1)) {
|
if (old != nullptr) {
|
||||||
ReleaseRef(old);
|
ReleaseRef(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1300,7 +1313,7 @@ bool hasExternalRefs(ContainerHeader* container, ContainerHeaderSet* visited) {
|
|||||||
bool result = container->refCount() != 0;
|
bool result = container->refCount() != 0;
|
||||||
traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) {
|
||||||
auto child = ref->container();
|
auto child = ref->container();
|
||||||
if (!child->permanent() && (visited->find(child) == visited->end())) {
|
if (!child->permanentOrFrozen() && (visited->find(child) == visited->end())) {
|
||||||
result |= hasExternalRefs(child, visited);
|
result |= hasExternalRefs(child, visited);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1314,18 +1327,26 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
|||||||
auto state = memoryState;
|
auto state = memoryState;
|
||||||
auto container = root->container();
|
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;
|
ContainerHeaderSet visited;
|
||||||
if (!checked) {
|
if (!checked) {
|
||||||
hasExternalRefs(container, &visited);
|
hasExternalRefs(container, &visited);
|
||||||
} else {
|
} else {
|
||||||
container->decRefCount();
|
if (!container->permanentOrFrozen()) {
|
||||||
MarkGray<false>(container);
|
container->decRefCount<false>();
|
||||||
auto bad = hasExternalRefs(container, &visited);
|
MarkGray<false>(container);
|
||||||
ScanBlack<false>(container);
|
auto bad = hasExternalRefs(container, &visited);
|
||||||
container->incRefCount();
|
ScanBlack<false>(container);
|
||||||
if (bad) return false;
|
container->incRefCount<false>();
|
||||||
|
if (bad) return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: not very effecient traversal.
|
||||||
for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) {
|
for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) {
|
||||||
auto container = *it;
|
auto container = *it;
|
||||||
if (visited.find(container) != visited.end()) {
|
if (visited.find(container) != visited.end()) {
|
||||||
@@ -1339,4 +1360,115 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
|||||||
return true;
|
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"
|
} // 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "TypeInfo.h"
|
#include "TypeInfo.h"
|
||||||
|
|
||||||
// Must fit in two bits.
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
// Those bit masks are applied to refCount_ field.
|
// Those bit masks are applied to refCount_ field.
|
||||||
// Container is normal thread local container.
|
// Container is normal thread local container.
|
||||||
@@ -43,17 +42,19 @@ typedef enum {
|
|||||||
|
|
||||||
// Those bit masks are applied to objectCount_ field.
|
// Those bit masks are applied to objectCount_ field.
|
||||||
// Shift to get actual object count.
|
// 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,
|
CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT,
|
||||||
// Color of a container.
|
// Color mask of a container.
|
||||||
CONTAINER_TAG_GC_COLOR_MASK = ((CONTAINER_TAG_GC_INCREMENT >> 2) - 1),
|
CONTAINER_TAG_GC_COLOR_MASK = (1 << 2) - 1,
|
||||||
// Colors.
|
// Colors.
|
||||||
CONTAINER_TAG_GC_BLACK = 0,
|
CONTAINER_TAG_GC_BLACK = 0,
|
||||||
CONTAINER_TAG_GC_GRAY = 1,
|
CONTAINER_TAG_GC_GRAY = 1,
|
||||||
CONTAINER_TAG_GC_WHITE = 2,
|
CONTAINER_TAG_GC_WHITE = 2,
|
||||||
CONTAINER_TAG_GC_PURPLE = 3,
|
CONTAINER_TAG_GC_PURPLE = 3,
|
||||||
CONTAINER_TAG_GC_MARKED = 4,
|
// Individual state bits used during GC and freezing.
|
||||||
CONTAINER_TAG_GC_BUFFERED = 8
|
CONTAINER_TAG_GC_MARKED = 1 << 2,
|
||||||
|
CONTAINER_TAG_GC_BUFFERED = 1 << 3,
|
||||||
|
CONTAINER_TAG_GC_SEEN = 1 << 4
|
||||||
} ContainerTag;
|
} ContainerTag;
|
||||||
|
|
||||||
typedef uint32_t container_offset_t;
|
typedef uint32_t container_offset_t;
|
||||||
@@ -71,17 +72,47 @@ struct ContainerHeader {
|
|||||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT;
|
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 {
|
inline unsigned refCount() const {
|
||||||
return refCount_ >> CONTAINER_TAG_SHIFT;
|
return refCount_ >> CONTAINER_TAG_SHIFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool Atomic>
|
||||||
inline void incRefCount() {
|
inline void incRefCount() {
|
||||||
|
#ifdef KONAN_NO_THREADS
|
||||||
refCount_ += CONTAINER_TAG_INCREMENT;
|
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() {
|
inline int decRefCount() {
|
||||||
refCount_ -= CONTAINER_TAG_INCREMENT;
|
#ifdef KONAN_NO_THREADS
|
||||||
return refCount_ >> CONTAINER_TAG_SHIFT;
|
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 {
|
inline unsigned tag() const {
|
||||||
@@ -131,6 +162,18 @@ struct ContainerHeader {
|
|||||||
inline void unMark() {
|
inline void unMark() {
|
||||||
objectCount_ &= ~CONTAINER_TAG_GC_MARKED;
|
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;
|
struct ArrayHeader;
|
||||||
@@ -413,7 +456,10 @@ void DisposeStablePointer(void* pointer) RUNTIME_NOTHROW;
|
|||||||
OBJ_GETTER(DerefStablePointer, void*) RUNTIME_NOTHROW;
|
OBJ_GETTER(DerefStablePointer, void*) RUNTIME_NOTHROW;
|
||||||
// Move stable pointer ownership.
|
// Move stable pointer ownership.
|
||||||
OBJ_GETTER(AdoptStablePointer, void*) RUNTIME_NOTHROW;
|
OBJ_GETTER(AdoptStablePointer, void*) RUNTIME_NOTHROW;
|
||||||
|
// Check mutability state.
|
||||||
|
void MutationCheck(ObjHeader* obj);
|
||||||
|
// Freeze object subgraph.
|
||||||
|
void FreezeSubgraph(ObjHeader* root);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
void ThrowWorkerInvalidState();
|
RUNTIME_NORETURN void ThrowWorkerInvalidState();
|
||||||
void ThrowWorkerUnsupported();
|
RUNTIME_NORETURN void ThrowWorkerUnsupported();
|
||||||
OBJ_GETTER(WorkerLaunchpad, KRef);
|
OBJ_GETTER(WorkerLaunchpad, KRef);
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
@@ -571,4 +571,13 @@ KNativePtr Kotlin_Worker_detachObjectGraphInternal(KInt transferMode, KRef produ
|
|||||||
return detachObjectGraphInternal(transferMode, producer);
|
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"
|
} // 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