[K/N] Rework object and enum classes initialization

Now it works with common logic for all static scope not with custom one.
This commit is contained in:
Pavel Kunyavskiy
2022-11-01 17:29:13 +01:00
committed by Space Team
parent 7006eb938d
commit d3adfec2fb
38 changed files with 393 additions and 1050 deletions
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
@@ -257,7 +258,13 @@ private class ExportedElement(val kind: ElementKind,
// Produce instance getter if needed.
if (isSingletonObject) {
generateFunction(owner.codegen, owner.kGetObjectFuncType, "${cname}_instance") {
val value = getObjectValue(irClass, ExceptionHandler.Caller, null)
val value = call(
owner.codegen.llvmFunction(context.getObjectClassInstanceFunction(irClass)),
emptyList(),
Lifetime.GLOBAL,
ExceptionHandler.Caller,
false,
returnSlot)
ret(value)
}
}
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.konan.target.Architecture
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.needSmallBinary
import org.jetbrains.kotlin.library.SerializedIrModule
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.name.FqName
@@ -50,15 +49,13 @@ internal class NativeMapping : DefaultMapping() {
data class BridgeKey(val target: IrSimpleFunction, val bridgeDirections: BridgeDirections)
val outerThisFields = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrField>()
val enumImplObjects = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrClass>()
val enumValueGetters = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrFunction>()
val enumEntriesMaps = mutableMapOf<IrClass, Map<Name, LoweredEnumEntryDescription>>()
val bridges = mutableMapOf<BridgeKey, IrSimpleFunction>()
val notLoweredInlineFunctions = mutableMapOf<IrFunctionSymbol, IrFunction>()
val companionObjectCacheAccessors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val outerThisCacheAccessors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val lateinitPropertyCacheAccessors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrProperty, IrSimpleFunction>()
val enumValuesCacheAccessors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val objectInstanceGetter = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
}
internal class Context(config: KonanConfig) : KonanBackendContext(config), ConfigChecks {
@@ -95,8 +92,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config), Confi
val innerClassesSupport by lazy { InnerClassesSupport(mapping, irFactory) }
val bridgesSupport by lazy { BridgesSupport(mapping, irBuiltIns, irFactory) }
val inlineFunctionsSupport by lazy { InlineFunctionsSupport(mapping) }
val enumsSupport by lazy { EnumsSupport(mapping, ir.symbols, irBuiltIns, irFactory) }
val cachesAbiSupport by lazy { CachesAbiSupport(mapping, ir.symbols, irFactory) }
val enumsSupport by lazy { EnumsSupport(mapping, irBuiltIns, irFactory) }
val cachesAbiSupport by lazy { CachesAbiSupport(mapping, irFactory) }
open class LazyMember<T>(val initializer: Context.() -> T) {
operator fun getValue(thisRef: Context, property: KProperty<*>): T = thisRef.getValue(this)
@@ -250,6 +250,12 @@ internal val initializersPhase = makeKonanFileLoweringPhase(
prerequisite = setOf(enumConstructorsPhase)
)
internal val objectClassesPhase = makeKonanFileLoweringPhase(
::ObjectClassLowering,
name = "ObjectClasses",
description = "Enum constructors lowering"
)
internal val localFunctionsPhase = makeKonanFileOpPhase(
op = { context, irFile ->
LocalDelegatedPropertiesLowering().lower(irFile)
@@ -292,6 +292,7 @@ internal val allLoweringsPhase = SameTypeNamedCompilerPhase(
coroutinesPhase,
typeOperatorPhase,
expressionBodyTransformPhase,
objectClassesPhase,
constantInliningPhase,
staticInitializersPhase,
bridgesPhase,
@@ -1387,7 +1387,7 @@ private object IgnoredUnitArgumentPassing : ArgumentPassing {
}
override fun CCallbackBuilder.receiveValue(): IrExpression {
return bridgeBuilder.kotlinIrBuilder.irGetObject(irBuiltIns.unitClass)
return bridgeBuilder.kotlinIrBuilder.irCall(symbols.theUnitInstance)
}
}
@@ -498,8 +498,8 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
}
return declarations.mapNotNull {
when (it) {
is IrField -> it.takeIf { it.isReal }?.toFieldInfo()
is IrProperty -> it.takeIf { it.isReal }?.backingField?.toFieldInfo()
is IrField -> it.takeIf { it.isReal && !it.isStatic }?.toFieldInfo()
is IrProperty -> it.takeIf { it.isReal }?.backingField?.takeIf { !it.isStatic }?.toFieldInfo()
else -> null
}
}
@@ -232,6 +232,8 @@ internal class KonanSymbols(
val reinterpret = internalFunction("reinterpret")
val theUnitInstance = internalFunction("theUnitInstance")
val ieee754Equals = internalFunctions("ieee754Equals")
val equals = irBuiltIns.findBuiltInClassMemberFunctions(any, Name.identifier("equals")).single()
@@ -313,8 +315,6 @@ internal class KonanSymbols(
val initInstance = internalFunction("initInstance")
val freeze = irBuiltIns.findFunctions(Name.identifier("freeze"), "kotlin", "native", "concurrent").single()
val println = irBuiltIns.findFunctions(Name.identifier("println"), "kotlin", "io")
.single { it.descriptor.valueParameters.singleOrNull()?.type == (irBuiltIns as IrBuiltInsOverDescriptors).builtIns.stringType }
@@ -23,28 +23,6 @@ import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.konan.ForeignExceptionMode
private fun IrConstructor.isAutogeneratedSimpleConstructor(context: Context): Boolean {
if (!this.isPrimary) return false
val statements = this.body?.statements ?: return false
if (statements.size < 2) return false
val lastStatement = statements.last()
if (lastStatement !is IrReturn ||
(lastStatement.value as? IrGetObjectValue)?.symbol != context.irBuiltIns.unitClass) return false
val constructorCall = statements[0] as? IrDelegatingConstructorCall ?: return false
val constructor = constructorCall.symbol.owner as? IrConstructor ?: return false
if (!constructor.constructedClass.isAny()) return false
return statements.asSequence().take(statements.size - 1).drop(1).all {
it is IrBlock && it.origin == IrStatementOrigin.INITIALIZE_FIELD
}
}
// TODO: shall we memoize that property?
internal fun IrClass.hasConstStateAndNoSideEffects(context: Context): Boolean {
if (!context.shouldOptimize()) return false
if (this.hasAnnotation(KonanFqNames.canBePrecreated)) return true
val fields = context.getLayoutBuilder(this).fields
return fields.all { it.isConst } && this.constructors.all { it.isAutogeneratedSimpleConstructor(context) }
}
internal class CodeGenerator(override val context: Context) : ContextUtils {
fun llvmFunction(function: IrFunction): LlvmCallable =
@@ -1260,109 +1238,12 @@ internal abstract class FunctionGenerationContext(
return null
}
@Suppress("UNUSED_PARAMETER")
fun getObjectValue(irClass: IrClass, exceptionHandler: ExceptionHandler,
startLocationInfo: LocationInfo?, endLocationInfo: LocationInfo? = null,
resultSlot: LLVMValueRef? = null
startLocationInfo: LocationInfo?, endLocationInfo: LocationInfo? = null,
resultSlot: LLVMValueRef? = null
): LLVMValueRef {
// TODO: could be processed the same way as other stateless objects.
if (irClass.isUnit()) {
return codegen.theUnitInstanceRef.llvm
}
if (irClass.isCompanion) {
val parent = irClass.parent as IrClass
if (parent.isObjCClass()) {
// TODO: cache it too.
return call(
codegen.llvmFunction(context.ir.symbols.interopInterpretObjCPointer.owner),
listOf(getObjCClass(parent, exceptionHandler)),
Lifetime.GLOBAL,
exceptionHandler
)
}
}
val storageKind = irClass.storageKind(context)
val objectPtr = if (isExternal(irClass)) {
when (storageKind) {
// If thread local object is imported - access it via getter function.
ObjectStorageKind.THREAD_LOCAL -> {
val valueGetterName = irClass.threadLocalObjectStorageGetterSymbolName
val valueGetterFunction = LLVMGetNamedFunction(llvm.module, valueGetterName)
?: addLlvmFunctionWithDefaultAttributes(
context,
llvm.module,
valueGetterName,
functionType(kObjHeaderPtrPtr, false)
)
call(valueGetterFunction,
listOf(),
resultLifetime = Lifetime.GLOBAL,
exceptionHandler = exceptionHandler)
}
// If global object is imported - import it's storage directly.
ObjectStorageKind.PERMANENT, ObjectStorageKind.SHARED -> {
val llvmType = irClass.defaultType.toLLVMType(llvm)
importGlobal(
irClass.globalObjectStorageSymbolName,
llvmType,
origin = irClass.llvmSymbolOrigin
)
}
}
} else {
// Local globals and thread locals storage info is stored in our map.
val singleton = llvmDeclarations.forSingleton(irClass)
val instanceAddress = singleton.instanceStorage
instanceAddress.getAddress(this)
}
when (storageKind) {
ObjectStorageKind.SHARED ->
// If current file used a shared object, make file's (de)initializer function deinit it.
llvm.globalSharedObjects += objectPtr
ObjectStorageKind.THREAD_LOCAL ->
// If current file used locally defined TLS objects, make file's (de)initializer function
// init and deinit TLS.
// Note: for exported TLS objects a getter is generated in a file they're defined in. Which
// adds TLS init and deinit to that file's (de)initializer function.
if (!isExternal(irClass))
llvm.fileUsesThreadLocalObjects = true
ObjectStorageKind.PERMANENT -> { /* Do nothing, no need to free such an instance. */ }
}
if (storageKind == ObjectStorageKind.PERMANENT) {
return loadSlot(objectPtr, false)
}
val bbInit = basicBlock("label_init", startLocationInfo, endLocationInfo)
val bbExit = basicBlock("label_continue", startLocationInfo, endLocationInfo)
val objectVal = loadSlot(objectPtr, false, memoryOrder = LLVMAtomicOrdering.LLVMAtomicOrderingAcquire)
val objectInitialized = icmpUGt(ptrToInt(objectVal, codegen.intPtrType), codegen.immOneIntPtrType)
val bbCurrent = currentBlock
condBr(objectInitialized, bbExit, bbInit)
positionAtEnd(bbInit)
val typeInfo = codegen.typeInfoForAllocation(irClass)
val defaultConstructor = irClass.constructors.single { it.valueParameters.size == 0 }
val ctor = codegen.llvmFunction(defaultConstructor).llvmValue
val initFunction =
if (storageKind == ObjectStorageKind.SHARED && context.config.threadsAreAllowed) {
llvm.initSingletonFunction
} else {
llvm.initThreadLocalSingleton
}
val args = listOf(objectPtr, typeInfo, ctor)
val newValue = call(initFunction, args, Lifetime.GLOBAL, exceptionHandler, resultSlot = resultSlot)
val bbInitResult = currentBlock
br(bbExit)
positionAtEnd(bbExit)
val valuePhi = phi(irClass.defaultType.toLLVMType(llvm))
addPhiIncoming(valuePhi, bbCurrent to objectVal, bbInitResult to newValue)
return valuePhi
error("Should be lowered out: ${irClass.render()} while generating ${irFunction?.dump()}")
}
/**
@@ -481,8 +481,6 @@ internal class Llvm(private val context: Context, val module: LLVMModuleRef) : R
val allocInstanceFunction = importRtFunction("AllocInstance")
val allocArrayFunction = importRtFunction("AllocArrayInstance")
val initThreadLocalSingleton = importRtFunction("InitThreadLocalSingleton")
val initSingletonFunction = importRtFunction("InitSingleton")
val initAndRegisterGlobalFunction = importRtFunction("InitAndRegisterGlobal")
val updateHeapRefFunction = importRtFunction("UpdateHeapRef")
val updateStackRefFunction = importRtFunction("UpdateStackRef")
@@ -564,7 +562,6 @@ internal class Llvm(private val context: Context, val module: LLVMModuleRef) : R
val compilerUsedGlobals = mutableListOf<LLVMValueRef>()
val irStaticInitializers = mutableListOf<IrStaticInitializer>()
val otherStaticInitializers = mutableListOf<LLVMValueRef>()
var fileUsesThreadLocalObjects = false
val globalSharedObjects = mutableSetOf<LLVMValueRef>()
val initializersGenerationState = InitializersGenerationState()
val boxCacheGlobals = mutableMapOf<BoxCache, StaticData.Global>()
@@ -66,6 +66,7 @@ internal enum class IntrinsicType {
IMMUTABLE_BLOB,
INIT_INSTANCE,
IS_EXPERIMENTAL_MM,
THE_UNIT_INSTANCE,
// Enums
ENUM_VALUES,
ENUM_VALUE_OF,
@@ -241,6 +242,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
IntrinsicType.IDENTITY -> emitIdentity(args)
IntrinsicType.INTEROP_MEMORY_COPY -> emitMemoryCopy(callSite, args)
IntrinsicType.IS_EXPERIMENTAL_MM -> emitIsExperimentalMM()
IntrinsicType.THE_UNIT_INSTANCE -> theUnitInstanceRef.llvm
IntrinsicType.GET_CONTINUATION,
IntrinsicType.RETURN_IF_SUSPENDED,
IntrinsicType.INTEROP_BITS_TO_FLOAT,
@@ -46,12 +46,6 @@ internal enum class FieldStorageKind {
THREAD_LOCAL
}
internal enum class ObjectStorageKind {
PERMANENT,
THREAD_LOCAL,
SHARED
}
// TODO: maybe unannotated singleton objects shall be accessed from main thread only as well?
internal fun IrField.storageKind(context: Context): FieldStorageKind {
// TODO: Is this correct?
@@ -77,12 +71,6 @@ internal fun IrField.needsGCRegistration(context: Context) =
(hasNonConstInitializer || // which are initialized from heap object
!isFinal) // or are not final
internal fun IrClass.storageKind(context: Context): ObjectStorageKind = when {
this.annotations.hasAnnotation(KonanFqNames.threadLocal) &&
context.config.threadsAreAllowed -> ObjectStorageKind.THREAD_LOCAL
this.hasConstStateAndNoSideEffects(context) -> ObjectStorageKind.PERMANENT
else -> ObjectStorageKind.SHARED
}
internal fun IrField.isGlobalNonPrimitive(context: Context) = when {
type.computePrimitiveBinaryTypeOrNull() != null -> false
@@ -883,44 +871,6 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
}
}
if (declaration.kind.isSingleton && !declaration.isUnit()) {
val singleton = context.generationState.llvmDeclarations.forSingleton(declaration)
val access = singleton.instanceStorage
if (access is GlobalAddressAccess) {
// Global objects are kept in a data segment and can be accessed by any module (if exported) and also
// they need to be initialized statically.
LLVMSetInitializer(access.getAddress(null), if (declaration.storageKind(context) == ObjectStorageKind.PERMANENT)
llvm.staticData.createConstKotlinObject(declaration,
*computeFields(declaration)).llvm else codegen.kNullObjHeaderPtr)
} else {
// Thread local objects are kept in a special map, so they need a getter function to be accessible
// by other modules.
val isObjCCompanion = declaration.isCompanion && declaration.parentAsClass.isObjCClass()
// If can be exported and can be instantiated.
if (declaration.isExported() && !isObjCCompanion &&
declaration.constructors.singleOrNull() { it.valueParameters.size == 0 } != null) {
val valueGetterName = declaration.threadLocalObjectStorageGetterSymbolName
generateFunction(codegen,
functionType(codegen.kObjHeaderPtrPtr, false),
valueGetterName) {
val value = access.getAddress(this)
ret(value)
}
// Getter uses TLS object, so need to ensure that this file's (de)initializer function
// inits and deinits TLS.
llvm.fileUsesThreadLocalObjects = true
}
}
}
}
private fun computeFields(declaration: IrClass): Array<ConstValue> {
val fields = context.getLayoutBuilder(declaration).fields
return Array(fields.size) { index ->
val initializer = fields[index].irField!!.initializer!!.expression as IrConst<*>
evaluateConst(initializer)
}
}
override fun visitProperty(declaration: IrProperty) {
@@ -987,7 +937,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
is IrVararg -> return evaluateVararg (value)
is IrBreak -> return evaluateBreak (value)
is IrContinue -> return evaluateContinue (value)
is IrGetObjectValue -> return evaluateGetObjectValue (value, resultSlot)
is IrGetObjectValue -> return evaluateGetObjectValue (value)
is IrFunctionReference -> return evaluateFunctionReference (value)
is IrSuspendableExpression ->
return evaluateSuspendableExpression (value, resultSlot)
@@ -1012,14 +962,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------//
private fun evaluateGetObjectValue(value: IrGetObjectValue, resultSlot: LLVMValueRef?): LLVMValueRef =
functionGenerationContext.getObjectValue(
value.symbol.owner,
currentCodeContext.exceptionHandler,
value.startLocation,
value.endLocation,
resultSlot
)
private fun evaluateGetObjectValue(value: IrGetObjectValue): LLVMValueRef {
error("Should be lowered out: ${value.symbol.owner.render()}")
}
//-------------------------------------------------------------------------//
@@ -1798,7 +1743,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
)
if (context.config.threadsAreAllowed && value.symbol.owner.storageKind(context) == FieldStorageKind.GLOBAL)
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
if (value.symbol.owner.shouldBeFrozen(context))
if (value.symbol.owner.shouldBeFrozen(context) && value.origin != ObjectClassLowering.IrStatementOriginFieldPreInit)
functionGenerationContext.freeze(valueToAssign, currentCodeContext.exceptionHandler)
functionGenerationContext.storeAny(valueToAssign, globalAddress, false)
}
@@ -1909,14 +1854,29 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val fields = if (value.constructor.owner.isConstantConstructorIntrinsic) {
intrinsicGenerator.evaluateConstantConstructorFields(value, value.valueArguments.map { evaluateConstantValue(it) })
} else {
context.getLayoutBuilder(constructedClass).fields.map { field ->
val index = value.constructor.owner.valueParameters
.indexOfFirst { it.name.toString() == field.name }
.takeIf { it >= 0 }
?: error("Bad statically initialized object: field ${field.name} value not set in ${constructedClass.name}")
evaluateConstantValue(value.valueArguments[index])
val fields = context.getLayoutBuilder(constructedClass).fields
val valueParameters = value.constructor.owner.valueParameters.associateBy { it.name.toString() }
fields.map { field ->
if (field.isConst) {
val init = field.irField!!.initializer?.expression
require(field.name !in valueParameters) {
"Constant field ${field.name} of class ${constructedClass.name} shouldn't be a constructor parameter"
}
when (init) {
is IrConst<*> -> evaluateConst(init)
is IrConstantValue -> evaluateConstantValue(init)
null -> error("Constant field ${field.name} of class ${constructedClass.name} should have initializer")
else -> error("Unexpected constant initializer type: ${init::class}")
}
} else {
val index = valueParameters[field.name]?.index
?: error("Bad statically initialized object: field ${field.name} value not set in ${constructedClass.name}")
evaluateConstantValue(value.valueArguments[index])
}
}.also {
require(it.size == value.valueArguments.size) { "Bad statically initialized object: too many fields" }
require(it.size == value.valueArguments.size + fields.count { it.isConst }) {
"Bad statically initialized object of class ${constructedClass.name}: too many fields"
}
}
}
@@ -52,9 +52,6 @@ internal class LlvmDeclarations(private val unique: Map<UniqueKind, UniqueLlvmDe
fun forStaticField(field: IrField) = (field.metadata as? CodegenStaticFieldMetadata)?.llvm ?:
error(field.descriptor.toString())
fun forSingleton(irClass: IrClass) = forClass(irClass).singletonDeclarations ?:
error(irClass.descriptor.toString())
fun forUnique(kind: UniqueKind) = unique[kind] ?: error("No unique $kind")
}
@@ -64,11 +61,8 @@ internal class ClassLlvmDeclarations(
val typeInfoGlobal: StaticData.Global,
val writableTypeInfoGlobal: StaticData.Global?,
val typeInfo: ConstPointer,
val singletonDeclarations: SingletonLlvmDeclarations?,
val objCDeclarations: KotlinObjCClassLlvmDeclarations?)
internal class SingletonLlvmDeclarations(val instanceStorage: AddressAccess)
internal class KotlinObjCClassLlvmDeclarations(
val classInfoGlobal: StaticData.Global,
val bodyOffsetGlobal: StaticData.Global
@@ -219,12 +213,6 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
if (declaration.isUnit() || declaration.isKotlinArray())
createUniqueDeclarations(declaration, typeInfoPtr, bodyType)
val singletonDeclarations = if (declaration.kind.isSingleton) {
createSingletonDeclarations(declaration)
} else {
null
}
val objCDeclarations = if (declaration.isKotlinObjCClass()) {
createKotlinObjCClassDeclarations(declaration)
} else {
@@ -245,8 +233,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
it.setZeroInitializer()
}
return ClassLlvmDeclarations(bodyType, typeInfoGlobal, writableTypeInfoGlobal, typeInfoPtr,
singletonDeclarations, objCDeclarations)
return ClassLlvmDeclarations(bodyType, typeInfoGlobal, writableTypeInfoGlobal, typeInfoPtr, objCDeclarations)
}
private fun createUniqueDeclarations(
@@ -264,28 +251,6 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
}
}
private fun createSingletonDeclarations(irClass: IrClass): SingletonLlvmDeclarations? {
if (irClass.isUnit()) {
return null
}
val storageKind = irClass.storageKind(context)
val threadLocal = storageKind == ObjectStorageKind.THREAD_LOCAL
val isExported = irClass.isExported()
val symbolName = if (isExported) {
irClass.globalObjectStorageSymbolName
} else {
"kobjref:" + qualifyInternalName(irClass)
}
val instanceAddress = if (threadLocal) {
addKotlinThreadLocal(symbolName, irClass.defaultType.toLLVMType(llvm))
} else {
addKotlinGlobal(symbolName, irClass.defaultType.toLLVMType(llvm), isExported)
}
return SingletonLlvmDeclarations(instanceAddress)
}
private fun createKotlinObjCClassDeclarations(irClass: IrClass): KotlinObjCClassLlvmDeclarations {
val internalName = qualifyInternalName(irClass)
@@ -312,7 +277,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
super.visitField(declaration)
val containingClass = declaration.parent as? IrClass
if (containingClass != null) {
if (containingClass != null && !declaration.isStatic) {
if (!containingClass.requiresRtti()) return
val classDeclarations = (containingClass.metadata as? CodegenClassMetadata)?.llvm
?: error(containingClass.descriptor.toString())
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.getSuperClassNotAny
import org.jetbrains.kotlin.backend.konan.ir.isAny
import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference
import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.*
@@ -463,17 +464,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
}
val associatedObjectTableRecords = associatedObjects.map { (key, value) ->
val associatedObjectGetter = generateFunction(
CodeGenerator(context),
functionType(kObjHeaderPtr, false, kObjHeaderPtrPtr),
""
) {
ret(getObjectValue(value, ExceptionHandler.Caller, startLocationInfo = null))
}.also {
LLVMSetLinkage(it, LLVMLinkage.LLVMPrivateLinkage)
}
val function = context.getObjectClassInstanceFunction(value)
val llvmFunction = context.generationState.llvmDeclarations.forFunction(function).llvmValue
Struct(runtime.associatedObjectTableRecordType, key.typeInfoPtr, constPointer(associatedObjectGetter))
Struct(runtime.associatedObjectTableRecordType, key.typeInfoPtr, constPointer(llvmFunction))
}
return staticData.placeGlobalConstArray(
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCCodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator
import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.backend.konan.serialization.resolveFakeOverrideMaybeAbstract
import org.jetbrains.kotlin.descriptors.ClassKind
@@ -995,7 +996,8 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
target: IrFunction?,
baseMethod: IrFunction,
methodBridge: MethodBridge,
isVirtual: Boolean = false
isVirtual: Boolean = false,
customBridgeSuffix: String? = null,
) = if (target == null) {
generateAbstractObjCImp(methodBridge, baseMethod)
} else {
@@ -1003,7 +1005,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
methodBridge,
isDirect = !isVirtual,
baseMethod = baseMethod,
bridgeSuffix = (if (isVirtual) "virtual_" else "") + target.computeSymbolName()
bridgeSuffix = customBridgeSuffix ?: ((if (isVirtual) "virtual_" else "") + target.computeSymbolName())
) { args, resultLifetime, exceptionHandler ->
if (target is IrConstructor && target.constructedClass.isAbstract()) {
callFromBridge(
@@ -1913,12 +1915,20 @@ private fun ObjCExportCodeGenerator.createObjectInstanceAdapter(
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
assert(objectClass.kind == ClassKind.OBJECT)
assert(!objectClass.isUnit())
val bridgeSuffix = "${owner.computeTypeInfoSymbolName()}#$selector"
return generateObjCToKotlinSyntheticGetter(selector, bridgeSuffix) {
initRuntimeIfNeeded() // For instance methods it gets called when allocating.
val value = getObjectValue(objectClass, startLocationInfo = null, exceptionHandler = ExceptionHandler.Caller)
autoreleaseAndRet(kotlinReferenceToRetainedObjC(value))
}
val methodBridge = MethodBridge(
returnBridge = MethodBridge.ReturnValue.Mapped(ReferenceBridge),
receiver = MethodBridgeReceiver.Static,
valueParameters = emptyList()
)
val function = context.getObjectClassInstanceFunction(objectClass)
val imp = generateObjCImp(
function, function, methodBridge,
isVirtual = false,
customBridgeSuffix = "${owner.computeTypeInfoSymbolName()}#$selector")
return objCToKotlinMethodAdapter(selector, methodBridge, imp)
}
private fun ObjCExportCodeGenerator.createEnumEntryAdapter(
@@ -239,7 +239,7 @@ private class InlineClassTransformer(private val context: Context) : IrBuildingT
val field = expression.symbol.owner
val parentClass = field.parentClassOrNull
return if (parentClass == null || !parentClass.isInlined())
return if (parentClass == null || !parentClass.isInlined() || field.isStatic)
expression
else {
builder.at(expression)
@@ -254,7 +254,7 @@ private class InlineClassTransformer(private val context: Context) : IrBuildingT
override fun visitSetField(expression: IrSetField): IrExpression {
super.visitSetField(expression)
return if (expression.symbol.owner.parentClassOrNull?.isInlined() == true) {
return if (expression.symbol.owner.parentClassOrNull?.isInlined() == true && !expression.symbol.owner.isStatic) {
// Happens in one of the cases:
// 1. In primary constructor of the inlined class. Makes no sense, "has no effect", can be removed.
// The constructor will be lowered and used.
@@ -442,7 +442,7 @@ private class InlineClassTransformer(private val context: Context) : IrBuildingT
lateinit var thisVar: IrValueDeclaration
fun IrBuilderWithScope.genReturnValue(): IrExpression = if (irConstructor.isPrimary) {
irGetObject(irBuiltIns.unitClass)
irCall(symbols.theUnitInstance)
} else {
irGet(thisVar)
}
@@ -11,9 +11,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.NativeMapping
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
import org.jetbrains.kotlin.backend.konan.isObjCClass
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
@@ -22,8 +20,6 @@ import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetField
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.name.Name
@@ -39,26 +35,11 @@ internal object INTERNAL_ABI_ORIGIN : IrDeclarationOriginImpl("INTERNAL_ABI")
* In case of compiler caches, this means that it is not accessible as Lazy IR
* and we have to explicitly add an external declaration.
*/
internal class CachesAbiSupport(mapping: NativeMapping, symbols: KonanSymbols, private val irFactory: IrFactory) {
private val companionObjectAccessors = mapping.companionObjectCacheAccessors
internal class CachesAbiSupport(mapping: NativeMapping, private val irFactory: IrFactory) {
private val outerThisAccessors = mapping.outerThisCacheAccessors
private val lateinitPropertyAccessors = mapping.lateinitPropertyCacheAccessors
private val enumValuesAccessors = mapping.enumValuesCacheAccessors
private val lateInitFieldToNullableField = mapping.lateInitFieldToNullableField
private val array = symbols.array
fun getCompanionObjectAccessor(irClass: IrClass): IrSimpleFunction {
require(irClass.isCompanion) { "Expected a companion object but was: ${irClass.render()}" }
return companionObjectAccessors.getOrPut(irClass) {
irFactory.buildFun {
name = getMangledNameFor("globalAccessor", irClass)
origin = INTERNAL_ABI_ORIGIN
returnType = irClass.defaultType
}.apply {
parent = irClass.getPackageFragment()
}
}
}
fun getOuterThisAccessor(irClass: IrClass): IrSimpleFunction {
require(irClass.isInner) { "Expected an inner class but was: ${irClass.render()}" }
@@ -103,19 +84,6 @@ internal class CachesAbiSupport(mapping: NativeMapping, symbols: KonanSymbols, p
}
}
fun getEnumValuesAccessor(irClass: IrClass): IrSimpleFunction {
require(irClass.isEnumClass) { "Expected a enum class but was: ${irClass.render()}" }
return enumValuesAccessors.getOrPut(irClass) {
irFactory.buildFun {
name = getMangledNameFor("getValues", irClass)
returnType = array.typeWith(irClass.defaultType)
origin = INTERNAL_ABI_ORIGIN
}.apply {
parent = irClass.getPackageFragment()
}
}
}
/**
* Generate name for declaration that will be a part of internal ABI.
*/
@@ -143,15 +111,6 @@ internal class ExportCachesAbiVisitor(val context: Context) : FileLoweringPass,
if (declaration.isLocal) return
if (declaration.isCompanion) {
val function = cachesAbiSupport.getCompanionObjectAccessor(declaration)
context.createIrBuilder(function.symbol).apply {
function.body = irBlockBody {
+irReturn(irGetObjectValue(declaration.defaultType, declaration.symbol))
}
}
data.add(function)
}
if (declaration.isInner) {
val function = cachesAbiSupport.getOuterThisAccessor(declaration)
@@ -165,16 +124,6 @@ internal class ExportCachesAbiVisitor(val context: Context) : FileLoweringPass,
}
data.add(function)
}
if (declaration.isEnumClass) {
val function = cachesAbiSupport.getEnumValuesAccessor(declaration)
context.createIrBuilder(function.symbol).run {
function.body = irBlockBody {
+irReturn(with(this@ExportCachesAbiVisitor.context.enumsSupport) { irGetValuesField(declaration) })
}
}
data.add(function)
}
}
override fun visitProperty(declaration: IrProperty, data: MutableList<IrFunction>) {
@@ -198,29 +147,12 @@ internal class ExportCachesAbiVisitor(val context: Context) : FileLoweringPass,
internal class ImportCachesAbiTransformer(val context: Context) : FileLoweringPass, IrElementTransformerVoid() {
private val cachesAbiSupport = context.cachesAbiSupport
private val enumsSupport = context.enumsSupport
private val llvmImports = context.generationState.llvmImports
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(this)
}
override fun visitGetObjectValue(expression: IrGetObjectValue): IrExpression {
expression.transformChildrenVoid(this)
val irClass = expression.symbol.owner
if (!irClass.isCompanion || context.llvmModuleSpecification.containsDeclaration(irClass)) {
return expression
}
val parent = irClass.parentAsClass
if (parent.isObjCClass()) {
// Access to Obj-C metaclass is done via intrinsic.
return expression
}
val accessor = cachesAbiSupport.getCompanionObjectAccessor(irClass)
llvmImports.add(irClass.llvmSymbolOrigin)
return irCall(expression.startOffset, expression.endOffset, accessor, emptyList())
}
override fun visitGetField(expression: IrGetField): IrExpression {
expression.transformChildrenVoid(this)
@@ -249,17 +181,6 @@ internal class ImportCachesAbiTransformer(val context: Context) : FileLoweringPa
}
}
field.origin == DECLARATION_ORIGIN_ENUM -> {
val enumClass = irClass?.parentClassOrNull
require(enumClass != null) { "Unexpected usage of enum VALUES field" }
require(enumClass.isEnumClass) { "Expected a enum class: ${enumClass.render()}" }
require(enumsSupport.getImplObject(enumClass) == irClass) { "Expected a enum's impl object: ${irClass.render()}" }
require(field == enumsSupport.getValuesField(irClass)) { "Expected VALUES field: ${field.render()}" }
val accessor = cachesAbiSupport.getEnumValuesAccessor(enumClass)
llvmImports.add(enumClass.llvmSymbolOrigin)
return irCall(expression.startOffset, expression.endOffset, accessor, emptyList())
}
else -> expression
}
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.DefaultDelegateFactory
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.getOrPut
import org.jetbrains.kotlin.backend.common.lower.EnumWhenLowering
@@ -12,11 +13,10 @@ import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.MemoryModel
import org.jetbrains.kotlin.backend.konan.NativeMapping
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.ir.KonanNameConventions
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.ir.buildSimpleAnnotation
import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
import org.jetbrains.kotlin.descriptors.ClassKind
@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
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.buildClass
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
@@ -44,17 +43,29 @@ internal data class LoweredEnumEntryDescription(val ordinal: Int, val getterId:
internal class EnumsSupport(
mapping: NativeMapping,
symbols: KonanSymbols,
private val irBuiltIns: IrBuiltIns,
private val irFactory: IrFactory,
) {
private val enumImplObjects = mapping.enumImplObjects
private val enumValueGetters = mapping.enumValueGetters
private val enumEntriesMaps = mapping.enumEntriesMaps
private val array = symbols.array
private val enumEntries = symbols.enumEntriesInterface
private val genericValueOfSymbol = symbols.valueOfForEnum
private val genericValuesSymbol = symbols.valuesForEnum
private val valuesFunctions = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrFunction>()
private val valueOfFunctions = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrFunction>()
fun getValuesFunction(enumClass: IrClass) = valuesFunctions.getOrPut(enumClass) {
enumClass.simpleFunctions().single {
it.name == valuesFunctionName && it.dispatchReceiverParameter == null
}
}
fun saveValuesFunction(enumClass: IrClass) { getValuesFunction(enumClass) }
fun getValueOfFunction(enumClass: IrClass) = valueOfFunctions.getOrPut(enumClass) {
enumClass.simpleFunctions().single {
it.name == valueOfFunctionName && it.dispatchReceiverParameter == null
}
}
fun saveValueOfFunction(enumClass: IrClass) { getValueOfFunction(enumClass) }
fun enumEntriesMap(enumClass: IrClass): Map<Name, LoweredEnumEntryDescription> {
require(enumClass.isEnumClass) { "Expected enum class but was: ${enumClass.render()}" }
@@ -70,41 +81,6 @@ internal class EnumsSupport(
}
}
fun getImplObject(enumClass: IrClass): IrClass {
require(enumClass.isEnumClass) { "Expected enum class but was: ${enumClass.render()}" }
return enumImplObjects.getOrPut(enumClass) {
irFactory.buildClass {
startOffset = enumClass.startOffset
endOffset = enumClass.endOffset
origin = DECLARATION_ORIGIN_ENUM
name = implObjectName
kind = ClassKind.OBJECT
}.apply {
superTypes = listOf(irBuiltIns.anyType)
parent = enumClass
addChild(irFactory.buildField {
startOffset = enumClass.startOffset
endOffset = enumClass.endOffset
origin = DECLARATION_ORIGIN_ENUM
name = valuesFieldName
type = array.typeWith(enumClass.defaultType)
visibility = DescriptorVisibilities.PRIVATE
isFinal = true
})
addChild(irFactory.buildField {
startOffset = enumClass.startOffset
endOffset = enumClass.endOffset
origin = DECLARATION_ORIGIN_ENUM
name = entriesFieldName
type = enumEntries.typeWith(enumClass.defaultType)
visibility = DescriptorVisibilities.PRIVATE
isFinal = true
})
}
}
}
fun getValueGetter(enumClass: IrClass): IrFunction {
require(enumClass.isEnumClass) { "Expected enum class but was: ${enumClass.render()}" }
return enumValueGetters.getOrPut(enumClass) {
@@ -126,36 +102,9 @@ internal class EnumsSupport(
}
}
fun getValuesField(implObject: IrClass) = implObject.fields.single { it.name == valuesFieldName }
fun getEntriesField(implObject: IrClass) = implObject.fields.single { it.name == entriesFieldName }
fun IrBuilderWithScope.irGetValuesField(enumClass: IrClass): IrExpression {
val implObject = getImplObject(enumClass)
val valuesField = getValuesField(implObject)
return irGetField(irGetObject(implObject.symbol), valuesField)
}
fun IrBuilderWithScope.irEnumValues(enumClass: IrClass) : IrExpression =
irCall(genericValuesSymbol, listOf(enumClass.defaultType)).apply {
putValueArgument(0, irGetValuesField(enumClass))
}
fun IrBuilderWithScope.irEnumValueOf(enumClass: IrClass, value: IrExpression) : IrExpression =
irCall(genericValueOfSymbol, listOf(enumClass.defaultType)).apply {
putValueArgument(0, value)
putValueArgument(1, irGetValuesField(enumClass))
}
fun IrBuilderWithScope.irEnumEntries(enumClass: IrClass) : IrExpression {
val implObject = getImplObject(enumClass)
val entriesField = getEntriesField(implObject)
return irGetField(irGetObject(implObject.symbol), entriesField)
}
companion object {
val valuesFieldName = "VALUES".synthesizedName
val entriesFieldName = "ENTRIES".synthesizedName
val implObjectName = "OBJECT".synthesizedName
val valuesFunctionName = Name.identifier("values")
val valueOfFunctionName = Name.identifier("valueOf")
}
}
@@ -170,8 +119,6 @@ internal class NativeEnumWhenLowering constructor(context: Context) : EnumWhenLo
internal class EnumUsageLowering(val context: Context) : IrElementTransformer<IrBuilderWithScope?>, FileLoweringPass {
private val enumsSupport = context.enumsSupport
private val symbols = context.ir.symbols
private val arrayGet = symbols.arrayGet[symbols.array]!!
override fun lower(irFile: IrFile) {
visitFile(irFile, data = null)
@@ -206,19 +153,21 @@ internal class EnumUsageLowering(val context: Context) : IrElementTransformer<Ir
require(irClass.kind == ClassKind.ENUM_CLASS)
return with(enumsSupport) {
if (intrinsicType == IntrinsicType.ENUM_VALUES)
data.irEnumValues(irClass)
else
data.irEnumValueOf(irClass, expression.getValueArgument(0)!!)
return when (intrinsicType) {
IntrinsicType.ENUM_VALUES -> data.irCall(enumsSupport.getValuesFunction(irClass))
IntrinsicType.ENUM_VALUE_OF -> data.irCall(enumsSupport.getValueOfFunction(irClass)).apply {
putValueArgument(0, expression.getValueArgument(0)!!)
}
else -> TODO("Unsupported intrinsic type ${intrinsicType}")
}
}
private fun IrBuilderWithScope.loadEnumEntry(enumClass: IrClass, name: Name) = with (enumsSupport) {
irCall(getValueGetter(enumClass).symbol, enumClass.defaultType).apply {
putValueArgument(0, irInt(enumEntriesMap(enumClass).getValue(name).getterId))
}
}
private fun IrBuilderWithScope.loadEnumEntry(enumClass: IrClass, name: Name) =
irCall(arrayGet, enumClass.defaultType).apply {
dispatchReceiver = with(enumsSupport) { irGetValuesField(enumClass) }
putValueArgument(0, irInt(enumsSupport.enumEntriesMap(enumClass).getValue(name).getterId))
}
}
internal class EnumClassLowering(val context: Context) : FileLoweringPass {
@@ -228,7 +177,6 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
private val createEnumEntries = symbols.createEnumEntries
private val initInstance = symbols.initInstance
private val arrayGet = symbols.array.owner.functions.single { it.name == KonanNameConventions.getWithoutBoundCheck }.symbol
private val constructorOfAny = context.irBuiltIns.anyClass.owner.constructors.first()
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
@@ -242,14 +190,37 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
}
private inner class EnumClassTransformer(val irClass: IrClass) {
private val implObject = enumsSupport.getImplObject(irClass)
private val valuesField = enumsSupport.getValuesField(implObject)
private val entriesField = enumsSupport.getEntriesField(implObject)
private val valuesField = context.irFactory.buildField {
startOffset = irClass.startOffset
endOffset = irClass.endOffset
origin = DECLARATION_ORIGIN_ENUM
name = "VALUES".synthesizedName
type = context.irBuiltIns.arrayClass.typeWith(irClass.defaultType)
visibility = DescriptorVisibilities.PRIVATE
isFinal = true
isStatic = true
}
private val entriesField = context.irFactory.buildField {
startOffset = irClass.startOffset
endOffset = irClass.endOffset
origin = DECLARATION_ORIGIN_ENUM
name = "ENTRIES".synthesizedName
type = symbols.enumEntriesInterface.typeWith(irClass.defaultType)
visibility = DescriptorVisibilities.PRIVATE
isFinal = true
isStatic = true
}
// also saves this in enumSupport before removing them from list
private val enumEntriesMap = enumsSupport.enumEntriesMap(irClass)
fun run() {
val enumEntries = transformEnumBody()
defineImplObject(enumEntries)
// These fields are inserted into begining to be initialized before companion object
// The values field should be initialized first, so we need to insert entries field first
defineEntriesField()
defineValuesField(enumEntries)
defineValueGetter()
}
@@ -263,23 +234,31 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
declaration.correspondingClass = null
listOfNotNull(correspondingClass)
}
else -> null
}
}
enumsSupport.saveValuesFunction(irClass)
enumsSupport.saveValueOfFunction(irClass)
irClass.simpleFunctions().forEach { declaration ->
val body = declaration.body
if (body is IrSyntheticBody) {
declaration.body = when (body.kind) {
IrSyntheticBodyKind.ENUM_VALUEOF -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
+irReturn(with(enumsSupport) { irEnumValueOf(irClass, irGet(declaration.valueParameters[0])) })
+irReturn(irCall(symbols.valueOfForEnum, listOf(irClass.defaultType)).apply {
putValueArgument(0, irGet(declaration.valueParameters[0]))
putValueArgument(1, irGetField(null, valuesField))
})
}
IrSyntheticBodyKind.ENUM_VALUES -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
+irReturn(with(enumsSupport) { irEnumValues(irClass) })
+irReturn(irCall(symbols.valuesForEnum, listOf(irClass.defaultType)).apply {
putValueArgument(0, irGetField(null, valuesField))
})
}
IrSyntheticBodyKind.ENUM_ENTRIES -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
+irReturn(with(enumsSupport) { irEnumEntries(irClass) })
+irReturn(irGetField(null, entriesField))
}
}
}
@@ -292,7 +271,7 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
context.createIrBuilder(valueGetter.symbol).run {
valueGetter.body = irBlockBody(valueGetter) {
+irReturn(irCall(arrayGet, irClass.defaultType).apply {
dispatchReceiver = with(enumsSupport) { irGetValuesField(irClass) }
dispatchReceiver = irGetField(null, valuesField)
putValueArgument(0, irGet(valueGetter.valueParameters[0]))
})
}
@@ -300,49 +279,18 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
irClass.declarations.add(valueGetter)
}
private fun defineImplObject(enumEntries: List<IrEnumEntry>) {
implObject.createParameterDeclarations()
implObject.addSimpleDelegatingConstructor(constructorOfAny, context.irBuiltIns, true /* TODO: why primary? */)
implObject.addFakeOverrides(context.typeSystem)
irClass.declarations.add(implObject)
ImplConstructorBuilder(implObject.constructors.single()).build(enumEntries)
}
private inner class ImplConstructorBuilder(val constructor: IrConstructor) {
private val irBuilder = context.createIrBuilder(constructor.symbol, constructor.startOffset, constructor.endOffset)
fun build(enumEntries: List<IrEnumEntry>) {
val statements = (constructor.body as IrBlockBody).statements
// The initialization order is the following:
// - first all enum entries in their declaration order
// - then companion object if it exists
statements += buildValuesFieldInitializer(enumEntries)
// Split allocation and constructors calling because enum entries can reference one another.
statements += callEnumEntriesConstructors(enumEntries)
statements += buildEntriesFieldInitializer()
// Needed for legacy MM targets that do not support threads.
if (this@EnumClassLowering.context.memoryModel != MemoryModel.EXPERIMENTAL) {
statements += irBuilder.irBlock {
irCall(this@EnumClassLowering.context.ir.symbols.freeze, listOf(valuesField.type)).apply {
extensionReceiver = irGet(implObject.thisReceiver!!)
}
}
private fun IrBlockBuilder.irInitInstanceCall(instance: IrCall, constructor: IrConstructorCall): IrCall =
irCall(initInstance).apply {
putValueArgument(0, instance)
putValueArgument(1, constructor)
}
irClass.companionObject()?.let { statements += irBuilder.irGetObject(it.symbol) }
}
private fun defineValuesField(enumEntries: List<IrEnumEntry>) {
irClass.declarations.add(0, valuesField)
valuesField.parent = irClass
val irBuilder = context.createIrBuilder(valuesField.symbol, irClass.startOffset, irClass.endOffset)
private fun IrBlockBuilder.irInitInstanceCall(instance: IrCall, constructor: IrConstructorCall): IrCall =
irCall(initInstance).apply {
putValueArgument(0, instance)
putValueArgument(1, constructor)
}
private fun buildValuesFieldInitializer(enumEntries: List<IrEnumEntry>) = irBuilder.run {
valuesField.initializer = irBuilder.irExprBody(irBuilder.irBlock {
val irValuesInitializer = this@EnumClassLowering.context.createArrayOfExpression(
startOffset, endOffset,
irClass.defaultType,
@@ -363,11 +311,22 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
irCall(createUninitializedInstance, listOf(entryClass.defaultType))
}
)
irSetField(irGet(implObject.thisReceiver!!), valuesField, irValuesInitializer)
val instances = irTemporary(irValuesInitializer)
+irSetField(null, valuesField, irGet(instances), origin = ObjectClassLowering.IrStatementOriginFieldPreInit)
callEnumEntriesConstructors(instances, enumEntries)
+irGet(instances)
}).also {
it.setDeclarationsParent(valuesField)
}
valuesField.annotations += buildSimpleAnnotation(context.irBuiltIns, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, context.ir.symbols.sharedImmutable.owner)
}
private fun buildEntriesFieldInitializer() = irBuilder.irBlock {
val irValuesArray = irTemporary(irGetField(irGet(implObject.thisReceiver!!), valuesField))
fun defineEntriesField() {
irClass.declarations.add(0, entriesField)
entriesField.parent = irClass
val irBuilder = context.createIrBuilder(entriesField.symbol, irClass.startOffset, irClass.endOffset)
entriesField.initializer = irBuilder.irExprBody(irBuilder.irBlock {
val irValuesArray = irTemporary(irGetField(null, valuesField))
val irEntriesArray = this@EnumClassLowering.context.createArrayOfExpression(
startOffset, endOffset,
irClass.defaultType,
@@ -380,34 +339,30 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
}
}
)
val irEntriesInitializer = irCall(createEnumEntries, listOf(irClass.defaultType)).apply {
+irCall(createEnumEntries, listOf(irClass.defaultType)).apply {
putValueArgument(0, irEntriesArray)
}
+irSetField(irGet(implObject.thisReceiver!!), entriesField, irEntriesInitializer)
}
})
}
private fun callEnumEntriesConstructors(enumEntries: List<IrEnumEntry>) = irBuilder.irBlock {
val receiver = implObject.thisReceiver!!
val instances = irTemporary(irGetField(irGet(receiver), valuesField))
enumEntries.forEach {
val instance = irCall(arrayGet).apply {
dispatchReceiver = irGet(instances)
putValueArgument(0, irInt(enumEntriesMap[it.name]!!.getterId))
private fun IrBlockBuilder.callEnumEntriesConstructors(instances: IrVariable, enumEntries: List<IrEnumEntry>) {
enumEntries.forEach {
val instance = irCall(arrayGet).apply {
dispatchReceiver = irGet(instances)
putValueArgument(0, irInt(enumEntriesMap[it.name]!!.getterId))
}
val initializer = it.initializerExpression!!.expression
when {
initializer is IrConstructorCall -> +irInitInstanceCall(instance, initializer)
initializer is IrBlock && initializer.origin == ARGUMENTS_REORDERING_FOR_CALL -> {
val statements = initializer.statements
val constructorCall = statements.last() as IrConstructorCall
statements[statements.lastIndex] = irInitInstanceCall(instance, constructorCall)
+initializer
}
val initializer = it.initializerExpression!!.expression
initializer.setDeclarationsParent(constructor)
when {
initializer is IrConstructorCall -> +irInitInstanceCall(instance, initializer)
initializer is IrBlock && initializer.origin == ARGUMENTS_REORDERING_FOR_CALL -> {
val statements = initializer.statements
val constructorCall = statements.last() as IrConstructorCall
statements[statements.lastIndex] = irInitInstanceCall(instance, constructorCall)
+initializer
}
else -> error("Unexpected initializer: $initializer")
}
else -> error("Unexpected initializer: $initializer")
}
}
}
@@ -0,0 +1,174 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.getOrPut
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.ir.buildSimpleAnnotation
import org.jetbrains.kotlin.backend.konan.ir.isAny
import org.jetbrains.kotlin.backend.konan.ir.isUnit
import org.jetbrains.kotlin.backend.konan.isObjCClass
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addField
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.irCall
import org.jetbrains.kotlin.name.Name
internal fun Context.getObjectClassInstanceFunction(clazz: IrClass) = mapping.objectInstanceGetter.getOrPut(clazz) {
when {
clazz.isUnit() -> ir.symbols.theUnitInstance.owner
clazz.isCompanion -> {
require((clazz.parent as? IrClass)?.isExternalObjCClass() != true) { "External objc Classes can't be used this way"}
irFactory.buildFun {
name = "companion".synthesizedName
returnType = clazz.defaultType
}.apply {
parent = clazz.parent
}
}
else -> {
irFactory.buildFun {
name = "instance".synthesizedName
returnType = clazz.defaultType
}.apply {
parent = clazz
}
}
}
}
internal class ObjectClassLowering(val context: Context) : FileLoweringPass {
val symbols = context.ir.symbols
override fun lower(irFile: IrFile) {
irFile.transform(object: IrBuildingTransformer(context) {
override fun visitClass(declaration: IrClass) : IrDeclaration {
super.visitClass(declaration)
if (declaration.isObject && !declaration.isCompanion && !declaration.isUnit()) {
processObjectClass(
declaration,
declaration,
instanceFieldName
)
}
declaration.declarations.singleOrNull { (it as? IrClass)?.isCompanion == true }?.let {
processObjectClass(
it as IrClass,
declaration,
companionFieldName
)
}
return declaration
}
override fun visitGetObjectValue(expression: IrGetObjectValue) : IrExpression {
builder.at(expression)
val singleton = expression.symbol.owner
return if (singleton.isCompanion && (singleton.parent as IrClass).isExternalObjCClass())
builder.irGetObjCClassCompanion(singleton)
else
builder.irCall(context.getObjectClassInstanceFunction(singleton))
}
}, null)
}
fun IrBuilderWithScope.irGetObjCClassCompanion(declaration: IrClass): IrExpression {
require(declaration.isCompanion && (declaration.parent as IrClass).isObjCClass())
return irCall(symbols.interopInterpretObjCPointer, listOf(declaration.defaultType)).apply {
putValueArgument(0, irCall(symbols.interopGetObjCClass, listOf((declaration.parent as IrClass).defaultType)))
}
}
fun processObjectClass(
declaration: IrClass,
classToAdd: IrClass,
fieldName: Name
) {
val function = context.getObjectClassInstanceFunction(declaration)
classToAdd.declarations.add(function)
val primaryConstructor = declaration.constructors.single { it.isPrimary }
require(primaryConstructor.valueParameters.isEmpty())
val instanceField = classToAdd.addField {
name = fieldName
isFinal = true
isStatic = true
type = declaration.defaultType
visibility = DescriptorVisibilities.PRIVATE
}.also { field ->
val builder = context.createIrBuilder(field.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
val initializer = if (declaration.isCompanion && classToAdd.isObjCClass()) {
builder.irGetObjCClassCompanion(declaration)
} else {
if (declaration.hasConstStateAndNoSideEffects() && !declaration.annotations.hasAnnotation(KonanFqNames.threadLocal)) {
builder.irConstantObject(
declaration,
emptyList()
)
} else {
builder.irBlock {
// we need to make object available for rereading from the same thread while initializing
+irSetField(null, field, irCall(symbols.createUninitializedInstance, listOf(declaration.defaultType)), origin = IrStatementOriginFieldPreInit)
+irCall(symbols.initInstance).apply {
putValueArgument(0, irGetField(null, field))
putValueArgument(1, irCallConstructor(primaryConstructor.symbol, emptyList()))
}
+irGetField(null, field)
}
}
}
field.initializer = builder.irExprBody(initializer)
if (declaration.annotations.hasAnnotation(KonanFqNames.threadLocal)) {
field.annotations += buildSimpleAnnotation(context.irBuiltIns, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, context.ir.symbols.threadLocal.owner)
} else if (declaration.annotations.hasAnnotation(KonanFqNames.sharedImmutable) || context.memoryModel != MemoryModel.EXPERIMENTAL){
field.annotations += buildSimpleAnnotation(context.irBuiltIns, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, context.ir.symbols.sharedImmutable.owner)
}
}
function.body = context.createIrBuilder(function.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
val value = irGetField(null, instanceField)
+irReturn(value)
}
}
private fun IrConstructor.isAutogeneratedSimpleConstructor(): Boolean {
if (!this.isPrimary) return false
if (valueParameters.isNotEmpty()) return false
val statements = this.body?.statements ?: return false
if (statements.isEmpty()) return false
val constructorCall = statements[0] as? IrDelegatingConstructorCall ?: return false
val constructor = constructorCall.symbol.owner as? IrConstructor ?: return false
if (!constructor.constructedClass.isAny()) return false
return statements.asSequence().drop(1).all {
it is IrBlock && it.origin == IrStatementOrigin.INITIALIZE_FIELD
}
}
private fun IrClass.hasConstStateAndNoSideEffects(): Boolean {
if (this.hasAnnotation(KonanFqNames.canBePrecreated)) return true
val fields = context.getLayoutBuilder(this).fields
return fields.all { it.isConst } && this.constructors.all { it.isAutogeneratedSimpleConstructor() }
}
companion object {
val companionFieldName = "companionField".synthesizedName
val instanceFieldName = "instanceField".synthesizedName
}
// This is a hack to avoid early freezing. Should be removed when freezing is removed
object IrStatementOriginFieldPreInit : IrStatementOriginImpl("FIELD_PRE_INIT")
}
@@ -9,13 +9,11 @@ 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.ir.IrElement
import org.jetbrains.kotlin.ir.builders.irGetObject
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.isNullableNothing
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
@@ -38,7 +36,7 @@ internal class ReturnsInsertionLowering(val context: Context) : FileLoweringPass
body as IrBlockBody
context.createIrBuilder(declaration.symbol, declaration.endOffset, declaration.endOffset).run {
if (declaration is IrConstructor || declaration.returnType == context.irBuiltIns.unitType) {
body.statements += irReturn(irGetObject(symbols.unit))
body.statements += irReturn(irCall(symbols.theUnitInstance, context.irBuiltIns.unitType))
} else if (declaration.returnType.isNullable()) {
// this is a workaround for KT-42832
val typeOperatorCall = body.statements.lastOrNull() as? IrTypeOperatorCall
@@ -56,7 +54,7 @@ internal class ReturnsInsertionLowering(val context: Context) : FileLoweringPass
if (expression.inlineFunctionSymbol?.owner?.returnType == context.irBuiltIns.unitType) {
val offset = (expression.statements.lastOrNull() ?: expression).endOffset
context.createIrBuilder(expression.symbol, offset, offset).run {
expression.statements += irReturn(irGetObject(symbols.unit))
expression.statements += irReturn(irCall(symbols.theUnitInstance, context.irBuiltIns.unitType))
}
}
}
@@ -11,9 +11,7 @@ import org.jetbrains.kotlin.backend.konan.ConfigChecks
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.DECLARATION_ORIGIN_ENTRY_POINT
import org.jetbrains.kotlin.backend.konan.KonanFqNames
import org.jetbrains.kotlin.backend.konan.llvm.FieldStorageKind
import org.jetbrains.kotlin.backend.konan.llvm.needsGCRegistration
import org.jetbrains.kotlin.backend.konan.llvm.storageKind
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.ir.isBoxOrUnboxCall
import org.jetbrains.kotlin.backend.konan.util.IntArrayList
import org.jetbrains.kotlin.backend.konan.util.LongArrayList
import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
@@ -106,7 +107,7 @@ internal object DevirtualizationAnalysis {
context.getLayoutBuilder(declaration).associatedObjects.values.forEach {
assert(it.kind == ClassKind.OBJECT) { "An object expected but was ${it.dump()}" }
associatedObjectConstructors += moduleDFG.symbolTable.mapFunction(it.constructors.single())
associatedObjectConstructors += moduleDFG.symbolTable.mapFunction(context.getObjectClassInstanceFunction(it))
}
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.backend.konan.optimizations
import org.jetbrains.kotlin.backend.common.atMostOne
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlock
import org.jetbrains.kotlin.backend.konan.Context
@@ -21,7 +20,6 @@ import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyClass
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrReturnTargetSymbol
import org.jetbrains.kotlin.ir.util.*
@@ -427,15 +425,7 @@ internal object StaticInitializersOptimization {
}
override fun visitGetObjectValue(expression: IrGetObjectValue, data: BitSet): BitSet {
val objectClass = expression.symbol.owner
val constructor = objectClass.constructors.toList().atMostOne()
if (constructor != null) {
updateResultForFunction(constructor, data)
} else {
require(objectClass.isExternal || objectClass is IrLazyClass) { "No constructor for ${objectClass.render()}" }
}
// Don't update [data] as the constructor might not be called here (the object might be initialized already).
return data
error("IrGetObjectValue should be lowered away at this point")
}
private fun processCall(expression: IrFunctionAccessExpression, actualCallee: IrFunction, data: BitSet): BitSet {
@@ -24,12 +24,12 @@ func testKotlin2Objc() throws {
func testCompanionObject() throws {
let trace = Foo.companion.trace
try assertTrue(trace[6].contains("objc2kotlin_kclass:Foo#companion"))
try assertTrue(trace[8].contains("objc2kotlin_kclass:Foo#companion"))
}
func testStandaloneObject() throws {
let trace = Object.shared.trace
try assertTrue(trace[6].contains("objc2kotlin_kclass:Object#shared"))
try assertTrue(trace[8].contains("objc2kotlin_kclass:Object#shared"))
}
func testEnumEntry() throws {
@@ -31,8 +31,6 @@ touchType(KRefSharedHolder)
touchFunction(AllocInstance)
touchFunction(AllocArrayInstance)
touchFunction(InitThreadLocalSingleton)
touchFunction(InitSingleton)
touchFunction(InitAndRegisterGlobal)
touchFunction(UpdateHeapRef)
touchFunction(UpdateStackRef)
@@ -2373,104 +2373,6 @@ OBJ_GETTER(allocArrayInstance, const TypeInfo* type_info, int32_t elements) {
RETURN_OBJ(container.GetPlace()->obj());
}
template <bool Strict>
OBJ_GETTER(initThreadLocalSingleton,
ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
ObjHeader* value = *location;
if (value != nullptr) {
// OK'ish, inited by someone else.
RETURN_OBJ(value);
}
ObjHeader* object = allocInstance<Strict>(typeInfo, OBJ_RESULT);
updateHeapRef<Strict>(location, object);
#if KONAN_NO_EXCEPTIONS
ctor(object);
return object;
#else
try {
ctor(object);
return object;
} catch (...) {
UpdateReturnRef(OBJ_RESULT, nullptr);
ZeroHeapRef(location);
throw;
}
#endif
}
template <bool Strict>
OBJ_GETTER(initSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
#if KONAN_NO_THREADS
ObjHeader* value = *location;
if (value != nullptr) {
// OK'ish, inited by someone else.
RETURN_OBJ(value);
}
ObjHeader* object = AllocInstance(typeInfo, OBJ_RESULT);
UpdateHeapRef(location, object);
#if KONAN_NO_EXCEPTIONS
ctor(object);
FreezeSubgraph(object);
return object;
#else
try {
ctor(object);
if (Strict)
FreezeSubgraph(object);
return object;
} catch (...) {
UpdateReturnRef(OBJ_RESULT, nullptr);
ZeroHeapRef(location);
throw;
}
#endif // KONAN_NO_EXCEPTIONS
#else // KONAN_NO_THREADS
// Search from the top of the stack.
for (auto it = memoryState->initializingSingletons.rbegin(); it != memoryState->initializingSingletons.rend(); ++it) {
if (it->first == location) {
RETURN_OBJ(it->second);
}
}
ObjHeader* initializing = kInitializingSingleton;
// Spin lock.
ObjHeader* value = nullptr;
while ((value = __sync_val_compare_and_swap(location, nullptr, initializing)) == initializing);
if (value != nullptr) {
// OK'ish, inited by someone else.
RETURN_OBJ(value);
}
ObjHeader* object = AllocInstance(typeInfo, OBJ_RESULT);
memoryState->initializingSingletons.push_back(std::make_pair(location, object));
#if KONAN_NO_EXCEPTIONS
ctor(object);
if (Strict)
FreezeSubgraph(object);
UpdateHeapRef(location, object);
synchronize();
memoryState->initializingSingletons.pop_back();
return object;
#else // KONAN_NO_EXCEPTIONS
try {
ctor(object);
if (Strict)
FreezeSubgraph(object);
UpdateHeapRef(location, object);
synchronize();
memoryState->initializingSingletons.pop_back();
return object;
} catch (...) {
UpdateReturnRef(OBJ_RESULT, nullptr);
zeroHeapRef(location);
memoryState->initializingSingletons.pop_back();
synchronize();
throw;
}
#endif // KONAN_NO_EXCEPTIONS
#endif // KONAN_NO_THREADS
}
/**
* We keep thread affinity and reference value based cookie in the atomic references, so that
* repeating read operation of the same value do not lead to the repeating rememberNewContainer() operation.
@@ -3398,20 +3300,6 @@ OBJ_GETTER(AllocArrayInstanceRelaxed, const TypeInfo* typeInfo, int32_t elements
RETURN_RESULT_OF(allocArrayInstance<false>, typeInfo, elements);
}
OBJ_GETTER(InitThreadLocalSingletonStrict, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(initThreadLocalSingleton<true>, location, typeInfo, ctor);
}
OBJ_GETTER(InitThreadLocalSingletonRelaxed, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(initThreadLocalSingleton<false>, location, typeInfo, ctor);
}
OBJ_GETTER(InitSingletonStrict, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(initSingleton<true>, location, typeInfo, ctor);
}
OBJ_GETTER(InitSingletonRelaxed, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(initSingleton<false>, location, typeInfo, ctor);
}
void RUNTIME_NOTHROW InitAndRegisterGlobal(ObjHeader** location, const ObjHeader* initialValue) {
RuntimeCheck(false, "Global registration is impossible in legacy MM");
}
@@ -309,11 +309,6 @@ OBJ_GETTER(AllocInstanceRelaxed, const TypeInfo* type_info) RUNTIME_NOTHROW;
OBJ_GETTER(AllocArrayInstanceStrict, const TypeInfo* type_info, int32_t elements);
OBJ_GETTER(AllocArrayInstanceRelaxed, const TypeInfo* type_info, int32_t elements);
OBJ_GETTER(InitThreadLocalSingletonStrict, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
OBJ_GETTER(InitThreadLocalSingletonRelaxed, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
OBJ_GETTER(InitSingletonStrict, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
OBJ_GETTER(InitSingletonRelaxed, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
MODEL_VARIANTS(void, SetStackRef, ObjHeader** location, const ObjHeader* object);
MODEL_VARIANTS(void, SetHeapRef, ObjHeader** location, const ObjHeader* object);
@@ -25,8 +25,6 @@ void ensureUsed(Ret (*f)(Args...)) {
void EnsureDeclarationsEmitted() {
ensureUsed(AllocInstance);
ensureUsed(AllocArrayInstance);
ensureUsed(InitThreadLocalSingleton);
ensureUsed(InitSingleton);
ensureUsed(InitAndRegisterGlobal);
ensureUsed(UpdateHeapRef);
ensureUsed(UpdateStackRef);
@@ -83,6 +83,7 @@ void VLog(Level level, std::initializer_list<const char*> tags, const char* form
inline constexpr const char* kTagGC = "gc";
inline constexpr const char* kTagMM = "mm";
inline constexpr const char* kTagTLS = "tls";
} // namespace kotlin
@@ -195,9 +195,6 @@ OBJ_GETTER(AllocInstance, const TypeInfo* type_info) RUNTIME_NOTHROW;
OBJ_GETTER(AllocArrayInstance, const TypeInfo* type_info, int32_t elements);
OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
OBJ_GETTER(InitSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
// `initialValue` may be `nullptr`, which signifies that the appropriate initial value was already
// set by static initialization.
@@ -53,6 +53,7 @@ class IntrinsicType {
const val IMMUTABLE_BLOB = "IMMUTABLE_BLOB"
const val INIT_INSTANCE = "INIT_INSTANCE"
const val IS_EXPERIMENTAL_MM = "IS_EXPERIMENTAL_MM"
const val THE_UNIT_INSTANCE = "THE_UNIT_INSTANCE"
// Enums
const val ENUM_VALUES = "ENUM_VALUES"
@@ -30,3 +30,6 @@ import kotlin.native.internal.IntrinsicType
// Reinterprets this value from T to R having the same binary representation (e.g. to unwrap inline class).
@TypedIntrinsic(IntrinsicType.IDENTITY) @PublishedApi external internal fun <T, R> T.reinterpret(): R
@TypedIntrinsic(IntrinsicType.THE_UNIT_INSTANCE) @ExportForCompiler external internal fun theUnitInstance(): Unit
@@ -1,80 +0,0 @@
/*
* 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.
*/
#include "InitializationScheme.hpp"
#include "Common.h"
#include "ObjectOps.hpp"
#include "ThreadData.hpp"
#include "ThreadState.hpp"
using namespace kotlin;
OBJ_GETTER(mm::InitThreadLocalSingleton, ThreadData* threadData, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
AssertThreadState(threadData, ThreadState::kRunnable);
if (auto* value = *location) {
// Initialized by someone else.
RETURN_OBJ(value);
}
auto* value = mm::AllocateObject(threadData, typeInfo, OBJ_RESULT);
mm::SetHeapRef(location, value);
#if KONAN_NO_EXCEPTIONS
ctor(value);
#else
try {
ctor(value);
} catch (...) {
mm::SetStackRef(OBJ_RESULT, nullptr);
mm::SetHeapRef(location, nullptr);
throw;
}
#endif
return value;
}
OBJ_GETTER(mm::InitSingleton, ThreadData* threadData, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
AssertThreadState(threadData, ThreadState::kRunnable);
auto& initializingSingletons = threadData->initializingSingletons();
// Search from the top of the stack.
for (auto it = initializingSingletons.rbegin(); it != initializingSingletons.rend(); ++it) {
if (it->first == location) {
RETURN_OBJ(it->second);
}
}
ObjHeader* initializing = kInitializingSingleton;
// Spin lock.
ObjHeader* value = nullptr;
{
ThreadStateGuard guard(ThreadState::kNative);
while ((value = __sync_val_compare_and_swap(location, nullptr, initializing)) == initializing) {
}
}
if (value != nullptr) {
// Initialized by someone else.
RETURN_OBJ(value);
}
auto* object = mm::AllocateObject(threadData, typeInfo, OBJ_RESULT);
initializingSingletons.push_back(std::make_pair(location, object));
#if KONAN_NO_EXCEPTIONS
ctor(object);
#else
try {
ctor(object);
} catch (...) {
mm::SetStackRef(OBJ_RESULT, nullptr);
mm::SetHeapRefAtomic(location, nullptr);
initializingSingletons.pop_back();
throw;
}
#endif
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(threadData, location);
mm::SetHeapRefAtomic(location, object);
initializingSingletons.pop_back();
return object;
}
@@ -1,22 +0,0 @@
/*
* 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.
*/
#ifndef RUNTIME_MM_INITIALIZATION_SCHEME_H
#define RUNTIME_MM_INITIALIZATION_SCHEME_H
#include "Memory.h"
namespace kotlin {
namespace mm {
class ThreadData;
OBJ_GETTER(InitThreadLocalSingleton, ThreadData* threadData, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
OBJ_GETTER(InitSingleton, ThreadData* threadData, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*));
} // namespace mm
} // namespace kotlin
#endif // RUNTIME_MM_INITIALIZATION_SCHEME_H
@@ -1,270 +0,0 @@
/*
* 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.
*/
#include "InitializationScheme.hpp"
#include <atomic>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "ObjectTestSupport.hpp"
#include "ScopedThread.hpp"
#include "TestSupport.hpp"
#include "ThreadData.hpp"
#include "Types.h"
#include "std_support/Vector.hpp"
using namespace kotlin;
using testing::_;
namespace {
struct EmptyPayload {
using Field = ObjHeader* EmptyPayload::*;
static constexpr std::array<Field, 0> kFields{};
};
class InitSingletonTest : public testing::Test {
public:
InitSingletonTest() {
globalConstructor_ = &constructor_;
}
~InitSingletonTest() {
globalConstructor_ = nullptr;
mm::GlobalData::Instance().gc().ClearForTests();
mm::GlobalData::Instance().globalsRegistry().ClearForTests();
}
testing::MockFunction<void(ObjHeader*)>& constructor() { return constructor_; }
OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, mm::ThreadData& threadData) {
RETURN_RESULT_OF(mm::InitThreadLocalSingleton, &threadData, location, type_.typeInfo(), constructorImpl);
}
OBJ_GETTER(InitSingleton, ObjHeader** location, mm::ThreadData& threadData) {
RETURN_RESULT_OF(mm::InitSingleton, &threadData, location, type_.typeInfo(), constructorImpl);
}
private:
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> constructor_;
test_support::TypeInfoHolder type_{test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
static testing::MockFunction<void(ObjHeader*)>* globalConstructor_;
static void constructorImpl(ObjHeader* object) { globalConstructor_->Call(object); }
};
// static
testing::MockFunction<void(ObjHeader*)>* InitSingletonTest::globalConstructor_ = nullptr;
} // namespace
TEST_F(InitSingletonTest, InitThreadLocalSingleton) {
RunInNewThread([this](mm::ThreadData& threadData) {
ObjHeader* location = nullptr;
ObjHeader* stackLocation = nullptr;
ObjHeader* valueAtConstructor = nullptr;
EXPECT_CALL(constructor(), Call(_)).WillOnce(
[&location, &stackLocation, &valueAtConstructor](ObjHeader* value) {
EXPECT_THAT(value, stackLocation);
EXPECT_THAT(value, location);
valueAtConstructor = value;
});
ObjHeader* value = InitThreadLocalSingleton(&location, threadData, &stackLocation);
EXPECT_THAT(value, stackLocation);
EXPECT_THAT(value, location);
EXPECT_THAT(valueAtConstructor, location);
});
}
TEST_F(InitSingletonTest, InitThreadLocalSingletonTwice) {
RunInNewThread([this](mm::ThreadData& threadData) {
ObjHeader previousValue;
ObjHeader* location = &previousValue;
ObjHeader* stackLocation = nullptr;
EXPECT_CALL(constructor(), Call(_)).Times(0);
ObjHeader* value = InitThreadLocalSingleton(&location, threadData, &stackLocation);
EXPECT_THAT(value, stackLocation);
EXPECT_THAT(value, location);
EXPECT_THAT(value, &previousValue);
});
}
TEST_F(InitSingletonTest, InitThreadLocalSingletonFail) {
RunInNewThread([this](mm::ThreadData& threadData) {
ObjHeader* location = nullptr;
ObjHeader* stackLocation = nullptr;
constexpr int kException = 42;
EXPECT_CALL(constructor(), Call(_)).WillOnce([]() { throw kException; });
try {
InitThreadLocalSingleton(&location, threadData, &stackLocation);
ASSERT_TRUE(false); // Cannot be reached.
} catch (int exception) {
EXPECT_THAT(exception, kException);
}
EXPECT_THAT(stackLocation, nullptr);
EXPECT_THAT(location, nullptr);
});
}
TEST_F(InitSingletonTest, InitSingleton) {
RunInNewThread([this](mm::ThreadData& threadData) {
ObjHeader* location = nullptr;
ObjHeader* stackLocation = nullptr;
ObjHeader* valueAtConstructor = nullptr;
EXPECT_CALL(constructor(), Call(_)).WillOnce(
[&location, &stackLocation, &valueAtConstructor](ObjHeader* value) {
EXPECT_THAT(value, stackLocation);
EXPECT_THAT(location, kInitializingSingleton);
valueAtConstructor = value;
});
ObjHeader* value = InitSingleton(&location, threadData, &stackLocation);
EXPECT_THAT(value, stackLocation);
EXPECT_THAT(value, location);
EXPECT_THAT(valueAtConstructor, location);
});
}
TEST_F(InitSingletonTest, InitSingletonTwice) {
RunInNewThread([this](mm::ThreadData& threadData) {
ObjHeader previousValue;
ObjHeader* location = &previousValue;
ObjHeader* stackLocation = nullptr;
EXPECT_CALL(constructor(), Call(_)).Times(0);
ObjHeader* value = InitSingleton(&location, threadData, &stackLocation);
EXPECT_THAT(value, stackLocation);
EXPECT_THAT(value, location);
EXPECT_THAT(value, &previousValue);
});
}
TEST_F(InitSingletonTest, InitSingletonFail) {
RunInNewThread([this](mm::ThreadData& threadData) {
ObjHeader* location = nullptr;
ObjHeader* stackLocation = nullptr;
constexpr int kException = 42;
EXPECT_CALL(constructor(), Call(_)).WillOnce([]() { throw kException; });
try {
InitSingleton(&location, threadData, &stackLocation);
ASSERT_TRUE(false); // Cannot be reached.
} catch (int exception) {
EXPECT_THAT(exception, kException);
}
EXPECT_THAT(stackLocation, nullptr);
EXPECT_THAT(location, nullptr);
});
}
TEST_F(InitSingletonTest, InitSingletonRecursive) {
RunInNewThread([this](mm::ThreadData& threadData) {
// The first singleton. Its constructor depends on the second singleton.
ObjHeader* location1 = nullptr;
ObjHeader* stackLocation1 = nullptr;
// The second singleton. Its constructor depends on the first singleton.
ObjHeader* location2 = nullptr;
ObjHeader* stackLocation2 = nullptr;
EXPECT_CALL(constructor(), Call(_))
.Times(2) // called only once for each singleton.
.WillRepeatedly([this, &location1, &stackLocation1, &location2, &stackLocation2, &threadData](ObjHeader* value) {
if (value == stackLocation1) {
ObjHeader* result = InitSingleton(&location2, threadData, &stackLocation2);
EXPECT_THAT(result, stackLocation2);
EXPECT_THAT(result, location2);
EXPECT_THAT(result, testing::Not(testing::Truly(isNullOrMarker)));
} else {
ObjHeader* result = InitSingleton(&location1, threadData, &stackLocation1);
EXPECT_THAT(result, stackLocation1);
EXPECT_THAT(result, testing::Ne(location1));
EXPECT_THAT(location1, kInitializingSingleton);
}
});
ObjHeader* value = InitSingleton(&location1, threadData, &stackLocation1);
EXPECT_THAT(value, stackLocation1);
EXPECT_THAT(value, location1);
});
}
TEST_F(InitSingletonTest, InitSingletonConcurrent) {
constexpr size_t kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<size_t> readyCount(0);
std_support::vector<ScopedThread> threads;
ObjHeader* location = nullptr;
std_support::vector<ObjHeader*> stackLocations(kThreadCount, nullptr);
std_support::vector<ObjHeader*> actual(kThreadCount, nullptr);
for (size_t i = 0; i < kThreadCount; ++i) {
threads.emplace_back([this, i, &location, &stackLocations, &actual, &readyCount, &canStart]() {
ScopedMemoryInit init;
auto* threadData = init.memoryState()->GetThreadData();
++readyCount;
while (!canStart) {
}
actual[i] = InitSingleton(&location, *threadData, &stackLocations[i]);
threadData->Publish();
});
}
while (readyCount < kThreadCount) {
}
// Constructor is called exactly once.
EXPECT_CALL(constructor(), Call(_));
canStart = true;
threads.clear();
testing::Mock::VerifyAndClearExpectations(&constructor());
EXPECT_THAT(location, testing::Not(testing::Truly(isNullOrMarker)));
EXPECT_THAT(stackLocations, testing::Each(location));
EXPECT_THAT(actual, testing::Each(location));
}
TEST_F(InitSingletonTest, InitSingletonConcurrentFailing) {
constexpr size_t kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<size_t> readyCount(0);
std_support::vector<ScopedThread> threads;
constexpr int kException = 42;
ObjHeader* location = nullptr;
std_support::vector<ObjHeader*> stackLocations(kThreadCount, nullptr);
for (size_t i = 0; i < kThreadCount; ++i) {
threads.emplace_back([this, i, &location, &stackLocations, &readyCount, &canStart]() {
ScopedMemoryInit init;
auto* threadData = init.memoryState()->GetThreadData();
++readyCount;
while (!canStart) {
}
try {
InitSingleton(&location, *threadData, &stackLocations[i]);
ASSERT_TRUE(false); // Cannot be reached.
} catch (int exception) {
EXPECT_THAT(exception, kException);
}
threadData->Publish();
});
}
while (readyCount < kThreadCount) {
}
// Constructor is called exactly `kThreadCount` times.
EXPECT_CALL(constructor(), Call(_)).Times(kThreadCount).WillRepeatedly([]() { throw kException; });
canStart = true;
threads.clear();
testing::Mock::VerifyAndClearExpectations(&constructor());
EXPECT_THAT(location, nullptr);
EXPECT_THAT(stackLocations, testing::Each(nullptr));
}
@@ -11,7 +11,6 @@
#include "Freezing.hpp"
#include "GC.hpp"
#include "GlobalsRegistry.hpp"
#include "InitializationScheme.hpp"
#include "KAssert.h"
#include "Natives.h"
#include "ObjectOps.hpp"
@@ -146,18 +145,6 @@ extern "C" OBJ_GETTER(AllocArrayInstance, const TypeInfo* typeInfo, int32_t elem
RETURN_RESULT_OF(mm::AllocateArray, threadData, typeInfo, static_cast<uint32_t>(elements));
}
extern "C" ALWAYS_INLINE OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
RETURN_RESULT_OF(mm::InitThreadLocalSingleton, threadData, location, typeInfo, ctor);
}
extern "C" ALWAYS_INLINE OBJ_GETTER(InitSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
RETURN_RESULT_OF(mm::InitSingleton, threadData, location, typeInfo, ctor);
}
extern "C" RUNTIME_NOTHROW void InitAndRegisterGlobal(ObjHeader** location, const ObjHeader* initialValue) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
AssertThreadState(threadData, ThreadState::kRunnable);
@@ -4,12 +4,14 @@
*/
#include "ThreadLocalStorage.hpp"
#include "Logging.hpp"
using namespace kotlin;
void mm::ThreadLocalStorage::AddRecord(Key key, int size) noexcept {
RuntimeAssert(state_ == State::kBuilding, "Storage must be in the building state");
RuntimeAssert(size >= 0, "Size cannot be negative");
RuntimeLogDebug({kTagTLS}, "Add record key = %p, size = %d\n", key, size);
auto it = map_.find(key);
if (it != map_.end()) {
RuntimeAssert(it->second.size == size, "Attempt to add TLS record with the same key, but different size");
@@ -20,12 +22,14 @@ void mm::ThreadLocalStorage::AddRecord(Key key, int size) noexcept {
}
void mm::ThreadLocalStorage::Commit() noexcept {
RuntimeLogDebug({kTagTLS}, "Committed");
RuntimeAssert(state_ == State::kBuilding, "Storage must be in the building state");
storage_.resize(size_);
state_ = State::kCommitted;
}
void mm::ThreadLocalStorage::Clear() noexcept {
RuntimeLogDebug({kTagTLS}, "Cleared");
RuntimeAssert(state_ == State::kCommitted, "Storage must be in the committed state");
// Just free the storage.
storage_.clear();
@@ -33,6 +37,7 @@ void mm::ThreadLocalStorage::Clear() noexcept {
}
ObjHeader** mm::ThreadLocalStorage::Lookup(Key key, int index) noexcept {
RuntimeLogDebug({kTagTLS}, "Lookup key = %p, index = %d", key, index);
RuntimeAssert(state_ == State::kCommitted, "Storage must be in the committed state");
if (lastKeyAndEntry_.first == key) {
return Lookup(lastKeyAndEntry_.second, index);
@@ -44,6 +49,7 @@ ObjHeader** mm::ThreadLocalStorage::Lookup(Key key, int index) noexcept {
}
ObjHeader** mm::ThreadLocalStorage::Lookup(Entry entry, int index) noexcept {
RuntimeLogDebug({kTagTLS}, "Lookup entry = {%d, %d}, index = %d", entry.offset, entry.size, index);
RuntimeAssert(index < entry.size, "Out of bounds TLS access");
return &storage_[entry.offset + index];
}
@@ -19,14 +19,6 @@ OBJ_GETTER(AllocArrayInstance, const TypeInfo* typeInfo, int32_t elements) {
RETURN_RESULT_OF(AllocArrayInstanceRelaxed, typeInfo, elements);
}
OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(InitThreadLocalSingletonRelaxed, location, typeInfo, ctor);
}
OBJ_GETTER(InitSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(InitSingletonRelaxed, location, typeInfo, ctor);
}
RUNTIME_NOTHROW void ReleaseHeapRef(const ObjHeader* object) {
ReleaseHeapRefRelaxed(object);
}
@@ -19,14 +19,6 @@ OBJ_GETTER(AllocArrayInstance, const TypeInfo* typeInfo, int32_t elements) {
RETURN_RESULT_OF(AllocArrayInstanceStrict, typeInfo, elements);
}
OBJ_GETTER(InitThreadLocalSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(InitThreadLocalSingletonStrict, location, typeInfo, ctor);
}
OBJ_GETTER(InitSingleton, ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) {
RETURN_RESULT_OF(InitSingletonStrict, location, typeInfo, ctor);
}
RUNTIME_NOTHROW void ReleaseHeapRef(const ObjHeader* object) {
ReleaseHeapRefStrict(object);
}