JVM_IR KT-34594 strip fake variable initialization during inlining

This commit is contained in:
Dmitry Petrov
2021-08-02 14:43:12 +03:00
parent 37050e0616
commit bd71fbe982
15 changed files with 182 additions and 26 deletions
@@ -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)
@@ -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("<fake>", 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: <label>
// If variable 'x' is not "used" (see above), remove p0 and p1 instructions.
var changes = false
for (p0 in insnArray) {
if (p0.opcode != Opcodes.ICONST_0) continue
val p1 = p0.next ?: break
if (p1.opcode != Opcodes.ISTORE) continue
val p2 = p1.next ?: break
if (p2.type != AbstractInsnNode.LABEL) continue
val varIndex = (p1 as VarInsnNode).`var`
if (!usedIntegerVar[varIndex]) {
changes = true
node.instructions.remove(p0)
node.instructions.remove(p1)
}
}
if (changes) {
// If we removed some instructions, some TCBs could (in theory) become empty.
// Remove empty TCBs if there are any.
node.removeEmptyCatchBlocks()
}
}
private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean {
if (type == null || type.sort != Type.OBJECT) return false
return inliningContext.isRegeneratedAnonymousObject(type.internalName)
@@ -23,13 +23,11 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.resolve.jvm.annotations.isCallableMemberCompiledToJvmDefault
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import org.jetbrains.kotlin.types.expressions.LabelResolver
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -3288,6 +3288,24 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFun.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunFromStdlib.kt")
public void testNoFakeVariableForInlineOnlyFunFromStdlib() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunFromStdlib.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunMap.kt")
public void testNoFakeVariableForInlineOnlyFunMap() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunMap.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunWithLambda.kt")
public void testNoFakeVariableForInlineOnlyFunWithLambda() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunWithLambda.kt");
}
@Test
@TestMetadata("noSynAccessor.kt")
public void testNoSynAccessor() throws Exception {
@@ -17,13 +17,13 @@ fun box(): String {
}
// Shared variable slots (x1, x2):
// 4 ILOAD 6
// 4 ISTORE 6
// Temporary variable for 'x2++' + store to fake variable marking the outer `run`:
// 2 ISTORE 1
// 0 NEW
// 0 GETFIELD
// 0 PUTFIELD
// No fake variables for @InlineOnly functions
// 0 ILOAD 6
// 0 ISTORE 6
@@ -14,7 +14,7 @@ fun main(args: Array<String>) {
@BuilderInference
suspend fun SequenceScope<Int>.awaitSeq(): Int = 42
// 1 LINENUMBER 9 L19
// 1 LOCALVARIABLE a I L[0-9]+ L18
// 1 LINENUMBER 9 L18
// 1 LOCALVARIABLE a I L[0-9]+ L17
// IGNORE_BACKEND_FIR: JVM_IR
@@ -16,5 +16,5 @@ fun box(): String {
// 0 component1
// 0 component2
// The 1st ICONST_0 is for initializing the list. 2nd is for initializing the index in the lowered for-loop.
// 2 ICONST_0
// - Initializing the index in the lowered for-loop.
// 1 ICONST_0
@@ -16,5 +16,5 @@ fun box(): String {
// 0 component1
// 0 component2
// The 1st ICONST_0 is for initializing the list. 2nd is for initializing the index in the lowered for-loop.
// 2 ICONST_0
// - Initializing the index in the lowered for-loop.
// 1 ICONST_0
@@ -49,4 +49,5 @@ class A {
fun calcRequiredLayoutSize(count: Int, max: Int, toInt: Int) = 0
// 3 ISTORE 10
// 2 ISTORE 10
@@ -1,17 +1,23 @@
// IGNORE_BACKEND_FIR: JVM_IR
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@kotlin.internal.InlineOnly
inline fun inlineOnlyFun() = 42
inline fun inlineOnlyFun(): Int {
var unused = 0
var used1 = 0
var used2 = 0
++used2
return 42 + used1
}
fun test() = inlineOnlyFun()
// JVM_TEMPLATES
// 1 LDC 0
// 1 ICONST_0
// 0 ISTORE 1
// 2 ISTORE 0
// 5 ICONST_0
// 6 ISTORE
// JVM_IR_TEMPLATES
// 0 LDC 0
// 0 ICONST_0
// 0 ISTORE 1
// 0 ISTORE 0
// 5 ICONST_0
// 5 ISTORE
@@ -0,0 +1,9 @@
// WITH_RUNTIME
import kotlin.math.sin
fun test(x: Double) = sin(x)
// 0 LDC 0
// 0 ICONST_0
// 0 ISTORE
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun test(xs: List<String>) =
xs.map { it.length }
// $i$f$map, $i$f$mapTo, $i$a$-map-NoFakeVariableForInlineOnlyFunMapKt$test$1
// 3 ICONST_0
// 3 ISTORE
@@ -0,0 +1,14 @@
// WITH_RUNTIME
fun test() = run {
var tmp = 0
"OK"
}
// 2 LOCALVARIABLE
// 1 LOCALVARIABLE tmp I
// 1 LOCALVARIABLE \$i\$a\$-run-NoFakeVariableForInlineOnlyFunWithLambdaKt\$test\$1 I
// 0 LDC 0
// 2 ICONST_0
// 2 ISTORE
@@ -3150,6 +3150,24 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFun.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunFromStdlib.kt")
public void testNoFakeVariableForInlineOnlyFunFromStdlib() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunFromStdlib.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunMap.kt")
public void testNoFakeVariableForInlineOnlyFunMap() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunMap.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunWithLambda.kt")
public void testNoFakeVariableForInlineOnlyFunWithLambda() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunWithLambda.kt");
}
@Test
@TestMetadata("noSynAccessor.kt")
public void testNoSynAccessor() throws Exception {
@@ -3288,6 +3288,24 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFun.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunFromStdlib.kt")
public void testNoFakeVariableForInlineOnlyFunFromStdlib() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunFromStdlib.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunMap.kt")
public void testNoFakeVariableForInlineOnlyFunMap() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunMap.kt");
}
@Test
@TestMetadata("noFakeVariableForInlineOnlyFunWithLambda.kt")
public void testNoFakeVariableForInlineOnlyFunWithLambda() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inline/noFakeVariableForInlineOnlyFunWithLambda.kt");
}
@Test
@TestMetadata("noSynAccessor.kt")
public void testNoSynAccessor() throws Exception {