From 8c8acbc6260e2e6f22ad936c9790f1d8fd8611fb Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Tue, 9 Jul 2019 10:30:07 +0300 Subject: [PATCH] [Inliner] Fixed bug with incorrect eval order We need to extract to temp locals all bound arguments of function reference parameters of an inline call to preserve the correct evaluation order --- .../backend/konan/lower/FunctionInlining.kt | 45 +++++++++++++++---- backend.native/tests/build.gradle | 5 +++ .../inline/correctOrderFunctionReference.kt | 40 +++++++++++++++++ 3 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 backend.native/tests/codegen/inline/correctOrderFunctionReference.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt index 8f96b4ec4e6..2a74a43ee59 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt @@ -223,6 +223,7 @@ internal class FunctionInlining(val context: Context) : IrElementTransformerVoid return super.visitCall(expression) if (functionArgument is IrFunctionReference) { + functionArgument.transformChildrenVoid(this) val function = functionArgument.symbol.owner val functionParameters = function.explicitParameters val boundFunctionParameters = functionArgument.getArgumentsWithIr() @@ -241,10 +242,10 @@ internal class FunctionInlining(val context: Context) : IrElementTransformerVoid }.apply { functionParameters.forEach { val argument = - if (!unboundArgsSet.contains(it)) - boundFunctionParametersMap[it]!! - else + if (unboundArgsSet.contains(it)) valueParameters[unboundIndex++].second + else + boundFunctionParametersMap[it]!! when (it) { function.dispatchReceiverParameter -> this.dispatchReceiver = argument.implicitCastIfNeededTo(function.dispatchReceiverParameter!!.type) @@ -318,8 +319,7 @@ internal class FunctionInlining(val context: Context) : IrElementTransformerVoid } } - //-------------------------------------------------------------------------// - + // callee might be a copied version of callsite.symbol.owner private fun buildParameterToArgument(callSite: IrFunctionAccessExpression, callee: IrFunction): List { val parameterToArgument = mutableListOf() @@ -395,12 +395,38 @@ internal class FunctionInlining(val context: Context) : IrElementTransformerVoid //-------------------------------------------------------------------------// - private fun evaluateArguments(callSite: IrFunctionAccessExpression, callee: IrFunction): List { - - val parameterToArgumentOld = buildParameterToArgument(callSite, callee) + private fun evaluateArguments(functionReference: IrFunctionReference): List { + val arguments = functionReference.getArgumentsWithIr().map { ParameterToArgument(it.first, it.second) } val evaluationStatements = mutableListOf() val substitutor = ParameterSubstitutor() - parameterToArgumentOld.forEach { + val referenced = functionReference.symbol.owner + arguments.forEach { + val newArgument = if (it.isImmutableVariableLoad) { + it.argumentExpression.transform(substitutor, data = null) // Arguments may reference the previous ones - substitute them. + } else { + val newVariable = currentScope.scope.createTemporaryVariableWithWrappedDescriptor( // Create new variable and init it with the parameter expression. + irExpression = it.argumentExpression.transform(substitutor, data = null), // Arguments may reference the previous ones - substitute them. + nameHint = callee.symbol.owner.name.toString(), + isMutable = false) + + evaluationStatements.add(newVariable) + + IrGetValueImpl(it.argumentExpression.startOffset, it.argumentExpression.endOffset, newVariable.symbol) + } + when (it.parameter) { + referenced.dispatchReceiverParameter -> functionReference.dispatchReceiver = newArgument + referenced.extensionReceiverParameter -> functionReference.extensionReceiver = newArgument + else -> functionReference.putValueArgument(it.parameter.index, newArgument) + } + } + return evaluationStatements + } + + private fun evaluateArguments(callSite: IrFunctionAccessExpression, callee: IrFunction): List { + val arguments = buildParameterToArgument(callSite, callee) + val evaluationStatements = mutableListOf() + val substitutor = ParameterSubstitutor() + arguments.forEach { /* * We need to create temporary variable for each argument except inlinable lambda arguments. * For simplicity and to produce simpler IR we don't create temporaries for every immutable variable, @@ -408,6 +434,7 @@ internal class FunctionInlining(val context: Context) : IrElementTransformerVoid */ if (it.isInlinableLambdaArgument) { substituteMap[it.parameter] = it.argumentExpression + (it.argumentExpression as? IrFunctionReference)?.let { evaluationStatements += evaluateArguments(it) } return@forEach } diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index a57c97a53f1..9bd2528c68f 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2874,6 +2874,11 @@ task inline_genericFunctionReference(type: KonanLocalTest) { source = "codegen/inline/genericFunctionReference.kt" } +task inline_correctOrderFunctionReference(type: KonanLocalTest) { + goldValue = "OK\n" + source = "codegen/inline/correctOrderFunctionReference.kt" +} + task classDeclarationInsideInline(type: KonanLocalTest) { source = "codegen/inline/classDeclarationInsideInline.kt" goldValue = "test1: 1.0\n1\ntest2\n" diff --git a/backend.native/tests/codegen/inline/correctOrderFunctionReference.kt b/backend.native/tests/codegen/inline/correctOrderFunctionReference.kt new file mode 100644 index 00000000000..bd99d731e0b --- /dev/null +++ b/backend.native/tests/codegen/inline/correctOrderFunctionReference.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package codegen.inline.correctOrderFunctionReference + +import kotlin.test.* + +class Foo(val a: String) { + + fun test() = a +} + +inline fun test(a: String, b: () -> String, c: () -> String, d: () -> String, e: String): String { + return a + b() + c() + d() + e +} + +var effects = "" + +fun create(a: String): Foo { + effects += a + return Foo(a) +} + +fun create2(a: String, f: () -> String): Foo { + effects += a + return Foo(a) +} + +fun box(): String { + val result = test(create("A").a, create("B")::a, create("C")::test, create2("E", create("D")::test)::test, create("F").a) + if (effects != "ABCDEF") return "fail 1: $effects" + + return if (result == "ABCEF") "OK" else "fail 2: $result" +} + +@Test fun runTest() { + println(box()) +} \ No newline at end of file