Support suspendCoroutineUninterceptedOrReturn intrinsic

KT-17336: Fixed
This commit is contained in:
Ilmir Usmanov
2017-10-27 21:03:17 +03:00
parent 4c583c5a61
commit 0fbbe10143
13 changed files with 420 additions and 8 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.backend.common
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyGetterDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
@@ -26,11 +27,23 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
val SUSPEND_COROUTINE_OR_RETURN_NAME = Name.identifier("suspendCoroutineOrReturn")
val INTERCEPTED_NAME = Name.identifier("intercepted")
val COROUTINE_SUSPENDED_NAME = Name.identifier("COROUTINE_SUSPENDED")
val COROUTINES_INTRINSICS_PACKAGE_FQ_NAME = DescriptorUtils.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("intrinsics"))
val COROUTINE_CONTEXT_FQ_NAME = COROUTINES_INTRINSICS_PACKAGE_FQ_NAME.child(Name.identifier("coroutineContext"))
val SUSPEND_COROUTINE_UNINTERCEPTED_OR_RETURN_NAME = Name.identifier("suspendCoroutineUninterceptedOrReturn")
fun FunctionDescriptor.isBuiltInIntercepted(): Boolean {
if (name != INTERCEPTED_NAME) return false
val original =
module.getPackage(COROUTINES_INTRINSICS_PACKAGE_FQ_NAME).memberScope
.getContributedFunctions(INTERCEPTED_NAME, NoLookupLocation.FROM_BACKEND)
.singleOrNull() as CallableDescriptor
return DescriptorEquivalenceForOverrides.areEquivalent(original, this)
}
fun FunctionDescriptor.isBuiltInSuspendCoroutineOrReturn(): Boolean {
if (name != SUSPEND_COROUTINE_OR_RETURN_NAME) return false
@@ -49,3 +62,10 @@ fun FunctionDescriptor.getBuiltInSuspendCoroutineOrReturn() =
fun FunctionDescriptor.isBuiltInCoroutineContext() =
(this as? PropertyGetterDescriptor)?.correspondingProperty?.fqNameSafe == COROUTINE_CONTEXT_FQ_NAME
fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn(): Boolean {
if (name != SUSPEND_COROUTINE_UNINTERCEPTED_OR_RETURN_NAME) return false
val original = module.getPackage(COROUTINES_INTRINSICS_PACKAGE_FQ_NAME).memberScope
.getContributedFunctions(SUSPEND_COROUTINE_UNINTERCEPTED_OR_RETURN_NAME, NoLookupLocation.FROM_BACKEND)
.singleOrNull() as CallableDescriptor
return DescriptorEquivalenceForOverrides.areEquivalent(original, this)
}
@@ -172,7 +172,7 @@ private fun NewResolvedCallImpl<VariableDescriptor>.asDummyOldResolvedCall(bindi
fun ResolvedCall<*>.isSuspendNoInlineCall() =
resultingDescriptor.safeAs<FunctionDescriptor>()
?.let {
it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm())
it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm() || it.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm())
} == true
fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean {
@@ -245,7 +245,6 @@ fun ModuleDescriptor.getContinuationOfTypeOrAny(kotlinType: KotlinType) =
)
} ?: module.builtIns.nullableAnyType
fun FunctionDescriptor.isBuiltInSuspendCoroutineOrReturnInJvm() =
getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION)?.isBuiltInSuspendCoroutineOrReturn() == true
@@ -289,6 +288,40 @@ fun createMethodNodeForSuspendCoroutineOrReturn(
return node
}
fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm() =
getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION)?.isBuiltInSuspendCoroutineUninterceptedOrReturn() == true
fun createMethodNodeForIntercepted(
functionDescriptor: FunctionDescriptor,
typeMapper: KotlinTypeMapper
): MethodNode {
assert(functionDescriptor.isBuiltInIntercepted()) {
"functionDescriptor must be kotlin.coroutines.intrinsics.intercepted"
}
val node =
MethodNode(
Opcodes.ASM5,
Opcodes.ACC_STATIC,
"fake",
typeMapper.mapAsmMethod(functionDescriptor).descriptor, null, null
)
node.visitVarInsn(Opcodes.ALOAD, 0)
node.visitMethodInsn(
Opcodes.INVOKESTATIC,
INTERNAL_COROUTINE_INTRINSICS_OWNER_INTERNAL_NAME,
NORMALIZE_CONTINUATION_METHOD_NAME,
Type.getMethodDescriptor(CONTINUATION_ASM_TYPE, CONTINUATION_ASM_TYPE),
false
)
node.visitInsn(Opcodes.ARETURN)
node.visitMaxs(1, 1)
return node
}
fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor): MethodNode {
assert(functionDescriptor.isBuiltInCoroutineContext()) {
"functionDescriptor must be kotlin.coroutines.intrinsics.coroutineContext property getter"
@@ -318,6 +351,39 @@ fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor):
return node
}
fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn(
functionDescriptor: FunctionDescriptor,
typeMapper: KotlinTypeMapper
): MethodNode {
assert(functionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm()) {
"functionDescriptor must be kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn"
}
val node =
MethodNode(
Opcodes.ASM5,
Opcodes.ACC_STATIC,
"fake",
typeMapper.mapAsmMethod(functionDescriptor).descriptor, null, null
)
node.visitVarInsn(Opcodes.ALOAD, 0)
node.visitVarInsn(Opcodes.ALOAD, 1)
node.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
typeMapper.mapType(functionDescriptor.valueParameters[0]).internalName,
OperatorNameConventions.INVOKE.identifier,
"(${AsmTypes.OBJECT_TYPE})${AsmTypes.OBJECT_TYPE}",
true
)
node.visitInsn(Opcodes.ARETURN)
node.visitMaxs(2, 2)
return node
}
@Suppress("UNCHECKED_CAST")
fun <D : CallableDescriptor?> D.unwrapInitialDescriptorForSuspendFunction(): D =
this.safeAs<SimpleFunctionDescriptor>()?.getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION) as D ?: this
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.inline
import com.intellij.psi.PsiElement
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.backend.common.isBuiltInCoroutineContext
import org.jetbrains.kotlin.backend.common.isBuiltInIntercepted
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags
@@ -490,13 +491,23 @@ abstract class InlineCodegen<out T: BaseExpressionCodegen>(
}
functionDescriptor.isBuiltInSuspendCoroutineOrReturnInJvm() ->
return SMAPAndMethodNode(
createMethodNodeForSuspendCoroutineOrReturn(functionDescriptor, state.typeMapper),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
createMethodNodeForSuspendCoroutineOrReturn(functionDescriptor, state.typeMapper),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
)
functionDescriptor.isBuiltInIntercepted() ->
return SMAPAndMethodNode(
createMethodNodeForIntercepted(functionDescriptor, state.typeMapper),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
)
functionDescriptor.isBuiltInCoroutineContext() ->
return SMAPAndMethodNode(
createMethodNodeForCoroutineContext(functionDescriptor),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
createMethodNodeForCoroutineContext(functionDescriptor),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
)
functionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm() ->
return SMAPAndMethodNode(
createMethodNodeForSuspendCoroutineUninterceptedOrReturn(functionDescriptor, state.typeMapper),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
)
}
@@ -0,0 +1,82 @@
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.experimental.*
import kotlin.coroutines.experimental.intrinsics.*
import kotlin.test.assertEquals
suspend fun suspendHereUnintercepted(): String = suspendCoroutineUninterceptedOrReturn { x ->
x.resume("OK")
COROUTINE_SUSPENDED
}
suspend fun suspendWithExceptionUnintercepted(): String = suspendCoroutineUninterceptedOrReturn { x ->
x.resumeWithException(RuntimeException("OK"))
COROUTINE_SUSPENDED
}
suspend fun suspendHereIntercepted(): String = suspendCoroutineUninterceptedOrReturn { x ->
x.intercepted().resume("OK")
COROUTINE_SUSPENDED
}
suspend fun suspendWithExceptionIntercepted(): String = suspendCoroutineUninterceptedOrReturn { x ->
x.intercepted().resumeWithException(RuntimeException("OK"))
COROUTINE_SUSPENDED
}
fun builder(expectedCount: Int, c: suspend () -> String): String {
var fromSuspension: String? = null
var counter = 0
val result = try {
c.startCoroutineUninterceptedOrReturn(object: Continuation<String> {
override val context: CoroutineContext
get() = ContinuationDispatcher { counter++ }
override fun resumeWithException(exception: Throwable) {
fromSuspension = "Exception: " + exception.message!!
}
override fun resume(value: String) {
fromSuspension = value
}
})
} catch (e: Exception) {
"Exception: ${e.message}"
}
if (counter != expectedCount) throw RuntimeException("fail 0")
return fromSuspension!!
}
class ContinuationDispatcher(val dispatcher: () -> Unit) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(dispatcher, continuation)
}
private class DispatchedContinuation<T>(
val dispatcher: () -> Unit,
val continuation: Continuation<T>
): Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resume(value: T) {
dispatcher()
continuation.resume(value)
}
override fun resumeWithException(exception: Throwable) {
dispatcher()
continuation.resumeWithException(exception)
}
}
fun box(): String {
if (builder(0) { suspendHereUnintercepted() } != "OK") return "fail 2"
if (builder(1) { suspendHereIntercepted() } != "OK") return "fail 3"
if (builder(0) { suspendWithExceptionUnintercepted() } != "Exception: OK") return "fail 4"
if (builder(1) { suspendWithExceptionIntercepted() } != "Exception: OK") return "fail 5"
return "OK"
}
@@ -0,0 +1,80 @@
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.experimental.*
import kotlin.coroutines.experimental.intrinsics.*
import kotlin.test.assertEquals
suspend fun suspendHere(): String = suspendCoroutineUninterceptedOrReturn { x ->
x.resume("OK")
COROUTINE_SUSPENDED
}
suspend fun suspendWithException(): String = suspendCoroutineUninterceptedOrReturn { x ->
x.resumeWithException(RuntimeException("OK"))
COROUTINE_SUSPENDED
}
fun builder(shouldSuspend: Boolean, c: suspend () -> String): String {
var fromSuspension: String? = null
var counter = 0
val result = try {
c.startCoroutineUninterceptedOrReturn(object: Continuation<String> {
override val context: CoroutineContext
get() = ContinuationDispatcher { counter++ }
override fun resumeWithException(exception: Throwable) {
fromSuspension = "Exception: " + exception.message!!
}
override fun resume(value: String) {
fromSuspension = value
}
})
} catch (e: Exception) {
"Exception: ${e.message}"
}
if (counter != 0) throw RuntimeException("fail 0 $counter")
if (shouldSuspend) {
if (result !== COROUTINE_SUSPENDED) throw RuntimeException("fail 1")
if (fromSuspension == null) throw RuntimeException("fail 2")
return fromSuspension!!
}
if (result === COROUTINE_SUSPENDED) throw RuntimeException("fail 3")
return result as String
}
class ContinuationDispatcher(val dispatcher: () -> Unit) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(dispatcher, continuation)
}
private class DispatchedContinuation<T>(
val dispatcher: () -> Unit,
val continuation: Continuation<T>
): Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resume(value: T) {
dispatcher()
continuation.resume(value)
}
override fun resumeWithException(exception: Throwable) {
dispatcher()
continuation.resumeWithException(exception)
}
}
fun box(): String {
if (builder(false) { "OK" } != "OK") return "fail 4"
if (builder(true) { suspendHere() } != "OK") return "fail 5"
if (builder(false) { throw RuntimeException("OK") } != "Exception: OK") return "fail 6"
if (builder(true) { suspendWithException() } != "Exception: OK") return "fail 7"
return "OK"
}
@@ -6064,6 +6064,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
@TestMetadata("intercepted.kt")
public void testIntercepted() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt");
doTest(fileName);
}
@TestMetadata("startCoroutine.kt")
public void testStartCoroutine() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt");
@@ -6081,6 +6087,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt");
doTest(fileName);
}
@TestMetadata("suspendCoroutineUninterceptedOrReturn.kt")
public void testSuspendCoroutineUninterceptedOrReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/multiModule")
@@ -6064,6 +6064,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("intercepted.kt")
public void testIntercepted() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt");
doTest(fileName);
}
@TestMetadata("startCoroutine.kt")
public void testStartCoroutine() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt");
@@ -6081,6 +6087,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt");
doTest(fileName);
}
@TestMetadata("suspendCoroutineUninterceptedOrReturn.kt")
public void testSuspendCoroutineUninterceptedOrReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/multiModule")
@@ -6064,6 +6064,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
@TestMetadata("intercepted.kt")
public void testIntercepted() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt");
doTest(fileName);
}
@TestMetadata("startCoroutine.kt")
public void testStartCoroutine() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt");
@@ -6081,6 +6087,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt");
doTest(fileName);
}
@TestMetadata("suspendCoroutineUninterceptedOrReturn.kt")
public void testSuspendCoroutineUninterceptedOrReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/multiModule")