diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InventNamesForLocalClasses.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InventNamesForLocalClasses.kt index 56ba124106e..f05fb58b78a 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InventNamesForLocalClasses.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InventNamesForLocalClasses.kt @@ -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) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt index 1f87ec0b016..7adc9d37fbb 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/inline/FunctionInlining.kt @@ -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().lastOrNull() ?: allScopes.map { it.irElement }.filterIsInstance().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") diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt index fbe7ae54128..60611e45933 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt @@ -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 ) ) diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt index 15182c00841..db7fc14a086 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt @@ -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")), diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/SingleArgumentInlineFunctionIntrinsic.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/SingleArgumentInlineFunctionIntrinsic.kt new file mode 100644 index 00000000000..3b7e137ea2b --- /dev/null +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/SingleArgumentInlineFunctionIntrinsic.kt @@ -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 + } +} \ No newline at end of file diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index 5d0871b3623..0a4f0eac1c8 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -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( +internal val functionInliningPhase = makeIrModulePhase( { context -> class JvmInlineFunctionResolver : InlineFunctionResolver { override fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction { @@ -293,6 +295,8 @@ private val functionInliningPhase = makeIrModulePhase( } 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 diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/CreateSeparateCallForInlinedLambdasLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/CreateSeparateCallForInlinedLambdasLowering.kt new file mode 100644 index 00000000000..e869f6112af --- /dev/null +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/CreateSeparateCallForInlinedLambdasLowering.kt @@ -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 { + 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) + } +} diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MarkNecessaryInlinedClassesAsRegeneratedLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MarkNecessaryInlinedClassesAsRegeneratedLowering.kt new file mode 100644 index 00000000000..f9339b55bea --- /dev/null +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MarkNecessaryInlinedClassesAsRegeneratedLowering.kt @@ -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 { + val classesToRegenerate = mutableSetOf() + this.acceptVoid(object : IrElementVisitorVoid { + private val containersStack = mutableListOf() + private val inlinableParameters = mutableListOf() + private val reifiedArguments = mutableListOf() + private var processingBeforeInlineDeclaration = false + + fun IrInlinedFunctionBlock.getInlinableParameters(): List { + 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 { + 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): 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() + + 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) { + 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) + } + }) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDuplicatedInlinedLocalClassesLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDuplicatedInlinedLocalClassesLowering.kt new file mode 100644 index 00000000000..3ec4ee9b9c0 --- /dev/null +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDuplicatedInlinedLocalClassesLowering.kt @@ -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, FileLoweringPass { + private var insideInlineBlock = false + private val visited = mutableSetOf() + + 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) + } +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index c3bcc0b7bc8..ac67f6a9c43 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -173,6 +173,8 @@ class JvmBackendContext( val publicAbiSymbols = mutableSetOf() + val visitedDeclarationsForRegenerationLowering: MutableSet = ConcurrentHashMap.newKeySet() + init { state.mapInlineClass = { descriptor -> defaultTypeMapper.mapType(referenceClass(descriptor).defaultType) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 1bd175d2540..92784817e8d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -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" } diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt index 283b4ce3de7..d6b3ab4d1c4 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt @@ -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) { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index 7587a2d9e40..76678eb8af2 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -92,11 +92,10 @@ fun IrFunctionAccessExpression.getArgumentsWithSymbols(): List.getArgumentsWithIr(): List> { - val res = mutableListOf>() +fun IrMemberAccessExpression<*>.getAllArgumentsWithIr(): List> { val irFunction = when (this) { is IrFunctionAccessExpression -> this.symbol.owner is IrFunctionReference -> this.symbol.owner @@ -107,6 +106,16 @@ fun IrMemberAccessExpression<*>.getArgumentsWithIr(): List 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> { + val res = mutableListOf>() + dispatchReceiver?.let { arg -> irFunction.dispatchReceiverParameter?.let { parameter -> res += (parameter to arg) } } @@ -116,15 +125,21 @@ fun IrMemberAccessExpression<*>.getArgumentsWithIr(): List - 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> { + return getAllArgumentsWithIr().filter { it.second != null } as List> +} + /** * Sets arguments that are specified by given mapping of parameters. */