JVM_IR: do not box inline classes in suspend multifile bridges

This commit is contained in:
pyos
2021-04-22 12:39:49 +02:00
committed by Ilmir Usmanov
parent 7d95943b8b
commit 23ffbe4d9e
9 changed files with 97 additions and 58 deletions
@@ -10608,6 +10608,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt"); runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt");
} }
@Test
@TestMetadata("multifileBridge.kt")
public void testMultifileBridge() throws Exception {
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/multifileBridge.kt");
}
@Test @Test
@TestMetadata("overrideSuspendFun.kt") @TestMetadata("overrideSuspendFun.kt")
public void testOverrideSuspendFun() throws Exception { public void testOverrideSuspendFun() throws Exception {
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.isStaticInlineClassReplacement import org.jetbrains.kotlin.backend.jvm.ir.isStaticInlineClassReplacement
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.backend.jvm.lower.isMultifileBridge import org.jetbrains.kotlin.backend.jvm.lower.isMultifileBridge
import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal
@@ -141,7 +142,7 @@ private fun IrFunction.isStaticInlineClassReplacementDelegatingCall(): Boolean =
parentAsClass.declarations.find { it is IrAttributeContainer && it.attributeOwnerId == attributeOwnerId && it !== this } parentAsClass.declarations.find { it is IrAttributeContainer && it.attributeOwnerId == attributeOwnerId && it !== this }
?.isStaticInlineClassReplacement == true ?.isStaticInlineClassReplacement == true
internal val BRIDGE_ORIGINS = setOf( private val BRIDGE_ORIGINS = setOf(
IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER, IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER,
JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER, JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER,
JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR,
@@ -151,12 +152,17 @@ internal val BRIDGE_ORIGINS = setOf(
IrDeclarationOrigin.BRIDGE_SPECIAL, IrDeclarationOrigin.BRIDGE_SPECIAL,
) )
internal fun IrFunction.shouldContainSuspendMarkers(): Boolean = origin !in BRIDGE_ORIGINS && // These functions contain a single `suspend` tail call, the value of which should be returned as is
// (i.e. if it's an unboxed inline class value, it should remain unboxed).
internal fun IrFunction.isNonBoxingSuspendDelegation(): Boolean =
origin in BRIDGE_ORIGINS || isMultifileBridge() || isBridgeToSuspendImplMethod()
internal fun IrFunction.shouldContainSuspendMarkers(): Boolean = !isNonBoxingSuspendDelegation() &&
// These functions also contain a single `suspend` tail call, but if it returns an unboxed inline class value,
// the return of it should be checked for a suspension and potentially boxed to satisfy an interface.
origin != IrDeclarationOrigin.DELEGATED_MEMBER && origin != IrDeclarationOrigin.DELEGATED_MEMBER &&
!isInvokeSuspendOfContinuation() && !isInvokeSuspendOfContinuation() &&
!isMultifileBridge() &&
!isInvokeOfSuspendCallableReference() && !isInvokeOfSuspendCallableReference() &&
!isBridgeToSuspendImplMethod() &&
!isStaticInlineClassReplacementDelegatingCall() !isStaticInlineClassReplacementDelegatingCall()
internal fun IrFunction.hasContinuation(): Boolean = isInvokeSuspendOfLambda() || internal fun IrFunction.hasContinuation(): Boolean = isInvokeSuspendOfLambda() ||
@@ -186,9 +192,8 @@ internal fun createFakeContinuation(context: JvmBackendContext): IrExpression =
internal fun IrFunction.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(): IrType? { internal fun IrFunction.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(): IrType? {
if (!isSuspend) return null if (!isSuspend) return null
// Check whether we in fact return inline class // Check whether we in fact return inline class
if (returnType.classOrNull?.owner?.isInline != true) return null val unboxedReturnType = InlineClassAbi.unboxType(returnType.makeNotNull()) ?: return null
val unboxedReturnType = returnType.makeNotNull().unboxInlineClass() // Force boxing for primitives. NOTE: this also forbids unboxing a nullable inline class into a nullable primitive.
// Force boxing for primitives
if (unboxedReturnType.isPrimitiveType()) return null if (unboxedReturnType.isPrimitiveType()) return null
// Force boxing for nullable inline class types with nullable underlying type // Force boxing for nullable inline class types with nullable underlying type
if (returnType.isNullable() && unboxedReturnType.isNullable()) return null if (returnType.isNullable() && unboxedReturnType.isNullable()) return null
@@ -229,17 +229,9 @@ class ExpressionCodegen(
if (irFunction.origin != JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER) { if (irFunction.origin != JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER) {
irFunction.markLineNumber(startOffset = irFunction is IrConstructor && irFunction.isPrimary) irFunction.markLineNumber(startOffset = irFunction is IrConstructor && irFunction.isPrimary)
} }
val unboxedInlineClass = val (returnType, returnIrType) = irFunction.returnAsmAndIrTypes()
irFunction.suspendFunctionOriginal().originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass() result.materializeAt(returnType, returnIrType)
if (unboxedInlineClass != null && irFunction.origin !in BRIDGE_ORIGINS) { mv.areturn(returnType)
result.materializeAt(unboxedInlineClass.asmType, unboxedInlineClass)
} else {
val returnIrType = if (irFunction !is IrConstructor) irFunction.returnType else context.irBuiltIns.unitType
result.materializeAt(signature.returnType, returnIrType)
}
// `signature.returnType` is valid here even if the return value of a suspend function was unboxed,
// as it's still a reference type.
mv.areturn(signature.returnType)
} }
val endLabel = markNewLabel() val endLabel = markNewLabel()
writeLocalVariablesInTable(info, endLabel) writeLocalVariablesInTable(info, endLabel)
@@ -470,21 +462,6 @@ class ExpressionCodegen(
addInlineMarker(mv, isStartNotEnd = false) addInlineMarker(mv, isStartNotEnd = false)
} }
if (unboxedInlineClassIrType != null) {
val isFunctionReference = irFunction.origin != IrDeclarationOrigin.BRIDGE &&
irFunction.parentAsClass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
val isDelegateCall = irFunction.origin == IrDeclarationOrigin.DELEGATED_MEMBER
if (irFunction.isInvokeSuspendOfContinuation() || isFunctionReference || isDelegateCall) {
mv.generateCoroutineSuspendedCheck(state.languageVersionSettings)
mv.checkcast(unboxedInlineClassIrType.asmType)
}
if (irFunction.isInvokeSuspendOfContinuation()) {
StackValue.boxInlineClass(unboxedInlineClassIrType, mv, typeMapper)
}
}
return when { return when {
(expression.type.isNothing() || expression.type.isUnit()) && irFunction.shouldContainSuspendMarkers() -> { (expression.type.isNothing() || expression.type.isUnit()) && irFunction.shouldContainSuspendMarkers() -> {
// NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail. // NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail.
@@ -505,18 +482,20 @@ class ExpressionCodegen(
wrapJavaClassesIntoKClasses(mv) wrapJavaClassesIntoKClasses(mv)
MaterialValue(this, AsmTypes.K_CLASS_ARRAY_TYPE, expression.type) MaterialValue(this, AsmTypes.K_CLASS_ARRAY_TYPE, expression.type)
} }
unboxedInlineClassIrType != null && !irFunction.isInvokeSuspendOfContinuation() && irFunction.origin !in BRIDGE_ORIGINS -> unboxedInlineClassIrType != null && !irFunction.isNonBoxingSuspendDelegation() -> {
object : PromisedValue(this, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType) { if (!irFunction.shouldContainSuspendMarkers()) {
override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) { // Since the coroutine transformer won't run, we need to do this manually.
mv.checkcast(unboxedInlineClassIrType.asmType) mv.generateCoroutineSuspendedCheck(state.languageVersionSettings)
MaterialValue(this@ExpressionCodegen, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType)
.materializeAt(target, irTarget, castForReified)
}
override fun discard() {
pop(mv, OBJECT_TYPE)
}
} }
mv.checkcast(unboxedInlineClassIrType.asmType)
if (irFunction.isInvokeSuspendOfContinuation()) {
// TODO: why is simply materializing the value with type `Object` not enough? This branch shouldn't be needed.
StackValue.boxInlineClass(unboxedInlineClassIrType, mv, typeMapper)
MaterialValue(this, callable.asmMethod.returnType, callable.returnType)
} else {
MaterialValue(this, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType)
}
}
else -> else ->
MaterialValue(this, callable.asmMethod.returnType, callable.returnType) MaterialValue(this, callable.asmMethod.returnType, callable.returnType)
} }
@@ -899,20 +878,25 @@ class ExpressionCodegen(
} }
} }
private fun IrFunction.returnAsmAndIrTypes(): Pair<Type, IrType> {
val unboxedInlineClass = suspendFunctionOriginal().originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass()
// In case of non-boxing delegation, the return type of the tail call was considered to be `Object`,
// so that's also what we'll return here to avoid casts/unboxings/etc.
if (unboxedInlineClass != null && !isNonBoxingSuspendDelegation()) {
return unboxedInlineClass.asmType to unboxedInlineClass
}
val asmType = if (this == irFunction) signature.returnType else methodSignatureMapper.mapReturnType(this)
val irType = if (this is IrConstructor) context.irBuiltIns.unitType else returnType
return asmType to irType
}
override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue { override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue {
val returnTarget = expression.returnTargetSymbol.owner val returnTarget = expression.returnTargetSymbol.owner
val owner = returnTarget as? IrFunction ?: error("Unsupported IrReturnTarget: $returnTarget") val owner = returnTarget as? IrFunction ?: error("Unsupported IrReturnTarget: $returnTarget")
// TODO: should be owner != irFunction // TODO: should be owner != irFunction
val isNonLocalReturn = methodSignatureMapper.mapFunctionName(owner) != methodSignatureMapper.mapFunctionName(irFunction) val isNonLocalReturn = methodSignatureMapper.mapFunctionName(owner) != methodSignatureMapper.mapFunctionName(irFunction)
var returnType = if (owner == irFunction) signature.returnType else methodSignatureMapper.mapReturnType(owner) val (returnType, returnIrType) = owner.returnAsmAndIrTypes()
var returnIrType = owner.returnType
val unboxedInlineClass = owner.suspendFunctionOriginal().originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass()
if (unboxedInlineClass != null) {
returnIrType = unboxedInlineClass
returnType = unboxedInlineClass.asmType
}
val afterReturnLabel = Label() val afterReturnLabel = Label()
expression.value.accept(this, data).materializeAt(returnType, returnIrType) expression.value.accept(this, data).materializeAt(returnType, returnIrType)
// In case of non-local return from suspend lambda 'materializeAt' does not box return value, box it manually. // In case of non-local return from suspend lambda 'materializeAt' does not box return value, box it manually.
@@ -261,6 +261,7 @@ private fun IrSimpleFunction.createMultifileDelegateIfNeeded(
} }
} }
function.copyAttributes(target)
function.copyAnnotationsFrom(target) function.copyAnnotationsFrom(target)
function.copyParameterDeclarationsFrom(target) function.copyParameterDeclarationsFrom(target)
function.returnType = target.returnType.substitute(target.typeParameters, function.typeParameters.map { it.defaultType }) function.returnType = target.returnType.substitute(target.typeParameters, function.typeParameters.map { it.defaultType })
@@ -0,0 +1,29 @@
// TARGET_PLATFORM: JVM
// WITH_RUNTIME
// WITH_COROUTINES
// FILE: a.kt
@file:JvmMultifileClass
@file:JvmName("A")
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
@Suppress("UNSUPPORTED_FEATURE")
inline class I(val x: Any?)
suspend fun <T> suspendHere(x: T): T = suspendCoroutineUninterceptedOrReturn {
it.resume(x)
COROUTINE_SUSPENDED
}
suspend fun f(): I = I(suspendHere("OK"))
// FILE: z.kt
import helpers.*
import kotlin.coroutines.*
fun box(): String {
var result = "fail"
suspend { result = f().x as String }.startCoroutine(EmptyContinuation)
return result
}
@@ -8,16 +8,13 @@ import kotlin.coroutines.intrinsics.*
@Suppress("UNSUPPORTED_FEATURE") @Suppress("UNSUPPORTED_FEATURE")
inline class I(val x: Any?) inline class I(val x: Any?)
suspend fun suspendHere(): Unit = suspendCoroutineUninterceptedOrReturn { c -> suspend fun <T> suspendHere(x: T): T = suspendCoroutineUninterceptedOrReturn {
c.resume(Unit) it.resume(x)
COROUTINE_SUSPENDED COROUTINE_SUSPENDED
} }
class C { class C {
private suspend fun f(): I { private suspend fun f(): I = I(suspendHere("OK"))
suspendHere()
return I("OK")
}
fun g() = suspend { f() } fun g() = suspend { f() }
} }
@@ -10608,6 +10608,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt"); runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt");
} }
@Test
@TestMetadata("multifileBridge.kt")
public void testMultifileBridge() throws Exception {
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/multifileBridge.kt");
}
@Test @Test
@TestMetadata("overrideSuspendFun.kt") @TestMetadata("overrideSuspendFun.kt")
public void testOverrideSuspendFun() throws Exception { public void testOverrideSuspendFun() throws Exception {
@@ -10608,6 +10608,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt"); runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt");
} }
@Test
@TestMetadata("multifileBridge.kt")
public void testMultifileBridge() throws Exception {
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/multifileBridge.kt");
}
@Test @Test
@TestMetadata("overrideSuspendFun.kt") @TestMetadata("overrideSuspendFun.kt")
public void testOverrideSuspendFun() throws Exception { public void testOverrideSuspendFun() throws Exception {
@@ -8419,6 +8419,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt"); runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/invokeOperator.kt");
} }
@TestMetadata("multifileBridge.kt")
public void testMultifileBridge() throws Exception {
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/multifileBridge.kt");
}
@TestMetadata("overrideSuspendFun.kt") @TestMetadata("overrideSuspendFun.kt")
public void testOverrideSuspendFun() throws Exception { public void testOverrideSuspendFun() throws Exception {
runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/overrideSuspendFun.kt"); runTest("compiler/testData/codegen/box/coroutines/inlineClasses/resume/overrideSuspendFun.kt");