[K/N] Add binary option to disable freezing

We don't want to deprecate freezing at all, but it is possible
that freezing, new memory model and lazy global initialization
combination might not work in some cases. It might be a problem
when such case appears in 3rd-party library that user can't fix.

To mitigate this problem this commit introduces `freezing` binary
option. It has three variants:
* Full - ol' good behavior.
* Disabled - well, no freezing at all.
* ExplicitOnly - a compromise when user want to freeze something
themselves, but something is messed up during globals initialization.
This commit is contained in:
Sergey Bogolepov
2021-08-28 11:10:49 +07:00
committed by Space
parent db34f67c1c
commit 4b5a7c6646
9 changed files with 70 additions and 5 deletions
@@ -15,6 +15,8 @@ object BinaryOptions : BinaryOptionRegistry() {
val runtimeAssertionsMode by option<RuntimeAssertsMode>()
val memoryModel by option<MemoryModel>()
val freezing by option<Freezing>()
}
open class BinaryOption<T : Any>(
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan
/**
* Gradually control what parts of freezing are enabled.
* Not intended to be used with legacy MM where freezing is a must.
*
* If [enableFreezeAtRuntime] is false then `Any.freeze()` and `checkIfFrozen(ref: Any?)` are no-op.
* [freezeImplicit] enabled freezing for @Frozen types and @SharedImmutable globals (i.e. implicit calls to `Any.freeze()`).
*/
enum class Freezing(val enableFreezeAtRuntime: Boolean, val freezeImplicit: Boolean) {
/**
* Enable freezing in `Any.freeze()` as well as for @Frozen types and @SharedImmutable globals.
*/
Full(true, true),
/**
* Enable freezing only in explicit calls to `Any.freeze()`.
*/
ExplicitOnly(true, false),
/**
* No freezing at all.
*/
Disabled(false, false);
companion object {
// Users might depend on runtime guarantees of freezing, so it should be enabled by default.
val Default = Full
}
}
@@ -81,6 +81,19 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
val runtimeAssertsMode: RuntimeAssertsMode get() = configuration.get(BinaryOptions.runtimeAssertionsMode) ?: RuntimeAssertsMode.IGNORE
val workerExceptionHandling: WorkerExceptionHandling get() = configuration.get(KonanConfigKeys.WORKER_EXCEPTION_HANDLING)!!
val runtimeLogs: String? get() = configuration.get(KonanConfigKeys.RUNTIME_LOGS)
val freezing: Freezing by lazy {
val freezingMode = configuration.get(BinaryOptions.freezing)
when {
freezingMode == null -> Freezing.Default
memoryModel != MemoryModel.EXPERIMENTAL && freezingMode != Freezing.Default -> {
configuration.report(
CompilerMessageSeverity.ERROR,
"`freezing` can only be adjusted with experimental MM. Falling back to default behavior.")
Freezing.Default
}
else -> freezingMode
}
}
val needVerifyIr: Boolean
get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true
@@ -88,6 +88,11 @@ val IrField.isGlobalNonPrimitive get() = when {
else -> storageKind == FieldStorageKind.GLOBAL
}
internal fun IrField.shouldBeFrozen(context: Context): Boolean =
this.storageKind == FieldStorageKind.SHARED_FROZEN &&
(context.memoryModel != MemoryModel.EXPERIMENTAL || context.config.freezing.freezeImplicit)
internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
val generator = RTTIGenerator(context)
@@ -341,7 +346,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(this)
val initialValue = if (irField.initializer?.expression !is IrConst<*>?) {
val initialization = evaluateExpression(irField.initializer!!.expression)
if (irField.storageKind == FieldStorageKind.SHARED_FROZEN)
if (irField.shouldBeFrozen(context))
freeze(initialization, currentCodeContext.exceptionHandler)
initialization
} else {
@@ -1754,7 +1759,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
)
if (context.config.threadsAreAllowed && value.symbol.owner.storageKind == FieldStorageKind.GLOBAL)
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
if (value.symbol.owner.storageKind == FieldStorageKind.SHARED_FROZEN)
if (value.symbol.owner.shouldBeFrozen(context))
functionGenerationContext.freeze(valueToAssign, currentCodeContext.exceptionHandler)
functionGenerationContext.storeAny(valueToAssign, globalAddress, false)
}
@@ -2629,6 +2634,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
overrideRuntimeGlobal("Kotlin_destroyRuntimeMode", Int32(context.config.destroyRuntimeMode.value))
overrideRuntimeGlobal("Kotlin_gcAggressive", Int32(if (context.config.gcAggressive) 1 else 0))
overrideRuntimeGlobal("Kotlin_workerExceptionHandling", Int32(context.config.workerExceptionHandling.value))
overrideRuntimeGlobal("Kotlin_freezingEnabled", Int32(if (context.config.freezing.enableFreezeAtRuntime) 1 else 0))
}
//-------------------------------------------------------------------------//
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.symbols.isPublicApi
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
@@ -54,7 +53,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
private fun flagsFromClass(irClass: IrClass): Int {
var result = 0
if (irClass.isFrozen)
if (irClass.isFrozen && context.config.freezing.freezeImplicit)
result = result or TF_IMMUTABLE
// TODO: maybe perform deeper analysis to find surely acyclic types.
if (!irClass.isInterface && !irClass.isAbstract() && !irClass.isAnnotationClass) {
@@ -176,6 +176,9 @@ KNativePtr Kotlin_AtomicNativePtr_get(KRef thiz) {
}
void Kotlin_AtomicReference_checkIfFrozen(KRef value) {
if (!kotlin::compiler::freezingEnabled()) {
return;
}
if (value != nullptr && !isPermanentOrFrozen(value)) {
ThrowInvalidMutabilityException(value);
}
@@ -13,6 +13,7 @@ using namespace kotlin;
RUNTIME_WEAK int32_t Kotlin_destroyRuntimeMode = 1;
RUNTIME_WEAK int32_t Kotiln_gcAggressive = 0;
RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0;
RUNTIME_WEAK int32_t Kotlin_freezingEnabled = 1;
ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept {
return static_cast<compiler::DestroyRuntimeMode>(Kotlin_destroyRuntimeMode);
@@ -25,3 +26,7 @@ ALWAYS_INLINE bool compiler::gcAggressive() noexcept {
ALWAYS_INLINE compiler::WorkerExceptionHandling compiler::workerExceptionHandling() noexcept {
return static_cast<compiler::WorkerExceptionHandling>(Kotlin_workerExceptionHandling);
}
ALWAYS_INLINE bool compiler::freezingEnabled() noexcept {
return Kotlin_freezingEnabled != 0;
}
@@ -69,6 +69,8 @@ ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept {
return Kotlin_runtimeLogs == nullptr ? std::string_view() : std::string_view(Kotlin_runtimeLogs);
}
bool freezingEnabled() noexcept;
} // namespace compiler
} // namespace kotlin
@@ -1167,7 +1167,7 @@ KNativePtr Kotlin_Worker_detachObjectGraphInternal(KInt transferMode, KRef produ
}
void Kotlin_Worker_freezeInternal(KRef object) {
if (object != nullptr)
if (object != nullptr && compiler::freezingEnabled())
FreezeSubgraph(object);
}