Replace last SourceInterpreter with specific one in inliner

#KT-38489: Fixed
This commit is contained in:
Ilmir Usmanov
2020-05-06 14:26:03 +02:00
parent 2c88844409
commit 05797afaf8
10 changed files with 164 additions and 74 deletions
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
@@ -39,7 +40,9 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.LocalVariablesSorter
import org.jetbrains.org.objectweb.asm.commons.MethodRemapper
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.util.Printer
import java.util.*
import kotlin.math.max
@@ -502,7 +505,7 @@ class MethodInliner(
val toDelete = SmartSet.create<AbstractInsnNode>()
val sources = analyzeMethodWithFunctionalArgumentInterpreter(this, processingNode, toDelete)
val sources = analyzeMethodNodeWithInterpreter(processingNode, FunctionalArgumentInterpreter(this, toDelete))
val instructions = processingNode.instructions
var awaitClassReification = false
@@ -671,7 +674,7 @@ class MethodInliner(
if (inliningContext.state.isIrBackend) return
val lambdaInfo = inliningContext.lambdaInfo ?: return
if (!lambdaInfo.isSuspend) return
val sources = analyzeMethodNodeBeforeInline(processingNode)
val sources = analyzeMethodNodeWithInterpreter(processingNode, Aload0Interpreter(processingNode))
val cfg = ControlFlowGraph.build(processingNode)
val aload0s = processingNode.instructions.asSequence().filter { it.opcode == Opcodes.ALOAD && it.safeAs<VarInsnNode>()?.`var` == 0 }
@@ -726,10 +729,17 @@ class MethodInliner(
"${suspensionPoint.name}${suspensionPoint.desc} shall have 1+ parameters"
}
}
paramTypes.reversed().asSequence().withIndex()
.filter { it.value == languageVersionSettings.continuationAsmType() || it.value == OBJECT_TYPE }
.flatMap { frame.getStack(frame.stackSize - it.index - 1).insns.asSequence() }
.filter { it in aload0s }.let { toReplace.addAll(it) }
for ((index, param) in paramTypes.reversed().withIndex()) {
if (param != languageVersionSettings.continuationAsmType() && param != OBJECT_TYPE) continue
val sourceIndices = (frame.getStack(frame.stackSize - index - 1) as? Aload0BasicValue)?.indices ?: continue
for (sourceIndex in sourceIndices) {
val src = processingNode.instructions[sourceIndex]
if (src in aload0s) {
toReplace.add(src)
}
}
}
}
// Expected pattern here:
@@ -776,7 +786,7 @@ class MethodInliner(
ApiVersionCallsPreprocessingMethodTransformer(targetApiVersion).transform("fake", node)
}
val frames = analyzeMethodNodeWithBasicInterpreter(node)
val frames = analyzeMethodNodeWithInterpreter(node, BasicInterpreter())
val localReturnsNormalizer = LocalReturnsNormalizer()
@@ -1016,51 +1026,6 @@ class MethodInliner(
)
}
private fun analyzeMethodNodeWithBasicInterpreter(node: MethodNode): Array<Frame<BasicValue>?> {
val analyzer = object : Analyzer<BasicValue>(BasicInterpreter()) {
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> {
return object : Frame<BasicValue>(nLocals, nStack) {
@Throws(AnalyzerException::class)
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>) {
// This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else.
if (insn.opcode == Opcodes.RETURN) return
super.execute(insn, interpreter)
}
}
}
}
try {
return analyzer.analyze("fake", node)
} catch (e: AnalyzerException) {
throw RuntimeException(e)
}
}
private fun analyzeMethodNodeBeforeInline(node: MethodNode): Array<Frame<SourceValue>?> {
val analyzer = object : Analyzer<SourceValue>(SourceInterpreter()) {
override fun newFrame(nLocals: Int, nStack: Int): Frame<SourceValue> {
return object : Frame<SourceValue>(nLocals, nStack) {
@Throws(AnalyzerException::class)
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<SourceValue>) {
// This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else.
if (insn.opcode == Opcodes.RETURN) return
super.execute(insn, interpreter)
}
}
}
}
try {
return analyzer.analyze("fake", node)
} catch (e: AnalyzerException) {
throw RuntimeException(e)
}
}
//remove next template:
// aload x
// LDC paramName
@@ -1068,7 +1033,7 @@ class MethodInliner(
private fun removeClosureAssertions(node: MethodNode) {
val toDelete = arrayListOf<AbstractInsnNode>()
InsnSequence(node.instructions).filterIsInstance<MethodInsnNode>().forEach { methodInsnNode ->
if (isParameterNullabilityCheck(methodInsnNode)) {
if (methodInsnNode.isCheckParameterIsNotNull()) {
val prev = methodInsnNode.previous
assert(Opcodes.LDC == prev?.opcode) { "'${methodInsnNode.name}' should go after LDC but $prev" }
val prevPev = methodInsnNode.previous.previous
@@ -5,12 +5,16 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
import org.jetbrains.org.objectweb.asm.tree.analysis.*
fun parameterOffsets(isStatic: Boolean, valueParameters: List<JvmMethodParameterSignature>): Array<Int> {
@@ -56,9 +60,9 @@ fun AbstractInsnNode.getNextMeaningful(): AbstractInsnNode? {
return result
}
// Interpreter, that analyzes only functional arguments, to replace SourceInterpreter, since SourceInterpreter' merge has O(N²) complexity
// Interpreter, that analyzes functional arguments only, to replace SourceInterpreter, since SourceInterpreter's merge has O(N²) complexity
internal open class FunctionalArgumentValue(
internal class FunctionalArgumentValue(
val functionalArgument: FunctionalArgument, basicValue: BasicValue?
) : BasicValue(basicValue?.type) {
override fun toString(): String = "$functionalArgument"
@@ -67,17 +71,17 @@ internal open class FunctionalArgumentValue(
val BasicValue?.functionalArgument
get() = (this as? FunctionalArgumentValue)?.functionalArgument
private class FunctionalArgumentInterpreter(
internal class FunctionalArgumentInterpreter(
private val inliner: MethodInliner, private val toDelete: MutableSet<AbstractInsnNode>
) : BasicInterpreter(Opcodes.ASM5) {
) : BasicInterpreter(Opcodes.API_VERSION) {
override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue): BasicValue? =
markInstructionIfNeeded(insn, super.unaryOperation(insn, value))
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? {
val basicValue = super.copyOperation(insn, value)
// Paramter checks are processed separately
if (insn.next?.opcode == Opcodes.LDC && isParameterNullabilityCheck(insn.next?.next)) {
// Parameter checks are processed separately
if (insn.next?.opcode == Opcodes.LDC && insn.next?.next?.isCheckParameterIsNotNull() == true) {
return basicValue
}
if (value.functionalArgument is LambdaInfo) {
@@ -109,24 +113,35 @@ private class FunctionalArgumentInterpreter(
else super.merge(v, w)
}
fun isParameterNullabilityCheck(methodInsnNode: AbstractInsnNode?): Boolean =
methodInsnNode is MethodInsnNode && methodInsnNode.owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
(methodInsnNode.name == "checkParameterIsNotNull" || methodInsnNode.name == "checkNotNullParameter")
// Interpreter, that analyzes only ALOAD_0s, which are used as continuation arguments
internal fun analyzeMethodWithFunctionalArgumentInterpreter(
inliner: MethodInliner,
node: MethodNode,
toDelete: MutableSet<AbstractInsnNode>
): Array<out Frame<BasicValue>?> {
val analyzer = object : Analyzer<BasicValue>(FunctionalArgumentInterpreter(inliner, toDelete)) {
internal class Aload0BasicValue private constructor(val indices: Set<Int>) : BasicValue(AsmTypes.OBJECT_TYPE) {
constructor(i: Int) : this(setOf(i)) {}
operator fun plus(other: Aload0BasicValue) = Aload0BasicValue(indices + other.indices)
}
internal class Aload0Interpreter(private val node: MethodNode) : BasicInterpreter(Opcodes.API_VERSION) {
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? =
when {
insn.isAload0() -> Aload0BasicValue(node.instructions.indexOf(insn))
insn.opcode == Opcodes.ALOAD -> if (value == null) null else BasicValue(value.type)
else -> super.copyOperation(insn, value)
}
override fun merge(v: BasicValue?, w: BasicValue?): BasicValue =
if (v is Aload0BasicValue && w is Aload0BasicValue) v + w else super.merge(v, w)
}
internal fun AbstractInsnNode.isAload0() = opcode == Opcodes.ALOAD && (this as VarInsnNode).`var` == 0
internal fun analyzeMethodNodeWithInterpreter(node: MethodNode, interpreter: BasicInterpreter): Array<out Frame<BasicValue>?> {
val analyzer = object : Analyzer<BasicValue>(interpreter) {
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> {
return object : Frame<BasicValue>(nLocals, nStack) {
@Throws(AnalyzerException::class)
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>) {
if (node.name == "waitForEx\$default" && node.instructions.indexOf(insn) == 18) {
{}()
}
// This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else.
if (insn.opcode == Opcodes.RETURN) return
super.execute(insn, interpreter)
@@ -0,0 +1,28 @@
// WITH_RUNTIME
// FILE: 1.kt
inline fun List<String?>.forEachNotNull(s: String, fn: (String, String) -> Unit) {
for (x in this) {
fn(s, x ?: continue)
}
}
inline fun List<String?>.forEachUntilNull(s: String, fn: (String, String) -> Unit) {
for (x in this) {
fn(s, x ?: break)
}
}
// FILE: 2.kt
fun box(): String {
var res = ""
listOf("O", null, "K").forEachNotNull("|") { a, b ->
res += a + b
}
if (res != "|O|K") return res
res = ""
listOf("O", null, "K").forEachUntilNull("|") { a, b ->
res += a + b
}
if (res != "|O") return res
return "OK"
}
@@ -0,0 +1,22 @@
// FILE: 1.kt
class A(val s: String)
inline fun inlineMe(limit: Int, c: (String) -> String): A {
var index = 0
var res: A?
while (true) {
res = A(c(try {
throw IllegalStateException("")
} catch (ignored: Throwable) {
"OK"
})
)
if (index++ == limit) break
}
return res!!
}
// FILE: 2.kt
fun box(): String {
return inlineMe(1) { "OK" }.s
}
@@ -1111,6 +1111,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/complexStack/asCheck2.kt");
}
@TestMetadata("breaContinuekInInlineLambdaArgument.kt")
public void testBreaContinuekInInlineLambdaArgument() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/breaContinuekInInlineLambdaArgument.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simple.kt");
@@ -1135,6 +1140,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
public void testSimpleExtension() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simpleExtension.kt");
}
@TestMetadata("spillConstructorArgumentsAndInlineLambdaParameter.kt")
public void testSpillConstructorArgumentsAndInlineLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/spillConstructorArgumentsAndInlineLambdaParameter.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/contracts")
@@ -1111,6 +1111,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/complexStack/asCheck2.kt");
}
@TestMetadata("breaContinuekInInlineLambdaArgument.kt")
public void testBreaContinuekInInlineLambdaArgument() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/breaContinuekInInlineLambdaArgument.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simple.kt");
@@ -1135,6 +1140,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
public void testSimpleExtension() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simpleExtension.kt");
}
@TestMetadata("spillConstructorArgumentsAndInlineLambdaParameter.kt")
public void testSpillConstructorArgumentsAndInlineLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/spillConstructorArgumentsAndInlineLambdaParameter.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/contracts")
@@ -1111,6 +1111,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/complexStack/asCheck2.kt");
}
@TestMetadata("breaContinuekInInlineLambdaArgument.kt")
public void testBreaContinuekInInlineLambdaArgument() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/breaContinuekInInlineLambdaArgument.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simple.kt");
@@ -1135,6 +1140,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
public void testSimpleExtension() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simpleExtension.kt");
}
@TestMetadata("spillConstructorArgumentsAndInlineLambdaParameter.kt")
public void testSpillConstructorArgumentsAndInlineLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/spillConstructorArgumentsAndInlineLambdaParameter.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/contracts")
@@ -1111,6 +1111,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/complexStack/asCheck2.kt");
}
@TestMetadata("breaContinuekInInlineLambdaArgument.kt")
public void testBreaContinuekInInlineLambdaArgument() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/breaContinuekInInlineLambdaArgument.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simple.kt");
@@ -1135,6 +1140,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
public void testSimpleExtension() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simpleExtension.kt");
}
@TestMetadata("spillConstructorArgumentsAndInlineLambdaParameter.kt")
public void testSpillConstructorArgumentsAndInlineLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/spillConstructorArgumentsAndInlineLambdaParameter.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/contracts")
@@ -956,6 +956,11 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
runTest("compiler/testData/codegen/boxInline/complexStack/asCheck2.kt");
}
@TestMetadata("breaContinuekInInlineLambdaArgument.kt")
public void testBreaContinuekInInlineLambdaArgument() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/breaContinuekInInlineLambdaArgument.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simple.kt");
@@ -980,6 +985,11 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
public void testSimpleExtension() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simpleExtension.kt");
}
@TestMetadata("spillConstructorArgumentsAndInlineLambdaParameter.kt")
public void testSpillConstructorArgumentsAndInlineLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/spillConstructorArgumentsAndInlineLambdaParameter.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/contracts")
@@ -956,6 +956,11 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
runTest("compiler/testData/codegen/boxInline/complexStack/asCheck2.kt");
}
@TestMetadata("breaContinuekInInlineLambdaArgument.kt")
public void testBreaContinuekInInlineLambdaArgument() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/breaContinuekInInlineLambdaArgument.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simple.kt");
@@ -980,6 +985,11 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
public void testSimpleExtension() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/simpleExtension.kt");
}
@TestMetadata("spillConstructorArgumentsAndInlineLambdaParameter.kt")
public void testSpillConstructorArgumentsAndInlineLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/boxInline/complexStack/spillConstructorArgumentsAndInlineLambdaParameter.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/contracts")