JVM: surround captured lambdas with conditional suspension markers
This commit is contained in:
@@ -2633,15 +2633,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
if (maybeSuspensionPoint) {
|
||||
addSuspendMarker(v, suspensionPointKind, true);
|
||||
addSuspendMarker(v, true, suspensionPointKind == SuspensionPointKind.NOT_INLINE);
|
||||
}
|
||||
|
||||
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
|
||||
|
||||
if (maybeSuspensionPoint) {
|
||||
addReturnsUnitMarkerIfNecessary(v, resolvedCall);
|
||||
|
||||
addSuspendMarker(v, suspensionPointKind, false);
|
||||
addSuspendMarker(v, false, suspensionPointKind == SuspensionPointKind.NOT_INLINE);
|
||||
addInlineMarker(v, false);
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -144,8 +144,7 @@ class AnonymousObjectTransformer(
|
||||
inliningContext,
|
||||
classBuilder,
|
||||
methodsToTransform,
|
||||
superClassName,
|
||||
allCapturedParamBuilder.listCaptured()
|
||||
superClassName
|
||||
)
|
||||
loop@ for (next in methodsToTransform) {
|
||||
val deferringVisitor =
|
||||
@@ -526,7 +525,7 @@ class AnonymousObjectTransformer(
|
||||
alreadyAddedParam?.newFieldName ?: getNewFieldName(desc.fieldName, false),
|
||||
alreadyAddedParam != null
|
||||
)
|
||||
if (info is ExpressionLambda && info.isCapturedSuspend(desc, inliningContext)) {
|
||||
if (info is ExpressionLambda && info.isCapturedSuspend(desc)) {
|
||||
recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend
|
||||
}
|
||||
val composed = StackValue.field(
|
||||
|
||||
@@ -65,7 +65,10 @@ abstract class LambdaInfo(@JvmField val isCrossInline: Boolean) : FunctionalArgu
|
||||
for (info in capturedVars) {
|
||||
val field = remapper.findField(FieldInsnNode(0, 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()
|
||||
@@ -226,7 +229,7 @@ abstract class ExpressionLambda(isCrossInline: Boolean) : LambdaInfo(isCrossInli
|
||||
}
|
||||
|
||||
abstract fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor
|
||||
abstract fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean
|
||||
abstract fun isCapturedSuspend(desc: CapturedParamDesc): Boolean
|
||||
}
|
||||
|
||||
class PsiExpressionLambda(
|
||||
@@ -341,6 +344,6 @@ class PsiExpressionLambda(
|
||||
)
|
||||
}
|
||||
|
||||
override fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean =
|
||||
isCapturedSuspendLambda(closure, desc.fieldName, inliningContext.state.bindingContext)
|
||||
override fun isCapturedSuspend(desc: CapturedParamDesc): Boolean =
|
||||
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.fixStack.peek
|
||||
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.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
|
||||
@@ -216,10 +215,8 @@ class MethodInliner(
|
||||
val info = invokeCall.functionalArgument
|
||||
|
||||
if (info !is LambdaInfo) {
|
||||
if (info == NonInlineableArgumentForInlineableSuspendParameter) {
|
||||
super.visitMethodInsn(Opcodes.INVOKESTATIC, NOINLINE_CALL_MARKER, NOINLINE_CALL_MARKER, "()V", false)
|
||||
}
|
||||
//noninlinable lambda
|
||||
markNoinlineLambdaIfSuspend(mv, info)
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf)
|
||||
return
|
||||
}
|
||||
@@ -363,8 +360,6 @@ class MethodInliner(
|
||||
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
|
||||
} else if (owner == NOINLINE_CALL_MARKER && name == NOINLINE_CALL_MARKER) {
|
||||
// do not generate multiple markers on single invoke
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf)
|
||||
}
|
||||
@@ -385,21 +380,8 @@ class MethodInliner(
|
||||
|
||||
node.accept(lambdaInliner)
|
||||
|
||||
return surroundInvokesWithSuspendMarkersIfNeeded(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
|
||||
surroundInvokesWithSuspendMarkersIfNeeded(resultNode)
|
||||
return resultNode
|
||||
}
|
||||
|
||||
private fun isDefaultLambdaWithReification(lambdaInfo: LambdaInfo) =
|
||||
@@ -901,6 +883,8 @@ class MethodInliner(
|
||||
getFunctionalArgumentIfExists((insnNode as VarInsnNode).`var`)
|
||||
insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) ->
|
||||
findCapturedField(insnNode, nodeRemapper).functionalArgument
|
||||
insnNode is FieldInsnNode && insnNode.isSuspendLambdaCapturedByOuterObjectOrLambda(inliningContext) ->
|
||||
NonInlineableArgumentForInlineableParameterCalledInSuspend
|
||||
else ->
|
||||
null
|
||||
}
|
||||
|
||||
+82
-131
@@ -6,36 +6,28 @@
|
||||
package org.jetbrains.kotlin.codegen.inline.coroutines
|
||||
|
||||
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.TransformationMethodVisitor
|
||||
import org.jetbrains.kotlin.codegen.coroutines.*
|
||||
import org.jetbrains.kotlin.codegen.inline.*
|
||||
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.fixStack.FixStackMethodTransformer
|
||||
import org.jetbrains.kotlin.config.isReleaseCoroutines
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
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.tree.*
|
||||
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.SourceValue
|
||||
|
||||
const val NOINLINE_CALL_MARKER = "NOINLINE_CALL_MARKER"
|
||||
|
||||
const val FOR_INLINE_SUFFIX = "\$\$forInline"
|
||||
|
||||
class CoroutineTransformer(
|
||||
private val inliningContext: InliningContext,
|
||||
private val classBuilder: ClassBuilder,
|
||||
private val methods: List<MethodNode>,
|
||||
private val superClassName: String,
|
||||
private val capturedParams: List<CapturedParamInfo>
|
||||
private val superClassName: String
|
||||
) {
|
||||
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
|
||||
@@ -89,21 +81,18 @@ class CoroutineTransformer(
|
||||
)
|
||||
) {
|
||||
val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline
|
||||
val stateMachineBuilder = surroundNoinlineCallsWithMarkers(
|
||||
node,
|
||||
CoroutineTransformerMethodVisitor(
|
||||
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { classBuilder },
|
||||
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
|
||||
// TODO: this linenumbers might not be correct and since they are used only for step-over, check them.
|
||||
lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber,
|
||||
sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "",
|
||||
languageVersionSettings = state.languageVersionSettings,
|
||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = classBuilder.thisName,
|
||||
isForNamedFunction = false,
|
||||
disableTailCallOptimizationForFunctionReturningUnit = false
|
||||
)
|
||||
val stateMachineBuilder = CoroutineTransformerMethodVisitor(
|
||||
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { classBuilder },
|
||||
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
|
||||
// TODO: this linenumbers might not be correct and since they are used only for step-over, check them.
|
||||
lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber,
|
||||
sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "",
|
||||
languageVersionSettings = state.languageVersionSettings,
|
||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = classBuilder.thisName,
|
||||
isForNamedFunction = false,
|
||||
disableTailCallOptimizationForFunctionReturningUnit = false
|
||||
)
|
||||
|
||||
if (generateForInline)
|
||||
@@ -126,23 +115,20 @@ class CoroutineTransformer(
|
||||
// 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 sourceCompilerForInline = inliningContext.root.sourceCompilerForInline
|
||||
val stateMachineBuilder = surroundNoinlineCallsWithMarkers(
|
||||
node,
|
||||
CoroutineTransformerMethodVisitor(
|
||||
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
|
||||
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
|
||||
lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber,
|
||||
sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "",
|
||||
languageVersionSettings = state.languageVersionSettings,
|
||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = classBuilder.thisName,
|
||||
isForNamedFunction = true,
|
||||
needDispatchReceiver = true,
|
||||
internalNameForDispatchReceiver = classBuilder.thisName,
|
||||
disableTailCallOptimizationForFunctionReturningUnit = disableTailCallOptimization,
|
||||
putContinuationParameterToLvt = !state.isIrBackend
|
||||
)
|
||||
val stateMachineBuilder = CoroutineTransformerMethodVisitor(
|
||||
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
|
||||
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
|
||||
lineNumber = sourceCompilerForInline.inlineCallSiteInfo.lineNumber,
|
||||
sourceFile = sourceCompilerForInline.callsiteFile?.name ?: "",
|
||||
languageVersionSettings = state.languageVersionSettings,
|
||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = classBuilder.thisName,
|
||||
isForNamedFunction = true,
|
||||
needDispatchReceiver = true,
|
||||
internalNameForDispatchReceiver = classBuilder.thisName,
|
||||
disableTailCallOptimizationForFunctionReturningUnit = disableTailCallOptimization,
|
||||
putContinuationParameterToLvt = !state.isIrBackend
|
||||
)
|
||||
|
||||
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 {
|
||||
return classBuilder.newMethod(
|
||||
JvmDeclarationOrigin.NO_ORIGIN, node.access, name, node.desc, node.signature, ArrayUtil.toStringArray(node.exceptions)
|
||||
@@ -219,77 +176,71 @@ class CoroutineTransformer(
|
||||
}
|
||||
}
|
||||
|
||||
class SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor(
|
||||
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)
|
||||
private const val NOINLINE_CALL_MARKER = "\$\$\$\$\$NOINLINE_CALL_MARKER\$\$\$\$\$"
|
||||
|
||||
FixStackMethodTransformer().transform(thisName, methodNode)
|
||||
val sourceFrames = MethodTransformer.analyze(thisName, methodNode, SourceInterpreter())
|
||||
|
||||
val noinlineInvokes = arrayListOf<Pair<AbstractInsnNode, AbstractInsnNode>>()
|
||||
|
||||
for (insn in methodNode.instructions.asSequence()) {
|
||||
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)
|
||||
fun markNoinlineLambdaIfSuspend(mv: MethodVisitor, info: FunctionalArgument?) {
|
||||
when (info) {
|
||||
NonInlineableArgumentForInlineableSuspendParameter ->
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, NOINLINE_CALL_MARKER, "always", "()V", false)
|
||||
NonInlineableArgumentForInlineableParameterCalledInSuspend ->
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, NOINLINE_CALL_MARKER, "conditional", "()V", false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FunctionalArgument.isSuspendLambda(): Boolean =
|
||||
this is NonInlineableArgumentForInlineableParameterCalledInSuspend || (this is ExpressionLambda && isSuspend)
|
||||
private fun Frame<SourceValue>.getSource(offset: Int): AbstractInsnNode? =
|
||||
getStack(stackSize - offset - 1)?.insns?.singleOrNull()
|
||||
|
||||
fun surroundInvokesWithSuspendMarkers(
|
||||
methodNode: MethodNode,
|
||||
noinlineInvokes: List<Pair<AbstractInsnNode, AbstractInsnNode>>
|
||||
) {
|
||||
for ((invoke, aload) in noinlineInvokes) {
|
||||
// Generate inline markers for stack transformation. It is required for local variables spilling.
|
||||
methodNode.instructions.insertBefore(aload, withInstructionAdapter {
|
||||
fun surroundInvokesWithSuspendMarkersIfNeeded(node: MethodNode) {
|
||||
val markers = node.instructions.asSequence().filter {
|
||||
it.opcode == Opcodes.INVOKESTATIC && (it as MethodInsnNode).owner == NOINLINE_CALL_MARKER
|
||||
}.toList()
|
||||
if (markers.isEmpty()) return
|
||||
|
||||
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)
|
||||
})
|
||||
methodNode.instructions.insertBefore(invoke, withInstructionAdapter {
|
||||
addSuspendMarker(this, isStartNotEnd = true)
|
||||
node.instructions.insertBefore(invoke, withInstructionAdapter {
|
||||
addSuspendMarker(this, isStartNotEnd = true, inlinable = conditional)
|
||||
})
|
||||
methodNode.instructions.insert(invoke, withInstructionAdapter {
|
||||
addSuspendMarker(this, isStartNotEnd = false)
|
||||
node.instructions.insert(invoke, withInstructionAdapter {
|
||||
addSuspendMarker(this, isStartNotEnd = false, inlinable = conditional)
|
||||
addInlineMarker(this, isStartNotEnd = false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: What to do if suddenly there are not exactly one receiver?
|
||||
fun findReceiverOfInvoke(frame: Frame<SourceValue>, insn: MethodInsnNode): AbstractInsnNode? =
|
||||
frame.getStack(frame.stackSize - insn.owner.removePrefix(NUMBERED_FUNCTION_PREFIX).toInt() - 1)?.insns?.singleOrNull()
|
||||
|
||||
fun AbstractInsnNode.isNoinlineCallMarker(): Boolean =
|
||||
opcode == Opcodes.INVOKESTATIC && cast<MethodInsnNode>().let { it.owner == NOINLINE_CALL_MARKER && it.name == NOINLINE_CALL_MARKER }
|
||||
// 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:
|
||||
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.InlineLambdaContext
|
||||
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.optimization.common.InsnSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||
@@ -432,18 +431,14 @@ fun addReturnsUnitMarker(v: InstructionAdapter) {
|
||||
v.emitInlineMarker(INLINE_MARKER_RETURNS_UNIT)
|
||||
}
|
||||
|
||||
fun addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
|
||||
v.emitInlineMarker(if (isStartNotEnd) INLINE_MARKER_BEFORE_SUSPEND_ID else INLINE_MARKER_AFTER_SUSPEND_ID)
|
||||
}
|
||||
|
||||
fun addInlineSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
|
||||
v.emitInlineMarker(if (isStartNotEnd) INLINE_MARKER_BEFORE_INLINE_SUSPEND_ID else INLINE_MARKER_AFTER_INLINE_SUSPEND_ID)
|
||||
}
|
||||
|
||||
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 addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean, inlinable: Boolean = false) {
|
||||
val marker = when {
|
||||
inlinable && isStartNotEnd -> INLINE_MARKER_BEFORE_INLINE_SUSPEND_ID
|
||||
inlinable -> INLINE_MARKER_AFTER_INLINE_SUSPEND_ID
|
||||
isStartNotEnd -> INLINE_MARKER_BEFORE_SUSPEND_ID
|
||||
else -> INLINE_MARKER_AFTER_SUSPEND_ID
|
||||
}
|
||||
v.emitInlineMarker(marker)
|
||||
}
|
||||
|
||||
fun addFakeContinuationConstructorCallMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
|
||||
|
||||
+6
-4
@@ -377,6 +377,10 @@ class ExpressionCodegen(
|
||||
val asmType = if (expression is IrConstructorCall) typeMapper.mapTypeAsDeclaration(expression.type) else expression.asmType
|
||||
val isSuspensionPoint = expression.isSuspensionPoint()
|
||||
|
||||
if (isSuspensionPoint != SuspensionPointKind.NEVER) {
|
||||
addInlineMarker(mv, isStartNotEnd = true)
|
||||
}
|
||||
|
||||
when {
|
||||
expression is IrConstructorCall -> {
|
||||
closureReifiedMarkers[expression.symbol.owner.parentAsClass]?.let {
|
||||
@@ -404,8 +408,6 @@ class ExpressionCodegen(
|
||||
}
|
||||
expression.symbol.descriptor is ConstructorDescriptor ->
|
||||
throw AssertionError("IrCall with ConstructorDescriptor: ${expression.javaClass.simpleName}")
|
||||
isSuspensionPoint != SuspensionPointKind.NEVER ->
|
||||
addInlineMarker(mv, isStartNotEnd = true)
|
||||
}
|
||||
|
||||
expression.dispatchReceiver?.let { receiver ->
|
||||
@@ -430,7 +432,7 @@ class ExpressionCodegen(
|
||||
expression.markLineNumber(true)
|
||||
|
||||
if (isSuspensionPoint != SuspensionPointKind.NEVER) {
|
||||
addSuspendMarker(mv, isSuspensionPoint, isStartNotEnd = true)
|
||||
addSuspendMarker(mv, isStartNotEnd = true, isSuspensionPoint == SuspensionPointKind.NOT_INLINE)
|
||||
}
|
||||
|
||||
if (irFunction.isInvokeSuspendOfContinuation()) {
|
||||
@@ -443,7 +445,7 @@ class ExpressionCodegen(
|
||||
}
|
||||
|
||||
if (isSuspensionPoint != SuspensionPointKind.NEVER) {
|
||||
addSuspendMarker(mv, isSuspensionPoint, isStartNotEnd = false)
|
||||
addSuspendMarker(mv, isStartNotEnd = false, isSuspensionPoint == SuspensionPointKind.NOT_INLINE)
|
||||
addInlineMarker(mv, isStartNotEnd = false)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ class IrExpressionLambdaImpl(
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@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
|
||||
public final class TcoContinuationKt$foo$$inlined$map$1$2 {
|
||||
synthetic final field $this_flow$inlined: FlowCollector
|
||||
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$1
|
||||
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
|
||||
}
|
||||
@@ -100,6 +122,14 @@ public final class TcoContinuationKt$map$$inlined$transform$1$1 {
|
||||
|
||||
@kotlin.Metadata
|
||||
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 exception: java.lang.Throwable
|
||||
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
|
||||
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 exception: java.lang.Throwable
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
public final class TcoContinuationKt$foo$$inlined$map$1$2 {
|
||||
synthetic final field $this_flow$inlined: FlowCollector
|
||||
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$1
|
||||
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
|
||||
}
|
||||
@@ -93,7 +114,16 @@ public final class TcoContinuationKt$map$$inlined$transform$1$1 {
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
@kotlin.coroutines.jvm.internal.DebugMetadata
|
||||
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
|
||||
synthetic field result: java.lang.Object
|
||||
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.coroutines.jvm.internal.DebugMetadata
|
||||
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
|
||||
synthetic field result: java.lang.Object
|
||||
synthetic final field this$0: TcoContinuationKt$map$$inlined$transform$2$2
|
||||
|
||||
Reference in New Issue
Block a user