JVM: surround captured lambdas with conditional suspension markers

This commit is contained in:
pyos
2020-04-03 14:03:39 +02:00
committed by Ilmir Usmanov
parent c650c9570f
commit 829343cf6f
10 changed files with 190 additions and 180 deletions
@@ -2633,15 +2633,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
} }
if (maybeSuspensionPoint) { if (maybeSuspensionPoint) {
addSuspendMarker(v, suspensionPointKind, true); addSuspendMarker(v, true, suspensionPointKind == SuspensionPointKind.NOT_INLINE);
} }
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this); callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
if (maybeSuspensionPoint) { if (maybeSuspensionPoint) {
addReturnsUnitMarkerIfNecessary(v, resolvedCall); addReturnsUnitMarkerIfNecessary(v, resolvedCall);
addSuspendMarker(v, false, suspensionPointKind == SuspensionPointKind.NOT_INLINE);
addSuspendMarker(v, suspensionPointKind, false);
addInlineMarker(v, false); addInlineMarker(v, false);
} }
@@ -144,8 +144,7 @@ class AnonymousObjectTransformer(
inliningContext, inliningContext,
classBuilder, classBuilder,
methodsToTransform, methodsToTransform,
superClassName, superClassName
allCapturedParamBuilder.listCaptured()
) )
loop@ for (next in methodsToTransform) { loop@ for (next in methodsToTransform) {
val deferringVisitor = val deferringVisitor =
@@ -526,7 +525,7 @@ class AnonymousObjectTransformer(
alreadyAddedParam?.newFieldName ?: getNewFieldName(desc.fieldName, false), alreadyAddedParam?.newFieldName ?: getNewFieldName(desc.fieldName, false),
alreadyAddedParam != null alreadyAddedParam != null
) )
if (info is ExpressionLambda && info.isCapturedSuspend(desc, inliningContext)) { if (info is ExpressionLambda && info.isCapturedSuspend(desc)) {
recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend
} }
val composed = StackValue.field( val composed = StackValue.field(
@@ -65,7 +65,10 @@ abstract class LambdaInfo(@JvmField val isCrossInline: Boolean) : FunctionalArgu
for (info in capturedVars) { for (info in capturedVars) {
val field = remapper.findField(FieldInsnNode(0, info.containingLambdaName, info.fieldName, "")) val field = remapper.findField(FieldInsnNode(0, info.containingLambdaName, info.fieldName, ""))
?: error("Captured field not found: " + info.containingLambdaName + "." + info.fieldName) ?: error("Captured field not found: " + info.containingLambdaName + "." + info.fieldName)
builder.addCapturedParam(field, info.fieldName) val recapturedParamInfo = builder.addCapturedParam(field, info.fieldName)
if (this is ExpressionLambda && isCapturedSuspend(info)) {
recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend
}
} }
return builder.buildParameters() return builder.buildParameters()
@@ -226,7 +229,7 @@ abstract class ExpressionLambda(isCrossInline: Boolean) : LambdaInfo(isCrossInli
} }
abstract fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor abstract fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor
abstract fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean abstract fun isCapturedSuspend(desc: CapturedParamDesc): Boolean
} }
class PsiExpressionLambda( class PsiExpressionLambda(
@@ -341,6 +344,6 @@ class PsiExpressionLambda(
) )
} }
override fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean = override fun isCapturedSuspend(desc: CapturedParamDesc): Boolean =
isCapturedSuspendLambda(closure, desc.fieldName, inliningContext.state.bindingContext) isCapturedSuspendLambda(closure, desc.fieldName, typeMapper.bindingContext)
} }
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationM
import org.jetbrains.kotlin.codegen.optimization.common.* import org.jetbrains.kotlin.codegen.optimization.common.*
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.fixStack.top import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor import org.jetbrains.kotlin.descriptors.ParameterDescriptor
@@ -216,10 +215,8 @@ class MethodInliner(
val info = invokeCall.functionalArgument val info = invokeCall.functionalArgument
if (info !is LambdaInfo) { if (info !is LambdaInfo) {
if (info == NonInlineableArgumentForInlineableSuspendParameter) {
super.visitMethodInsn(Opcodes.INVOKESTATIC, NOINLINE_CALL_MARKER, NOINLINE_CALL_MARKER, "()V", false)
}
//noninlinable lambda //noninlinable lambda
markNoinlineLambdaIfSuspend(mv, info)
super.visitMethodInsn(opcode, owner, name, desc, itf) super.visitMethodInsn(opcode, owner, name, desc, itf)
return return
} }
@@ -363,8 +360,6 @@ class MethodInliner(
ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false)) ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))
) { ) {
//we shouldn't process here content of inlining lambda it should be reified at external level except default lambdas //we shouldn't process here content of inlining lambda it should be reified at external level except default lambdas
} else if (owner == NOINLINE_CALL_MARKER && name == NOINLINE_CALL_MARKER) {
// do not generate multiple markers on single invoke
} else { } else {
super.visitMethodInsn(opcode, owner, name, desc, itf) super.visitMethodInsn(opcode, owner, name, desc, itf)
} }
@@ -385,21 +380,8 @@ class MethodInliner(
node.accept(lambdaInliner) node.accept(lambdaInliner)
return surroundInvokesWithSuspendMarkersIfNeeded(resultNode) surroundInvokesWithSuspendMarkersIfNeeded(resultNode)
} return resultNode
private fun surroundInvokesWithSuspendMarkersIfNeeded(node: MethodNode): MethodNode {
val markers = node.instructions.asSequence().filter { it.isNoinlineCallMarker() }.toList()
if (markers.isEmpty()) return node
val invokes = markers.map { it.next as MethodInsnNode }
node.instructions.removeAll(markers)
val sourceFrames = MethodTransformer.analyze(inlineCallSiteInfo.ownerClassName, node, SourceInterpreter())
val toSurround = invokes.mapNotNull { insn ->
findReceiverOfInvoke(sourceFrames[node.instructions.indexOf(insn)], insn)?.let { insn to it }
}
surroundInvokesWithSuspendMarkers(node, toSurround)
return node
} }
private fun isDefaultLambdaWithReification(lambdaInfo: LambdaInfo) = private fun isDefaultLambdaWithReification(lambdaInfo: LambdaInfo) =
@@ -901,6 +883,8 @@ class MethodInliner(
getFunctionalArgumentIfExists((insnNode as VarInsnNode).`var`) getFunctionalArgumentIfExists((insnNode as VarInsnNode).`var`)
insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) -> insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) ->
findCapturedField(insnNode, nodeRemapper).functionalArgument findCapturedField(insnNode, nodeRemapper).functionalArgument
insnNode is FieldInsnNode && insnNode.isSuspendLambdaCapturedByOuterObjectOrLambda(inliningContext) ->
NonInlineableArgumentForInlineableParameterCalledInSuspend
else -> else ->
null null
} }
@@ -6,36 +6,28 @@
package org.jetbrains.kotlin.codegen.inline.coroutines package org.jetbrains.kotlin.codegen.inline.coroutines
import com.intellij.util.ArrayUtil import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.codegen.AsmUtil.CAPTURED_THIS_FIELD
import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
import org.jetbrains.kotlin.codegen.coroutines.* import org.jetbrains.kotlin.codegen.coroutines.*
import org.jetbrains.kotlin.codegen.inline.* import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.optimization.common.asSequence import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.findPreviousOrNull
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer
import org.jetbrains.kotlin.config.isReleaseCoroutines import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.tree.* import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue
const val NOINLINE_CALL_MARKER = "NOINLINE_CALL_MARKER"
const val FOR_INLINE_SUFFIX = "\$\$forInline" const val FOR_INLINE_SUFFIX = "\$\$forInline"
class CoroutineTransformer( class CoroutineTransformer(
private val inliningContext: InliningContext, private val inliningContext: InliningContext,
private val classBuilder: ClassBuilder, private val classBuilder: ClassBuilder,
private val methods: List<MethodNode>, private val methods: List<MethodNode>,
private val superClassName: String, private val superClassName: String
private val capturedParams: List<CapturedParamInfo>
) { ) {
private val state = inliningContext.state private val state = inliningContext.state
// If we inline into inline function, we should generate both method with state-machine for Java interop and method without // If we inline into inline function, we should generate both method with state-machine for Java interop and method without
@@ -89,21 +81,18 @@ class CoroutineTransformer(
) )
) { ) {
val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline
val stateMachineBuilder = surroundNoinlineCallsWithMarkers( val stateMachineBuilder = CoroutineTransformerMethodVisitor(
node, createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
CoroutineTransformerMethodVisitor( obtainClassBuilderForCoroutineState = { classBuilder },
createNewMethodFrom(node, name), node.access, name, node.desc, null, null, reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
obtainClassBuilderForCoroutineState = { classBuilder }, // TODO: this linenumbers might not be correct and since they are used only for step-over, check them.
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) }, lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber,
// TODO: this linenumbers might not be correct and since they are used only for step-over, check them. sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "",
lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber, languageVersionSettings = state.languageVersionSettings,
sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "", shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
languageVersionSettings = state.languageVersionSettings, containingClassInternalName = classBuilder.thisName,
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization, isForNamedFunction = false,
containingClassInternalName = classBuilder.thisName, disableTailCallOptimizationForFunctionReturningUnit = false
isForNamedFunction = false,
disableTailCallOptimizationForFunctionReturningUnit = false
)
) )
if (generateForInline) if (generateForInline)
@@ -126,23 +115,20 @@ class CoroutineTransformer(
// If the node already has state-machine, it is safer to generate state-machine. // If the node already has state-machine, it is safer to generate state-machine.
val disableTailCallOptimization = methods.find { it.name == name && it.desc == node.desc }?.let { isStateMachine(it) } ?: false val disableTailCallOptimization = methods.find { it.name == name && it.desc == node.desc }?.let { isStateMachine(it) } ?: false
val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline
val stateMachineBuilder = surroundNoinlineCallsWithMarkers( val stateMachineBuilder = CoroutineTransformerMethodVisitor(
node, createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
CoroutineTransformerMethodVisitor( obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
createNewMethodFrom(node, name), node.access, name, node.desc, null, null, reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! }, lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber,
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) }, sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "",
lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber, languageVersionSettings = state.languageVersionSettings,
sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "", shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
languageVersionSettings = state.languageVersionSettings, containingClassInternalName = classBuilder.thisName,
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization, isForNamedFunction = true,
containingClassInternalName = classBuilder.thisName, needDispatchReceiver = true,
isForNamedFunction = true, internalNameForDispatchReceiver = classBuilder.thisName,
needDispatchReceiver = true, disableTailCallOptimizationForFunctionReturningUnit = disableTailCallOptimization,
internalNameForDispatchReceiver = classBuilder.thisName, putContinuationParameterToLvt = !state.isIrBackend
disableTailCallOptimizationForFunctionReturningUnit = disableTailCallOptimization,
putContinuationParameterToLvt = !state.isIrBackend
)
) )
if (generateForInline) if (generateForInline)
@@ -152,35 +138,6 @@ class CoroutineTransformer(
} }
} }
private fun surroundNoinlineCallsWithMarkers(node: MethodNode, delegate: MethodVisitor): MethodVisitor =
SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor(
delegate, node.access, node.name, node.desc, classBuilder.thisName, this::fieldIsCapturedSuspendLambda
)
private fun fieldIsCapturedSuspendLambda(field: FieldInsnNode): Boolean =
capturedParams.find { it.newFieldName == field.name }?.let { it.functionalArgument?.isSuspendLambda() == true }
?: isSuspendLambdaCapturedByOuterObjectOrLambda(field)
// We cannot find the lambda in captured parameters: it came from object outside of the our reach:
// this can happen when the lambda capture by non-transformed closure:
// inline fun inlineMe(crossinline c: suspend() -> Unit) = suspend { c() }
// inline fun inlineMe2(crossinline c: suspend() -> Unit) = suspend { inlineMe { c() }() }
// Suppose, we inline inlineMe into inlineMe2: the only knowledge we have about inlineMe$1 is captured receiver (this$0)
// Thus, transformed lambda from inlineMe, inlineMe3$$inlined$inlineMe2$1 contains the following bytecode
// ALOAD 0
// GETFIELD inlineMe2$1$invokeSuspend$$inlined$inlineMe$1.this$0 : LScratchKt$inlineMe2$1;
// GETFIELD inlineMe2$1.$c : Lkotlin/jvm/functions/Function1;
// Since inlineMe2's lambda is outside of reach of the inliner, find crossinline parameter from compilation context:
private fun isSuspendLambdaCapturedByOuterObjectOrLambda(field: FieldInsnNode): Boolean {
val functionDescriptor = inliningContext.root.sourceCompilerForInline.compilationContextFunctionDescriptor
val classDescriptor = functionDescriptor.findContainingClassOrLambda() ?: return false
return isCapturedSuspendLambda(classDescriptor, field.name, inliningContext.state.bindingContext)
}
private tailrec fun DeclarationDescriptor.findContainingClassOrLambda(): ClassDescriptor? =
if (containingDeclaration is ClassDescriptor) containingDeclaration as ClassDescriptor
else containingDeclaration?.findContainingClassOrLambda()
private fun createNewMethodFrom(node: MethodNode, name: String): MethodVisitor { private fun createNewMethodFrom(node: MethodNode, name: String): MethodVisitor {
return classBuilder.newMethod( return classBuilder.newMethod(
JvmDeclarationOrigin.NO_ORIGIN, node.access, name, node.desc, node.signature, ArrayUtil.toStringArray(node.exceptions) JvmDeclarationOrigin.NO_ORIGIN, node.access, name, node.desc, node.signature, ArrayUtil.toStringArray(node.exceptions)
@@ -219,77 +176,71 @@ class CoroutineTransformer(
} }
} }
class SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor( private const val NOINLINE_CALL_MARKER = "\$\$\$\$\$NOINLINE_CALL_MARKER\$\$\$\$\$"
delegate: MethodVisitor, access: Int, name: String, desc: String,
private val thisName: String,
private val isCapturedSuspendLambda: (FieldInsnNode) -> Boolean
) : TransformationMethodVisitor(delegate, access, name, desc, null, null) {
override fun performTransformations(methodNode: MethodNode) {
fun AbstractInsnNode.index() = methodNode.instructions.indexOf(this)
FixStackMethodTransformer().transform(thisName, methodNode) fun markNoinlineLambdaIfSuspend(mv: MethodVisitor, info: FunctionalArgument?) {
val sourceFrames = MethodTransformer.analyze(thisName, methodNode, SourceInterpreter()) when (info) {
NonInlineableArgumentForInlineableSuspendParameter ->
val noinlineInvokes = arrayListOf<Pair<AbstractInsnNode, AbstractInsnNode>>() mv.visitMethodInsn(Opcodes.INVOKESTATIC, NOINLINE_CALL_MARKER, "always", "()V", false)
NonInlineableArgumentForInlineableParameterCalledInSuspend ->
for (insn in methodNode.instructions.asSequence()) { mv.visitMethodInsn(Opcodes.INVOKESTATIC, NOINLINE_CALL_MARKER, "conditional", "()V", false)
if (insn.opcode != Opcodes.INVOKEINTERFACE) continue
insn as MethodInsnNode
if (!isInvokeOnLambda(insn.owner, insn.name)) continue
val frame = sourceFrames[insn.index()] ?: continue
val receiver = findReceiverOfInvoke(frame, insn).takeIf { it?.isSuspendLambda(insn) == true } as? FieldInsnNode ?: continue
val aload = receiver.findPreviousOrNull { it.opcode != Opcodes.GETFIELD } ?: error("GETFIELD cannot be the first instruction")
assert(aload.opcode == Opcodes.ALOAD) { "Before GETFIELD there shall be ALOAD" }
noinlineInvokes.add(insn to aload)
}
surroundInvokesWithSuspendMarkers(methodNode, noinlineInvokes)
}
private fun AbstractInsnNode.isSuspendLambda(invoke: MethodInsnNode): Boolean {
if (opcode != Opcodes.GETFIELD) return false
this as FieldInsnNode
if (desc != "L${invoke.owner};") return false
var current: FieldInsnNode? = this
// Unroll the battery of
// GETFIELD <outer1>.this$0 L<outer2>;
// GETFIELD <outer2>.this$0 L<outer3>;
// ...
// GETFIELD <outerN>.$action Lkotlin/jvm/functions/FunctionM;
while (current != null) {
if (current.owner == thisName) break
if (current.previous?.opcode != Opcodes.GETFIELD || current.previous.cast<FieldInsnNode>().name != CAPTURED_THIS_FIELD) return false
current = current.previous as FieldInsnNode
}
return isCapturedSuspendLambda(this)
} }
} }
private fun FunctionalArgument.isSuspendLambda(): Boolean = private fun Frame<SourceValue>.getSource(offset: Int): AbstractInsnNode? =
this is NonInlineableArgumentForInlineableParameterCalledInSuspend || (this is ExpressionLambda && isSuspend) getStack(stackSize - offset - 1)?.insns?.singleOrNull()
fun surroundInvokesWithSuspendMarkers( fun surroundInvokesWithSuspendMarkersIfNeeded(node: MethodNode) {
methodNode: MethodNode, val markers = node.instructions.asSequence().filter {
noinlineInvokes: List<Pair<AbstractInsnNode, AbstractInsnNode>> it.opcode == Opcodes.INVOKESTATIC && (it as MethodInsnNode).owner == NOINLINE_CALL_MARKER
) { }.toList()
for ((invoke, aload) in noinlineInvokes) { if (markers.isEmpty()) return
// Generate inline markers for stack transformation. It is required for local variables spilling.
methodNode.instructions.insertBefore(aload, withInstructionAdapter { val sourceFrames = MethodTransformer.analyze("fake", node, SourceInterpreter())
val loads = markers.map { marker ->
val arity = (marker.next as MethodInsnNode).owner.removePrefix(NUMBERED_FUNCTION_PREFIX).toInt()
var receiver = sourceFrames[node.instructions.indexOf(marker) + 1].getSource(arity)
// Navigate the ALOAD+GETFIELD+... chain to the first instruction. We need to insert a stack
// spilling marker before it starts.
while (receiver?.opcode == Opcodes.GETFIELD) {
receiver = sourceFrames[node.instructions.indexOf(receiver)].getSource(0)
}
receiver
}
for ((marker, load) in markers.zip(loads)) {
val conditional = (marker as MethodInsnNode).name == "conditional"
val invoke = marker.next as MethodInsnNode
node.instructions.remove(marker)
if (load == null) {
continue // dead code, doesn't matter
}
node.instructions.insertBefore(load, withInstructionAdapter {
addInlineMarker(this, isStartNotEnd = true) addInlineMarker(this, isStartNotEnd = true)
}) })
methodNode.instructions.insertBefore(invoke, withInstructionAdapter { node.instructions.insertBefore(invoke, withInstructionAdapter {
addSuspendMarker(this, isStartNotEnd = true) addSuspendMarker(this, isStartNotEnd = true, inlinable = conditional)
}) })
methodNode.instructions.insert(invoke, withInstructionAdapter { node.instructions.insert(invoke, withInstructionAdapter {
addSuspendMarker(this, isStartNotEnd = false) addSuspendMarker(this, isStartNotEnd = false, inlinable = conditional)
addInlineMarker(this, isStartNotEnd = false) addInlineMarker(this, isStartNotEnd = false)
}) })
} }
} }
// TODO: What to do if suddenly there are not exactly one receiver? // We cannot find the lambda in captured parameters: it came from object outside of the our reach:
fun findReceiverOfInvoke(frame: Frame<SourceValue>, insn: MethodInsnNode): AbstractInsnNode? = // this can happen when the lambda capture by non-transformed closure:
frame.getStack(frame.stackSize - insn.owner.removePrefix(NUMBERED_FUNCTION_PREFIX).toInt() - 1)?.insns?.singleOrNull() // inline fun inlineMe(crossinline c: suspend() -> Unit) = suspend { c() }
// inline fun inlineMe2(crossinline c: suspend() -> Unit) = suspend { inlineMe { c() }() }
fun AbstractInsnNode.isNoinlineCallMarker(): Boolean = // Suppose, we inline inlineMe into inlineMe2: the only knowledge we have about inlineMe$1 is captured receiver (this$0)
opcode == Opcodes.INVOKESTATIC && cast<MethodInsnNode>().let { it.owner == NOINLINE_CALL_MARKER && it.name == NOINLINE_CALL_MARKER } // Thus, transformed lambda from inlineMe, inlineMe3$$inlined$inlineMe2$1 contains the following bytecode
// ALOAD 0
// GETFIELD inlineMe2$1$invokeSuspend$$inlined$inlineMe$1.this$0 : LScratchKt$inlineMe2$1;
// GETFIELD inlineMe2$1.$c : Lkotlin/jvm/functions/Function1;
// Since inlineMe2's lambda is outside of reach of the inliner, find crossinline parameter from compilation context:
fun FieldInsnNode.isSuspendLambdaCapturedByOuterObjectOrLambda(inliningContext: InliningContext): Boolean {
var container: DeclarationDescriptor = inliningContext.root.sourceCompilerForInline.compilationContextFunctionDescriptor
while (container !is ClassDescriptor) {
container = container.containingDeclaration ?: return false
}
return isCapturedSuspendLambda(container, name, inliningContext.state.bindingContext)
}
@@ -18,7 +18,6 @@ import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.CodegenContextUtil import org.jetbrains.kotlin.codegen.context.CodegenContextUtil
import org.jetbrains.kotlin.codegen.context.InlineLambdaContext import org.jetbrains.kotlin.codegen.context.InlineLambdaContext
import org.jetbrains.kotlin.codegen.context.MethodContext import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.coroutines.SuspensionPointKind
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence import org.jetbrains.kotlin.codegen.optimization.common.asSequence
@@ -432,18 +431,14 @@ fun addReturnsUnitMarker(v: InstructionAdapter) {
v.emitInlineMarker(INLINE_MARKER_RETURNS_UNIT) v.emitInlineMarker(INLINE_MARKER_RETURNS_UNIT)
} }
fun addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) { fun addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean, inlinable: Boolean = false) {
v.emitInlineMarker(if (isStartNotEnd) INLINE_MARKER_BEFORE_SUSPEND_ID else INLINE_MARKER_AFTER_SUSPEND_ID) val marker = when {
} inlinable && isStartNotEnd -> INLINE_MARKER_BEFORE_INLINE_SUSPEND_ID
inlinable -> INLINE_MARKER_AFTER_INLINE_SUSPEND_ID
fun addInlineSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) { isStartNotEnd -> INLINE_MARKER_BEFORE_SUSPEND_ID
v.emitInlineMarker(if (isStartNotEnd) INLINE_MARKER_BEFORE_INLINE_SUSPEND_ID else INLINE_MARKER_AFTER_INLINE_SUSPEND_ID) else -> INLINE_MARKER_AFTER_SUSPEND_ID
} }
v.emitInlineMarker(marker)
fun addSuspendMarker(v: InstructionAdapter, kind: SuspensionPointKind, isStartNotEnd: Boolean) = when (kind) {
SuspensionPointKind.NEVER -> Unit
SuspensionPointKind.NOT_INLINE -> addInlineSuspendMarker(v, isStartNotEnd)
SuspensionPointKind.ALWAYS -> addSuspendMarker(v, isStartNotEnd)
} }
fun addFakeContinuationConstructorCallMarker(v: InstructionAdapter, isStartNotEnd: Boolean) { fun addFakeContinuationConstructorCallMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
@@ -377,6 +377,10 @@ class ExpressionCodegen(
val asmType = if (expression is IrConstructorCall) typeMapper.mapTypeAsDeclaration(expression.type) else expression.asmType val asmType = if (expression is IrConstructorCall) typeMapper.mapTypeAsDeclaration(expression.type) else expression.asmType
val isSuspensionPoint = expression.isSuspensionPoint() val isSuspensionPoint = expression.isSuspensionPoint()
if (isSuspensionPoint != SuspensionPointKind.NEVER) {
addInlineMarker(mv, isStartNotEnd = true)
}
when { when {
expression is IrConstructorCall -> { expression is IrConstructorCall -> {
closureReifiedMarkers[expression.symbol.owner.parentAsClass]?.let { closureReifiedMarkers[expression.symbol.owner.parentAsClass]?.let {
@@ -404,8 +408,6 @@ class ExpressionCodegen(
} }
expression.symbol.descriptor is ConstructorDescriptor -> expression.symbol.descriptor is ConstructorDescriptor ->
throw AssertionError("IrCall with ConstructorDescriptor: ${expression.javaClass.simpleName}") throw AssertionError("IrCall with ConstructorDescriptor: ${expression.javaClass.simpleName}")
isSuspensionPoint != SuspensionPointKind.NEVER ->
addInlineMarker(mv, isStartNotEnd = true)
} }
expression.dispatchReceiver?.let { receiver -> expression.dispatchReceiver?.let { receiver ->
@@ -430,7 +432,7 @@ class ExpressionCodegen(
expression.markLineNumber(true) expression.markLineNumber(true)
if (isSuspensionPoint != SuspensionPointKind.NEVER) { if (isSuspensionPoint != SuspensionPointKind.NEVER) {
addSuspendMarker(mv, isSuspensionPoint, isStartNotEnd = true) addSuspendMarker(mv, isStartNotEnd = true, isSuspensionPoint == SuspensionPointKind.NOT_INLINE)
} }
if (irFunction.isInvokeSuspendOfContinuation()) { if (irFunction.isInvokeSuspendOfContinuation()) {
@@ -443,7 +445,7 @@ class ExpressionCodegen(
} }
if (isSuspensionPoint != SuspensionPointKind.NEVER) { if (isSuspensionPoint != SuspensionPointKind.NEVER) {
addSuspendMarker(mv, isSuspensionPoint, isStartNotEnd = false) addSuspendMarker(mv, isStartNotEnd = false, isSuspensionPoint == SuspensionPointKind.NOT_INLINE)
addInlineMarker(mv, isStartNotEnd = false) addInlineMarker(mv, isStartNotEnd = false)
} }
@@ -250,7 +250,7 @@ class IrExpressionLambdaImpl(
override fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor = function.descriptor override fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor = function.descriptor
override fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean = override fun isCapturedSuspend(desc: CapturedParamDesc): Boolean =
capturedParameters[desc]?.let { it.isInlineParameter() && it.type.isSuspendFunctionTypeOrSubtype() } == true capturedParameters[desc]?.let { it.isInlineParameter() && it.type.isSuspendFunctionTypeOrSubtype() } == true
} }
@@ -68,11 +68,33 @@ public final class TcoContinuationKt$foo$$inlined$flow$1 {
public @org.jetbrains.annotations.Nullable method collect(@org.jetbrains.annotations.NotNull p0: FlowCollector, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object public @org.jetbrains.annotations.Nullable method collect(@org.jetbrains.annotations.NotNull p0: FlowCollector, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object
} }
@kotlin.Metadata
public final class TcoContinuationKt$foo$$inlined$map$1$2$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field L$5: java.lang.Object
field L$6: java.lang.Object
field L$7: java.lang.Object
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
synthetic final field this$0: TcoContinuationKt$foo$$inlined$map$1$2
inner class TcoContinuationKt$foo$$inlined$map$1$2
inner class TcoContinuationKt$foo$$inlined$map$1$2$1
public method <init>(p0: TcoContinuationKt$foo$$inlined$map$1$2, p1: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
synthetic final method getLabel(): int
synthetic final method setLabel(p0: int): void
}
@kotlin.Metadata @kotlin.Metadata
public final class TcoContinuationKt$foo$$inlined$map$1$2 { public final class TcoContinuationKt$foo$$inlined$map$1$2 {
synthetic final field $this_flow$inlined: FlowCollector synthetic final field $this_flow$inlined: FlowCollector
synthetic final field this$0: TcoContinuationKt$foo$$inlined$map$1 synthetic final field this$0: TcoContinuationKt$foo$$inlined$map$1
inner class TcoContinuationKt$foo$$inlined$map$1$2 inner class TcoContinuationKt$foo$$inlined$map$1$2
inner class TcoContinuationKt$foo$$inlined$map$1$2$1
public method <init>(p0: FlowCollector, p1: TcoContinuationKt$foo$$inlined$map$1): void public method <init>(p0: FlowCollector, p1: TcoContinuationKt$foo$$inlined$map$1): void
public @org.jetbrains.annotations.Nullable method emit(p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object public @org.jetbrains.annotations.Nullable method emit(p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object
} }
@@ -100,6 +122,14 @@ public final class TcoContinuationKt$map$$inlined$transform$1$1 {
@kotlin.Metadata @kotlin.Metadata
public final class TcoContinuationKt$map$$inlined$transform$1$2$1 { public final class TcoContinuationKt$map$$inlined$transform$1$2$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field L$5: java.lang.Object
field L$6: java.lang.Object
field L$7: java.lang.Object
synthetic field data: java.lang.Object synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable synthetic field exception: java.lang.Throwable
synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$1$2 synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$1$2
@@ -148,6 +178,14 @@ public final class TcoContinuationKt$map$$inlined$transform$2$1 {
@kotlin.Metadata @kotlin.Metadata
public final class TcoContinuationKt$map$$inlined$transform$2$2$1 { public final class TcoContinuationKt$map$$inlined$transform$2$2$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field L$5: java.lang.Object
field L$6: java.lang.Object
field L$7: java.lang.Object
synthetic field data: java.lang.Object synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable synthetic field exception: java.lang.Throwable
synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$2$2 synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$2$2
@@ -64,11 +64,32 @@ public final class TcoContinuationKt$foo$$inlined$flow$1 {
public @org.jetbrains.annotations.Nullable method collect(@org.jetbrains.annotations.NotNull p0: FlowCollector, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object public @org.jetbrains.annotations.Nullable method collect(@org.jetbrains.annotations.NotNull p0: FlowCollector, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
} }
@kotlin.Metadata
@kotlin.coroutines.jvm.internal.DebugMetadata
public final class TcoContinuationKt$foo$$inlined$map$1$2$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field L$5: java.lang.Object
field L$6: java.lang.Object
field L$7: java.lang.Object
field label: int
synthetic field result: java.lang.Object
synthetic final field this$0: TcoContinuationKt$foo$$inlined$map$1$2
inner class TcoContinuationKt$foo$$inlined$map$1$2
inner class TcoContinuationKt$foo$$inlined$map$1$2$1
public method <init>(p0: TcoContinuationKt$foo$$inlined$map$1$2, p1: kotlin.coroutines.Continuation): void
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata @kotlin.Metadata
public final class TcoContinuationKt$foo$$inlined$map$1$2 { public final class TcoContinuationKt$foo$$inlined$map$1$2 {
synthetic final field $this_flow$inlined: FlowCollector synthetic final field $this_flow$inlined: FlowCollector
synthetic final field this$0: TcoContinuationKt$foo$$inlined$map$1 synthetic final field this$0: TcoContinuationKt$foo$$inlined$map$1
inner class TcoContinuationKt$foo$$inlined$map$1$2 inner class TcoContinuationKt$foo$$inlined$map$1$2
inner class TcoContinuationKt$foo$$inlined$map$1$2$1
public method <init>(p0: FlowCollector, p1: TcoContinuationKt$foo$$inlined$map$1): void public method <init>(p0: FlowCollector, p1: TcoContinuationKt$foo$$inlined$map$1): void
public @org.jetbrains.annotations.Nullable method emit(p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object public @org.jetbrains.annotations.Nullable method emit(p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
} }
@@ -93,7 +114,16 @@ public final class TcoContinuationKt$map$$inlined$transform$1$1 {
} }
@kotlin.Metadata @kotlin.Metadata
@kotlin.coroutines.jvm.internal.DebugMetadata
public final class TcoContinuationKt$map$$inlined$transform$1$2$1 { public final class TcoContinuationKt$map$$inlined$transform$1$2$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field L$5: java.lang.Object
field L$6: java.lang.Object
field L$7: java.lang.Object
field label: int field label: int
synthetic field result: java.lang.Object synthetic field result: java.lang.Object
synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$1$2 synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$1$2
@@ -137,7 +167,16 @@ public final class TcoContinuationKt$map$$inlined$transform$2$1 {
} }
@kotlin.Metadata @kotlin.Metadata
@kotlin.coroutines.jvm.internal.DebugMetadata
public final class TcoContinuationKt$map$$inlined$transform$2$2$1 { public final class TcoContinuationKt$map$$inlined$transform$2$2$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field L$5: java.lang.Object
field L$6: java.lang.Object
field L$7: java.lang.Object
field label: int field label: int
synthetic field result: java.lang.Object synthetic field result: java.lang.Object
synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$2$2 synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$2$2