Support new strategy for suspend inline functions
The main idea is to leave all the inline functions as is, without state machines (but keeping suspend-calls markers) and determine whether we need a state machine from the bytecode after inlining into a non-inline function #KT-17585 In Progress #KT-16603 In Progress #KT-16448 Fixed
This commit is contained in:
@@ -1184,7 +1184,6 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
|
||||
@Nullable
|
||||
private StackValue genCoroutineInstanceForSuspendLambda(@NotNull FunctionDescriptor suspendFunction) {
|
||||
if (!CoroutineCodegenUtilKt.isStateMachineNeeded(suspendFunction, bindingContext)) return null;
|
||||
if (!(suspendFunction instanceof AnonymousFunctionDescriptor)) return null;
|
||||
|
||||
ClassDescriptor suspendLambdaClassDescriptor = bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, suspendFunction);
|
||||
@@ -2138,9 +2137,9 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
@NotNull CallGenerator callGenerator,
|
||||
@NotNull ArgumentGenerator argumentGenerator
|
||||
) {
|
||||
boolean isSuspensionPoint = CoroutineCodegenUtilKt.isSuspensionPointInStateMachine(resolvedCall, bindingContext);
|
||||
boolean isSuspendCall = CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall);
|
||||
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
|
||||
putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspensionPoint, isConstructor);
|
||||
putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspendCall, isConstructor);
|
||||
|
||||
callGenerator.processAndPutHiddenParameters(false);
|
||||
|
||||
@@ -2171,7 +2170,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
}
|
||||
|
||||
if (isSuspensionPoint) {
|
||||
if (isSuspendCall) {
|
||||
v.invokestatic(
|
||||
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
|
||||
CoroutineCodegenUtilKt.BEFORE_SUSPENSION_POINT_MARKER_NAME,
|
||||
@@ -2181,7 +2180,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
|
||||
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
|
||||
|
||||
if (isSuspensionPoint) {
|
||||
if (isSuspendCall) {
|
||||
v.invokestatic(
|
||||
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
|
||||
CoroutineCodegenUtilKt.AFTER_SUSPENSION_POINT_MARKER_NAME, "()V", false);
|
||||
@@ -2199,12 +2198,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
@NotNull Callable callableMethod,
|
||||
@NotNull ResolvedCall<?> resolvedCall,
|
||||
@NotNull StackValue receiver,
|
||||
boolean isSuspensionPoint,
|
||||
boolean isSuspendCall,
|
||||
boolean isConstructor
|
||||
) {
|
||||
boolean isSafeCallOrOnStack = receiver instanceof StackValue.SafeCall || receiver instanceof StackValue.OnStack;
|
||||
|
||||
if (isSuspensionPoint && !isSafeCallOrOnStack) {
|
||||
if (isSuspendCall && !isSafeCallOrOnStack) {
|
||||
// Inline markers are used to spill the stack before coroutine suspension
|
||||
addInlineMarker(v, true);
|
||||
}
|
||||
@@ -2231,7 +2230,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
// The problem is that the stack before the call is not restored in case of null receiver.
|
||||
// The solution is to spill stack just after receiver is loaded (after IFNULL) in case of safe call.
|
||||
// But the problem is that we should leave the receiver itself on the stack, so we store it in a temporary variable.
|
||||
if (isSuspensionPoint && isSafeCallOrOnStack) {
|
||||
if (isSuspendCall && isSafeCallOrOnStack) {
|
||||
boolean bothReceivers =
|
||||
receiver instanceof StackValue.CallReceiver
|
||||
&& ((StackValue.CallReceiver) receiver).getDispatchReceiver().type.getSort() != Type.VOID
|
||||
|
||||
@@ -131,7 +131,7 @@ public class FunctionCodegen {
|
||||
|
||||
if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) {
|
||||
FunctionGenerationStrategy strategy;
|
||||
if (functionDescriptor.isSuspend()) {
|
||||
if (functionDescriptor.isSuspend() && !functionDescriptor.isInline()) {
|
||||
strategy = new SuspendFunctionGenerationStrategy(
|
||||
state,
|
||||
CoroutineCodegenUtilKt.<FunctionDescriptor>unwrapInitialDescriptorForSuspendFunction(functionDescriptor),
|
||||
|
||||
+39
-31
@@ -103,6 +103,20 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
return recordClassForCallable(element, callableDescriptor, supertypes, name, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ClassDescriptor recordClassForFunction(
|
||||
@NotNull KtElement element,
|
||||
@NotNull FunctionDescriptor functionDescriptor,
|
||||
@NotNull String name,
|
||||
@Nullable DeclarationDescriptor customContainer
|
||||
) {
|
||||
return recordClassForCallable(
|
||||
element, functionDescriptor,
|
||||
runtimeTypes.getSupertypesForClosure(functionDescriptor),
|
||||
name, customContainer
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ClassDescriptor recordClassForCallable(
|
||||
@NotNull KtElement element,
|
||||
@@ -454,18 +468,23 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
jvmSuspendFunctionView
|
||||
);
|
||||
|
||||
if (CoroutineCodegenUtilKt.containsNonTailSuspensionCalls(functionDescriptor, bindingContext)) {
|
||||
if (nameForClassOrPackageMember != null) {
|
||||
nameStack.push(nameForClassOrPackageMember);
|
||||
}
|
||||
|
||||
processNamedFunctionWithClosure(function, functionDescriptor, functionDescriptor).setSuspend(true);
|
||||
|
||||
if (nameForClassOrPackageMember != null) {
|
||||
nameStack.pop();
|
||||
}
|
||||
return;
|
||||
if (nameForClassOrPackageMember != null) {
|
||||
nameStack.push(nameForClassOrPackageMember);
|
||||
}
|
||||
|
||||
String name = inventAnonymousClassName();
|
||||
ClassDescriptor classDescriptor =
|
||||
recordClassForFunction(function, functionDescriptor, name, functionDescriptor);
|
||||
MutableClosure closure = recordClosure(classDescriptor, name);
|
||||
closure.setSuspend(true);
|
||||
|
||||
super.visitNamedFunction(function);
|
||||
|
||||
if (nameForClassOrPackageMember != null) {
|
||||
nameStack.pop();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (nameForClassOrPackageMember != null) {
|
||||
@@ -474,29 +493,18 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
nameStack.pop();
|
||||
}
|
||||
else {
|
||||
processNamedFunctionWithClosure(function, functionDescriptor, null);
|
||||
String name = inventAnonymousClassName();
|
||||
ClassDescriptor classDescriptor = recordClassForFunction(function, functionDescriptor, name, null);
|
||||
recordClosure(classDescriptor, name);
|
||||
|
||||
classStack.push(classDescriptor);
|
||||
nameStack.push(name);
|
||||
super.visitNamedFunction(function);
|
||||
nameStack.pop();
|
||||
classStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private MutableClosure processNamedFunctionWithClosure(
|
||||
@NotNull KtNamedFunction function,
|
||||
@NotNull FunctionDescriptor functionDescriptor,
|
||||
@Nullable DeclarationDescriptor customContainer
|
||||
) {
|
||||
String name = inventAnonymousClassName();
|
||||
Collection<KotlinType> supertypes = runtimeTypes.getSupertypesForClosure(functionDescriptor);
|
||||
ClassDescriptor classDescriptor = recordClassForCallable(function, functionDescriptor, supertypes, name, customContainer);
|
||||
MutableClosure closure = recordClosure(classDescriptor, name);
|
||||
|
||||
classStack.push(classDescriptor);
|
||||
nameStack.push(name);
|
||||
super.visitNamedFunction(function);
|
||||
nameStack.pop();
|
||||
classStack.pop();
|
||||
|
||||
return closure;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getNameForClassOrPackageMember(@NotNull DeclarationDescriptor descriptor) {
|
||||
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
|
||||
|
||||
+232
-13
@@ -21,12 +21,12 @@ import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
|
||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.analyzeLiveness
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.insnListOf
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.*
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
@@ -36,6 +36,8 @@ import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue
|
||||
|
||||
class CoroutineTransformerMethodVisitor(
|
||||
delegate: MethodVisitor,
|
||||
@@ -45,15 +47,35 @@ class CoroutineTransformerMethodVisitor(
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?,
|
||||
private val containingClassInternalName: String,
|
||||
private val classBuilderForCoroutineState: ClassBuilder,
|
||||
isForNamedFunction: Boolean
|
||||
obtainClassBuilderForCoroutineState: () -> ClassBuilder,
|
||||
private val isForNamedFunction: Boolean,
|
||||
// It's only matters for named functions, may differ from '!isStatic(access)' in case of DefaultImpls
|
||||
private val needDispatchReceiver: Boolean = false,
|
||||
// May differ from containingClassInternalName in case of DefaultImpls
|
||||
private val internalNameForDispatchReceiver: String? = null
|
||||
) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) {
|
||||
|
||||
private val classBuilderForCoroutineState: ClassBuilder by lazy(obtainClassBuilderForCoroutineState)
|
||||
|
||||
private val continuationIndex = if (isForNamedFunction) getLastParameterIndex(desc, access) else 0
|
||||
private val dataIndex = continuationIndex + 1
|
||||
private val exceptionIndex = dataIndex + 1
|
||||
var dataIndex = if (isForNamedFunction) -1 else 1
|
||||
var exceptionIndex = if (isForNamedFunction) -1 else 2
|
||||
|
||||
override fun performTransformations(methodNode: MethodNode) {
|
||||
val suspensionPoints = collectSuspensionPoints(methodNode)
|
||||
|
||||
if (isForNamedFunction) {
|
||||
if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) {
|
||||
dropSuspensionMarkers(methodNode, suspensionPoints)
|
||||
return
|
||||
}
|
||||
|
||||
dataIndex = methodNode.maxLocals++
|
||||
exceptionIndex = methodNode.maxLocals++
|
||||
|
||||
prepareMethodNodePreludeForNamedFunction(methodNode)
|
||||
}
|
||||
|
||||
val customCoroutineStart = run {
|
||||
val customCoroutineStartMarker = methodNode.instructions.toArray().filterIsInstance<MethodInsnNode>().firstOrNull {
|
||||
it.owner == COROUTINE_MARKER_OWNER && it.name == ACTUAL_COROUTINE_START_MARKER_NAME
|
||||
@@ -64,8 +86,6 @@ class CoroutineTransformerMethodVisitor(
|
||||
}
|
||||
}
|
||||
|
||||
val suspensionPoints = collectSuspensionPoints(methodNode)
|
||||
|
||||
for (suspensionPoint in suspensionPoints) {
|
||||
splitTryCatchBlocksContainingSuspensionPoint(methodNode, suspensionPoint)
|
||||
}
|
||||
@@ -125,6 +145,87 @@ class CoroutineTransformerMethodVisitor(
|
||||
|
||||
}
|
||||
|
||||
private fun prepareMethodNodePreludeForNamedFunction(methodNode: MethodNode) {
|
||||
val objectTypeForState = Type.getObjectType(classBuilderForCoroutineState.thisName)
|
||||
methodNode.instructions.insert(withInstructionAdapter {
|
||||
val createStateInstance = Label()
|
||||
val storeStateObject = Label()
|
||||
|
||||
// We have to distinguish the following situations:
|
||||
// - Our function got called in a common way (e.g. from another function or via recursive call) and we should execute our
|
||||
// code from the beginning
|
||||
// - We got called from `doResume` of our continuation, i.e. we need to continue from the last suspension point
|
||||
//
|
||||
// Also in the first case we wrap the completion into a special anonymous class instance (let's call it X$1)
|
||||
// that we'll use as a continuation argument for suspension points
|
||||
//
|
||||
// How we distinguish the cases:
|
||||
// - If the continuation is not an instance of X$1 we know exactly it's not the second case, because when resuming
|
||||
// the continuation we pass an instance of that class
|
||||
// - Otherwise it's still can be a recursive call. To check it's not the case we set the last bit in the label in
|
||||
// `doResume` just before calling the suspend function (see kotlin.coroutines.experimental.jvm.internal.CoroutineImplForNamedFunction).
|
||||
// So, if it's set we're in continuation.
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
instanceOf(objectTypeForState)
|
||||
ifeq(createStateInstance)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
invokevirtual(
|
||||
COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName,
|
||||
"checkAndFlushLastBit", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false
|
||||
)
|
||||
ifeq(createStateInstance)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
goTo(storeStateObject)
|
||||
|
||||
visitLabel(createStateInstance)
|
||||
|
||||
anew(objectTypeForState)
|
||||
dup()
|
||||
|
||||
val parameterTypesAndIndices =
|
||||
getParameterTypesIndicesForCoroutineConstructor(
|
||||
methodNode.desc,
|
||||
methodNode.access,
|
||||
needDispatchReceiver, internalNameForDispatchReceiver ?: containingClassInternalName
|
||||
)
|
||||
for ((type, index) in parameterTypesAndIndices) {
|
||||
load(index, type)
|
||||
}
|
||||
|
||||
invokespecial(
|
||||
classBuilderForCoroutineState.thisName,
|
||||
"<init>",
|
||||
Type.getMethodDescriptor(
|
||||
Type.VOID_TYPE,
|
||||
*getParameterTypesForCoroutineConstructor(
|
||||
methodNode.desc, needDispatchReceiver,
|
||||
internalNameForDispatchReceiver ?: containingClassInternalName
|
||||
)
|
||||
),
|
||||
false
|
||||
)
|
||||
|
||||
visitLabel(storeStateObject)
|
||||
visitVarInsn(Opcodes.ASTORE, continuationIndex)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "data", AsmTypes.OBJECT_TYPE.descriptor)
|
||||
visitVarInsn(Opcodes.ASTORE, dataIndex)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "exception", AsmTypes.JAVA_THROWABLE_TYPE.descriptor)
|
||||
visitVarInsn(Opcodes.ASTORE, exceptionIndex)
|
||||
|
||||
invokestatic(COROUTINE_MARKER_OWNER, ACTUAL_COROUTINE_START_MARKER_NAME, "()V", false)
|
||||
})
|
||||
}
|
||||
|
||||
private fun removeUnreachableSuspensionPointsAndExitPoints(methodNode: MethodNode, suspensionPoints: MutableList<SuspensionPoint>) {
|
||||
val dceResult = DeadCodeEliminationMethodTransformer().transformWithResult(containingClassInternalName, methodNode)
|
||||
|
||||
@@ -216,10 +317,12 @@ class CoroutineTransformerMethodVisitor(
|
||||
// k + 1 - data
|
||||
// k + 2 - exception
|
||||
val variablesToSpill =
|
||||
((exceptionIndex + 1) until localsCount)
|
||||
(0 until localsCount)
|
||||
.filter{ it !in setOf(continuationIndex, dataIndex, exceptionIndex) }
|
||||
.map { Pair(it, frame.getLocal(it)) }
|
||||
.filter { (index, value) ->
|
||||
value != StrictBasicValue.UNINITIALIZED_VALUE && livenessFrame.isAlive(index)
|
||||
(index == 0 && needDispatchReceiver && isForNamedFunction) ||
|
||||
(value != StrictBasicValue.UNINITIALIZED_VALUE && livenessFrame.isAlive(index))
|
||||
}
|
||||
|
||||
for ((index, basicValue) in variablesToSpill) {
|
||||
@@ -446,4 +549,120 @@ private class SuspensionPoint(
|
||||
}
|
||||
|
||||
private fun getLastParameterIndex(desc: String, access: Int) =
|
||||
Type.getArgumentTypes(desc).dropLast(1).map { it.size }.sum() + (if (access and Opcodes.ACC_STATIC != 0) 0 else 1)
|
||||
Type.getArgumentTypes(desc).dropLast(1).map { it.size }.sum() + (if (!isStatic(access)) 1 else 0)
|
||||
|
||||
private fun getParameterTypesForCoroutineConstructor(desc: String, hasDispatchReceiver: Boolean, thisName: String) =
|
||||
listOfNotNull(if (!hasDispatchReceiver) null else Type.getObjectType(thisName)).toTypedArray() +
|
||||
Type.getArgumentTypes(desc).last()
|
||||
|
||||
private fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0
|
||||
|
||||
private fun getParameterTypesIndicesForCoroutineConstructor(
|
||||
desc: String,
|
||||
containingFunctionAccess: Int,
|
||||
needDispatchReceiver: Boolean,
|
||||
thisName: String
|
||||
): Collection<Pair<Type, Int>> {
|
||||
return mutableListOf<Pair<Type, Int>>().apply {
|
||||
if (needDispatchReceiver) {
|
||||
add(Type.getObjectType(thisName) to 0)
|
||||
}
|
||||
val continuationIndex =
|
||||
getAllParameterTypes(desc, !isStatic(containingFunctionAccess), thisName).dropLast(1).map(Type::getSize).sum()
|
||||
add(CONTINUATION_ASM_TYPE to continuationIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAllParameterTypes(desc: String, hasDispatchReceiver: Boolean, thisName: String) =
|
||||
listOfNotNull(if (!hasDispatchReceiver) null else Type.getObjectType(thisName)).toTypedArray() +
|
||||
Type.getArgumentTypes(desc)
|
||||
|
||||
private fun allSuspensionPointsAreTailCalls(
|
||||
thisName: String,
|
||||
methodNode: MethodNode,
|
||||
suspensionPoints: List<SuspensionPoint>
|
||||
): Boolean {
|
||||
val safelyReachableReturns = findSafelyReachableReturns(methodNode)
|
||||
val sourceFrames = MethodTransformer.analyze(thisName, methodNode, IgnoringCopyOperationSourceInterpreter())
|
||||
|
||||
val instructions = methodNode.instructions
|
||||
return suspensionPoints.all { suspensionPoint ->
|
||||
val beginIndex = instructions.indexOf(suspensionPoint.suspensionCallBegin)
|
||||
val endIndex = instructions.indexOf(suspensionPoint.suspensionCallEnd)
|
||||
|
||||
safelyReachableReturns[endIndex + 1]?.all { returnIndex ->
|
||||
val sourceInsn =
|
||||
sourceFrames[returnIndex].top().sure {
|
||||
"There must be some value on stack to return"
|
||||
}.insns.singleOrNull()
|
||||
|
||||
sourceInsn?.let(instructions::indexOf) in beginIndex..endIndex
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
private class IgnoringCopyOperationSourceInterpreter : SourceInterpreter() {
|
||||
override fun copyOperation(insn: AbstractInsnNode?, value: SourceValue?) = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's call an instruction safe if its execution is always invisible: stack modifications, branching, variable insns (invisible in debug)
|
||||
*
|
||||
* For some instruction `insn` define the result as following:
|
||||
* - if there is a path leading to the non-safe instruction then result is `null`
|
||||
* - Otherwise result contains all the reachable ARETURN indices
|
||||
*
|
||||
* @return indices of safely reachable returns for each instruction in the method node
|
||||
*/
|
||||
private fun findSafelyReachableReturns(methodNode: MethodNode): Array<Set<Int>?> {
|
||||
val controlFlowGraph = ControlFlowGraph.build(methodNode)
|
||||
|
||||
val insns = methodNode.instructions
|
||||
val reachableReturnsIndices = Array<Set<Int>?>(insns.size()) init@{ index ->
|
||||
val insn = insns[index]
|
||||
|
||||
if (insn.opcode == Opcodes.ARETURN) {
|
||||
return@init setOf(index)
|
||||
}
|
||||
|
||||
if (!insn.isMeaningful || insn.opcode in SAFE_OPCODES || insn.isInvisibleInDebugVarInsn(methodNode) ||
|
||||
InlineCodegenUtil.isInlineMarker(insn)) {
|
||||
setOf()
|
||||
}
|
||||
else null
|
||||
}
|
||||
|
||||
var changed: Boolean
|
||||
do {
|
||||
changed = false
|
||||
for (index in 0 until insns.size()) {
|
||||
if (insns[index].opcode == Opcodes.ARETURN) continue
|
||||
|
||||
val newResult =
|
||||
controlFlowGraph
|
||||
.getSuccessorsIndices(index).plus(index)
|
||||
.map(reachableReturnsIndices::get)
|
||||
.fold<Set<Int>?, Set<Int>?>(mutableSetOf<Int>()) { acc, successorsResult ->
|
||||
if (acc != null && successorsResult != null) acc + successorsResult else null
|
||||
}
|
||||
|
||||
if (newResult != reachableReturnsIndices[index]) {
|
||||
reachableReturnsIndices[index] = newResult
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
} while (changed)
|
||||
|
||||
return reachableReturnsIndices
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode?.isInvisibleInDebugVarInsn(methodNode: MethodNode): Boolean {
|
||||
val insns = methodNode.instructions
|
||||
val index = insns.indexOf(this)
|
||||
return (this is VarInsnNode && methodNode.localVariables.none {
|
||||
it.index == `var` && index in it.start.let(insns::indexOf)..it.end.let(insns::indexOf)
|
||||
})
|
||||
}
|
||||
|
||||
private val SAFE_OPCODES =
|
||||
((Opcodes.DUP..Opcodes.DUP2_X2) + Opcodes.POP + Opcodes.POP2 + (Opcodes.IFEQ..Opcodes.GOTO)).toSet()
|
||||
|
||||
+23
-82
@@ -20,15 +20,14 @@ import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||
import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
|
||||
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class SuspendFunctionGenerationStrategy(
|
||||
state: GenerationState,
|
||||
@@ -36,98 +35,40 @@ class SuspendFunctionGenerationStrategy(
|
||||
private val declaration: KtFunction,
|
||||
private val containingClassInternalName: String
|
||||
) : FunctionGenerationStrategy.CodegenBased(state) {
|
||||
private val containsNonTailSuspensionCalls = originalSuspendDescriptor.containsNonTailSuspensionCalls(state.bindingContext)
|
||||
|
||||
private lateinit var transformer: CoroutineTransformerMethodVisitor
|
||||
private lateinit var codegen: ExpressionCodegen
|
||||
|
||||
private val classBuilderForCoroutineState by lazy {
|
||||
state.factory.newVisitor(
|
||||
OtherOrigin(declaration, originalSuspendDescriptor),
|
||||
CodegenBinding.asmTypeForAnonymousClass(state.bindingContext, originalSuspendDescriptor),
|
||||
declaration.containingFile
|
||||
)
|
||||
).also {
|
||||
val coroutineCodegen =
|
||||
CoroutineCodegenForNamedFunction.create(it, codegen, originalSuspendDescriptor, declaration)
|
||||
coroutineCodegen.generate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun wrapMethodVisitor(mv: MethodVisitor, access: Int, name: String, desc: String): MethodVisitor {
|
||||
if (containsNonTailSuspensionCalls) {
|
||||
return CoroutineTransformerMethodVisitor(
|
||||
mv, access, name, desc, null, null, containingClassInternalName, classBuilderForCoroutineState,
|
||||
isForNamedFunction = true
|
||||
)
|
||||
}
|
||||
if (access and Opcodes.ACC_ABSTRACT != 0) return mv
|
||||
|
||||
return super.wrapMethodVisitor(mv, access, name, desc)
|
||||
return CoroutineTransformerMethodVisitor(
|
||||
mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState,
|
||||
isForNamedFunction = true,
|
||||
needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null,
|
||||
internalNameForDispatchReceiver = containingClassInternalNameOrNull()
|
||||
).also {
|
||||
transformer = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun containingClassInternalNameOrNull() =
|
||||
originalSuspendDescriptor.containingDeclaration.safeAs<ClassDescriptor>()?.let(state.typeMapper::mapClass)?.internalName
|
||||
|
||||
override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) {
|
||||
if (!containsNonTailSuspensionCalls) {
|
||||
codegen.returnExpression(declaration.bodyExpression)
|
||||
return
|
||||
}
|
||||
|
||||
val coroutineCodegen =
|
||||
CoroutineForNamedFunctionCodegen.create(classBuilderForCoroutineState, codegen, originalSuspendDescriptor, declaration)
|
||||
coroutineCodegen.generate()
|
||||
|
||||
val functionDescriptor = codegen.context.functionDescriptor
|
||||
|
||||
val continuationIndex = codegen.frameMap.getIndex(functionDescriptor.valueParameters.last())
|
||||
val objectTypeForState = Type.getObjectType(classBuilderForCoroutineState.thisName)
|
||||
|
||||
val dataIndex = codegen.frameMap.enterTemp(AsmTypes.OBJECT_TYPE)
|
||||
val exceptionIndex = codegen.frameMap.enterTemp(AsmTypes.OBJECT_TYPE)
|
||||
|
||||
with(codegen.v) {
|
||||
val createStateInstance = Label()
|
||||
val storeStateObject = Label()
|
||||
|
||||
// We have to distinguish the following situations:
|
||||
// - Our function got called in a common way (e.g. from another function or via recursive call) and we should execute our
|
||||
// code from the beginning
|
||||
// - We got called from `doResume` of our continuation, i.e. we need to continue from the last suspension point
|
||||
//
|
||||
// Also in the first case we wrap the completion into a special anonymous class instance (let's call it X$1)
|
||||
// that we'll use as a continuation argument for suspension points
|
||||
//
|
||||
// How we distinguish the cases:
|
||||
// - If the continuation is not an instance of X$1 we know exactly it's not the second case, because when resuming
|
||||
// the continuation we pass an instance of that class
|
||||
// - Otherwise it's still can be a recursive call. To check it's not the case we set the last bit in the label in
|
||||
// `doResume` just before calling the suspend function (see kotlin.coroutines.experimental.jvm.internal.CoroutineImplForNamedFunction).
|
||||
// So, if it's set we're in continuation.
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
instanceOf(objectTypeForState)
|
||||
ifeq(createStateInstance)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
invokevirtual(
|
||||
COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName,
|
||||
"checkAndFlushLastBit", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false
|
||||
)
|
||||
ifeq(createStateInstance)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
goTo(storeStateObject)
|
||||
|
||||
visitLabel(createStateInstance)
|
||||
coroutineCodegen.putInstanceOnStack(codegen, null).put(objectTypeForState, this)
|
||||
|
||||
visitLabel(storeStateObject)
|
||||
visitVarInsn(Opcodes.ASTORE, continuationIndex)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "data", AsmTypes.OBJECT_TYPE.descriptor)
|
||||
visitVarInsn(Opcodes.ASTORE, dataIndex)
|
||||
|
||||
visitVarInsn(Opcodes.ALOAD, continuationIndex)
|
||||
checkcast(objectTypeForState)
|
||||
getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "exception", AsmTypes.JAVA_THROWABLE_TYPE.descriptor)
|
||||
visitVarInsn(Opcodes.ASTORE, exceptionIndex)
|
||||
|
||||
invokestatic(COROUTINE_MARKER_OWNER, ACTUAL_COROUTINE_START_MARKER_NAME, "()V", false)
|
||||
}
|
||||
|
||||
this.codegen = codegen
|
||||
codegen.returnExpression(declaration.bodyExpression)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-13
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.codegen.topLevelClassAsmType
|
||||
import org.jetbrains.kotlin.codegen.topLevelClassInternalName
|
||||
import org.jetbrains.kotlin.coroutines.isSuspendLambda
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
@@ -156,18 +155,11 @@ fun ResolvedCall<*>.replaceSuspensionFunctionWithRealDescriptor(
|
||||
return ResolvedCallWithRealDescriptor(newCall, thisExpression)
|
||||
}
|
||||
|
||||
fun ResolvedCall<*>.isSuspensionPointInStateMachine(bindingContext: BindingContext): Boolean {
|
||||
if (resultingDescriptor.safeAs<FunctionDescriptor>()?.isSuspend != true) return false
|
||||
val enclosingSuspendFunction = bindingContext[BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, call] ?: return false
|
||||
|
||||
return enclosingSuspendFunction.isStateMachineNeeded(bindingContext)
|
||||
}
|
||||
|
||||
fun FunctionDescriptor.isStateMachineNeeded(bindingContext: BindingContext) =
|
||||
isSuspendLambda || containsNonTailSuspensionCalls(bindingContext)
|
||||
|
||||
fun FunctionDescriptor.containsNonTailSuspensionCalls(bindingContext: BindingContext) =
|
||||
bindingContext[BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, original] == true
|
||||
fun ResolvedCall<*>.isSuspendNoInlineCall() =
|
||||
resultingDescriptor.safeAs<FunctionDescriptor>()
|
||||
?.let {
|
||||
it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm())
|
||||
} == true
|
||||
|
||||
fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean {
|
||||
if (this !is FunctionDescriptor) return false
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment;
|
||||
import org.jetbrains.kotlin.codegen.*;
|
||||
import org.jetbrains.kotlin.codegen.context.*;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.SuspendFunctionGenerationStrategy;
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructorsKt;
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||
@@ -593,14 +592,6 @@ public class InlineCodegen extends CallGenerator {
|
||||
else if (expression instanceof KtFunctionLiteral) {
|
||||
strategy = new ClosureGenerationStrategy(state, (KtDeclarationWithBody) expression);
|
||||
}
|
||||
else if (descriptor.isSuspend() && expression instanceof KtFunction) {
|
||||
strategy =
|
||||
new SuspendFunctionGenerationStrategy(
|
||||
state,
|
||||
CoroutineCodegenUtilKt.unwrapInitialDescriptorForSuspendFunction(descriptor),
|
||||
(KtFunction) expression, null
|
||||
);
|
||||
}
|
||||
else {
|
||||
strategy = new FunctionGenerationStrategy.FunctionDefault(state, (KtDeclarationWithBody) expression);
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
class ControlFlowGraph private constructor(private val insns: InsnList) {
|
||||
private val edges: Array<MutableList<Int>> = Array(insns.size()) { arrayListOf<Int>() }
|
||||
|
||||
fun getSuccessorsIndices(insn: AbstractInsnNode): List<Int> = edges[insns.indexOf(insn)]
|
||||
fun getSuccessorsIndices(insn: AbstractInsnNode): List<Int> = getSuccessorsIndices(insns.indexOf(insn))
|
||||
fun getSuccessorsIndices(index: Int): List<Int> = edges[index]
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
||||
@@ -787,7 +787,8 @@ public class KotlinTypeMapper {
|
||||
isInterfaceMember = true;
|
||||
}
|
||||
else {
|
||||
boolean isPrivateFunInvocation = Visibilities.isPrivate(functionDescriptor.getVisibility());
|
||||
boolean isPrivateFunInvocation =
|
||||
Visibilities.isPrivate(functionDescriptor.getVisibility()) && !functionDescriptor.isSuspend();
|
||||
invokeOpcode = superCall || isPrivateFunInvocation ? INVOKESPECIAL : INVOKEVIRTUAL;
|
||||
isInterfaceMember = superCall && currentIsInterface;
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// IGNORE_BACKEND: JS
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
import helpers.*
|
||||
// CHECK_BYTECODE_LISTING
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
inline suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
|
||||
x.resume(v)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
// There's no state machine in the suspendHere, since it's inline
|
||||
inline suspend fun suspendHere(): String = suspendThere("O") + suspendThere("K")
|
||||
// There should be a state machine for mainSuspend as it has two suspend non-tail calls inlined
|
||||
suspend fun mainSuspend() = suspendHere()
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = ""
|
||||
|
||||
builder {
|
||||
result = mainSuspend()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
@kotlin.Metadata
|
||||
final class InlineWithStateMachineKt$box$1 {
|
||||
synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef
|
||||
field L$0: java.lang.Object
|
||||
inner class InlineWithStateMachineKt$box$1
|
||||
method <init>(p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void
|
||||
public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): kotlin.coroutines.experimental.Continuation
|
||||
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
|
||||
public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public synthetic method invoke(p0: java.lang.Object): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
final class InlineWithStateMachineKt$mainSuspend$1 {
|
||||
field L$0: java.lang.Object
|
||||
field L$1: java.lang.Object
|
||||
inner class InlineWithStateMachineKt$mainSuspend$1
|
||||
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
|
||||
public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
public final class InlineWithStateMachineKt {
|
||||
inner class InlineWithStateMachineKt$box$1
|
||||
inner class InlineWithStateMachineKt$mainSuspend$1
|
||||
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void
|
||||
public final static @org.jetbrains.annotations.Nullable method mainSuspend(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.Nullable method suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.Nullable method suspendThere(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
// IGNORE_BACKEND: NATIVE
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
import helpers.*
|
||||
// CHECK_BYTECODE_LISTING
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
inline suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
|
||||
x.resume(v)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun suspendHere(): String = suspendThere("O")
|
||||
|
||||
// There is a kind of redundant state machine generated for complexSuspend:
|
||||
// it's basically has the only suspend call just before return, but there is
|
||||
// a redundant CHECKCAST String in the run's lambda, so we have to insert the state machine.
|
||||
// TODO: Think of avoiding such redundant casts
|
||||
suspend fun complexSuspend(): String {
|
||||
return run {
|
||||
suspendThere("K")
|
||||
}
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = ""
|
||||
|
||||
builder {
|
||||
result = suspendHere() + complexSuspend()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
@kotlin.Metadata
|
||||
final class InlineWithoutStateMachineKt$box$1 {
|
||||
synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef
|
||||
field L$0: java.lang.Object
|
||||
field L$1: java.lang.Object
|
||||
inner class InlineWithoutStateMachineKt$box$1
|
||||
method <init>(p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void
|
||||
public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): kotlin.coroutines.experimental.Continuation
|
||||
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
|
||||
public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public synthetic method invoke(p0: java.lang.Object): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
final class InlineWithoutStateMachineKt$complexSuspend$1 {
|
||||
field L$0: java.lang.Object
|
||||
field L$1: java.lang.Object
|
||||
inner class InlineWithoutStateMachineKt$complexSuspend$1
|
||||
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
|
||||
public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
public final class InlineWithoutStateMachineKt {
|
||||
inner class InlineWithoutStateMachineKt$box$1
|
||||
inner class InlineWithoutStateMachineKt$complexSuspend$1
|
||||
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void
|
||||
public final static @org.jetbrains.annotations.Nullable method complexSuspend(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.Nullable method suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.Nullable method suspendThere(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// IGNORE_BACKEND: NATIVE
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
// CHECK_BYTECODE_LISTING
|
||||
import helpers.*
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
|
||||
x.resume(v)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun suspendHere(): String = suspendThere("OK")
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = ""
|
||||
|
||||
builder {
|
||||
result = suspendHere()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
@kotlin.Metadata
|
||||
final class SimpleKt$box$1 {
|
||||
synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef
|
||||
field L$0: java.lang.Object
|
||||
inner class SimpleKt$box$1
|
||||
method <init>(p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void
|
||||
public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): kotlin.coroutines.experimental.Continuation
|
||||
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
|
||||
public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public synthetic method invoke(p0: java.lang.Object): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
public final class SimpleKt {
|
||||
inner class SimpleKt$box$1
|
||||
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void
|
||||
public final static @org.jetbrains.annotations.Nullable method suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.Nullable method suspendThere(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
@kotlin.Metadata
|
||||
final class Controller$multipleSuspensions$1 {
|
||||
field L$0: java.lang.Object
|
||||
synthetic final field this$0: Controller
|
||||
inner class Controller$multipleSuspensions$1
|
||||
method <init>(p0: Controller, 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
|
||||
public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
final class Controller$nonTailCall$1 {
|
||||
field L$0: java.lang.Object
|
||||
synthetic final field this$0: Controller
|
||||
inner class Controller$nonTailCall$1
|
||||
method <init>(p0: Controller, 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
|
||||
public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
@@ -28,9 +30,9 @@ public final class Controller {
|
||||
@kotlin.Metadata
|
||||
final class CoroutineFieldsKt$box$1 {
|
||||
synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef
|
||||
private field J$0: long
|
||||
private field L$0: java.lang.Object
|
||||
private field L$1: java.lang.Object
|
||||
field J$0: long
|
||||
field L$0: java.lang.Object
|
||||
field L$1: java.lang.Object
|
||||
private field p$: Controller
|
||||
inner class CoroutineFieldsKt$box$1
|
||||
method <init>(p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void
|
||||
|
||||
+27
@@ -5708,6 +5708,33 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TailCallOptimizations extends AbstractIrBlackBoxCodegenTest {
|
||||
public void testAllFilesPresentInTailCallOptimizations() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithStateMachine.kt")
|
||||
public void testInlineWithStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithoutStateMachine.kt")
|
||||
public void testInlineWithoutStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -5708,6 +5708,33 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TailCallOptimizations extends AbstractBlackBoxCodegenTest {
|
||||
public void testAllFilesPresentInTailCallOptimizations() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithStateMachine.kt")
|
||||
public void testInlineWithStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithoutStateMachine.kt")
|
||||
public void testInlineWithoutStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -5708,6 +5708,33 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TailCallOptimizations extends AbstractLightAnalysisModeTest {
|
||||
public void testAllFilesPresentInTailCallOptimizations() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithStateMachine.kt")
|
||||
public void testInlineWithStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithoutStateMachine.kt")
|
||||
public void testInlineWithoutStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
+33
@@ -6405,6 +6405,39 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TailCallOptimizations extends AbstractJsCodegenBoxTest {
|
||||
public void testAllFilesPresentInTailCallOptimizations() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithStateMachine.kt")
|
||||
public void testInlineWithStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineWithoutStateMachine.kt")
|
||||
public void testInlineWithoutStateMachine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user