From 4b27074979ed46a6683141d14dd29fcce732b185 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Wed, 8 Feb 2017 19:15:55 +0300 Subject: [PATCH] Extremely conservative escape analysis. (#212) --- .../kotlin/backend/konan/EscapeAnalysis.kt | 481 ++++++++++++++++++ .../kotlin/backend/konan/llvm/ContextUtils.kt | 2 +- .../kotlin/backend/konan/llvm/DataLayout.kt | 5 +- .../backend/konan/llvm/EscapeAnalysis.kt | 38 -- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 34 +- .../kotlin/backend/konan/llvm/Runtime.kt | 3 + .../backend/konan/llvm/VariableManager.kt | 9 +- .../tests/runtime/memory/escape0.kt | 58 +++ .../kotlin/collections/AbstractCollection.kt | 9 +- 9 files changed, 571 insertions(+), 68 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.kt delete mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EscapeAnalysis.kt create mode 100644 backend.native/tests/runtime/memory/escape0.kt 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 new file mode 100644 index 00000000000..fbcb5fb44e4 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EscapeAnalysis.kt @@ -0,0 +1,481 @@ +package org.jetbrains.kotlin.backend.konan + +import org.jetbrains.kotlin.backend.konan.descriptors.allValueParameters +import org.jetbrains.kotlin.backend.konan.llvm.Lifetime +import org.jetbrains.kotlin.backend.konan.llvm.RuntimeAware +import org.jetbrains.kotlin.backend.konan.ir.getArguments +import org.jetbrains.kotlin.backend.konan.ir.ir2string +import org.jetbrains.kotlin.backend.konan.llvm.isObjectType +import org.jetbrains.kotlin.descriptors.* +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.expressions.* +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.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isNothing +import org.jetbrains.kotlin.types.typeUtil.isUnit + +val DEBUG = false + +// 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 +} + +internal class RoleInfoEntry(val data: Any? = null) + +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) + } + + fun add(roles: Roles) { + roles.data.forEach { role, info -> + info.entries.forEach { entry -> + add(role, entry) + } + } + } + + fun remove(role: Role) = data.remove(role) + + 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("; ") + } + 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)) + } + } + + // 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) + } + } else { + result.add(element) + } + } + } + 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 + // Virtual dispatch. + if (callee is FunctionDescriptor && callee.isOverridable) return false + if (DEBUG) println("passed as ${argument}") + val rolesInCallee = elementData[argument] ?: return false + if (DEBUG) println("In callee is: $rolesInCallee") + // TODO: make recursive computation here. + if (rolesInCallee.escapes()) return false + } + 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) + println("unescaped expression ${it.key}") + } + elementData.forEach { + if (propagateCallArguments(it.value) && DEBUG) + 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.allValueParameters.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 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(module: IrModuleFragment) { + module.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) + println("for ${expression} roles are ${roles.data.keys.joinToString(", ")}") + var role = rolesToLifetime(roles) + if (role == Lifetime.LOCAL) { + println("arena alloc for ${ir2string(expression)} in ${findContext(irModule, expression)}") + // Not yet enabled. + role = Lifetime.GLOBAL + } + lifetimes[expression] = role + } + if (DEBUG) { + 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/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 29b16df3a71..4877f111965 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 @@ -29,7 +29,7 @@ internal enum class SlotType { } // Lifetimes class of reference, computed by escape analysis. -internal enum class Lifetime(val slotType: SlotType) { +enum class Lifetime(val slotType: SlotType) { // If reference is frame-local (only obtained from some call and never leaves). LOCAL(SlotType.ARENA), // If reference is only returned. diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt index d6707d6af9e..8251236464d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt @@ -4,6 +4,7 @@ import llvm.* import org.jetbrains.kotlin.backend.konan.ValueType import org.jetbrains.kotlin.backend.konan.isRepresentedAs import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isUnit private val valueTypes = ValueType.values().associate { @@ -38,4 +39,6 @@ internal fun RuntimeAware.getLLVMReturnType(type: KotlinType): LLVMTypeRef { // KotlinBuiltIns.isNothing(type) -> LLVMVoidType() else -> getLLVMType(type) } -} \ No newline at end of file +} + +fun RuntimeAware.isObjectType(type: KotlinType) : Boolean = isObjectType(getLLVMType(type)) \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EscapeAnalysis.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EscapeAnalysis.kt deleted file mode 100644 index 5fa8790731a..00000000000 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EscapeAnalysis.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.jetbrains.kotlin.backend.konan.llvm - -import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression -import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid -import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlin.ir.visitors.acceptVoid - -// Analysis we're implementing here is as following. -// We build graph with the following nodes: -// * allocation set, keeping tuple of [local, ctor call, owner function], AS -// * local store set, keeping pair [local, stored], LSS -// * field store set, keeping tuple [local, object to store, stored field id], FSS -// * global store set, [local, stored global address], GSS -// Function we're trying to compute is the following: -// for each element of AS, could it be referred by someone, whose value is -// alive on return from function, where element was allocated. -// Each element in RS is associated with few elements in AS, which it could refer to. -// -internal class EscapeAnalyzerVisitor( - val lifetimes: MutableMap) : IrElementVisitorVoid { - - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitModuleFragment(module: IrModuleFragment) { - module.acceptChildrenVoid(this) - } -} - -internal fun computeLifetimes(irModule: IrModuleFragment, - lifetimes: MutableMap) { - assert(lifetimes.size == 0) - - irModule.acceptVoid(EscapeAnalyzerVisitor(lifetimes)) -} \ No newline at end of file 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 c688f81ed6a..eede8254a63 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 @@ -168,7 +168,7 @@ internal interface CodeContext { internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid { val codegen = CodeGenerator(context) - val resultLifetimes = mutableMapOf() + val resultLifetimes = mutableMapOf() //-------------------------------------------------------------------------// @@ -237,7 +237,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid override fun visitModuleFragment(module: IrModuleFragment) { context.log("visitModule : ${ir2string(module)}") - computeLifetimes(module, resultLifetimes) + computeLifetimes(module, this.codegen, resultLifetimes) module.acceptChildrenVoid(this) appendLlvmUsed(context.llvm.usedFunctions) @@ -476,11 +476,11 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid */ private fun bindParameters(descriptor: FunctionDescriptor?): Map { if (descriptor == null) return emptyMap() - val paramDescriptors = descriptor.allValueParameters - return paramDescriptors.mapIndexed { i, paramDescriptor -> - val param = codegen.param(descriptor, i) - assert(codegen.getLLVMType(paramDescriptor.type) == param.type) - paramDescriptor to param + val parameterDescriptors = descriptor.allValueParameters + return parameterDescriptors.mapIndexed { i, parameterDescriptor -> + val parameter = codegen.param(descriptor, i) + assert(codegen.getLLVMType(parameterDescriptor.type) == parameter.type) + parameterDescriptor to parameter }.toMap() } @@ -1081,7 +1081,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } - assert (loop.type.isUnit()) + assert(loop.type.isUnit()) return codegen.theUnitInstanceRef.llvm } @@ -1104,7 +1104,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid codegen.positionAtEnd(loopScope.loopExit) } - assert (loop.type.isUnit()) + assert(loop.type.isUnit()) return codegen.theUnitInstanceRef.llvm } @@ -1119,11 +1119,11 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun evaluateSetVariable(value: IrSetVariable): LLVMValueRef { context.log("evaluateSetVariable : ${ir2string(value)}") - val ret = evaluateExpression(value.value) + val result = evaluateExpression(value.value) val variable = currentCodeContext.getDeclaredVariable(value.descriptor) - codegen.vars.store(ret, variable) + codegen.vars.store(result, variable) - assert (value.type.isUnit()) + assert(value.type.isUnit()) return codegen.theUnitInstanceRef.llvm } @@ -1131,8 +1131,8 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun generateVariable(value: IrVariable) { context.log("generateVariable : ${ir2string(value)}") - val ret = value.initializer?.let { evaluateExpression(it) } - currentCodeContext.genDeclareVariable(value.descriptor, ret) + val result = value.initializer?.let { evaluateExpression(it) } + currentCodeContext.genDeclareVariable(value.descriptor, result) } //-------------------------------------------------------------------------// @@ -1454,7 +1454,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } } - assert (value.type.isUnit()) + assert(value.type.isUnit()) return codegen.theUnitInstanceRef.llvm } } @@ -1621,8 +1621,8 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } //-------------------------------------------------------------------------// - private fun resultLifetime(callee: IrMemberAccessExpression): Lifetime { - return resultLifetimes.getOrElse(callee) { Lifetime.GLOBAL } + private fun resultLifetime(callee: IrElement): Lifetime { + return resultLifetimes.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/llvm/Runtime.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt index 43a7e145052..ae07dc95135 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt @@ -2,6 +2,9 @@ package org.jetbrains.kotlin.backend.konan.llvm import kotlinx.cinterop.* import llvm.* +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isNothing +import org.jetbrains.kotlin.types.typeUtil.isUnit interface RuntimeAware { val runtime: Runtime diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/VariableManager.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/VariableManager.kt index 7b5c5e3e0c3..a8bc4568417 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/VariableManager.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/VariableManager.kt @@ -70,17 +70,13 @@ internal class VariableManager(val codegen: CodeGenerator) { } // Creates anonymous mutable variable. - fun createAnonymousMutable(type: KotlinType, value: LLVMValueRef? = null) : Int { - return createAnonymousMutable(codegen.getLLVMType(type), value) - } - // Think of slot reuse. fun createAnonymousSlot(value: LLVMValueRef? = null) : LLVMValueRef { val index = createAnonymousMutable(codegen.kObjHeaderPtr, value) return addressOf(index) } - fun createAnonymousMutable(type: LLVMTypeRef, value: LLVMValueRef? = null) : Int { + private fun createAnonymousMutable(type: LLVMTypeRef, value: LLVMValueRef? = null) : Int { val index = variables.size val slot = codegen.alloca(type) if (value != null) @@ -91,9 +87,8 @@ internal class VariableManager(val codegen: CodeGenerator) { fun createImmutable(scoped: Pair, value: LLVMValueRef) : Int { val scopedVariable = ScopedVariable(scoped) - if(contextVariablesToIndex.containsKey(scopedVariable)) + if (contextVariablesToIndex.containsKey(scopedVariable)) throw Error("${scopedVariable.descriptor} is already defined") - assert(!contextVariablesToIndex.containsKey(scopedVariable)) val index = variables.size variables.add(ValueRecord(value, scopedVariable.descriptor.name)) contextVariablesToIndex[scopedVariable] = index diff --git a/backend.native/tests/runtime/memory/escape0.kt b/backend.native/tests/runtime/memory/escape0.kt new file mode 100644 index 00000000000..040fdc87884 --- /dev/null +++ b/backend.native/tests/runtime/memory/escape0.kt @@ -0,0 +1,58 @@ +fun foo1(arg: String) : String = arg + +/* +fun foo2(arg: String) : String = arg + " foo2" + +fun bar(arg1: String, arg2: String) : String = arg1 + " bar " + arg2 + +fun zoo1() : String { + var x = foo1("") + var y = 4 + return x +} + +fun zoo2() : String { + val x = foo1("") + var y = 5 + return x +} + +class Node(var previous: Node?) + +fun zoo3() : Node { + var current = Node(null) + for (i in 1 .. 5) { + current = Node(current) + } + return current +} + +fun zoo4(arg: Int) : Any { + var a = Any() + var b = Any() + var c = Any() + a = b + val x = 3 + a = when { + x < arg -> b + else -> c + } + return a +} */ + +fun zoo5(arg: String) : String { + return arg + foo1(arg) +} + +fun zoo6(arg: Any) : Any { + return zoo7(arg, "foo", 11) +} + +fun zoo7(arg1: Any, arg2: Any, selector: Int) : Any { + return if (selector < 2) arg1 else arg2; +} + +fun main(args : Array) { + val z = zoo7(Any(), Any(), 1) + //println(bar(foo1(foo2("")), foo2(foo1("")))) +} diff --git a/runtime/src/main/kotlin/kotlin/collections/AbstractCollection.kt b/runtime/src/main/kotlin/kotlin/collections/AbstractCollection.kt index cb464dfde1f..9bee9013fb2 100644 --- a/runtime/src/main/kotlin/kotlin/collections/AbstractCollection.kt +++ b/runtime/src/main/kotlin/kotlin/collections/AbstractCollection.kt @@ -11,8 +11,9 @@ public abstract class AbstractCollection protected constructor() : Collec override fun isEmpty(): Boolean = size == 0 - /* - override fun toString(): String = joinToString(", ", "[", "]") { - if (it === this) "(this Collection)" else it.toString() - } */ + + @Fixme + override fun toString(): String = TODO() // joinToString(", ", "[", "]") { + // if (it === this) "(this Collection)" else it.toString() + // } }