Support not boxing inline class of reference class

even if the class is other than Any.
 #KT-41429 In progress
This commit is contained in:
Ilmir Usmanov
2020-08-20 21:44:35 +02:00
parent 5c011bc954
commit 023cdd7cd4
9 changed files with 196 additions and 67 deletions
@@ -46,6 +46,7 @@ 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;
@@ -312,6 +313,8 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
stackValue = suspendFunctionTypeWrapperIfNeeded(selector, stackValue);
stackValue = coerceAndBoxInlineClassIfNeeded(selector, stackValue);
RuntimeAssertionInfo runtimeAssertionInfo = null;
if (selector instanceof KtExpression) {
KtExpression expression = (KtExpression) selector;
@@ -360,6 +363,53 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return stackValue;
}
private StackValue coerceAndBoxInlineClassIfNeeded(KtElement selector, StackValue stackValue) {
ResolvedCall<? extends CallableDescriptor> resolvedCall = CallUtilKt.getResolvedCall(selector, bindingContext);
if (resolvedCall == null) return stackValue;
CallableDescriptor descriptor = resolvedCall.getResultingDescriptor();
if (!(descriptor instanceof FunctionDescriptor)) return stackValue;
FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
if (!functionDescriptor.isSuspend()) return stackValue;
KotlinType unboxedInlineClass = CoroutineCodegenUtilKt
.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(functionDescriptor, typeMapper);
StackValue stackValueToWrap = stackValue;
KotlinType originalKotlinType;
if (unboxedInlineClass != null) {
originalKotlinType = unboxedInlineClass;
} else {
originalKotlinType = stackValueToWrap.kotlinType;
}
Type originalType;
if (unboxedInlineClass != null) {
originalType = typeMapper.mapType(unboxedInlineClass);
} else {
originalType = stackValueToWrap.type;
}
stackValue = new StackValue(originalType, originalKotlinType) {
@Override
public void putSelector(
@NotNull Type type, @Nullable KotlinType kotlinType, @NotNull InstructionAdapter v
) {
if (((originalKotlinType != null && InlineClassesUtilsKt.isInlineClassType(originalKotlinType)) ||
(kotlinType != null && InlineClassesUtilsKt.isInlineClassType(kotlinType)))
) {
stackValueToWrap.put(v);
// Suspend functions always return Ljava/lang/Object;, so, before inline-class boxing/unboxing we need to generate CHECKCAST
StackValue.coerce(OBJECT_TYPE, originalType, v);
// Box/unbox inline class
StackValue.coerce(originalType, originalKotlinType, type, kotlinType, v);
} else {
// No inline class -> usual coerce is enough
stackValueToWrap.put(type, kotlinType, v);
}
}
};
return stackValue;
}
public StackValue gen(KtElement expr) {
StackValue tempVar = tempVariables.get(expr);
return tempVar != null ? tempVar : genQualified(StackValue.none(), expr);
@@ -1654,12 +1704,32 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
Type returnType;
KotlinType returnKotlinType;
if (isNonLocalReturn) {
returnType = nonLocalReturn.returnType.getType();
returnKotlinType = nonLocalReturn.returnType.getKotlinType();
// This is inline lambda. Find inline-site and check, whether it is suspend functions returning unboxed inline class
CodegenContext<?> inlineSiteContext = this.context.getFirstCrossInlineOrNonInlineContext();
KotlinType originalInlineClass = null;
if (inlineSiteContext instanceof MethodContext) {
originalInlineClass = CoroutineCodegenUtilKt
.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(
((MethodContext) inlineSiteContext).getFunctionDescriptor(), typeMapper);
}
if (originalInlineClass != null) {
returnType = typeMapper.mapType(originalInlineClass);
returnKotlinType = originalInlineClass;
} else {
returnType = nonLocalReturn.returnType.getType();
returnKotlinType = nonLocalReturn.returnType.getKotlinType();
}
}
else {
returnType = this.returnType;
returnKotlinType = typeMapper.getReturnValueType(this.context.getFunctionDescriptor());
KotlinType originalInlineClass = CoroutineCodegenUtilKt
.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(context.getFunctionDescriptor(), typeMapper);
if (originalInlineClass != null) {
returnType = typeMapper.mapType(originalInlineClass);
returnKotlinType = originalInlineClass;
} else {
returnType = this.returnType;
returnKotlinType = this.context.getFunctionDescriptor().getReturnType();
}
}
StackValue valueToReturn = returnedExpression != null ? gen(returnedExpression) : StackValue.none();
@@ -1714,7 +1784,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return new NonLocalReturnInfo(
new JvmKotlinType(
typeMapper.mapReturnType(containingFunction),
typeMapper.getReturnValueType(containingFunction)
containingFunction.getReturnType()
),
FIRST_FUN_LABEL
);
@@ -1746,16 +1816,24 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
boolean isVoidCoroutineLambda =
originalSuspendLambdaDescriptor != null && TypeSignatureMappingKt.hasVoidReturnType(originalSuspendLambdaDescriptor);
KotlinType originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass =
CoroutineCodegenUtilKt.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(
context.getFunctionDescriptor(), typeMapper);
// If generating body for named block-bodied function or Unit-typed coroutine lambda, generate it as sequence of statements
Type typeForExpression =
isBlockedNamedFunction || isVoidCoroutineLambda
? Type.VOID_TYPE
: returnType;
: originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass != null
? typeMapper.mapType(originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass)
: returnType;
KotlinType kotlinTypeForExpression =
isBlockedNamedFunction || isVoidCoroutineLambda
? null
: typeMapper.getReturnValueType(context.getFunctionDescriptor());
: originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass != null
? originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass
: context.getFunctionDescriptor().getReturnType();
gen(expr, typeForExpression, kotlinTypeForExpression);
@@ -1060,7 +1060,7 @@ public class FunctionCodegen {
// or all return types are supertypes of inline class (and can't be inline classes).
for (DescriptorBasedFunctionHandleForJvm handle : bridge.getOriginalFunctions()) {
return state.getTypeMapper().getReturnValueType(handle.getDescriptor());
return handle.getDescriptor().getReturnType();
}
if (state.getClassBuilderMode().mightBeIncorrectCode) {
@@ -1457,7 +1457,7 @@ public class FunctionCodegen {
}
}
KotlinType returnValueType = state.getTypeMapper().getReturnValueType(descriptor);
KotlinType returnValueType = descriptor.getReturnType();
StackValue.coerce(delegateTo.getReturnType(), returnValueType, bridge.getReturnType(), bridgeReturnType, iv);
iv.areturn(bridge.getReturnType());
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.inline.addFakeContinuationMarker
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.coroutines.isSuspendLambda
import org.jetbrains.kotlin.descriptors.*
@@ -32,15 +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.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Label
@@ -453,6 +453,33 @@ fun FunctionDescriptor.getOriginalSuspendFunctionView(bindingContext: BindingCon
fun FunctionDescriptor.getOriginalSuspendFunctionView(bindingContext: BindingContext, state: GenerationState) =
getOriginalSuspendFunctionView(bindingContext, state.languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines))
// For each suspend function, we have a corresponding JVM view function that has an extra continuation parameter,
// and, more importantly, returns 'kotlin.Any' (so that it can return as a reference value or a special COROUTINE_SUSPENDED object).
// This also causes boxing of primitives and inline class values.
// If we have a function returning an inline class value that is mapped to a reference type, we want to avoid boxing.
// However, we have to do that consistently both on declaration site and on call site.
fun FunctionDescriptor.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(typeMapper: KotlinTypeMapper): KotlinType? {
if (!isSuspend) return null
// Suspend lambdas cannot return unboxed inline class
if (this is AnonymousFunctionDescriptor) return null
val originalDescriptor = unwrapInitialDescriptorForSuspendFunction().original
val originalReturnType = originalDescriptor.returnType ?: return null
if (!originalReturnType.isInlineClassType()) return null
// Force boxing for primitives
if (AsmUtil.isPrimitive(typeMapper.mapType(originalReturnType))) 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
if (originalDescriptor.overriddenDescriptors.any {
(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
}
fun InstructionAdapter.loadCoroutineSuspendedMarker(languageVersionSettings: LanguageVersionSettings) {
invokestatic(
languageVersionSettings.coroutinesIntrinsicsFileFacadeInternalName().internalName,
@@ -16,9 +16,7 @@ import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.isStaticMethod
import org.jetbrains.kotlin.codegen.JvmCodegenUtil.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.*
import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
import org.jetbrains.kotlin.codegen.coroutines.isSuspendFunctionNotSuspensionView
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
import org.jetbrains.kotlin.codegen.coroutines.*
import org.jetbrains.kotlin.codegen.inline.FictitiousArrayConstructor
import org.jetbrains.kotlin.codegen.signature.AsmTypeFactory
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
@@ -356,43 +354,6 @@ class KotlinTypeMapper @JvmOverloads constructor(
}
}
fun getReturnValueType(functionDescriptor: FunctionDescriptor): KotlinType =
getActualReturnTypeForSuspendFunctionWithInlineClassReturnTypeHack(functionDescriptor)
?: functionDescriptor.returnType!!
private fun getActualReturnTypeForSuspendFunctionWithInlineClassReturnTypeHack(functionDescriptor: FunctionDescriptor): KotlinType? {
// For each suspend function, we have a corresponding JVM view function that has an extra continuation parameter,
// and, more importantly, returns 'kotlin.Any' (so that it can return as a reference value or a special COROUTINE_SUSPENDED object).
// This also causes boxing of primitives and inline class values.
// If we have a function returning an inline class value that is mapped to a reference type, we want to avoid boxing.
// However, we have to do that consistently both on declaration site and on call site.
if (!functionDescriptor.isSuspend) return null
val originalSuspendFunction = functionDescriptor.unwrapInitialDescriptorForSuspendFunction()
val originalReturnType = originalSuspendFunction.returnType!!
if (!originalReturnType.isInlineClassType()) return null
// Force boxing for primitives
if (AsmUtil.isPrimitive(mapType(originalReturnType))) {
return functionDescriptor.builtIns.nullableAnyType
}
// Force boxing for nullable inline class types with nullable underlying type
if (originalReturnType.isMarkedNullable && originalReturnType.isNullableUnderlyingType()) {
return functionDescriptor.builtIns.nullableAnyType
}
// Force boxing for Result type, otherwise, the coroutines machinery will break
if (originalReturnType.constructor.declarationDescriptor?.fqNameSafe == RESULT_FQ_NAME) {
return functionDescriptor.builtIns.nullableAnyType
}
// Don't box other inline classes
return originalReturnType
}
@JvmOverloads
fun mapToCallableMethod(
descriptor: FunctionDescriptor,
@@ -464,7 +425,7 @@ class KotlinTypeMapper @JvmOverloads constructor(
invokeOpcode = INVOKESTATIC
val originalDescriptor = descriptor.original
signature = mapSignatureSkipGeneric(originalDescriptor, OwnerKind.DEFAULT_IMPLS)
returnKotlinType = getReturnValueType(originalDescriptor)
returnKotlinType = originalDescriptor.returnType
if (descriptor is AccessorForCallableDescriptor<*> && descriptor.calleeDescriptor.isCompiledToJvmDefault(jvmDefaultMode)) {
owner = mapClass(functionParent)
isInterfaceMember = true
@@ -515,7 +476,11 @@ class KotlinTypeMapper @JvmOverloads constructor(
skipGenericSignature = true,
hasSpecialBridge = false
)
returnKotlinType = getReturnValueType(functionToCall)
returnKotlinType =
(if (functionToCall.isSuspend &&
functionToCall.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(this) == null
) functionDescriptor.builtIns.nullableAnyType else functionToCall.returnType)
val receiver = if (currentIsInterface && !originalIsInterface || functionParent is FunctionClassDescriptor)
declarationOwner
@@ -528,7 +493,7 @@ class KotlinTypeMapper @JvmOverloads constructor(
} else {
val originalDescriptor = functionDescriptor.original
signature = mapSignatureSkipGeneric(originalDescriptor)
returnKotlinType = getReturnValueType(originalDescriptor)
returnKotlinType = originalDescriptor.returnType
owner = mapOwner(functionDescriptor)
ownerForDefaultImpl = owner
baseMethodDescriptor = functionDescriptor
@@ -0,0 +1,26 @@
// IGNORE_BACKEND: JVM_IR
inline class ICString(val x: String)
suspend fun suspendICString(): ICString = ICString("")
suspend fun suspendAny(): Any = ICString("")
suspend fun <T> suspendGeneric(x: T): T = x
fun useICString(x: ICString) {}
fun useAny(x: Any) {}
suspend fun test() {
useICString(suspendICString())
useICString(suspendGeneric(ICString("")))
useAny(suspendAny())
useAny(suspendICString())
}
// -- 1 in 'suspendAny(): Any = ICString("")'
// -- 1 in 'useAny(suspendICString())'
// -- 1 in 'suspendGeneric(ICString(""))'
// 3 INVOKESTATIC ICString\.box-impl
// -- 1 in 'useICString(suspendGeneric(ICString("")))
// -- 1 in 'equals-impl' for ICString
// 2 INVOKEVIRTUAL ICString\.unbox-impl
@@ -1,3 +1,5 @@
// IGNORE_BACKEND: JVM_IR
@Suppress("RESULT_CLASS_IN_RETURN_TYPE")
suspend fun signInFlowStepFirst(): Result<Unit> = Result.success(Unit)
@@ -6,4 +8,4 @@ inline class OurAny(val a: Any)
suspend fun returnsUnboxed(): OurAny = OurAny("OK")
// 1 INVOKESTATIC kotlin/Result.box-impl
// 0 INVOKESTATIC kotlin/OurAny.box-impl
// 0 INVOKESTATIC OurAny.box-impl
@@ -0,0 +1,11 @@
inline class IC(val s: String)
interface I {
suspend fun returnAny(): Any
}
class C : I {
override suspend fun returnAny(): IC = IC("OK")
}
// 1 INVOKESTATIC IC.box-impl
@@ -1426,11 +1426,6 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/coroutines/nonLocalReturn.kt");
}
@TestMetadata("returnResult.kt")
public void testReturnResult() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/returnResult.kt");
}
@TestMetadata("returnUnitInLambda.kt")
public void testReturnUnitInLambda_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/bytecodeText/coroutines/returnUnitInLambda.kt", "kotlin.coroutines.experimental");
@@ -1586,6 +1581,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
public void testNoInlineClassBoxingInSuspendFunReturn_InlineAny() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/noInlineClassBoxingInSuspendFunReturn_InlineAny.kt");
}
@TestMetadata("noInlineClassBoxingInSuspendFunReturn_String.kt")
public void testNoInlineClassBoxingInSuspendFunReturn_String() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/noInlineClassBoxingInSuspendFunReturn_String.kt");
}
@TestMetadata("returnResult.kt")
public void testReturnResult() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/returnResult.kt");
}
@TestMetadata("returnStringOverride.kt")
public void testReturnStringOverride() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/returnStringOverride.kt");
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling")
@@ -1436,11 +1436,6 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/coroutines/nonLocalReturn.kt");
}
@TestMetadata("returnResult.kt")
public void testReturnResult() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/returnResult.kt");
}
@TestMetadata("returnUnitInLambda.kt")
public void testReturnUnitInLambda_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/bytecodeText/coroutines/returnUnitInLambda.kt", "kotlin.coroutines");
@@ -1591,6 +1586,21 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
public void testNoInlineClassBoxingInSuspendFunReturn_InlineAny() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/noInlineClassBoxingInSuspendFunReturn_InlineAny.kt");
}
@TestMetadata("noInlineClassBoxingInSuspendFunReturn_String.kt")
public void testNoInlineClassBoxingInSuspendFunReturn_String() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/noInlineClassBoxingInSuspendFunReturn_String.kt");
}
@TestMetadata("returnResult.kt")
public void testReturnResult() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/returnResult.kt");
}
@TestMetadata("returnStringOverride.kt")
public void testReturnStringOverride() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/coroutines/inlineClasses/returnStringOverride.kt");
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling")