[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.
This commit is contained in:
Sergey Bogolepov
2020-09-03 15:12:12 +07:00
committed by Sergey Bogolepov
parent 9ea3ee9ad2
commit 31bad64728
6 changed files with 239 additions and 46 deletions
@@ -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<IrClass, IrField>()
private val loweredEnums = mutableMapOf<IrClass, LoweredEnum>()
private val ordinals = mutableMapOf<ClassDescriptor, Map<ClassDescriptor, Int>>()
private val internalLoweredEnums = mutableMapOf<IrClass, InternalLoweredEnum>()
private val externalLoweredEnums = mutableMapOf<IrClass, ExternalLoweredEnum>()
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<String, LLVMTypeRef> = HashMap()
/**
* Manages internal ABI references and declarations.
*/
val internalAbi = InternalAbi(this)
}
private fun MemberScope.getContributedClassifier(name: String) =
@@ -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<Name, Int>)
/**
* 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<Name, Int>
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<Name, Int>
) : 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<Name, Int>
) : 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<Name, Int> =
enumClass.declarations
.filterIsInstance<IrEnumEntry>()
.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<IrEnumEntry>()
.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)
@@ -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<ModuleDescriptor, IrFile>()
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") {}
}
}
@@ -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")
}
@@ -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() {
@@ -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() {