JVM_IR KT-34594 strip fake variable initialization during inlining
This commit is contained in:
+2
-1
@@ -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
|
||||
|
||||
+18
@@ -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 {
|
||||
|
||||
Vendored
+4
-4
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
Vendored
+2
-2
@@ -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
|
||||
|
||||
compiler/testData/codegen/bytecodeText/forLoop/forInSequenceWithIndex/forInEmptySequenceWithIndex.kt
Vendored
+2
-2
@@ -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
|
||||
+13
-7
@@ -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
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
import kotlin.math.sin
|
||||
|
||||
fun test(x: Double) = sin(x)
|
||||
|
||||
// 0 LDC 0
|
||||
// 0 ICONST_0
|
||||
// 0 ISTORE
|
||||
+8
@@ -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
|
||||
+14
@@ -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
|
||||
+18
@@ -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 {
|
||||
|
||||
+18
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user