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
import kotlin.native.*
import kotlin.native.internal.ExportTypeInfo
import kotlin.native.internal.ExportForCppRuntime
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
@@ -63,6 +64,7 @@ internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (t
@SymbolName("Kotlin_Interop_refFromObjC")
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
@ExportForCppRuntime
inline fun <T : Any> interpretObjCPointer(objcPtr: NativePtr): T = interpretObjCPointerOrNull<T>(objcPtr)!!
@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 }
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 immutableBlobOf by lazy {
builtIns.builtInsModule.getPackage(KonanFqNames.packageName).memberScope.getContributedFunctions("immutableBlobOf").single()
@@ -459,6 +460,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
lateinit var codegenVisitor: CodeGeneratorVisitor
var devirtualizationAnalysisResult: Devirtualization.AnalysisResult? = null
var referencedFunctions: Set<IrFunction>? = null
val isNativeLibrary: Boolean by lazy {
val kind = config.configuration.get(KonanConfigKeys.PRODUCE)
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.descriptors.ClassDescriptor
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.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrType
@@ -275,6 +276,6 @@ private object IrTypeInlineClassesSupport : InlineClassesSupport<IrClass, IrType
.firstOrNull { it.descriptor.fqNameUnsafe == InteropFqNames.nativePointed }
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
internal const val NATIVE_PTR_NAME = "NativePtr"
internal const val NON_NULL_NATIVE_PTR_NAME = "NonNullNativePtr"
object KonanFqNames {
val packageName = FqName("kotlin.native")
val internalPackageName = FqName("kotlin.native.internal")
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")
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
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.Name
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
@@ -223,6 +224,7 @@ fun ConstructorDescriptor.objCConstructorIsDesignated(): Boolean {
val IrConstructor.isObjCConstructor get() = this.descriptor.annotations.hasAnnotation(objCConstructorFqName)
// TODO-DCE-OBJC-INIT: Selector should be preserved by DCE.
fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? {
return this.descriptor.annotations.findAnnotation(objCConstructorFqName)?.let {
val initSelector = it.getStringValue("initSelector")
@@ -8,6 +8,7 @@ object RuntimeNames {
val exportForCompilerAnnotation = FqName("kotlin.native.internal.ExportForCompiler")
val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo")
val cCall = FqName("kotlinx.cinterop.internal.CCall")
val objCMethodImp = FqName("kotlinx.cinterop.ObjCMethodImp")
val independent = FqName("kotlin.native.internal.Independent")
val filterExceptions = FqName("kotlin.native.internal.FilterExceptions")
val kotlinNativeInternalPackageName = FqName.fromSegments(listOf("kotlin", "native", "internal"))
@@ -291,13 +291,14 @@ internal val dependenciesLowerPhase = SameTypeNamedPhaseWrapper(
internal val bitcodePhase = namedIrModulePhase(
name = "Bitcode",
description = "LLVM Bitcode generation",
lower = contextLLVMSetupPhase then
RTTIPhase then
generateDebugInfoHeaderPhase then
buildDFGPhase then
lower = buildDFGPhase then
serializeDFGPhase then
deserializeDFGPhase then
devirtualizationPhase then
dcePhase then
contextLLVMSetupPhase then
RTTIPhase then
generateDebugInfoHeaderPhase then
escapeAnalysisPhase then
codegenPhase 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.types.isUnit
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.overrides
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.
// 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.getExternalObjCMethodInfo() = this.descriptor.getExternalObjCMethodInfo()
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.incremental.components.NoLookupLocation
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.IrFunctionSymbol
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 enum = symbolTable.referenceClass(builtIns.enum)
val nativePtr = symbolTable.referenceClass(context.nativePtr)
val nativePointed = symbolTable.referenceClass(context.interopBuiltIns.nativePointed)
val nativePtrType = nativePtr.typeWith(arguments = emptyList())
val nonNullNativePtr = symbolTable.referenceClass(context.nonNullNativePtr)
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 invokeSuspendFunction =
symbolTable.referenceSimpleFunction(
baseContinuationImpl.descriptor.unsubstitutedMemberScope
.getContributedFunctions(Name.identifier("invokeSuspend"), NoLookupLocation.FROM_BACKEND)
.single()
)
override val coroutineSuspendedGetter = symbolTable.referenceSimpleFunction(
coroutinesIntrinsicsPackage
.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.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)
/**
@@ -66,14 +49,6 @@ fun IrClass.isNothing() = this.fqNameForIrSerialization == KotlinBuiltIns.FQ_NAM
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.
val IrProperty.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 org.jetbrains.kotlin.backend.konan.*
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(
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(
// Disabled by default !!!!
name = "EscapeAnalysis",
@@ -81,7 +179,7 @@ internal val escapeAnalysisPhase = makeKonanModuleOpPhase(
val callGraph = CallGraphBuilder(
context, context.moduleDFG!!,
externalModulesDFG,
context.devirtualizationAnalysisResult,
context.devirtualizationAnalysisResult!!,
false
).build()
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.fqNameForIrSerialization
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.resolver.TopologicalLibraryOrder
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 {
val caller = functionGenerationContext.irFunction!!
return if (caller.isSuspend)
codegen.param(caller, caller.allParameters.size) // The last argument.
else {
// Suspend call from non-suspend function - must be [BaseContinuationImpl].
assert ((caller as IrSimpleFunction).overrides(invokeSuspendFunction),
// Suspend call from non-suspend function - must be [invokeSuspend].
assert ((caller as IrSimpleFunction).overrides(context.ir.symbols.invokeSuspendFunction.owner),
{ "Expected 'BaseContinuationImpl.invokeSuspend' but was '$caller'" })
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.declarations.*
import org.jetbrains.kotlin.ir.types.isNothing
import org.jetbrains.kotlin.ir.util.defaultType
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.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.name.FqName
@@ -127,7 +124,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
return objectNamer.getName(parent, declaration)
}
return declaration.name
return declaration.nameForIrSerialization
}
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.descriptors.*
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.ir.declarations.*
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.
val vtableEntries = context.getLayoutBuilder(irClass).vtableEntries.map {
val implementation = it.implementation
if (implementation == null || implementation.isExternalObjCClassMethod()) {
if (implementation == null || implementation.isExternalObjCClassMethod() || context.referencedFunctions?.contains(implementation) == false) {
NullPointer(int8Type)
} else {
implementation.entryPointAddress
@@ -269,7 +267,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
// TODO: compile-time resolution limits binary compatibility.
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)
}.sortedBy { it.nameSignature.value }
}
@@ -47,7 +47,7 @@ internal class CallGraph(val directEdges: Map<DataFlowIR.FunctionSymbol, CallGra
internal class CallGraphBuilder(val context: Context,
val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG,
devirtualizationAnalysisResult: Devirtualization.AnalysisResult?,
val devirtualizationAnalysisResult: Devirtualization.AnalysisResult,
val gotoExternal: Boolean) {
private val DEBUG = 0
@@ -56,7 +56,7 @@ internal class CallGraphBuilder(val context: Context,
if (DEBUG > severity) block()
}
private val devirtualizedCallSites = devirtualizationAnalysisResult?.devirtualizedCallSites
private val devirtualizedCallSites = devirtualizationAnalysisResult.devirtualizedCallSites
private fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol {
if (this is DataFlowIR.FunctionSymbol.External)
@@ -88,6 +88,10 @@ internal class CallGraphBuilder(val context: Context,
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) =
nodes.forEach { node ->
when (node) {
@@ -95,42 +99,81 @@ internal class CallGraphBuilder(val context: Context,
is DataFlowIR.Node.Singleton ->
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) {
visitedFunctions += symbol
if (gotoExternal) {
addNode(symbol)
val function = moduleDFG.functions[symbol] ?: externalModulesDFG.functionDFGs[symbol]
val body = function!!.body
body
.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites?.get(it) }
if (devirtualizedCallSite == null) {
val callee = call.callee.resolved()
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& call !is DataFlowIR.Node.VirtualCall
&& !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)
}
val function = moduleDFG.functions[symbol] ?: externalModulesDFG.functionDFGs[symbol] ?: return
val body = function.body
body.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites[it] }
when {
call !is DataFlowIR.Node.VirtualCall -> staticCall(symbol, call, call.callee.resolved())
devirtualizedCallSite != null -> {
devirtualizedCallSite.possibleCallees.forEach {
staticCall(symbol, call, it.callee.resolved())
}
}
body.nodes.filterIsInstance<DataFlowIR.Node.FunctionReference>()
.forEach {
val callee = it.symbol.resolved()
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !directEdges.containsKey(callee))
dfs(callee)
call.receiverType == DataFlowIR.Type.Virtual -> {
// Skip callsite. This can only be for invocations Any's methods on instances of ObjC classes.
}
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 {
var function = moduleDFG.functions[symbol]
var local = true
@@ -141,42 +184,31 @@ internal class CallGraphBuilder(val context: Context,
local = false
}
val body = function.body
body
.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites?.get(it) }
if (devirtualizedCallSite == null) {
val callee = call.callee.resolved()
if (moduleDFG.functions.containsKey(callee))
addNode(callee)
if (local)
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& call !is DataFlowIR.Node.VirtualCall
&& !visitedFunctions.contains(callee))
dfs(callee)
} else {
devirtualizedCallSite.possibleCallees.forEach {
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()
body.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites?.get(it) }
if (devirtualizedCallSite == null) {
val callee = call.callee.resolved()
if (moduleDFG.functions.containsKey(callee))
addNode(callee)
if (local)
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& call !is DataFlowIR.Node.VirtualCall
&& !visitedFunctions.contains(callee))
dfs(callee)
} else {
devirtualizedCallSite.possibleCallees.forEach {
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)
}
}
}
}
}
}
@@ -13,11 +13,11 @@ import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.konan.*
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.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.functionName
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.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
@@ -212,12 +212,16 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
}
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) {
println("Analysing function ${declaration.descriptor}")
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.target
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.functionName
import org.jetbrains.kotlin.backend.konan.llvm.isExported
import org.jetbrains.kotlin.backend.konan.llvm.localHash
import org.jetbrains.kotlin.backend.konan.llvm.symbolName
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler.functionName
import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler.symbolName
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
import org.jetbrains.kotlin.descriptors.Modality
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.isNothing
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
@@ -121,6 +118,7 @@ internal object DataFlowIR {
val IS_GLOBAL_INITIALIZER = 1
val RETURNS_UNIT = 2
val RETURNS_NOTHING = 4
val EXPLICITLY_EXPORTED = 8
}
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 returnsUnit = attributes.and(FunctionAttributes.RETURNS_UNIT) != 0
val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0
val explicitlyExported = attributes.and(FunctionAttributes.EXPLICITLY_EXPORTED) != 0
var escapes: Int? = null
var pointsTo: IntArray? = null
@@ -572,12 +571,6 @@ internal object DataFlowIR {
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) {
is IrFunction -> mapFunction(declaration)
is IrField -> mapPropertyInitializer(declaration)
@@ -587,7 +580,13 @@ internal object DataFlowIR {
private fun mapFunction(function: IrFunction): FunctionSymbol = function.target.let {
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 returnsNothing = !it.isSuspend && it.returnType.isNothing()
var attributes = 0
@@ -595,6 +594,11 @@ internal object DataFlowIR {
attributes = attributes or FunctionAttributes.RETURNS_UNIT
if (returnsNothing)
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 {
it.isExternal || (it.symbol in context.irBuiltIns.irBuiltInsSymbols) -> {
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.expressions.*
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.impl.IrReturnableBlockSymbolImpl
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.toKotlinType
import org.jetbrains.kotlin.ir.util.irCall
@@ -67,19 +69,22 @@ internal object Devirtualization {
listOf(moduleDFG.symbolTable.mapFunction(entryPoint).resolved())
else
// 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
.filterIsInstance<DataFlowIR.Type.Declared>()
.flatMap { it.vtable + it.itable.values }
.filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
.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 =
moduleDFG.functions.keys.filter { it.isGlobalInitializer } +
moduleDFG.symbolTable.functionMap.values.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 {
@@ -88,9 +93,9 @@ internal object Devirtualization {
private val VIRTUAL_TYPE_ID = 0 // Id of [DataFlowIR.Type.Virtual].
private class DevirtualizationAnalysis(val context: Context,
val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG) {
internal class DevirtualizationAnalysis(val context: Context,
val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG) {
private val entryPoint = context.ir.symbols.entryPoint?.owner
@@ -188,7 +193,7 @@ internal object Devirtualization {
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>>()
init {
@@ -229,6 +234,7 @@ internal object Devirtualization {
fun search(): Set<DataFlowIR.Type.Declared> {
// Rapid Type Analysis: find all instantiations and conservatively estimate call graph.
// Add all final parameters of the roots.
rootSet.forEach {
it.parameters
@@ -238,8 +244,7 @@ internal object Devirtualization {
}
if (entryPoint == null) {
// For library assume all public non-abstract classes could be instantiated.
moduleDFG.symbolTable.classMap.values
.asSequence()
symbolTable.classMap.values
.filterIsInstance<DataFlowIR.Type.Public>()
.filter { !it.isAbstract }
.forEach { addInstantiatingClass(it) }
@@ -333,7 +338,7 @@ internal object Devirtualization {
val function = (moduleDFG.functions[resolvedFunctionSymbol]
?: externalModulesDFG.functionDFGs[resolvedFunctionSymbol])
?: error("Unknown function $resolvedFunctionSymbol")
?: return
DEBUG_OUTPUT(1) { function.debugOutput() }
@@ -452,7 +457,7 @@ internal object Devirtualization {
} while (cur != node)
}
fun analyze(): Map<DataFlowIR.Node.VirtualCall, DevirtualizedCallSite> {
fun analyze(): AnalysisResult {
val functions = moduleDFG.functions + externalModulesDFG.functionDFGs
val typeHierarchy = TypeHierarchy(symbolTable.classMap.values.filterIsInstance<DataFlowIR.Type.Declared>() +
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>,
@@ -719,10 +724,10 @@ internal object Devirtualization {
fieldNode(constraintGraph.arrayItemField)
)
}
rootSet.forEach { createFunctionConstraintGraph(it, true)!! }
rootSet.forEach { createFunctionConstraintGraph(it, true) }
while (stack.isNotEmpty()) {
val symbol = stack.pop()
val function = functions[symbol] ?: error("Unknown function: $symbol")
val function = functions[symbol] ?: continue
val body = function.body
val functionConstraintGraph = constraintGraph.functions[symbol]!!
@@ -757,7 +762,7 @@ internal object Devirtualization {
symbol.parameters.forEachIndexed { index, type ->
val resolvedType = type.type.resolved()
val node = if (!resolvedType.isFinal)
constraintGraph.virtualNode
constraintGraph.virtualNode // TODO: May be do this only for a library?
else
concreteClass(resolvedType)
node.addEdge(parameters[index])
@@ -783,14 +788,18 @@ internal object Devirtualization {
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,
edge: DataFlowIR.Edge): Node {
val result = dfgNodeToConstraintNode(function, edge.node)
val castToType = edge.castToType?.resolved() ?: return result
val castNode = ordinaryNode { "Cast\$${function.symbol}" }
val castEdge = createCastEdge(castNode, castToType)
result.addCastEdge(castEdge)
return castNode
return doCast(function, result, castToType)
}
/**
@@ -809,7 +818,7 @@ internal object Devirtualization {
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) {
"Function ${callee.symbol} takes ${callee.parameters.size} but caller ${function.symbol}" +
" provided ${arguments.size}"
@@ -818,7 +827,7 @@ internal object Devirtualization {
val argument = argumentToConstraintNode(arguments[index])
argument.addEdge(parameter)
}
return callee.returns
return doCast(function, callee.returns, returnType)
}
fun doCall(callee: DataFlowIR.FunctionSymbol,
@@ -832,20 +841,25 @@ internal object Devirtualization {
val fictitiousReturnNode = ordinaryNode { "External$resolvedCallee" }
if (returnType.isFinal)
concreteClass(returnType).addEdge(fictitiousReturnNode)
else
else {
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
}
} else {
calleeConstraintGraph.throws.addEdge(function.throws)
if (receiverType == null)
doCall(calleeConstraintGraph, arguments)
doCall(calleeConstraintGraph, arguments, returnType)
else {
val receiverNode = argumentToConstraintNode(arguments[0])
val castedReceiver = ordinaryNode { "CastedReceiver\$${function.symbol}" }
val castedEdge = createCastEdge(castedReceiver, receiverType)
receiverNode.addCastEdge(castedEdge)
doCall(calleeConstraintGraph, listOf(castedReceiver) + arguments.drop(1))
doCall(calleeConstraintGraph,
listOf(doCast(function, receiverNode, receiverType)) + arguments.drop(1),
returnType)
}
}
}
@@ -923,9 +937,7 @@ internal object Devirtualization {
val receiverNode = edgeToConstraintNode(node.arguments[0])
if (receiverType == DataFlowIR.Type.Virtual)
constraintGraph.virtualNode.addEdge(receiverNode)
val castedReceiver = ordinaryNode { "CastedReceiver\$${function.symbol}" }
val castedEdge = createCastEdge(castedReceiver, receiverType)
receiverNode.addCastEdge(castedEdge)
val castedReceiver = doCast(function, receiverNode, receiverType)
val arguments = listOf(castedReceiver) + node.arguments.drop(1)
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.
// With this we're reflecting the fact that unknown function can return anything.
val virtualTypeFilter = BitSet().apply { set(VIRTUAL_TYPE_ID) }
if (!returnType.isFinal) {
if (!returnType.isFinal && entryPoint == null) {
receiverNode.addCastEdge(Node.CastEdge(returnsNode, virtualTypeFilter))
}
// And throw anything.
@@ -1007,19 +1019,21 @@ internal object Devirtualization {
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)
: AnalysisResult {
val devirtualizationAnalysisResult = DevirtualizationAnalysis(context, moduleDFG, externalModulesDFG).analyze()
val devirtualizedCallSites =
devirtualizationAnalysisResult
devirtualizationAnalysisResult.devirtualizedCallSites
.asSequence()
.filter { it.key.irCallSite != null }
.associate { it.key.irCallSite!! to it.value }
devirtualize(irModule, context, externalModulesDFG, devirtualizedCallSites)
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
}
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")
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
@SymbolName("Kotlin_ObjCExport_trapOnUndeclaredException")
@ExportForCppRuntime
internal external fun trapOnUndeclaredException(exception: Throwable)
@ExportForCppRuntime
@@ -28,10 +28,12 @@ fun ThrowTypeCastException(): Nothing {
throw TypeCastException()
}
@ExportForCppRuntime
fun ThrowInvalidReceiverTypeException(klass: KClass<*>): Nothing {
throw RuntimeException("Unexpected receiver type: " + (klass.qualifiedName ?: "noname"))
}
@ExportForCppRuntime
internal fun ThrowArithmeticException() : Nothing {
throw ArithmeticException()
}