Replace SourceInterpreter with interpreter, which track only

functional arguments.
This commit is contained in:
Ilmir Usmanov
2020-05-05 19:45:00 +02:00
parent 3173113cb3
commit 2c88844409
10 changed files with 187 additions and 93 deletions
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer
import org.jetbrains.kotlin.codegen.inline.coroutines.isSuspendLambdaCapturedByOuterObjectOrLambda
import org.jetbrains.kotlin.codegen.inline.coroutines.markNoinlineLambdaIfSuspend
import org.jetbrains.kotlin.codegen.inline.coroutines.surroundInvokesWithSuspendMarkersIfNeeded
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph
@@ -501,9 +500,9 @@ class MethodInliner(
replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode)
val sources = analyzeMethodNodeBeforeInline(processingNode)
val toDelete = SmartSet.create<AbstractInsnNode>()
val sources = analyzeMethodWithFunctionalArgumentInterpreter(this, processingNode, toDelete)
val instructions = processingNode.instructions
var awaitClassReification = false
@@ -530,9 +529,7 @@ class MethodInliner(
val firstParameterIndex = frame.stackSize - paramCount
if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
val sourceValue = frame.getStack(firstParameterIndex)
val functionalArgument =
getFunctionalArgumentIfExistsAndMarkInstructions(sourceValue, true, instructions, sources, toDelete)
invokeCalls.add(InvokeCall(functionalArgument, currentFinallyDeep))
invokeCalls.add(InvokeCall(sourceValue.functionalArgument, currentFinallyDeep))
} else if (isSamWrapperConstructorCall(owner, name)) {
recordTransformation(SamWrapperTransformationInfo(owner, inliningContext, isAlreadyRegenerated(owner)))
} else if (isAnonymousConstructorCall(owner, name)) {
@@ -542,11 +539,9 @@ class MethodInliner(
var capturesAnonymousObjectThatMustBeRegenerated = false
for (i in 0 until paramCount) {
val sourceValue = frame.getStack(firstParameterIndex + i)
val functionalArgument = getFunctionalArgumentIfExistsAndMarkInstructions(
sourceValue, false, instructions, sources, toDelete
)
val functionalArgument = sourceValue.functionalArgument
if (functionalArgument != null) {
functionalArgumentMapping.put(offset, functionalArgument)
functionalArgumentMapping[offset] = functionalArgument
} else if (i < argTypes.size && isAnonymousClassThatMustBeRegenerated(argTypes[i])) {
capturesAnonymousObjectThatMustBeRegenerated = true
}
@@ -556,7 +551,11 @@ class MethodInliner(
recordTransformation(
buildConstructorInvocation(
owner, cur.desc, functionalArgumentMapping, awaitClassReification, capturesAnonymousObjectThatMustBeRegenerated
owner,
cur.desc,
functionalArgumentMapping,
awaitClassReification,
capturesAnonymousObjectThatMustBeRegenerated
)
)
awaitClassReification = false
@@ -604,14 +603,8 @@ class MethodInliner(
}
}
cur.opcode == Opcodes.POP -> getFunctionalArgumentIfExistsAndMarkInstructions(
frame.top()!!,
true,
instructions,
sources,
toDelete
)?.let {
if (it is LambdaInfo) {
cur.opcode == Opcodes.POP -> {
if (frame.top().functionalArgument is LambdaInfo) {
toDelete.add(cur)
}
}
@@ -630,8 +623,7 @@ class MethodInliner(
nodeRemapper.originalLambdaInternalName == fieldInsn.owner
) {
val stackTransformations = mutableSetOf<AbstractInsnNode>()
val lambdaInfo =
getFunctionalArgumentIfExistsAndMarkInstructions(frame.peek(1)!!, false, instructions, sources, stackTransformations)
val lambdaInfo = frame.peek(1)?.functionalArgument
if (lambdaInfo is LambdaInfo && stackTransformations.all { it is VarInsnNode }) {
assert(lambdaInfo.lambdaClassType.internalName == nodeRemapper.originalLambdaInternalName) {
"Wrong bytecode template for contract template: ${lambdaInfo.lambdaClassType.internalName} != ${nodeRemapper.originalLambdaInternalName}"
@@ -1076,9 +1068,7 @@ class MethodInliner(
private fun removeClosureAssertions(node: MethodNode) {
val toDelete = arrayListOf<AbstractInsnNode>()
InsnSequence(node.instructions).filterIsInstance<MethodInsnNode>().forEach { methodInsnNode ->
if (methodInsnNode.owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
(methodInsnNode.name == "checkParameterIsNotNull" || methodInsnNode.name == "checkNotNullParameter")
) {
if (isParameterNullabilityCheck(methodInsnNode)) {
val prev = methodInsnNode.previous
assert(Opcodes.LDC == prev?.opcode) { "'${methodInsnNode.name}' should go after LDC but $prev" }
val prevPev = methodInsnNode.previous.previous
@@ -5,80 +5,13 @@
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.fixStack.top
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue
fun MethodInliner.getFunctionalArgumentIfExistsAndMarkInstructions(
sourceValue: SourceValue,
processSwap: Boolean,
insnList: InsnList,
frames: Array<Frame<SourceValue>?>,
toDelete: MutableSet<AbstractInsnNode>
): FunctionalArgument? {
val toDeleteInner = SmartSet.create<AbstractInsnNode>()
val functionalArgumentSet = SmartSet.create<FunctionalArgument?>()
sourceValue.insns.mapTo(functionalArgumentSet) {
getFunctionalArgumentIfExistsAndMarkInstructions(it, processSwap, insnList, frames, toDeleteInner)
}
return functionalArgumentSet.singleOrNull()?.also {
if (it is LambdaInfo) {
toDelete.addAll(toDeleteInner)
}
}
}
private fun SourceValue.singleOrNullInsn() = insns.singleOrNull()
private fun MethodInliner.getFunctionalArgumentIfExistsAndMarkInstructions(
insnNode: AbstractInsnNode?,
processSwap: Boolean,
insnList: InsnList,
frames: Array<Frame<SourceValue>?>,
toDelete: MutableSet<AbstractInsnNode>
): FunctionalArgument? {
if (insnNode == null) return null
getFunctionalArgumentIfExists(insnNode)?.let {
//delete lambda aload instruction
toDelete.add(insnNode)
return it
}
if (insnNode is VarInsnNode && insnNode.opcode == Opcodes.ALOAD) {
val varIndex = insnNode.`var`
val localFrame = frames[insnList.indexOf(insnNode)] ?: return null
val storeIns = localFrame.getLocal(varIndex).singleOrNullInsn()
if (storeIns is VarInsnNode && storeIns.getOpcode() == Opcodes.ASTORE) {
val frame = frames[insnList.indexOf(storeIns)] ?: return null
val topOfStack = frame.top()!!
getFunctionalArgumentIfExistsAndMarkInstructions(topOfStack, processSwap, insnList, frames, toDelete)?.let {
//remove intermediate lambda astore, aload instruction: see 'complexStack/simple.1.kt' test
toDelete.add(storeIns)
toDelete.add(insnNode)
return it
}
}
} else if (processSwap && insnNode.opcode == Opcodes.SWAP) {
val swapFrame = frames[insnList.indexOf(insnNode)] ?: return null
val dispatchReceiver = swapFrame.top()!!
getFunctionalArgumentIfExistsAndMarkInstructions(dispatchReceiver, false, insnList, frames, toDelete)?.let {
//remove swap instruction (dispatch receiver would be deleted on recursion call): see 'complexStack/simpleExtension.1.kt' test
toDelete.add(insnNode)
return it
}
}
return null
}
import org.jetbrains.org.objectweb.asm.tree.analysis.*
fun parameterOffsets(isStatic: Boolean, valueParameters: List<JvmMethodParameterSignature>): Array<Int> {
var nextOffset = if (isStatic) 0 else 1
@@ -121,4 +54,90 @@ fun AbstractInsnNode.getNextMeaningful(): AbstractInsnNode? {
result = result.next
}
return result
}
// Interpreter, that analyzes only functional arguments, to replace SourceInterpreter, since SourceInterpreter' merge has O(N²) complexity
internal open class FunctionalArgumentValue(
val functionalArgument: FunctionalArgument, basicValue: BasicValue?
) : BasicValue(basicValue?.type) {
override fun toString(): String = "$functionalArgument"
}
val BasicValue?.functionalArgument
get() = (this as? FunctionalArgumentValue)?.functionalArgument
private class FunctionalArgumentInterpreter(
private val inliner: MethodInliner, private val toDelete: MutableSet<AbstractInsnNode>
) : BasicInterpreter(Opcodes.ASM5) {
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)) {
return basicValue
}
if (value.functionalArgument is LambdaInfo) {
// SWAP and ASTORE
toDelete.add(insn)
return value
}
return markInstructionIfNeeded(insn, basicValue)
}
private fun markInstructionIfNeeded(
insn: AbstractInsnNode,
basicValue: BasicValue?
): BasicValue? {
val functionalArgument = inliner.getFunctionalArgumentIfExists(insn)
return if (functionalArgument != null) {
if (functionalArgument is LambdaInfo) {
toDelete.add(insn)
}
FunctionalArgumentValue(functionalArgument, basicValue)
} else basicValue
}
override fun newOperation(insn: AbstractInsnNode): BasicValue? =
markInstructionIfNeeded(insn, super.newOperation(insn))
override fun merge(v: BasicValue?, w: BasicValue?): BasicValue? =
if (v is FunctionalArgumentValue && w is FunctionalArgumentValue && v.functionalArgument == w.functionalArgument) v
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")
internal fun analyzeMethodWithFunctionalArgumentInterpreter(
inliner: MethodInliner,
node: MethodNode,
toDelete: MutableSet<AbstractInsnNode>
): Array<out Frame<BasicValue>?> {
val analyzer = object : Analyzer<BasicValue>(FunctionalArgumentInterpreter(inliner, toDelete)) {
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)
}
}
}
}
try {
return analyzer.analyze("fake", node)
} catch (e: AnalyzerException) {
throw RuntimeException(e)
}
}
@@ -0,0 +1,15 @@
// FILE: 1.kt
fun check1() = true
fun check2() = false
inline fun inlineMe1(fn: (String, String) -> String): String {
return fn(if (check1()) return "O" else "1", return "2")
}
inline fun inlineMe2(fn: (String) -> String): String {
return fn(if (check2()) return "3" else "K")
}
// FILE: 2.kt
fun box() = inlineMe1 { _, _ -> "FAIL1" } + inlineMe2 { it }
@@ -0,0 +1,10 @@
// FILE: 1.kt
inline fun alwaysOk(s: String, fn: (String) -> String): String {
return fn(try {return "OK"} catch (e: Exception) {
"fail1"
}) + fn(try {return "FF"} catch (e: Exception) {
"fail2"
})
}
// FILE: 2.kt
fun box() = alwaysOk("what?") { it }
@@ -3696,6 +3696,16 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt");
}
@TestMetadata("poppedLocalReturn.kt")
public void testPoppedLocalReturn() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn.kt");
}
@TestMetadata("poppedLocalReturn2.kt")
public void testPoppedLocalReturn2() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn2.kt");
}
@TestMetadata("returnLong.kt")
public void testReturnLong() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt");
@@ -3696,6 +3696,16 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt");
}
@TestMetadata("poppedLocalReturn.kt")
public void testPoppedLocalReturn() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn.kt");
}
@TestMetadata("poppedLocalReturn2.kt")
public void testPoppedLocalReturn2() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn2.kt");
}
@TestMetadata("returnLong.kt")
public void testReturnLong() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt");
@@ -3696,6 +3696,16 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt");
}
@TestMetadata("poppedLocalReturn.kt")
public void testPoppedLocalReturn() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn.kt");
}
@TestMetadata("poppedLocalReturn2.kt")
public void testPoppedLocalReturn2() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn2.kt");
}
@TestMetadata("returnLong.kt")
public void testReturnLong() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt");
@@ -3696,6 +3696,16 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt");
}
@TestMetadata("poppedLocalReturn.kt")
public void testPoppedLocalReturn() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn.kt");
}
@TestMetadata("poppedLocalReturn2.kt")
public void testPoppedLocalReturn2() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn2.kt");
}
@TestMetadata("returnLong.kt")
public void testReturnLong() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt");
@@ -3296,6 +3296,16 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
runTest("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt");
}
@TestMetadata("poppedLocalReturn.kt")
public void testPoppedLocalReturn() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn.kt");
}
@TestMetadata("poppedLocalReturn2.kt")
public void testPoppedLocalReturn2() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn2.kt");
}
@TestMetadata("returnLong.kt")
public void testReturnLong() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt");
@@ -3296,6 +3296,16 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt");
}
@TestMetadata("poppedLocalReturn.kt")
public void testPoppedLocalReturn() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn.kt");
}
@TestMetadata("poppedLocalReturn2.kt")
public void testPoppedLocalReturn2() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/poppedLocalReturn2.kt");
}
@TestMetadata("returnLong.kt")
public void testReturnLong() throws Exception {
runTest("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt");