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) { }