From fe5a8f664a0691c27adda329864fd70e4bca16e1 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Tue, 31 Jan 2023 10:02:48 +0000 Subject: [PATCH] Add debug information for inline function parameters (#5050) Add debug information for inline function parameters Previously during debugging Kotlin/Native applications you couldn't inspect the inline function or inline lambda parameters. This happened because the debug information for them wasn't generated at all. To fix this issue we have to store the information about which expressions were inlined instead of which parameters. This commit adds a new mapping, which allows storing the required information during the inlining phase. The mapping is then reused during the bitcode generation phase to add the debug information for inline function parameters. ^KT-55440 --- .../common/lower/inline/FunctionInlining.kt | 73 ++--- .../ir/declarations/IrDeclarationOrigin.kt | 2 + .../backend/konan/driver/phases/Lowerings.kt | 6 +- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 18 +- .../kotlin/native/test/debugger/LldbTests.kt | 259 +++++++++++++++++- 5 files changed, 317 insertions(+), 41 deletions(-) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt index 98f0807db1b..286868b79f6 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt @@ -83,7 +83,8 @@ class FunctionInlining( val context: CommonBackendContext, val inlineFunctionResolver: InlineFunctionResolver, val innerClassesSupport: InnerClassesSupport? = null, - val insertAdditionalImplicitCasts: Boolean = false + val insertAdditionalImplicitCasts: Boolean = false, + val alwaysCreateTemporaryVariablesForArguments: Boolean = false ) : IrElementTransformerVoidWithContext(), BodyLoweringPass { constructor(context: CommonBackendContext) : this(context, DefaultInlineFunctionResolver(context), null) @@ -590,55 +591,63 @@ class FunctionInlining( val evaluationStatements = mutableListOf() val substitutor = ParameterSubstitutor() arguments.forEach { argument -> + val parameter = argument.parameter /* * 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, * not only for those referring to inlinable lambdas. */ if (argument.isInlinableLambdaArgument) { - substituteMap[argument.parameter] = argument.argumentExpression + substituteMap[parameter] = argument.argumentExpression (argument.argumentExpression as? IrFunctionReference)?.let { evaluationStatements += evaluateArguments(it) } return@forEach } - if (argument.isImmutableVariableLoad) { - substituteMap[argument.parameter] = - argument.argumentExpression.transform( // Arguments may reference the previous ones - substitute them. - substitutor, - data = null - ) - return@forEach - } - // Arguments may reference the previous ones - substitute them. val variableInitializer = argument.argumentExpression.transform(substitutor, data = null) - - val argumentExtracted = !argument.argumentExpression.isPure(false, context = context) - - if (!argumentExtracted) { - substituteMap[argument.parameter] = variableInitializer - } else { - val newVariable = - currentScope.scope.createTemporaryVariable( - irExpression = IrBlockImpl( - variableInitializer.startOffset, - variableInitializer.endOffset, - variableInitializer.type, - InlinerExpressionLocationHint((currentScope.irElement as IrSymbolOwner).symbol) - ).apply { - statements.add(variableInitializer) - }, - nameHint = callee.symbol.owner.name.toString(), - isMutable = false - ) - + val shouldCreateTemporaryVariable = + (alwaysCreateTemporaryVariablesForArguments && !parameter.isInlineParameter()) || + argument.shouldBeSubstitutedViaTemporaryVariable() + if (shouldCreateTemporaryVariable) { + val newVariable = createTemporaryVariable(parameter, variableInitializer, callee) evaluationStatements.add(newVariable) - substituteMap[argument.parameter] = IrGetValueWithoutLocation(newVariable.symbol) + substituteMap[parameter] = IrGetValueWithoutLocation(newVariable.symbol) + } else { + substituteMap[parameter] = variableInitializer } } return evaluationStatements } + + private fun ParameterToArgument.shouldBeSubstitutedViaTemporaryVariable(): Boolean = + !isImmutableVariableLoad && !argumentExpression.isPure(false, context = context) + + private fun createTemporaryVariable(parameter: IrValueParameter, variableInitializer: IrExpression, callee: IrFunction): IrVariable { + val variable = currentScope.scope.createTemporaryVariable( + irExpression = IrBlockImpl( + variableInitializer.startOffset, + variableInitializer.endOffset, + variableInitializer.type, + InlinerExpressionLocationHint((currentScope.irElement as IrSymbolOwner).symbol) + ).apply { + statements.add(variableInitializer) + }, + nameHint = callee.symbol.owner.name.toString(), + isMutable = false, + origin = if (parameter == callee.extensionReceiverParameter) { + IrDeclarationOrigin.IR_TEMPORARY_VARIABLE_FOR_INLINED_EXTENSION_RECEIVER + } else { + IrDeclarationOrigin.IR_TEMPORARY_VARIABLE_FOR_INLINED_PARAMETER + } + ) + + if (alwaysCreateTemporaryVariablesForArguments) { + variable.name = parameter.name + } + + return variable + } } private class IrGetValueWithoutLocation( diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt index 4b58e0c846d..53ad8a101d5 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt @@ -52,6 +52,8 @@ interface IrDeclarationOrigin { object INSTANCE_RECEIVER : IrDeclarationOriginImpl("INSTANCE_RECEIVER") object PRIMARY_CONSTRUCTOR_PARAMETER : IrDeclarationOriginImpl("PRIMARY_CONSTRUCTOR_PARAMETER") object IR_TEMPORARY_VARIABLE : IrDeclarationOriginImpl("IR_TEMPORARY_VARIABLE") + object IR_TEMPORARY_VARIABLE_FOR_INLINED_PARAMETER : IrDeclarationOriginImpl("IR_TEMPORARY_VARIABLE_FOR_INLINED_PARAMETER") + object IR_TEMPORARY_VARIABLE_FOR_INLINED_EXTENSION_RECEIVER : IrDeclarationOriginImpl("IR_TEMPORARY_VARIABLE_FOR_INLINED_EXTENSION_RECEIVER") object IR_EXTERNAL_DECLARATION_STUB : IrDeclarationOriginImpl("IR_EXTERNAL_DECLARATION_STUB") object IR_EXTERNAL_JAVA_DECLARATION_STUB : IrDeclarationOriginImpl("IR_EXTERNAL_JAVA_DECLARATION_STUB") object IR_BUILTINS_STUB : IrDeclarationOriginImpl("IR_BUILTINS_STUB") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Lowerings.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Lowerings.kt index 7957472ee7d..91e23d76944 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Lowerings.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Lowerings.kt @@ -321,7 +321,11 @@ private val inlinePhase = createFileLoweringPhase( lowering = { context: NativeGenerationState -> object : FileLoweringPass { override fun lower(irFile: IrFile) { - FunctionInlining(context.context, NativeInlineFunctionResolver(context.context, context)).lower(irFile) + FunctionInlining( + context.context, + NativeInlineFunctionResolver(context.context, context), + alwaysCreateTemporaryVariablesForArguments = context.shouldContainDebugInfo() + ).lower(irFile) } } }, diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 70253e3b6a7..5b242d72797 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -2905,12 +2905,10 @@ internal class CodeGeneratorVisitor( } } -private fun IrValueParameter.debugNameConversion() = descriptor.name.debugNameConversion() - -private fun IrVariable.debugNameConversion() = descriptor.name.debugNameConversion() - private val thisName = Name.special("") private val underscoreThisName = Name.identifier("_this") +private val doubleUnderscoreThisName = Name.identifier("__this") + /** * HACK: this is workaround for GH-2316, to let IDE some how operate with this. * We're experiencing issue with libclang which is used as compiler of expression in lldb @@ -2918,9 +2916,15 @@ private val underscoreThisName = Name.identifier("_this") * 1. isn't accepted by libclang as valid variable name. * 2. this is reserved name and compiled in special way. */ -private fun Name.debugNameConversion(): Name = when(this) { - thisName -> underscoreThisName - else -> this +private fun IrValueDeclaration.debugNameConversion(): Name { + val name = descriptor.name + if (name == thisName) { + return when (origin) { + IrDeclarationOrigin.IR_TEMPORARY_VARIABLE_FOR_INLINED_EXTENSION_RECEIVER -> doubleUnderscoreThisName + else -> underscoreThisName + } + } + return name } internal class LocationInfo(val scope: DIScopeOpaqueRef, diff --git a/kotlin-native/backend.native/tests/debugger/src/test/kotlin/org/jetbrains/kotlin/native/test/debugger/LldbTests.kt b/kotlin-native/backend.native/tests/debugger/src/test/kotlin/org/jetbrains/kotlin/native/test/debugger/LldbTests.kt index e1d9e76bdeb..e60c9c78aa5 100644 --- a/kotlin-native/backend.native/tests/debugger/src/test/kotlin/org/jetbrains/kotlin/native/test/debugger/LldbTests.kt +++ b/kotlin-native/backend.native/tests/debugger/src/test/kotlin/org/jetbrains/kotlin/native/test/debugger/LldbTests.kt @@ -495,4 +495,261 @@ class LldbTests { """.trimIndent().lldb(program) } -} \ No newline at end of file + @Test + fun `inline function arguments as expressions are visible`() = lldbTest(""" + inline fun foo( + p1: Int, p2: Int, p3: Int, p4: Int, p5: Int, p6: Int, p7: Int, + f: (Int, Int, Int, Int, Int, Int, Int) -> Unit + ) { + println() + f(p1, p2, p3, p4, p5, p6, p7) + } + + fun bar() = 3 + inline fun baz() = 3 + fun getCondition() = true + fun getNull(): Int? = null + + const val X = 10 + + fun main() { + val tmp = bar() + foo( + 1, + tmp, + tmp + 2, + bar(), + X, + if (getCondition()) 1 else 2, + when { + tmp >= 0 -> 1 + tmp < 0 -> 2 + else -> 0 + } + ) { p1, p2, p3, p4, p5, p6, p7 -> + println(p1 + p2 + p3 + p4 + p5 + p6 + p7) + } + + foo( + { 2 + 2 }(), + baz(), + listOf(1, 2, 3).filter { it > 2 }.sum(), + getNull()?.let { it + 1 } ?: 0, + try { bar() } finally { baz() }, + "".length, + object : Any() { + override fun hashCode(): Int { + return 1 + } + }.hashCode() + ) { p1, p2, p3, p4, p5, p6, p7 -> + println(p1 + p2 + p3 + p4 + p5 + p6 + p7) + } + } + """, """ + > b main.kt:5 + > b main.kt:31 + > b main.kt:47 + > ${lldbCommandRunOrContinue()} + > v + (int) p1 = 1 + (int) p2 = 3 + (int) p3 = 5 + (int) p4 = 3 + (int) p5 = 10 + (int) p6 = 1 + (int) p7 = 1 + > c + > v + (int) p1 = 1 + (int) p2 = 3 + (int) p3 = 5 + (int) p4 = 3 + (int) p5 = 10 + (int) p6 = 1 + (int) p7 = 1 + > c + > v + (int) p1 = 4 + (int) p2 = 3 + (int) p3 = 3 + (int) p4 = 0 + (int) p5 = 3 + (int) p6 = 0 + (int) p7 = 1 + > c + > v + (int) p1 = 4 + (int) p2 = 3 + (int) p3 = 3 + (int) p4 = 0 + (int) p5 = 3 + (int) p6 = 0 + (int) p7 = 1 + > q + """) + + @Test + fun `inline lambda anonymous argument is visible`() = lldbTest(""" + fun main() { + val list = listOf(1) + list.filter { + it > 2 + } + + list.map { + it * 2 + } + + list.find { + it == 1 + } + } + """, """ + > b main.kt:4 + > b main.kt:8 + > b main.kt:12 + > ${lldbCommandRunOrContinue()} + > v + (int) it = 1 + > c + > v + (int) it = 1 + > c + > v + (int) it = 1 + > q + """) + + @Test + fun `inline function arguments of various types are visible`() = lldbTest(""" + class A + object B + data class C(val x: Int) + interface I + + inline fun foo(a: A, b: B, c: C, i: I, f: (A, B, C, I) -> Unit) { + println() + f(a, b, c, i) + } + + fun main() { + val a = A() + val b = B + val c = C(0) + val i = object : I {} + foo(a, b, c, i) { pa, pb, pc, pi -> + println(pa) + println(pb) + println(pc) + println(pi) + } + + foo(A(), B, C(0), object : I {}) { pa, pb, pc, pi -> + println(pa) + println(pb) + println(pc) + println(pi) + } + } + """, """ + > b main.kt:7 + > b main.kt:17 + > b main.kt:24 + > ${lldbCommandRunOrContinue()} + > v + (ObjHeader *) a = [] + (ObjHeader *) b = [] + (ObjHeader *) c = [x: ...] + (ObjHeader *) i = [] + > c + > v + (ObjHeader *) pa = [] + (ObjHeader *) pb = [] + (ObjHeader *) pc = [x: ...] + (ObjHeader *) pi = [] + > c + > v + (ObjHeader *) a = [] + (ObjHeader *) b = [] + (ObjHeader *) c = [x: ...] + (ObjHeader *) i = [] + > c + > v + (ObjHeader *) pa = [] + (ObjHeader *) pb = [] + (ObjHeader *) pc = [x: ...] + (ObjHeader *) pi = [] + > q + """) + + @Test + fun `inline function default parameters are visible`() = lldbTest(""" + inline fun foo(x: Int = 0, y: String = "STRING", z: Any? = null) { + println(x) + println(y) + println(z) + } + + fun main() { + foo() + foo(1) + foo(1, "TEST_STRING") + foo(1, "TEST_STRING", Any()) + } + """, """ + > b main.kt:2 + > ${lldbCommandRunOrContinue()} + > v + (int) x = 0 + (ObjHeader *) y = STRING + (ObjHeader *) z = NULL + > c + > v + (int) x = 1 + (ObjHeader *) y = STRING + (ObjHeader *) z = NULL + > c + > v + (int) x = 1 + (ObjHeader *) y = TEST_STRING + (ObjHeader *) z = NULL + > c + > v + (int) x = 1 + (ObjHeader *) y = TEST_STRING + (ObjHeader *) z = [] + > q + """) + + @Test + fun `inline function extension receiver is visible`() = lldbTest(""" + class A { + inline fun String.foo(f: String.() -> Unit) { + println() + f() + } + + fun bar() { + "TEST".foo { + println() + } + } + } + + fun main() { + A().bar() + } + """, """ + > b main.kt:3 + > b main.kt:9 + > ${lldbCommandRunOrContinue()} + > v + (ObjHeader *) _this = [] + (ObjHeader *) __this = TEST + > c + > v + (ObjHeader *) ${"$"}this${"$"}foo = TEST + > q + """) +}