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
|
@Nullable
|
||||||
private StackValue genCoroutineInstanceForSuspendLambda(@NotNull FunctionDescriptor suspendFunction) {
|
private StackValue genCoroutineInstanceForSuspendLambda(@NotNull FunctionDescriptor suspendFunction) {
|
||||||
if (!CoroutineCodegenUtilKt.isStateMachineNeeded(suspendFunction, bindingContext)) return null;
|
|
||||||
if (!(suspendFunction instanceof AnonymousFunctionDescriptor)) return null;
|
if (!(suspendFunction instanceof AnonymousFunctionDescriptor)) return null;
|
||||||
|
|
||||||
ClassDescriptor suspendLambdaClassDescriptor = bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, suspendFunction);
|
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 CallGenerator callGenerator,
|
||||||
@NotNull ArgumentGenerator argumentGenerator
|
@NotNull ArgumentGenerator argumentGenerator
|
||||||
) {
|
) {
|
||||||
boolean isSuspensionPoint = CoroutineCodegenUtilKt.isSuspensionPointInStateMachine(resolvedCall, bindingContext);
|
boolean isSuspendCall = CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall);
|
||||||
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
|
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
|
||||||
putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspensionPoint, isConstructor);
|
putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspendCall, isConstructor);
|
||||||
|
|
||||||
callGenerator.processAndPutHiddenParameters(false);
|
callGenerator.processAndPutHiddenParameters(false);
|
||||||
|
|
||||||
@@ -2171,7 +2170,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSuspensionPoint) {
|
if (isSuspendCall) {
|
||||||
v.invokestatic(
|
v.invokestatic(
|
||||||
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
|
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
|
||||||
CoroutineCodegenUtilKt.BEFORE_SUSPENSION_POINT_MARKER_NAME,
|
CoroutineCodegenUtilKt.BEFORE_SUSPENSION_POINT_MARKER_NAME,
|
||||||
@@ -2181,7 +2180,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
|
|
||||||
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
|
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
|
||||||
|
|
||||||
if (isSuspensionPoint) {
|
if (isSuspendCall) {
|
||||||
v.invokestatic(
|
v.invokestatic(
|
||||||
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
|
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
|
||||||
CoroutineCodegenUtilKt.AFTER_SUSPENSION_POINT_MARKER_NAME, "()V", false);
|
CoroutineCodegenUtilKt.AFTER_SUSPENSION_POINT_MARKER_NAME, "()V", false);
|
||||||
@@ -2199,12 +2198,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
@NotNull Callable callableMethod,
|
@NotNull Callable callableMethod,
|
||||||
@NotNull ResolvedCall<?> resolvedCall,
|
@NotNull ResolvedCall<?> resolvedCall,
|
||||||
@NotNull StackValue receiver,
|
@NotNull StackValue receiver,
|
||||||
boolean isSuspensionPoint,
|
boolean isSuspendCall,
|
||||||
boolean isConstructor
|
boolean isConstructor
|
||||||
) {
|
) {
|
||||||
boolean isSafeCallOrOnStack = receiver instanceof StackValue.SafeCall || receiver instanceof StackValue.OnStack;
|
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
|
// Inline markers are used to spill the stack before coroutine suspension
|
||||||
addInlineMarker(v, true);
|
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 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.
|
// 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.
|
// 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 =
|
boolean bothReceivers =
|
||||||
receiver instanceof StackValue.CallReceiver
|
receiver instanceof StackValue.CallReceiver
|
||||||
&& ((StackValue.CallReceiver) receiver).getDispatchReceiver().type.getSort() != Type.VOID
|
&& ((StackValue.CallReceiver) receiver).getDispatchReceiver().type.getSort() != Type.VOID
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class FunctionCodegen {
|
|||||||
|
|
||||||
if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) {
|
if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) {
|
||||||
FunctionGenerationStrategy strategy;
|
FunctionGenerationStrategy strategy;
|
||||||
if (functionDescriptor.isSuspend()) {
|
if (functionDescriptor.isSuspend() && !functionDescriptor.isInline()) {
|
||||||
strategy = new SuspendFunctionGenerationStrategy(
|
strategy = new SuspendFunctionGenerationStrategy(
|
||||||
state,
|
state,
|
||||||
CoroutineCodegenUtilKt.<FunctionDescriptor>unwrapInitialDescriptorForSuspendFunction(functionDescriptor),
|
CoroutineCodegenUtilKt.<FunctionDescriptor>unwrapInitialDescriptorForSuspendFunction(functionDescriptor),
|
||||||
|
|||||||
+39
-31
@@ -103,6 +103,20 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
|||||||
return recordClassForCallable(element, callableDescriptor, supertypes, name, null);
|
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
|
@NotNull
|
||||||
private ClassDescriptor recordClassForCallable(
|
private ClassDescriptor recordClassForCallable(
|
||||||
@NotNull KtElement element,
|
@NotNull KtElement element,
|
||||||
@@ -454,18 +468,23 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
|||||||
jvmSuspendFunctionView
|
jvmSuspendFunctionView
|
||||||
);
|
);
|
||||||
|
|
||||||
if (CoroutineCodegenUtilKt.containsNonTailSuspensionCalls(functionDescriptor, bindingContext)) {
|
if (nameForClassOrPackageMember != null) {
|
||||||
if (nameForClassOrPackageMember != null) {
|
nameStack.push(nameForClassOrPackageMember);
|
||||||
nameStack.push(nameForClassOrPackageMember);
|
|
||||||
}
|
|
||||||
|
|
||||||
processNamedFunctionWithClosure(function, functionDescriptor, functionDescriptor).setSuspend(true);
|
|
||||||
|
|
||||||
if (nameForClassOrPackageMember != null) {
|
|
||||||
nameStack.pop();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (nameForClassOrPackageMember != null) {
|
||||||
@@ -474,29 +493,18 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
|||||||
nameStack.pop();
|
nameStack.pop();
|
||||||
}
|
}
|
||||||
else {
|
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
|
@Nullable
|
||||||
private String getNameForClassOrPackageMember(@NotNull DeclarationDescriptor descriptor) {
|
private String getNameForClassOrPackageMember(@NotNull DeclarationDescriptor descriptor) {
|
||||||
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
|
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.ClassBuilder
|
||||||
import org.jetbrains.kotlin.codegen.StackValue
|
import org.jetbrains.kotlin.codegen.StackValue
|
||||||
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
|
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.DeadCodeEliminationMethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
|
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
import org.jetbrains.kotlin.codegen.optimization.common.*
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.analyzeLiveness
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.insnListOf
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||||
import org.jetbrains.kotlin.utils.sure
|
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.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
import org.jetbrains.org.objectweb.asm.tree.*
|
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(
|
class CoroutineTransformerMethodVisitor(
|
||||||
delegate: MethodVisitor,
|
delegate: MethodVisitor,
|
||||||
@@ -45,15 +47,35 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
signature: String?,
|
signature: String?,
|
||||||
exceptions: Array<out String>?,
|
exceptions: Array<out String>?,
|
||||||
private val containingClassInternalName: String,
|
private val containingClassInternalName: String,
|
||||||
private val classBuilderForCoroutineState: ClassBuilder,
|
obtainClassBuilderForCoroutineState: () -> ClassBuilder,
|
||||||
isForNamedFunction: Boolean
|
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) {
|
) : 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 continuationIndex = if (isForNamedFunction) getLastParameterIndex(desc, access) else 0
|
||||||
private val dataIndex = continuationIndex + 1
|
var dataIndex = if (isForNamedFunction) -1 else 1
|
||||||
private val exceptionIndex = dataIndex + 1
|
var exceptionIndex = if (isForNamedFunction) -1 else 2
|
||||||
|
|
||||||
override fun performTransformations(methodNode: MethodNode) {
|
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 customCoroutineStart = run {
|
||||||
val customCoroutineStartMarker = methodNode.instructions.toArray().filterIsInstance<MethodInsnNode>().firstOrNull {
|
val customCoroutineStartMarker = methodNode.instructions.toArray().filterIsInstance<MethodInsnNode>().firstOrNull {
|
||||||
it.owner == COROUTINE_MARKER_OWNER && it.name == ACTUAL_COROUTINE_START_MARKER_NAME
|
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) {
|
for (suspensionPoint in suspensionPoints) {
|
||||||
splitTryCatchBlocksContainingSuspensionPoint(methodNode, suspensionPoint)
|
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>) {
|
private fun removeUnreachableSuspensionPointsAndExitPoints(methodNode: MethodNode, suspensionPoints: MutableList<SuspensionPoint>) {
|
||||||
val dceResult = DeadCodeEliminationMethodTransformer().transformWithResult(containingClassInternalName, methodNode)
|
val dceResult = DeadCodeEliminationMethodTransformer().transformWithResult(containingClassInternalName, methodNode)
|
||||||
|
|
||||||
@@ -216,10 +317,12 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
// k + 1 - data
|
// k + 1 - data
|
||||||
// k + 2 - exception
|
// k + 2 - exception
|
||||||
val variablesToSpill =
|
val variablesToSpill =
|
||||||
((exceptionIndex + 1) until localsCount)
|
(0 until localsCount)
|
||||||
|
.filter{ it !in setOf(continuationIndex, dataIndex, exceptionIndex) }
|
||||||
.map { Pair(it, frame.getLocal(it)) }
|
.map { Pair(it, frame.getLocal(it)) }
|
||||||
.filter { (index, value) ->
|
.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) {
|
for ((index, basicValue) in variablesToSpill) {
|
||||||
@@ -446,4 +549,120 @@ private class SuspensionPoint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getLastParameterIndex(desc: String, access: Int) =
|
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.FunctionGenerationStrategy
|
||||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtFunction
|
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.diagnostics.OtherOrigin
|
||||||
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
|
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.MethodVisitor
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
|
||||||
|
|
||||||
class SuspendFunctionGenerationStrategy(
|
class SuspendFunctionGenerationStrategy(
|
||||||
state: GenerationState,
|
state: GenerationState,
|
||||||
@@ -36,98 +35,40 @@ class SuspendFunctionGenerationStrategy(
|
|||||||
private val declaration: KtFunction,
|
private val declaration: KtFunction,
|
||||||
private val containingClassInternalName: String
|
private val containingClassInternalName: String
|
||||||
) : FunctionGenerationStrategy.CodegenBased(state) {
|
) : 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 {
|
private val classBuilderForCoroutineState by lazy {
|
||||||
state.factory.newVisitor(
|
state.factory.newVisitor(
|
||||||
OtherOrigin(declaration, originalSuspendDescriptor),
|
OtherOrigin(declaration, originalSuspendDescriptor),
|
||||||
CodegenBinding.asmTypeForAnonymousClass(state.bindingContext, originalSuspendDescriptor),
|
CodegenBinding.asmTypeForAnonymousClass(state.bindingContext, originalSuspendDescriptor),
|
||||||
declaration.containingFile
|
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 {
|
override fun wrapMethodVisitor(mv: MethodVisitor, access: Int, name: String, desc: String): MethodVisitor {
|
||||||
if (containsNonTailSuspensionCalls) {
|
if (access and Opcodes.ACC_ABSTRACT != 0) return mv
|
||||||
return CoroutineTransformerMethodVisitor(
|
|
||||||
mv, access, name, desc, null, null, containingClassInternalName, classBuilderForCoroutineState,
|
|
||||||
isForNamedFunction = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) {
|
||||||
if (!containsNonTailSuspensionCalls) {
|
this.codegen = codegen
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
codegen.returnExpression(declaration.bodyExpression)
|
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.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.codegen.topLevelClassAsmType
|
import org.jetbrains.kotlin.codegen.topLevelClassAsmType
|
||||||
import org.jetbrains.kotlin.codegen.topLevelClassInternalName
|
import org.jetbrains.kotlin.codegen.topLevelClassInternalName
|
||||||
import org.jetbrains.kotlin.coroutines.isSuspendLambda
|
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||||
@@ -156,18 +155,11 @@ fun ResolvedCall<*>.replaceSuspensionFunctionWithRealDescriptor(
|
|||||||
return ResolvedCallWithRealDescriptor(newCall, thisExpression)
|
return ResolvedCallWithRealDescriptor(newCall, thisExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ResolvedCall<*>.isSuspensionPointInStateMachine(bindingContext: BindingContext): Boolean {
|
fun ResolvedCall<*>.isSuspendNoInlineCall() =
|
||||||
if (resultingDescriptor.safeAs<FunctionDescriptor>()?.isSuspend != true) return false
|
resultingDescriptor.safeAs<FunctionDescriptor>()
|
||||||
val enclosingSuspendFunction = bindingContext[BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, call] ?: return false
|
?.let {
|
||||||
|
it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm())
|
||||||
return enclosingSuspendFunction.isStateMachineNeeded(bindingContext)
|
} == true
|
||||||
}
|
|
||||||
|
|
||||||
fun FunctionDescriptor.isStateMachineNeeded(bindingContext: BindingContext) =
|
|
||||||
isSuspendLambda || containsNonTailSuspensionCalls(bindingContext)
|
|
||||||
|
|
||||||
fun FunctionDescriptor.containsNonTailSuspensionCalls(bindingContext: BindingContext) =
|
|
||||||
bindingContext[BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, original] == true
|
|
||||||
|
|
||||||
fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean {
|
fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean {
|
||||||
if (this !is FunctionDescriptor) return false
|
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.*;
|
||||||
import org.jetbrains.kotlin.codegen.context.*;
|
import org.jetbrains.kotlin.codegen.context.*;
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
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.intrinsics.IntrinsicArrayConstructorsKt;
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||||
@@ -593,14 +592,6 @@ public class InlineCodegen extends CallGenerator {
|
|||||||
else if (expression instanceof KtFunctionLiteral) {
|
else if (expression instanceof KtFunctionLiteral) {
|
||||||
strategy = new ClosureGenerationStrategy(state, (KtDeclarationWithBody) expression);
|
strategy = new ClosureGenerationStrategy(state, (KtDeclarationWithBody) expression);
|
||||||
}
|
}
|
||||||
else if (descriptor.isSuspend() && expression instanceof KtFunction) {
|
|
||||||
strategy =
|
|
||||||
new SuspendFunctionGenerationStrategy(
|
|
||||||
state,
|
|
||||||
CoroutineCodegenUtilKt.unwrapInitialDescriptorForSuspendFunction(descriptor),
|
|
||||||
(KtFunction) expression, null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
strategy = new FunctionGenerationStrategy.FunctionDefault(state, (KtDeclarationWithBody) expression);
|
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) {
|
class ControlFlowGraph private constructor(private val insns: InsnList) {
|
||||||
private val edges: Array<MutableList<Int>> = Array(insns.size()) { arrayListOf<Int>() }
|
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 {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|||||||
@@ -787,7 +787,8 @@ public class KotlinTypeMapper {
|
|||||||
isInterfaceMember = true;
|
isInterfaceMember = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
boolean isPrivateFunInvocation = Visibilities.isPrivate(functionDescriptor.getVisibility());
|
boolean isPrivateFunInvocation =
|
||||||
|
Visibilities.isPrivate(functionDescriptor.getVisibility()) && !functionDescriptor.isSuspend();
|
||||||
invokeOpcode = superCall || isPrivateFunInvocation ? INVOKESPECIAL : INVOKEVIRTUAL;
|
invokeOpcode = superCall || isPrivateFunInvocation ? INVOKESPECIAL : INVOKEVIRTUAL;
|
||||||
isInterfaceMember = superCall && currentIsInterface;
|
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
|
@kotlin.Metadata
|
||||||
final class Controller$multipleSuspensions$1 {
|
final class Controller$multipleSuspensions$1 {
|
||||||
|
field L$0: java.lang.Object
|
||||||
synthetic final field this$0: Controller
|
synthetic final field this$0: Controller
|
||||||
inner class Controller$multipleSuspensions$1
|
inner class Controller$multipleSuspensions$1
|
||||||
method <init>(p0: Controller, p1: kotlin.coroutines.experimental.Continuation): void
|
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
|
@kotlin.Metadata
|
||||||
final class Controller$nonTailCall$1 {
|
final class Controller$nonTailCall$1 {
|
||||||
|
field L$0: java.lang.Object
|
||||||
synthetic final field this$0: Controller
|
synthetic final field this$0: Controller
|
||||||
inner class Controller$nonTailCall$1
|
inner class Controller$nonTailCall$1
|
||||||
method <init>(p0: Controller, p1: kotlin.coroutines.experimental.Continuation): void
|
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
|
@kotlin.Metadata
|
||||||
@@ -28,9 +30,9 @@ public final class Controller {
|
|||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
final class CoroutineFieldsKt$box$1 {
|
final class CoroutineFieldsKt$box$1 {
|
||||||
synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef
|
synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef
|
||||||
private field J$0: long
|
field J$0: long
|
||||||
private field L$0: java.lang.Object
|
field L$0: java.lang.Object
|
||||||
private field L$1: java.lang.Object
|
field L$1: java.lang.Object
|
||||||
private field p$: Controller
|
private field p$: Controller
|
||||||
inner class CoroutineFieldsKt$box$1
|
inner class CoroutineFieldsKt$box$1
|
||||||
method <init>(p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void
|
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")
|
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user