diff --git a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt index a5267d9afb3..754ba6a3f6b 100644 --- a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt +++ b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt @@ -336,6 +336,7 @@ class K2Native : CLICompiler() { RuntimeAssertsMode.IGNORE } }) + put(PROPERTY_LAZY_INITIALIZATION, arguments.propertyLazyInitialization) } } } diff --git a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt index e7972d0a395..5900a617a7a 100644 --- a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt +++ b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt @@ -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}", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt index d91bbe46f64..f9e70b3047d 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt @@ -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 ) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index a9264bbcf6f..a25b91e0542 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -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 diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index e3e0ea96258..3211085a6cf 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -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) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt index 52e025e704e..218e0ac3746 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt @@ -162,6 +162,8 @@ class KonanConfigKeys { val GARBAGE_COLLECTOR_AGRESSIVE: CompilerConfigurationKey = CompilerConfigurationKey.create("turn on agressive GC mode") val CHECK_LLD_COMPATIBILITY: CompilerConfigurationKey = CompilerConfigurationKey.create("check compatibility with LLD") val RUNTIME_ASSERTS_MODE: CompilerConfigurationKey = CompilerConfigurationKey.create("enable runtime asserts") + val PROPERTY_LAZY_INITIALIZATION: CompilerConfigurationKey + = CompilerConfigurationKey.create("lazy top level properties initialization") } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt index 94c2fa0060d..002c6d58dcf 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt @@ -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", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 1e70c36db5e..c221d868336 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -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) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt index 4a620c0c162..ad4c9539131 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt @@ -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() 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() + 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", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 9ff4a8dd14b..69a1794869c 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -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() + val fileThreadLocalInitStates = mutableMapOf() + + val topLevelFields = mutableListOf() + val moduleThreadLocalInitializers = mutableListOf() + val moduleGlobalInitializers = mutableListOf() + 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() val irStaticInitializers = mutableListOf() val otherStaticInitializers = mutableListOf() - val fileInitializers = mutableListOf() var fileUsesThreadLocalObjects = false val globalSharedObjects = mutableSetOf() + val initializersGenerationState = InitializersGenerationState() private object lazyRtFunction { operator fun provideDelegate( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index a19e52bae3d..6243758af00 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -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?) { + 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 - 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 + .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 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, resultLifetime: Lifetime): LLVMValueRef { val function = callee.symbol.owner @@ -2165,15 +2283,69 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map 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 } //-------------------------------------------------------------------------// diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 197c1db79ff..0ba54a6d4e4 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -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 diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DelegationLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DelegationLowering.kt index d34149ac253..0bf43d188ca 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DelegationLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DelegationLowering.kt @@ -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") } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FileInitializersLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FileInitializersLowering.kt new file mode 100644 index 00000000000..07d661ea86b --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FileInitializersLowering.kt @@ -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<*> } +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ReturnsInsertionLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ReturnsInsertionLowering.kt index ba1f8750270..4f3f9d66b23 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ReturnsInsertionLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ReturnsInsertionLowering.kt @@ -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) } } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt index 11fea45b4eb..f0adb235c55 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt @@ -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) { - val builder = context.createIrBuilder(irFile.symbol) + private fun generateTopLevelSuite(irFile: IrFile, functions: Collection): 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() 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 diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt index 4a82d86575c..b2d115c0253 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -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 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)?.value @Suppress("UNCHECKED_CAST") val pointsToBitMask = (pointsToAnnotation?.getValueArgument(0) as? IrVararg)?.elements?.map { (it as IrConst).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 diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt index 76a003c9ef7..8f6ace31ad3 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt @@ -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 } + diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index cf0addcf78e..742df2235bd 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -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] diff --git a/kotlin-native/backend.native/tests/codegen/initializers/correctOrder3.kt b/kotlin-native/backend.native/tests/codegen/initializers/correctOrder3.kt new file mode 100644 index 00000000000..af1714c4c4e --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/correctOrder3.kt @@ -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) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer1.kt b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer1.kt new file mode 100644 index 00000000000..e30f26071d7 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer1.kt @@ -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") + } +} diff --git a/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer2.kt b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer2.kt new file mode 100644 index 00000000000..eab9e0b8bf8 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer2.kt @@ -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") + } +} diff --git a/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer3.kt b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer3.kt new file mode 100644 index 00000000000..e9700f6b377 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer3.kt @@ -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") + } +} diff --git a/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer4.kt b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer4.kt new file mode 100644 index 00000000000..ed0917e72af --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/failInInitializer4.kt @@ -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") + } +} diff --git a/kotlin-native/backend.native/tests/codegen/initializers/globalInitedAfterAccessingFile.kt b/kotlin-native/backend.native/tests/codegen/initializers/globalInitedAfterAccessingFile.kt new file mode 100644 index 00000000000..02d0507fc40 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/globalInitedAfterAccessingFile.kt @@ -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) +} diff --git a/kotlin-native/backend.native/tests/codegen/initializers/globalInitedBeforeThreadLocal.kt b/kotlin-native/backend.native/tests/codegen/initializers/globalInitedBeforeThreadLocal.kt new file mode 100644 index 00000000000..e15ba86ee34 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/globalInitedBeforeThreadLocal.kt @@ -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) +} diff --git a/kotlin-native/backend.native/tests/codegen/initializers/workers1.kt b/kotlin-native/backend.native/tests/codegen/initializers/workers1.kt new file mode 100644 index 00000000000..90efc2dcf0e --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/workers1.kt @@ -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) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/codegen/initializers/workers2.kt b/kotlin-native/backend.native/tests/codegen/initializers/workers2.kt new file mode 100644 index 00000000000..e2972de9d3b --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/initializers/workers2.kt @@ -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 { } +} \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/cpp/Exceptions.h b/kotlin-native/runtime/src/main/cpp/Exceptions.h index 770621e2f03..066e501d62a 100644 --- a/kotlin-native/runtime/src/main/cpp/Exceptions.h +++ b/kotlin-native/runtime/src/main/cpp/Exceptions.h @@ -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. diff --git a/kotlin-native/runtime/src/main/cpp/Porting.cpp b/kotlin-native/runtime/src/main/cpp/Porting.cpp index c91917ad60a..b4d784c8300 100644 --- a/kotlin-native/runtime/src/main/cpp/Porting.cpp +++ b/kotlin-native/runtime/src/main/cpp/Porting.cpp @@ -243,6 +243,39 @@ void onThreadExit(void (*destructor)(void*), void* destructorParameter) { #endif // !KONAN_NO_THREADS } +#if KONAN_LINUX +#include +#include +#include +#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(&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(); diff --git a/kotlin-native/runtime/src/main/cpp/Porting.h b/kotlin-native/runtime/src/main/cpp/Porting.h index 78ebeb9c4e5..d7e0f54f883 100644 --- a/kotlin-native/runtime/src/main/cpp/Porting.h +++ b/kotlin-native/runtime/src/main/cpp/Porting.h @@ -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 diff --git a/kotlin-native/runtime/src/main/cpp/Runtime.cpp b/kotlin-native/runtime/src/main/cpp/Runtime.cpp index d9309b478d5..a22fd331125 100644 --- a/kotlin-native/runtime/src/main/cpp/Runtime.cpp +++ b/kotlin-native/runtime/src/main/cpp/Runtime.cpp @@ -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" diff --git a/kotlin-native/runtime/src/main/cpp/Runtime.h b/kotlin-native/runtime/src/main/cpp/Runtime.h index c49d33168e4..f14a7eded01 100644 --- a/kotlin-native/runtime/src/main/cpp/Runtime.h +++ b/kotlin-native/runtime/src/main/cpp/Runtime.h @@ -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(); diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt index a3db00a99ad..cd263803f2f 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt @@ -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. */ diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt index d1738e36607..ebe401ea784 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt @@ -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) diff --git a/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp index d098fbc5d89..e4e58145d3a 100644 --- a/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp +++ b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp @@ -56,6 +56,7 @@ extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = { "__cxa_begin_catch", "__cxa_end_catch", "__cxa_throw", + "__cxa_rethrow", "__memset_chk", "abort", diff --git a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp index 862abd35fbd..925373baea2 100644 --- a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp +++ b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp @@ -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"); }