Erase non-reified type parameters by-default when inlining.

Substitution of type arguments to non-reified type parameters may lead
to accidental reification, which should not be done (see ^KT-60174 for
examples). So, we should erase them, except the few cases.

^KT-60174: Fixed
^KT-60175: Fixed
This commit is contained in:
vladislav.grechko
2023-07-27 18:30:50 +02:00
committed by Space Team
parent 29ecc4d987
commit f318b5969d
89 changed files with 2369 additions and 280 deletions
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrDoWhileLoopImpl
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.transformStatement
import org.jetbrains.kotlin.ir.types.isSubtypeOf
import org.jetbrains.kotlin.ir.types.isUnit
// TODO migrate other usages and move this file to backend.jvm
@@ -90,7 +91,24 @@ class ReturnableBlockTransformer(val context: CommonBackendContext, val containe
val scopeSymbol = currentScope?.scope?.scopeOwnerSymbol ?: containerSymbol
val builder = context.createIrBuilder(scopeSymbol!!)
val variable by lazy {
builder.scope.createTmpVariable(expression.type, "tmp\$ret\$${labelCnt++}", true)
builder.scope.createTmpVariable(expression.type, "tmp\$ret\$${labelCnt++}", true).apply {
// Consider the code:
//
// inline fun <T> myrun(block: () -> T) = block()
// fun foo() = myrun L@{ if (false) return@L }
//
// Note that the block has execution path without explicit `Unit` return. That is why `variable` may be uninitialized
// before its reading.
// We worked it around that way: since explicit value return from `Unit` block is not obligatory, later in this lowering
// we don't create `variable` reading if its type is known to be `Unit`.
// On the other hand, despite the block in fact returns `Unit`, due to erasure of non-reified type parameters when inlining,
// block's type in IR can be any superclass of `Unit`, e.g. `Any?`. Thus, the workaround does not work in that case.
// Therefore, we should explicitly initialize `variable`.
// It is safe even if block actually returns something, because in that case `Unit` initializer will be overwritten.
if (!expression.type.isUnit() && context.irBuiltIns.unitType.isSubtypeOf(expression.type, context.typeSystem)) {
initializer = builder.irUnit()
}
}
}
val loop by lazy {
@@ -5,12 +5,16 @@
package org.jetbrains.kotlin.backend.common.lower.inline
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
import org.jetbrains.kotlin.ir.declarations.IrTypeParametersContainer
import org.jetbrains.kotlin.ir.declarations.copyAttributes
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
@@ -24,7 +28,8 @@ import org.jetbrains.kotlin.utils.memoryOptimizedMap
internal class DeepCopyIrTreeWithSymbolsForInliner(
val typeArguments: Map<IrTypeParameterSymbol, IrType?>?,
val parent: IrDeclarationParent?
val parent: IrDeclarationParent?,
defaultNonReifiedTypeParameterRemappingMode: NonReifiedTypeParameterRemappingMode,
) {
fun copy(irElement: IrElement): IrElement {
@@ -43,7 +48,8 @@ internal class DeepCopyIrTreeWithSymbolsForInliner(
private inner class InlinerTypeRemapper(
val symbolRemapper: SymbolRemapper,
val typeArguments: Map<IrTypeParameterSymbol, IrType?>?
val typeArguments: Map<IrTypeParameterSymbol, IrType?>?,
val defaultNonReifiedTypeParameterRemappingMode: NonReifiedTypeParameterRemappingMode,
) : TypeRemapper {
override fun enterScope(irTypeParametersContainer: IrTypeParametersContainer) {}
@@ -52,30 +58,36 @@ internal class DeepCopyIrTreeWithSymbolsForInliner(
private fun remapTypeArguments(
arguments: List<IrTypeArgument>,
erasedParameters: MutableSet<IrTypeParameterSymbol>?
erasedParameters: MutableSet<IrTypeParameterSymbol>?,
leaveNonReifiedAsIs: Boolean,
) =
arguments.memoryOptimizedMap { argument ->
(argument as? IrTypeProjection)?.let { proj ->
remapTypeAndOptionallyErase(proj.type, erasedParameters)?.let { newType ->
remapType(proj.type, erasedParameters, leaveNonReifiedAsIs)?.let { newType ->
makeTypeProjection(newType, proj.variance)
} ?: IrStarProjectionImpl
}
?: argument
}
override fun remapType(type: IrType) = remapTypeAndOptionallyErase(type, erase = false)
override fun remapType(type: IrType) = remapType(type, defaultNonReifiedTypeParameterRemappingMode)
fun remapTypeAndOptionallyErase(type: IrType, erase: Boolean): IrType {
val erasedParams = if (erase) mutableSetOf<IrTypeParameterSymbol>() else null
return remapTypeAndOptionallyErase(type, erasedParams) ?: error("Cannot substitute type ${type.render()}")
fun remapType(type: IrType, mode: NonReifiedTypeParameterRemappingMode): IrType {
val erasedParams = if (mode == NonReifiedTypeParameterRemappingMode.ERASE) mutableSetOf<IrTypeParameterSymbol>() else null
return remapType(type, erasedParams, mode == NonReifiedTypeParameterRemappingMode.LEAVE_AS_IS)
?: error("Cannot substitute type ${type.render()}")
}
private fun remapTypeAndOptionallyErase(type: IrType, erasedParameters: MutableSet<IrTypeParameterSymbol>?): IrType? {
private fun remapType(type: IrType, erasedParameters: MutableSet<IrTypeParameterSymbol>?, leaveNonReifiedAsIs: Boolean): IrType? {
if (type !is IrSimpleType) return type
val classifier = type.classifier
val substitutedType = typeArguments?.get(classifier)
if (leaveNonReifiedAsIs && classifier is IrTypeParameterSymbol && !classifier.owner.isReified) {
return type
}
// Erase non-reified type parameter if asked to.
if (erasedParameters != null && substitutedType != null && (classifier as? IrTypeParameterSymbol)?.owner?.isReified == false) {
@@ -94,7 +106,7 @@ internal class DeepCopyIrTreeWithSymbolsForInliner(
val upperBound = superClass ?: superTypes.first()
// TODO: Think about how to reduce complexity from k^N to N^k
val erasedUpperBound = remapTypeAndOptionallyErase(upperBound, erasedParameters)
val erasedUpperBound = remapType(upperBound, erasedParameters, leaveNonReifiedAsIs)
?: error("Cannot erase upperbound ${upperBound.render()}")
erasedParameters.remove(classifier)
@@ -111,7 +123,7 @@ internal class DeepCopyIrTreeWithSymbolsForInliner(
return type.buildSimpleType {
kotlinType = null
this.classifier = symbolRemapper.getReferencedClassifier(classifier)
arguments = remapTypeArguments(type.arguments, erasedParameters)
arguments = remapTypeArguments(type.arguments, erasedParameters, leaveNonReifiedAsIs)
annotations = type.annotations.memoryOptimizedMap { it.transform(copier, null) as IrConstructorCall }
}
}
@@ -136,16 +148,50 @@ internal class DeepCopyIrTreeWithSymbolsForInliner(
}
private val symbolRemapper = SymbolRemapperImpl(NullDescriptorsRemapper)
private val typeRemapper = InlinerTypeRemapper(symbolRemapper, typeArguments)
private val typeRemapper = InlinerTypeRemapper(symbolRemapper, typeArguments, defaultNonReifiedTypeParameterRemappingMode)
private val copier = object : DeepCopyIrTreeWithSymbols(symbolRemapper, typeRemapper) {
private fun IrType.remapTypeAndErase() = typeRemapper.remapTypeAndOptionallyErase(this, erase = true)
private fun IrType.leaveNonReifiedAsIs() = typeRemapper.remapType(this, NonReifiedTypeParameterRemappingMode.LEAVE_AS_IS)
private fun IrType.substituteAll() = typeRemapper.remapType(this, NonReifiedTypeParameterRemappingMode.SUBSTITUTE)
private fun IrType.erase() = typeRemapper.remapType(this, NonReifiedTypeParameterRemappingMode.ERASE)
override fun visitClass(declaration: IrClass): IrClass {
// Substitute type argument to make Class::genericSuperclass work as expected (see kt52417.kt)
// Substitution to the super types does not lead to reification and therefore is safe
return super.visitClass(declaration).apply {
superTypes = declaration.superTypes.memoryOptimizedMap {
it.substituteAll()
}
}
}
override fun visitCall(expression: IrCall): IrCall {
if (!Symbols.isTypeOfIntrinsic(expression.symbol)) return super.visitCall(expression)
// We should neither erase nor substitute non-reified type parameters in the `typeOf` call so that reflection is able
// to create a proper KTypeParameter for it. See KT-60175, KT-30279.
return IrCallImpl(
expression.startOffset, expression.endOffset,
expression.type,
expression.symbol,
expression.typeArgumentsCount,
expression.valueArgumentsCount,
expression.origin,
expression.superQualifierSymbol
).apply {
for (i in 0 until typeArgumentsCount) {
putTypeArgument(i, expression.getTypeArgument(i)?.leaveNonReifiedAsIs())
}
}.copyAttributes(expression)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall) =
IrTypeOperatorCallImpl(
expression.startOffset, expression.endOffset,
expression.type.remapTypeAndErase(),
expression.type.erase(),
expression.operator,
expression.typeOperand.remapTypeAndErase(),
expression.typeOperand.erase(),
expression.argument.transform()
).copyAttributes(expression)
}
@@ -52,18 +52,10 @@ interface InlineFunctionResolver {
}
}
fun IrFunction.isTopLevelInPackage(name: String, packageName: String): Boolean {
if (name != this.name.asString()) return false
val containingDeclaration = parent as? IrPackageFragment ?: return false
val packageFqName = containingDeclaration.packageFqName.asString()
return packageName == packageFqName
}
fun IrFunction.isBuiltInSuspendCoroutineUninterceptedOrReturn(): Boolean =
isTopLevelInPackage(
"suspendCoroutineUninterceptedOrReturn",
StandardNames.COROUTINES_INTRINSICS_PACKAGE_FQ_NAME.asString()
StandardNames.COROUTINES_INTRINSICS_PACKAGE_FQ_NAME
)
open class InlineFunctionResolverReplacingCoroutineIntrinsics(open val context: CommonBackendContext) : InlineFunctionResolver {
@@ -89,9 +81,8 @@ class FunctionInlining(
private val insertAdditionalImplicitCasts: Boolean = false,
private val alwaysCreateTemporaryVariablesForArguments: Boolean = false,
private val regenerateInlinedAnonymousObjects: Boolean = false,
private val inlineArgumentsWithTheirOriginalTypeAndOffset: Boolean = false,
private val inlineArgumentsWithOriginalOffset: Boolean = false,
private val allowExternalInlining: Boolean = false,
private val useTypeParameterUpperBound: Boolean = false
) : IrElementTransformerVoidWithContext(), BodyLoweringPass {
private var containerScope: ScopeWithIr? = null
@@ -172,7 +163,7 @@ class FunctionInlining(
(0 until callSite.typeArgumentsCount).associate {
typeParameters[it].symbol to callSite.getTypeArgument(it)
}
DeepCopyIrTreeWithSymbolsForInliner(typeArguments, parent)
DeepCopyIrTreeWithSymbolsForInliner(typeArguments, parent, NonReifiedTypeParameterRemappingMode.ERASE)
}
val substituteMap = mutableMapOf<IrValueParameter, IrExpression>()
@@ -317,7 +308,7 @@ class FunctionInlining(
private fun inlinePropertyReference(expression: IrCall, propertyReference: IrPropertyReference): IrExpression {
val getterCall = IrCallImpl.fromSymbolOwner(
expression.startOffset, expression.endOffset, expression.type, propertyReference.getter!!,
expression.startOffset, expression.endOffset, propertyReference.getter!!.owner.returnType, propertyReference.getter!!,
origin = INLINED_FUNCTION_REFERENCE
)
@@ -327,14 +318,16 @@ class FunctionInlining(
}
val receiverFromField = propertyReference.dispatchReceiver ?: propertyReference.extensionReceiver
getterCall.dispatchReceiver = getterCall.symbol.owner.dispatchReceiverParameter?.let {
receiverFromField ?: tryToGetArg(0)
getterCall.dispatchReceiver = getterCall.symbol.owner.dispatchReceiverParameter?.let { dispatchReceiverParam ->
val dispatchReceiverArgument = receiverFromField ?: tryToGetArg(0)
dispatchReceiverArgument?.doImplicitCastIfNeededTo(dispatchReceiverParam.type)
}
getterCall.extensionReceiver = getterCall.symbol.owner.extensionReceiverParameter?.let {
when (getterCall.symbol.owner.dispatchReceiverParameter) {
getterCall.extensionReceiver = getterCall.symbol.owner.extensionReceiverParameter?.let { extensionReceiverParam ->
val extensionReceiverArgument = when (getterCall.symbol.owner.dispatchReceiverParameter) {
null -> receiverFromField ?: tryToGetArg(0)
else -> tryToGetArg(if (receiverFromField != null) 0 else 1)
}
extensionReceiverArgument?.doImplicitCastIfNeededTo(extensionReceiverParam.type)
}
return wrapInStubFunction(super.visitExpression(getterCall), expression, propertyReference)
@@ -427,8 +420,8 @@ class FunctionInlining(
is IrConstructor -> {
val classTypeParametersCount = inlinedFunction.parentAsClass.typeParameters.size
IrConstructorCallImpl.fromSymbolOwner(
if (inlineArgumentsWithTheirOriginalTypeAndOffset) irFunctionReference.startOffset else irCall.startOffset,
if (inlineArgumentsWithTheirOriginalTypeAndOffset) irFunctionReference.endOffset else irCall.endOffset,
if (inlineArgumentsWithOriginalOffset) irFunctionReference.startOffset else irCall.startOffset,
if (inlineArgumentsWithOriginalOffset) irFunctionReference.endOffset else irCall.endOffset,
functionReferenceReturnType,
inlinedFunction.symbol,
classTypeParametersCount,
@@ -437,8 +430,8 @@ class FunctionInlining(
}
is IrSimpleFunction ->
IrCallImpl(
if (inlineArgumentsWithTheirOriginalTypeAndOffset) irFunctionReference.startOffset else irCall.startOffset,
if (inlineArgumentsWithTheirOriginalTypeAndOffset) irFunctionReference.endOffset else irCall.endOffset,
if (inlineArgumentsWithOriginalOffset) irFunctionReference.startOffset else irCall.startOffset,
if (inlineArgumentsWithOriginalOffset) irFunctionReference.endOffset else irCall.endOffset,
functionReferenceReturnType,
inlinedFunction.symbol,
inlinedFunction.typeParameters.size,
@@ -689,7 +682,9 @@ class FunctionInlining(
startOffset = if (it.isDefaultArg) irExpression.startOffset else UNDEFINED_OFFSET,
endOffset = if (it.isDefaultArg) irExpression.startOffset else UNDEFINED_OFFSET,
irExpression = irExpression,
irType = if (inlineArgumentsWithTheirOriginalTypeAndOffset) it.parameter.getOriginalType() else irExpression.type,
// If original type of parameter is T, then `it.parameter.type` is T after substitution or erasure,
// depending on whether T reified or not.
irType = it.parameter.type,
nameHint = callee.symbol.owner.name.asStringStripSpecialMarkers() + "_" + it.parameter.name.asStringStripSpecialMarkers(),
isMutable = false
)
@@ -735,50 +730,6 @@ class FunctionInlining(
return original.allParameters.singleOrNull { it.name == this.name && it.startOffset == this.startOffset } ?: this
}
// In short this is needed for `kt44429` test. We need to get original generic type to trick type system on JVM backend.
// Probably this it is relevant only for numeric types in JVM.
private fun IrValueParameter.getOriginalType(): IrType {
if (this.parent !is IrFunction) return type
val copy = this.parent as IrFunction // contains substituted type parameters with corresponding type arguments
val original = copy.originalFunction // contains original unsubstituted type parameters
// Note 1: the following method will replace super types fow the owner type parameter. So in every other IrSimpleType that
// refers this type parameter we will see substituted values. This should not be a problem because earlier we replace all type
// parameters with corresponding type arguments.
// Note 2: this substitution can be dropped if we will learn how to copy IR function and leave its type parameters as they are.
// But this sounds a little complicated.
fun IrType.substituteSuperTypes(): IrType {
val typeClassifier = this.classifierOrNull?.owner as? IrTypeParameter ?: return this
typeClassifier.superTypes = original.typeParameters[typeClassifier.index].superTypes.map {
val superTypeClassifier = it.classifierOrNull?.owner as? IrTypeParameter ?: return@map it
copy.typeParameters[superTypeClassifier.index].defaultType.substituteSuperTypes()
}
return this
}
fun IrValueParameter?.getTypeIfFromTypeParameter(): IrType? {
val typeClassifier = this?.type?.classifierOrNull?.owner as? IrTypeParameter ?: return null
if (typeClassifier.parent != this.parent) return null
// We take type parameter from copied callee and not from original because we need an actual copy. Without this copy,
// in case of recursive call, we can get a situation there the same type parameter will be mapped on different type arguments.
// (see compiler/testData/codegen/boxInline/complex/use.kt test file)
val newTypeParameter = copy.typeParameters[typeClassifier.index].defaultType.substituteSuperTypes()
return if (useTypeParameterUpperBound) typeClassifier.firstRealUpperBound().mergeNullability(type) else newTypeParameter
}
return when (this) {
copy.dispatchReceiverParameter -> original.dispatchReceiverParameter?.getTypeIfFromTypeParameter()
?: copy.dispatchReceiverParameter!!.type
copy.extensionReceiverParameter -> original.extensionReceiverParameter?.getTypeIfFromTypeParameter()
?: copy.extensionReceiverParameter!!.type
else -> copy.valueParameters.first { it == this }.let { valueParameter ->
original.valueParameters.getOrNull(valueParameter.index)?.getTypeIfFromTypeParameter()
?: valueParameter.type
}
}
}
private fun IrTypeParameter?.firstRealUpperBound(): IrType {
val queue = this?.superTypes?.toMutableList() ?: mutableListOf()
@@ -876,7 +827,9 @@ class FunctionInlining(
irExpression = IrBlockImpl(
if (isDefaultArg) variableInitializer.startOffset else UNDEFINED_OFFSET,
if (isDefaultArg) variableInitializer.endOffset else UNDEFINED_OFFSET,
if (inlineArgumentsWithTheirOriginalTypeAndOffset) parameter.getOriginalType() else variableInitializer.type,
// If original type of parameter is T, then `parameter.type` is T after substitution or erasure,
// depending on whether T reified or not.
parameter.type
).apply {
statements.add(variableInitializer)
},
@@ -921,3 +874,7 @@ class FunctionInlining(
val INLINED_FUNCTION_REFERENCE by IrStatementOriginImpl
val INLINED_FUNCTION_ARGUMENTS by IrStatementOriginImpl
val INLINED_FUNCTION_DEFAULT_ARGUMENTS by IrStatementOriginImpl
enum class NonReifiedTypeParameterRemappingMode {
LEAVE_AS_IS, SUBSTITUTE, ERASE
}
@@ -213,10 +213,9 @@ private val functionInliningPhase = makeIrModulePhase(
it,
JsInlineFunctionResolver(it),
it.innerClassesSupport,
allowExternalInlining = true,
useTypeParameterUpperBound = true,
alwaysCreateTemporaryVariablesForArguments = true,
inlineArgumentsWithTheirOriginalTypeAndOffset = true
inlineArgumentsWithOriginalOffset = true,
allowExternalInlining = true
)
},
name = "FunctionInliningPhase",
@@ -259,6 +259,14 @@ private val returnableBlocksPhase = makeIrFilePhase(
prerequisite = setOf(arrayConstructorPhase, assertionPhase, directInvokeLowering)
)
private val singletonReferencesPhase = makeIrFilePhase(
::SingletonReferencesLowering,
name = "SingletonReferences",
description = "Handle singleton references",
// ReturnableBlock lowering may produce references to the `Unit` object
prerequisite = setOf(returnableBlocksPhase)
)
private val syntheticAccessorPhase = makeIrFilePhase(
::SyntheticAccessorLowering,
name = "SyntheticAccessor",
@@ -287,7 +295,7 @@ internal val functionInliningPhase = makeIrModulePhase(
innerClassesSupport = context.innerClassesSupport,
alwaysCreateTemporaryVariablesForArguments = true,
regenerateInlinedAnonymousObjects = true,
inlineArgumentsWithTheirOriginalTypeAndOffset = true
inlineArgumentsWithOriginalOffset = true
)
},
name = "FunctionInliningPhase",
@@ -367,10 +375,10 @@ private val jvmFilePhases = listOf(
// makePatchParentsPhase(),
enumWhenPhase,
singletonReferencesPhase,
assertionPhase,
returnableBlocksPhase,
singletonReferencesPhase,
sharedVariablesPhase,
localDeclarationsPhase,
// makePatchParentsPhase(),
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
@@ -23,13 +22,7 @@ import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.isAnonymousObject
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
internal val singletonReferencesPhase = makeIrFilePhase(
::SingletonReferencesLowering,
name = "SingletonReferences",
description = "Handle singleton references"
)
private class SingletonReferencesLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
internal class SingletonReferencesLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
private val constructingEnums = arrayListOf<IrDeclarationParent>()
override fun lower(irFile: IrFile) {
@@ -136,9 +136,10 @@ private val functionInliningPhase = makeCustomPhase<WasmBackendContext>(
{ context, module ->
FunctionInlining(
context = context,
innerClassesSupport = context.innerClassesSupport,
inlineFunctionResolver = WasmInlineFunctionResolver(context),
innerClassesSupport = context.innerClassesSupport,
insertAdditionalImplicitCasts = true,
alwaysCreateTemporaryVariablesForArguments = true
).inline(module)
module.patchDeclarationParents()
},