From eabe2435bffff8748a7a6a434c4355d4c44f8d8b Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Wed, 2 Aug 2017 18:36:49 +0300 Subject: [PATCH] Rewrote escape analysis --- .../jetbrains/kotlin/backend/konan/Context.kt | 3 + .../kotlin/backend/konan/EscapeAnalysis.kt | 1728 ++++++++++++----- .../kotlin/backend/konan/EscapeAnalysis.proto | 15 + .../kotlin/backend/konan/GraphAlgorithms.kt | 99 + .../kotlin/backend/konan/KonanPhases.kt | 1 + .../backend/konan/library/KonanLibrary.kt | 4 +- .../konan/library/KonanLibraryReader.kt | 1 + .../konan/library/KonanLibraryWriter.kt | 1 + .../library/impl/KonanLibraryReaderImpl.kt | 1 + .../library/impl/KonanLibraryWriterImpl.kt | 8 +- .../backend/konan/llvm/CodeGenerator.kt | 31 +- .../kotlin/backend/konan/llvm/ContextUtils.kt | 99 +- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 17 +- .../lower/InlineConstructorsTransformation.kt | 7 +- .../tests/runtime/memory/escape0.kt | 31 +- runtime/src/main/cpp/Arrays.cpp | 4 +- runtime/src/main/cpp/Common.h | 2 + runtime/src/main/cpp/KString.h | 2 - runtime/src/main/cpp/Memory.cpp | 39 +- runtime/src/main/cpp/Memory.h | 29 +- runtime/src/main/kotlin/konan/Annotations.kt | 4 + runtime/src/main/kotlin/kotlin/Array.kt | 2 + .../kotlin/kotlin/collections/ArrayUtil.kt | 2 + 23 files changed, 1601 insertions(+), 529 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.proto create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/GraphAlgorithms.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 7cb6addb8eb..dc6acb3d531 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -170,6 +170,9 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { // But we have to wait until the code generation phase, // to dump this information into generated file. var serializedLinkData: LinkData? = null + val moduleEscapeAnalysisResult: ModuleEscapeAnalysisResult.ModuleEAResult.Builder by lazy { + ModuleEscapeAnalysisResult.ModuleEAResult.newBuilder() + } @Deprecated("") lateinit var psi2IrGeneratorContext: GeneratorContext diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.kt index 5487dfc6a01..20e5be9bddf 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.kt @@ -16,496 +16,1304 @@ package org.jetbrains.kotlin.backend.konan +import org.jetbrains.kotlin.backend.common.IrElementVisitorVoidWithContext import org.jetbrains.kotlin.backend.common.descriptors.allParameters -import org.jetbrains.kotlin.backend.konan.llvm.Lifetime -import org.jetbrains.kotlin.backend.konan.llvm.RuntimeAware import org.jetbrains.kotlin.ir.util.getArguments -import org.jetbrains.kotlin.backend.common.ir.ir2string -import org.jetbrains.kotlin.backend.konan.llvm.isObjectType +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.descriptors.target +import org.jetbrains.kotlin.backend.konan.ir.IrReturnableBlock +import org.jetbrains.kotlin.backend.konan.ir.IrSuspendableExpression +import org.jetbrains.kotlin.backend.konan.ir.IrSuspensionPoint +import org.jetbrains.kotlin.backend.konan.llvm.* +import org.jetbrains.kotlin.backend.konan.lower.isInlineConstructor import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.IrElement -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.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl 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.constants.ConstantValue +import org.jetbrains.kotlin.resolve.constants.IntValue +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.* -val DEBUG = 0 +internal object EscapeAnalysis { -// Roles in which particular object reference is being used. Lifetime is computed from -// all roles reference. -internal enum class Role { - // If reference is created as call result. - CALL_RESULT, - // If reference is created as allocation call result. - ALLOC_RESULT, - // If reference is being returned. - RETURN_VALUE, - // If reference is being thrown. - THROW_VALUE, - // If reference's field is being read. - FIELD_READ, - // If reference's field is being written to. - FIELD_WRITTEN, - // If reference is being read from the field. - READ_FROM_FIELD, - // If reference is being written to the field. - WRITTEN_TO_FIELD, - // If reference is being read from the gloabl. - READ_FROM_GLOBAL, - // If reference is being written to the global. - WRITTEN_TO_GLOBAL, - // Outgoing call argument. - CALL_ARGUMENT -} + /* + * The goal of escape analysis is to estimate lifetimes of all expressions in a program. + * Possible lifetimes are: + * 1. Local - an object is used only within a function. + * 2. Return value - an object is either returned or set to a field of an object being returned. + * 3. Parameter - an object is set to a field of exactly one parameter of a function. + * 4. Global - otherwise. + * + * The analysis is performed in two main steps - intraprocedural and interprocedural. + * During intraprocedural analysis we remove all control flow related expressions and compute all possible + * values of all variables within a function. + * The goal of interprocedural analysis is to build points-to graph (an edge is created from A to B iff A holds + * a reference to B). This is done by building call graph (using devirtualization for more precise result). + * + * How do exactly we build the points-to graph out of the call graph? + * 1. Build condensation of the call graph. + * 2. Handle vertices of the resulting DAG in topological order (ensuring that all functions being called + * are already handled). + * 3. For a strongly connected component build the points-to graph iteratively starting with empty graph + * (since edges can only be added the process will end eventually). + * + * When we have the points-to graph it is easy to compute lifetimes - using DFS compute the graph's closure. + */ -internal class RoleInfoEntry(val data: Any? = null) + private val DEBUG = 0 -internal open class RoleInfo { - val entries = mutableListOf() - open fun add(entry: RoleInfoEntry) = entries.add(entry) -} - -private fun RuntimeAware.isInteresting(type: KotlinType) : Boolean = - !type.isUnit() && !type.isNothing() && isObjectType(type) - -internal class Roles { - val data = HashMap() - - fun add(role: Role, info: RoleInfoEntry?) { - val entry = data.getOrPut(role, { RoleInfo() }) - if (info != null) entry.add(info) + private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) { + if (DEBUG > severity) block() } - fun add(roles: Roles) { - roles.data.forEach { role, info -> - info.entries.forEach { entry -> - add(role, entry) + // Roles in which particular object reference is being used. Lifetime is computed from all roles reference. + private enum class Role { + // If reference is being returned. + RETURN_VALUE, + // If reference is being thrown. + THROW_VALUE, + // If reference's field is being written to. + FIELD_WRITTEN, + // If reference is being written to the global. + WRITTEN_TO_GLOBAL + } + + private class RoleInfoEntry(val data: Any? = null) + + private open class RoleInfo { + val entries = mutableListOf() + + open fun add(entry: RoleInfoEntry) = entries.add(entry) + } + + private fun isInteresting(type: KotlinType?): Boolean = + type != null && !type.isUnit() && !type.isNothing() + + private class Roles { + val data = HashMap() + + fun add(role: Role, info: RoleInfoEntry?) { + val entry = data.getOrPut(role, { RoleInfo() }) + if (info != null) entry.add(info) + } + + fun has(role: Role): Boolean = data[role] != null + + fun escapes() = has(Role.WRITTEN_TO_GLOBAL) || has(Role.THROW_VALUE) + } + + 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 get(variable: VariableDescriptor): Set? = + elementData[variable] + + fun computeClosure() { + elementData.forEach { key, value -> + value.addAll(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) + } } } } - fun remove(role: Role) = data.remove(role) + private class ParameterRoles { + val elementData = HashMap() - fun has(role: Role) : Boolean = data[role] != null - - fun escapes() : Boolean { - return has(Role.WRITTEN_TO_FIELD) || has(Role.WRITTEN_TO_GLOBAL) || - has(Role.CALL_ARGUMENT) || has(Role.THROW_VALUE) - } - - fun pure() : Boolean = !escapes() && has(Role.RETURN_VALUE) - - fun info(role: Role) : RoleInfo? = data[role] - - override fun toString() : String { - val builder = StringBuilder() - data.forEach { t, u -> - builder.append(t.name) - builder.append(": ") - builder.append(u.entries.joinToString(", ")) - builder.append("; ") + fun addParameter(parameter: ParameterDescriptor) { + elementData.getOrPut(parameter) { Roles() } } - return builder.toString() - } -} -internal class VariableValues { - val elementData = HashMap>() - - fun addEmpty(variable: ValueDescriptor) = - elementData.getOrPut(variable, { mutableSetOf() }) - fun add(variable: ValueDescriptor, element: IrExpression) = - elementData.get(variable)?.add(element) - fun add(variable: ValueDescriptor, elements: Set) = - elementData.get(variable)?.addAll(elements) - fun get(variable: ValueDescriptor) : Set? = - elementData[variable] - - fun computeClosure() { - elementData.forEach { key, _ -> - add(key, computeValueClosure(key)) + fun add(parameter: ParameterDescriptor, role: Role, roleInfoEntry: RoleInfoEntry?) { + val roles = elementData.getOrPut(parameter, { Roles() }) + roles.add(role, roleInfoEntry) } } - // Computes closure of all possible values for given variable. - fun computeValueClosure(value: ValueDescriptor): Set { - val result = mutableSetOf() - val workset = mutableSetOf(value) - val seen = mutableSetOf() - while (!workset.isEmpty()) { - val value = workset.first() - workset -= value - val elements = elementData[value] ?: continue - for (element in elements) { - if (element is IrGetValue) { - if (!seen.contains(element)) { - seen.add(element) - workset.add(element.descriptor) + private class ExpressionValuesExtractor(val returnableBlockValues: Map>, + val suspendableExpressionValues: Map>) { + + fun forEachValue(expression: IrExpression, block: (IrExpression) -> Unit) { + if (expression.type.isUnit() || expression.type.isNothing()) return + 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 -> 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) + + // If constant plays certain role - this information is useless. + is IrConst<*> -> { } + + is IrTypeOperatorCall -> { + when (expression.operator) { + IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.CAST -> + forEachValue(expression.argument, block) + + // No info from those ones. + IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF, + IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> { } + + else -> error("Unexpected type operator: ${expression.operator}") + } + } + + is IrTry -> { + forEachValue(expression.tryResult, block) + expression.catches.forEach { forEachValue(it.result, block) } + } + + is IrGetObjectValue -> { /* Shall we do anything here? */ + block(expression) + } + + else -> error("Unexpected expression: ${ir2stringWhole(expression)}") + } + } + } + + private fun ExpressionValuesExtractor.extractNodesUsingVariableValues(expression: IrExpression, + variableValues: VariableValues?): List { + val values = mutableListOf() + forEachValue(expression) { + if (it !is IrGetValue) + values += it + else { + val descriptor = it.descriptor + if (descriptor is ParameterDescriptor) + values += it.descriptor + else { + descriptor as VariableDescriptor + variableValues?.get(descriptor)?.forEach { + if (it !is IrGetValue) + values += it + else if (it.descriptor is ParameterDescriptor) { + values += it.descriptor + } + } + } + } + } + return values + } + + private class FunctionAnalysisResult(val function: IrFunction, + val expressionToRoles: Map, + val variableValues: VariableValues, + val parameterRoles: ParameterRoles) + + private class IntraproceduralAnalysisResult(val functionAnalysisResults: Map, + val expressionValuesExtractor: ExpressionValuesExtractor) + + private class IntraproceduralAnalysis() { + + // 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) + + private fun isInteresting(expression: IrExpression) = + (expression is IrMemberAccessExpression && isInteresting(expression.type)) + || (expression is IrGetValue && isInteresting(expression.type)) + || (expression is IrGetField && isInteresting(expression.type)) + + private fun isInteresting(variable: ValueDescriptor) = isInteresting(variable.type) + + fun analyze(irModule: IrModuleFragment): IntraproceduralAnalysisResult { + val result = mutableMapOf() + irModule.accept(object : IrElementVisitorVoid { + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFunction(declaration: IrFunction) { + val body = declaration.body + ?: return + + DEBUG_OUTPUT(1) { println("Analysing function ${declaration.descriptor}") } + + // Find all interesting expressions, variables and functions. + val parameterRoles = ParameterRoles() + declaration.descriptor.allParameters.forEach { + if (isInteresting(it)) + parameterRoles.addParameter(it) + } + val visitor = ElementFinderVisitor() + declaration.acceptVoid(visitor) + val functionAnalysisResult = FunctionAnalysisResult(declaration, visitor.expressionToRoles, + visitor.variableValues, parameterRoles) + result.put(declaration.descriptor, functionAnalysisResult) + + // On this pass, we collect all possible variable values and assign roles to expressions. + body.acceptVoid(RoleAssignerVisitor(declaration.descriptor, functionAnalysisResult, false)) + + DEBUG_OUTPUT(1) { + println("FIRST PHASE") + functionAnalysisResult.parameterRoles.elementData.forEach { t, u -> + println("PARAM $t: $u") + } + functionAnalysisResult.variableValues.elementData.forEach { t, u -> + println("VAR $t:") + u.forEach { + println(" ${ir2stringWhole(it)}") + } + } + functionAnalysisResult.expressionToRoles.forEach { t, u -> + println("EXP ${ir2stringWhole(t)}") + println(" :$u") + } + } + + // Compute transitive closure of possible values for variables. + functionAnalysisResult.variableValues.computeClosure() + + DEBUG_OUTPUT(1) { + println("SECOND PHASE") + functionAnalysisResult.parameterRoles.elementData.forEach { t, u -> + println("PARAM $t: $u") + } + functionAnalysisResult.variableValues.elementData.forEach { t, u -> + println("VAR $t:") + u.forEach { + println(" ${ir2stringWhole(it)}") + } + } + } + + // On this pass, we use possible variable values to assign roles to expressions. + body.acceptVoid(RoleAssignerVisitor(declaration.descriptor, functionAnalysisResult, true)) + + DEBUG_OUTPUT(1) { + println("THIRD PHASE") + functionAnalysisResult.parameterRoles.elementData.forEach { t, u -> + println("PARAM $t: $u") + } + functionAnalysisResult.expressionToRoles.forEach { t, u -> + println("EXP ${ir2stringWhole(t)}") + println(" :$u") + } + } + } + }, data = null) + + return IntraproceduralAnalysisResult(result, expressionValuesExtractor) + } + + private inner class ElementFinderVisitor : IrElementVisitorVoid { + + val expressionToRoles = mutableMapOf() + val variableValues = VariableValues() + + private val returnableBlocks = mutableMapOf() + private val suspendableExpressionStack = mutableListOf() + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitExpression(expression: IrExpression) { + if (isInteresting(expression)) { + expressionToRoles[expression] = Roles() + } + 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 visitReturn(expression: IrReturn) { + val returnableBlock = returnableBlocks[expression.returnTarget] + if (returnableBlock != null) { + returnableBlockValues[returnableBlock]!!.add(expression.value) + } + super.visitReturn(expression) + } + + override fun visitVariable(declaration: IrVariable) { + if (isInteresting(declaration.descriptor)) + variableValues.addEmpty(declaration.descriptor) + super.visitVariable(declaration) + } + } + + // + // elementToRoles is filled with all possible roles given element can play. + // varValues is filled with all possible elements that could be stored in a variable. + // + private inner class RoleAssignerVisitor(val functionDescriptor: FunctionDescriptor, + functionAnalysisResult: FunctionAnalysisResult, + val useVarValues: Boolean) : IrElementVisitorVoid { + + private val expressionRoles = functionAnalysisResult.expressionToRoles + private val variableValues = functionAnalysisResult.variableValues + private val parameterRoles = functionAnalysisResult.parameterRoles + + private fun assignExpressionRole(expression: IrExpression, role: Role, infoEntry: RoleInfoEntry?) { + if (!useVarValues) + expressionRoles[expression]?.add(role, infoEntry) + } + + private fun assignValueRole(value: ValueDescriptor, role: Role, infoEntry: RoleInfoEntry?) { + if (!useVarValues) return + // Whenever we see variable use in certain role - we propagate this role + // to all possible expressions this variable can be assigned from. + when (value) { + is ParameterDescriptor -> { + if (isInteresting(value)) + parameterRoles.add(value, role, infoEntry) + } + + is VariableDescriptor -> { + val possibleValues = variableValues.get(value) + if (possibleValues != null) { + for (possibleValue in possibleValues) { + expressionRoles[possibleValue]?.add(role, infoEntry) + } + } + } + } + } + + // Here we handle variable assignment. + private fun assignVariable(variable: VariableDescriptor, value: IrExpression) { + if (useVarValues) return + expressionValuesExtractor.forEachValue(value) { + variableValues.add(variable, it) + } + } + + // Here we assign a role to expression's value. + private fun assignRole(expression: IrExpression, role: Role, infoEntry: RoleInfoEntry?) { + expressionValuesExtractor.forEachValue(expression) { + if (it is IrGetValue) + assignValueRole(it.descriptor, role, infoEntry) + else assignExpressionRole(it, role, infoEntry) + } + } + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitSetField(expression: IrSetField) { + val receiver = expression.receiver + if (receiver == null) + assignRole(expression.value, Role.WRITTEN_TO_GLOBAL, RoleInfoEntry(expression)) + else { + val nodes = expressionValuesExtractor.extractNodesUsingVariableValues( + expression = expression.value, + variableValues = if (useVarValues) variableValues else null + ) + nodes.forEach { assignRole(receiver, Role.FIELD_WRITTEN, RoleInfoEntry(it)) } + } + super.visitSetField(expression) + } + + override fun visitGetObjectValue(expression: IrGetObjectValue) { + assignRole(expression, Role.WRITTEN_TO_GLOBAL, RoleInfoEntry(expression)) + super.visitGetObjectValue(expression) + } + + override fun visitGetField(expression: IrGetField) { + val receiver = expression.receiver + if (receiver == null) + assignRole(expression, Role.WRITTEN_TO_GLOBAL, RoleInfoEntry(expression)) + else { + // Receiver holds reference to all its fields. + assignRole(receiver, Role.FIELD_WRITTEN, RoleInfoEntry(expression)) + + /* + * The opposite (a field points to its receiver) is also kind of true. + * Here is an example why we need these edges: + * + * class B + * class A { val b = B() } + * fun foo(): B { + * val a = A() <- here [a] is created and so does [a.b], therefore they have the same lifetime. + * return a.b <- a.b escapes to return value. If there were no edge from [a.b] to [a], + * then [a] would've been considered local and since [a.b] has the same lifetime as [a], + * [a.b] would be local as well. + * } + * + */ + val nodes = expressionValuesExtractor.extractNodesUsingVariableValues( + expression = receiver, + variableValues = if (useVarValues) variableValues else null + ) + nodes.forEach { assignRole(expression, Role.FIELD_WRITTEN, RoleInfoEntry(it)) } + } + super.visitGetField(expression) + } + + override fun visitField(declaration: IrField) { + val initializer = declaration.initializer + if (initializer != null) { + assert(declaration.descriptor.dispatchReceiverParameter == null, + { "Instance field initializers should've been lowered" }) + assignRole(initializer.expression, Role.WRITTEN_TO_GLOBAL, RoleInfoEntry(declaration)) + } + super.visitField(declaration) + } + + override fun visitSetVariable(expression: IrSetVariable) { + assignVariable(expression.descriptor, expression.value) + super.visitSetVariable(expression) + } + + override fun visitVariable(declaration: IrVariable) { + declaration.initializer?.let { assignVariable(declaration.descriptor, it) } + super.visitVariable(declaration) + } + + // TODO: hack to overcome bad code in InlineConstructorsTransformation. + override fun visitReturn(expression: IrReturn) { + if (expression.returnTarget == functionDescriptor // Non-local return. + && !functionDescriptor.isInlineConstructor) // Not inline constructor. + assignRole(expression.value, Role.RETURN_VALUE, RoleInfoEntry(expression)) + super.visitReturn(expression) + } + + override fun visitThrow(expression: IrThrow) { + assignRole(expression.value, Role.THROW_VALUE, RoleInfoEntry(expression)) + super.visitThrow(expression) + } + + override fun visitVararg(expression: IrVararg) { + expression.elements.forEach { + when (it) { + is IrExpression -> { + val nodes = expressionValuesExtractor.extractNodesUsingVariableValues( + expression = it, + variableValues = if (useVarValues) variableValues else null + ) + nodes.forEach { assignRole(expression, Role.FIELD_WRITTEN, RoleInfoEntry(it)) } + } + is IrSpreadElement -> { + val nodes = expressionValuesExtractor.extractNodesUsingVariableValues( + expression = it.expression, + variableValues = if (useVarValues) variableValues else null + ) + nodes.forEach { assignRole(expression, Role.FIELD_WRITTEN, RoleInfoEntry(it)) } + } + else -> error("Unsupported vararg element") + } + } + super.visitVararg(expression) + } + } + } + + private class ParameterEscapeAnalysisResult(val escapes: Boolean, val pointsTo: IntArray) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ParameterEscapeAnalysisResult) return false + + if (escapes != other.escapes) return false + if (pointsTo.size != other.pointsTo.size) return false + return pointsTo.indices.all { pointsTo[it] == other.pointsTo[it] } + } + + override fun hashCode(): Int { + var result = escapes.hashCode() + pointsTo.forEach { result = 31 * result + it.hashCode() } + return result + } + + override fun toString() = "${if (escapes) "ESCAPES" else "LOCAL"}, POINTS TO: ${pointsTo.contentToString()}" + } + + private class FunctionEscapeAnalysisResult(val parameters: Array) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FunctionEscapeAnalysisResult) return false + + if (parameters.size != other.parameters.size) return false + return parameters.indices.all { parameters[it] == other.parameters[it] } + } + + override fun hashCode(): Int { + var result = 0 + parameters.forEach { result = 31 * result + it.hashCode() } + return result + } + + override fun toString(): String { + return parameters.withIndex().joinToString("\n") { + if (it.index < parameters.size - 1) + "PARAM#${it.index}: ${it.value}" + else "RETURN: ${it.value}" + } + } + + val isTrivial get() = parameters.all { !it.escapes && it.pointsTo.isEmpty() } + + companion object { + fun fromBits(escapesMask: Int, pointsToMasks: List) = FunctionEscapeAnalysisResult( + pointsToMasks.indices.map { parameterIndex -> + val escapes = escapesMask and (1 shl parameterIndex) != 0 + val curPointsToMask = pointsToMasks[parameterIndex] + val pointsTo = (0..31).filter { curPointsToMask and (1 shl it) != 0 }.toIntArray() + ParameterEscapeAnalysisResult(escapes, pointsTo) + }.toTypedArray() + ) + } + } + + private class InterproceduralAnalysisResult(val functionEscapeAnalysisResults: Map) + + private class InterproceduralAnalysis(context: Context, + val externalFunctionEscapeAnalysisResults: Map, + val intraproceduralAnalysisResult: IntraproceduralAnalysisResult, + val lifetimes: MutableMap) { + + private val expressionValuesExtractor = intraproceduralAnalysisResult.expressionValuesExtractor + + fun analyze(irModule: IrModuleFragment): InterproceduralAnalysisResult { + val callGraph = buildCallGraph(irModule) + + DEBUG_OUTPUT(0) { + println("CALL GRAPH") + callGraph.directEdges.forEach { t, u -> + println(" FUN $t") + u.callSites.forEach { + val local = callGraph.directEdges.containsKey(it.actualCallee) + println(" CALLS ${if (local) "LOCAL" else "EXTERNAL"} ${it.actualCallee}") + } + callGraph.reversedEdges[t]!!.forEach { + println(" CALLED BY $it") + } + } + } + + val condensation = callGraph.buildCondensation() + + DEBUG_OUTPUT(0) { + println("CONDENSATION") + condensation.topologicalOrder.forEach { multiNode -> + println(" MULTI-NODE") + multiNode.nodes.forEach { + println(" $it") + callGraph.directEdges[it]!!.callSites + .filter { callGraph.directEdges.containsKey(it.actualCallee) } + .forEach { println(" CALLS ${it.actualCallee}") } + callGraph.reversedEdges[it]!!.forEach { + println(" CALLED BY $it") + } + } + } + } + + callGraph.directEdges.forEach { function, node -> + val parameters = function.allParameters + node.escapeAnalysisResult = FunctionEscapeAnalysisResult( + // Assume no edges at the beginning. + // Then iteratively add needed. + (parameters.map { ParameterEscapeAnalysisResult(false, IntArray(0)) } + + ParameterEscapeAnalysisResult(false, IntArray(0)) + ).toTypedArray() + ) + } + + for (multiNode in condensation.topologicalOrder) + analyze(callGraph, multiNode) + + return InterproceduralAnalysisResult(callGraph.directEdges.entries.associateBy( + { it.key }, + { it.value.escapeAnalysisResult }) + ) + } + + private class CallSite(val expression: IrMemberAccessExpression, val actualCallee: FunctionDescriptor) + + private class CallGraphNode(val graph: CallGraph, val descriptor: FunctionDescriptor): DirectedGraphNode { + override val key: FunctionDescriptor + get() = descriptor + + override val directEdges: List by lazy { + graph.directEdges[descriptor]!!.callSites + .map { it.actualCallee } + .filter { graph.reversedEdges.containsKey(it) } + } + + override val reversedEdges: List by lazy { + graph.reversedEdges[descriptor]!! + } + + val callSites = mutableListOf() + lateinit var escapeAnalysisResult: FunctionEscapeAnalysisResult + } + + private class CallGraph(val directEdges: Map, + val reversedEdges: Map>) + : DirectedGraph { + + override val nodes: Collection + get() = directEdges.values + + override fun get(key: FunctionDescriptor) = directEdges[key]!! + + fun addEdge(caller: FunctionDescriptor, callSite: CallSite) { + directEdges[caller]!!.callSites += callSite + reversedEdges[callSite.actualCallee]?.add(caller) + } + + fun buildCondensation() = DirectedGraphCondensationBuilder(this).build() + } + + private fun buildCallGraph(element: IrElement): CallGraph { + val directEdges = mutableMapOf() + val reversedEdges = mutableMapOf>() + val callGraph = CallGraph(directEdges, reversedEdges) + intraproceduralAnalysisResult.functionAnalysisResults.keys.forEach { + directEdges.put(it, CallGraphNode(callGraph, it)) + reversedEdges.put(it, mutableListOf()) + } + element.acceptVoid(object : IrElementVisitorVoidWithContext() { + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitCall(expression: IrCall) { + addEdge(expression) + super.visitCall(expression) + } + + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) { + addEdge(expression) + super.visitDelegatingConstructorCall(expression) + } + + private fun addEdge(expression: IrMemberAccessExpression) { + val caller = currentFunction?.scope?.scopeOwner + if (caller != null) { + caller as FunctionDescriptor + val callee = (expression.descriptor as FunctionDescriptor).target + + if (!callee.isOverridable || expression !is IrCall || expression.superQualifier != null) { + val superQualifier = (expression as? IrCall)?.superQualifier + if (superQualifier == null) + callGraph.addEdge(caller, CallSite(expression, callee)) + else { + val actualCallee = superQualifier.unsubstitutedMemberScope.getOverridingOf(callee)?.target ?: callee + callGraph.addEdge(caller, CallSite(expression, actualCallee)) + } + } else { + DEBUG_OUTPUT(0) { println("A virtual call") } + + // TODO: Devirtualize. + callGraph.addEdge(caller, CallSite(expression, callee)) + } + } + } + + 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) } + } + }) + return callGraph + } + + private fun analyze(callGraph: CallGraph, multiNode: DirectedGraphMultiNode) { + DEBUG_OUTPUT(0) { + println("Analyzing multiNode:\n ${multiNode.nodes.joinToString("\n ") { it.toString() }}") + multiNode.nodes.forEach { from -> + println("IR") + println(ir2stringWhole(intraproceduralAnalysisResult.functionAnalysisResults[from]!!.function)) + callGraph.directEdges[from]!!.callSites.forEach { to -> + println("CALL") + println(" from $from") + println(" to ${ir2stringWhole(to.expression)}") + } + } + } + + val pointsToGraphs = multiNode.nodes.associateBy({ it }, { PointsToGraph(it) }) + val toAnalyze = mutableSetOf() + toAnalyze.addAll(multiNode.nodes) + while (toAnalyze.isNotEmpty()) { + val function = toAnalyze.first() + toAnalyze.remove(function) + + DEBUG_OUTPUT(0) { println("Processing function $function") } + + val startResult = callGraph.directEdges[function]!!.escapeAnalysisResult + + DEBUG_OUTPUT(0) { println("Start escape analysis result:\n$startResult") } + + analyze(callGraph, pointsToGraphs[function]!!, function) + val endResult = callGraph.directEdges[function]!!.escapeAnalysisResult + if (startResult == endResult) { + DEBUG_OUTPUT(0) { println("Escape analysis is not changed") } + } else { + DEBUG_OUTPUT(0) { println("Escape analysis was refined:\n$endResult") } + + callGraph.reversedEdges[function]?.forEach { + if (multiNode.nodes.contains(it)) + toAnalyze.add(it) + } + } + } + pointsToGraphs.values.forEach { graph -> + graph.nodes.keys + .filterIsInstance() + .forEach { lifetimes.put(it, graph.lifetimeOf(it)) } + } + } + + private fun analyze(callGraph: CallGraph, pointsToGraph: PointsToGraph, function: FunctionDescriptor) { + DEBUG_OUTPUT(0) { + println("Before calls analysis") + pointsToGraph.print() + } + + callGraph.directEdges[function]!!.callSites.forEach { + val callee = it.actualCallee + val calleeEAResult = callGraph.directEdges[callee]?.escapeAnalysisResult ?: getExternalFunctionEAResult(it) + pointsToGraph.processCall(it, calleeEAResult) + } + + DEBUG_OUTPUT(0) { + println("After calls analysis") + pointsToGraph.print() + } + + // Build transitive closure. + val eaResult = pointsToGraph.buildClosure() + + DEBUG_OUTPUT(0) { + println("After closure building") + pointsToGraph.print() + } + + callGraph.directEdges[function]!!.escapeAnalysisResult = eaResult + } + + private val NAME_ESCAPES = Name.identifier("Escapes") + private val NAME_POINTS_TO = Name.identifier("PointsTo") + private val FQ_NAME_KONAN = FqName.fromSegments(listOf("konan")) + + private val FQ_NAME_ESCAPES = FQ_NAME_KONAN.child(NAME_ESCAPES) + private val FQ_NAME_POINTS_TO = FQ_NAME_KONAN.child(NAME_POINTS_TO) + + private val konanPackage = context.builtIns.builtInsModule.getPackage(FQ_NAME_KONAN).memberScope + private val escapesAnnotationDescriptor = konanPackage.getContributedClassifier( + NAME_ESCAPES, NoLookupLocation.FROM_BACKEND) as ClassDescriptor + private val escapesWhoDescriptor = escapesAnnotationDescriptor.unsubstitutedPrimaryConstructor!!.valueParameters.single() + private val pointsToAnnotationDescriptor = konanPackage.getContributedClassifier( + NAME_POINTS_TO, NoLookupLocation.FROM_BACKEND) as ClassDescriptor + private val pointsToOnWhomDescriptor = pointsToAnnotationDescriptor.unsubstitutedPrimaryConstructor!!.valueParameters.single() + + private fun KotlinType.erasure(): KotlinType { + val descriptor = this.constructor.declarationDescriptor + return when (descriptor) { + is ClassDescriptor -> this + is TypeParameterDescriptor -> { + val upperBound = descriptor.upperBounds.singleOrNull() ?: + TODO("$descriptor : ${descriptor.upperBounds}") + + if (this.isMarkedNullable) { + // `T?` + upperBound.erasure().makeNullable() + } else { + upperBound.erasure() + } + } + else -> TODO(this.toString()) + } + } + + private fun getExternalFunctionEAResult(callSite: CallSite): FunctionEscapeAnalysisResult { + val callee = callSite.actualCallee + + DEBUG_OUTPUT(0) { println("External callee: $callee") } + + val parameters = callee.allParameters + val calleeEAResult = + if (!callee.isOverridable || callSite.expression !is IrCall || callSite.expression.superQualifier != null) { + getExternalFunctionEAResult(callee) + } else { + DEBUG_OUTPUT(0) { println("A virtual call") } + + FunctionEscapeAnalysisResult((0..parameters.size).map { + val type = if (it < parameters.size) parameters[it].type else callee.returnType + ParameterEscapeAnalysisResult( + escapes = isInteresting(type), // Conservatively assume all references escape. + pointsTo = IntArray(0) + ) + }.toTypedArray()) + } + + DEBUG_OUTPUT(0) { + println("Escape analysis result") + println(calleeEAResult.toString()) + } + + return calleeEAResult + } + + private fun parseEAResultFromAnnotations(function: FunctionDescriptor): FunctionEscapeAnalysisResult { + DEBUG_OUTPUT(0) { println("Parsing from annotations, function: $function") } + + val escapesAnnotation = function.annotations.findAnnotation(FQ_NAME_ESCAPES) + val pointsToAnnotation = function.annotations.findAnnotation(FQ_NAME_POINTS_TO) + @Suppress("UNCHECKED_CAST") + val escapesBitMask = (escapesAnnotation?.allValueArguments?.get(escapesWhoDescriptor.name) as? ConstantValue)?.value + @Suppress("UNCHECKED_CAST") + val pointsToBitMask = (pointsToAnnotation?.allValueArguments?.get(pointsToOnWhomDescriptor.name) as? ConstantValue>)?.value + return FunctionEscapeAnalysisResult.fromBits( + escapesBitMask ?: 0, + (0..function.allParameters.size).map { pointsToBitMask?.elementAtOrNull(it)?.value ?: 0 } + ) + } + + private fun tryGetFromExternalEAResults(function: FunctionDescriptor): FunctionEscapeAnalysisResult? { + if (!function.isExported()) return null + val symbolName = function.symbolName + + DEBUG_OUTPUT(0) { println("Trying get external results for function: $symbolName") } + + return externalFunctionEscapeAnalysisResults[symbolName] + } + + private fun getExternalFunctionEAResult(function: FunctionDescriptor): FunctionEscapeAnalysisResult { + DEBUG_OUTPUT(0) { println("External function: $function") } + + val functionEAResult = tryGetFromExternalEAResults(function) ?: parseEAResultFromAnnotations(function) + + DEBUG_OUTPUT(0) { + println("Escape analysis result") + println(functionEAResult.toString()) + } + + return functionEAResult + } + + private enum class PointsToGraphNodeKind(val weight: Int) { + LOCAL(0), + RETURN_VALUE(1), + ESCAPES(2) + } + + private class PointsToGraphNode(roles: Roles) { + // TODO: replace Any with sealed class. + val edges = mutableSetOf() + + var kind = when { + roles.escapes() -> PointsToGraphNodeKind.ESCAPES + roles.has(Role.RETURN_VALUE) -> PointsToGraphNodeKind.RETURN_VALUE + else -> PointsToGraphNodeKind.LOCAL + } + + val beingReturned = roles.has(Role.RETURN_VALUE) + + var parameterPointingOnUs: Int? = null + + fun addIncomingParameter(parameter: Int) { + if (kind == PointsToGraphNodeKind.ESCAPES) + return + if (parameterPointingOnUs == null) + parameterPointingOnUs = parameter + else { + parameterPointingOnUs = null + kind = PointsToGraphNodeKind.ESCAPES + } + } + } + + private inner class PointsToGraph(val function: FunctionDescriptor) { + + val nodes = mutableMapOf() + + fun lifetimeOf(node: Any?) = nodes[node]!!.let { + when (it.kind) { + PointsToGraphNodeKind.ESCAPES -> Lifetime.GLOBAL + + PointsToGraphNodeKind.LOCAL -> { + val parameterPointingOnUs = it.parameterPointingOnUs + if (parameterPointingOnUs != null) + // A value is stored into a parameter field. + Lifetime.PARAMETER_FIELD(parameterPointingOnUs) + else + // A value is neither stored into a global nor into any parameter nor into the return value - + // it can be allocated locally. + Lifetime.LOCAL + } + + PointsToGraphNodeKind.RETURN_VALUE -> { + when { + // If a value is stored into a parameter field and into the return value - consider it escapes. + it.parameterPointingOnUs != null -> Lifetime.GLOBAL + // If a value is explicitly returned. + returnValues.contains(node) -> Lifetime.RETURN_VALUE + // A value is stored into a field of the return value. + else -> Lifetime.INDIRECT_RETURN_VALUE + } + } + } + } + + init { + val functionAnalysisResult = intraproceduralAnalysisResult.functionAnalysisResults[function]!! + + DEBUG_OUTPUT(0) { + println("Building points-to graph for function $function") + println("Results of preliminary function analysis") + } + + functionAnalysisResult.parameterRoles.elementData.forEach { parameter, roles -> + DEBUG_OUTPUT(0) { println("PARAM $parameter: $roles") } + + nodes.put(parameter, PointsToGraphNode(roles)) + } + functionAnalysisResult.expressionToRoles.forEach { expression, roles -> + DEBUG_OUTPUT(0) { println("EXPRESSION ${ir2stringWhole(expression)}: $roles") } + + nodes.put(expression, PointsToGraphNode(roles)) + } + functionAnalysisResult.expressionToRoles.forEach { expression, roles -> + addEdges(expression, roles) + } + functionAnalysisResult.parameterRoles.elementData.forEach { parameter, roles -> + addEdges(parameter, roles) + } + } + + private val returnValues = nodes.filter { it.value.beingReturned } + .map { it.key } + .toSet() + + private fun addEdges(from: Any, roles: Roles) { + val pointsToEdge = roles.data[Role.FIELD_WRITTEN] + ?: return + pointsToEdge.entries.forEach { + val to = it.data!! + if (nodes.containsKey(to)) { + nodes[from]!!.edges.add(it.data) + + DEBUG_OUTPUT(0) { + println("EDGE: ") + println(" FROM: ${nodeToString(from)}") + println(" TO: ${nodeToString(it.data)}") + } + } + } + } + + private fun nodeToString(node: Any) = if (node is IrExpression) ir2stringWhole(node) else node.toString() + + fun print() { + println("POINTS-TO GRAPH") + println("NODES") + nodes.forEach { t, _ -> + println(" ${lifetimeOf(t)} ${nodeToString(t)}") + } + println("EDGES") + nodes.forEach { t, u -> + u.edges.forEach { + println(" FROM ${nodeToString(t)}") + println(" TO ${nodeToString(it)}") + } + } + } + + fun processCall(callSite: CallSite, calleeEscapeAnalysisResult: FunctionEscapeAnalysisResult) { + DEBUG_OUTPUT(0) { + println("Processing callSite: ${ir2stringWhole(callSite.expression)}") + println("Callee: ${callSite.actualCallee}") + println("Callee escape analysis result:") + println(calleeEscapeAnalysisResult.toString()) + } + + val callee = callSite.actualCallee + val callResult = callSite.expression + val arguments = callResult.getArguments() + val possibleArgumentValues = if (callee is ConstructorDescriptor) { + // Constructor returns nothing. + if (callResult is IrDelegatingConstructorCall) { + // For a delegating constructor call add implicit this as a parameter. + (0..arguments.size).map { + val thiz = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, + (function as ConstructorDescriptor).constructedClass.thisAsReceiverParameter) + expressionValuesExtractor.extractNodesUsingVariableValues( + expression = if (it == 0) thiz else arguments[it - 1].second, + variableValues = intraproceduralAnalysisResult.functionAnalysisResults[function]!!.variableValues + ) + } + } else { + // For a constructor call add implicit this - it is the result of a call actually. + (0..arguments.size).map { + expressionValuesExtractor.extractNodesUsingVariableValues( + expression = if (it == 0) callResult else arguments[it - 1].second, + variableValues = intraproceduralAnalysisResult.functionAnalysisResults[function]!!.variableValues + ) + } } } else { - result.add(element) + (0..arguments.size).map { + expressionValuesExtractor.extractNodesUsingVariableValues( + expression = if (it < arguments.size) arguments[it].second else callResult, + variableValues = intraproceduralAnalysisResult.functionAnalysisResults[function]!!.variableValues + ) + } + } + for (index in 0..callee.allParameters.size) { + val parameterEAResult = calleeEscapeAnalysisResult.parameters[index] + if (parameterEAResult.escapes) { + possibleArgumentValues[index].forEach { + nodes[it]?.kind = PointsToGraphNodeKind.ESCAPES + + DEBUG_OUTPUT(0) { nodes[it]?.let { _ -> println("Node ${nodeToString(it)} escapes") } } + } + } + parameterEAResult.pointsTo.forEach { toIndex -> + possibleArgumentValues[index].forEach { from -> + val nodeFrom = nodes[from] + if (nodeFrom == null) { + DEBUG_OUTPUT(0) { + println("There is no node") + println(" FROM ${nodeToString(from)}") + } + } else { + possibleArgumentValues[toIndex].forEach { to -> + val nodeTo = nodes[to] + if (nodeTo == null) { + DEBUG_OUTPUT(0) { + println("There is no node") + println(" TO ${nodeToString(to)}") + } + } else { + DEBUG_OUTPUT(0) { + println("Adding edge") + println(" FROM ${nodeToString(from)}") + println(" TO ${nodeToString(to)}") + } + + nodeFrom.edges.add(to) + } + } + } + } + } + } + } + + fun buildClosure(): FunctionEscapeAnalysisResult { + val parameters = function.allParameters.withIndex().toList() + val reachabilities = mutableListOf() + + DEBUG_OUTPUT(0) { println("BUILDING CLOSURE") } + + parameters.forEach { + val visited = mutableSetOf() + if (nodes[it.value] != null) + findReachable(it.value, visited) + visited -= it.value + + DEBUG_OUTPUT(0) { + println("Reachable from ${it.value}") + visited.forEach { + println(" ${nodeToString(it)}") + } + } + + val reachable = mutableListOf() + parameters.forEach { (index, parameter) -> + if (visited.contains(parameter)) + reachable += index + } + if (returnValues.any { visited.contains(it) }) + reachable += parameters.size + reachabilities.add(reachable.toIntArray()) + visited.forEach { node -> + if (node !is ParameterDescriptor) + nodes[node]!!.addIncomingParameter(it.index) + } + } + val visitedFromReturnValues = mutableSetOf() + returnValues.forEach { + if (!visitedFromReturnValues.contains(it)) { + findReachable(it, visitedFromReturnValues) + } + } + reachabilities.add( + parameters.filter { visitedFromReturnValues.contains(it.value) } + .map { it.index }.toIntArray() + ) + + propagate(PointsToGraphNodeKind.ESCAPES) + propagate(PointsToGraphNodeKind.RETURN_VALUE) + + return FunctionEscapeAnalysisResult(reachabilities.withIndex().map { (index, reachability) -> + val escapes = + if (index == parameters.size) // Return value. + returnValues.any { nodes[it]!!.kind == PointsToGraphNodeKind.ESCAPES } + else { + isInteresting(parameters[index].value.type) + && nodes[parameters[index].value]!!.kind == PointsToGraphNodeKind.ESCAPES + } + ParameterEscapeAnalysisResult(escapes, reachability) + }.toTypedArray()) + } + + private fun findReachable(node: Any, visited: MutableSet) { + visited += node + nodes[node]!!.edges.forEach { + if (!visited.contains(it)) { + findReachable(it, visited) + } + } + } + + private fun propagate(kind: PointsToGraphNodeKind) { + val visited = mutableSetOf() + nodes.filter { it.value.kind == kind } + .forEach { node, _ -> propagate(node, kind, visited) } + } + + private fun propagate(node: Any, kind: PointsToGraphNodeKind, visited: MutableSet) { + if (visited.contains(node)) return + visited.add(node) + val nodeInfo = nodes[node]!! + if (nodeInfo.kind.weight < kind.weight) + nodeInfo.kind = kind + nodeInfo.edges.forEach { propagate(it, kind, visited) } + } + } + } + + internal fun computeLifetimes(irModule: IrModuleFragment, context: Context, + lifetimes: MutableMap) { + assert(lifetimes.isEmpty()) + + val isStdlib = context.config.configuration[KonanConfigKeys.NOSTDLIB] == true + + val externalFunctionEAResults = mutableMapOf() + // TODO: serialize/deserialize in binary. + context.config.libraries.forEach { library -> + val libraryEscapeAnalysis = library.escapeAnalysis + if (libraryEscapeAnalysis != null) { + DEBUG_OUTPUT(0) { + println("Escape analysis size for lib '${library.libraryName}': ${libraryEscapeAnalysis.size}") + } + + val moduleEAResult = ModuleEscapeAnalysisResult.ModuleEAResult.parseFrom(libraryEscapeAnalysis) + moduleEAResult.functionEAResultsList.forEach { + externalFunctionEAResults.put(it.fqName, FunctionEscapeAnalysisResult.fromBits(it.escapes, it.pointsToList)) + } + + DEBUG_OUTPUT(0) { + println("Escape analysis results count for lib '${library.libraryName}': ${moduleEAResult.functionEAResultsList.size}") } } } - return result - } -} - -internal class ParameterRoles { - val elementData = HashMap() - - fun addParameter(parameter: ParameterDescriptor) { - elementData.getOrPut(parameter) { Roles() } - } - - fun add(parameter: ParameterDescriptor, role: Role, roleInfoEntry: RoleInfoEntry?) { - val roles = elementData.getOrPut(parameter, { Roles() }); - roles.add(role, roleInfoEntry) - } - - fun canEliminateCallArgument(info: RoleInfo): Boolean { - for (call in info.entries) { - val entry = call.data as Pair - val argument = entry.first - val callee = entry.second - if (callee is FunctionDescriptor) { - // Virtual dispatch. - if (callee.isOverridable) return false - // Calling external function. - // TODO: add list of pure external functions in stdlib. - if (callee.isExternal) return false - } - if (DEBUG > 1) println("passed as ${argument}") - val rolesInCallee = elementData[argument] ?: return false - if (DEBUG > 1) println("In callee is: $rolesInCallee") - // TODO: make recursive computation here. - if (rolesInCallee.escapes()) return false + val intraproceduralAnalysisResult = IntraproceduralAnalysis().analyze(irModule) + val interproceduralAnalysisResult = InterproceduralAnalysis(context, + externalFunctionEAResults, intraproceduralAnalysisResult, lifetimes).analyze(irModule) + if (isStdlib) { // Save only for stdlib for now. + interproceduralAnalysisResult.functionEscapeAnalysisResults + .filterNot { it.value.isTrivial } + .filter { it.key.isExported() } + .forEach { functionDescriptor, functionEAResult -> + val functionEAResultBuilder = ModuleEscapeAnalysisResult.FunctionEAResult.newBuilder() + functionEAResultBuilder.fqName = functionDescriptor.symbolName + var escapes = 0 + functionEAResult.parameters.forEachIndexed { index, parameterEAResult -> + if (parameterEAResult.escapes) + escapes = escapes or (1 shl index) + var pointsToMask = 0 + parameterEAResult.pointsTo.forEach { + pointsToMask = pointsToMask or (1 shl it) + } + functionEAResultBuilder.addPointsTo(pointsToMask) + } + functionEAResultBuilder.escapes = escapes + context.moduleEscapeAnalysisResult.addFunctionEAResults(functionEAResultBuilder) + } } - return true - } - - fun propagateCallArguments(roles: Roles): Boolean { - val info = roles.info(Role.CALL_ARGUMENT) ?: return false - if (canEliminateCallArgument(info)) { - roles.remove(Role.CALL_ARGUMENT) - return true - } - return false - } - - fun propagateCallArguments(expressionToRoles: MutableMap) { - expressionToRoles.forEach { - if (propagateCallArguments(it.value) && DEBUG > 0) - println("unescaped expression ${it.key}") - } - elementData.forEach { - if (propagateCallArguments(it.value) && DEBUG > 0) - println("unescaped paramater ${it.key}") - } - } -} - -internal fun rolesToLifetime(roles: Roles) : Lifetime { - val roleSetSize = roles.data.keys.size - return when { - // If reference is stored to global or generic field - it must be global. - roles.escapes() -> - Lifetime.GLOBAL - // If we pass it to unknown methods or throw - it must be global. - roles.has(Role.CALL_ARGUMENT) || roles.has(Role.THROW_VALUE) -> - Lifetime.GLOBAL - // If reference is only obtained as call result and never used - it can be local. - roleSetSize == 1 && roles.has(Role.CALL_RESULT) -> - Lifetime.LOCAL // throw Error() - // If reference is obtained as some call result and returned - we can use return. - roleSetSize == 2 && roles.has(Role.CALL_RESULT) && roles.has(Role.RETURN_VALUE) -> - Lifetime.RETURN_VALUE - // Otherwise, say it is global. - else -> - Lifetime.GLOBAL - } -} - -internal class ElementFinderVisitor( - val context: RuntimeAware, - val expressionToRoles: MutableMap, - val variableValues: VariableValues, - val parameterRoles: ParameterRoles) - : IrElementVisitorVoid { - - fun isInteresting(element: IrExpression) : Boolean { - return (element is IrMemberAccessExpression && context.isInteresting(element.type)) || - (element is IrGetValue && context.isInteresting(element.type)) - } - - fun isInteresting(variable: ValueDescriptor) : Boolean { - return context.isInteresting(variable.type) - } - - override fun visitElement(element: IrElement) { - if (element is IrExpression && isInteresting(element)) { - expressionToRoles[element] = Roles() - } - element.acceptChildrenVoid(this) - } - - override fun visitTypeOperator(expression: IrTypeOperatorCall) { - super.visitTypeOperator(expression) - if (expression.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT) { - expressionToRoles.remove(expression.argument) - } - } - - override fun visitFunction(declaration: IrFunction) { - declaration.descriptor.allParameters.forEach { - if (isInteresting(it)) - parameterRoles.addParameter(it) - } - super.visitFunction(declaration) - } - - override fun visitVariable(expression: IrVariable) { - if (isInteresting(expression.descriptor)) - variableValues.addEmpty(expression.descriptor) - super.visitVariable(expression) - } -} - -// -// elementToRoles is filled with all possible roles given element can play. -// varValues is filled with all possible elements that could be stored in a variable. -// -internal class RoleAssignerVisitor( - val expressionRoles: MutableMap, - val variableValues: VariableValues, - val parameterRoles: ParameterRoles, - val useVarValues: Boolean) : IrElementVisitorVoid { - - fun assignExpressionRole(expression: IrExpression, role: Role, infoEntry: RoleInfoEntry?) { - if (!useVarValues) - expressionRoles[expression]?.add(role, infoEntry) - } - - fun assignValueRole(value: ValueDescriptor, role: Role, infoEntry: RoleInfoEntry?) { - if (!useVarValues) return - // Whenever we see variable use in certain role - we propagate this role - // to all possible expression this variable can be assigned to. - val possibleValues = variableValues.get(value) - if (possibleValues != null) { - for (possibleValue in possibleValues) { - expressionRoles[possibleValue]?.add(role, infoEntry) - } - } - if (value is ParameterDescriptor) { - parameterRoles.add(value, role, infoEntry) - } - } - - // Here we handle variable assignment. - fun assignVariable(variable: VariableDescriptor, value: IrExpression) { - if (useVarValues) return - // Sometimes we can assign value to Unit variable (unit4.kt) - we don't care. - if (value.type.isUnit()) return - when (value) { - is IrContainerExpression -> assignVariable(variable, value.statements.last() as IrExpression) - // is IrVararg -> value.elements.forEach { assignVariable(variable, it as IrExpression) } - is IrWhen -> value.branches.forEach { assignVariable(variable, it.result) } - is IrMemberAccessExpression -> variableValues.add(variable, value) - is IrGetValue -> variableValues.add(variable, value) - is IrGetField -> variableValues.add(variable, value) - is IrConst<*> -> { } - is IrTypeOperatorCall -> { - when (value.operator) { - IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.CAST -> - assignVariable(variable, value.argument) - else -> TODO(ir2string(value)) - } - } - is IrTry -> (value.catches + value.tryResult).forEach { - if (it is IrExpression) assignVariable(variable, it) - } - is IrThrow -> { /* Do nothing, error path in an assignment. */ } - is IrGetObjectValue -> { } - // TODO: is it correct? - is IrReturn -> { /* Do nothing, return path in an assignment. */ } - else -> TODO(ir2string(value)) - } - } - - // Here we assign a role to expression's value. - fun assignRole(expression: IrExpression, role: Role, infoEntry: RoleInfoEntry?) { - if (expression.type.isUnit()) return - when (expression) { - is IrContainerExpression -> assignRole(expression.statements.last() as IrExpression, role, infoEntry) - is IrVararg -> expression.elements.forEach { assignRole(it as IrExpression, role, infoEntry) } - is IrWhen -> expression.branches.forEach { assignRole(it.result, role, infoEntry) } - is IrMemberAccessExpression -> assignExpressionRole(expression, role, infoEntry) - is IrGetValue -> assignValueRole(expression.descriptor, role, infoEntry) - // If field plays certain role - we cannot use this info now. - is IrGetField -> {} - // If constant plays certain role - this information is useless. - is IrConst<*> -> {} - is IrTypeOperatorCall -> { - when (expression.operator) { - IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.CAST -> - assignRole(expression.argument, role, infoEntry) - // No info from those ones. - IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF, - IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> {} - else -> TODO(ir2string(expression)) - } - } - is IrThrow -> { /* Do nothing, error path in an assignment. */ } - is IrGetObjectValue -> { /* Shall we do anything here? */ } - // TODO: is it correct? - is IrReturn -> { /* Do nothing, return path in an assignment. */ } - else -> TODO(ir2string(expression)) - } - } - - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitCall(expression: IrCall) { - assignRole(expression, Role.CALL_RESULT, RoleInfoEntry(expression)) - for (argument in expression.getArguments()) { - assignRole(argument.second, Role.CALL_ARGUMENT, - RoleInfoEntry(Pair(argument.first, expression.descriptor))) - } - super.visitCall(expression) - } - - override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) { - for (argument in expression.getArguments()) { - assignRole(argument.second, Role.CALL_ARGUMENT, - RoleInfoEntry(Pair(argument.first, expression.descriptor))) - } - super.visitDelegatingConstructorCall(expression) - } - - override fun visitSetField(expression: IrSetField) { - assignRole(expression.value, Role.WRITTEN_TO_FIELD, RoleInfoEntry(expression)) - super.visitSetField(expression) - } - - override fun visitField(declaration: IrField) { - val initializer = declaration.initializer - if (initializer != null) { - assignRole(initializer.expression, - Role.WRITTEN_TO_FIELD, RoleInfoEntry(declaration)) - } - super.visitField(declaration) - } - - override fun visitSetVariable(expression: IrSetVariable) { - assignVariable(expression.descriptor, expression.value) - super.visitSetVariable(expression) - } - - override fun visitVariable(expression: IrVariable) { - val initializer = expression.initializer - if (initializer != null) { - assignVariable(expression.descriptor, initializer) - } - super.visitVariable(expression) - } - - override fun visitReturn(expression: IrReturn) { - assignRole(expression.value, Role.RETURN_VALUE, RoleInfoEntry(expression)) - super.visitReturn(expression) - } - - override fun visitThrow(expression: IrThrow) { - assignRole(expression.value, Role.THROW_VALUE, RoleInfoEntry(expression)) - super.visitThrow(expression) - } -} - -internal class ContextFinderVisitor(val needle: IrElement) : IrElementVisitorVoid { - val stack = mutableListOf() - var found = false - - override fun visitElement(element: IrElement) { - if (found) return - stack.add(element) - if (element == needle) { - found = true - } - element.acceptChildrenVoid(this) - if (found) return - stack.removeAt(stack.size - 1) - } - - override fun visitModuleFragment(declaration: IrModuleFragment) { - declaration.acceptChildrenVoid(this) - } -} - -private fun findContext(irModule: IrModuleFragment, element: IrElement) : String { - val visitor = ContextFinderVisitor(element) - irModule.acceptVoid(visitor) - val result = StringBuilder() - if (visitor.found) { - visitor.stack.forEach { - result.append(ir2string(it)) - result.append(" -> ") - } - } else { - result.append("") - } - return result.toString() -} - -// -// Analysis we're implementing here is as following. -// * compute roles IR value nodes can play -// * merge role information with all methods being called -// * squash set of roles to conservatively estimated lifetime -// -fun computeLifetimes(irModule: IrModuleFragment, context: RuntimeAware, - lifetimes: MutableMap) { - assert(lifetimes.size == 0) - - val expressionToRoles = mutableMapOf() - val variableValues = VariableValues() - val parameterRoles = ParameterRoles() - irModule.acceptVoid(ElementFinderVisitor( - context, expressionToRoles, variableValues, parameterRoles)) - // On this pass, we collect all possible variable values and assign roles - // to expressions. - irModule.acceptVoid(RoleAssignerVisitor( - expressionToRoles, variableValues, parameterRoles, false)) - // Compute transitive closure of possible values for variables. - variableValues.computeClosure() - // On this pass, we use possible variable values to assign roles to expression. - irModule.acceptVoid(RoleAssignerVisitor( - expressionToRoles, variableValues, parameterRoles, true)) - parameterRoles.propagateCallArguments(expressionToRoles) - - expressionToRoles.forEach { expression, roles -> - if (DEBUG > 1) - println("for ${expression} roles are ${roles.data.keys.joinToString(", ")}") - var role = rolesToLifetime(roles) - if (role == Lifetime.LOCAL) { - if (DEBUG > 0) - println("arena alloc for ${ir2string(expression)} in ${findContext(irModule, expression)}") - // Not yet enabled. - role = Lifetime.GLOBAL - } - lifetimes[expression] = role - } - if (DEBUG > 1) { - variableValues.elementData.forEach { variable, expressions -> - println("variable $variable could be ${expressions.joinToString(", ")}") } - parameterRoles.elementData.forEach { parameter, roles -> - println("parameter $parameter could be $roles") } } } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.proto b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.proto new file mode 100644 index 00000000000..4258c40c123 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.proto @@ -0,0 +1,15 @@ +syntax = "proto2"; +package org.jetbrains.kotlin.backend.konan; + +option java_outer_classname = "ModuleEscapeAnalysisResult"; +option optimize_for = LITE_RUNTIME; + +message FunctionEAResult { + required string fqName = 1; + required int32 escapes = 2; + repeated int32 pointsTo = 3; +} + +message ModuleEAResult { + repeated FunctionEAResult functionEAResults = 1; +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/GraphAlgorithms.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/GraphAlgorithms.kt new file mode 100644 index 00000000000..e148c53eefb --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/GraphAlgorithms.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan + +internal interface DirectedGraphNode { + val key: K + val directEdges: List + val reversedEdges: List +} + +internal interface DirectedGraph> { + val nodes: Collection + fun get(key: K): N +} + +internal class DirectedGraphMultiNode(val nodes: Set) + +internal class DirectedGraphCondensation(val topologicalOrder: List>) + +internal class DirectedGraphCondensationBuilder>(val graph: DirectedGraph) { + private val visited = mutableSetOf() + private val order = mutableListOf() + private val nodeToMultiNodeMap = mutableMapOf>() + private val multiNodesOrder = mutableListOf>() + + fun build(): DirectedGraphCondensation { + // First phase. + graph.nodes.forEach { + if (!visited.contains(it.key)) + findOrder(it) + } + + // Second phase. + visited.clear() + val multiNodes = mutableListOf>() + order.reversed().forEach { + if (!visited.contains(it.key)) { + val nodes = mutableSetOf() + paint(it, nodes) + multiNodes += DirectedGraphMultiNode(nodes) + } + } + + // Topsort of built condensation. + multiNodes.forEach { multiNode -> + multiNode.nodes.forEach { nodeToMultiNodeMap.put(graph.get(it), multiNode) } + } + visited.clear() + multiNodes.forEach { + if (!visited.contains(it.nodes.first())) + findMultiNodesOrder(it) + } + + return DirectedGraphCondensation(multiNodesOrder) + } + + private fun findOrder(node: N) { + visited += node.key + node.directEdges.forEach { + if (!visited.contains(it)) + findOrder(graph.get(it)) + } + order += node + } + + private fun paint(node: N, multiNode: MutableSet) { + visited += node.key + multiNode += node.key + node.reversedEdges.forEach { + if (!visited.contains(it)) + paint(graph.get(it), multiNode) + } + } + + private fun findMultiNodesOrder(node: DirectedGraphMultiNode) { + visited.addAll(node.nodes) + node.nodes.forEach { + graph.get(it).directEdges.forEach { + if (!visited.contains(it)) + findMultiNodesOrder(nodeToMultiNodeMap[graph.get(it)]!!) + } + } + multiNodesOrder += node + } +} 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 e2f7d2a49e3..9fbc83213e4 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 @@ -56,6 +56,7 @@ enum class KonanPhase(val description: String, /* ... ... */ AUTOBOX("Autoboxing of primitive types", BRIDGES_BUILDING, LOWER_COROUTINES), /* ... */ BITCODE("LLVM BitCode Generation"), /* ... ... */ RTTI("RTTI Generation"), + /* ... ... */ ESCAPE_ANALYSIS("Escape analysis"), /* ... ... */ 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/library/KonanLibrary.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibrary.kt index 2941b177dbc..f2db1436989 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibrary.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibrary.kt @@ -41,7 +41,9 @@ interface KonanLibraryLayout { get() = File(libDir, "linkdata") val moduleHeaderFile get() = File(linkdataDir, "module") - fun packageFile(packageName: String) + val escapeAnalysisFile + get() = File(linkdataDir, "module_escape_analysis") + fun packageFile(packageName: String) = File(linkdataDir, if (packageName == "") "root_package" else "package_$packageName") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt index e4121336628..e7337514119 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt @@ -23,6 +23,7 @@ interface KonanLibraryReader { val libraryName: String val bitcodePaths: List val linkerOpts: List + val escapeAnalysis: ByteArray? fun moduleDescriptor(specifics: LanguageVersionSettings): ModuleDescriptor } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryWriter.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryWriter.kt index 93e4b950619..7a63f2adaf0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryWriter.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryWriter.kt @@ -23,6 +23,7 @@ interface KonanLibraryWriter { fun addNativeBitcode(library: String) fun addKotlinBitcode(llvmModule: LLVMModuleRef) fun addManifestAddend(path: String) + fun addEscapeAnalysis(escapeAnalysis: ByteArray) val mainBitcodeFileName: String fun commit() } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt index 885dc51e971..6ebe3eb8d11 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt @@ -47,6 +47,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t } val targetList = inPlace.targetsDir.listFiles.map{it.name} + override val escapeAnalysis: ByteArray? by lazy { inPlace.escapeAnalysisFile.let { if (it.exists) it.readBytes() else null } } override val libraryName get() = inPlace.libraryName diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryWriterImpl.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryWriterImpl.kt index 273551b3b10..be157fa6e06 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryWriterImpl.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryWriterImpl.kt @@ -82,6 +82,10 @@ class LibraryWriterImpl(override val libDir: File, currentAbiVersion: Int, println("manifest addend: ${properties.stringPropertyNames().joinToString(" ")}") } + override fun addEscapeAnalysis(escapeAnalysis: ByteArray) { + escapeAnalysisFile.writeBytes(escapeAnalysis) + } + override fun commit() { manifestProperties.saveToFile(manifestFile) if (!nopack) { @@ -99,7 +103,8 @@ internal fun buildLibrary( output: String, llvmModule: LLVMModuleRef, nopack: Boolean, - manifest: String?): KonanLibraryWriter { + manifest: String?, + escapeAnalysis: ByteArray?): KonanLibraryWriter { val library = LibraryWriterImpl(output, abiVersion, target, nopack) @@ -109,6 +114,7 @@ internal fun buildLibrary( library.addNativeBitcode(it) } manifest ?.let { library.addManifestAddend(it) } + escapeAnalysis?.let { library.addEscapeAnalysis(it) } library.commit() return library diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index ef2ab988fec..1f0f363ce57 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -226,7 +226,9 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, fun call(llvmFunction: LLVMValueRef, args: List, resultLifetime: Lifetime = Lifetime.IRRELEVANT, lazyLandingpad: () -> LLVMBasicBlockRef? = { null }): LLVMValueRef { - val callArgs = if (isObjectReturn(llvmFunction.type)) { + val callArgs = if (!isObjectReturn(llvmFunction.type)) { + args + } else { // If function returns an object - create slot for the returned value or give local arena. // This allows appropriate rootset accounting by just looking at the stack slots, // along with ability to allocate in appropriate arena. @@ -235,15 +237,28 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, localAllocs++ arenaSlot!! } + SlotType.RETURN -> returnSlot!! - // TODO: for RETURN_IF_ARENA choose between created slot and arenaSlot - // dynamically. - SlotType.ANONYMOUS, SlotType.RETURN_IF_ARENA -> vars.createAnonymousSlot() - else -> throw Error("Incorrect slot type") + + SlotType.ANONYMOUS -> vars.createAnonymousSlot() + + SlotType.RETURN_IF_ARENA -> returnSlot.let { + if (it != null) + call(context.llvm.getReturnSlotIfArenaFunction, listOf(it, vars.createAnonymousSlot())) + else { + // Return type is not an object type - can allocate locally. + localAllocs++ + arenaSlot!! + } + } + + is SlotType.PARAM_IF_ARENA -> + call(context.llvm.getParamSlotIfArenaFunction, + listOf(vars.load(resultLifetime.slotType.parameter), vars.createAnonymousSlot())) + + else -> throw Error("Incorrect slot type: ${resultLifetime.slotType}") } args + resultSlot - } else { - args } return callRaw(llvmFunction, callArgs, lazyLandingpad) } @@ -385,7 +400,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, internal fun prologue() { assert(returns.isEmpty()) if (isObjectType(returnType!!)) { - this.returnSlot = LLVMGetParam(function, numParameters(function.type) - 1) + returnSlot = LLVMGetParam(function, numParameters(function.type) - 1) } positionAtEnd(localsInitBb) slotsPhi = phi(kObjHeaderPtrPtr) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index e81e06bb8e1..62d10fcc708 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -32,40 +32,105 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils -internal enum class SlotType { +internal sealed class SlotType { // Frame local arena slot can be used. - ARENA, + class ARENA: SlotType() // Return slot can be used. - RETURN, + class RETURN: SlotType() // Return slot, if it is an arena, can be used. - RETURN_IF_ARENA, + class RETURN_IF_ARENA: SlotType() + // Return slot, if it is an arena, can be used. + class PARAM_IF_ARENA(val parameter: Int): SlotType() // Anonymous slot. - ANONYMOUS, + class ANONYMOUS: SlotType() // Unknown slot type. - UNKNOWN + class UNKNOWN: SlotType() + + companion object { + val ARENA = ARENA() + val RETURN = RETURN() + val RETURN_IF_ARENA = RETURN_IF_ARENA() + val ANONYMOUS = ANONYMOUS() + val UNKNOWN = UNKNOWN() + } } // Lifetimes class of reference, computed by escape analysis. -enum class Lifetime(val slotType: SlotType) { +internal sealed class Lifetime(val slotType: SlotType) { // If reference is frame-local (only obtained from some call and never leaves). - LOCAL(SlotType.ARENA), + class LOCAL: Lifetime(SlotType.ARENA) { + override fun toString(): String { + return "LOCAL" + } + } + // If reference is only returned. - RETURN_VALUE(SlotType.RETURN), + class RETURN_VALUE: Lifetime(SlotType.RETURN) { + override fun toString(): String { + return "RETURN_VALUE" + } + } + // If reference is set as field of references of class RETURN_VALUE or INDIRECT_RETURN_VALUE. - INDIRECT_RETURN_VALUE(SlotType.RETURN_IF_ARENA), + class INDIRECT_RETURN_VALUE: Lifetime(SlotType.RETURN_IF_ARENA) { + override fun toString(): String { + return "INDIRECT_RETURN_VALUE" + } + } + // If reference is stored to the field of an incoming parameters. - PARAMETER_FIELD(SlotType.ANONYMOUS), + class PARAMETER_FIELD(val parameter: Int): Lifetime(SlotType.PARAM_IF_ARENA(parameter)) { + override fun toString(): String { + return "PARAMETER_FIELD($parameter)" + } + } + // If reference refers to the global (either global object or global variable). - GLOBAL(SlotType.ANONYMOUS), + class GLOBAL: Lifetime(SlotType.ANONYMOUS) { + override fun toString(): String { + return "GLOBAL" + } + } + // If reference used to throw. - THROW(SlotType.ANONYMOUS), + class THROW: Lifetime(SlotType.ANONYMOUS) { + override fun toString(): String { + return "THROW" + } + } + // If reference used as an argument of outgoing function. Class can be improved by escape analysis // of called function. - ARGUMENT(SlotType.ANONYMOUS), + class ARGUMENT: Lifetime(SlotType.ANONYMOUS) { + override fun toString(): String { + return "ARGUMENT" + } + } + // If reference class is unknown. - UNKNOWN(SlotType.UNKNOWN), + class UNKNOWN: Lifetime(SlotType.UNKNOWN) { + override fun toString(): String { + return "UNKNOWN" + } + } + // If reference class is irrelevant. - IRRELEVANT(SlotType.UNKNOWN) + class IRRELEVANT: Lifetime(SlotType.UNKNOWN) { + override fun toString(): String { + return "IRRELEVANT" + } + } + + companion object { + val LOCAL = LOCAL() + val RETURN_VALUE = RETURN_VALUE() + val INDIRECT_RETURN_VALUE = INDIRECT_RETURN_VALUE() + val GLOBAL = GLOBAL() + val THROW = THROW() + val ARGUMENT = ARGUMENT() + val UNKNOWN = UNKNOWN() + val IRRELEVANT = IRRELEVANT() + } } /** @@ -253,6 +318,8 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val setRefFunction = importRtFunction("SetRef") val updateRefFunction = importRtFunction("UpdateRef") val leaveFrameFunction = importRtFunction("LeaveFrame") + val getReturnSlotIfArenaFunction = importRtFunction("GetReturnSlotIfArena") + val getParamSlotIfArenaFunction = importRtFunction("GetParamSlotIfArena") val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod") val isInstanceFunction = importRtFunction("IsInstance") val checkInstanceFunction = importRtFunction("CheckInstance") 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 b3dc8d261df..821bcf5b3f6 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 @@ -71,8 +71,13 @@ internal fun emitLLVM(context: Context) { generateDebugInfoHeader(context) + val lifetimes = mutableMapOf() + phaser.phase(KonanPhase.ESCAPE_ANALYSIS) { + EscapeAnalysis.computeLifetimes(irModule, context, lifetimes) + } + phaser.phase(KonanPhase.CODEGEN) { - irModule.acceptVoid(CodeGeneratorVisitor(context)) + irModule.acceptVoid(CodeGeneratorVisitor(context, lifetimes)) } if (context.shouldContainDebugInfo()) { @@ -118,7 +123,8 @@ internal fun produceOutput(context: Context) { libraryName, llvmModule, nopack, - manifest) + manifest, + context.moduleEscapeAnalysisResult?.build()?.toByteArray()) context.library = library context.bitcodeFileName = library.mainBitcodeFileName @@ -232,10 +238,9 @@ internal interface CodeContext { //-------------------------------------------------------------------------// -internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid { +internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map) : IrElementVisitorVoid { val codegen = CodeGenerator(context) - val resultLifetimes = mutableMapOf() //-------------------------------------------------------------------------// @@ -310,8 +315,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid override fun visitModuleFragment(declaration: IrModuleFragment) { context.log{"visitModule : ${ir2string(declaration)}"} - // computeLifetimes(module, this.codegen, resultLifetimes) - declaration.acceptChildrenVoid(this) appendLlvmUsed(context.llvm.usedFunctions) appendStaticInitializers(context.llvm.staticInitializers) @@ -1847,7 +1850,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// private fun resultLifetime(callee: IrElement): Lifetime { - return resultLifetimes.getOrElse(callee) { /* TODO: make IRRELEVANT */ Lifetime.GLOBAL } + return lifetimes.getOrElse(callee) { /* TODO: make IRRELEVANT */ Lifetime.GLOBAL } } private fun evaluateConstructorCall(callee: IrCall, args: List): LLVMValueRef { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InlineConstructorsTransformation.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InlineConstructorsTransformation.kt index 612a53857de..d9f5d546c55 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InlineConstructorsTransformation.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InlineConstructorsTransformation.kt @@ -39,11 +39,14 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe //-----------------------------------------------------------------------------// +private val inlineConstructor by lazy { FqName("konan.internal.InlineConstructor") } + +internal val FunctionDescriptor.isInlineConstructor get() = annotations.hasAnnotation(inlineConstructor) + internal class InlineConstructorsTransformation(val context: Context): IrElementTransformerVoidWithContext() { private val deserializer by lazy { DeserializerDriver(context) } private val substituteMap by lazy { mutableMapOf() } - private val inlineConstructor by lazy { FqName("konan.internal.InlineConstructor") } //-------------------------------------------------------------------------// @@ -55,7 +58,7 @@ internal class InlineConstructorsTransformation(val context: Context): IrElement val irCall = super.visitCall(expression) as IrCall val functionDescriptor = irCall.descriptor - if (!functionDescriptor.annotations.hasAnnotation(inlineConstructor)) return irCall // This call does not need inlining. + if (!functionDescriptor.isInlineConstructor) return irCall // This call does not need inlining. val functionDeclaration = getFunctionDeclaration(irCall) // Get declaration of the function to be inlined. if (functionDeclaration == null) { // We failed to get the declaration. diff --git a/backend.native/tests/runtime/memory/escape0.kt b/backend.native/tests/runtime/memory/escape0.kt index 040fdc87884..64655ed5ce2 100644 --- a/backend.native/tests/runtime/memory/escape0.kt +++ b/backend.native/tests/runtime/memory/escape0.kt @@ -1,20 +1,13 @@ -fun foo1(arg: String) : String = arg +//fun foo1(arg: String) : String = foo0(arg) +fun foo1(arg: Any) : Any = foo0(arg) -/* -fun foo2(arg: String) : String = arg + " foo2" +fun foo0(arg: Any) : Any = Any() -fun bar(arg1: String, arg2: String) : String = arg1 + " bar " + arg2 +var global : Any = Any() -fun zoo1() : String { - var x = foo1("") - var y = 4 - return x -} - -fun zoo2() : String { - val x = foo1("") - var y = 5 - return x +fun foo0_escape(arg: Any) : Any{ + global = arg + return Any() } class Node(var previous: Node?) @@ -38,10 +31,11 @@ fun zoo4(arg: Int) : Any { else -> c } return a -} */ +} -fun zoo5(arg: String) : String { - return arg + foo1(arg) +fun zoo5(arg: Any) : Any{ + foo1(arg) + return arg } fun zoo6(arg: Any) : Any { @@ -53,6 +47,7 @@ fun zoo7(arg1: Any, arg2: Any, selector: Int) : Any { } fun main(args : Array) { - val z = zoo7(Any(), Any(), 1) + //val z = zoo7(Any(), Any(), 1) + val x = zoo5(Any()) //println(bar(foo1(foo2("")), foo2(foo1("")))) } diff --git a/runtime/src/main/cpp/Arrays.cpp b/runtime/src/main/cpp/Arrays.cpp index 3989df8fec6..419c67e45e4 100644 --- a/runtime/src/main/cpp/Arrays.cpp +++ b/runtime/src/main/cpp/Arrays.cpp @@ -137,8 +137,8 @@ void Kotlin_CharArray_set(KRef thiz, KInt index, KChar value) { OBJ_GETTER(Kotlin_CharArray_copyOf, KConstRef thiz, KInt newSize) { const ArrayHeader* array = thiz->array(); - ArrayHeader* result = ArrayContainer( - theCharArrayTypeInfo, newSize).GetPlace(); + ArrayHeader* result = AllocArrayInstance( + array->type_info(), newSize, OBJ_RESULT)->array(); KInt toCopy = array->count_ < newSize ? array->count_ : newSize; memcpy( PrimitiveArrayAddressOfElementAt(result, 0), diff --git a/runtime/src/main/cpp/Common.h b/runtime/src/main/cpp/Common.h index 677794c9443..21c1170fab1 100644 --- a/runtime/src/main/cpp/Common.h +++ b/runtime/src/main/cpp/Common.h @@ -26,4 +26,6 @@ #define THREAD_LOCAL_VARIABLE __thread #endif +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + #endif // RUNTIME_COMMON_H diff --git a/runtime/src/main/cpp/KString.h b/runtime/src/main/cpp/KString.h index d61e2570b02..5994e628927 100644 --- a/runtime/src/main/cpp/KString.h +++ b/runtime/src/main/cpp/KString.h @@ -52,6 +52,4 @@ int binarySearchRange(const T* array, int arrayLength, T needle) { return middle - (needle < value ? 1 : 0); } -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) - #endif // RUNTIME_STRING_H diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 3fd750d818a..e238100eb8a 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -29,7 +29,7 @@ // If garbage collection algorithm for cyclic garbage to be used. #define USE_GC 1 -// Optmize management of cyclic garbage (increases memory footprint). +// Optimize management of cyclic garbage (increases memory footprint). // Not recommended for low-end embedded targets. #define OPTIMIZE_GC 1 // Define to 1 to print all memory operations. @@ -467,6 +467,7 @@ bool ArenaContainer::allocContainer(container_size_t minSize) { RuntimeAssert(result != nullptr, "Cannot alloc memory"); if (result == nullptr) return false; result->next = currentChunk_; + result->arena = this; result->asHeader()->refCount_ = (CONTAINER_TAG_STACK | CONTAINER_TAG_INCREMENT); currentChunk_ = result; current_ = reinterpret_cast(result->asHeader() + 1); @@ -491,6 +492,16 @@ void* ArenaContainer::place(container_size_t size) { return result; } +#define ARENA_SLOTS_CHUNK_SIZE 16 + +ObjHeader** ArenaContainer::getSlot() { + if (slots_ == nullptr || slotsCount_ >= ARENA_SLOTS_CHUNK_SIZE) { + slots_ = PlaceArray(theArrayTypeInfo, ARENA_SLOTS_CHUNK_SIZE); + slotsCount_ = 0; + } + return ArrayAddressOfElementAt(slots_, slotsCount_++); +} + ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) { RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object"); uint32_t size = type_info->instanceSize_ + sizeof(ObjHeader); @@ -618,7 +629,7 @@ OBJ_GETTER(AllocInstance, const TypeInfo* type_info) { auto arena = initedArena(asArenaSlot(OBJ_RESULT)); auto result = arena->PlaceObject(type_info); #if TRACE_MEMORY - fprintf(stderr, "instace %p in arena: %p\n", result, arena); + fprintf(stderr, "instance %p in arena: %p\n", result, arena); #endif return result; } @@ -680,8 +691,30 @@ void SetRef(ObjHeader** location, const ObjHeader* object) { } } +ObjHeader** GetReturnSlotIfArena(ObjHeader** returnSlot, ObjHeader** localSlot) { + return isArenaSlot(returnSlot) ? returnSlot : localSlot; +} + +ObjHeader** GetParamSlotIfArena(ObjHeader* param, ObjHeader** localSlot) { + if (param == nullptr) return localSlot; + auto container = param->container(); + if ((container->refCount_ & CONTAINER_TAG_MASK) != CONTAINER_TAG_STACK) + return localSlot; + auto chunk = reinterpret_cast(container) - 1; + return reinterpret_cast(reinterpret_cast(&chunk->arena) | ARENA_BIT); +} + void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) { - if (isArenaSlot(returnSlot)) return; + if (isArenaSlot(returnSlot)) { + return; + if (object == nullptr + || (object->container()->refCount_ & CONTAINER_TAG_MASK) > CONTAINER_TAG_NORMAL) { + // Not a subject of reference counting. + return; + } + auto arena = initedArena(asArenaSlot(returnSlot)); + returnSlot = arena->getSlot(); + } ObjHeader* old = *returnSlot; #if TRACE_MEMORY fprintf(stderr, "UpdateReturnRef *%p: %p -> %p\n", returnSlot, old, object); diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 3bf141dd618..62a54a9a059 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -36,7 +36,7 @@ typedef enum { CONTAINER_TAG_SEEN = 4, // Shift to get actual counter. CONTAINER_TAG_SHIFT = 3, - // Actual value to increment/decrement conatiner by. Tag is in lower bits. + // Actual value to increment/decrement container by. Tag is in lower bits. CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT, // Mask for container type, disregard seen bit. CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1) @@ -239,6 +239,17 @@ class ArrayContainer : public Container { // Container is used for reference counting, and it is assumed that objects // with related placement will share container. Only // whole container can be freed, individual objects are not taken into account. +class ArenaContainer; + +struct ContainerChunk { + ContainerChunk* next; + ArenaContainer* arena; + // Then we have ContainerHeader here. + ContainerHeader* asHeader() { + return reinterpret_cast(this + 1); + } +}; + class ArenaContainer { public: void Init(); @@ -252,15 +263,9 @@ class ArenaContainer { // same operation could be used to place strings. ArrayHeader* PlaceArray(const TypeInfo* array_type_info, container_size_t count); - private: - struct ContainerChunk { - ContainerChunk* next; - // Then we have ContainerHeader here. - ContainerHeader* asHeader() { - return reinterpret_cast(this + 1); - } - }; + ObjHeader** getSlot(); + private: void* place(container_size_t size); bool allocContainer(container_size_t minSize); void setMeta(ObjHeader* obj, const TypeInfo* type_info) { @@ -272,6 +277,8 @@ class ArenaContainer { ContainerChunk* currentChunk_; uint8_t* current_; uint8_t* end_; + ArrayHeader* slots_; + uint32_t slotsCount_; }; #ifdef __cplusplus @@ -350,6 +357,10 @@ void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) RUNTIME_NO void ReleaseRefs(ObjHeader** start, int count) RUNTIME_NOTHROW; // Called on frame leave, if it has object slots. void LeaveFrame(ObjHeader** start, int count) RUNTIME_NOTHROW; +// Tries to use returnSlot's arena for allocation. +ObjHeader** GetReturnSlotIfArena(ObjHeader** returnSlot, ObjHeader** localSlot) RUNTIME_NOTHROW; +// Tries to use param's arena for allocation. +ObjHeader** GetParamSlotIfArena(ObjHeader* param, ObjHeader** localSlot) RUNTIME_NOTHROW; // Collect garbage, which cannot be found by reference counting (cycles). void GarbageCollect() RUNTIME_NOTHROW; // Clears object subgraph references from memory subsystem, and optionally diff --git a/runtime/src/main/kotlin/konan/Annotations.kt b/runtime/src/main/kotlin/konan/Annotations.kt index 1bf097a986a..5134f24cd95 100644 --- a/runtime/src/main/kotlin/konan/Annotations.kt +++ b/runtime/src/main/kotlin/konan/Annotations.kt @@ -89,3 +89,7 @@ public annotation class FixmeInline * Need to be fixed. */ public annotation class Fixme + +public annotation class Escapes(val who: Int) + +public annotation class PointsTo(vararg val onWhom: Int) \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/Array.kt b/runtime/src/main/kotlin/kotlin/Array.kt index 64c967594d1..7dc81b714ed 100644 --- a/runtime/src/main/kotlin/kotlin/Array.kt +++ b/runtime/src/main/kotlin/kotlin/Array.kt @@ -38,9 +38,11 @@ public final class Array { get() = getArrayLength() @SymbolName("Kotlin_Array_get") + @PointsTo(0b0100, 0, 0b0001) // points to , points to . external public operator fun get(index: Int): T @SymbolName("Kotlin_Array_set") + @PointsTo(0b0100) // points to . external public operator fun set(index: Int, value: T): Unit public operator fun iterator(): kotlin.collections.Iterator { diff --git a/runtime/src/main/kotlin/kotlin/collections/ArrayUtil.kt b/runtime/src/main/kotlin/kotlin/collections/ArrayUtil.kt index 9ec53d2a6ee..22eff6050e9 100644 --- a/runtime/src/main/kotlin/kotlin/collections/ArrayUtil.kt +++ b/runtime/src/main/kotlin/kotlin/collections/ArrayUtil.kt @@ -191,6 +191,7 @@ internal fun Array.resetAt(index: Int) { } @SymbolName("Kotlin_Array_fillImpl") +@PointsTo(0b01000) // points to . external private fun fillImpl(array: Array, fromIndex: Int, toIndex: Int, value: Any?) @SymbolName("Kotlin_IntArray_fillImpl") @@ -212,6 +213,7 @@ internal fun IntArray.fill(fromIndex: Int, toIndex: Int, value: Int) { } @SymbolName("Kotlin_Array_copyImpl") +@PointsTo(0, 0, 0b000001) // points to . external private fun copyImpl(array: Array, fromIndex: Int, destination: Array, toIndex: Int, count: Int)