Collect classes for JVM that must be regenerated after IR inline

This commit is contained in:
Ivan Kylchik
2022-12-14 16:45:31 +01:00
committed by Space Team
parent 41b56e5f16
commit 4baf970d5b
13 changed files with 561 additions and 49 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.common.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.expressions.IrPropertyReference
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
@@ -120,6 +121,12 @@ abstract class InventNamesForLocalClasses(private val allowTopLevelCallables: Bo
expression.acceptChildren(this, data)
}
override fun visitFunctionExpression(expression: IrFunctionExpression, data: Data) {
expression.acceptChildren(this, data)
val internalName = localFunctionNames[expression.function.symbol] ?: inventName(null, data)
putLocalClassName(expression, internalName)
}
override fun visitPropertyReference(expression: IrPropertyReference, data: Data) {
val internalName = inventName(null, data)
putLocalClassName(expression, internalName)
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.backend.common.lower.InnerClassesSupport
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
@@ -29,9 +30,7 @@ import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrReturnableBlockSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.util.OperatorNameConventions
fun IrValueParameter.isInlineParameter(type: IrType = this.type) =
@@ -85,9 +84,10 @@ class FunctionInlining(
val innerClassesSupport: InnerClassesSupport? = null,
val insertAdditionalImplicitCasts: Boolean = false,
val alwaysCreateTemporaryVariablesForArguments: Boolean = false,
val inlinePureArguments: Boolean = true,
val regenerateInlinedAnonymousObjects: Boolean = false,
val inlineArgumentsWithTheirOriginalType: Boolean = false,
) : IrElementTransformerVoidWithContext(), BodyLoweringPass {
constructor(context: CommonBackendContext) : this(context, DefaultInlineFunctionResolver(context), null)
constructor(context: CommonBackendContext, innerClassesSupport: InnerClassesSupport) : this(
context,
@@ -133,13 +133,34 @@ class FunctionInlining(
return expression
}
withinScope(actualCallee) {
actualCallee.body?.transformChildrenVoid()
}
val parent = allScopes.map { it.irElement }.filterIsInstance<IrDeclarationParent>().lastOrNull()
?: allScopes.map { it.irElement }.filterIsInstance<IrDeclaration>().lastOrNull()?.parent
?: containerScope?.irElement as? IrDeclarationParent
?: (containerScope?.irElement as? IrDeclaration)?.parent
val inliner = Inliner(expression, actualCallee, currentScope ?: containerScope!!, parent, context)
return inliner.inline()
return inliner.inline().markAsRegenerated()
}
private fun IrReturnableBlock.markAsRegenerated(): IrReturnableBlock {
if (!regenerateInlinedAnonymousObjects) return this
acceptVoid(object : IrElementVisitorVoid {
private fun IrAttributeContainer.setUpCorrectAttributeOwner() {
if (this.attributeOwnerId == this) return
this.attributeOwnerIdBeforeInline = this.attributeOwnerId
this.attributeOwnerId = this
}
override fun visitElement(element: IrElement) {
if (element is IrAttributeContainer) element.setUpCorrectAttributeOwner()
element.acceptChildrenVoid(this)
}
})
return this
}
private val IrFunction.needsInlining get() = this.isInline && !this.isExternal
@@ -248,7 +269,6 @@ class FunctionInlining(
//---------------------------------------------------------------------//
private inner class ParameterSubstitutor : IrElementTransformerVoid() {
override fun visitGetValue(expression: IrGetValue): IrExpression {
val newExpression = super.visitGetValue(expression) as IrGetValue
val argument = substituteMap[newExpression.symbol.owner] ?: return newExpression
@@ -267,13 +287,17 @@ class FunctionInlining(
}
override fun visitCall(expression: IrCall): IrExpression {
// TODO extract to common utils OR reuse ContractDSLRemoverLowering
if (expression.symbol.owner.hasAnnotation(ContractsDslNames.CONTRACTS_DSL_ANNOTATION_FQN)) {
return IrCompositeImpl(expression.startOffset, expression.endOffset, context.irBuiltIns.unitType)
}
if (!isLambdaCall(expression))
return super.visitCall(expression)
val dispatchReceiver = expression.dispatchReceiver?.unwrapAdditionalImplicitCastsIfNeeded() as IrGetValue
val functionArgument = substituteMap[dispatchReceiver.symbol.owner] ?: return super.visitCall(expression)
if ((dispatchReceiver.symbol.owner as? IrValueParameter)?.isNoinline == true)
return super.visitCall(expression)
if ((dispatchReceiver.symbol.owner as? IrValueParameter)?.isNoinline == true) return super.visitCall(expression)
return when {
functionArgument is IrFunctionReference ->
@@ -370,7 +394,8 @@ class FunctionInlining(
endOffset,
inlinedFunction.returnType,
inlinedFunction.symbol,
classTypeParametersCount
classTypeParametersCount,
INLINED_FUNCTION_REFERENCE
)
}
is IrSimpleFunction ->
@@ -380,7 +405,8 @@ class FunctionInlining(
inlinedFunction.returnType,
inlinedFunction.symbol,
inlinedFunction.typeParameters.size,
inlinedFunction.valueParameters.size
inlinedFunction.valueParameters.size,
INLINED_FUNCTION_REFERENCE
)
else ->
error("Unknown function kind : ${inlinedFunction.render()}")
@@ -468,15 +494,16 @@ class FunctionInlining(
val argumentExpression: IrExpression,
val isDefaultArg: Boolean = false
) {
val isInlinableLambdaArgument: Boolean
get() = parameter.isInlineParameter() &&
// must take "original" parameter because it can have generic type and so considered as no inline; see `lambdaAsGeneric.kt`
get() = parameter.getOriginalParameter().isInlineParameter() &&
(argumentExpression is IrFunctionReference
|| argumentExpression is IrFunctionExpression
|| argumentExpression.isAdaptedFunctionReference())
val isInlinablePropertyReference: Boolean
get() = parameter.isInlineParameter() && argumentExpression is IrPropertyReference
// must take "original" parameter because it can have generic type and so considered as no inline; see `lambdaAsGeneric.kt`
get() = parameter.getOriginalParameter().isInlineParameter() && argumentExpression is IrPropertyReference
val isImmutableVariableLoad: Boolean
get() = argumentExpression.let { argument ->
@@ -631,6 +658,12 @@ class FunctionInlining(
return evaluationStatements
}
private fun IrValueParameter.getOriginalParameter(): IrValueParameter {
if (this.parent !is IrFunction) return this
val original = (this.parent as IrFunction).originalFunction
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.
private fun IrValueParameter.getOriginalType(): IrType {
if (this.parent !is IrFunction) return type
@@ -679,6 +712,7 @@ class FunctionInlining(
val shouldCreateTemporaryVariable =
(alwaysCreateTemporaryVariablesForArguments && !parameter.isInlineParameter()) ||
argument.shouldBeSubstitutedViaTemporaryVariable()
if (shouldCreateTemporaryVariable) {
val newVariable = createTemporaryVariable(parameter, variableInitializer, callee)
@@ -710,7 +744,7 @@ class FunctionInlining(
}
private fun ParameterToArgument.shouldBeSubstitutedViaTemporaryVariable(): Boolean =
!isImmutableVariableLoad && !argumentExpression.isPure(false, context = context)
!isImmutableVariableLoad && !(argumentExpression.isPure(false, context = context) && inlinePureArguments)
private fun createTemporaryVariable(parameter: IrValueParameter, variableInitializer: IrExpression, callee: IrFunction): IrVariable {
val variable = currentScope.scope.createTemporaryVariable(
@@ -760,6 +794,7 @@ class FunctionInlining(
}
}
object INLINED_FUNCTION_REFERENCE : IrStatementOriginImpl("INLINED_FUNCTION_REFERENCE")
object INLINED_FUNCTION_ARGUMENTS : IrStatementOriginImpl("INLINED_FUNCTION_ARGUMENTS")
object INLINED_FUNCTION_DEFAULT_ARGUMENTS : IrStatementOriginImpl("INLINED_FUNCTION_DEFAULT_ARGUMENTS")
@@ -25,8 +25,8 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
@@ -67,8 +67,19 @@ fun IrFrameMap.leave(irDeclaration: IrSymbolOwner): Int {
return leave(irDeclaration.symbol)
}
fun IrClass.getOriginalDeclaration(): IrDeclaration? {
val originalClass = this.attributeOwnerIdBeforeInline ?: return null
return when (originalClass) {
is IrClass -> return originalClass
is IrFunctionExpression -> originalClass.function
is IrFunctionReference -> originalClass.symbol.owner
else -> null
}
}
fun JvmBackendContext.getSourceMapper(declaration: IrClass): SourceMapper {
val fileEntry = declaration.fileParent.fileEntry
val originalDeclarationBeforeInline = declaration.getOriginalDeclaration() ?: declaration
val fileEntry = originalDeclarationBeforeInline.fileParent.fileEntry
// NOTE: apparently inliner requires the source range to cover the
// whole file the class is declared in rather than the class only.
val endLineNumber = when (fileEntry) {
@@ -77,12 +88,13 @@ fun JvmBackendContext.getSourceMapper(declaration: IrClass): SourceMapper {
}
val sourceFileName = when (fileEntry) {
is MultifileFacadeFileEntry -> fileEntry.partFiles.singleOrNull()?.name
else -> declaration.fileParent.name
else -> originalDeclarationBeforeInline.fileParent.name
}
val type = declaration.attributeOwnerIdBeforeInline?.let { getLocalClassType(it)!! } ?: defaultTypeMapper.mapClass(declaration)
return SourceMapper(
SourceInfo(
sourceFileName,
defaultTypeMapper.mapClass(declaration).internalName,
type.internalName,
endLineNumber + 1
)
)
@@ -70,6 +70,7 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
irBuiltIns.ororSymbol.toKey()!! to OrOr,
irBuiltIns.dataClassArrayMemberHashCodeSymbol.toKey()!! to IrDataClassArrayMemberHashCode,
irBuiltIns.dataClassArrayMemberToStringSymbol.toKey()!! to IrDataClassArrayMemberToString,
symbols.singleArgumentInlineFunction.toKey()!! to SingleArgumentInlineFunctionIntrinsic,
symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce,
symbols.signatureStringIntrinsic.toKey()!! to SignatureString,
symbols.throwNullPointerException.toKey()!! to ThrowException(Type.getObjectType("java/lang/NullPointerException")),
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.unwrapInlineLambda
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
object SingleArgumentInlineFunctionIntrinsic : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val sourceCompiler = IrSourceCompilerForInline(codegen.state, expression, expression.symbol.owner, codegen, data)
val argumentExpression = expression.getValueArgument(0)!!
val inlineLambda = argumentExpression.unwrapInlineLambda()
if (inlineLambda != null) {
val lambdaInfo = IrExpressionLambdaImpl(codegen, inlineLambda)
lambdaInfo.generateLambdaBody(sourceCompiler)
}
return codegen.unitValue
}
}
@@ -120,33 +120,35 @@ internal val IrClass.isGeneratedLambdaClass: Boolean
origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL ||
origin == JvmLoweredDeclarationOrigin.GENERATED_PROPERTY_REFERENCE
private class JvmVisibilityPolicy : VisibilityPolicy {
// Note: any condition that results in non-`LOCAL` visibility here should be duplicated in `JvmLocalClassPopupLowering`,
// else it won't detect the class as local.
override fun forClass(declaration: IrClass, inInlineFunctionScope: Boolean): DescriptorVisibility =
if (declaration.isGeneratedLambdaClass) {
scopedVisibility(inInlineFunctionScope)
} else {
declaration.visibility
}
override fun forConstructor(declaration: IrConstructor, inInlineFunctionScope: Boolean): DescriptorVisibility =
if (declaration.parentAsClass.isAnonymousObject)
scopedVisibility(inInlineFunctionScope)
else
declaration.visibility
override fun forCapturedField(value: IrValueSymbol): DescriptorVisibility =
JavaDescriptorVisibilities.PACKAGE_VISIBILITY // avoid requiring a synthetic accessor for it
private fun scopedVisibility(inInlineFunctionScope: Boolean): DescriptorVisibility =
if (inInlineFunctionScope) DescriptorVisibilities.PUBLIC else JavaDescriptorVisibilities.PACKAGE_VISIBILITY
}
internal val localDeclarationsPhase = makeIrFilePhase(
{ context ->
LocalDeclarationsLowering(
context,
NameUtils::sanitizeAsJavaIdentifier,
object : VisibilityPolicy {
// Note: any condition that results in non-`LOCAL` visibility here should be duplicated in `JvmLocalClassPopupLowering`,
// else it won't detect the class as local.
override fun forClass(declaration: IrClass, inInlineFunctionScope: Boolean): DescriptorVisibility =
if (declaration.isGeneratedLambdaClass) {
scopedVisibility(inInlineFunctionScope)
} else {
declaration.visibility
}
override fun forConstructor(declaration: IrConstructor, inInlineFunctionScope: Boolean): DescriptorVisibility =
if (declaration.parentAsClass.isAnonymousObject)
scopedVisibility(inInlineFunctionScope)
else
declaration.visibility
override fun forCapturedField(value: IrValueSymbol): DescriptorVisibility =
JavaDescriptorVisibilities.PACKAGE_VISIBILITY // avoid requiring a synthetic accessor for it
private fun scopedVisibility(inInlineFunctionScope: Boolean): DescriptorVisibility =
if (inInlineFunctionScope) DescriptorVisibilities.PUBLIC else JavaDescriptorVisibilities.PACKAGE_VISIBILITY
},
JvmVisibilityPolicy(),
compatibilityModeForInlinedLocalDelegatedPropertyAccessors = true,
forceFieldsForInlineCaptures = true,
postLocalDeclarationLoweringCallback = context.localDeclarationsLoweringData?.let {
@@ -280,7 +282,7 @@ private val kotlinNothingValueExceptionPhase = makeIrFilePhase<CommonBackendCont
description = "Throw proper exception for calls returning value of type 'kotlin.Nothing'"
)
private val functionInliningPhase = makeIrModulePhase<JvmBackendContext>(
internal val functionInliningPhase = makeIrModulePhase<JvmBackendContext>(
{ context ->
class JvmInlineFunctionResolver : InlineFunctionResolver {
override fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction {
@@ -293,6 +295,8 @@ private val functionInliningPhase = makeIrModulePhase<JvmBackendContext>(
}
FunctionInlining(
context, JvmInlineFunctionResolver(), context.innerClassesSupport,
inlinePureArguments = false,
regenerateInlinedAnonymousObjects = true,
inlineArgumentsWithTheirOriginalType = true
)
},
@@ -355,6 +359,7 @@ private val jvmFilePhases = listOf(
localDeclarationsPhase,
// makePatchParentsPhase(),
removeDuplicatedInlinedLocalClasses,
jvmLocalClassExtractionPhase,
staticCallableReferencePhase,
@@ -440,6 +445,8 @@ private fun buildJvmLoweringPhases(
repeatedAnnotationPhase then
functionInliningPhase then
createSeparateCallForInlinedLambdas then
markNecessaryInlinedClassesAsRegenerated then
buildLoweringsPhase(phases) then
generateMultifileFacadesPhase then
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.getAdditionalStatementsFromInlinedBlock
import org.jetbrains.kotlin.backend.common.ir.isFunctionInlining
import org.jetbrains.kotlin.backend.common.ir.putStatementBeforeActualInline
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.functionInliningPhase
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.util.getArgumentsWithIr
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
internal val createSeparateCallForInlinedLambdas = makeIrModulePhase(
::CreateSeparateCallForInlinedLambdasLowering,
name = "CreateSeparateCallForInlinedLambdasLowering",
description = "This lowering will create separate call `singleArgumentInlineFunction` with previously inlined lambda as argument",
prerequisite = setOf(functionInliningPhase)
)
class CreateSeparateCallForInlinedLambdasLowering(val context: JvmBackendContext) : IrElementTransformerVoid(), FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid()
}
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
if (expression is IrInlinedFunctionBlock && expression.isFunctionInlining()) {
val newCalls = expression.getOnlyInlinableArguments().map { arg ->
IrCallImpl.fromSymbolOwner(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.ir.symbols.singleArgumentInlineFunction)
.also { it.putValueArgument(0, arg.transform(this, null)) }
}
// we don't need to transform body of original function, just arguments that were extracted as variables
expression.getAdditionalStatementsFromInlinedBlock().forEach { it.transformChildrenVoid() }
newCalls.reversed().forEach {
expression.putStatementBeforeActualInline(context.createJvmIrBuilder(it.symbol), it)
}
return expression
}
return super.visitContainerExpression(expression)
}
private fun IrInlinedFunctionBlock.getOnlyInlinableArguments(): List<IrExpression> {
return this.inlineCall.getArgumentsWithIr()
.filter { (param, arg) -> param.isInlineParameter() && arg.isInlinableExpression() }
.map { it.second }
}
private fun IrExpression.isInlinableExpression(): Boolean {
return this is IrFunctionExpression || this is IrFunctionReference || this is IrPropertyReference
|| (this is IrBlock && origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE)
}
}
@@ -0,0 +1,251 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.inlineDeclaration
import org.jetbrains.kotlin.backend.common.ir.isFunctionInlining
import org.jetbrains.kotlin.backend.common.lower.inline.INLINED_FUNCTION_REFERENCE
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.functionInliningPhase
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.util.getAllArgumentsWithIr
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
internal val markNecessaryInlinedClassesAsRegenerated = makeIrModulePhase(
::MarkNecessaryInlinedClassesAsRegeneratedLowering,
name = "MarkNecessaryInlinedClassesAsRegeneratedLowering",
description = "Will scan all inlined functions and mark anonymous objects that must be later regenerated at backend",
prerequisite = setOf(functionInliningPhase, createSeparateCallForInlinedLambdas)
)
class MarkNecessaryInlinedClassesAsRegeneratedLowering(val context: JvmBackendContext) : IrElementVisitorVoid, FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.acceptChildrenVoid(this)
}
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitBlock(expression: IrBlock) {
if (expression is IrInlinedFunctionBlock && expression.isFunctionInlining()) {
val element = expression.inlineDeclaration
if (context.visitedDeclarationsForRegenerationLowering.add(element)) {
// Note: functions from other module will not be affected here, they are loaded as IrLazy declarations.
// BUT during IR serialization support we need to carefully test this logic.
element.acceptVoid(this)
}
val mustBeRegenerated = expression.collectDeclarationsThatMustBeRegenerated()
expression.setUpCorrectAttributesForAllInnerElements(mustBeRegenerated)
return
}
super.visitBlock(expression)
}
private fun IrInlinedFunctionBlock.collectDeclarationsThatMustBeRegenerated(): Set<IrAttributeContainer> {
val classesToRegenerate = mutableSetOf<IrAttributeContainer>()
this.acceptVoid(object : IrElementVisitorVoid {
private val containersStack = mutableListOf<IrAttributeContainer>()
private val inlinableParameters = mutableListOf<IrValueParameter>()
private val reifiedArguments = mutableListOf<IrType>()
private var processingBeforeInlineDeclaration = false
fun IrInlinedFunctionBlock.getInlinableParameters(): List<IrValueParameter> {
val callee = this.inlineDeclaration
if (callee !is IrFunction) return emptyList()
// Must pass `callee` explicitly because there can be problems if call was created for fake override
return this.inlineCall.getAllArgumentsWithIr(callee)
.filter { (param, arg) ->
param.isInlineParameter() && (arg ?: param.defaultValue?.expression) is IrFunctionExpression ||
arg is IrGetValue && arg.symbol.owner in inlinableParameters
}
.map { it.first }
}
fun IrInlinedFunctionBlock.getReifiedArguments(): List<IrType> {
val callee = this.inlineDeclaration
if (callee !is IrFunction) return emptyList()
return callee.typeParameters.mapIndexedNotNull { index, param ->
this.inlineCall.getTypeArgument(index)?.takeIf { param.isReified }
}
}
private fun saveDeclarationsFromStackIntoRegenerationPool() {
containersStack.forEach {
classesToRegenerate += it
}
}
override fun visitElement(element: IrElement) = element.acceptChildrenVoid(this)
private fun visitAnonymousDeclaration(declaration: IrAttributeContainer) {
containersStack += declaration
if (declaration.hasReifiedTypeArguments(reifiedArguments)) {
saveDeclarationsFromStackIntoRegenerationPool()
}
if (!processingBeforeInlineDeclaration) {
processingBeforeInlineDeclaration = true
declaration.attributeOwnerIdBeforeInline?.acceptChildrenVoid(this) // check if we need to save THIS declaration
processingBeforeInlineDeclaration = false
}
declaration.acceptChildrenVoid(this) // check if we need to save INNER declarations
containersStack.removeLast()
}
override fun visitClass(declaration: IrClass) {
visitAnonymousDeclaration(declaration)
}
override fun visitFunctionExpression(expression: IrFunctionExpression) {
visitAnonymousDeclaration(expression)
}
override fun visitFunctionReference(expression: IrFunctionReference) {
visitAnonymousDeclaration(expression)
}
override fun visitGetValue(expression: IrGetValue) {
super.visitGetValue(expression)
if (
expression.symbol.owner in inlinableParameters ||
expression.type.getClass()?.let { classesToRegenerate.contains(it) } == true
) {
saveDeclarationsFromStackIntoRegenerationPool()
}
}
override fun visitCall(expression: IrCall) {
if (expression.symbol == context.ir.symbols.singleArgumentInlineFunction) {
when (val lambda = expression.getValueArgument(0)) {
is IrBlock -> (lambda.statements.last() as IrFunctionReference).acceptVoid(this)
is IrFunctionExpression -> lambda.function.acceptVoid(this)
else -> lambda?.acceptVoid(this) // for example IrFunctionReference
}
return
}
if (expression.origin == INLINED_FUNCTION_REFERENCE) {
saveDeclarationsFromStackIntoRegenerationPool()
}
super.visitCall(expression)
}
override fun visitContainerExpression(expression: IrContainerExpression) {
if (expression is IrInlinedFunctionBlock && expression.isFunctionInlining()) {
val additionalInlinableParameters = expression.getInlinableParameters()
val additionalTypeArguments = expression.getReifiedArguments()
inlinableParameters.addAll(additionalInlinableParameters)
reifiedArguments.addAll(additionalTypeArguments)
super.visitContainerExpression(expression)
inlinableParameters.dropLast(additionalInlinableParameters.size)
reifiedArguments.dropLast(additionalTypeArguments.size)
return
}
super.visitContainerExpression(expression)
}
})
return classesToRegenerate
}
private fun IrAttributeContainer.hasReifiedTypeArguments(reifiedArguments: List<IrType>): Boolean {
var hasReified = false
fun IrType.recursiveWalkDown(visitor: IrElementVisitorVoid) {
hasReified = hasReified || this@recursiveWalkDown in reifiedArguments
(this@recursiveWalkDown as? IrSimpleType)?.arguments?.forEach { it.typeOrNull?.recursiveWalkDown(visitor) }
}
this.acceptVoid(object : IrElementVisitorVoid {
private val visitedClasses = mutableSetOf<IrClass>()
override fun visitElement(element: IrElement) {
if (hasReified) return
element.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
if (!visitedClasses.add(declaration)) return
declaration.superTypes.forEach { it.recursiveWalkDown(this) }
super.visitClass(declaration)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall) {
expression.typeOperand.takeIf { it is IrSimpleType }?.recursiveWalkDown(this)
super.visitTypeOperator(expression)
}
override fun visitCall(expression: IrCall) {
(0 until expression.typeArgumentsCount).forEach {
expression.getTypeArgument(it)?.recursiveWalkDown(this)
}
super.visitCall(expression)
}
override fun visitClassReference(expression: IrClassReference) {
expression.classType.recursiveWalkDown(this)
super.visitClassReference(expression)
}
})
return hasReified
}
private fun IrElement.setUpCorrectAttributesForAllInnerElements(mustBeRegenerated: Set<IrAttributeContainer>) {
this.acceptChildrenVoid(object : IrElementVisitorVoid {
private fun checkAndSetUpCorrectAttributes(element: IrAttributeContainer) {
when {
element !in mustBeRegenerated && element.attributeOwnerIdBeforeInline != null -> element.setUpOriginalAttributes()
else -> element.acceptChildrenVoid(this)
}
}
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
checkAndSetUpCorrectAttributes(declaration)
}
override fun visitFunctionExpression(expression: IrFunctionExpression) {
checkAndSetUpCorrectAttributes(expression)
}
override fun visitFunctionReference(expression: IrFunctionReference) {
checkAndSetUpCorrectAttributes(expression)
}
})
}
private fun IrElement.setUpOriginalAttributes() {
acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
if (element is IrAttributeContainer && element.attributeOwnerIdBeforeInline != null) {
// Basically we need to generate SEQUENCE of `element.attributeOwnerIdBeforeInline` and find the original one.
// But we process nested inlined functions first, so `element.attributeOwnerIdBeforeInline` will be processed already.
// This mean that when we start to precess current container, all inner ones in SEQUENCE will already be processed.
element.attributeOwnerId = element.attributeOwnerIdBeforeInline!!.attributeOwnerId
element.attributeOwnerIdBeforeInline = null
}
element.acceptChildrenVoid(this)
}
})
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.getDefaultAdditionalStatementsFromInlinedBlock
import org.jetbrains.kotlin.backend.common.ir.getNonDefaultAdditionalStatementsFromInlinedBlock
import org.jetbrains.kotlin.backend.common.ir.getOriginalStatementsFromInlinedBlock
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.functionInliningPhase
import org.jetbrains.kotlin.backend.jvm.localDeclarationsPhase
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
internal val removeDuplicatedInlinedLocalClasses = makeIrFilePhase(
::RemoveDuplicatedInlinedLocalClassesLowering,
name = "RemoveDuplicatedInlinedLocalClasses",
description = "Drop excess local classes that were copied by ir inliner",
prerequisite = setOf(functionInliningPhase, localDeclarationsPhase)
)
// There are three types of inlined local classes:
// 1. MUST BE regenerated according to set of rules in AnonymousObjectTransformationInfo.
// They all have `attributeOwnerIdBeforeInline != null`.
// 2. MUST NOT BE regenerated and MUST BE CREATED only once because they are copied from call site.
// This lambda will not exist after inline, so we copy declaration into new temporary inline call `singleArgumentInlineFunction`.
// 3. MUST NOT BE created at all because will be created at callee site.
// This lowering drops declarations that correspond to second and third type.
class RemoveDuplicatedInlinedLocalClassesLowering(val context: JvmBackendContext) : IrElementTransformer<Boolean>, FileLoweringPass {
private var insideInlineBlock = false
private val visited = mutableSetOf<IrElement>()
override fun lower(irFile: IrFile) {
irFile.transformChildren(this, false)
}
override fun visitCall(expression: IrCall, data: Boolean): IrElement {
if (expression.symbol == context.ir.symbols.singleArgumentInlineFunction) return expression
return super.visitCall(expression, data)
}
override fun visitBlock(expression: IrBlock, data: Boolean): IrExpression {
if (expression is IrInlinedFunctionBlock) {
val oldInsideInlineBlock = insideInlineBlock
insideInlineBlock = true
expression.getNonDefaultAdditionalStatementsFromInlinedBlock().forEach { it.transform(this, false) }
expression.getDefaultAdditionalStatementsFromInlinedBlock().forEach { it.transform(this, true) }
expression.getOriginalStatementsFromInlinedBlock().forEach { it.transform(this, true) }
insideInlineBlock = oldInsideInlineBlock
return expression
}
return super.visitBlock(expression, data)
}
// Basically we want to remove all anonymous classes after inline. Exceptions are:
// 1. classes that must be regenerated (declaration.attributeOwnerIdBeforeInline != null)
// 2. classes that are originally declared on call site or are default lambdas (data == true)
override fun visitClass(declaration: IrClass, data: Boolean): IrStatement {
if (!insideInlineBlock || declaration.attributeOwnerIdBeforeInline != null || !data) {
return super.visitClass(declaration, data)
}
// TODO big note. Here we drop anonymous class declaration but there still present a constructor call that has reference to this class.
// Everything works fine because somewhere in code we still have class declaration with the same name. So this doesn't ruin code
// generation and will not drop on runtime, but this is something to be aware of.
return IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.irBuiltIns.unitType)
}
override fun visitFunctionReference(expression: IrFunctionReference, data: Boolean): IrElement {
if (!visited.add(expression.symbol.owner)) return expression
expression.symbol.owner.accept(this, data)
return super.visitFunctionReference(expression, data)
}
}
@@ -173,6 +173,8 @@ class JvmBackendContext(
val publicAbiSymbols = mutableSetOf<IrClassSymbol>()
val visitedDeclarationsForRegenerationLowering: MutableSet<IrDeclaration> = ConcurrentHashMap.newKeySet()
init {
state.mapInlineClass = { descriptor ->
defaultTypeMapper.mapType(referenceClass(descriptor).defaultType)
@@ -150,6 +150,9 @@ class JvmSymbols(
addValueParameter("message", irBuiltIns.stringType)
}
klass.addFunction("throwNpe", irBuiltIns.unitType, isStatic = true)
klass.addFunction("singleArgumentInlineFunction", irBuiltIns.unitType, isStatic = true, isInline = true).apply {
addValueParameter("arg", irBuiltIns.functionClass.defaultType)
}
klass.declarations.add(irFactory.buildClass {
name = Name.identifier("Kotlin")
@@ -159,6 +162,11 @@ class JvmSymbols(
})
}
// This function is used only with ir inliner. It is needed to ensure that all local declarations inside lambda will be generated,
// because after inline these lambdas can be dropped.
val singleArgumentInlineFunction: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "singleArgumentInlineFunction" }
val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "checkExpressionValueIsNotNull" }
@@ -198,6 +198,7 @@ fun IrClass.addFunction(
isStatic: Boolean = false,
isSuspend: Boolean = false,
isFakeOverride: Boolean = false,
isInline: Boolean = false,
origin: IrDeclarationOrigin = IrDeclarationOrigin.DEFINED,
startOffset: Int = UNDEFINED_OFFSET,
endOffset: Int = UNDEFINED_OFFSET
@@ -211,6 +212,7 @@ fun IrClass.addFunction(
this.visibility = visibility
this.isSuspend = isSuspend
this.isFakeOverride = isFakeOverride
this.isInline = isInline
this.origin = origin
}.apply {
if (!isStatic) {
@@ -92,11 +92,10 @@ fun IrFunctionAccessExpression.getArgumentsWithSymbols(): List<Pair<IrValueParam
}
/**
* Binds the arguments explicitly represented in the IR to the parameters of the accessed function.
* Binds all arguments represented in the IR to the parameters of the accessed function.
* The arguments are to be evaluated in the same order as they appear in the resulting list.
*/
fun IrMemberAccessExpression<*>.getArgumentsWithIr(): List<Pair<IrValueParameter, IrExpression>> {
val res = mutableListOf<Pair<IrValueParameter, IrExpression>>()
fun IrMemberAccessExpression<*>.getAllArgumentsWithIr(): List<Pair<IrValueParameter, IrExpression?>> {
val irFunction = when (this) {
is IrFunctionAccessExpression -> this.symbol.owner
is IrFunctionReference -> this.symbol.owner
@@ -107,6 +106,16 @@ fun IrMemberAccessExpression<*>.getArgumentsWithIr(): List<Pair<IrValueParameter
else -> error(this)
}
return getAllArgumentsWithIr(irFunction)
}
/**
* Binds all arguments represented in the IR to the parameters of the explicitly given function.
* The arguments are to be evaluated in the same order as they appear in the resulting list.
*/
fun IrMemberAccessExpression<*>.getAllArgumentsWithIr(irFunction: IrFunction): List<Pair<IrValueParameter, IrExpression?>> {
val res = mutableListOf<Pair<IrValueParameter, IrExpression?>>()
dispatchReceiver?.let { arg ->
irFunction.dispatchReceiverParameter?.let { parameter -> res += (parameter to arg) }
}
@@ -116,15 +125,21 @@ fun IrMemberAccessExpression<*>.getArgumentsWithIr(): List<Pair<IrValueParameter
}
irFunction.valueParameters.forEachIndexed { index, it ->
val arg = getValueArgument(index)
if (arg != null) {
res += (it to arg)
}
res += it to getValueArgument(index)
}
return res
}
/**
* Binds the arguments explicitly represented in the IR to the parameters of the accessed function.
* The arguments are to be evaluated in the same order as they appear in the resulting list.
*/
@Suppress("UNCHECKED_CAST")
fun IrMemberAccessExpression<*>.getArgumentsWithIr(): List<Pair<IrValueParameter, IrExpression>> {
return getAllArgumentsWithIr().filter { it.second != null } as List<Pair<IrValueParameter, IrExpression>>
}
/**
* Sets arguments that are specified by given mapping of parameters.
*/