[K/N][IR][runtime] Implemented lazy per-file initialization strategy

This commit is contained in:
Igor Chevdar
2021-03-12 11:18:55 +05:00
parent 504449f03e
commit b11201be81
37 changed files with 976 additions and 123 deletions
@@ -336,6 +336,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
RuntimeAssertsMode.IGNORE
}
})
put(PROPERTY_LAZY_INITIALIZATION, arguments.propertyLazyInitialization)
}
}
}
@@ -314,6 +314,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
@Argument(value="-Xgc-aggressive", description = "Make GC agressive. Works only with -memory-model experimental")
var gcAggressive: Boolean = false
@Argument(value = "-Xir-property-lazy-initialization", description = "Initialize top level properties lazily per file")
var propertyLazyInitialization: Boolean = false
@Argument(
value = "-Xcheck-compatibility-with-lld",
valueDescription = "{disable|enable}",
@@ -50,15 +50,22 @@ class CacheSupport(
library to cachePath
}
val hasCachedLibs = explicitCacheFiles.isNotEmpty() || implicitCacheDirectories.isNotEmpty()
val optimized = configuration.getBoolean(KonanConfigKeys.OPTIMIZATION)
if (optimized && (explicitCacheFiles.isNotEmpty() || implicitCacheDirectories.isNotEmpty()))
if (optimized && hasCachedLibs)
configuration.report(CompilerMessageSeverity.WARNING, "Cached libraries will not be used for optimized compilation")
val usePropertyLazyInitialization = configuration.getBoolean(KonanConfigKeys.PROPERTY_LAZY_INITIALIZATION)
if (usePropertyLazyInitialization && hasCachedLibs)
configuration.report(CompilerMessageSeverity.WARNING, "Cached libraries will not be used with experimental lazy top levels initialization")
val ignoreCachedLibraries = optimized || usePropertyLazyInitialization
CachedLibraries(
target = target,
allLibraries = allLibraries,
explicitCaches = if (optimized) emptyMap() else explicitCaches,
implicitCacheDirectories = if (optimized) emptyList() else implicitCacheDirectories
explicitCaches = if (ignoreCachedLibraries) emptyMap() else explicitCaches,
implicitCacheDirectories = if (ignoreCachedLibraries) emptyList() else implicitCacheDirectories
)
}
@@ -446,6 +446,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
fun shouldOptimize() = config.configuration.getBoolean(KonanConfigKeys.OPTIMIZATION)
fun ghaEnabled() = ::globalHierarchyAnalysisResult.isInitialized
fun useLazyFileInitializers() = config.propertyLazyInitialization
val memoryModel = config.memoryModel
@@ -221,6 +221,8 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
}
internal val isInteropStubs: Boolean get() = manifestProperties?.getProperty("interop") == "true"
internal val propertyLazyInitialization: Boolean get() = configuration.get(KonanConfigKeys.PROPERTY_LAZY_INITIALIZATION)!!
}
fun CompilerConfiguration.report(priority: CompilerMessageSeverity, message: String)
@@ -162,6 +162,8 @@ class KonanConfigKeys {
val GARBAGE_COLLECTOR_AGRESSIVE: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("turn on agressive GC mode")
val CHECK_LLD_COMPATIBILITY: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("check compatibility with LLD")
val RUNTIME_ASSERTS_MODE: CompilerConfigurationKey<RuntimeAssertsMode> = CompilerConfigurationKey.create("enable runtime asserts")
val PROPERTY_LAZY_INITIALIZATION: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("lazy top level properties initialization")
}
}
@@ -395,6 +395,19 @@ internal val autoboxPhase = makeKonanFileLoweringPhase(
prerequisite = setOf(bridgesPhase, coroutinesPhase)
)
internal val expressionBodyTransformPhase = makeKonanFileLoweringPhase(
::ExpressionBodyTransformer,
name = "ExpressionBodyTransformer",
description = "Replace IrExpressionBody with IrBlockBody"
)
internal val fileInitializersPhase = makeKonanFileLoweringPhase(
::FileInitializersLowering,
name = "FileInitializers",
description = "Add calls to file initializers",
prerequisite = setOf(expressionBodyTransformPhase)
)
internal val ifNullExpressionsFusionPhase = makeKonanFileLoweringPhase(
::IfNullExpressionsFusionLowering,
name = "IfNullExpressionsFusionLowering",
@@ -232,6 +232,8 @@ internal val allLoweringsPhase = NamedCompilerPhase(
kotlinNothingValueExceptionPhase,
coroutinesPhase,
typeOperatorPhase,
expressionBodyTransformPhase,
fileInitializersPhase,
bridgesPhase,
autoboxPhase,
)
@@ -375,6 +377,7 @@ internal val bitcodePhase = NamedCompilerPhase(
devirtualizationPhase then
redundantCoercionsCleaningPhase then
dcePhase then
removeRedundantCallsToFileInitializersPhase then
createLLVMDeclarationsPhase then
ghaPhase then
RTTIPhase then
@@ -467,9 +470,13 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
// debug information.
disableUnless(propertyAccessorInlinePhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(dcePhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(removeRedundantCallsToFileInitializersPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(ghaPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(verifyBitcodePhase, config.needCompilerVerification || getBoolean(KonanConfigKeys.VERIFY_BITCODE))
disableUnless(fileInitializersPhase, getBoolean(KonanConfigKeys.PROPERTY_LAZY_INITIALIZATION))
disableUnless(removeRedundantCallsToFileInitializersPhase, getBoolean(KonanConfigKeys.PROPERTY_LAZY_INITIALIZATION))
val isDescriptorsOnlyLibrary = config.metadataKlib == true
disableIf(psiToIrPhase, isDescriptorsOnlyLibrary)
disableIf(destroySymbolTablePhase, isDescriptorsOnlyLibrary)
@@ -12,12 +12,17 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaserState
import org.jetbrains.kotlin.backend.common.phaser.namedUnitPhase
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.GlobalHierarchyAnalysis
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.RedundantCoercionsCleaner
import org.jetbrains.kotlin.backend.konan.lower.ReturnsInsertionLowering
import org.jetbrains.kotlin.backend.konan.optimizations.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -159,11 +164,11 @@ internal val dcePhase = makeKonanModuleOpPhase(
val referencedFunctions = mutableSetOf<IrFunction>()
callGraph.rootExternalFunctions.forEach {
if (!it.isGlobalInitializer)
if (!it.isTopLevelFieldInitializer)
referencedFunctions.add(it.irFunction ?: error("No IR for: $it"))
}
for (node in callGraph.directEdges.values) {
if (!node.symbol.isGlobalInitializer)
if (!node.symbol.isTopLevelFieldInitializer)
referencedFunctions.add(node.symbol.irFunction ?: error("No IR for: ${node.symbol}"))
node.callSites.forEach {
assert (!it.isVirtual) { "There should be no virtual calls in the call graph, but was: ${it.actualCallee}" }
@@ -228,6 +233,51 @@ internal val dcePhase = makeKonanModuleOpPhase(
}
)
internal val removeRedundantCallsToFileInitializersPhase = makeKonanModuleOpPhase(
name = "RemoveRedundantCallsToFileInitializersPhase",
description = "Redundant file initializers calls removal",
prerequisite = setOf(devirtualizationPhase),
op = { context, _ ->
val moduleDFG = context.moduleDFG!!
val externalModulesDFG = ExternalModulesDFG(emptyList(), emptyMap(), emptyMap(), emptyMap())
val callGraph = CallGraphBuilder(
context, moduleDFG,
externalModulesDFG,
context.devirtualizationAnalysisResult!!,
nonDevirtualizedCallSitesUnfoldFactor = Int.MAX_VALUE
).build()
val functionsBeingCalledFromOtherFiles = mutableSetOf<IrFunction>()
for (node in callGraph.directEdges.values) {
val callerFile = node.symbol.irFile
node.callSites.forEach {
require(!it.isVirtual) { "There should be no virtual calls in the call graph, but was: ${it.actualCallee}" }
val calleeFile = it.actualCallee.irFile
if (callerFile == null || callerFile != calleeFile)
functionsBeingCalledFromOtherFiles.add(it.actualCallee.irFunction ?: error("No IR for: ${it.actualCallee}"))
}
}
val rootSet = Devirtualization.computeRootSet(context, moduleDFG, externalModulesDFG).toSet()
context.irModule!!.transformChildrenVoid(object: IrElementTransformerVoid() {
override fun visitFunction(declaration: IrFunction): IrStatement {
declaration.transformChildrenVoid(this)
if (declaration in functionsBeingCalledFromOtherFiles
|| moduleDFG.symbolTable.mapFunction(declaration) in rootSet) return declaration
val body = declaration.body ?: return declaration
(body as IrBlockBody).statements.removeAll {
val calleeOrigin = (it as? IrCall)?.symbol?.owner?.origin
calleeOrigin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
|| calleeOrigin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|| calleeOrigin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
}
return declaration
}
})
}
)
internal val escapeAnalysisPhase = makeKonanModuleOpPhase(
name = "EscapeAnalysis",
description = "Escape analysis",
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.backend.konan.CachedLibraries
import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
@@ -229,6 +228,31 @@ internal val Name.localHash: LocalHash
internal val FqName.localHash: LocalHash
get() = this.toString().localHash
internal class InitializersGenerationState {
val fileGlobalInitStates = mutableMapOf<IrFile, LLVMValueRef>()
val fileThreadLocalInitStates = mutableMapOf<IrFile, AddressAccess>()
val topLevelFields = mutableListOf<IrField>()
val moduleThreadLocalInitializers = mutableListOf<IrFunction>()
val moduleGlobalInitializers = mutableListOf<IrFunction>()
var globalInitFunction: IrFunction? = null
var globalInitState: LLVMValueRef? = null
var threadLocalInitFunction: IrFunction? = null
var threadLocalInitState: AddressAccess? = null
fun reset() {
moduleThreadLocalInitializers.clear()
moduleGlobalInitializers.clear()
topLevelFields.clear()
globalInitFunction = null
globalInitState = null
threadLocalInitFunction = null
threadLocalInitState = null
}
fun isEmpty() = topLevelFields.isEmpty() && globalInitState == null && threadLocalInitState == null
&& moduleGlobalInitializers.isEmpty() && moduleThreadLocalInitializers.isEmpty()
}
internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
@@ -468,6 +492,8 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val isInstanceOfClassFastFunction = importRtFunction("IsInstanceOfClassFast")
val throwExceptionFunction = importRtFunction("ThrowException")
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
val callInitGlobalPossiblyLock = importRtFunction("CallInitGlobalPossiblyLock")
val callInitThreadLocal = importRtFunction("CallInitThreadLocal")
val addTLSRecord = importRtFunction("AddTLSRecord")
val lookupTLS = importRtFunction("LookupTLS")
val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded")
@@ -577,9 +603,9 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val compilerUsedGlobals = mutableListOf<LLVMValueRef>()
val irStaticInitializers = mutableListOf<IrStaticInitializer>()
val otherStaticInitializers = mutableListOf<LLVMValueRef>()
val fileInitializers = mutableListOf<IrField>()
var fileUsesThreadLocalObjects = false
val globalSharedObjects = mutableSetOf<LLVMValueRef>()
val initializersGenerationState = InitializersGenerationState()
private object lazyRtFunction {
operator fun provideDelegate(
@@ -16,6 +16,11 @@ import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_MODULE_GLOBAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_MODULE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
@@ -319,15 +324,87 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
context.cAdapterGenerator.generateBindings(codegen)
}
private fun FunctionGenerationContext.initThreadLocalField(irField: IrField) {
val initializer = irField.initializer ?: return
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(this)
storeAny(evaluateExpression(initializer.expression), address, false)
}
private fun FunctionGenerationContext.initGlobalField(irField: IrField) {
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)
freeze(initialization, currentCodeContext.exceptionHandler)
initialization
} else {
null
}
val needRegistration =
context.memoryModel == MemoryModel.EXPERIMENTAL && // only for the new MM
irField.type.binaryTypeIsReference() && // only for references
(initialValue != null || // which are initialized from heap object
!irField.isFinal) // or are not final
if (needRegistration) {
call(context.llvm.initAndRegisterGlobalFunction, listOf(address, initialValue
?: kNullObjHeaderPtr))
} else if (initialValue != null) {
storeAny(initialValue, address, false)
}
}
private fun runAndProcessInitializers(konanLibrary: KotlinLibrary?, f: () -> Unit) {
// TODO: collect those two in one place.
context.llvm.fileInitializers.clear()
context.llvm.fileUsesThreadLocalObjects = false
context.llvm.globalSharedObjects.clear()
context.llvm.initializersGenerationState.reset()
f()
if (context.llvm.fileInitializers.isEmpty() && !context.llvm.fileUsesThreadLocalObjects && context.llvm.globalSharedObjects.isEmpty()) {
context.llvm.initializersGenerationState.globalInitFunction?.let { fileInitFunction ->
generateFunction(codegen, fileInitFunction, null, null) {
using(FunctionScope(fileInitFunction, it)) {
val parameterScope = ParameterScope(fileInitFunction, functionGenerationContext)
using(parameterScope) usingParameterScope@{
using(VariableScope()) usingVariableScope@{
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.SHARED_FROZEN }
.forEach { initGlobalField(it) }
ret(null)
}
}
}
}
}
context.llvm.initializersGenerationState.threadLocalInitFunction?.let { fileInitFunction ->
generateFunction(codegen, fileInitFunction, null, null) {
using(FunctionScope(fileInitFunction, it)) {
val parameterScope = ParameterScope(fileInitFunction, functionGenerationContext)
using(parameterScope) usingParameterScope@{
using(VariableScope()) usingVariableScope@{
val bbInitGlobals = basicBlock("label_init_global", null, null)
val bbInitThreadLocals = basicBlock("label_init_thread_local", null, null)
condBr(param(0), bbInitGlobals, bbInitThreadLocals)
positionAtEnd(bbInitGlobals)
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.GLOBAL }
.forEach { initGlobalField(it) }
br(bbInitThreadLocals)
positionAtEnd(bbInitThreadLocals)
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initThreadLocalField(it) }
ret(null)
}
}
}
}
}
if (!context.llvm.fileUsesThreadLocalObjects && context.llvm.globalSharedObjects.isEmpty()
&& context.llvm.initializersGenerationState.isEmpty()) {
return
}
@@ -385,6 +462,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val INIT_THREAD_LOCAL_GLOBALS = 2
val DEINIT_GLOBALS = 3
val FILE_NOT_INITIALIZED = 0
val FILE_INITIALIZED = 2
private fun createInitBody(): LLVMValueRef {
val initFunction = addLlvmFunctionWithDefaultAttributes(
context,
@@ -410,49 +490,37 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
Int32(DEINIT_GLOBALS).llvm to bbGlobalDeinit),
bbDefault)
// Globals initalizers may contain accesses to objects, so visit them first.
// Globals initializers may contain accesses to objects, so visit them first.
appendingTo(bbInit) {
context.llvm.fileInitializers
.forEach { irField ->
if (irField.storageKind != FieldStorageKind.THREAD_LOCAL) {
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(
functionGenerationContext
)
val initialValue = if (irField.initializer?.expression !is IrConst<*>?) {
val initialization = evaluateExpression(irField.initializer!!.expression)
if (irField.storageKind == FieldStorageKind.SHARED_FROZEN)
freeze(initialization, currentCodeContext.exceptionHandler)
initialization
} else {
null
}
val needRegistration =
context.memoryModel == MemoryModel.EXPERIMENTAL && // only for the new MM
irField.type.binaryTypeIsReference() && // only for references
(initialValue != null || // which are initialized from heap object
!irField.isFinal) // or are not final
if (needRegistration) {
call(context.llvm.initAndRegisterGlobalFunction, listOf(address, initialValue
?: kNullObjHeaderPtr))
} else if (initialValue != null) {
storeAny(initialValue, address, false)
}
}
}
if (!context.useLazyFileInitializers()) {
context.llvm.initializersGenerationState.topLevelFields
.filterNot { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initGlobalField(it) }
}
context.llvm.initializersGenerationState.moduleGlobalInitializers.forEach {
if (context.shouldContainLocationDebugInfo())
debugLocation(it.startLocation!!, it.endLocation)
evaluateSimpleFunctionCall(it, emptyList(), Lifetime.IRRELEVANT)
}
ret(null)
}
appendingTo(bbLocalInit) {
context.llvm.fileInitializers
.forEach { irField ->
if (irField.initializer != null && irField.storageKind == FieldStorageKind.THREAD_LOCAL) {
val initialization = evaluateExpression(irField.initializer!!.expression)
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(
functionGenerationContext
)
storeAny(initialization, address, false)
}
}
context.llvm.initializersGenerationState.threadLocalInitState?.let {
val address = it.getAddress(functionGenerationContext)
store(Int32(FILE_NOT_INITIALIZED).llvm, address)
LLVMSetInitializer(address, Int32(FILE_NOT_INITIALIZED).llvm)
}
if (!context.useLazyFileInitializers()) {
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initThreadLocalField(it) }
}
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.forEach {
if (context.shouldContainLocationDebugInfo())
debugLocation(it.startLocation!!, it.endLocation)
evaluateSimpleFunctionCall(it, emptyList(), Lifetime.IRRELEVANT)
}
ret(null)
}
@@ -466,9 +534,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
appendingTo(bbGlobalDeinit) {
context.llvm.fileInitializers
context.llvm.initializersGenerationState.topLevelFields
// Only if a subject for memory management.
.forEach {irField ->
.forEach { irField ->
if (irField.type.binaryTypeIsReference() && irField.storageKind != FieldStorageKind.THREAD_LOCAL) {
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(
functionGenerationContext
@@ -479,6 +547,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
context.llvm.globalSharedObjects.forEach { address ->
storeHeapRef(codegen.kNullObjHeaderPtr, address)
}
context.llvm.initializersGenerationState.globalInitState?.let {
store(Int32(FILE_NOT_INITIALIZED).llvm, it)
}
ret(null)
}
}
@@ -704,11 +775,51 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}.toMap()
}
private fun getGlobalInitStateFor(file: IrFile): LLVMValueRef =
context.llvm.initializersGenerationState.fileGlobalInitStates.getOrPut(file) {
codegen.addGlobal("state_global$${file.fileEntry.name}", int32Type, false).also {
LLVMSetInitializer(it, Int32(FILE_NOT_INITIALIZED).llvm)
}
}
private fun getThreadLocalInitStateFor(file: IrFile): AddressAccess =
context.llvm.initializersGenerationState.fileThreadLocalInitStates.getOrPut(file) {
codegen.addKotlinThreadLocal("state_thread_local$${file.fileEntry.name}", int32Type)
}
override fun visitFunction(declaration: IrFunction) {
context.log{"visitFunction : ${ir2string(declaration)}"}
val body = declaration.body
if (declaration.origin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER) {
require(context.llvm.initializersGenerationState.globalInitFunction == null) { "There can only be at most one global file initializer" }
require(body == null) { "The body of file initializer should be null" }
require(declaration.valueParameters.singleOrNull()?.type == context.irBuiltIns.booleanType) { "File initializer must take a single boolean parameter" }
require(declaration.returnsUnit()) { "File initializer must return Unit" }
context.llvm.initializersGenerationState.globalInitFunction = declaration
context.llvm.initializersGenerationState.globalInitState = getGlobalInitStateFor(declaration.parent as IrFile)
}
if (declaration.origin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|| declaration.origin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER) {
require(context.llvm.initializersGenerationState.threadLocalInitFunction == null) { "There can only be at most one thread local file initializer" }
require(body == null) { "The body of file initializer should be null" }
require(declaration.valueParameters.singleOrNull()?.type == context.irBuiltIns.booleanType) { "File initializer must take a single boolean parameter" }
require(declaration.returnsUnit()) { "File initializer must return Unit" }
context.llvm.initializersGenerationState.threadLocalInitFunction = declaration
context.llvm.initializersGenerationState.threadLocalInitState = getThreadLocalInitStateFor(declaration.parent as IrFile)
}
if (declaration.origin == DECLARATION_ORIGIN_MODULE_GLOBAL_INITIALIZER) {
require(declaration.valueParameters.isEmpty()) { "Module initializer must be a parameterless function" }
require(declaration.returnsUnit()) { "Module initializer must return Unit" }
context.llvm.initializersGenerationState.moduleGlobalInitializers.add(declaration)
}
if (declaration.origin == DECLARATION_ORIGIN_MODULE_THREAD_LOCAL_INITIALIZER) {
require(declaration.valueParameters.isEmpty()) { "Module initializer must be a parameterless function" }
require(declaration.returnsUnit()) { "Module initializer must return Unit" }
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.add(declaration)
}
if ((declaration as? IrSimpleFunction)?.modality == Modality.ABSTRACT
|| declaration.isExternal
|| body == null)
@@ -845,7 +956,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
// (Cannot do this before the global is initialized).
LLVMSetLinkage(globalProperty, LLVMLinkage.LLVMInternalLinkage)
}
context.llvm.fileInitializers.add(declaration)
context.llvm.initializersGenerationState.topLevelFields.add(declaration)
}
}
@@ -2155,6 +2266,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------//
private fun evaluateClassReference(classReference: IrClassReference): LLVMValueRef {
val typeInfoPtr = codegen.typeInfoValue(classReference.symbol.owner as IrClass)
return functionGenerationContext.bitcast(int8TypePtr, typeInfoPtr)
}
//-------------------------------------------------------------------------//
private fun evaluateFunctionCall(callee: IrCall, args: List<LLVMValueRef>,
resultLifetime: Lifetime): LLVMValueRef {
val function = callee.symbol.owner
@@ -2165,15 +2283,69 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return when {
function.isTypedIntrinsic -> intrinsicGenerator.evaluateCall(callee, args)
function.isBuiltInOperator -> evaluateOperatorCall(callee, argsWithContinuationIfNeeded)
function.origin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER -> evaluateFileGlobalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER -> evaluateFileThreadLocalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER -> evaluateFileStandaloneThreadLocalInitializerCall(function)
else -> evaluateSimpleFunctionCall(function, argsWithContinuationIfNeeded, resultLifetime, callee.superQualifierSymbol?.owner)
}
}
//-------------------------------------------------------------------------//
private fun evaluateFileGlobalInitializerCall(fileInitializer: IrFunction) = with(functionGenerationContext) {
val statePtr = getGlobalInitStateFor(fileInitializer.parent as IrFile)
val initializerPtr = with(codegen) { fileInitializer.llvmFunction }
private fun evaluateClassReference(classReference: IrClassReference): LLVMValueRef {
val typeInfoPtr = codegen.typeInfoValue(classReference.symbol.owner as IrClass)
return functionGenerationContext.bitcast(int8TypePtr, typeInfoPtr)
val bbInit = basicBlock("label_init", null)
val bbExit = basicBlock("label_continue", null)
// TODO: Is it ok to use non-volatile read here since once value is FILE_INITIALIZED, it is no longer change?
val state = load(statePtr)
LLVMSetVolatile(state, 1)
condBr(icmpEq(state, Int32(FILE_INITIALIZED).llvm), bbExit, bbInit)
positionAtEnd(bbInit)
call(context.llvm.callInitGlobalPossiblyLock, listOf(statePtr, initializerPtr),
exceptionHandler = currentCodeContext.exceptionHandler)
br(bbExit)
positionAtEnd(bbExit)
codegen.theUnitInstanceRef.llvm
}
private fun evaluateFileThreadLocalInitializerCall(fileInitializer: IrFunction) = with(functionGenerationContext) {
val globalStatePtr = getGlobalInitStateFor(fileInitializer.parent as IrFile)
val localState = getThreadLocalInitStateFor(fileInitializer.parent as IrFile)
val localStatePtr = localState.getAddress(functionGenerationContext)
val initializerPtr = with(codegen) { fileInitializer.llvmFunction }
val bbInit = basicBlock("label_init", null)
val bbCheckLocalState = basicBlock("label_check_local", null)
val bbExit = basicBlock("label_continue", null)
val globalState = load(globalStatePtr)
LLVMSetVolatile(globalState, 1)
// Make sure we're not in the middle of global initializer invocation -
// thread locals can be initialized only after all shared globals have been initialized.
condBr(icmpNe(globalState, Int32(FILE_INITIALIZED).llvm), bbExit, bbCheckLocalState)
positionAtEnd(bbCheckLocalState)
condBr(icmpNe(load(localStatePtr), Int32(FILE_INITIALIZED).llvm), bbInit, bbExit)
positionAtEnd(bbInit)
call(context.llvm.callInitThreadLocal, listOf(globalStatePtr, localStatePtr, initializerPtr),
exceptionHandler = currentCodeContext.exceptionHandler)
br(bbExit)
positionAtEnd(bbExit)
codegen.theUnitInstanceRef.llvm
}
private fun evaluateFileStandaloneThreadLocalInitializerCall(fileInitializer: IrFunction) = with(functionGenerationContext) {
val state = getThreadLocalInitStateFor(fileInitializer.parent as IrFile)
val statePtr = state.getAddress(functionGenerationContext)
val initializerPtr = with(codegen) { fileInitializer.llvmFunction }
val bbInit = basicBlock("label_init", null)
val bbExit = basicBlock("label_continue", null)
condBr(icmpEq(load(statePtr), Int32(FILE_INITIALIZED).llvm), bbExit, bbInit)
positionAtEnd(bbInit)
call(context.llvm.callInitThreadLocal, listOf(kNullInt32Ptr, statePtr, initializerPtr),
exceptionHandler = currentCodeContext.exceptionHandler)
br(bbExit)
positionAtEnd(bbExit)
codegen.theUnitInstanceRef.llvm
}
//-------------------------------------------------------------------------//
@@ -182,6 +182,8 @@ internal val kBoolean get() = kInt1
internal val kInt8Ptr get() = pointerType(int8Type)
internal val kInt8PtrPtr get() = pointerType(kInt8Ptr)
internal val kNullInt8Ptr get() = LLVMConstNull(kInt8Ptr)!!
internal val kInt32Ptr get() = pointerType(int32Type)
internal val kNullInt32Ptr get() = LLVMConstNull(kInt32Ptr)!!
internal val kImmInt32Zero get() = Int32(0).llvm
internal val kImmInt32One get() = Int32(1).llvm
internal val ContextUtils.kNullObjHeaderPtr: LLVMValueRef
@@ -29,6 +29,8 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
internal object DECLARATION_ORIGIN_KPROPERTIES_FOR_DELEGATION : IrDeclarationOriginImpl("KPROPERTIES_FOR_DELEGATION")
internal class PropertyDelegationLowering(val context: Context) : FileLoweringPass {
private var tempIndex = 0
@@ -161,11 +163,10 @@ internal class PropertyDelegationLowering(val context: Context) : FileLoweringPa
if (kProperties.isNotEmpty()) {
val initializers = kProperties.values.sortedBy { it.second }.map { it.first }
// TODO: move to object for lazy initialization.
irFile.declarations.add(0, kPropertiesField.apply {
initializer = IrExpressionBodyImpl(startOffset, endOffset,
context.createArrayOfExpression(startOffset, endOffset, kPropertyImplType, initializers))
})
// TODO: replace with static initialization.
kPropertiesField.initializer = IrExpressionBodyImpl(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET,
context.createArrayOfExpression(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, kPropertyImplType, initializers))
irFile.declarations.add(0, kPropertiesField)
}
}
@@ -291,7 +292,4 @@ internal class PropertyDelegationLowering(val context: Context) : FileLoweringPa
}
return type.classifier == expectedClass
}
private object DECLARATION_ORIGIN_KPROPERTIES_FOR_DELEGATION :
IrDeclarationOriginImpl("KPROPERTIES_FOR_DELEGATION")
}
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.llvm.FieldStorageKind
import org.jetbrains.kotlin.backend.konan.llvm.storageKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.setDeclarationsParent
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
internal object DECLARATION_ORIGIN_MODULE_GLOBAL_INITIALIZER : IrDeclarationOriginImpl("MODULE_GLOBAL_INITIALIZER")
internal object DECLARATION_ORIGIN_MODULE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("MODULE_THREAD_LOCAL_INITIALIZER")
internal object DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER : IrDeclarationOriginImpl("FILE_GLOBAL_INITIALIZER")
internal object DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("FILE_THREAD_LOCAL_INITIALIZER")
internal object DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("FILE_STANDALONE_THREAD_LOCAL_INITIALIZER")
internal fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
irCall(initializer).apply { putValueArgument(0, irFalse()) }
// TODO: ExplicitlyExported for IR proto are not longer needed.
internal class FileInitializersLowering(val context: Context) : FileLoweringPass {
override fun lower(irFile: IrFile) {
var requireGlobalInitializer = false
var requireThreadLocalInitializer = false
var kPropertiesField: IrField? = null
for (declaration in irFile.declarations) {
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
if (irField == null || !irField.hasNonConstInitializer) continue
when {
irField.origin == DECLARATION_ORIGIN_KPROPERTIES_FOR_DELEGATION -> {
require(kPropertiesField == null) { "Expected at most one kProperties field" }
kPropertiesField = irField
}
irField.storageKind == FieldStorageKind.SHARED_FROZEN -> requireGlobalInitializer = true
else -> requireThreadLocalInitializer = true // Either marked with thread local or only main thread visible.
}
}
// TODO: think about pure initializers.
if (!requireGlobalInitializer && !requireThreadLocalInitializer) {
kPropertiesField?.let { transformKPropertiesInitializerToModuleInitializer(irFile, it) }
return
}
kPropertiesField?.let { requireGlobalInitializer = true }
val globalInitFunction =
if (requireGlobalInitializer)
buildInitFileFunction(irFile, "\$init_global", DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER)
else null
val threadLocalInitFunction =
if (requireThreadLocalInitializer)
buildInitFileFunction(irFile, "\$init_thread_local",
if (requireGlobalInitializer)
DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
else DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
)
else null
irFile.transformChildrenVoid(object: IrElementTransformerVoid() {
override fun visitFunction(declaration: IrFunction): IrStatement {
declaration.transformChildrenVoid(this)
val body = declaration.body ?: return declaration
val statements = (body as IrBlockBody).statements
context.createIrBuilder(declaration.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).run {
// The order of calling initializers: first global, then thread-local.
// It is ok for a thread local top level property to reference a global, but not vice versa.
threadLocalInitFunction?.let { statements.add(0, irCallFileInitializer(it.symbol)) }
globalInitFunction?.let { statements.add(0, irCallFileInitializer(it.symbol)) }
}
return declaration
}
})
}
private fun transformKPropertiesInitializerToModuleInitializer(irFile: IrFile, irField: IrField) {
val initializer = context.irFactory.buildFun {
startOffset = irField.startOffset
endOffset = irField.endOffset
origin = DECLARATION_ORIGIN_MODULE_GLOBAL_INITIALIZER
name = Name.identifier("\$kProperties_init")
visibility = DescriptorVisibilities.PRIVATE
returnType = context.irBuiltIns.unitType
}.apply {
val function = this
parent = irFile
body = context.createIrBuilder(symbol, startOffset, endOffset).irBlockBody {
+irSetField(null, irField, irField.initializer!!.expression.also { it.setDeclarationsParent(function) })
}
}
irField.initializer = null
irFile.declarations.add(initializer)
}
private fun buildInitFileFunction(irFile: IrFile, name: String, origin: IrDeclarationOrigin) = context.irFactory.buildFun {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
this.origin = origin
this.name = Name.identifier(name)
visibility = DescriptorVisibilities.PRIVATE
returnType = context.irBuiltIns.unitType
}.apply {
parent = irFile
addValueParameter("isMainThread", context.irBuiltIns.booleanType)
irFile.declarations.add(0, this)
}
private val IrField.hasNonConstInitializer: Boolean
get() = initializer.let { it != null && it !is IrConst<*> }
}
@@ -22,6 +22,28 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
internal class ExpressionBodyTransformer(val context: Context) : FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunction(declaration: IrFunction) {
declaration.acceptChildrenVoid(this)
context.createIrBuilder(declaration.symbol, declaration.endOffset, declaration.endOffset).run {
val body = declaration.body
if (body is IrExpressionBody)
declaration.body = IrBlockBodyImpl(body.startOffset, body.endOffset) {
statements += irReturn(body.expression)
}
}
}
})
}
}
internal class ReturnsInsertionLowering(val context: Context) : FileLoweringPass {
private val symbols = context.ir.symbols
@@ -34,24 +56,17 @@ internal class ReturnsInsertionLowering(val context: Context) : FileLoweringPass
override fun visitFunction(declaration: IrFunction) {
declaration.acceptChildrenVoid(this)
val body = declaration.body ?: return
body as IrBlockBody
context.createIrBuilder(declaration.symbol, declaration.endOffset, declaration.endOffset).run {
when (val body = declaration.body) {
is IrExpressionBody -> {
declaration.body = IrBlockBodyImpl(body.startOffset, body.endOffset) {
statements += irReturn(body.expression)
}
}
is IrBlockBody -> {
if (declaration is IrConstructor || declaration.returnType == context.irBuiltIns.unitType) {
body.statements += irReturn(irGetObject(symbols.unit))
} else if (declaration.returnType.isSimpleTypeWithQuestionMark) {
// this is a workaround for KT-42832
val typeOperatorCall = body.statements.lastOrNull() as? IrTypeOperatorCall
if (typeOperatorCall?.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT
&& typeOperatorCall.argument.type.isNullableNothing()) {
body.statements[body.statements.lastIndex] = irReturn(typeOperatorCall.argument)
}
}
if (declaration is IrConstructor || declaration.returnType == context.irBuiltIns.unitType) {
body.statements += irReturn(irGetObject(symbols.unit))
} else if (declaration.returnType.isSimpleTypeWithQuestionMark) {
// this is a workaround for KT-42832
val typeOperatorCall = body.statements.lastOrNull() as? IrTypeOperatorCall
if (typeOperatorCall?.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT
&& typeOperatorCall.argument.type.isNullableNothing()) {
body.statements[body.statements.lastIndex] = irReturn(typeOperatorCall.argument)
}
}
}
@@ -5,7 +5,10 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.ir.addFakeOverrides
import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter
import org.jetbrains.kotlin.backend.common.ir.createParameterDeclarations
import org.jetbrains.kotlin.backend.common.ir.simpleFunctions
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.reportWarning
import org.jetbrains.kotlin.backend.konan.Context
@@ -15,21 +18,28 @@ import org.jetbrains.kotlin.backend.konan.getIncludedLibraryDescriptors
import org.jetbrains.kotlin.backend.konan.ir.typeWithStarProjections
import org.jetbrains.kotlin.backend.konan.ir.typeWithoutArguments
import org.jetbrains.kotlin.backend.konan.reportCompilationError
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrConstructorSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.addChild
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
@@ -527,12 +537,8 @@ internal class TestProcessor (val context: Context) {
with(buildClassSuite(testClass.ownerClass, testClass.companion,testClass.functions)) {
irFile.addChild(this)
val irConstructor = constructors.single()
context.createIrBuilder(irFile.symbol, testClass.ownerClass.startOffset, testClass.ownerClass.endOffset).run {
irFile.addTopLevelInitializer(
irCall(irConstructor),
this@TestProcessor.context,
threadLocal = true)
}
val irBuilder = context.createIrBuilder(irFile.symbol, testClass.ownerClass.startOffset, testClass.ownerClass.endOffset)
irBuilder.irCall(irConstructor)
}
/** Check if this fqName already used or not. */
@@ -564,36 +570,50 @@ internal class TestProcessor (val context: Context) {
&& it.valueParameters[2].type.isBoolean()
}
private fun generateTopLevelSuite(irFile: IrFile, functions: Collection<TestFunction>) {
val builder = context.createIrBuilder(irFile.symbol)
private fun generateTopLevelSuite(irFile: IrFile, functions: Collection<TestFunction>): IrExpression? {
val irBuilder = context.createIrBuilder(irFile.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
val suiteName = irFile.topLevelSuiteName
if (!checkSuiteName(irFile, suiteName)) {
return
return null
}
// TODO: an awful hack, we make this initializer thread local, so that it doesn't freeze suite,
// and later on we could modify some suite's properties. This shall be redesigned.
irFile.addTopLevelInitializer(builder.irBlock {
return irBuilder.irBlock {
val constructorCall = irCall(topLevelSuiteConstructor).apply {
putValueArgument(0, IrConstImpl.string(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET,
context.irBuiltIns.stringType, suiteName))
putValueArgument(0, irString(suiteName))
}
val testSuiteVal = irTemporary(constructorCall, "topLevelTestSuite")
val testSuiteVal = irTemporary(constructorCall, "topLevelTestSuite")
generateFunctionRegistration(testSuiteVal,
topLevelSuiteRegisterTestCase,
topLevelSuiteRegisterFunction,
functions)
}, context, threadLocal = true)
}
}
private fun createTestSuites(irFile: IrFile, annotationCollector: AnnotationCollector) {
val statements = mutableListOf<IrExpression>()
annotationCollector.testClasses.filter {
it.value.functions.any { it.kind == FunctionKind.TEST }
}.forEach { _, testClass ->
generateClassSuite(irFile, testClass)
statements.add(generateClassSuite(irFile, testClass))
}
if (annotationCollector.topLevelFunctions.isNotEmpty()) {
generateTopLevelSuite(irFile, annotationCollector.topLevelFunctions)
generateTopLevelSuite(irFile, annotationCollector.topLevelFunctions)?.let { statements.add(it) }
}
if (statements.isNotEmpty()) {
context.irFactory.buildFun {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
origin = DECLARATION_ORIGIN_MODULE_THREAD_LOCAL_INITIALIZER
name = Name.identifier("\$createTestSuites")
visibility = DescriptorVisibilities.PRIVATE
returnType = context.irBuiltIns.unitType
}.apply {
parent = irFile
irFile.declarations.add(this)
statements.forEach { it.accept(SetDeclarationsParentVisitor, this) }
body = IrBlockBodyImpl(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, statements)
}
}
}
// endregion
@@ -16,6 +16,8 @@ import org.jetbrains.kotlin.backend.konan.llvm.computeSymbolName
import org.jetbrains.kotlin.backend.konan.llvm.isExported
import org.jetbrains.kotlin.backend.konan.llvm.localHash
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_BRIDGE_METHOD
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_MODULE_GLOBAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_MODULE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -113,18 +115,20 @@ internal object DataFlowIR {
}
object FunctionAttributes {
val IS_GLOBAL_INITIALIZER = 1
val RETURNS_UNIT = 2
val RETURNS_NOTHING = 4
val EXPLICITLY_EXPORTED = 8
val IS_TOP_LEVEL_FIELD_INITIALIZER = 1
val IS_GLOBAL_INITIALIZER = 2
val RETURNS_UNIT = 4
val RETURNS_NOTHING = 8
val EXPLICITLY_EXPORTED = 16
}
class FunctionParameter(val type: Type, val boxFunction: FunctionSymbol?, val unboxFunction: FunctionSymbol?)
abstract class FunctionSymbol(val attributes: Int, val irFunction: IrFunction?, val name: String?) {
abstract class FunctionSymbol(val attributes: Int, val irFile: IrFile?, val irFunction: IrFunction?, val name: String?) {
lateinit var parameters: Array<FunctionParameter>
lateinit var returnParameter: FunctionParameter
val isTopLevelFieldInitializer = attributes.and(FunctionAttributes.IS_TOP_LEVEL_FIELD_INITIALIZER) != 0
val isGlobalInitializer = attributes.and(FunctionAttributes.IS_GLOBAL_INITIALIZER) != 0
val returnsUnit = attributes.and(FunctionAttributes.RETURNS_UNIT) != 0
val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0
@@ -133,8 +137,8 @@ internal object DataFlowIR {
var escapes: Int? = null
var pointsTo: IntArray? = null
class External(val hash: Long, attributes: Int, irFunction: IrFunction?, name: String? = null, val isExported: Boolean)
: FunctionSymbol(attributes, irFunction, name) {
class External(val hash: Long, attributes: Int, irFile: IrFile?, irFunction: IrFunction?, name: String? = null, val isExported: Boolean)
: FunctionSymbol(attributes, irFile, irFunction, name) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -153,14 +157,14 @@ internal object DataFlowIR {
}
abstract class Declared(val module: Module, val symbolTableIndex: Int,
attributes: Int, irFunction: IrFunction?, var bridgeTarget: FunctionSymbol?, name: String?)
: FunctionSymbol(attributes, irFunction, name) {
attributes: Int, irFile: IrFile?, irFunction: IrFunction?, var bridgeTarget: FunctionSymbol?, name: String?)
: FunctionSymbol(attributes, irFile, irFunction, name) {
}
class Public(val hash: Long, module: Module, symbolTableIndex: Int,
attributes: Int, irFunction: IrFunction?, bridgeTarget: FunctionSymbol?, name: String? = null)
: Declared(module, symbolTableIndex, attributes, irFunction, bridgeTarget, name) {
attributes: Int, irFile: IrFile?, irFunction: IrFunction?, bridgeTarget: FunctionSymbol?, name: String? = null)
: Declared(module, symbolTableIndex, attributes, irFile, irFunction, bridgeTarget, name) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -179,8 +183,8 @@ internal object DataFlowIR {
}
class Private(val index: Int, module: Module, symbolTableIndex: Int,
attributes: Int, irFunction: IrFunction?, bridgeTarget: FunctionSymbol?, name: String? = null)
: Declared(module, symbolTableIndex, attributes, irFunction, bridgeTarget, name) {
attributes: Int, irFile: IrFile?, irFunction: IrFunction?, bridgeTarget: FunctionSymbol?, name: String? = null)
: Declared(module, symbolTableIndex, attributes, irFile, irFunction, bridgeTarget, name) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -580,6 +584,9 @@ internal object DataFlowIR {
|| it.hasAnnotation(RuntimeNames.objCMethodImp)) {
attributes = attributes or FunctionAttributes.EXPLICITLY_EXPORTED
}
if (it.origin == DECLARATION_ORIGIN_MODULE_GLOBAL_INITIALIZER
|| it.origin == DECLARATION_ORIGIN_MODULE_THREAD_LOCAL_INITIALIZER)
attributes = attributes or FunctionAttributes.IS_GLOBAL_INITIALIZER
val symbol = when {
it.isExternal || it.isBuiltInOperator -> {
val escapesAnnotation = it.annotations.findAnnotation(FQ_NAME_ESCAPES)
@@ -588,7 +595,7 @@ internal object DataFlowIR {
val escapesBitMask = (escapesAnnotation?.getValueArgument(0) as? IrConst<Int>)?.value
@Suppress("UNCHECKED_CAST")
val pointsToBitMask = (pointsToAnnotation?.getValueArgument(0) as? IrVararg)?.elements?.map { (it as IrConst<Int>).value }
FunctionSymbol.External(name.localHash.value, attributes, it, takeName { name }, it.isExported()).apply {
FunctionSymbol.External(name.localHash.value, attributes, it.fileOrNull, it, takeName { name }, it.isExported()).apply {
escapes = escapesBitMask
pointsTo = pointsToBitMask?.toIntArray()
}
@@ -608,9 +615,9 @@ internal object DataFlowIR {
val symbolTableIndex = if (placeToFunctionsTable) module.numberOfFunctions++ else -1
val frozen = it is IrConstructor && irClass!!.annotations.findAnnotation(KonanFqNames.frozen) != null
val functionSymbol = if (it.isExported())
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it.fileOrNull, it, bridgeTargetSymbol, takeName { name })
else
FunctionSymbol.Private(privateFunIndex++, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
FunctionSymbol.Private(privateFunIndex++, module, symbolTableIndex, attributes, it.fileOrNull, it, bridgeTargetSymbol, takeName { name })
if (frozen) {
functionSymbol.escapes = 0b1 // Assume instances of frozen classes escape.
}
@@ -639,8 +646,8 @@ internal object DataFlowIR {
functionMap[irField]?.let { return it }
assert(irField.parent !is IrClass) { "All local properties initializers should've been lowered" }
val attributes = FunctionAttributes.IS_GLOBAL_INITIALIZER or FunctionAttributes.RETURNS_UNIT
val symbol = FunctionSymbol.Private(privateFunIndex++, module, -1, attributes, null, null, takeName { "${irField.computeSymbolName()}_init" })
val attributes = FunctionAttributes.IS_TOP_LEVEL_FIELD_INITIALIZER or FunctionAttributes.RETURNS_UNIT
val symbol = FunctionSymbol.Private(privateFunIndex++, module, -1, attributes, irField.fileOrNull, null, null, takeName { "${irField.computeSymbolName()}_init" })
functionMap[irField] = symbol
@@ -84,9 +84,10 @@ internal object Devirtualization {
.filter { moduleDFG.functions.containsKey(it) }
}
// TODO: Are globals initializers always called whether they are actually reachable from roots or not?
// TODO: With the changed semantics of global initializers this is no longer the case - rework.
val globalInitializers =
moduleDFG.symbolTable.functionMap.values.filter { it.isGlobalInitializer } +
externalModulesDFG.functionDFGs.keys.filter { it.isGlobalInitializer }
moduleDFG.symbolTable.functionMap.values.filter { it.isTopLevelFieldInitializer || it.isGlobalInitializer } +
externalModulesDFG.functionDFGs.keys.filter { it.isTopLevelFieldInitializer || it.isGlobalInitializer }
val explicitlyExportedFunctions =
moduleDFG.symbolTable.functionMap.values.filter { it.explicitlyExported } +
@@ -1377,6 +1377,53 @@ task initializers_correctOrder2(type: KonanLocalTest) {
source = "codegen/initializers/correctOrder2.kt"
}
standaloneTest("initializers_correctOrder3") {
source = "codegen/initializers/correctOrder3.kt"
goldValue = "3\n"
}
standaloneTest("initializers_globalInitedAfterAccessingFile") {
source = "codegen/initializers/globalInitedAfterAccessingFile.kt"
}
standaloneTest("initializers_globalInitedBeforeThreadLocal") {
source = "codegen/initializers/globalInitedBeforeThreadLocal.kt"
}
standaloneTest("initializers_workers1") {
source = "codegen/initializers/workers1.kt"
goldValue = "42\n3\n"
}
standaloneTest("initializers_workers2") {
source = "codegen/initializers/workers2.kt"
goldValue = "42\n42\n"
}
standaloneTest("initializers_failInInitializer1") {
source = "codegen/initializers/failInInitializer1.kt"
goldValue = "caught\n"
flags = ['-Xir-property-lazy-initialization']
}
standaloneTest("initializers_failInInitializer2") {
source = "codegen/initializers/failInInitializer2.kt"
goldValue = "caught\ncaught2\n"
flags = ['-Xir-property-lazy-initialization']
}
standaloneTest("initializers_failInInitializer3") {
source = "codegen/initializers/failInInitializer3.kt"
goldValue = "caught\ncaught2\n"
flags = ['-Xir-property-lazy-initialization']
}
standaloneTest("initializers_failInInitializer4") {
source = "codegen/initializers/failInInitializer4.kt"
goldValue = "caught\ncaught2\n"
flags = ['-Xir-property-lazy-initialization']
}
linkTest("initializers_linkTest1") {
goldValue = "1200\n"
source = "codegen/initializers/linkTest1_main.kt"
@@ -2152,7 +2199,10 @@ standaloneTest("link_testLib_explicitly") {
enabled = project.target.name == project.hostName
dependsOn installTestLib
goldValue = "This is a side effect of a test library linked into the binary.\nYou should not be seeing this.\n\nHello\n"
if (project.globalTestArgs.contains('-Xir-property-lazy-initialization'))
goldValue = "Hello\n"
else
goldValue = "This is a side effect of a test library linked into the binary.\nYou should not be seeing this.\n\nHello\n"
source = "link/omit/hello.kt"
// We force library inclusion, so need to see the warning banner.
flags = ['-l', 'testLibrary', '-r', testLibraryDir]
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
// FILE: lib.kt
class X(val s: String)
val x = X("zzz")
// FILE: lib2.kt
class Z(val x: Int)
val z2 = Z(x.s.length)
// FILE: main.kt
fun main() {
println(z2.x)
}
@@ -0,0 +1,18 @@
/*
* 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.
*/
// FILE: lib.kt
val x: String = computeX()
fun computeX(): String = error("zzz")
// FILE: main.kt
fun main() {
try {
println(x)
} catch(t: IllegalStateException) {
println("caught")
}
}
@@ -0,0 +1,28 @@
/*
* 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.
*/
// FILE: lib.kt
val x: String = computeX()
fun computeX(): String = error("zzz")
val y: String = computeY()
fun computeY(): String = "qzz"
// FILE: main.kt
@OptIn(ExperimentalStdlibApi::class)
fun main() {
try {
println(x)
} catch(t: IllegalStateException) {
println("caught")
}
try {
println(y)
} catch(t: kotlin.native.FileFailedToInitializeException) {
println("caught2")
}
}
@@ -0,0 +1,31 @@
/*
* 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.
*/
// FILE: lib.kt
import kotlin.native.concurrent.*
@ThreadLocal
val x: String = computeX()
fun computeX(): String = error("zzz")
val y: String = computeY()
fun computeY(): String = "qzz"
// FILE: main.kt
@OptIn(ExperimentalStdlibApi::class)
fun main() {
try {
println(x)
} catch(t: IllegalStateException) {
println("caught")
}
try {
println(y)
} catch(t: kotlin.native.FileFailedToInitializeException) {
println("caught2")
}
}
@@ -0,0 +1,32 @@
/*
* 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.
*/
// FILE: lib.kt
import kotlin.native.concurrent.*
@ThreadLocal
val x: String = computeX()
fun computeX(): String = error("zzz")
@ThreadLocal
val y: String = computeY()
fun computeY(): String = "qzz"
// FILE: main.kt
@OptIn(ExperimentalStdlibApi::class)
fun main() {
try {
println(x)
} catch(t: IllegalStateException) {
println("caught")
}
try {
println(y)
} catch(t: kotlin.native.FileFailedToInitializeException) {
println("caught2")
}
}
@@ -0,0 +1,27 @@
/*
* 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.
*/
// FILE: lib.kt
var z = false
// FILE: lib2.kt
import kotlin.test.*
val x = foo()
private fun foo(): Int {
z = true
return 42
}
fun bar() = 117
// FILE: main.kt
import kotlin.test.*
fun main() {
bar()
assertTrue(z)
}
@@ -0,0 +1,26 @@
/*
* 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.
*/
// FILE: lib.kt
import kotlin.test.*
val x = foo()
@ThreadLocal
val y = bar()
private fun foo() = 42
private fun bar(): Int {
assertEquals(x, 42)
return 117
}
// FILE: main.kt
import kotlin.test.*
fun main() {
assertEquals(y, 117)
}
@@ -0,0 +1,34 @@
/*
* 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.
*/
// FILE: lib.kt
class X(val s: String)
val x = X("zzz")
// FILE: lib2.kt
import kotlin.native.concurrent.*
class Z(val x: Int)
@SharedImmutable
val z1 = Z(42)
val z2 = Z(x.s.length)
// FILE: main.kt
import kotlin.native.concurrent.*
fun foo() {
val worker = Worker.start()
worker.execute(TransferMode.SAFE, { -> }, {
it -> println(z1.x)
}).consume { }
}
fun main() {
foo()
println(z2.x)
}
@@ -0,0 +1,23 @@
/*
* 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.
*/
// FILE: lib.kt
import kotlin.native.concurrent.*
class Z(val x: Int)
@ThreadLocal
val z = Z(42)
// FILE: main.kt
import kotlin.native.concurrent.*
fun main() {
println(z.x)
val worker = Worker.start()
worker.execute(TransferMode.SAFE, { -> }, {
it -> println(z.x)
}).consume { }
}
@@ -58,6 +58,7 @@ void RUNTIME_NORETURN ThrowIllegalArgumentException();
void RUNTIME_NORETURN ThrowIllegalStateException();
void RUNTIME_NORETURN ThrowInvalidMutabilityException(KConstRef where);
void RUNTIME_NORETURN ThrowIncorrectDereferenceException();
void RUNTIME_NORETURN ThrowFileFailedToInitializeException();
void RUNTIME_NORETURN ThrowIllegalObjectSharingException(KConstNativePtr typeInfo, KConstNativePtr address);
void RUNTIME_NORETURN ThrowFreezingException(KRef toFreeze, KRef blocker);
// Prints out message of Throwable.
@@ -243,6 +243,39 @@ void onThreadExit(void (*destructor)(void*), void* destructorParameter) {
#endif // !KONAN_NO_THREADS
}
#if KONAN_LINUX
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#endif
NO_EXTERNAL_CALLS_CHECK int currentThreadId() {
#if KONAN_NO_THREADS
#if KONAN_WASM || KONAN_ZEPHYR
// No way to do that.
return 0;
#else
#error "How to find currentThreadId()?"
#endif
#else // !KONAN_NO_THREADS
#if defined(KONAN_OSX) or defined(KONAN_IOS) or defined(KONAN_TVOS) or defined(KONAN_WATCHOS)
uint64_t tid;
pthread_t self = pthread_self();
RuntimeCheck(!pthread_threadid_np(self, &tid), "Error getting thread id");
RuntimeCheck((*(reinterpret_cast<int32_t*>(&tid) + 1)) == 0, "Thread id is not a uint32");
return tid;
#elif KONAN_ANDROID
return gettid();
#elif KONAN_LINUX
return syscall(__NR_gettid);
#elif KONAN_WINDOWS
return GetCurrentThreadId();
#else
#error "How to find currentThreadId()?"
#endif
#endif // !KONAN_NO_THREADS
}
// Process execution.
void abort(void) {
::abort();
@@ -42,6 +42,7 @@ RUNTIME_NORETURN void exit(int32_t status);
// Thread control.
void onThreadExit(void (*destructor)(void*), void* destructorParameter);
bool isOnThreadExitNotSetOrAlreadyStarted();
int currentThreadId();
// String/byte operations.
// memcpy/memmove/memcmp are not here intentionally, as frequently implemented/optimized
@@ -73,6 +73,8 @@ constexpr RuntimeState* kInvalidRuntime = nullptr;
THREAD_LOCAL_VARIABLE RuntimeState* runtimeState = kInvalidRuntime;
volatile int mainThreadId = 0;
inline bool isValidRuntime() {
return ::runtimeState != kInvalidRuntime;
}
@@ -135,6 +137,7 @@ RuntimeState* initRuntime() {
CommitTLSStorage(result->memoryState);
// Keep global variables in state as well.
if (firstRuntime) {
mainThreadId = konan::currentThreadId();
konan::consoleInit();
#if KONAN_OBJC_INTEROP
Kotlin_ObjCExport_initialize();
@@ -410,4 +413,55 @@ RUNTIME_NOTHROW void Kotlin_initRuntimeIfNeededFromKotlin() {
}
}
static constexpr int FILE_NOT_INITIALIZED = 0;
static constexpr int FILE_BEING_INITIALIZED = 1;
static constexpr int FILE_INITIALIZED = 2;
static constexpr int FILE_FAILED_TO_INITIALIZE = 3;
void CallInitGlobalPossiblyLock(int volatile* state, void (*init)(bool)) {
int localState = *state;
if (localState == FILE_INITIALIZED) return;
if (localState == FILE_FAILED_TO_INITIALIZE)
ThrowFileFailedToInitializeException();
int threadId = konan::currentThreadId();
if ((localState & 3) == FILE_BEING_INITIALIZED) {
if ((localState & ~3) != (threadId << 2)) {
do {
localState = *state;
if (localState == FILE_FAILED_TO_INITIALIZE)
ThrowFileFailedToInitializeException();
} while (localState != FILE_INITIALIZED);
}
return;
}
if (compareAndSwap(state, FILE_NOT_INITIALIZED, FILE_BEING_INITIALIZED | (threadId << 2)) == FILE_NOT_INITIALIZED) {
// actual initialization
try {
init(threadId == mainThreadId);
} catch (...) {
*state = FILE_FAILED_TO_INITIALIZE;
throw;
}
*state = FILE_INITIALIZED;
} else {
do {
localState = *state;
if (localState == FILE_FAILED_TO_INITIALIZE)
ThrowFileFailedToInitializeException();
} while (localState != FILE_INITIALIZED);
}
}
void CallInitThreadLocal(int volatile* globalState, int* localState, void (*init)(bool)) {
if (*localState == FILE_FAILED_TO_INITIALIZE || (globalState != nullptr && *globalState == FILE_FAILED_TO_INITIALIZE))
ThrowFileFailedToInitializeException();
*localState = FILE_INITIALIZED;
try {
init(konan::currentThreadId() == mainThreadId);
} catch(...) {
*localState = FILE_FAILED_TO_INITIALIZE;
throw;
}
}
} // extern "C"
@@ -38,6 +38,9 @@ void Kotlin_shutdownRuntime();
// Appends given node to an initializer list.
void AppendToInitializersTail(struct InitNode*);
void CallInitGlobalPossiblyLock(int volatile* state, void (*init)(bool));
void CallInitThreadLocal(int volatile* globalState, int* localState, void (*init)(bool));
bool Kotlin_memoryLeakCheckerEnabled();
bool Kotlin_cleanersLeakCheckerEnabled();
@@ -31,6 +31,16 @@ public class IncorrectDereferenceException : RuntimeException {
constructor(message: String) : super(message)
}
/**
* Exception thrown when there was an error during file initalization.
*/
@ExperimentalStdlibApi
public class FileFailedToInitializeException : RuntimeException {
constructor() : super()
constructor(message: String) : super(message)
}
/**
* Typealias describing custom exception reporting hook.
*/
@@ -106,6 +106,12 @@ internal fun ThrowIncorrectDereferenceException() {
"Trying to access top level value not marked as @ThreadLocal or @SharedImmutable from non-main thread")
}
@ExportForCppRuntime
@OptIn(ExperimentalStdlibApi::class)
internal fun ThrowFileFailedToInitializeException() {
throw FileFailedToInitializeException("There was an error during file initialization")
}
@ExportForCppRuntime
internal fun PrintThrowable(throwable: Throwable) {
println(throwable)
@@ -56,6 +56,7 @@ extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = {
"__cxa_begin_catch",
"__cxa_end_catch",
"__cxa_throw",
"__cxa_rethrow",
"__memset_chk",
"abort",
@@ -177,6 +177,10 @@ void RUNTIME_NORETURN ThrowIncorrectDereferenceException() {
throw std::runtime_error("Not implemented for tests");
}
void RUNTIME_NORETURN ThrowFileFailedToInitializeException() {
throw std::runtime_error("Not implemented for tests");
}
void RUNTIME_NORETURN ThrowIllegalObjectSharingException(KConstNativePtr typeInfo, KConstNativePtr address) {
throw std::runtime_error("Not implemented for tests");
}