From 0a07db93ed8e8bdc42c233c16fe3b894e6e68dae Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Tue, 21 Nov 2017 12:44:29 +0300 Subject: [PATCH] Data flow graph building --- .../kotlin/backend/konan/KonanPhases.kt | 3 +- .../konan/descriptors/ClassVtablesBuilder.kt | 18 +- .../konan/descriptors/DescriptorUtils.kt | 3 + .../backend/konan/llvm/BinaryInterface.kt | 3 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 8 + .../backend/konan/llvm/LlvmDeclarations.kt | 10 +- .../backend/konan/llvm/RTTIGenerator.kt | 12 +- .../backend/konan/optimizations/DFGBuilder.kt | 580 ++++++++++++++++++ .../backend/konan/optimizations/DataFlowIR.kt | 434 +++++++++++++ .../kotlin/backend/konan/util/util.kt | 2 + 10 files changed, 1055 insertions(+), 18 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index 8320bc87583..45415eb3cbf 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -59,7 +59,8 @@ enum class KonanPhase(val description: String, /* ... ... */ RETURNS_INSERTION("Returns insertion for Unit functions", AUTOBOX, LOWER_COROUTINES, LOWER_ENUMS), /* ... */ BITCODE("LLVM BitCode Generation"), /* ... ... */ RTTI("RTTI Generation"), - /* ... ... */ ESCAPE_ANALYSIS("Escape analysis"), + /* ... ... */ BUILD_DFG("Data flow graph building"), + /* ... ... */ ESCAPE_ANALYSIS("Escape analysis", BUILD_DFG), /* ... ... */ CODEGEN("Code Generation"), /* ... ... */ BITCODE_LINKER("Bitcode linking"), /* */ LINK_STAGE("Link stage"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt index 7f19b0a318d..4ec722d7260 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt @@ -25,6 +25,10 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny +internal val FunctionDescriptor.canBeCalledVirtually: Boolean + // We check that either method is open, or one of declarations it overrides is open. + get() = isOverridable || DescriptorUtils.getAllOverriddenDeclarations(this).any { it.isOverridable } + internal class OverriddenFunctionDescriptor(val descriptor: FunctionDescriptor, overriddenDescriptor: FunctionDescriptor) { val overriddenDescriptor = overriddenDescriptor.original @@ -41,8 +45,7 @@ internal class OverriddenFunctionDescriptor(val descriptor: FunctionDescriptor, } // We check that either method is open, or one of declarations it overrides is open. - return (overriddenDescriptor.isOverridable - || DescriptorUtils.getAllOverriddenDeclarations(overriddenDescriptor).any { it.isOverridable }) + return overriddenDescriptor.canBeCalledVirtually } val inheritsBridge: Boolean @@ -50,6 +53,17 @@ internal class OverriddenFunctionDescriptor(val descriptor: FunctionDescriptor, && OverridingUtil.overrides(descriptor.target, overriddenDescriptor) && descriptor.bridgeDirectionsTo(overriddenDescriptor).allNotNeeded() + fun getImplementation(context: Context): FunctionDescriptor { + val target = descriptor.target + if (!needBridge) return target + val bridgeOwner = if (inheritsBridge) { + target // Bridge is inherited from superclass. + } else { + descriptor + } + return context.specialDeclarationsFactory.getBridgeDescriptor(OverriddenFunctionDescriptor(bridgeOwner, overriddenDescriptor)) + } + override fun toString(): String { return "(descriptor=$descriptor, overriddenDescriptor=$overriddenDescriptor)" } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 652ef3cd2db..5df1a733c4a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.builtins.isSuspendFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptorBase import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.OverridingUtil @@ -80,6 +81,8 @@ private val intrinsicAnnotation = FqName("konan.internal.Intrinsic") internal val FunctionDescriptor.isIntrinsic: Boolean get() = this.annotations.findAnnotation(intrinsicAnnotation) != null +internal fun FunctionDescriptor.externalOrIntrinsic() = isExternal || isIntrinsic || (this is IrBuiltinOperatorDescriptorBase) + private val intrinsicTypes = setOf( "kotlin.Boolean", "kotlin.Char", "kotlin.Byte", "kotlin.Short", diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index df10e3663ee..be4a95a36e2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors @@ -64,6 +65,8 @@ internal tailrec fun DeclarationDescriptor.isExported(): Boolean { return true } + if (DescriptorUtils.isAnonymousObject(this)) + return false if (this is ConstructorDescriptor && constructedClass.kind.isSingleton) { // Currently code generator can access the constructor of the singleton, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 0437f92dba5..34fdba717d9 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -24,6 +24,9 @@ import org.jetbrains.kotlin.backend.common.ir.ir2string import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.* +import org.jetbrains.kotlin.backend.konan.library.impl.buildLibrary +import org.jetbrains.kotlin.backend.konan.optimizations.* +import org.jetbrains.kotlin.backend.konan.util.getValueOrNull import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -73,6 +76,11 @@ internal fun emitLLVM(context: Context) { generateDebugInfoHeader(context) + var moduleDFG: ModuleDFG? = null + phaser.phase(KonanPhase.BUILD_DFG) { + moduleDFG = ModuleDFGBuilder(context, irModule).build() + } + val lifetimes = mutableMapOf() val codegenVisitor = CodeGeneratorVisitor(context, lifetimes) phaser.phase(KonanPhase.ESCAPE_ANALYSIS) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index c8e3d2e1ae7..9f07d872d5b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -99,7 +99,9 @@ internal class StaticFieldLlvmDeclarations(val storage: LLVMValueRef) * All fields of the class instance. * The order respects the class hierarchy, i.e. a class [fields] contains superclass [fields] as a prefix. */ -internal fun ContextUtils.getFields(classDescriptor: ClassDescriptor): List { +internal fun ContextUtils.getFields(classDescriptor: ClassDescriptor) = context.getFields(classDescriptor) + +internal fun Context.getFields(classDescriptor: ClassDescriptor): List { val superClass = classDescriptor.getSuperClassNotAny() // TODO: what if Any has fields? val superFields = if (superClass != null) getFields(superClass) else emptyList() @@ -109,7 +111,7 @@ internal fun ContextUtils.getFields(classDescriptor: ClassDescriptor): List { +private fun Context.getDeclaredFields(classDescriptor: ClassDescriptor): List { // TODO: Here's what is going on here: // The existence of a backing field for a property is only described in the IR, // but not in the PropertyDescriptor. @@ -117,10 +119,10 @@ private fun ContextUtils.getDeclaredFields(classDescriptor: ClassDescriptor): Li // We mark serialized properties with a Konan protobuf extension bit, // so it is present in DeserializedPropertyDescriptor. // - // In this function we check the presence of the backing filed + // In this function we check the presence of the backing field // two ways: first we check IR, then we check the protobuf extension. - val irClass = context.ir.moduleIndexForCodegen.classes[classDescriptor] + val irClass = ir.moduleIndexForCodegen.classes[classDescriptor] val fields = if (irClass != null) { val declarations = irClass.declarations diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 45c2b3ed379..6c55773ba61 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -208,17 +208,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { exportTypeInfoIfRequired(classDesc, classDesc.llvmTypeInfoPtr) } - internal val OverriddenFunctionDescriptor.implementation: FunctionDescriptor - get() { - val target = descriptor.target - if (!needBridge) return target - val bridgeOwner = if (inheritsBridge) { - target // Bridge is inherited from superclass. - } else { - descriptor - } - return context.specialDeclarationsFactory.getBridgeDescriptor(OverriddenFunctionDescriptor(bridgeOwner, overriddenDescriptor)) - } + private val OverriddenFunctionDescriptor.implementation get() = getImplementation(context) data class ReflectionInfo(val packageName: String?, val relativeName: String?) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt new file mode 100644 index 00000000000..79d7ecad349 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -0,0 +1,580 @@ +package org.jetbrains.kotlin.backend.konan.optimizations + +import org.jetbrains.kotlin.backend.common.descriptors.allParameters +import org.jetbrains.kotlin.backend.common.descriptors.isSuspend +import org.jetbrains.kotlin.backend.common.ir.ir2stringWhole +import org.jetbrains.kotlin.backend.common.peek +import org.jetbrains.kotlin.backend.common.pop +import org.jetbrains.kotlin.backend.common.push +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.descriptors.isInterface +import org.jetbrains.kotlin.backend.konan.descriptors.target +import org.jetbrains.kotlin.backend.konan.ir.IrSuspendableExpression +import org.jetbrains.kotlin.backend.konan.ir.IrSuspensionPoint +import org.jetbrains.kotlin.backend.konan.llvm.functionName +import org.jetbrains.kotlin.backend.konan.llvm.localHash +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl +import org.jetbrains.kotlin.ir.util.getArguments +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.OverridingUtil +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isNothing +import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable + +private fun computeErasure(type: KotlinType, erasure: MutableList) { + val descriptor = type.constructor.declarationDescriptor + when (descriptor) { + is ClassDescriptor -> erasure += type.makeNotNullable() + is TypeParameterDescriptor -> { + descriptor.upperBounds.forEach { + computeErasure(it, erasure) + } + } + else -> TODO(descriptor.toString()) + } +} + +internal fun KotlinType.erasure(): List { + val result = mutableListOf() + computeErasure(this, result) + return result +} + +private fun MemberScope.getOverridingOf(function: FunctionDescriptor) = when (function) { + is PropertyGetterDescriptor -> + this.getContributedVariables(function.correspondingProperty.name, NoLookupLocation.FROM_BACKEND) + .firstOrNull { OverridingUtil.overrides(it, function.correspondingProperty) }?.getter + + is PropertySetterDescriptor -> + this.getContributedVariables(function.correspondingProperty.name, NoLookupLocation.FROM_BACKEND) + .firstOrNull { OverridingUtil.overrides(it, function.correspondingProperty) }?.setter + + else -> this.getContributedFunctions(function.name, NoLookupLocation.FROM_BACKEND) + .firstOrNull { OverridingUtil.overrides(it, function) } +} + +private fun IrTypeOperator.isCast() = + this == IrTypeOperator.CAST || this == IrTypeOperator.IMPLICIT_CAST || this == IrTypeOperator.SAFE_CAST + + +private class VariableValues { + val elementData = HashMap>() + + fun addEmpty(variable: VariableDescriptor) = + elementData.getOrPut(variable, { mutableSetOf() }) + + fun add(variable: VariableDescriptor, element: IrExpression) = + elementData[variable]?.add(element) + + fun add(variable: VariableDescriptor, elements: Set) = + elementData[variable]?.addAll(elements) + + fun get(variable: VariableDescriptor): Set? = + elementData[variable] + + fun computeClosure() { + elementData.forEach { key, _ -> + add(key, computeValueClosure(key)) + } + } + + // Computes closure of all possible values for given variable. + private fun computeValueClosure(value: VariableDescriptor): Set { + val result = mutableSetOf() + val seen = mutableSetOf() + dfs(value, seen, result) + return result + } + + private fun dfs(value: VariableDescriptor, seen: MutableSet, result: MutableSet) { + seen += value + val elements = elementData[value] + ?: return + for (element in elements) { + if (element !is IrGetValue) + result += element + else { + val descriptor = element.descriptor + if (descriptor is VariableDescriptor && !seen.contains(descriptor)) + dfs(descriptor, seen, result) + } + } + } +} + +private class ExpressionValuesExtractor(val returnableBlockValues: Map>, + val suspendableExpressionValues: Map>) { + + fun forEachValue(expression: IrExpression, block: (IrExpression) -> Unit) { + when (expression) { + is IrReturnableBlock -> returnableBlockValues[expression]!!.forEach { forEachValue(it, block) } + + is IrSuspendableExpression -> + (suspendableExpressionValues[expression]!! + expression.result).forEach { forEachValue(it, block) } + + is IrSuspensionPoint -> { + forEachValue(expression.result, block) + forEachValue(expression.resumeResult, block) + } + + is IrContainerExpression -> { + if (expression.statements.isNotEmpty()) + forEachValue(expression.statements.last() as IrExpression, block) + } + + is IrWhen -> expression.branches.forEach { forEachValue(it.result, block) } + + is IrMemberAccessExpression -> block(expression) + + is IrGetValue -> block(expression) + + is IrGetField -> block(expression) + + is IrVararg -> /* Sometimes, we keep vararg till codegen phase (for constant arrays). */ + block(expression) + + is IrConst<*> -> block(expression) + + is IrTypeOperatorCall -> { + if (!expression.operator.isCast()) + block(expression) + else { // Propagate cast to sub-values. + forEachValue(expression.argument) { value -> + with(expression) { + block(IrTypeOperatorCallImpl(startOffset, endOffset, type, operator, typeOperand, value)) + } + } + } + } + + is IrTry -> { + forEachValue(expression.tryResult, block) + expression.catches.forEach { forEachValue(it.result, block) } + } + + is IrGetObjectValue -> block(expression) + + is IrFunctionReference -> block(expression) + + is IrSetField -> block(expression) + + else -> { + if ((expression.type.isUnit() || expression.type.isNothing())) { + block(IrGetObjectValueImpl(expression.startOffset, expression.endOffset, + expression.type, (expression.type.constructor.declarationDescriptor as ClassDescriptor))) + } + else TODO(ir2stringWhole(expression)) + } + } + } +} + +internal class ModuleDFG(val functions: Map, + val symbolTable: DataFlowIR.SymbolTable) + +internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFragment) { + + private val DEBUG = 0 + + private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) { + if (DEBUG > severity) block() + } + + private val module = DataFlowIR.Module(irModule.descriptor) + private val symbolTable = DataFlowIR.SymbolTable(context, irModule, module) + + // Possible values of a returnable block. + private val returnableBlockValues = mutableMapOf>() + + // All suspension points within specified suspendable expression. + private val suspendableExpressionValues = mutableMapOf>() + + private val expressionValuesExtractor = ExpressionValuesExtractor(returnableBlockValues, suspendableExpressionValues) + + fun build(): ModuleDFG { + val functions = mutableMapOf() + irModule.accept(object : IrElementVisitorVoid { + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFunction(declaration: IrFunction) { + declaration.body?.let { + DEBUG_OUTPUT(1) { + println("Analysing function ${declaration.descriptor}") + println("IR: ${ir2stringWhole(declaration)}") + } + analyze(declaration.descriptor, it) + } + } + + override fun visitField(declaration: IrField) { + declaration.initializer?.let { + DEBUG_OUTPUT(1) { + println("Analysing global field ${declaration.descriptor}") + println("IR: ${ir2stringWhole(declaration)}") + } + analyze(declaration.descriptor, it) + } + } + + private fun analyze(descriptor: CallableDescriptor, body: IrElement) { + // Find all interesting expressions, variables and functions. + val visitor = ElementFinderVisitor() + body.acceptVoid(visitor) + + DEBUG_OUTPUT(1) { + println("FIRST PHASE") + visitor.variableValues.elementData.forEach { t, u -> + println("VAR $t:") + u.forEach { + println(" ${ir2stringWhole(it)}") + } + } + visitor.expressions.forEach { t -> + println("EXP ${ir2stringWhole(t)}") + } + } + + // Compute transitive closure of possible values for variables. + visitor.variableValues.computeClosure() + + DEBUG_OUTPUT(1) { + println("SECOND PHASE") + visitor.variableValues.elementData.forEach { t, u -> + println("VAR $t:") + u.forEach { + println(" ${ir2stringWhole(it)}") + } + } + } + + val function = FunctionDFGBuilder(expressionValuesExtractor, visitor.variableValues, + descriptor, visitor.expressions, visitor.returnValues).build() + + DEBUG_OUTPUT(1) { + function.debugOutput() + } + + functions.put(function.symbol, function) + } + }, data = null) + + DEBUG_OUTPUT(2) { + println("SYMBOL TABLE:") + symbolTable.classMap.forEach { descriptor, type -> + println(" DESCRIPTOR: $descriptor") + println(" TYPE: $type") + if (type !is DataFlowIR.Type.Declared) + return@forEach + println(" SUPER TYPES:") + type.superTypes.forEach { println(" $it") } + println(" VTABLE:") + type.vtable.forEach { println(" $it") } + println(" ITABLE:") + type.itable.forEach { println(" ${it.key} -> ${it.value}") } + } + } + + return ModuleDFG(functions, symbolTable) + } + + private inner class ElementFinderVisitor : IrElementVisitorVoid { + + val expressions = mutableListOf() + val variableValues = VariableValues() + val returnValues = mutableListOf() + + private val returnableBlocks = mutableMapOf() + private val suspendableExpressionStack = mutableListOf() + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + private fun assignVariable(variable: VariableDescriptor, value: IrExpression) { + expressionValuesExtractor.forEachValue(value) { + variableValues.add(variable, it) + } + } + + override fun visitExpression(expression: IrExpression) { + when (expression) { + is IrMemberAccessExpression, + is IrGetField, + is IrGetObjectValue, + is IrVararg, + is IrConst<*>, + is IrTypeOperatorCall -> + expressions += expression + } + + if (expression is IrReturnableBlock) { + returnableBlocks.put(expression.descriptor, expression) + returnableBlockValues.put(expression, mutableListOf()) + } + if (expression is IrSuspendableExpression) { + suspendableExpressionStack.push(expression) + suspendableExpressionValues.put(expression, mutableListOf()) + } + if (expression is IrSuspensionPoint) + suspendableExpressionValues[suspendableExpressionStack.peek()!!]!!.add(expression) + super.visitExpression(expression) + if (expression is IrReturnableBlock) + returnableBlocks.remove(expression.descriptor) + if (expression is IrSuspendableExpression) + suspendableExpressionStack.pop() + } + + override fun visitSetField(expression: IrSetField) { + expressions += expression + super.visitSetField(expression) + } + + // TODO: hack to overcome bad code in InlineConstructorsTransformation. + private val FQ_NAME_INLINE_CONSTRUCTOR = FqName("konan.internal.InlineConstructor") + + override fun visitReturn(expression: IrReturn) { + val returnableBlock = returnableBlocks[expression.returnTarget] + if (returnableBlock != null) { + returnableBlockValues[returnableBlock]!!.add(expression.value) + } else { // Non-local return. + if (!expression.type.isUnit()) { + if (!expression.returnTarget.annotations.hasAnnotation(FQ_NAME_INLINE_CONSTRUCTOR)) // Not inline constructor. + returnValues += expression.value + } + } + super.visitReturn(expression) + } + + override fun visitSetVariable(expression: IrSetVariable) { + super.visitSetVariable(expression) + assignVariable(expression.descriptor, expression.value) + } + + override fun visitVariable(declaration: IrVariable) { + variableValues.addEmpty(declaration.descriptor) + super.visitVariable(declaration) + declaration.initializer?.let { assignVariable(declaration.descriptor, it) } + } + } + + private val doResumeFunctionDescriptor = context.getInternalClass("CoroutineImpl").unsubstitutedMemberScope + .getContributedFunctions(Name.identifier("doResume"), NoLookupLocation.FROM_BACKEND).single() + private val getContinuationSymbol = context.ir.symbols.getContinuation + + private inner class FunctionDFGBuilder(val expressionValuesExtractor: ExpressionValuesExtractor, + val variableValues: VariableValues, + val descriptor: CallableDescriptor, + val expressions: List, + val returnValues: List) { + + private val allParameters = (descriptor as? FunctionDescriptor)?.allParameters ?: emptyList() + private val templateParameters = allParameters.withIndex().associateBy({ it.value }, { DataFlowIR.Node.Parameter(it.index) }) + + private val continuationParameter = + if (descriptor.isSuspend) + DataFlowIR.Node.Parameter(allParameters.size) + else { + if (doResumeFunctionDescriptor in descriptor.overriddenDescriptors) // is a CoroutineImpl inheritor. + templateParameters[descriptor.dispatchReceiverParameter!!] // It is its own continuation. + else null + } + + private fun getContinuation() = continuationParameter ?: error("Function $descriptor has no continuation parameter") + + private val nodes = mutableMapOf() + private val variables = variableValues.elementData.keys.associateBy( + { it }, + { DataFlowIR.Node.Variable(mutableListOf(), false) } + ) + + fun build(): DataFlowIR.Function { + expressions.forEach { getNode(it) } + val returnsNode = DataFlowIR.Node.Variable(returnValues.map { expressionToEdge(it) }, true) + variables.forEach { descriptor, node -> + variableValues.elementData[descriptor]!!.forEach { + node.values += expressionToEdge(it) + } + } + val allNodes = nodes.values + variables.values + templateParameters.values + returnsNode + + (if (descriptor.isSuspend) listOf(continuationParameter!!) else emptyList()) + + return DataFlowIR.Function( + symbol = symbolTable.mapFunction(descriptor), + isGlobalInitializer = descriptor is PropertyDescriptor, + numberOfParameters = templateParameters.size + if (descriptor.isSuspend) 1 else 0, + body = DataFlowIR.FunctionBody(allNodes.distinct().toList(), returnsNode) + ) + } + + private fun expressionToEdge(expression: IrExpression) = + if (expression is IrTypeOperatorCall && expression.operator.isCast()) + DataFlowIR.Edge(getNode(expression.argument), symbolTable.mapType(expression.typeOperand)) + else DataFlowIR.Edge(getNode(expression), null) + + private fun getNode(expression: IrExpression): DataFlowIR.Node { + if (expression is IrGetValue) { + val descriptor = expression.descriptor + if (descriptor is ParameterDescriptor) + return templateParameters[descriptor]!! + return variables[descriptor as VariableDescriptor]!! + } + return nodes.getOrPut(expression) { + DEBUG_OUTPUT(1) { + println("Converting expression") + println(ir2stringWhole(expression)) + } + val values = mutableListOf() + expressionValuesExtractor.forEachValue(expression) { values += it } + if (values.size != 1) { + DataFlowIR.Node.Variable(values.map { expressionToEdge(it) }, true) + } else { + val value = values[0] + if (value != expression) { + val edge = expressionToEdge(value) + if (edge.castToType == null) + edge.node + else + DataFlowIR.Node.Variable(listOf(edge), true) + } else { + when (value) { + is IrGetValue -> getNode(value) + + is IrVararg, + is IrConst<*>, + is IrFunctionReference -> DataFlowIR.Node.Const(symbolTable.mapType(value.type)) + + is IrGetObjectValue -> DataFlowIR.Node.Singleton( + symbolTable.mapType(value.type), + if (value.type.isNothing()) // is not a singleton though its instance is get with operation. + null + else symbolTable.mapFunction(value.descriptor.constructors.single()) + ) + + is IrCall -> { + if (value.symbol == getContinuationSymbol) { + getContinuation() + } else { + val callee = value.descriptor + val arguments = value.getArguments() + .map { expressionToEdge(it.second) } + .let { + if (callee.isSuspend) + it + DataFlowIR.Edge(getContinuation(), null) + else + it + } + if (callee is ConstructorDescriptor) { + DataFlowIR.Node.NewObject( + symbolTable.mapFunction(callee), + arguments, + symbolTable.mapClass(callee.constructedClass) + ) + } else { + if (callee.isOverridable && value.superQualifier == null) { + val owner = callee.containingDeclaration as ClassDescriptor + val vTableBuilder = context.getVtableBuilder(owner) + if (owner.isInterface) { + DataFlowIR.Node.ItableCall( + symbolTable.mapFunction(callee.target), + symbolTable.mapClass(owner), + callee.functionName.localHash.value, + arguments, + symbolTable.mapType(callee.returnType!!), + value + ) + } else { + val vtableIndex = vTableBuilder.vtableIndex(callee) + assert(vtableIndex >= 0, { "Unable to find function $callee in vtable of $owner" }) + DataFlowIR.Node.VtableCall( + symbolTable.mapFunction(callee.target), + symbolTable.mapClass(owner), + vtableIndex, + arguments, + symbolTable.mapType(callee.returnType!!), + value + ) + } + } else { + val actualCallee = (value.superQualifier?.unsubstitutedMemberScope?.getOverridingOf(callee) ?: callee).target + DataFlowIR.Node.StaticCall( + symbolTable.mapFunction(actualCallee), + arguments, + symbolTable.mapType(actualCallee.returnType!!), + actualCallee.dispatchReceiverParameter?.let { symbolTable.mapType(it.type) } + ) + } + } + } + } + + is IrDelegatingConstructorCall -> { + val thiz = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, + (descriptor as ConstructorDescriptor).constructedClass.thisAsReceiverParameter) + val arguments = listOf(thiz) + value.getArguments().map { it.second } + DataFlowIR.Node.StaticCall( + symbolTable.mapFunction(value.descriptor), + arguments.map { expressionToEdge(it) }, + symbolTable.mapClass(context.builtIns.unit), + symbolTable.mapType(thiz.type) + ) + } + + is IrGetField -> { + val receiver = value.receiver?.let { expressionToEdge(it) } + val receiverType = value.receiver?.let { symbolTable.mapType(it.type) } + DataFlowIR.Node.FieldRead( + receiver, + DataFlowIR.Field( + receiverType, + value.descriptor.name.asString() + ) + ) + } + + is IrSetField -> { + val receiver = value.receiver?.let { expressionToEdge(it) } + val receiverType = value.receiver?.let { symbolTable.mapType(it.type) } + DataFlowIR.Node.FieldWrite( + receiver, + DataFlowIR.Field( + receiverType, + value.descriptor.name.asString() + ), + expressionToEdge(value.value) + ) + } + + is IrTypeOperatorCall -> { + assert(!value.operator.isCast(), { "Casts should've been handled earlier" }) + expressionToEdge(value.argument) // Put argument as a separate vertex. + DataFlowIR.Node.Const(symbolTable.mapType(value.type)) // All operators except casts are basically constants. + } + + else -> TODO("Unknown expression: ${ir2stringWhole(value)}") + } + } + } + } + } + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt new file mode 100644 index 00000000000..584d1f095ac --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -0,0 +1,434 @@ +package org.jetbrains.kotlin.backend.konan.optimizations + +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.descriptors.externalOrIntrinsic +import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract +import org.jetbrains.kotlin.backend.konan.descriptors.isInterface +import org.jetbrains.kotlin.backend.konan.isObjCClass +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.descriptors.* +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes + +internal object DataFlowIR { + + abstract class Type { + // Special marker type forbidding devirtualization on its instances. + object Virtual : Declared(false, true) + + class External(val name: String) : Type() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is External) return false + + return name == other.name + } + + override fun hashCode(): Int { + return name.hashCode() + } + + override fun toString(): String { + return "ExternalType(name='$name')" + } + } + + abstract class Declared(val isFinal: Boolean, val isAbstract: Boolean) : Type() { + val superTypes = mutableListOf() + val vtable = mutableListOf() + val itable = mutableMapOf() + } + + class Public(val name: String, isFinal: Boolean, isAbstract: Boolean) : Declared(isFinal, isAbstract) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Public) return false + + return name == other.name + } + + override fun hashCode(): Int { + return name.hashCode() + } + + override fun toString(): String { + return "PublicType(name='$name')" + } + } + + class Private(val name: String, val index: Int, isFinal: Boolean, isAbstract: Boolean) : Declared(isFinal, isAbstract) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Private) return false + + return index == other.index + } + + override fun hashCode(): Int { + return index + } + + override fun toString(): String { + return "PrivateType(index=$index, name='$name')" + } + } + } + + class Module(val descriptor: ModuleDescriptor) { + var numberOfFunctions = 0 + } + + abstract class FunctionSymbol { + class External(val name: String) : FunctionSymbol() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is External) return false + + return name == other.name + } + + override fun hashCode(): Int { + return name.hashCode() + } + + override fun toString(): String { + return "ExternalFunction(name='$name')" + } + } + + abstract class Declared(val module: Module, val symbolTableIndex: Int) : FunctionSymbol() + + class Public(val name: String, module: Module, symbolTableIndex: Int) : Declared(module, symbolTableIndex) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Public) return false + + return name == other.name + } + + override fun hashCode(): Int { + return name.hashCode() + } + + override fun toString(): String { + return "PublicFunction(name='$name')" + } + } + + class Private(val name: String, val index: Int, module: Module, symbolTableIndex: Int) : Declared(module, symbolTableIndex) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Private) return false + + return index == other.index + } + + override fun hashCode(): Int { + return index + } + + override fun toString(): String { + return "PrivateFunction(index=$index, name='$name')" + } + } + } + + data class Field(val type: Type?, val name: String) + + class Edge(val castToType: Type?) { + + lateinit var node: Node + + constructor(node: Node, castToType: Type?) : this(castToType) { + this.node = node + } + } + + sealed class Node { + class Parameter(val index: Int) : Node() + + class Const(val type: Type) : Node() + + open class Call(val callee: FunctionSymbol, val arguments: List, val returnType: Type) : Node() + + class StaticCall(callee: FunctionSymbol, arguments: List, returnType: Type, + val receiverType: Type?) : Call(callee, arguments, returnType) + + class NewObject(constructor: FunctionSymbol, arguments: List, type: Type) : Call(constructor, arguments, type) + + open class VirtualCall(callee: FunctionSymbol, arguments: List, returnType: Type, + val receiverType: Type, val callSite: IrCall?) : Call(callee, arguments, returnType) + + class VtableCall(callee: FunctionSymbol, receiverType: Type, val calleeVtableIndex: Int, + arguments: List, returnType: Type, callSite: IrCall?) + : VirtualCall(callee, arguments, returnType, receiverType, callSite) + + class ItableCall(callee: FunctionSymbol, receiverType: Type, val calleeHash: Long, + arguments: List, returnType: Type, callSite: IrCall?) + : VirtualCall(callee, arguments, returnType, receiverType, callSite) + + class Singleton(val type: Type, val constructor: FunctionSymbol?) : Node() + + class FieldRead(val receiver: Edge?, val field: Field) : Node() + + class FieldWrite(val receiver: Edge?, val field: Field, val value: Edge) : Node() + + class Variable(values: List, val temp: Boolean) : Node() { + val values = mutableListOf().also { it += values } + } + } + + class FunctionBody(val nodes: List, val returns: Node.Variable) + + class Function(val symbol: FunctionSymbol, + val isGlobalInitializer: Boolean, + val numberOfParameters: Int, + val body: FunctionBody) { + + + fun printNode(node: Node, ids: Map) { + when (node) { + is Node.Const -> + println(" CONST ${node.type}") + + is Node.Parameter -> + println(" PARAM ${node.index}") + + is Node.Singleton -> + println(" SINGLETON ${node.type}") + + is Node.StaticCall -> { + println(" STATIC CALL ${node.callee}") + node.arguments.forEach { + print(" ARG #${ids[it.node]!!}") + if (it.castToType == null) + println() + else + println(" CASTED TO ${it.castToType}") + } + } + + is Node.VtableCall -> { + println(" VIRTUAL CALL ${node.callee}") + println(" RECEIVER: ${node.receiverType}") + println(" VTABLE INDEX: ${node.calleeVtableIndex}") + node.arguments.forEach { + print(" ARG #${ids[it.node]!!}") + if (it.castToType == null) + println() + else + println(" CASTED TO ${it.castToType}") + } + } + + is Node.ItableCall -> { + println(" INTERFACE CALL ${node.callee}") + println(" RECEIVER: ${node.receiverType}") + println(" METHOD HASH: ${node.calleeHash}") + node.arguments.forEach { + print(" ARG #${ids[it.node]!!}") + if (it.castToType == null) + println() + else + println(" CASTED TO ${it.castToType}") + } + } + + is Node.NewObject -> { + println(" NEW OBJECT ${node.callee}") + println(" TYPE ${node.returnType}") + node.arguments.forEach { + print(" ARG #${ids[it.node]!!}") + if (it.castToType == null) + println() + else + println(" CASTED TO ${it.castToType}") + } + } + + is Node.FieldRead -> { + println(" FIELD READ ${node.field}") + print(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}") + if (node.receiver?.castToType == null) + println() + else + println(" CASTED TO ${node.receiver.castToType}") + } + + is Node.FieldWrite -> { + println(" FIELD WRITE ${node.field}") + print(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}") + if (node.receiver?.castToType == null) + println() + else + println(" CASTED TO ${node.receiver.castToType}") + print(" VALUE #${ids[node.value.node]!!}") + if (node.value.castToType == null) + println() + else + println(" CASTED TO ${node.value.castToType}") + } + + is Node.Variable -> { + println(" ${if (node.temp) "TEMP VAR" else "VARIABLE"} ") + node.values.forEach { + print(" VAL #${ids[it.node]!!}") + if (it.castToType == null) + println() + else + println(" CASTED TO ${it.castToType}") + } + + } + + else -> { + println(" UNKNOWN: ${node::class.java}") + } + } + } + + fun debugOutput() { + println("FUNCTION TEMPLATE $symbol") + println("Params: $numberOfParameters") + val ids = body.nodes.withIndex().associateBy({ it.value }, { it.index }) + body.nodes.forEach { + println(" NODE #${ids[it]!!}") + printNode(it, ids) + } + println(" RETURNS") + printNode(body.returns, ids) + } + } + + class SymbolTable(val context: Context, val irModule: IrModuleFragment, val module: Module) { + val classMap = mutableMapOf() + val functionMap = mutableMapOf() + + var privateTypeIndex = 0 + var privateFunIndex = 0 + var couldBeCalledVirtuallyIndex = 0 + + init { + irModule.accept(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFunction(declaration: IrFunction) { + declaration.body?.let { mapFunction(declaration.descriptor) } + } + + override fun visitField(declaration: IrField) { + declaration.initializer?.let { mapFunction(declaration.descriptor) } + } + + override fun visitClass(declaration: IrClass) { + declaration.acceptChildrenVoid(this) + + mapClass(declaration.descriptor) + } + }, data = null) + } + + private fun ClassDescriptor.isFinal() = modality == Modality.FINAL && kind != ClassKind.ENUM_CLASS + + fun mapClass(descriptor: ClassDescriptor): Type { + // Do not try to devirtualize ObjC classes. + if (descriptor.module.name == Name.special("") || descriptor.isObjCClass()) + return Type.Virtual + + val name = descriptor.fqNameSafe.asString() + if (descriptor.module != irModule.descriptor) + return classMap.getOrPut(descriptor) { Type.External(name) } + + classMap[descriptor]?.let { return it } + + val isFinal = descriptor.isFinal() + val isAbstract = descriptor.isAbstract() + val type = if (descriptor.isExported()) + Type.Public(name, isFinal, isAbstract) + else + Type.Private(name, privateTypeIndex++, isFinal, isAbstract) + if (!descriptor.isInterface) { + val vtableBuilder = context.getVtableBuilder(descriptor) + type.vtable += vtableBuilder.vtableEntries.map { mapFunction(it.getImplementation(context)) } + if (!isAbstract) { + vtableBuilder.methodTableEntries.forEach { + type.itable.put( + it.overriddenDescriptor.functionName.localHash.value, + mapFunction(it.getImplementation(context)) + ) + } + } + } + classMap.put(descriptor, type) + type.superTypes += descriptor.defaultType.immediateSupertypes().map { mapType(it) } + return type + } + + fun mapType(type: KotlinType) = + mapClass(type.erasure().single().constructor.declarationDescriptor as ClassDescriptor) + + // TODO: use from LlvmDeclarations. + private fun getFqName(descriptor: DeclarationDescriptor): FqName { + if (descriptor is PackageFragmentDescriptor) { + return descriptor.fqName + } + + val containingDeclaration = descriptor.containingDeclaration + val parent = if (containingDeclaration != null) { + getFqName(containingDeclaration) + } else { + FqName.ROOT + } + + val localName = descriptor.name + return parent.child(localName) + } + + fun mapFunction(descriptor: CallableDescriptor) = descriptor.original.let { + functionMap.getOrPut(it) { + when (it) { + is PropertyDescriptor -> + FunctionSymbol.Private("${it.symbolName}_init", privateFunIndex++, module, -1) + + is FunctionDescriptor -> { + if (it.module != irModule.descriptor || it.externalOrIntrinsic()) + FunctionSymbol.External(it.symbolName) + else { + val isAbstract = it.modality == Modality.ABSTRACT + val classDescriptor = it.containingDeclaration as? ClassDescriptor + val placeToFunctionsTable = !isAbstract && it !is ConstructorDescriptor && classDescriptor != null + && classDescriptor.kind != ClassKind.ANNOTATION_CLASS + && (it.isOverridableOrOverrides || it.name.asString().contains(" error("Unknown descriptor: $it") + } + } + } + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt index e014d3b5bf5..566cf77566e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt @@ -41,3 +41,5 @@ fun String.suffixIfNot(suffix: String) = fun String.removeSuffixIfPresent(suffix: String) = if (this.endsWith(suffix)) this.dropLast(suffix.length) else this + +fun Lazy.getValueOrNull(): T? = if (isInitialized()) value else null \ No newline at end of file