[K/N][IR][runtime] Implemented lazy per-file initialization strategy
This commit is contained in:
@@ -336,6 +336,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
|
||||
RuntimeAssertsMode.IGNORE
|
||||
}
|
||||
})
|
||||
put(PROPERTY_LAZY_INITIALIZATION, arguments.propertyLazyInitialization)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -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}",
|
||||
|
||||
+10
-3
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
|
||||
+2
@@ -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)
|
||||
|
||||
+2
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
@@ -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",
|
||||
|
||||
+7
@@ -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)
|
||||
|
||||
+52
-2
@@ -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",
|
||||
|
||||
+28
-2
@@ -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(
|
||||
|
||||
+219
-47
@@ -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
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+6
-8
@@ -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")
|
||||
}
|
||||
|
||||
+125
@@ -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<*> }
|
||||
}
|
||||
+32
-17
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-23
@@ -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
|
||||
|
||||
+25
-18
@@ -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
|
||||
|
||||
|
||||
+3
-2
@@ -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")
|
||||
}
|
||||
}
|
||||
+27
@@ -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)
|
||||
}
|
||||
+26
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user