Implemented DCE before code generation

This commit is contained in:
Igor Chevdar
2019-03-25 17:30:30 +03:00
parent 46edc3595d
commit 63d763338d
23 changed files with 308 additions and 165 deletions
@@ -19,6 +19,7 @@
package kotlinx.cinterop package kotlinx.cinterop
import kotlin.native.* import kotlin.native.*
import kotlin.native.internal.ExportTypeInfo import kotlin.native.internal.ExportTypeInfo
import kotlin.native.internal.ExportForCppRuntime
import kotlin.native.internal.TypedIntrinsic import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType import kotlin.native.internal.IntrinsicType
@@ -63,6 +64,7 @@ internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (t
@SymbolName("Kotlin_Interop_refFromObjC") @SymbolName("Kotlin_Interop_refFromObjC")
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T? external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
@ExportForCppRuntime
inline fun <T : Any> interpretObjCPointer(objcPtr: NativePtr): T = interpretObjCPointerOrNull<T>(objcPtr)!! inline fun <T : Any> interpretObjCPointer(objcPtr: NativePtr): T = interpretObjCPointerOrNull<T>(objcPtr)!!
@SymbolName("Kotlin_Interop_refToObjC") @SymbolName("Kotlin_Interop_refToObjC")
@@ -215,6 +215,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
private val packageScope by lazy { builtIns.builtInsModule.getPackage(KonanFqNames.internalPackageName).memberScope } private val packageScope by lazy { builtIns.builtInsModule.getPackage(KonanFqNames.internalPackageName).memberScope }
val nativePtr by lazy { packageScope.getContributedClassifier(NATIVE_PTR_NAME) as ClassDescriptor } val nativePtr by lazy { packageScope.getContributedClassifier(NATIVE_PTR_NAME) as ClassDescriptor }
val nonNullNativePtr by lazy { packageScope.getContributedClassifier(NON_NULL_NATIVE_PTR_NAME) as ClassDescriptor }
val getNativeNullPtr by lazy { packageScope.getContributedFunctions("getNativeNullPtr").single() } val getNativeNullPtr by lazy { packageScope.getContributedFunctions("getNativeNullPtr").single() }
val immutableBlobOf by lazy { val immutableBlobOf by lazy {
builtIns.builtInsModule.getPackage(KonanFqNames.packageName).memberScope.getContributedFunctions("immutableBlobOf").single() builtIns.builtInsModule.getPackage(KonanFqNames.packageName).memberScope.getContributedFunctions("immutableBlobOf").single()
@@ -459,6 +460,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
lateinit var codegenVisitor: CodeGeneratorVisitor lateinit var codegenVisitor: CodeGeneratorVisitor
var devirtualizationAnalysisResult: Devirtualization.AnalysisResult? = null var devirtualizationAnalysisResult: Devirtualization.AnalysisResult? = null
var referencedFunctions: Set<IrFunction>? = null
val isNativeLibrary: Boolean by lazy { val isNativeLibrary: Boolean by lazy {
val kind = config.configuration.get(KonanConfigKeys.PRODUCE) val kind = config.configuration.get(KonanConfigKeys.PRODUCE)
kind == CompilerOutputKind.DYNAMIC || kind == CompilerOutputKind.STATIC kind == CompilerOutputKind.DYNAMIC || kind == CompilerOutputKind.STATIC
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.konan.ir.containsNull
import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
@@ -275,6 +276,6 @@ private object IrTypeInlineClassesSupport : InlineClassesSupport<IrClass, IrType
.firstOrNull { it.descriptor.fqNameUnsafe == InteropFqNames.nativePointed } .firstOrNull { it.descriptor.fqNameUnsafe == InteropFqNames.nativePointed }
override fun getInlinedClassUnderlyingType(clazz: IrClass): IrType = override fun getInlinedClassUnderlyingType(clazz: IrClass): IrType =
clazz.constructors.first { it.isPrimary }.valueParameters.single().type clazz.constructors.firstOrNull { it.isPrimary }?.valueParameters?.single()?.type
?: clazz.declarations.filterIsInstance<IrProperty>().single { it.backingField != null }.backingField!!.type
} }
@@ -10,13 +10,14 @@ import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
internal const val NATIVE_PTR_NAME = "NativePtr" internal const val NATIVE_PTR_NAME = "NativePtr"
internal const val NON_NULL_NATIVE_PTR_NAME = "NonNullNativePtr"
object KonanFqNames { object KonanFqNames {
val packageName = FqName("kotlin.native") val packageName = FqName("kotlin.native")
val internalPackageName = FqName("kotlin.native.internal") val internalPackageName = FqName("kotlin.native.internal")
val nativePtr = internalPackageName.child(Name.identifier(NATIVE_PTR_NAME)).toUnsafe() val nativePtr = internalPackageName.child(Name.identifier(NATIVE_PTR_NAME)).toUnsafe()
val nonNullNativePtr = FqNameUnsafe("kotlin.native.internal.NonNullNativePtr") val nonNullNativePtr = internalPackageName.child(Name.identifier(NON_NULL_NATIVE_PTR_NAME)).toUnsafe()
val throws = FqName("kotlin.native.Throws") val throws = FqName("kotlin.native.Throws")
} }
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.util.constructedClass import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.hasAnnotation import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isReal
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
@@ -223,6 +224,7 @@ fun ConstructorDescriptor.objCConstructorIsDesignated(): Boolean {
val IrConstructor.isObjCConstructor get() = this.descriptor.annotations.hasAnnotation(objCConstructorFqName) val IrConstructor.isObjCConstructor get() = this.descriptor.annotations.hasAnnotation(objCConstructorFqName)
// TODO-DCE-OBJC-INIT: Selector should be preserved by DCE.
fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? { fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? {
return this.descriptor.annotations.findAnnotation(objCConstructorFqName)?.let { return this.descriptor.annotations.findAnnotation(objCConstructorFqName)?.let {
val initSelector = it.getStringValue("initSelector") val initSelector = it.getStringValue("initSelector")
@@ -8,6 +8,7 @@ object RuntimeNames {
val exportForCompilerAnnotation = FqName("kotlin.native.internal.ExportForCompiler") val exportForCompilerAnnotation = FqName("kotlin.native.internal.ExportForCompiler")
val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo") val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo")
val cCall = FqName("kotlinx.cinterop.internal.CCall") val cCall = FqName("kotlinx.cinterop.internal.CCall")
val objCMethodImp = FqName("kotlinx.cinterop.ObjCMethodImp")
val independent = FqName("kotlin.native.internal.Independent") val independent = FqName("kotlin.native.internal.Independent")
val filterExceptions = FqName("kotlin.native.internal.FilterExceptions") val filterExceptions = FqName("kotlin.native.internal.FilterExceptions")
val kotlinNativeInternalPackageName = FqName.fromSegments(listOf("kotlin", "native", "internal")) val kotlinNativeInternalPackageName = FqName.fromSegments(listOf("kotlin", "native", "internal"))
@@ -291,13 +291,14 @@ internal val dependenciesLowerPhase = SameTypeNamedPhaseWrapper(
internal val bitcodePhase = namedIrModulePhase( internal val bitcodePhase = namedIrModulePhase(
name = "Bitcode", name = "Bitcode",
description = "LLVM Bitcode generation", description = "LLVM Bitcode generation",
lower = contextLLVMSetupPhase then lower = buildDFGPhase then
RTTIPhase then
generateDebugInfoHeaderPhase then
buildDFGPhase then
serializeDFGPhase then serializeDFGPhase then
deserializeDFGPhase then deserializeDFGPhase then
devirtualizationPhase then devirtualizationPhase then
dcePhase then
contextLLVMSetupPhase then
RTTIPhase then
generateDebugInfoHeaderPhase then
escapeAnalysisPhase then escapeAnalysisPhase then
codegenPhase then codegenPhase then
finalizeDebugInfoPhase then finalizeDebugInfoPhase then
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.isReal
import org.jetbrains.kotlin.ir.util.isSuspend import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.util.overrides import org.jetbrains.kotlin.ir.util.overrides
import org.jetbrains.kotlin.types.SimpleType import org.jetbrains.kotlin.types.SimpleType
@@ -17,11 +17,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module
// This file contains some IR utilities which actually use descriptors. // This file contains some IR utilities which actually use descriptors.
// TODO: port this code to IR. // TODO: port this code to IR.
internal val IrDeclaration.isAnonymousObject get() = DescriptorUtils.isAnonymousObject(this.descriptor)
internal val IrDeclaration.isLocal get() = DescriptorUtils.isLocal(this.descriptor)
internal val IrDeclaration.module get() = this.descriptor.module
internal fun IrFunction.getObjCMethodInfo() = this.descriptor.getObjCMethodInfo() internal fun IrFunction.getObjCMethodInfo() = this.descriptor.getObjCMethodInfo()
internal fun IrFunction.getExternalObjCMethodInfo() = this.descriptor.getExternalObjCMethodInfo() internal fun IrFunction.getExternalObjCMethodInfo() = this.descriptor.getExternalObjCMethodInfo()
internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod() internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod()
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
@@ -53,7 +54,9 @@ internal class KonanSymbols(context: Context, private val symbolTable: SymbolTab
val string = symbolTable.referenceClass(builtIns.string) val string = symbolTable.referenceClass(builtIns.string)
val enum = symbolTable.referenceClass(builtIns.enum) val enum = symbolTable.referenceClass(builtIns.enum)
val nativePtr = symbolTable.referenceClass(context.nativePtr) val nativePtr = symbolTable.referenceClass(context.nativePtr)
val nativePointed = symbolTable.referenceClass(context.interopBuiltIns.nativePointed)
val nativePtrType = nativePtr.typeWith(arguments = emptyList()) val nativePtrType = nativePtr.typeWith(arguments = emptyList())
val nonNullNativePtr = symbolTable.referenceClass(context.nonNullNativePtr)
private fun unsignedClass(unsignedType: UnsignedType): IrClassSymbol = classById(unsignedType.classId) private fun unsignedClass(unsignedType: UnsignedType): IrClassSymbol = classById(unsignedType.classId)
@@ -351,6 +354,13 @@ internal class KonanSymbols(context: Context, private val symbolTable: SymbolTab
val continuationImpl = topLevelClass("kotlin.coroutines.native.internal.ContinuationImpl") val continuationImpl = topLevelClass("kotlin.coroutines.native.internal.ContinuationImpl")
val invokeSuspendFunction =
symbolTable.referenceSimpleFunction(
baseContinuationImpl.descriptor.unsubstitutedMemberScope
.getContributedFunctions(Name.identifier("invokeSuspend"), NoLookupLocation.FROM_BACKEND)
.single()
)
override val coroutineSuspendedGetter = symbolTable.referenceSimpleFunction( override val coroutineSuspendedGetter = symbolTable.referenceSimpleFunction(
coroutinesIntrinsicsPackage coroutinesIntrinsicsPackage
.getContributedVariables(COROUTINE_SUSPENDED_NAME, NoLookupLocation.FROM_BACKEND) .getContributedVariables(COROUTINE_SUSPENDED_NAME, NoLookupLocation.FROM_BACKEND)
@@ -23,23 +23,6 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
val IrDeclaration.name: Name
get() = when (this) {
is IrSimpleFunction -> this.name
is IrClass -> this.name
is IrEnumEntry -> this.name
is IrProperty -> this.name
is IrLocalDelegatedProperty -> this.name
is IrField -> this.name
is IrVariable -> this.name
is IrConstructor -> SPECIAL_INIT_NAME
is IrValueParameter -> this.name
else -> error(this)
}
private val SPECIAL_INIT_NAME = Name.special("<init>")
val IrField.fqNameForIrSerialization: FqName get() = this.parent.fqNameForIrSerialization.child(this.name) val IrField.fqNameForIrSerialization: FqName get() = this.parent.fqNameForIrSerialization.child(this.name)
/** /**
@@ -66,14 +49,6 @@ fun IrClass.isNothing() = this.fqNameForIrSerialization == KotlinBuiltIns.FQ_NAM
fun IrClass.getSuperInterfaces() = this.superClasses.map { it.owner }.filter { it.isInterface } fun IrClass.getSuperInterfaces() = this.superClasses.map { it.owner }.filter { it.isInterface }
val IrProperty.konanBackingField: IrField?
get() {
assert(this.isReal)
return this.backingField
}
val IrFunction.isReal get() = this.origin != IrDeclarationOrigin.FAKE_OVERRIDE
// Note: psi2ir doesn't set `origin = FAKE_OVERRIDE` for fields and properties yet. // Note: psi2ir doesn't set `origin = FAKE_OVERRIDE` for fields and properties yet.
val IrProperty.isReal: Boolean get() = this.descriptor.kind.isReal val IrProperty.isReal: Boolean get() = this.descriptor.kind.isReal
val IrField.isReal: Boolean get() = this.descriptor.kind.isReal val IrField.isReal: Boolean get() = this.descriptor.kind.isReal
@@ -8,7 +8,13 @@ package org.jetbrains.kotlin.backend.konan.llvm
import llvm.* import llvm.*
import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.optimizations.* import org.jetbrains.kotlin.backend.konan.optimizations.*
import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.util.OperatorNameConventions
internal val contextLLVMSetupPhase = makeKonanModuleOpPhase( internal val contextLLVMSetupPhase = makeKonanModuleOpPhase(
name = "ContextLLVMSetup", name = "ContextLLVMSetup",
@@ -71,6 +77,98 @@ internal val devirtualizationPhase = makeKonanModuleOpPhase(
} }
) )
internal val IrFunction.longName: String
get() = "${(parent as? IrClass)?.name?.asString() ?: "<root>"}.${(this as? IrSimpleFunction)?.name ?: "<init>"}"
internal val dcePhase = makeKonanModuleOpPhase(
name = "DCEPhase",
description = "Dead code elimination",
prerequisite = setOf(devirtualizationPhase),
op = { context, _ ->
val externalModulesDFG = ExternalModulesDFG(emptyList(), emptyMap(), emptyMap(), emptyMap())
val callGraph = CallGraphBuilder(
context, context.moduleDFG!!,
externalModulesDFG,
context.devirtualizationAnalysisResult!!,
true
).build()
val referencedFunctions = mutableSetOf<IrFunction>()
for (node in callGraph.directEdges.values) {
if (!node.symbol.isGlobalInitializer)
referencedFunctions.add(node.symbol.irFunction ?: error("No IR for: ${node.symbol}"))
node.callSites.forEach {
assert (!it.isVirtual) { "There should be no virtual calls in the call graph, but was: ${it.actualCallee}" }
referencedFunctions.add(it.actualCallee.irFunction ?: error("No IR for: ${it.actualCallee}"))
}
}
context.irModule!!.acceptChildrenVoid(object: IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunction(declaration: IrFunction) {
// TODO: Generalize somehow, not that graceful.
if (declaration.name == OperatorNameConventions.INVOKE
&& declaration.parent.let { it is IrClass && it.defaultType.isFunction() }) {
referencedFunctions.add(declaration)
}
super.visitFunction(declaration)
}
override fun visitConstructor(declaration: IrConstructor) {
// TODO: NativePointed is the only inline class for which the field's type and
// the constructor parameter's type are different.
// Thus we need to conserve the constructor no matter if it was actually referenced somehow or not.
// See [IrTypeInlineClassesSupport.getInlinedClassUnderlyingType] why.
if (declaration.parentAsClass.name.asString() == InteropFqNames.nativePointedName && declaration.isPrimary)
referencedFunctions.add(declaration)
super.visitConstructor(declaration)
}
override fun visitClass(declaration: IrClass) {
context.getLayoutBuilder(declaration).associatedObjects.values.forEach {
assert (it.kind == ClassKind.OBJECT) { "An object expected but was ${it.dump()}" }
referencedFunctions.add(it.constructors.single())
}
super.visitClass(declaration)
}
})
context.irModule!!.transformChildrenVoid(object: IrElementTransformerVoid() {
override fun visitFile(declaration: IrFile): IrFile {
declaration.declarations.removeAll {
(it is IrFunction && !referencedFunctions.contains(it))
}
return super.visitFile(declaration)
}
override fun visitClass(declaration: IrClass): IrStatement {
if (declaration == context.ir.symbols.nativePointed)
return super.visitClass(declaration)
declaration.declarations.removeAll {
(it is IrFunction && it.isReal && !referencedFunctions.contains(it))
}
return super.visitClass(declaration)
}
override fun visitProperty(declaration: IrProperty): IrStatement {
if (declaration.getter.let { it != null && it.isReal && !referencedFunctions.contains(it) }) {
declaration.getter = null
}
if (declaration.setter.let { it != null && it.isReal && !referencedFunctions.contains(it) }) {
declaration.setter = null
}
return super.visitProperty(declaration)
}
})
context.referencedFunctions = referencedFunctions
}
)
internal val escapeAnalysisPhase = makeKonanModuleOpPhase( internal val escapeAnalysisPhase = makeKonanModuleOpPhase(
// Disabled by default !!!! // Disabled by default !!!!
name = "EscapeAnalysis", name = "EscapeAnalysis",
@@ -81,7 +179,7 @@ internal val escapeAnalysisPhase = makeKonanModuleOpPhase(
val callGraph = CallGraphBuilder( val callGraph = CallGraphBuilder(
context, context.moduleDFG!!, context, context.moduleDFG!!,
externalModulesDFG, externalModulesDFG,
context.devirtualizationAnalysisResult, context.devirtualizationAnalysisResult!!,
false false
).build() ).build()
EscapeAnalysis.computeLifetimes( EscapeAnalysis.computeLifetimes(
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.isReal
import org.jetbrains.kotlin.konan.library.KonanLibrary import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.konan.library.resolver.TopologicalLibraryOrder import org.jetbrains.kotlin.konan.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -1893,16 +1893,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------// //-------------------------------------------------------------------------//
private val invokeSuspendFunction = context.ir.symbols.baseContinuationImpl.owner.declarations
.filterIsInstance<IrSimpleFunction>().single { it.name.asString() == "invokeSuspend" }
private fun getContinuation(): LLVMValueRef { private fun getContinuation(): LLVMValueRef {
val caller = functionGenerationContext.irFunction!! val caller = functionGenerationContext.irFunction!!
return if (caller.isSuspend) return if (caller.isSuspend)
codegen.param(caller, caller.allParameters.size) // The last argument. codegen.param(caller, caller.allParameters.size) // The last argument.
else { else {
// Suspend call from non-suspend function - must be [BaseContinuationImpl]. // Suspend call from non-suspend function - must be [invokeSuspend].
assert ((caller as IrSimpleFunction).overrides(invokeSuspendFunction), assert ((caller as IrSimpleFunction).overrides(context.ir.symbols.invokeSuspendFunction.owner),
{ "Expected 'BaseContinuationImpl.invokeSuspend' but was '$caller'" }) { "Expected 'BaseContinuationImpl.invokeSuspend' but was '$caller'" })
currentCodeContext.genGetValue(caller.dispatchReceiverParameter!!) currentCodeContext.genGetValue(caller.dispatchReceiverParameter!!)
} }
@@ -13,10 +13,7 @@ import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.isNothing import org.jetbrains.kotlin.ir.types.isNothing
import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
@@ -127,7 +124,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
return objectNamer.getName(parent, declaration) return objectNamer.getName(parent, declaration)
} }
return declaration.name return declaration.nameForIrSerialization
} }
private fun getFqName(declaration: IrDeclaration): FqName { private fun getFqName(declaration: IrDeclaration): FqName {
@@ -10,8 +10,6 @@ import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.ir.isAnonymousObject
import org.jetbrains.kotlin.backend.konan.ir.isLocal
import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
@@ -249,7 +247,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
// TODO: compile-time resolution limits binary compatibility. // TODO: compile-time resolution limits binary compatibility.
val vtableEntries = context.getLayoutBuilder(irClass).vtableEntries.map { val vtableEntries = context.getLayoutBuilder(irClass).vtableEntries.map {
val implementation = it.implementation val implementation = it.implementation
if (implementation == null || implementation.isExternalObjCClassMethod()) { if (implementation == null || implementation.isExternalObjCClassMethod() || context.referencedFunctions?.contains(implementation) == false) {
NullPointer(int8Type) NullPointer(int8Type)
} else { } else {
implementation.entryPointAddress implementation.entryPointAddress
@@ -269,7 +267,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
// TODO: compile-time resolution limits binary compatibility. // TODO: compile-time resolution limits binary compatibility.
val implementation = it.implementation val implementation = it.implementation
val methodEntryPoint = implementation?.entryPointAddress val methodEntryPoint =
if (implementation == null || context.referencedFunctions?.contains(implementation) == false)
null
else implementation.entryPointAddress
MethodTableRecord(nameSignature, methodEntryPoint) MethodTableRecord(nameSignature, methodEntryPoint)
}.sortedBy { it.nameSignature.value } }.sortedBy { it.nameSignature.value }
} }
@@ -47,7 +47,7 @@ internal class CallGraph(val directEdges: Map<DataFlowIR.FunctionSymbol, CallGra
internal class CallGraphBuilder(val context: Context, internal class CallGraphBuilder(val context: Context,
val moduleDFG: ModuleDFG, val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG, val externalModulesDFG: ExternalModulesDFG,
devirtualizationAnalysisResult: Devirtualization.AnalysisResult?, val devirtualizationAnalysisResult: Devirtualization.AnalysisResult,
val gotoExternal: Boolean) { val gotoExternal: Boolean) {
private val DEBUG = 0 private val DEBUG = 0
@@ -56,7 +56,7 @@ internal class CallGraphBuilder(val context: Context,
if (DEBUG > severity) block() if (DEBUG > severity) block()
} }
private val devirtualizedCallSites = devirtualizationAnalysisResult?.devirtualizedCallSites private val devirtualizedCallSites = devirtualizationAnalysisResult.devirtualizedCallSites
private fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol { private fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol {
if (this is DataFlowIR.FunctionSymbol.External) if (this is DataFlowIR.FunctionSymbol.External)
@@ -88,6 +88,10 @@ internal class CallGraphBuilder(val context: Context,
reversedEdges.put(symbol, list) reversedEdges.put(symbol, list)
} }
private val symbols = context.ir.symbols
private val arrayGet = symbols.arrayGet[symbols.array]!!.owner
private val arraySet = symbols.arraySet[symbols.array]!!.owner
private inline fun DataFlowIR.FunctionBody.forEachCallSite(block: (DataFlowIR.Node.Call) -> Unit) = private inline fun DataFlowIR.FunctionBody.forEachCallSite(block: (DataFlowIR.Node.Call) -> Unit) =
nodes.forEach { node -> nodes.forEach { node ->
when (node) { when (node) {
@@ -95,42 +99,81 @@ internal class CallGraphBuilder(val context: Context,
is DataFlowIR.Node.Singleton -> is DataFlowIR.Node.Singleton ->
node.constructor?.let { block(DataFlowIR.Node.Call(it, emptyList(), null)) } node.constructor?.let { block(DataFlowIR.Node.Call(it, emptyList(), null)) }
is DataFlowIR.Node.ArrayRead ->
block(DataFlowIR.Node.Call(
callee = moduleDFG.symbolTable.mapFunction(arrayGet),
arguments = listOf(node.array, node.index),
irCallSite = null)
)
is DataFlowIR.Node.ArrayWrite ->
block(DataFlowIR.Node.Call(
callee = moduleDFG.symbolTable.mapFunction(arraySet),
arguments = listOf(node.array, node.index, node.value),
irCallSite = null)
)
is DataFlowIR.Node.FunctionReference ->
block(DataFlowIR.Node.Call(
callee = node.symbol,
arguments = emptyList(),
irCallSite = null
))
} }
} }
private fun staticCall(caller: DataFlowIR.FunctionSymbol, call: DataFlowIR.Node.Call, callee: DataFlowIR.FunctionSymbol) {
callGraph.addEdge(caller, CallGraphNode.CallSite(call, false, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !directEdges.containsKey(callee))
dfs(callee)
}
private fun dfs(symbol: DataFlowIR.FunctionSymbol) { private fun dfs(symbol: DataFlowIR.FunctionSymbol) {
visitedFunctions += symbol visitedFunctions += symbol
if (gotoExternal) { if (gotoExternal) {
addNode(symbol) addNode(symbol)
val function = moduleDFG.functions[symbol] ?: externalModulesDFG.functionDFGs[symbol] val function = moduleDFG.functions[symbol] ?: externalModulesDFG.functionDFGs[symbol] ?: return
val body = function!!.body val body = function.body
body body.forEachCallSite { call ->
.forEachCallSite { call -> val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites[it] }
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites?.get(it) } when {
if (devirtualizedCallSite == null) { call !is DataFlowIR.Node.VirtualCall -> staticCall(symbol, call, call.callee.resolved())
val callee = call.callee.resolved()
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee)) devirtualizedCallSite != null -> {
if (callee is DataFlowIR.FunctionSymbol.Declared devirtualizedCallSite.possibleCallees.forEach {
&& call !is DataFlowIR.Node.VirtualCall staticCall(symbol, call, it.callee.resolved())
&& !directEdges.containsKey(callee))
dfs(callee)
} else {
devirtualizedCallSite.possibleCallees.forEach {
val callee = it.callee.resolved()
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, false, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !directEdges.containsKey(callee))
dfs(callee)
}
} }
} }
body.nodes.filterIsInstance<DataFlowIR.Node.FunctionReference>()
.forEach { call.receiverType == DataFlowIR.Type.Virtual -> {
val callee = it.symbol.resolved() // Skip callsite. This can only be for invocations Any's methods on instances of ObjC classes.
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !directEdges.containsKey(callee))
dfs(callee)
} }
else -> {
// Callsite has not been devirtualized - conservatively assume the worst:
// any inheritor of the receiver type is possible here.
val typeHierarcy = devirtualizationAnalysisResult.typeHierarchy
typeHierarcy.inheritorsOf(call.receiverType as DataFlowIR.Type.Declared)
.filterNot { it.isAbstract }
// TODO: Unconservative way - when we can use it?
//.filter { devirtualizationAnalysisResult.instantiatingClasses.contains(it) }
.forEach { receiverType ->
val actualCallee = when (call) {
is DataFlowIR.Node.VtableCall ->
receiverType.vtable[call.calleeVtableIndex]
is DataFlowIR.Node.ItableCall ->
receiverType.itable[call.calleeHash]!!
else -> error("Unreachable")
}
staticCall(symbol, call, actualCallee.resolved())
}
}
}
}
} else { } else {
var function = moduleDFG.functions[symbol] var function = moduleDFG.functions[symbol]
var local = true var local = true
@@ -141,42 +184,31 @@ internal class CallGraphBuilder(val context: Context,
local = false local = false
} }
val body = function.body val body = function.body
body body.forEachCallSite { call ->
.forEachCallSite { call -> val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites?.get(it) }
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites?.get(it) } if (devirtualizedCallSite == null) {
if (devirtualizedCallSite == null) { val callee = call.callee.resolved()
val callee = call.callee.resolved() if (moduleDFG.functions.containsKey(callee))
if (moduleDFG.functions.containsKey(callee)) addNode(callee)
addNode(callee) if (local)
if (local) callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee))
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee)) if (callee is DataFlowIR.FunctionSymbol.Declared
if (callee is DataFlowIR.FunctionSymbol.Declared && call !is DataFlowIR.Node.VirtualCall
&& call !is DataFlowIR.Node.VirtualCall && !visitedFunctions.contains(callee))
&& !visitedFunctions.contains(callee)) dfs(callee)
dfs(callee) } else {
} else { devirtualizedCallSite.possibleCallees.forEach {
devirtualizedCallSite.possibleCallees.forEach { val callee = it.callee.resolved()
val callee = it.callee.resolved()
if (moduleDFG.functions.containsKey(callee))
addNode(callee)
if (local)
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, false, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !visitedFunctions.contains(callee))
dfs(callee)
}
}
}
body.nodes.filterIsInstance<DataFlowIR.Node.FunctionReference>()
.forEach {
val callee = it.symbol.resolved()
if (moduleDFG.functions.containsKey(callee)) if (moduleDFG.functions.containsKey(callee))
addNode(callee) addNode(callee)
if (local)
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, false, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared if (callee is DataFlowIR.FunctionSymbol.Declared
&& !visitedFunctions.contains(callee)) && !visitedFunctions.contains(callee))
dfs(callee) dfs(callee)
} }
}
}
} }
} }
} }
@@ -13,11 +13,11 @@ import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.descriptors.target import org.jetbrains.kotlin.backend.konan.descriptors.target
import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.functionName import org.jetbrains.kotlin.backend.konan.llvm.functionName
import org.jetbrains.kotlin.backend.konan.llvm.localHash import org.jetbrains.kotlin.backend.konan.llvm.localHash
import org.jetbrains.kotlin.backend.konan.llvm.longName
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
@@ -212,12 +212,16 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
} }
override fun visitFunction(declaration: IrFunction) { override fun visitFunction(declaration: IrFunction) {
declaration.body?.let { val body = declaration.body
if (body == null) {
// External function or intrinsic.
symbolTable.mapFunction(declaration)
} else {
DEBUG_OUTPUT(0) { DEBUG_OUTPUT(0) {
println("Analysing function ${declaration.descriptor}") println("Analysing function ${declaration.descriptor}")
println("IR: ${ir2stringWhole(declaration)}") println("IR: ${ir2stringWhole(declaration)}")
} }
analyze(declaration, it) analyze(declaration, body)
} }
} }
@@ -9,10 +9,9 @@ import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
import org.jetbrains.kotlin.backend.konan.descriptors.target import org.jetbrains.kotlin.backend.konan.descriptors.target
import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.functionName import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.isExported import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler.functionName
import org.jetbrains.kotlin.backend.konan.llvm.localHash import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler.symbolName
import org.jetbrains.kotlin.backend.konan.llvm.symbolName
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -27,9 +26,7 @@ import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isNothing import org.jetbrains.kotlin.ir.types.isNothing
import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
@@ -121,6 +118,7 @@ internal object DataFlowIR {
val IS_GLOBAL_INITIALIZER = 1 val IS_GLOBAL_INITIALIZER = 1
val RETURNS_UNIT = 2 val RETURNS_UNIT = 2
val RETURNS_NOTHING = 4 val RETURNS_NOTHING = 4
val EXPLICITLY_EXPORTED = 8
} }
class FunctionParameter(val type: Type, val boxFunction: FunctionSymbol?, val unboxFunction: FunctionSymbol?) class FunctionParameter(val type: Type, val boxFunction: FunctionSymbol?, val unboxFunction: FunctionSymbol?)
@@ -132,6 +130,7 @@ internal object DataFlowIR {
val isGlobalInitializer = attributes.and(FunctionAttributes.IS_GLOBAL_INITIALIZER) != 0 val isGlobalInitializer = attributes.and(FunctionAttributes.IS_GLOBAL_INITIALIZER) != 0
val returnsUnit = attributes.and(FunctionAttributes.RETURNS_UNIT) != 0 val returnsUnit = attributes.and(FunctionAttributes.RETURNS_UNIT) != 0
val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0 val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0
val explicitlyExported = attributes.and(FunctionAttributes.EXPLICITLY_EXPORTED) != 0
var escapes: Int? = null var escapes: Int? = null
var pointsTo: IntArray? = null var pointsTo: IntArray? = null
@@ -572,12 +571,6 @@ internal object DataFlowIR {
inlinedClass?.let { mapFunction(context.getUnboxFunction(it)) }) inlinedClass?.let { mapFunction(context.getUnboxFunction(it)) })
} }
// TODO: use from LlvmDeclarations.
private fun getFqName(declaration: IrDeclaration): FqName =
declaration.parent.fqNameForIrSerialization.child(declaration.name)
private val IrFunction.internalName get() = getFqName(this).asString() + "#internal"
fun mapFunction(declaration: IrDeclaration): FunctionSymbol = when (declaration) { fun mapFunction(declaration: IrDeclaration): FunctionSymbol = when (declaration) {
is IrFunction -> mapFunction(declaration) is IrFunction -> mapFunction(declaration)
is IrField -> mapPropertyInitializer(declaration) is IrField -> mapPropertyInitializer(declaration)
@@ -587,7 +580,13 @@ internal object DataFlowIR {
private fun mapFunction(function: IrFunction): FunctionSymbol = function.target.let { private fun mapFunction(function: IrFunction): FunctionSymbol = function.target.let {
functionMap[it]?.let { return it } functionMap[it]?.let { return it }
val name = if (it.isExported()) it.symbolName else it.internalName val parent = it.parent
val containingDeclarationPart = parent.fqNameForIrSerialization.let {
if (it.isRoot) "" else "$it."
}
val name = "kfun:$containingDeclarationPart${it.functionName}"
val returnsUnit = it is IrConstructor || (!it.isSuspend && it.returnType.isUnit()) val returnsUnit = it is IrConstructor || (!it.isSuspend && it.returnType.isUnit())
val returnsNothing = !it.isSuspend && it.returnType.isNothing() val returnsNothing = !it.isSuspend && it.returnType.isNothing()
var attributes = 0 var attributes = 0
@@ -595,6 +594,11 @@ internal object DataFlowIR {
attributes = attributes or FunctionAttributes.RETURNS_UNIT attributes = attributes or FunctionAttributes.RETURNS_UNIT
if (returnsNothing) if (returnsNothing)
attributes = attributes or FunctionAttributes.RETURNS_NOTHING attributes = attributes or FunctionAttributes.RETURNS_NOTHING
if (it.hasAnnotation(RuntimeNames.exportForCppRuntime)
|| it.getExternalObjCMethodInfo() != null // TODO-DCE-OBJC-INIT
|| it.hasAnnotation(RuntimeNames.objCMethodImp)) {
attributes = attributes or FunctionAttributes.EXPLICITLY_EXPORTED
}
val symbol = when { val symbol = when {
it.isExternal || (it.symbol in context.irBuiltIns.irBuiltInsSymbols) -> { it.isExternal || (it.symbol in context.irBuiltIns.irBuiltInsSymbols) -> {
val escapesAnnotation = it.descriptor.annotations.findAnnotation(FQ_NAME_ESCAPES) val escapesAnnotation = it.descriptor.annotations.findAnnotation(FQ_NAME_ESCAPES)
@@ -22,9 +22,11 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
import org.jetbrains.kotlin.ir.descriptors.IrTemporaryVariableDescriptorImpl import org.jetbrains.kotlin.ir.descriptors.IrTemporaryVariableDescriptorImpl
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrReturnableBlockSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrReturnableBlockSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.impl.originalKotlinType import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.irCall import org.jetbrains.kotlin.ir.util.irCall
@@ -67,19 +69,22 @@ internal object Devirtualization {
listOf(moduleDFG.symbolTable.mapFunction(entryPoint).resolved()) listOf(moduleDFG.symbolTable.mapFunction(entryPoint).resolved())
else else
// In a library every public function and every function accessible via virtual call belongs to the rootset. // In a library every public function and every function accessible via virtual call belongs to the rootset.
moduleDFG.functions.keys.filterIsInstance<DataFlowIR.FunctionSymbol.Public>() + moduleDFG.symbolTable.functionMap.values.filterIsInstance<DataFlowIR.FunctionSymbol.Public>() +
moduleDFG.symbolTable.classMap.values moduleDFG.symbolTable.classMap.values
.filterIsInstance<DataFlowIR.Type.Declared>() .filterIsInstance<DataFlowIR.Type.Declared>()
.flatMap { it.vtable + it.itable.values } .flatMap { it.vtable + it.itable.values }
.filterIsInstance<DataFlowIR.FunctionSymbol.Declared>() .filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
.filter { moduleDFG.functions.containsKey(it) } .filter { moduleDFG.functions.containsKey(it) }
// TODO: Are globals inititalizers always called whether they are actually reachable from roots or not? // TODO: Are globals initializers always called whether they are actually reachable from roots or not?
val globalInitializers = val globalInitializers =
moduleDFG.functions.keys.filter { it.isGlobalInitializer } + moduleDFG.symbolTable.functionMap.values.filter { it.isGlobalInitializer } +
externalModulesDFG.functionDFGs.keys.filter { it.isGlobalInitializer } externalModulesDFG.functionDFGs.keys.filter { it.isGlobalInitializer }
return (exportedFunctions + globalInitializers).distinct() val explicitlyExportedFunctions =
moduleDFG.symbolTable.functionMap.values.filter { it.explicitlyExported } +
externalModulesDFG.functionDFGs.keys.filter { it.explicitlyExported }
return (exportedFunctions + globalInitializers + explicitlyExportedFunctions).distinct()
} }
fun BitSet.format(allTypes: List<DataFlowIR.Type.Declared>): String { fun BitSet.format(allTypes: List<DataFlowIR.Type.Declared>): String {
@@ -88,9 +93,9 @@ internal object Devirtualization {
private val VIRTUAL_TYPE_ID = 0 // Id of [DataFlowIR.Type.Virtual]. private val VIRTUAL_TYPE_ID = 0 // Id of [DataFlowIR.Type.Virtual].
private class DevirtualizationAnalysis(val context: Context, internal class DevirtualizationAnalysis(val context: Context,
val moduleDFG: ModuleDFG, val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG) { val externalModulesDFG: ExternalModulesDFG) {
private val entryPoint = context.ir.symbols.entryPoint?.owner private val entryPoint = context.ir.symbols.entryPoint?.owner
@@ -188,7 +193,7 @@ internal object Devirtualization {
return this return this
} }
private inner class TypeHierarchy(types: List<DataFlowIR.Type.Declared>) { inner class TypeHierarchy(types: List<DataFlowIR.Type.Declared>) {
private val typesSubTypes = mutableMapOf<DataFlowIR.Type.Declared, MutableList<DataFlowIR.Type.Declared>>() private val typesSubTypes = mutableMapOf<DataFlowIR.Type.Declared, MutableList<DataFlowIR.Type.Declared>>()
init { init {
@@ -229,6 +234,7 @@ internal object Devirtualization {
fun search(): Set<DataFlowIR.Type.Declared> { fun search(): Set<DataFlowIR.Type.Declared> {
// Rapid Type Analysis: find all instantiations and conservatively estimate call graph. // Rapid Type Analysis: find all instantiations and conservatively estimate call graph.
// Add all final parameters of the roots. // Add all final parameters of the roots.
rootSet.forEach { rootSet.forEach {
it.parameters it.parameters
@@ -238,8 +244,7 @@ internal object Devirtualization {
} }
if (entryPoint == null) { if (entryPoint == null) {
// For library assume all public non-abstract classes could be instantiated. // For library assume all public non-abstract classes could be instantiated.
moduleDFG.symbolTable.classMap.values symbolTable.classMap.values
.asSequence()
.filterIsInstance<DataFlowIR.Type.Public>() .filterIsInstance<DataFlowIR.Type.Public>()
.filter { !it.isAbstract } .filter { !it.isAbstract }
.forEach { addInstantiatingClass(it) } .forEach { addInstantiatingClass(it) }
@@ -333,7 +338,7 @@ internal object Devirtualization {
val function = (moduleDFG.functions[resolvedFunctionSymbol] val function = (moduleDFG.functions[resolvedFunctionSymbol]
?: externalModulesDFG.functionDFGs[resolvedFunctionSymbol]) ?: externalModulesDFG.functionDFGs[resolvedFunctionSymbol])
?: error("Unknown function $resolvedFunctionSymbol") ?: return
DEBUG_OUTPUT(1) { function.debugOutput() } DEBUG_OUTPUT(1) { function.debugOutput() }
@@ -452,7 +457,7 @@ internal object Devirtualization {
} while (cur != node) } while (cur != node)
} }
fun analyze(): Map<DataFlowIR.Node.VirtualCall, DevirtualizedCallSite> { fun analyze(): AnalysisResult {
val functions = moduleDFG.functions + externalModulesDFG.functionDFGs val functions = moduleDFG.functions + externalModulesDFG.functionDFGs
val typeHierarchy = TypeHierarchy(symbolTable.classMap.values.filterIsInstance<DataFlowIR.Type.Declared>() + val typeHierarchy = TypeHierarchy(symbolTable.classMap.values.filterIsInstance<DataFlowIR.Type.Declared>() +
externalModulesDFG.allTypes) externalModulesDFG.allTypes)
@@ -665,7 +670,7 @@ internal object Devirtualization {
} }
} }
return result.asSequence().associateBy({ it.key }, { it.value.first }) return AnalysisResult(result.asSequence().associateBy({ it.key }, { it.value.first }), typeHierarchy, instantiatingClasses.keys)
} }
private inner class ConstraintGraphBuilder(val functionNodesMap: MutableMap<DataFlowIR.Node, Node>, private inner class ConstraintGraphBuilder(val functionNodesMap: MutableMap<DataFlowIR.Node, Node>,
@@ -719,10 +724,10 @@ internal object Devirtualization {
fieldNode(constraintGraph.arrayItemField) fieldNode(constraintGraph.arrayItemField)
) )
} }
rootSet.forEach { createFunctionConstraintGraph(it, true)!! } rootSet.forEach { createFunctionConstraintGraph(it, true) }
while (stack.isNotEmpty()) { while (stack.isNotEmpty()) {
val symbol = stack.pop() val symbol = stack.pop()
val function = functions[symbol] ?: error("Unknown function: $symbol") val function = functions[symbol] ?: continue
val body = function.body val body = function.body
val functionConstraintGraph = constraintGraph.functions[symbol]!! val functionConstraintGraph = constraintGraph.functions[symbol]!!
@@ -757,7 +762,7 @@ internal object Devirtualization {
symbol.parameters.forEachIndexed { index, type -> symbol.parameters.forEachIndexed { index, type ->
val resolvedType = type.type.resolved() val resolvedType = type.type.resolved()
val node = if (!resolvedType.isFinal) val node = if (!resolvedType.isFinal)
constraintGraph.virtualNode constraintGraph.virtualNode // TODO: May be do this only for a library?
else else
concreteClass(resolvedType) concreteClass(resolvedType)
node.addEdge(parameters[index]) node.addEdge(parameters[index])
@@ -783,14 +788,18 @@ internal object Devirtualization {
return Node.CastEdge(node, suitableTypes) return Node.CastEdge(node, suitableTypes)
} }
private fun doCast(function: Function, node: Node, type: DataFlowIR.Type.Declared): Node {
val castNode = ordinaryNode { "Cast\$${function.symbol}" }
val castEdge = createCastEdge(castNode, type)
node.addCastEdge(castEdge)
return castNode
}
private fun edgeToConstraintNode(function: Function, private fun edgeToConstraintNode(function: Function,
edge: DataFlowIR.Edge): Node { edge: DataFlowIR.Edge): Node {
val result = dfgNodeToConstraintNode(function, edge.node) val result = dfgNodeToConstraintNode(function, edge.node)
val castToType = edge.castToType?.resolved() ?: return result val castToType = edge.castToType?.resolved() ?: return result
val castNode = ordinaryNode { "Cast\$${function.symbol}" } return doCast(function, result, castToType)
val castEdge = createCastEdge(castNode, castToType)
result.addCastEdge(castEdge)
return castNode
} }
/** /**
@@ -809,7 +818,7 @@ internal object Devirtualization {
else -> error("Unexpected argument: $argument") else -> error("Unexpected argument: $argument")
} }
fun doCall(callee: Function, arguments: List<Any>): Node { fun doCall(callee: Function, arguments: List<Any>, returnType: DataFlowIR.Type.Declared): Node {
assert(callee.parameters.size == arguments.size) { assert(callee.parameters.size == arguments.size) {
"Function ${callee.symbol} takes ${callee.parameters.size} but caller ${function.symbol}" + "Function ${callee.symbol} takes ${callee.parameters.size} but caller ${function.symbol}" +
" provided ${arguments.size}" " provided ${arguments.size}"
@@ -818,7 +827,7 @@ internal object Devirtualization {
val argument = argumentToConstraintNode(arguments[index]) val argument = argumentToConstraintNode(arguments[index])
argument.addEdge(parameter) argument.addEdge(parameter)
} }
return callee.returns return doCast(function, callee.returns, returnType)
} }
fun doCall(callee: DataFlowIR.FunctionSymbol, fun doCall(callee: DataFlowIR.FunctionSymbol,
@@ -832,20 +841,25 @@ internal object Devirtualization {
val fictitiousReturnNode = ordinaryNode { "External$resolvedCallee" } val fictitiousReturnNode = ordinaryNode { "External$resolvedCallee" }
if (returnType.isFinal) if (returnType.isFinal)
concreteClass(returnType).addEdge(fictitiousReturnNode) concreteClass(returnType).addEdge(fictitiousReturnNode)
else else {
constraintGraph.virtualNode.addEdge(fictitiousReturnNode) constraintGraph.virtualNode.addEdge(fictitiousReturnNode)
// TODO: Unconservative way - when we can use it?
// typeHierarchy.inheritorsOf(returnType)
// .filterNot { it.isAbstract }
// .filter { instantiatingClasses.containsKey(it) }
// .forEach { concreteClass(it).addEdge(fictitiousReturnNode) }
}
fictitiousReturnNode fictitiousReturnNode
} }
} else { } else {
calleeConstraintGraph.throws.addEdge(function.throws) calleeConstraintGraph.throws.addEdge(function.throws)
if (receiverType == null) if (receiverType == null)
doCall(calleeConstraintGraph, arguments) doCall(calleeConstraintGraph, arguments, returnType)
else { else {
val receiverNode = argumentToConstraintNode(arguments[0]) val receiverNode = argumentToConstraintNode(arguments[0])
val castedReceiver = ordinaryNode { "CastedReceiver\$${function.symbol}" } doCall(calleeConstraintGraph,
val castedEdge = createCastEdge(castedReceiver, receiverType) listOf(doCast(function, receiverNode, receiverType)) + arguments.drop(1),
receiverNode.addCastEdge(castedEdge) returnType)
doCall(calleeConstraintGraph, listOf(castedReceiver) + arguments.drop(1))
} }
} }
} }
@@ -923,9 +937,7 @@ internal object Devirtualization {
val receiverNode = edgeToConstraintNode(node.arguments[0]) val receiverNode = edgeToConstraintNode(node.arguments[0])
if (receiverType == DataFlowIR.Type.Virtual) if (receiverType == DataFlowIR.Type.Virtual)
constraintGraph.virtualNode.addEdge(receiverNode) constraintGraph.virtualNode.addEdge(receiverNode)
val castedReceiver = ordinaryNode { "CastedReceiver\$${function.symbol}" } val castedReceiver = doCast(function, receiverNode, receiverType)
val castedEdge = createCastEdge(castedReceiver, receiverType)
receiverNode.addCastEdge(castedEdge)
val arguments = listOf(castedReceiver) + node.arguments.drop(1) val arguments = listOf(castedReceiver) + node.arguments.drop(1)
val returnsNode = ordinaryNode { "VirtualCallReturns\$${function.symbol}" } val returnsNode = ordinaryNode { "VirtualCallReturns\$${function.symbol}" }
@@ -935,7 +947,7 @@ internal object Devirtualization {
// Add cast to [Virtual] edge from receiver to returns, if return type is not final. // Add cast to [Virtual] edge from receiver to returns, if return type is not final.
// With this we're reflecting the fact that unknown function can return anything. // With this we're reflecting the fact that unknown function can return anything.
val virtualTypeFilter = BitSet().apply { set(VIRTUAL_TYPE_ID) } val virtualTypeFilter = BitSet().apply { set(VIRTUAL_TYPE_ID) }
if (!returnType.isFinal) { if (!returnType.isFinal && entryPoint == null) {
receiverNode.addCastEdge(Node.CastEdge(returnsNode, virtualTypeFilter)) receiverNode.addCastEdge(Node.CastEdge(returnsNode, virtualTypeFilter))
} }
// And throw anything. // And throw anything.
@@ -1007,19 +1019,21 @@ internal object Devirtualization {
class DevirtualizedCallSite(val callee: DataFlowIR.FunctionSymbol, val possibleCallees: List<DevirtualizedCallee>) class DevirtualizedCallSite(val callee: DataFlowIR.FunctionSymbol, val possibleCallees: List<DevirtualizedCallee>)
class AnalysisResult(val devirtualizedCallSites: Map<DataFlowIR.Node.VirtualCall, DevirtualizedCallSite>) class AnalysisResult(val devirtualizedCallSites: Map<DataFlowIR.Node.VirtualCall, DevirtualizedCallSite>,
val typeHierarchy: DevirtualizationAnalysis.TypeHierarchy,
val instantiatingClasses: Set<DataFlowIR.Type.Declared>)
fun run(irModule: IrModuleFragment, context: Context, moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG) fun run(irModule: IrModuleFragment, context: Context, moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG)
: AnalysisResult { : AnalysisResult {
val devirtualizationAnalysisResult = DevirtualizationAnalysis(context, moduleDFG, externalModulesDFG).analyze() val devirtualizationAnalysisResult = DevirtualizationAnalysis(context, moduleDFG, externalModulesDFG).analyze()
val devirtualizedCallSites = val devirtualizedCallSites =
devirtualizationAnalysisResult devirtualizationAnalysisResult.devirtualizedCallSites
.asSequence() .asSequence()
.filter { it.key.irCallSite != null } .filter { it.key.irCallSite != null }
.associate { it.key.irCallSite!! to it.value } .associate { it.key.irCallSite!! to it.value }
devirtualize(irModule, context, externalModulesDFG, devirtualizedCallSites) devirtualize(irModule, context, externalModulesDFG, devirtualizedCallSites)
removeRedundantCoercions(irModule, context) removeRedundantCoercions(irModule, context)
return AnalysisResult(devirtualizationAnalysisResult) return devirtualizationAnalysisResult
} }
/** /**
+2 -2
View File
@@ -52,8 +52,8 @@ public final class String : Comparable<String>, CharSequence {
external public override fun equals(other: Any?): Boolean external public override fun equals(other: Any?): Boolean
} }
public operator fun kotlin.String?.plus(other: kotlin.Any?): kotlin.String = public inline operator fun kotlin.String?.plus(other: kotlin.Any?): kotlin.String =
(this?.toString() ?: "null").plus(other?.toString() ?: "null") (this?.toString() ?: "null").plus(other?.toString() ?: "null")
public fun Any?.toString() = this?.toString() ?: "null" public inline fun Any?.toString() = this?.toString() ?: "null"
@@ -286,6 +286,7 @@ private fun Kotlin_ObjCExport_isUnchecked(exception: Throwable): Boolean =
@PublishedApi @PublishedApi
@SymbolName("Kotlin_ObjCExport_trapOnUndeclaredException") @SymbolName("Kotlin_ObjCExport_trapOnUndeclaredException")
@ExportForCppRuntime
internal external fun trapOnUndeclaredException(exception: Throwable) internal external fun trapOnUndeclaredException(exception: Throwable)
@ExportForCppRuntime @ExportForCppRuntime
@@ -28,10 +28,12 @@ fun ThrowTypeCastException(): Nothing {
throw TypeCastException() throw TypeCastException()
} }
@ExportForCppRuntime
fun ThrowInvalidReceiverTypeException(klass: KClass<*>): Nothing { fun ThrowInvalidReceiverTypeException(klass: KClass<*>): Nothing {
throw RuntimeException("Unexpected receiver type: " + (klass.qualifiedName ?: "noname")) throw RuntimeException("Unexpected receiver type: " + (klass.qualifiedName ?: "noname"))
} }
@ExportForCppRuntime
internal fun ThrowArithmeticException() : Nothing { internal fun ThrowArithmeticException() : Nothing {
throw ArithmeticException() throw ArithmeticException()
} }