JVM_IR: Unbox inline classes in suspend functions
if the function can be verified to safely returning unboxed inline class. Box the return value on resume path.
This commit is contained in:
+1
-1
@@ -491,7 +491,7 @@ fun InstructionAdapter.loadCoroutineSuspendedMarker(languageVersionSettings: Lan
|
||||
)
|
||||
}
|
||||
|
||||
internal fun InstructionAdapter.generateCoroutineSuspendedCheck(languageVersionSettings: LanguageVersionSettings) {
|
||||
fun InstructionAdapter.generateCoroutineSuspendedCheck(languageVersionSettings: LanguageVersionSettings) {
|
||||
dup()
|
||||
loadCoroutineSuspendedMarker(languageVersionSettings)
|
||||
val elseLabel = Label()
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeProjectionImpl
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
@@ -414,17 +415,21 @@ 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)
|
||||
// Suspend functions always returns Any?, but the unboxing disrupts type analysis of the bytecode.
|
||||
// For example, if the underlying type is String, CHECKCAST String is removed.
|
||||
// However, the unboxing is moved to the resume path, the direct path still has Any?, but now, without the CHECKCAST.
|
||||
// Thus, we add CHECKCAST Object, which we remove, after we copy the unboxing to the resume path.
|
||||
v.checkcast(AsmTypes.OBJECT_TYPE)
|
||||
addAfterUnboxInlineClassMarker(v)
|
||||
generateResumePathUnboxing(v, inlineClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateResumePathUnboxing(v: InstructionAdapter, inlineClass: KotlinType) {
|
||||
addBeforeUnboxInlineClassMarker(v)
|
||||
StackValue.unboxInlineClass(AsmTypes.OBJECT_TYPE, inlineClass, v)
|
||||
// Suspend functions always returns Any?, but the unboxing disrupts type analysis of the bytecode.
|
||||
// For example, if the underlying type is String, CHECKCAST String is removed.
|
||||
// However, the unboxing is moved to the resume path, the direct path still has Any?, but now, without the CHECKCAST.
|
||||
// Thus, we add CHECKCAST Object, which we remove, after we copy the unboxing to the resume path.
|
||||
v.checkcast(AsmTypes.OBJECT_TYPE)
|
||||
addAfterUnboxInlineClassMarker(v)
|
||||
}
|
||||
|
||||
private fun addBeforeUnboxInlineClassMarker(v: InstructionAdapter) {
|
||||
v.emitInlineMarker(INLINE_MARKER_BEFORE_UNBOX_INLINE_CLASS)
|
||||
}
|
||||
|
||||
+23
-2
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.backend.common.lower.LocalDeclarationsLowering
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
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.suspendFunctionOriginal
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor
|
||||
@@ -24,9 +26,8 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
|
||||
import org.jetbrains.kotlin.ir.types.createType
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
||||
import org.jetbrains.kotlin.ir.types.isUnit
|
||||
import org.jetbrains.kotlin.ir.util.file
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.isSuspend
|
||||
@@ -179,3 +180,23 @@ internal fun createFakeContinuation(context: JvmBackendContext): IrExpression =
|
||||
context.ir.symbols.continuationClass.createType(true, listOf(makeTypeProjection(context.irBuiltIns.anyNType, Variance.INVARIANT))),
|
||||
"FAKE_CONTINUATION"
|
||||
)
|
||||
|
||||
internal fun IrFunction.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(): IrType? {
|
||||
if (!isSuspend) return null
|
||||
// Check whether we in fact return inline class
|
||||
if (returnType.classOrNull?.owner?.isInline != true) return null
|
||||
val unboxedReturnType = returnType.makeNotNull().unboxInlineClass()
|
||||
// Force boxing for primitives
|
||||
if (unboxedReturnType.isPrimitiveType()) return null
|
||||
// Force boxing for nullable inline class types with nullable underlying type
|
||||
if (returnType.isNullable() && unboxedReturnType.isNullable()) return null
|
||||
// Force boxing if the function overrides function with different type modulo nullability ignoring type parameters
|
||||
if ((this as? IrSimpleFunction)?.let {
|
||||
it.overriddenSymbols.any { overridden ->
|
||||
(overridden.owner.returnType.isNullable() && overridden.owner.returnType.makeNotNull().unboxInlineClass().isNullable()) ||
|
||||
overridden.owner.returnType.makeNotNull().classOrNull != returnType.makeNotNull().classOrNull
|
||||
}
|
||||
} != false) return null
|
||||
// Don't box other inline classes
|
||||
return returnType
|
||||
}
|
||||
+58
-6
@@ -14,9 +14,11 @@ import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.SuspensionPointKind
|
||||
import org.jetbrains.kotlin.codegen.coroutines.generateCoroutineSuspendedCheck
|
||||
import org.jetbrains.kotlin.codegen.inline.*
|
||||
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedClassReificationMarker
|
||||
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS
|
||||
@@ -225,10 +227,20 @@ class ExpressionCodegen(
|
||||
if (irFunction.origin != JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER) {
|
||||
irFunction.markLineNumber(startOffset = irFunction is IrConstructor && irFunction.isPrimary)
|
||||
}
|
||||
val returnType = signature.returnType
|
||||
val returnIrType = if (irFunction !is IrConstructor) irFunction.returnType else context.irBuiltIns.unitType
|
||||
result.materializeAt(returnType, returnIrType)
|
||||
mv.areturn(returnType)
|
||||
if (irFunction.isSuspend && irFunction.origin == IrDeclarationOrigin.BRIDGE) {
|
||||
mv.areturn(OBJECT_TYPE)
|
||||
} else {
|
||||
var returnType = signature.returnType
|
||||
var returnIrType = if (irFunction !is IrConstructor) irFunction.returnType else context.irBuiltIns.unitType
|
||||
val unboxedInlineClass =
|
||||
irFunction.suspendFunctionOriginal().originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass()
|
||||
if (unboxedInlineClass != null) {
|
||||
returnIrType = unboxedInlineClass
|
||||
returnType = unboxedInlineClass.asmType
|
||||
}
|
||||
result.materializeAt(returnType, returnIrType)
|
||||
mv.areturn(returnType)
|
||||
}
|
||||
}
|
||||
val endLabel = markNewLabel()
|
||||
writeLocalVariablesInTable(info, endLabel)
|
||||
@@ -436,11 +448,32 @@ class ExpressionCodegen(
|
||||
if (irFunction.isInvokeSuspendOfContinuation()) IrCallGenerator.DefaultCallGenerator else callGenerator
|
||||
generatorForActualCall.genCall(callable, this, expression, isInsideCondition)
|
||||
|
||||
val unboxedInlineClassIrType =
|
||||
callee.suspendFunctionOriginal().originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass()
|
||||
|
||||
if (isSuspensionPoint != SuspensionPointKind.NEVER) {
|
||||
addSuspendMarker(mv, isStartNotEnd = false, isSuspensionPoint == SuspensionPointKind.NOT_INLINE)
|
||||
if (unboxedInlineClassIrType != null) {
|
||||
generateResumePathUnboxing(mv, unboxedInlineClassIrType.toIrBasedKotlinType())
|
||||
}
|
||||
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.toIrBasedKotlinType(), mv)
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
(expression.type.isNothing() || expression.type.isUnit()) && irFunction.shouldContainSuspendMarkers() -> {
|
||||
// NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail.
|
||||
@@ -461,6 +494,18 @@ class ExpressionCodegen(
|
||||
wrapJavaClassesIntoKClasses(mv)
|
||||
MaterialValue(this, AsmTypes.K_CLASS_ARRAY_TYPE, expression.type)
|
||||
}
|
||||
unboxedInlineClassIrType != null && !irFunction.isInvokeSuspendOfContinuation() ->
|
||||
object : PromisedValue(this, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType) {
|
||||
override fun materializeAt(target: Type, irTarget: IrType) {
|
||||
mv.checkcast(unboxedInlineClassIrType.asmType)
|
||||
MaterialValue(this@ExpressionCodegen, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType)
|
||||
.materializeAt(target, irTarget)
|
||||
}
|
||||
|
||||
override fun discard() {
|
||||
pop(mv, OBJECT_TYPE)
|
||||
}
|
||||
}
|
||||
else ->
|
||||
MaterialValue(this, callable.asmMethod.returnType, callee.returnType)
|
||||
}
|
||||
@@ -787,9 +832,16 @@ class ExpressionCodegen(
|
||||
return unitValue
|
||||
}
|
||||
|
||||
val returnType = if (owner == irFunction) signature.returnType else methodSignatureMapper.mapReturnType(owner)
|
||||
var returnType = if (owner == irFunction) signature.returnType else methodSignatureMapper.mapReturnType(owner)
|
||||
var returnIrType = owner.returnType
|
||||
|
||||
val unboxedInlineClass = owner.suspendFunctionOriginal().originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass()
|
||||
if (unboxedInlineClass != null) {
|
||||
returnIrType = unboxedInlineClass
|
||||
returnType = unboxedInlineClass.asmType
|
||||
}
|
||||
val afterReturnLabel = Label()
|
||||
expression.value.accept(this, data).materializeAt(returnType, owner.returnType)
|
||||
expression.value.accept(this, data).materializeAt(returnType, returnIrType)
|
||||
generateFinallyBlocksIfNeeded(returnType, afterReturnLabel, data)
|
||||
expression.markLineNumber(startOffset = true)
|
||||
if (isNonLocalReturn) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
import helpers.*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
import helpers.*
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
import helpers.*
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// FULL_JDK
|
||||
// WITH_RUNTIME
|
||||
|
||||
-2
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
@Suppress("RESULT_CLASS_IN_RETURN_TYPE")
|
||||
suspend fun signInFlowStepFirst(): Result<Unit> = Result.success(Unit)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user