diff --git a/.idea/dictionaries/dzharkov.xml b/.idea/dictionaries/dzharkov.xml
index 63f1ee091b8..91938406f8b 100644
--- a/.idea/dictionaries/dzharkov.xml
+++ b/.idea/dictionaries/dzharkov.xml
@@ -4,6 +4,7 @@
checkcast
coroutine
insn
+ liveness
\ No newline at end of file
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java
index 73101de50dd..39f9dae7be8 100644
--- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java
@@ -2592,7 +2592,15 @@ public class ExpressionCodegen extends KtVisitor impleme
Callable callable = resolveToCallable(fd, superCallTarget != null, resolvedCall);
- return callable.invokeMethodWithArguments(resolvedCall, receiver, this);
+
+ StackValue result = callable.invokeMethodWithArguments(resolvedCall, receiver, this);
+
+ if (CoroutineCodegenUtilKt.isSuspensionPoint(resolvedCall)) {
+ // Suspension points should behave like they leave actual values on stack, while real methods return VOID
+ return new OperationStackValue(getSuspensionReturnTypeByResolvedCall(resolvedCall), ((OperationStackValue) result).getLambda());
+ }
+
+ return result;
}
@Nullable
@@ -2685,14 +2693,20 @@ public class ExpressionCodegen extends KtVisitor impleme
}
if (isSuspensionPoint) {
+ v.aconst(getSuspensionReturnTypeByResolvedCall(resolvedCall).getDescriptor());
v.invokestatic(
CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
- CoroutineCodegenUtilKt.SUSPENSION_POINT_MARKER_NAME, "()V", false);
+ CoroutineCodegenUtilKt.BEFORE_SUSPENSION_POINT_MARKER_NAME,
+ "(Ljava/lang/String;)V", false);
}
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
if (isSuspensionPoint) {
+ StackValue.coerce(Type.VOID_TYPE, getSuspensionReturnTypeByResolvedCall(resolvedCall), v);
+ v.invokestatic(
+ CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER,
+ CoroutineCodegenUtilKt.AFTER_SUSPENSION_POINT_MARKER_NAME, "()V", false);
addInlineMarker(v, false);
}
@@ -2703,6 +2717,18 @@ public class ExpressionCodegen extends KtVisitor impleme
}
}
+ @NotNull
+ private Type getSuspensionReturnTypeByResolvedCall(@NotNull ResolvedCall> resolvedCall) {
+ assert resolvedCall.getResultingDescriptor() instanceof SimpleFunctionDescriptor
+ : "Suspension point resolved call should be built on SimpleFunctionDescriptor";
+
+ KotlinType returnType = org.jetbrains.kotlin.resolve.coroutine.CoroutineUtilKt
+ .getSuspensionPointReturnType((SimpleFunctionDescriptor) resolvedCall.getResultingDescriptor());
+
+ assert returnType != null : "Return type of suspension point should not be null";
+ return typeMapper.mapType(returnType);
+ }
+
@NotNull
private CallGenerator getOrCreateCallGenerator(
@NotNull CallableDescriptor descriptor,
@@ -2719,7 +2745,8 @@ public class ExpressionCodegen extends KtVisitor impleme
if (!isInline) return defaultCallGenerator;
- FunctionDescriptor original = DescriptorUtils.unwrapFakeOverride((FunctionDescriptor) descriptor.getOriginal());
+ FunctionDescriptor original =
+ unwrapInitialSignatureDescriptor(DescriptorUtils.unwrapFakeOverride((FunctionDescriptor) descriptor.getOriginal()));
if (isDefaultCompilation) {
return new InlineCodegenForDefaultBody(original, this, state);
}
@@ -2728,6 +2755,12 @@ public class ExpressionCodegen extends KtVisitor impleme
}
}
+ @NotNull
+ private static FunctionDescriptor unwrapInitialSignatureDescriptor(@NotNull FunctionDescriptor function) {
+ if (function.getInitialSignatureDescriptor() != null) return function.getInitialSignatureDescriptor();
+ return function;
+ }
+
@NotNull
protected CallGenerator getOrCreateCallGeneratorForDefaultImplBody(@NotNull FunctionDescriptor descriptor, @Nullable KtNamedFunction function) {
return getOrCreateCallGenerator(descriptor, function, null, true);
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt
index 0051bd10f6d..a3473edce3a 100644
--- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt
@@ -16,11 +16,12 @@
package org.jetbrains.kotlin.codegen.coroutines
+import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
-import org.jetbrains.kotlin.codegen.optimization.common.asSequence
+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.transformer.MethodTransformer
@@ -31,7 +32,6 @@ import org.jetbrains.org.objectweb.asm.MethodVisitor
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.commons.Method
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
@@ -78,12 +78,14 @@ class CoroutineTransformerMethodVisitor(
if (suspensionPoints.isEmpty()) return
// Spill stack to variables before suspension points, try/catch blocks
- FixStackWithLabelNormalizationMethodTransformer().transform("fake", methodNode)
+ FixStackWithLabelNormalizationMethodTransformer().transform(classBuilder.thisName, methodNode)
// Remove unreachable suspension points
// If we don't do this, then relevant frames will not be analyzed, that is unexpected from point of view of next steps (e.g. variable spilling)
- DeadCodeEliminationMethodTransformer().transform("fake", methodNode)
- suspensionPoints.removeAll { it.suspensionCall.next == null && it.suspensionCall.previous == null }
+ DeadCodeEliminationMethodTransformer().transform(classBuilder.thisName, methodNode)
+ suspensionPoints.removeAll {
+ it.suspensionCallBegin.next == null && it.suspensionCallBegin.previous == null
+ }
processUninitializedStores(methodNode)
@@ -116,50 +118,98 @@ class CoroutineTransformerMethodVisitor(
})
}
+ dropSuspensionMarkers(methodNode, suspensionPoints)
methodNode.removeEmptyCatchBlocks()
}
private fun collectSuspensionPoints(methodNode: MethodNode): MutableList {
val suspensionPoints = mutableListOf()
+ val beforeSuspensionPointMarkerStack = Stack()
- for (methodInsn in methodNode.instructions.asSequence().filterIsInstance()) {
+ for (methodInsn in methodNode.instructions.toArray().filterIsInstance()) {
if (methodInsn.owner != COROUTINE_MARKER_OWNER) continue
when (methodInsn.name) {
- SUSPENSION_POINT_MARKER_NAME -> {
- assert(methodInsn.next is MethodInsnNode) {
- "Expected method call instruction after suspension point, but ${methodInsn.next} found"
+ BEFORE_SUSPENSION_POINT_MARKER_NAME -> {
+ beforeSuspensionPointMarkerStack.add(methodInsn)
+ }
+
+ AFTER_SUSPENSION_POINT_MARKER_NAME -> {
+ assert(beforeSuspensionPointMarkerStack.isNotEmpty()) { "Expected non-empty stack beforeSuspensionPointMarkerStack" }
+ val beforeSuspensionPointMarker = beforeSuspensionPointMarkerStack.pop()
+ assert(beforeSuspensionPointMarker.previous is LdcInsnNode) {
+ "Instruction before BEFORE_SUSPENSION_POINT_MARKER_NAME should be LDC with expected return type, " +
+ "but ${beforeSuspensionPointMarker.previous} was found"
}
- suspensionPoints.add(SuspensionPoint(methodInsn.next as MethodInsnNode))
+ val fakeReturnValueInsns = mutableListOf()
+
+ val suspensionPoint = SuspensionPoint(
+ suspensionCallBegin = beforeSuspensionPointMarker,
+ suspensionCallEnd = methodInsn,
+ fakeReturnValueInsns = fakeReturnValueInsns,
+ returnType = Type.getType((beforeSuspensionPointMarker.previous as LdcInsnNode).cst as String))
+ suspensionPoints.add(suspensionPoint)
+
+ // Drop type info from marker
+ methodNode.instructions.remove(beforeSuspensionPointMarker.previous)
+ beforeSuspensionPointMarker.desc = "()V"
+
+ if (suspensionPoint.returnType != Type.VOID_TYPE) {
+ val previous = suspensionPoint.suspensionCallEnd.previous
+ assert(previous.opcode in DEFAULT_VALUE_OPCODES) {
+ "Expected on of default value bytecodes, but ${previous.opcode} was found"
+ }
+ fakeReturnValueInsns.add(previous)
+
+ if (previous.opcode == Opcodes.ACONST_NULL) {
+ // Checkcast is needed to tell analyzer what type exactly should be returned from suspension point
+ val checkCast = TypeInsnNode(Opcodes.CHECKCAST, suspensionPoint.returnType.internalName)
+ methodNode.instructions.insert(previous, checkCast)
+ fakeReturnValueInsns.add(checkCast)
+ }
+ }
}
}
}
- // Drop markers
- suspensionPoints.forEach { methodNode.instructions.remove(it.suspensionCall.previous) }
+ assert(beforeSuspensionPointMarkerStack.isEmpty()) { "Unbalanced suspension markers stack" }
return suspensionPoints
}
+ private fun dropSuspensionMarkers(methodNode: MethodNode, suspensionPoints: List) {
+ // Drop markers
+ suspensionPoints.forEach {
+ // before-marker
+ methodNode.instructions.remove(it.suspensionCallBegin)
+ // after-marker
+ methodNode.instructions.remove(it.suspensionCallEnd)
+ }
+ }
+
private fun spillVariables(suspensionPoints: List, methodNode: MethodNode) {
val instructions = methodNode.instructions
- val frames = MethodTransformer.analyze("fake", methodNode, OptimizationBasicInterpreter())
+ val frames = MethodTransformer.analyze(classBuilder.thisName, methodNode, OptimizationBasicInterpreter())
fun AbstractInsnNode.index() = instructions.indexOf(this)
// We postpone these actions because they change instruction indices that we use when obtaining frames
val postponedActions = mutableListOf<() -> Unit>()
val maxVarsCountByType = mutableMapOf()
+ val livenessFrames = analyzeLiveness(methodNode)
for (suspension in suspensionPoints) {
- val call = suspension.suspensionCall
- assert(frames[call.next.index()].stackSize == (if (Type.getReturnType(call.desc).sort == Type.VOID) 0 else 1)) {
+ val suspensionCallBegin = suspension.suspensionCallBegin
+ val suspensionCallEnd = suspension.suspensionCallEnd
+ assert(frames[suspensionCallEnd.next.index()].stackSize == (if (suspension.returnType.sort == Type.VOID) 0 else 1)) {
"Stack should be spilled before suspension call"
}
val variableTypeInTable = arrayOfNulls(methodNode.maxLocals)
- methodNode.localVariables.filter { it.start.index() < call.index() && call.index() < it.end.index() }.forEach {
+ methodNode.localVariables.filter {
+ it.start.index() < suspensionCallBegin.index() && suspensionCallEnd.index() < it.end.index()
+ }.forEach {
val type = Type.getType(it.desc)
variableTypeInTable[it.index] = type
if (type.size == 2) {
@@ -167,9 +217,24 @@ class CoroutineTransformerMethodVisitor(
}
}
- val frame = frames[call.index()]
+ val frame = frames[suspensionCallBegin.index()]
val localsCount = frame.locals
val varsCountByType = mutableMapOf()
+
+ // We consider variable liveness to avoid problems with inline suspension functions:
+ //
+ // *
+ // RETURN (appears only on further transformation phase)
+ // ...
+ //
+ //
+ // The problem is that during current phase (before inserting RETURN opcode) we suppose variables generated
+ // within inline suspension point as correctly initialized, thus trying to spill them.
+ // While after RETURN introduction these variables become uninitialized (at the same time they can't be used further).
+ // So we only spill variables that are alive at the begin of suspension point.
+ // NB: it's also rather useful for sake of optimization
+ val livenessFrame = livenessFrames[suspensionCallBegin.index()]
+
// 0 - this
// 1 - continuation argument
// 2 - continuation exception
@@ -179,6 +244,7 @@ class CoroutineTransformerMethodVisitor(
.filter {
val (index, value) = it
value != BasicValue.UNINITIALIZED_VALUE
+ && livenessFrame.isAlive(index)
&& variableTypeInTable[index] !== UNINITIALIZED_TYPE
&& (variableTypeInTable[index] == null || variableTypeInTable[index]?.sort == value.type.sort)
}
@@ -195,7 +261,7 @@ class CoroutineTransformerMethodVisitor(
postponedActions.add {
with(instructions) {
// store variable before suspension call
- insertBefore(call, withInstructionAdapter {
+ insertBefore(suspension.suspensionCallBegin, withInstructionAdapter {
load(0, AsmTypes.OBJECT_TYPE)
load(index, type)
StackValue.coerce(type, normalizedType, this)
@@ -203,7 +269,7 @@ class CoroutineTransformerMethodVisitor(
})
// restore variable after suspension call
- insert(call.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
+ insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
load(0, AsmTypes.OBJECT_TYPE)
getfield(classBuilder.thisName, fieldName, normalizedType.descriptor)
StackValue.coerce(normalizedType, type, this)
@@ -230,33 +296,34 @@ class CoroutineTransformerMethodVisitor(
}
}
- private val MethodInsnNode.tryCatchBlockEndLabelAfterSuspensionCall: LabelNode
+ /**
+ * See 'splitTryCatchBlocksContainingSuspensionPoint'
+ */
+ private val SuspensionPoint.tryCatchBlockEndLabelAfterSuspensionCall: LabelNode
get() {
- assert(next is LabelNode) {
- "Next instruction after ${this} should be a label, but ${next.javaClass}/${next.opcode} was found"
+ assert(suspensionCallEnd.next is LabelNode) {
+ "Next instruction after ${this} should be a label, but " +
+ "${suspensionCallEnd.next.javaClass}/${suspensionCallEnd.next.opcode} was found"
}
- return next as LabelNode
+ return suspensionCallEnd.next as LabelNode
}
private fun transformCallAndReturnContinuationLabel(id: Int, suspension: SuspensionPoint, methodNode: MethodNode): LabelNode {
- val call = suspension.suspensionCall
- val method = Method(call.name, call.desc)
- call.desc = Method(method.name, Type.VOID_TYPE, method.argumentTypes).descriptor
-
val continuationLabel = LabelNode()
-
with(methodNode.instructions) {
// Save state
- insertBefore(call,
+ insertBefore(suspension.suspensionCallBegin,
insnListOf(
VarInsnNode(Opcodes.ALOAD, 0),
*withInstructionAdapter { iconst(id) }.toArray(),
FieldInsnNode(
Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor)))
+ // Drop default value that purpose was to simulate returned value by suspension method
+ suspension.fakeReturnValueInsns.forEach { methodNode.instructions.remove(it) }
- insert(call.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
+ insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
// Exit
areturn(Type.VOID_TYPE)
// Mark place for continuation
@@ -274,7 +341,6 @@ class CoroutineTransformerMethodVisitor(
}
remove(possibleTryCatchBlockStart.previous)
-
insert(possibleTryCatchBlockStart, withInstructionAdapter {
// Check if resumeWithException has been called
load(2, AsmTypes.OBJECT_TYPE)
@@ -288,7 +354,7 @@ class CoroutineTransformerMethodVisitor(
// Load continuation argument just like suspending function returns it
load(1, AsmTypes.OBJECT_TYPE)
- StackValue.coerce(AsmTypes.OBJECT_TYPE, method.returnType, this)
+ StackValue.coerce(AsmTypes.OBJECT_TYPE, suspension.returnType, this)
})
}
@@ -300,7 +366,11 @@ class CoroutineTransformerMethodVisitor(
// otherwise they would be treated as uninitialized within catch-block while they can be used there
// How suspension point area will look like after all transformations:
//
- // INVOKEVIRTUAL suspensionMethod()
+ // INVOKESTATIC beforeSuspensionMarker
+ // INVOKEVIRTUAL suspensionMethod()V
+ // ACONST_NULL -- default value
+ // CHECKCAST SomeType
+ // INVOKESTATIC afterSuspensionMarker
// L1: -- end of all TCB's that are containing the suspension point (inserted by this method)
// RETURN
// L2: -- continuation label (used for the TABLESWITCH)
@@ -309,11 +379,12 @@ class CoroutineTransformerMethodVisitor(
// ...
private fun splitTryCatchBlocksContainingSuspensionPoint(methodNode: MethodNode, suspensionPoint: SuspensionPoint) {
val instructions = methodNode.instructions
- val indexOfSuspension = instructions.indexOf(suspensionPoint.suspensionCall)
+ val beginIndex = instructions.indexOf(suspensionPoint.suspensionCallBegin)
+ val endIndex = instructions.indexOf(suspensionPoint.suspensionCallEnd)
val firstLabel = LabelNode()
val secondLabel = LabelNode()
- instructions.insert(suspensionPoint.suspensionCall, firstLabel)
+ instructions.insert(suspensionPoint.suspensionCallEnd, firstLabel)
// NOP is needed to preventing these label merge
// Here between these labels additional instructions are supposed to be inserted (variables spilling, etc.)
instructions.insert(firstLabel, InsnNode(Opcodes.NOP))
@@ -322,11 +393,15 @@ class CoroutineTransformerMethodVisitor(
methodNode.tryCatchBlocks =
methodNode.tryCatchBlocks.flatMap {
val isContainingSuspensionPoint =
- instructions.indexOf(it.start) < indexOfSuspension && indexOfSuspension < instructions.indexOf(it.end)
+ instructions.indexOf(it.start) < beginIndex && beginIndex < instructions.indexOf(it.end)
- if (isContainingSuspensionPoint)
+ if (isContainingSuspensionPoint) {
+ assert(instructions.indexOf(it.start) < endIndex && endIndex < instructions.indexOf(it.end)) {
+ "Try catch block containing marker before suspension point should also contain the marker after suspension point"
+ }
listOf(TryCatchBlockNode(it.start, firstLabel, it.handler, it.type),
TryCatchBlockNode(secondLabel, it.end, it.handler, it.type))
+ }
else
listOf(it)
}
@@ -384,8 +459,28 @@ private fun Type.normalize() =
else -> this
}
-private class SuspensionPoint(val suspensionCall: MethodInsnNode) {
+/**
+ * Suspension call may consists of several instructions:
+ * INVOKESTATIC beforeSuspensionMarker
+ * INVOKEVIRTUAL suspensionMethod()V // actually it could be some inline method instead of plain call
+ * ACONST_NULL // It's only needed when the suspension point returns something beside VOID
+ * CHECKCAST Type
+ * INVOKESTATIC afterSuspensionMarker
+ */
+private class SuspensionPoint(
+ // INVOKESTATIC beforeSuspensionMarker
+ val suspensionCallBegin: AbstractInsnNode,
+ // INVOKESTATIC afterSuspensionMarker
+ val suspensionCallEnd: AbstractInsnNode,
+ val fakeReturnValueInsns: List,
+ val returnType: Type
+) {
lateinit var tryCatchBlocksContinuationLabel: LabelNode
}
private val UNINITIALIZED_TYPE = Type.getObjectType("uninitialized")
+private val DEFAULT_VALUE_OPCODES =
+ setOf(Opcodes.ICONST_0, Opcodes.LCONST_0, Opcodes.FCONST_0, Opcodes.DCONST_0, Opcodes.ACONST_NULL,
+ // GETSTATIC Unit.Instance
+ Opcodes.GETSTATIC)
+
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt
index d7ff7752cfd..1ed609da940 100644
--- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.resolve.calls.util.CallMaker
+import org.jetbrains.kotlin.resolve.coroutine.REPLACED_SUSPENSION_POINT_KEY
import org.jetbrains.kotlin.resolve.coroutine.SUSPENSION_POINT_KEY
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
import org.jetbrains.kotlin.types.TypeSubstitutor
@@ -41,7 +42,8 @@ import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
val CONTINUATION_METHOD_ANNOTATION_DESC = "Lkotlin/ContinuationMethod;"
const val COROUTINE_MARKER_OWNER = "kotlin/coroutines/Markers"
-const val SUSPENSION_POINT_MARKER_NAME = "suspensionPoint"
+const val BEFORE_SUSPENSION_POINT_MARKER_NAME = "beforeSuspensionPoint"
+const val AFTER_SUSPENSION_POINT_MARKER_NAME = "afterSuspensionPoint"
const val HANDLE_EXCEPTION_MARKER_NAME = "handleException"
const val HANDLE_EXCEPTION_ARGUMENT_MARKER_NAME = "handleExceptionArgument"
@@ -54,7 +56,7 @@ data class ResolvedCallWithRealDescriptor(val resolvedCall: ResolvedCall<*>, val
// E.g. `fun await(f: CompletableFuture): V` instead of `fun await(f: CompletableFuture, machine: Continuation): Unit`
// See `createCoroutineSuspensionFunctionView` and it's usages for clarification
// But for call generation it's convenient to have `machine` (continuation) parameter/argument within resolvedCall.
-// So this function returns resolved call with descriptor looking like `fun await(f: CompletableFuture, machine: Continuation): V`
+// So this function returns resolved call with descriptor looking like `fun await(f: CompletableFuture, machine: Continuation): Unit`
// and fake `this` expression that used as argument for second parameter
fun ResolvedCall<*>.replaceSuspensionFunctionViewWithRealDescriptor(
project: Project
@@ -63,11 +65,14 @@ fun ResolvedCall<*>.replaceSuspensionFunctionViewWithRealDescriptor(
if (!isSuspensionPoint()) return null
val initialSignatureDescriptor = function.initialSignatureDescriptor ?: return null
+ if (function.getUserData(REPLACED_SUSPENSION_POINT_KEY) == true) return null
+
val newCandidateDescriptor =
initialSignatureDescriptor.createCustomCopy {
- // Here we know that last parameter should be Continuation where T is return type
- setReturnType(it.valueParameters.last().type.arguments.single().type)
+ setPreserveSourceElement()
+ setSignatureChange()
putUserData(SUSPENSION_POINT_KEY, true)
+ putUserData(REPLACED_SUSPENSION_POINT_KEY, true)
}
val newCall = ResolvedCallImpl(
@@ -88,8 +93,12 @@ fun ResolvedCall<*>.replaceSuspensionFunctionViewWithRealDescriptor(
newCandidateDescriptor.valueParameters.last(),
ExpressionValueArgument(arguments))
+ val newTypeArguments = newCandidateDescriptor.typeParameters.map {
+ Pair(it, typeArguments[candidateDescriptor.typeParameters[it.index]]!!.asTypeProjection())
+ }.toMap()
+
newCall.setResultingSubstitutor(
- TypeConstructorSubstitution.createByParametersMap(typeArguments.mapValues { it.value.asTypeProjection() }).buildSubstitutor())
+ TypeConstructorSubstitution.createByParametersMap(newTypeArguments).buildSubstitutor())
return ResolvedCallWithRealDescriptor(newCall, thisExpression)
}
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt
new file mode 100644
index 00000000000..e9695c6ebde
--- /dev/null
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.codegen.optimization.common
+
+import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
+import org.jetbrains.org.objectweb.asm.tree.InsnList
+import org.jetbrains.org.objectweb.asm.tree.MethodNode
+import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
+
+
+class ControlFlowGraph private constructor(private val insns: InsnList) {
+ private val edges: Array> = Array(insns.size()) { arrayListOf() }
+
+ fun getSuccessorsIndices(insn: AbstractInsnNode): List = edges[insns.indexOf(insn)]
+
+ companion object {
+ @JvmStatic
+ fun build(node: MethodNode): ControlFlowGraph {
+ val graph = ControlFlowGraph(node.instructions)
+
+ fun addEdge(from: Int, to: Int) {
+ graph.edges[from].add(to)
+ }
+
+ object : MethodAnalyzer("fake", node, OptimizationBasicInterpreter()) {
+ override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean {
+ addEdge(insn, successor)
+ return true
+ }
+
+ override fun visitControlFlowExceptionEdge(insn: Int, successor: Int): Boolean {
+ addEdge(insn, successor)
+ return true
+ }
+ }.analyze()
+
+ return graph
+ }
+ }
+}
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java
index 514de0a164d..abec8001ec1 100644
--- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java
@@ -21,7 +21,6 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
-import org.jetbrains.org.objectweb.asm.tree.LdcInsnNode;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
@@ -57,6 +56,14 @@ public class OptimizationBasicInterpreter extends BasicInterpreter {
}
}
+ @Override
+ public BasicValue newOperation(@NotNull AbstractInsnNode insn) throws AnalyzerException {
+ if (insn.getOpcode() == Opcodes.ACONST_NULL) {
+ return newValue(Type.getObjectType("java/lang/Object"));
+ }
+ return super.newOperation(insn);
+ }
+
@NotNull
@Override
public BasicValue merge(
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt
index 89d268b9644..50e0765796c 100644
--- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.codegen.optimization.common
+import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
@@ -125,3 +126,6 @@ val AbstractInsnNode.intConstant: Int? get() =
}
fun insnListOf(vararg insns: AbstractInsnNode) = InsnList().apply { insns.forEach { add(it) } }
+
+fun AbstractInsnNode.isStoreOperation(): Boolean = getOpcode() in Opcodes.ISTORE..Opcodes.ASTORE
+fun AbstractInsnNode.isLoadOperation(): Boolean = getOpcode() in Opcodes.ILOAD..Opcodes.ALOAD
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt
new file mode 100644
index 00000000000..e0f448f03c7
--- /dev/null
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.codegen.optimization.common
+
+import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
+import org.jetbrains.org.objectweb.asm.tree.IincInsnNode
+import org.jetbrains.org.objectweb.asm.tree.MethodNode
+import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
+import java.util.*
+
+
+class VariableLivenessFrame(val maxLocals: Int) {
+ private val bitSet = BitSet(maxLocals)
+
+ fun mergeFrom(other: VariableLivenessFrame) {
+ bitSet.or(other.bitSet)
+ }
+
+ fun markAlive(varIndex: Int) {
+ bitSet.set(varIndex, true)
+ }
+
+ fun markDead(varIndex: Int) {
+ bitSet.set(varIndex, false)
+ }
+
+ fun isAlive(varIndex: Int): Boolean = bitSet.get(varIndex)
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is VariableLivenessFrame) return false
+ return bitSet == other.bitSet
+ }
+
+ override fun hashCode() = bitSet.hashCode()
+}
+
+fun analyzeLiveness(node: MethodNode): Array {
+ val graph = ControlFlowGraph.build(node)
+ val insnList = node.instructions
+ val frames = Array(insnList.size()) { VariableLivenessFrame(node.maxLocals) }
+ val insnArray = insnList.toArray()
+
+ val varVisibility = Array(node.maxLocals) { BitSet(insnArray.size) }
+ for (localVar in node.localVariables) {
+ varVisibility[localVar.index].set(insnList.indexOf(localVar.start), insnList.indexOf(localVar.end), true)
+ }
+
+ // see Figure 9.16 from Dragon book
+ var wereChanges: Boolean
+
+ do {
+ wereChanges = false
+ for (insn in insnArray) {
+ val index = insnList.indexOf(insn)
+ val newFrame = VariableLivenessFrame(node.maxLocals)
+ for (successorIndex in graph.getSuccessorsIndices(insn)) {
+ newFrame.mergeFrom(frames[successorIndex])
+ }
+
+ def(newFrame, insn)
+ use(newFrame, insn, index, varVisibility)
+
+ if (frames[index] != newFrame) {
+ frames[index] = newFrame
+ wereChanges = true
+ }
+ }
+
+ } while (wereChanges)
+
+ return frames
+}
+
+private fun def(frame: VariableLivenessFrame, insn: AbstractInsnNode) {
+ if (insn is VarInsnNode && insn.isStoreOperation()) {
+ frame.markDead(insn.`var`)
+ }
+}
+
+private fun use(frame: VariableLivenessFrame, insn: AbstractInsnNode, index: Int, varVisibility: Array) {
+ for (i in 0..frame.maxLocals - 1) {
+ if (varVisibility[i].get(index)) {
+ frame.markAlive(i)
+ }
+ }
+
+ if (insn is VarInsnNode && insn.isLoadOperation()) {
+ frame.markAlive(insn.`var`)
+ }
+ else if (insn is IincInsnNode) {
+ frame.markAlive(insn.`var`)
+ }
+}
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt
index 6b7f13ae735..e9b83ef22c7 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.incremental.KotlinLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
-import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.sure
@@ -48,11 +47,6 @@ object SuspendModifierChecker : SimpleDeclarationChecker {
diagnosticHolder.report(Errors.INAPPLICABLE_MODIFIER.on(suspendModifierElement, KtTokens.SUSPEND_KEYWORD, message))
}
- if (functionDescriptor.isInline) {
- report("inline suspend functions are not supported")
- return
- }
-
val isValidContinuation = functionDescriptor.valueParameters.lastOrNull()?.type?.isValidContinuation() ?: false
if (!isValidContinuation) {
report("last parameter of suspend function should have a type of Continuation")
diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/coroutine/coroutineUtil.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/coroutine/coroutineUtil.kt
index 231ef0c5363..8b82f5fbc0b 100644
--- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/coroutine/coroutineUtil.kt
+++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/coroutine/coroutineUtil.kt
@@ -23,12 +23,13 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.types.KotlinType
val SUSPENSION_POINT_KEY: FunctionDescriptor.UserDataKey = object : FunctionDescriptor.UserDataKey {}
+val REPLACED_SUSPENSION_POINT_KEY: FunctionDescriptor.UserDataKey = object : FunctionDescriptor.UserDataKey {}
// Returns suspension function as it's visible within coroutines:
// E.g. `fun await(f: CompletableFuture): V` instead of `fun await(f: CompletableFuture, machine: Continuation): Unit`
fun SimpleFunctionDescriptor.createCoroutineSuspensionFunctionView(): SimpleFunctionDescriptor? {
if (!isSuspend) return null
- val returnType = valueParameters.lastOrNull()?.returnType?.arguments?.getOrNull(0)?.type ?: return null
+ val returnType = getSuspensionPointReturnType() ?: return null
val newOriginal =
if (original !== this)
@@ -45,4 +46,6 @@ fun SimpleFunctionDescriptor.createCoroutineSuspensionFunctionView(): SimpleFunc
}.build()!!
}
+fun SimpleFunctionDescriptor.getSuspensionPointReturnType(): KotlinType? = valueParameters.lastOrNull()?.returnType?.arguments?.getOrNull(0)?.type
+
class CoroutineReceiverValue(callableDescriptor: CallableDescriptor, receiverType: KotlinType) : ExtensionReceiver(callableDescriptor, receiverType)
diff --git a/compiler/testData/codegen/box/coroutines/inlineSuspendFunction.kt b/compiler/testData/codegen/box/coroutines/inlineSuspendFunction.kt
new file mode 100644
index 00000000000..e8d07ad7d16
--- /dev/null
+++ b/compiler/testData/codegen/box/coroutines/inlineSuspendFunction.kt
@@ -0,0 +1,40 @@
+// WITH_RUNTIME
+class Controller {
+ fun withValue(v: String, x: Continuation) {
+ x.resume(v)
+ }
+
+ suspend inline fun suspendInline(v: String, x: Continuation) {
+ withValue(v, x)
+ }
+
+ suspend inline fun suspendInline(crossinline b: () -> String, x: Continuation) {
+ suspendInline(b(), x)
+ }
+
+ suspend inline fun suspendInline(x: Continuation) {
+ suspendInline({ T::class.java.simpleName }, x)
+ }
+}
+
+fun builder(coroutine c: Controller.() -> Continuation) {
+ c(Controller()).resume(Unit)
+}
+
+class OK
+
+fun box(): String {
+ var result = ""
+
+ builder {
+ result = suspendInline("56")
+ if (result != "56") throw RuntimeException("fail 1")
+
+ result = suspendInline { "57" }
+ if (result != "57") throw RuntimeException("fail 2")
+
+ result = suspendInline()
+ }
+
+ return result
+}
diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt b/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt
index 1f424d2f039..28af94ccc0d 100644
--- a/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt
+++ b/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt
@@ -8,7 +8,7 @@ class Controller {
}
- inline suspend fun inlineFun(x: Continuation) {
+ inline suspend fun inlineFun(x: Continuation) {
}
diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java
index 26f70ce9f75..5a4dc671af4 100644
--- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java
@@ -4159,6 +4159,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
+ @TestMetadata("inlineSuspendFunction.kt")
+ public void testInlineSuspendFunction() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlineSuspendFunction.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("inlinedTryCatchFinally.kt")
public void testInlinedTryCatchFinally() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlinedTryCatchFinally.kt");
diff --git a/idea/testData/checker/infos/suspendApplicability.kt b/idea/testData/checker/infos/suspendApplicability.kt
index 114dde4c4f4..8c08fdb6fc6 100644
--- a/idea/testData/checker/infos/suspendApplicability.kt
+++ b/idea/testData/checker/infos/suspendApplicability.kt
@@ -8,7 +8,7 @@ class Controller {
}
- inline suspend fun inlineFun(x: Continuation) {
+ inline suspend fun inlineFun(x: Continuation) {
}