From bd71fbe9822905b8c383681bca26375e2686a595 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Mon, 2 Aug 2021 14:43:12 +0300 Subject: [PATCH] JVM_IR KT-34594 strip fake variable initialization during inlining --- .../inline/AnonymousObjectTransformer.kt | 3 +- .../kotlin/codegen/inline/MethodInliner.kt | 75 +++++++++++++++++-- .../inline/PsiSourceCompilerForInline.kt | 2 - .../codegen/FirBytecodeTextTestGenerated.java | 18 +++++ .../sharedSlotsWithCapturedVars.kt | 8 +- .../debug/localVariableCorrectLabel.kt | 4 +- .../forInEmptyListWithIndex.kt | 4 +- .../forInEmptySequenceWithIndex.kt | 4 +- .../inline/inlineArgumentSlots.kt | 3 +- .../inline/noFakeVariableForInlineOnlyFun.kt | 20 +++-- ...oFakeVariableForInlineOnlyFunFromStdlib.kt | 9 +++ .../noFakeVariableForInlineOnlyFunMap.kt | 8 ++ ...oFakeVariableForInlineOnlyFunWithLambda.kt | 14 ++++ .../codegen/BytecodeTextTestGenerated.java | 18 +++++ .../codegen/IrBytecodeTextTestGenerated.java | 18 +++++ 15 files changed, 182 insertions(+), 26 deletions(-) create mode 100644 compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunFromStdlib.kt create mode 100644 compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunMap.kt create mode 100644 compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunWithLambda.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt index 2ccc52d9891..5fceecf8026 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt @@ -320,7 +320,8 @@ class AnonymousObjectTransformer( inliningContext.callSiteInfo.isInlineOrInsideInline, inliningContext.callSiteInfo.file, inliningContext.callSiteInfo.lineNumber - ), null + ), + null ).doInline(deferringVisitor, LocalVarRemapper(parameters, 0), false, mapOf()) reifiedTypeParametersUsages?.let(result.reifiedTypeParametersUsages::mergeAll) deferringVisitor.visitMaxs(-1, -1) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt index 5174348e3d5..6824cad0d79 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt @@ -13,12 +13,8 @@ import org.jetbrains.kotlin.codegen.inline.coroutines.markNoinlineLambdaIfSuspen import org.jetbrains.kotlin.codegen.inline.coroutines.surroundInvokesWithSuspendMarkersIfNeeded import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer -import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph -import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence -import org.jetbrains.kotlin.codegen.optimization.common.asSequence -import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful +import org.jetbrains.kotlin.codegen.optimization.common.* import org.jetbrains.kotlin.codegen.optimization.fixStack.* -import org.jetbrains.kotlin.codegen.optimization.fixStack.FastStackAnalyzer import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn import org.jetbrains.kotlin.config.LanguageFeature @@ -747,6 +743,8 @@ class MethodInliner( ApiVersionCallsPreprocessingMethodTransformer(targetApiVersion).transform("fake", node) } + removeFakeVariablesInitializationIfPresent(node) + val frames = FastStackAnalyzer("", node, FixStackInterpreter()).analyze() val localReturnsNormalizer = LocalReturnsNormalizer() @@ -769,6 +767,73 @@ class MethodInliner( localReturnsNormalizer.transform(node) } + private fun removeFakeVariablesInitializationIfPresent(node: MethodNode) { + // Before 1.6, we generated fake variable initialization instructions + // ICONST_0 + // ISTORE x + // for all inline functions. Original intent was to mark inline function body for the debugger with corresponding LVT entry. + // However, for @InlineOnly functions corresponding LVT entries were not copied (assuming that nobody is actually debugging + // @InlineOnly functions). + // Since 1.6, we no longer generate fake variables for @InlineOnly functions + // Here we erase fake variable initialization for @InlineOnly functions inlined into existing bytecode (e.g., inline function + // inside third-party library). + // We consider a sequence of instructions 'ICONST_0; ISTORE x' a fake variable initialization if the corresponding variable 'x' + // is not used in the bytecode (see below). + + val insnArray = node.instructions.toArray() + + // Very conservative variable usage check. + // Here we look at integer variables only (this includes integral primitive types: byte, char, short, boolean). + // Variable is considered "used" if: + // - it's loaded with ILOAD instruction + // - it's incremented with IINC instruction + // - there's a local variable table entry for this variable + val usedIntegerVar = BooleanArray(node.maxLocals) + for (insn in insnArray) { + if (insn.type == AbstractInsnNode.VAR_INSN && insn.opcode == Opcodes.ILOAD) { + usedIntegerVar[(insn as VarInsnNode).`var`] = true + } else if (insn.type == AbstractInsnNode.IINC_INSN) { + usedIntegerVar[(insn as IincInsnNode).`var`] = true + } + } + for (localVariable in node.localVariables) { + val d0 = localVariable.desc[0] + // byte || char || short || int || boolean + if (d0 == 'B' || d0 == 'C' || d0 == 'S' || d0 == 'I' || d0 == 'Z') { + usedIntegerVar[localVariable.index] = true + } + } + + // Looking for sequences of instructions: + // p0: ICONST_0 + // p1: ISTORE x + // p2: