Box inline class in resume path of suspend call

#KT-41429
This commit is contained in:
Ilmir Usmanov
2020-09-01 20:53:49 +02:00
parent 023cdd7cd4
commit ccc5b7afe0
6 changed files with 87 additions and 19 deletions
@@ -46,7 +46,6 @@ import org.jetbrains.kotlin.config.ApiVersion;
import org.jetbrains.kotlin.config.JVMAssertionsMode;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor;
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor;
@@ -2735,8 +2734,11 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
if (maybeSuspensionPoint) {
// The order is important! Do not reorder!
// CoroutineTransformerMethodVisitor expects the marker exactly in this order.
addReturnsUnitMarkerIfNecessary(v, resolvedCall);
addSuspendMarker(v, false, suspensionPointKind == SuspensionPointKind.NOT_INLINE);
addUnboxInlineClassMarkersIfNeeded(v, resolvedCall.getResultingDescriptor(), typeMapper);
addInlineMarker(v, false);
}
@@ -636,6 +636,9 @@ class CoroutineCodegenForNamedFunction private constructor(
override val passArityToSuperClass get() = false
private val inlineClassToBoxInInvokeSuspend: KotlinType? =
originalSuspendFunctionDescriptor.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(state.typeMapper)
override fun generateBridges() {
// Do not generate any closure bridges
}
@@ -719,6 +722,22 @@ class CoroutineCodegenForNamedFunction private constructor(
callableMethod.genInvokeInstruction(codegen.v)
}
if (inlineClassToBoxInInvokeSuspend != null) {
with(codegen.v) {
// We need to box the returned inline class in resume path.
// But first, check for COROUTINE_SUSPENDED, since the function can return it
dup()
loadCoroutineSuspendedMarker(languageVersionSettings)
val elseLabel = Label()
ifacmpne(elseLabel)
areturn(AsmTypes.OBJECT_TYPE)
mark(elseLabel)
// Now we box the inline class
StackValue.coerce(AsmTypes.OBJECT_TYPE, typeMapper.mapType(inlineClassToBoxInInvokeSuspend), this)
StackValue.boxInlineClass(inlineClassToBoxInInvokeSuspend, this)
}
}
codegen.v.visitInsn(Opcodes.ARETURN)
}
}
@@ -114,6 +114,7 @@ class CoroutineTransformerMethodVisitor(
if (examiner.allSuspensionPointsAreTailCalls(suspensionPoints)) {
examiner.replacePopsBeforeSafeUnitInstancesWithCoroutineSuspendedChecks()
dropSuspensionMarkers(methodNode)
dropUnboxInlineClassMarkers(methodNode, suspensionPoints)
return
}
@@ -190,6 +191,7 @@ class CoroutineTransformerMethodVisitor(
initializeFakeInlinerVariables(methodNode, stateLabels)
dropSuspensionMarkers(methodNode)
dropUnboxInlineClassMarkers(methodNode, suspensionPoints)
methodNode.removeEmptyCatchBlocks()
if (languageVersionSettings.isReleaseCoroutines()) {
@@ -599,6 +601,17 @@ class CoroutineTransformerMethodVisitor(
}
}
private fun dropUnboxInlineClassMarkers(methodNode: MethodNode, suspensionPoints: List<SuspensionPoint>) {
for (marker in methodNode.instructions.asSequence()
.filter { isBeforeUnboxInlineClassMarker(it) || isAfterUnboxInlineClassMarker(it) }.toList()
) {
methodNode.instructions.removeAll(listOf(marker.previous, marker))
}
for (suspension in suspensionPoints) {
methodNode.instructions.removeAll(suspension.unboxInlineClassInstructions)
}
}
private fun spillVariables(suspensionPoints: List<SuspensionPoint>, methodNode: MethodNode): List<List<SpilledVariableAndField>> {
val instructions = methodNode.instructions
val frames =
@@ -918,6 +931,11 @@ class CoroutineTransformerMethodVisitor(
// Load continuation argument just like suspending function returns it
load(dataIndex, AsmTypes.OBJECT_TYPE)
// Unbox inline class, since this is the resume path and unlike the direct path
// the class is boxed.
for (insn in suspension.unboxInlineClassInstructions) {
insn.accept(this)
}
visitLabel(continuationLabelAfterLoadedResult.label)
@@ -1117,6 +1135,16 @@ internal class SuspensionPoint(
) {
lateinit var tryCatchBlocksContinuationLabel: LabelNode
val unboxInlineClassInstructions: List<AbstractInsnNode> = findUnboxInlineClassInstructions()
private fun findUnboxInlineClassInstructions(): List<AbstractInsnNode> {
val beforeMarker = suspensionCallEnd.next?.next ?: return emptyList()
if (!isBeforeUnboxInlineClassMarker(beforeMarker)) return emptyList()
val afterMarker = beforeMarker.findNextOrNull { isAfterUnboxInlineClassMarker(it) }
?: error("Before unbox inline class marker without after unbox inline class marker")
return InsnSequence(beforeMarker.next, afterMarker.previous).toList()
}
operator fun contains(insn: AbstractInsnNode): Boolean {
for (i in InsnSequence(suspensionCallBegin, suspensionCallEnd.next)) {
if (i == insn) return true
@@ -33,12 +33,14 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -111,8 +113,8 @@ fun computeLabelOwner(languageVersionSettings: LanguageVersionSettings, thisName
else
languageVersionSettings.coroutinesJvmInternalPackageFqName().child(Name.identifier("CoroutineImpl")).topLevelClassAsmType()
private val NORMALIZE_CONTINUATION_METHOD_NAME = "normalizeContinuation"
private val GET_CONTEXT_METHOD_NAME = "getContext"
private const val NORMALIZE_CONTINUATION_METHOD_NAME = "normalizeContinuation"
private const val GET_CONTEXT_METHOD_NAME = "getContext"
data class ResolvedCallWithRealDescriptor(val resolvedCall: ResolvedCall<*>, val fakeContinuationExpression: KtExpression)
@@ -140,11 +142,11 @@ fun ResolvedCall<*>.replaceSuspensionFunctionWithRealDescriptor(
if (this is VariableAsFunctionResolvedCall) {
val replacedFunctionCall =
functionCall.replaceSuspensionFunctionWithRealDescriptor(project, bindingContext, isReleaseCoroutines)
?: return null
?: return null
@Suppress("UNCHECKED_CAST")
return replacedFunctionCall.copy(
VariableAsFunctionResolvedCallImpl(
resolvedCall = VariableAsFunctionResolvedCallImpl(
replacedFunctionCall.resolvedCall as MutableResolvedCall<FunctionDescriptor>,
variableCall.asMutableResolvedCall(bindingContext)
)
@@ -413,7 +415,8 @@ fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn(languageVersionSett
load(1, OBJECT_TYPE) // continuation
checkcast(languageVersionSettings.continuationAsmType())
invokestatic(
languageVersionSettings.coroutinesJvmInternalPackageFqName().child(Name.identifier("DebugProbesKt")).topLevelClassAsmType().internalName,
languageVersionSettings.coroutinesJvmInternalPackageFqName().child(Name.identifier("DebugProbesKt"))
.topLevelClassAsmType().internalName,
"probeCoroutineSuspended",
"(${languageVersionSettings.continuationAsmType()})V",
false
@@ -466,7 +469,7 @@ fun FunctionDescriptor.originalReturnTypeOfSuspendFunctionReturningUnboxedInline
val originalReturnType = originalDescriptor.returnType ?: return null
if (!originalReturnType.isInlineClassType()) return null
// Force boxing for primitives
if (AsmUtil.isPrimitive(typeMapper.mapType(originalReturnType))) return null
if (AsmUtil.isPrimitive(typeMapper.mapType(originalReturnType.makeNotNullable()))) return null
// Force boxing for nullable inline class types with nullable underlying type
if (originalReturnType.isMarkedNullable && originalReturnType.isNullableUnderlyingType()) return null
// Force boxing if the function overrides function with return type Any
@@ -474,8 +477,6 @@ fun FunctionDescriptor.originalReturnTypeOfSuspendFunctionReturningUnboxedInline
(it.original.returnType?.isMarkedNullable == true && it.original.returnType?.isNullableUnderlyingType() == true) ||
it.original.returnType?.makeNotNullable() != originalReturnType.makeNotNullable()
}) return null
// TODO: Do not box `Result`
if (originalReturnType.constructor.declarationDescriptor?.fqNameSafe == StandardNames.RESULT_FQ_NAME) return null
// Don't box other inline classes
return originalReturnType
}
@@ -7,9 +7,7 @@ package org.jetbrains.kotlin.codegen.inline
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.ASSERTIONS_DISABLED_FIELD_NAME
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.BaseExpressionCodegen
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.SamWrapperCodegen.SAM_WRAPPER_SUFFIX
import org.jetbrains.kotlin.codegen.`when`.WhenByEnumsMapping
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
@@ -17,16 +15,14 @@ import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.CodegenContextUtil
import org.jetbrains.kotlin.codegen.context.InlineLambdaContext
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.coroutines.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.intConstant
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
@@ -80,6 +76,8 @@ private const val INLINE_MARKER_BEFORE_FAKE_CONTINUATION_CONSTRUCTOR_CALL = 4
private const val INLINE_MARKER_AFTER_FAKE_CONTINUATION_CONSTRUCTOR_CALL = 5
private const val INLINE_MARKER_BEFORE_INLINE_SUSPEND_ID = 6
private const val INLINE_MARKER_AFTER_INLINE_SUSPEND_ID = 7
private const val INLINE_MARKER_BEFORE_UNBOX_INLINE_CLASS = 8
private const val INLINE_MARKER_AFTER_UNBOX_INLINE_CLASS = 9
internal fun getMethodNode(
classData: ByteArray,
@@ -413,6 +411,23 @@ fun addInlineMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
)
}
internal fun addUnboxInlineClassMarkersIfNeeded(v: InstructionAdapter, descriptor: CallableDescriptor, typeMapper: KotlinTypeMapper) {
val inlineClass = (descriptor as? FunctionDescriptor)?.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(typeMapper)
if (inlineClass != null) {
addBeforeUnboxInlineClassMarker(v)
StackValue.unboxInlineClass(AsmTypes.OBJECT_TYPE, inlineClass, v)
addAfterUnboxInlineClassMarker(v)
}
}
private fun addBeforeUnboxInlineClassMarker(v: InstructionAdapter) {
v.emitInlineMarker(INLINE_MARKER_BEFORE_UNBOX_INLINE_CLASS)
}
private fun addAfterUnboxInlineClassMarker(v: InstructionAdapter) {
v.emitInlineMarker(INLINE_MARKER_AFTER_UNBOX_INLINE_CLASS)
}
internal fun addReturnsUnitMarkerIfNecessary(v: InstructionAdapter, resolvedCall: ResolvedCall<*>) {
val wrapperDescriptor = resolvedCall.candidateDescriptor.safeAs<FunctionDescriptor>() ?: return
val unsubstitutedDescriptor = wrapperDescriptor.unwrapInitialDescriptorForSuspendFunction()
@@ -477,6 +492,8 @@ internal fun isAfterInlineSuspendMarker(insn: AbstractInsnNode) = isSuspendMarke
internal fun isReturnsUnitMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKER_RETURNS_UNIT)
internal fun isFakeContinuationMarker(insn: AbstractInsnNode) =
insn.previous != null && isSuspendMarker(insn.previous, INLINE_MARKER_FAKE_CONTINUATION) && insn.opcode == Opcodes.ACONST_NULL
internal fun isBeforeUnboxInlineClassMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKER_BEFORE_UNBOX_INLINE_CLASS)
internal fun isAfterUnboxInlineClassMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKER_AFTER_UNBOX_INLINE_CLASS)
internal fun isBeforeFakeContinuationConstructorCallMarker(insn: AbstractInsnNode) =
isSuspendMarker(insn, INLINE_MARKER_BEFORE_FAKE_CONTINUATION_CONSTRUCTOR_CALL)
@@ -21,7 +21,8 @@ suspend fun signInFlowStepFirst(): Result<Unit> = try {
fun box(): String {
builder {
signInFlowStepFirst()
val res: Result<Unit> = signInFlowStepFirst()
if (res.exceptionOrNull()!!.message != "BOOYA") error("FAIL")
}
continuation!!.resumeWithException(Exception("BOOYA"))
return "OK"