From 31bad64728c5490969000a8e451165f9079761bc Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Thu, 3 Sep 2020 15:12:12 +0700 Subject: [PATCH] [Linkage][Lowering] InternalABI To effectively access enum entries during separate compilation (in other words, when compiling with caches), we need to reference Enum.$OBJECT.get-VALUES function. It is not a part of a public API because it is generated during EnumClassLowering. What are possible solutions? 1. Lazy IR. It is no-go, because there is no descriptor for it. 2. Generate IrFiles and appropriate packages during lowering. We can't do it, because lowerings are file-based and ConcurrentModificationException will occur. 2.1. Store declarations in list and add file later? Still not an option, because .parent (or module) for these declaration won't be initialized which breaks lowerings. 3. Something ugly. Here we go! We add a special file before lowerings which stores all declaration that form an internal ABI. --- .../jetbrains/kotlin/backend/konan/Context.kt | 31 +++- .../konan/EnumSpecialDescriptorsFactory.kt | 144 +++++++++++++++--- .../kotlin/backend/konan/InternalAbi.kt | 78 ++++++++++ .../konan/descriptors/DescriptorUtils.kt | 2 + .../kotlin/backend/konan/llvm/ContextUtils.kt | 3 +- .../backend/konan/lower/EnumClassLowering.kt | 27 +--- 6 files changed, 239 insertions(+), 46 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InternalAbi.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 04f68af0734..3ba5dc135ef 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -55,8 +55,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal internal class SpecialDeclarationsFactory(val context: Context) { private val enumSpecialDeclarationsFactory by lazy { EnumSpecialDeclarationsFactory(context) } private val outerThisFields = mutableMapOf() - private val loweredEnums = mutableMapOf() - private val ordinals = mutableMapOf>() + private val internalLoweredEnums = mutableMapOf() + private val externalLoweredEnums = mutableMapOf() private data class BridgeKey(val target: IrSimpleFunction, val bridgeDirections: BridgeDirections) @@ -103,10 +103,24 @@ internal class SpecialDeclarationsFactory(val context: Context) { } } - fun getLoweredEnum(enumClass: IrClass): LoweredEnum { + fun getLoweredEnum(enumClass: IrClass): LoweredEnumAccess { assert(enumClass.kind == ClassKind.ENUM_CLASS) { "Expected enum class but was: ${enumClass.descriptor}" } - return loweredEnums.getOrPut(enumClass) { - enumSpecialDeclarationsFactory.createLoweredEnum(enumClass) + return if (!context.llvmModuleSpecification.containsDeclaration(enumClass)) { + externalLoweredEnums.getOrPut(enumClass) { + enumSpecialDeclarationsFactory.createExternalLoweredEnum(enumClass) + } + } else { + internalLoweredEnums.getOrPut(enumClass) { + enumSpecialDeclarationsFactory.createInternalLoweredEnum(enumClass) + } + } + } + + fun getInternalLoweredEnum(enumClass: IrClass): InternalLoweredEnum { + assert(enumClass.kind == ClassKind.ENUM_CLASS) { "Expected enum class but was: ${enumClass.descriptor}" } + assert(context.llvmModuleSpecification.containsDeclaration(enumClass)) { "Expected enum class from current module." } + return internalLoweredEnums.getOrPut(enumClass) { + enumSpecialDeclarationsFactory.createInternalLoweredEnum(enumClass) } } @@ -278,7 +292,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { throw Error("Another IrModule in the context.") } field = module!! - + internalAbi.init(module) ir = KonanIr(this, module) } @@ -419,6 +433,11 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { ) val declaredLocalArrays: MutableMap = HashMap() + + /** + * Manages internal ABI references and declarations. + */ + val internalAbi = InternalAbi(this) } private fun MemberScope.getContributedClassifier(name: String) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt index 12615faeb08..44d1d9c4bf4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt @@ -8,10 +8,15 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.common.ir.addFakeOverridesViaIncorrectHeuristic import org.jetbrains.kotlin.backend.common.ir.addSimpleDelegatingConstructor import org.jetbrains.kotlin.backend.common.ir.createParameterDeclarations +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.irBlockBody +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFieldImpl @@ -19,27 +24,131 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.descriptors.WrappedClassDescriptor import org.jetbrains.kotlin.ir.descriptors.WrappedFieldDescriptor import org.jetbrains.kotlin.ir.descriptors.WrappedSimpleFunctionDescriptor +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.module internal object DECLARATION_ORIGIN_ENUM : IrDeclarationOriginImpl("ENUM") -internal data class LoweredEnum(val implObject: IrClass, - val valuesField: IrField, - val valuesGetter: IrSimpleFunction, - val itemGetterSymbol: IrSimpleFunctionSymbol, - val entriesMap: Map) +/** + * Common interface for both [InternalLoweredEnum] and [ExternalLoweredEnum] + * that allows to work with lowered enum regardless of its location. + */ +internal interface LoweredEnumAccess { + val valuesGetter: IrSimpleFunction + val itemGetterSymbol: IrSimpleFunctionSymbol + val entriesMap: Map + fun getValuesField(startOffset: Int, endOffset: Int): IrExpression +} + +/** + * Represents lowered enum from current module. + */ +internal data class InternalLoweredEnum( + val implObject: IrClass, + val valuesField: IrField, + val valuesGetterWrapper: IrSimpleFunction, + override val valuesGetter: IrSimpleFunction, + override val itemGetterSymbol: IrSimpleFunctionSymbol, + override val entriesMap: Map +) : LoweredEnumAccess { + private fun internalObjectGetter(startOffset: Int, endOffset: Int) = + IrGetObjectValueImpl(startOffset, endOffset, + implObject.defaultType, + implObject.symbol + ) + + override fun getValuesField(startOffset: Int, endOffset: Int): IrExpression = IrGetFieldImpl( + startOffset, + endOffset, + valuesField.symbol, + valuesField.type, + internalObjectGetter(startOffset, endOffset) + ) +} + +/** + * Represents lowered enum that's located in external module. + */ +internal data class ExternalLoweredEnum( + override val valuesGetter: IrSimpleFunction, + override val itemGetterSymbol: IrSimpleFunctionSymbol, + override val entriesMap: Map +) : LoweredEnumAccess { + override fun getValuesField(startOffset: Int, endOffset: Int): IrExpression = + IrCallImpl(startOffset, endOffset, valuesGetter.returnType, valuesGetter.symbol) +} internal class EnumSpecialDeclarationsFactory(val context: Context) { private val symbols = context.ir.symbols - fun createLoweredEnum(enumClass: IrClass): LoweredEnum { + private fun enumEntriesMap(enumClass: IrClass): Map = + enumClass.declarations + .filterIsInstance() + .sortedBy { it.name } + .withIndex() + .associate { it.value.name to it.index } + .toMap() + + private fun findItemGetterSymbol(): IrSimpleFunctionSymbol = + symbols.array.functions.single { it.descriptor.name == Name.identifier("get") } + + private fun valuesArrayType(enumClass: IrClass): IrType = + symbols.array.typeWith(enumClass.defaultType) + + // We can't move property getter to the top-level scope. + // So add a wrapper instead. + private fun createValuesGetterWrapper(enumClass: IrClass, isExternal: Boolean): IrSimpleFunction { + return WrappedSimpleFunctionDescriptor().let { + val valuesType = valuesArrayType(enumClass) + val origin = if (isExternal) InternalAbi.INTERNAL_ABI_ORIGIN else DECLARATION_ORIGIN_ENUM + val name = context.internalAbi.getMangledNameFor("get-VALUES", enumClass) + IrFunctionImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + origin, + IrSimpleFunctionSymbolImpl(it), + name, + DescriptorVisibilities.PUBLIC, + Modality.FINAL, + valuesType, + isInline = false, + isExternal = isExternal, + isTailrec = false, + isSuspend = false, + isExpect = false, + isFakeOverride = false, + isOperator = false, + isInfix = false + ).apply { + it.bind(this) + if (isExternal) { + context.internalAbi.reference(this, enumClass.module) + } else { + context.internalAbi.declare(this) + } + } + } + } + + fun createExternalLoweredEnum(enumClass: IrClass): ExternalLoweredEnum { + val enumEntriesMap = enumEntriesMap(enumClass) + val itemGetterSymbol = findItemGetterSymbol() + val valuesGetterWrapper = createValuesGetterWrapper(enumClass, isExternal = true) + return ExternalLoweredEnum(valuesGetterWrapper, itemGetterSymbol, enumEntriesMap) + } + + fun createInternalLoweredEnum(enumClass: IrClass): InternalLoweredEnum { val startOffset = enumClass.startOffset val endOffset = enumClass.endOffset @@ -66,7 +175,7 @@ internal class EnumSpecialDeclarationsFactory(val context: Context) { } } - val valuesType = symbols.array.typeWith(enumClass.defaultType) + val valuesType = valuesArrayType(enumClass) val valuesField = WrappedFieldDescriptor().let { IrFieldImpl( startOffset, endOffset, @@ -117,17 +226,18 @@ internal class EnumSpecialDeclarationsFactory(val context: Context) { implObject.superTypes += context.irBuiltIns.anyType implObject.addFakeOverridesViaIncorrectHeuristic() - val itemGetterSymbol = symbols.array.functions.single { it.descriptor.name == Name.identifier("get") } - val enumEntriesMap = enumClass.declarations - .filterIsInstance() - .sortedBy { it.name } - .withIndex() - .associate { it.value.name to it.index } - .toMap() - - return LoweredEnum( + val itemGetterSymbol = findItemGetterSymbol() + val enumEntriesMap = enumEntriesMap(enumClass) + val valuesGetterWrapper = createValuesGetterWrapper(enumClass, isExternal = false) + context.createIrBuilder(valuesGetterWrapper.symbol).run { + valuesGetterWrapper.body = irBlockBody { + +irReturn(irCall(valuesGetter)) + } + } + return InternalLoweredEnum( implObject, valuesField, + valuesGetterWrapper, valuesGetter, itemGetterSymbol, enumEntriesMap) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InternalAbi.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InternalAbi.kt new file mode 100644 index 00000000000..81b05c3653d --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InternalAbi.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2020 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 + +import org.jetbrains.kotlin.backend.common.ir.addChild +import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName +import org.jetbrains.kotlin.backend.konan.llvm.llvmSymbolOrigin +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrModuleFragmentImpl +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +/** + * Sometimes we need to reference symbols that are not declared in metadata. + * For example, symbol might be declared during lowering. + * 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 InternalAbi(private val context: Context) { + /** + * File that stores all internal ABI declarations. + * + * We have to store such declarations in top-level to avoid mangling that + * makes referencing harder. + * A bit better solution is to add files with proper packages, but it is impossible + * during FileLowering (hello, ConcurrentModificationException). + */ + private lateinit var internalAbiFile: IrFile + + /** + * Representation of ABI files from external modules. + */ + private val externalModules = mutableMapOf() + + fun init(module: IrModuleFragment) { + internalAbiFile = createAbiFile(module) + } + + private fun createAbiFile(module: IrModuleFragment): IrFile = + module.addFile(NaiveSourceBasedFileEntryImpl("internal"), FqName("kotlin.native.caches.abi")) + + /** + * Adds external [function] from [module] to a list of external references. + */ + fun reference(function: IrFunction, module: ModuleDescriptor) { + assert(function.isExternal) { "Function that represents external ABI should be marked as external" } + context.llvmImports.add(module.llvmSymbolOrigin) + externalModules.getOrPut(module) { + createAbiFile(IrModuleFragmentImpl(module, context.irBuiltIns)) + }.addChild(function) + } + + /** + * Generate name for declaration that will be a part of internal ABI. + */ + fun getMangledNameFor(declarationName: String, parent: IrDeclarationParent): Name { + val prefix = parent.fqNameForIrSerialization + return "$prefix.$declarationName".synthesizedName + } + + /** + * Adds [function] to a list of publicly available symbols. + */ + fun declare(function: IrFunction) { + internalAbiFile.addChild(function) + } + + companion object { + /** + * Allows to distinguish external declarations to internal ABI. + */ + val INTERNAL_ABI_ORIGIN = object : IrDeclarationOriginImpl("INTERNAL_ABI") {} + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 8e86cd3e360..3b5f13a5cb7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -297,6 +297,8 @@ fun IrFunction.externalSymbolOrThrow(): String? { if (annotations.hasAnnotation(RuntimeNames.cCall)) return null + if (origin == InternalAbi.INTERNAL_ABI_ORIGIN) return null + throw Error("external function ${this.longName} must have @TypedIntrinsic, @SymbolName or @ObjCMethod annotation") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 74d8d884641..6888ee44744 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -173,7 +173,8 @@ internal interface ContextUtils : RuntimeAware { * It may be declared as external function prototype. */ val IrFunction.llvmFunction: LLVMValueRef - get() = llvmFunctionOrNull ?: error("$name in $file/${parent.fqNameForIrSerialization}") + get() = llvmFunctionOrNull + ?: error("$name in $file/${parent.fqNameForIrSerialization}") val IrFunction.llvmFunctionOrNull: LLVMValueRef? get() { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumClassLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumClassLowering.kt index b2032bda7c0..c88cff06495 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumClassLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumClassLowering.kt @@ -31,7 +31,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name -internal class EnumSyntheticFunctionsBuilder(val context: Context) { +private class EnumSyntheticFunctionsBuilder(val context: Context) { fun buildValuesExpression(startOffset: Int, endOffset: Int, enumClass: IrClass): IrExpression { @@ -39,16 +39,7 @@ internal class EnumSyntheticFunctionsBuilder(val context: Context) { return irCall(startOffset, endOffset, genericValuesSymbol.owner, listOf(enumClass.defaultType)) .apply { - val receiver = IrGetObjectValueImpl(startOffset, endOffset, - loweredEnum.implObject.defaultType, - loweredEnum.implObject.symbol) - putValueArgument(0, IrGetFieldImpl( - startOffset, - endOffset, - loweredEnum.valuesField.symbol, - loweredEnum.valuesField.type, - receiver - )) + putValueArgument(0, loweredEnum.getValuesField(startOffset, endOffset)) } } @@ -60,15 +51,7 @@ internal class EnumSyntheticFunctionsBuilder(val context: Context) { return irCall(startOffset, endOffset, genericValueOfSymbol.owner, listOf(enumClass.defaultType)) .apply { putValueArgument(0, value) - val receiver = IrGetObjectValueImpl(startOffset, endOffset, - loweredEnum.implObject.defaultType, loweredEnum.implObject.symbol) - putValueArgument(1, IrGetFieldImpl( - startOffset, - endOffset, - loweredEnum.valuesField.symbol, - loweredEnum.valuesField.type, - receiver - )) + putValueArgument(1, loweredEnum.getValuesField(startOffset, endOffset)) } } @@ -126,7 +109,7 @@ internal class EnumUsageLowering(val context: Context) private fun loadEnumEntry(startOffset: Int, endOffset: Int, enumClass: IrClass, name: Name): IrExpression { val loweredEnum = context.specialDeclarationsFactory.getLoweredEnum(enumClass) - val ordinal = loweredEnum.entriesMap[name]!! + val ordinal = loweredEnum.entriesMap.getValue(name) return IrCallImpl( startOffset, endOffset, enumClass.defaultType, loweredEnum.itemGetterSymbol.owner.symbol, @@ -154,7 +137,7 @@ internal class EnumClassLowering(val context: Context) : ClassLoweringPass { } private inner class EnumClassTransformer(val irClass: IrClass) { - private val loweredEnum = context.specialDeclarationsFactory.getLoweredEnum(irClass) + private val loweredEnum = context.specialDeclarationsFactory.getInternalLoweredEnum(irClass) private val enumSyntheticFunctionsBuilder = EnumSyntheticFunctionsBuilder(context) fun run() {