diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index ff33db3689e..b813fcac000 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -41,12 +41,16 @@ abstract class InlineCodegen( AsmUtil.genThrow(codegen.visitor, "java/lang/UnsupportedOperationException", "Call is part of inline cycle: $text") } + fun compileInline(): SMAPAndMethodNode { + return sourceCompiler.compileInlineFunction(jvmSignature).apply { + node.preprocessSuspendMarkers(forInline = true, keepFakeContinuation = false) + } + } + fun performInline(registerLineNumberAfterwards: Boolean, isInlineOnly: Boolean) { var nodeAndSmap: SMAPAndMethodNode? = null try { - nodeAndSmap = sourceCompiler.compileInlineFunction(jvmSignature).apply { - node.preprocessSuspendMarkers(forInline = true, keepFakeContinuation = false) - } + nodeAndSmap = compileInline() val result = inlineCall(nodeAndSmap, isInlineOnly) leaveTemps() codegen.propagateChildReifiedTypeParametersUsages(result.reifiedTypeParametersUsages) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentFunctionFactory.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentFunctionFactory.kt index 1cb98be05fa..d5b18bacbf1 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentFunctionFactory.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentFunctionFactory.kt @@ -49,12 +49,9 @@ abstract class DefaultArgumentFunctionFactory(open val context: CommonBackendCon type = if (makeNullable) newType.makeNullable() else newType, defaultValue = if (it.defaultValue != null) { original.factory.createExpressionBody( - IrErrorExpressionImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - it.type, - "Default Stub" - ) + IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, it.type, "Default Stub").apply { + attributeOwnerId = it.defaultValue!!.expression + } ) } else null, isAssignable = it.defaultValue != null diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt index da580ab8a3f..ba92cbed21d 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt @@ -462,7 +462,9 @@ class DefaultParameterCleaner( if (replaceDefaultValuesWithStubs) { if (context.mapping.defaultArgumentsOriginalFunction[declaration.parent as IrFunction] == null) { declaration.defaultValue = context.irFactory.createExpressionBody( - IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, declaration.type, "Default Stub") + IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, declaration.type, "Default Stub").apply { + attributeOwnerId = declaration.defaultValue!!.expression + } ) } } else { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ProvisionalFunctionExpressionLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ProvisionalFunctionExpressionLowering.kt index 2ef8d275597..b47198ea15b 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ProvisionalFunctionExpressionLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ProvisionalFunctionExpressionLowering.kt @@ -6,24 +6,19 @@ package org.jetbrains.kotlin.backend.common.lower import org.jetbrains.kotlin.backend.common.BodyLoweringPass -import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.IrDeclaration -import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.declarations.copyAttributes import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl -import org.jetbrains.kotlin.ir.util.remapTypes import org.jetbrains.kotlin.ir.visitors.IrElementTransformer -import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid -import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid class ProvisionalFunctionExpressionLoweringContext( - val outer: ProvisionalFunctionExpressionLoweringContext? = null, val startOffset: Int? = null, - val endOffset: Int? = null) + val endOffset: Int? = null +) class ProvisionalFunctionExpressionLowering : IrElementTransformer, BodyLoweringPass { @@ -35,7 +30,6 @@ class ProvisionalFunctionExpressionLowering : override fun visitCall(expression: IrCall, data: ProvisionalFunctionExpressionLoweringContext) = super.visitCall( expression, ProvisionalFunctionExpressionLoweringContext( - data, expression.startOffset, expression.endOffset ) @@ -44,14 +38,18 @@ class ProvisionalFunctionExpressionLowering : override fun visitVariable(declaration: IrVariable, data: ProvisionalFunctionExpressionLoweringContext) = super.visitVariable( declaration, ProvisionalFunctionExpressionLoweringContext( - data, declaration.startOffset, declaration.endOffset ) ) + override fun visitContainerExpression(expression: IrContainerExpression, data: ProvisionalFunctionExpressionLoweringContext): IrExpression { + if (expression !is IrReturnableBlock) return super.visitContainerExpression(expression, data) + return super.visitContainerExpression(expression, ProvisionalFunctionExpressionLoweringContext()) + } + override fun visitFunctionExpression(expression: IrFunctionExpression, data: ProvisionalFunctionExpressionLoweringContext): IrElement { - expression.transformChildren(this, ProvisionalFunctionExpressionLoweringContext(data)) + expression.transformChildren(this, ProvisionalFunctionExpressionLoweringContext()) val startOffset = data.startOffset ?: expression.startOffset val endOffset = data.endOffset ?: expression.endOffset @@ -70,7 +68,7 @@ class ProvisionalFunctionExpressionLowering : valueArgumentsCount = function.valueParameters.size, reflectionTarget = null, origin = origin - ) + ).copyAttributes(expression) // TODO better to copy just name ) ) } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ReturnableBlockLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ReturnableBlockLowering.kt index f4f379bba59..c05c0f8bbad 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ReturnableBlockLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ReturnableBlockLowering.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.BodyLoweringPass import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner @@ -162,7 +163,8 @@ class ReturnableBlockTransformer(val context: CommonBackendContext, val containe if (!expression.type.isUnit()) { // In case of Unit return type we don't need to return an explicit value. This will not be optimized by JVM backend and // may result in exceptions in `MethodVerifier` before optimizations. - +irGet(variable) + // Also note that `UNDEFINED_OFFSET` is needed to make proper line number for JVM. + +at(UNDEFINED_OFFSET, UNDEFINED_OFFSET).irGet(variable) } } } 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 d60fd6710df..783539b2dc6 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 @@ -689,6 +689,8 @@ class FunctionInlining( } else { val newVariable = currentScope.scope.createTemporaryVariable( + 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, nameHint = callee.symbol.owner.name.asStringStripSpecialMarkers() + "_" + it.parameter.name.asStringStripSpecialMarkers(), @@ -793,14 +795,18 @@ class FunctionInlining( (alwaysCreateTemporaryVariablesForArguments && !parameter.isInlineParameter()) || argument.shouldBeSubstitutedViaTemporaryVariable() - if (!shouldCreateTemporaryVariable) { - substituteMap[parameter] = variableInitializer + if (shouldCreateTemporaryVariable) { + val newVariable = createTemporaryVariable(parameter, variableInitializer, argument.isDefaultArg, callee) + if (argument.isDefaultArg) evaluationStatementsFromDefault.add(newVariable) else evaluationStatements.add(newVariable) + substituteMap[parameter] = IrGetValueWithoutLocation(newVariable.symbol) return@forEach } - val newVariable = createTemporaryVariable(parameter, variableInitializer, callee) - if (argument.isDefaultArg) evaluationStatementsFromDefault.add(newVariable) else evaluationStatements.add(newVariable) - substituteMap[parameter] = IrGetValueWithoutLocation(newVariable.symbol) + substituteMap[parameter] = if (variableInitializer is IrGetValue) { + IrGetValueWithoutLocation(variableInitializer.symbol) + } else { + variableInitializer + } } @@ -824,13 +830,19 @@ class FunctionInlining( } private fun ParameterToArgument.shouldBeSubstitutedViaTemporaryVariable(): Boolean = - !isImmutableVariableLoad && !(argumentExpression.isPure(false, context = context) && inlinePureArguments) + !(isImmutableVariableLoad && parameter.index >= 0) && + !(argumentExpression.isPure(false, context = context) && inlinePureArguments) - private fun createTemporaryVariable(parameter: IrValueParameter, variableInitializer: IrExpression, callee: IrFunction): IrVariable { + private fun createTemporaryVariable( + parameter: IrValueParameter, + variableInitializer: IrExpression, + isDefaultArg: Boolean, + callee: IrFunction + ): IrVariable { val variable = currentScope.scope.createTemporaryVariable( irExpression = IrBlockImpl( - variableInitializer.startOffset, - variableInitializer.endOffset, + if (isDefaultArg) variableInitializer.startOffset else UNDEFINED_OFFSET, + if (isDefaultArg) variableInitializer.endOffset else UNDEFINED_OFFSET, if (inlineArgumentsWithTheirOriginalTypeAndOffset) parameter.getOriginalType() else variableInitializer.type, InlinerExpressionLocationHint((currentScope.irElement as IrSymbolOwner).symbol) ).apply { diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt index 2ac54d0f81e..76cf848da2d 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt @@ -81,6 +81,7 @@ class ClassCodegen private constructor( ) } + val smap = context.getSourceMapper(irClass) private val state get() = context.state private val innerClasses = mutableSetOf() @@ -158,7 +159,6 @@ class ClassCodegen private constructor( // Generating a method node may cause the addition of a field with an initializer if an inline function // call uses `assert` and the JVM assertions mode is enabled. To avoid concurrent modification errors, // there is a very specific generation order. - val smap = context.getSourceMapper(irClass) // 1. Any method other than `` can add a field and a `` statement: for (method in irClass.declarations.filterIsInstance()) { if (method.name.asString() != "" && diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 9da7fc8b6b6..8df3a64e7ea 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -5,8 +5,10 @@ package org.jetbrains.kotlin.backend.jvm.codegen +import org.jetbrains.kotlin.backend.common.ir.* import org.jetbrains.kotlin.backend.common.lower.BOUND_RECEIVER_PARAMETER import org.jetbrains.kotlin.backend.common.lower.LoweredStatementOrigins +import org.jetbrains.kotlin.backend.common.lower.inline.isAdaptedFunctionReference import org.jetbrains.kotlin.backend.jvm.* import org.jetbrains.kotlin.backend.jvm.intrinsics.IntrinsicMethod import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty @@ -34,6 +36,7 @@ import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol @@ -54,12 +57,15 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext import org.jetbrains.kotlin.types.computeExpandedTypeForInlineClass import org.jetbrains.kotlin.types.model.TypeParameterMarker +import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import java.util.* +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract sealed class ExpressionInfo { var blockInfo: BlockInfo? = null @@ -138,6 +144,21 @@ class ExpressionCodegen( val smap: SourceMapper, val reifiedTypeParametersUsages: ReifiedTypeParametersUsages, ) : IrElementVisitor, BaseExpressionCodegen { + data class AdditionalIrInlineData(val smap: SourceMapCopier, val inlinedBlock: IrInlinedFunctionBlock, val parentSmap: SourceMapper, val tryInfo: TryWithFinallyInfo?) { +// fun isInvokeOnLambda(): Boolean = inlinedBlock.inlineCall.symbol.owner.name == OperatorNameConventions.INVOKE + } + + val classToCachedSourceMapper = mutableMapOf() + val localSmapCopiersByClass = mutableListOf() + + private fun getLocalSmap(): List = /*context.*/localSmapCopiersByClass + private fun dropLastLocalSmap() { + /*context.*/localSmapCopiersByClass.removeLast() + } + + private fun addToLocalSmap(info: AdditionalIrInlineData) { + /*context.*/localSmapCopiersByClass.add(info) + } override fun toString(): String = signature.toString() @@ -152,7 +173,7 @@ class ExpressionCodegen( val state = context.state - private val fileEntry = irFunction.fileParent.fileEntry + private val fileEntry = irFunction.fileParentBeforeInline.fileEntry override val visitor: InstructionAdapter get() = mv @@ -187,7 +208,35 @@ class ExpressionCodegen( private fun markNewLinkedLabel() = linkedLabel().apply { mv.visitLabel(this) } - private fun getLineNumberForOffset(offset: Int): Int = fileEntry.getLineNumber(offset) + 1 + private fun getLineNumberForOffset(offset: Int): Int { + if (getLocalSmap().isNotEmpty()) { + var previousData: AdditionalIrInlineData? = null + var result = -1 + val iterator = getLocalSmap().reversed().iterator() + while (iterator.hasNext()) { + if (previousData?.inlinedBlock?.isLambdaInlining() == true) { + while (iterator.hasNext() && iterator.next().inlinedBlock.inlineDeclaration != getInlinedAt(previousData.inlinedBlock.inlinedElement)) { + // after lambda's smap we should skip "frames" that were inlined inside body of inline function that accept given lambda + continue + } + if (!iterator.hasNext()) break + } + val inlineData = iterator.next() + + previousData = inlineData + val localFileEntry = if (inlineData.inlinedBlock.inlinedElement is IrCallableReference<*>) + getLocalSmap().reversed().map { it.inlinedBlock } + .firstOrNull { it.isFunctionInlining() }?.inlineDeclaration?.fileOrNull?.fileEntry ?: fileEntry + else + inlineData.inlinedBlock.inlineDeclaration.fileEntry + val lineNumber = if (result == -1) localFileEntry.getLineNumber(offset) + 1 else result + val mappedLineNumber = inlineData.smap.mapLineNumber(lineNumber) + result = mappedLineNumber + } + return result + } + return fileEntry.getLineNumber(offset) + 1 + } private fun IrElement.markLineNumber(startOffset: Boolean) { if (noLineNumberScope) return @@ -195,6 +244,7 @@ class ExpressionCodegen( if (offset < 0) return val lineNumber = getLineNumberForOffset(offset) + assert(lineNumber > 0) if (lastLineNumber != lineNumber) { lastLineNumber = lineNumber @@ -204,6 +254,17 @@ class ExpressionCodegen( fun markLineNumber(element: IrElement) = element.markLineNumber(true) + @OptIn(ExperimentalContracts::class) + private inline fun noLineNumberScopeWithCondition(flag: Boolean, block: () -> Unit) { + contract { + callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE) + } + val previousState = noLineNumberScope + noLineNumberScope = noLineNumberScope || flag + block() + noLineNumberScope = previousState + } + fun noLineNumberScope(block: () -> Unit) { val previousState = noLineNumberScope noLineNumberScope = true @@ -400,6 +461,11 @@ class ExpressionCodegen( writeLocalVariablesInTable(info, markNewLabel()) } } + + if (expression is IrInlinedFunctionBlock && expression.isFunctionInlining()) { + markLineNumberAfterInlineIfNeeded(isInsideCondition) + } + if (isSynthesizedInitBlock) { expression.markLineNumber(startOffset = false) mv.nop() @@ -453,11 +519,138 @@ class ExpressionCodegen( } } - private fun visitStatementContainer(container: IrStatementContainer, data: BlockInfo) = - container.statements.fold(unitValue) { prev, exp -> + private inline fun stashSmapForGivenTry(tryInfo: TryWithFinallyInfo, block: () -> Unit) { + val lastLineNumberBeforeFinally = lastLineNumber + val localSmap = getLocalSmap() + val smapCountToDrop = localSmap.indexOfFirst { it.tryInfo == tryInfo } + if (smapCountToDrop == -1) { + return block() + } + + val smapInTryBlock = localSmap.takeLast(localSmap.size - smapCountToDrop) + smapInTryBlock.forEach { _ -> dropLastLocalSmap() } + block() + smapInTryBlock.forEach { addToLocalSmap(it) } + if (smapInTryBlock.isNotEmpty()) { + lastLineNumber = lastLineNumberBeforeFinally + } + } + + private fun visitInlinedFunctionBlock(block: IrInlinedFunctionBlock, data: BlockInfo): PromisedValue { + val lineNumberForOffset = getLineNumberForOffset(block.inlineCall.startOffset) + val callee = block.inlineDeclaration as? IrFunction + + block.getNonDefaultAdditionalStatementsFromInlinedBlock().forEach { exp -> + exp.accept(this, data).discard() + } + + // TODO start_4: reuse code from org/jetbrains/kotlin/codegen/inline/MethodInliner.kt:267 + if (block.isLambdaInlining()) { + val overrideLineNumber = getLocalSmap().reversed().firstOrNull { !it.inlinedBlock.isLambdaInlining() } + ?.inlinedBlock?.inlineDeclaration?.isInlineOnly() == true + val currentLineNumber = if (overrideLineNumber) getLocalSmap().last().smap.callSite!!.line else lineNumberForOffset + + val firstLine = callee?.body?.statements?.firstOrNull()?.let { + block.inlineDeclaration.fileEntry.getLineNumber(it.startOffset) + 1 + } ?: -1 + // TODO DefaultLambda + if (/*(info is DefaultLambda != overrideLineNumber) &&*/ currentLineNumber >= 0 && firstLine == currentLineNumber) { + val label = Label() + val fakeLineNumber = (getLocalSmap().lastOrNull()?.smap?.parent ?: smap) + .mapSyntheticLineNumber(SourceMapper.LOCAL_VARIABLE_INLINE_ARGUMENT_SYNTHETIC_LINE_NUMBER) + mv.visitLabel(label) + mv.visitLineNumber(fakeLineNumber, label) + } + } + // TODO end_4 + + noLineNumberScopeWithCondition(block.inlineDeclaration.isInlineOnly()) { + buildSmapFor(block, data) + + block.getDefaultAdditionalStatementsFromInlinedBlock().forEach { exp -> + exp.accept(this, data).discard() + } + + if (block.inlineCall.usesDefaultArguments()) { + // we must reset LN because at this point in original inliner we will inline non default call + lastLineNumber = -1 + } + + val result = block.getOriginalStatementsFromInlinedBlock().fold(unitValue) { prev, exp -> + prev.discard() + exp.accept(this, data) + } + + val calleeBody = callee?.body + if (block.inlinedElement !is IrCallableReference<*> || callee?.isInline == true) { + if (calleeBody !is IrStatementContainer || calleeBody.statements.lastOrNull() !is IrReturn) { + // TODO start_1: reuse code + // Allow setting a breakpoint on the closing brace of a void-returning function + // without an explicit return, or the `class Something(` line of a primary constructor. + if (callee?.origin != JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER) { + callee?.let { + it.markLineNumber(startOffset = callee is IrConstructor && callee.isPrimary) + mv.nop() + } + } + } + } + // TODO end_1 + +// if (!(block.origin as InlinedFunction).isLambdaInlining) { + // TODO start_3: reuse from visitReturn and see ReturnableBlockLowering + val lastStatement = callee?.body?.statements?.lastOrNull() + if (lastStatement is IrReturn) { + val returnTarget = lastStatement.returnTargetSymbol.owner + val originalTarget = (returnTarget as? IrAttributeContainer)?.attributeOwnerId ?: returnTarget + if (originalTarget == block.inlineDeclaration) { + // if return is implicit we must put new LN at the end of expression + block.statements.last().markLineNumber(startOffset = lastStatement.startOffset != lastStatement.endOffset) + mv.nop() + } + } + // TODO end_3 +// } + + dropLastLocalSmap() + + + if (block.isLambdaInlining()) { + val overrideLineNumber = getLocalSmap().reversed().firstOrNull { !it.inlinedBlock.isLambdaInlining() } + ?.inlinedBlock?.inlineDeclaration?.isInlineOnly() == true + val currentLineNumber = if (overrideLineNumber) getLocalSmap().last().smap.callSite!!.line else lineNumberForOffset +// currentLineNumber = getLineNumberForOffset(marker.inlineCall.startOffset) + // TODO start_2: reuse code + if (currentLineNumber != -1) { + if (overrideLineNumber) { + // This is from the function we're inlining into, so no need to remap. + mv.visitLineNumber(currentLineNumber, markNewLabel()) + } else { + // Need to go through the superclass here to properly remap the line number via `sourceMapper`. + block.inlineCall.markLineNumber(true) + } + mv.nop() + } + // TODO end_2 + } + + // takeUnless is required to avoid markLineNumberAfterInlineIfNeeded for inline only + if (block.isFunctionInlining()) { + lastLineNumber = lineNumberForOffset.takeUnless { noLineNumberScope } ?: -1 + } + return result + } + } + + private fun visitStatementContainer(container: IrStatementContainer, data: BlockInfo): PromisedValue { + if (container is IrInlinedFunctionBlock) { + return visitInlinedFunctionBlock(container, data) + } + return container.statements.fold(unitValue) { prev, exp -> prev.discard() exp.accept(this, data) } + } override fun visitBlockBody(body: IrBlockBody, data: BlockInfo): PromisedValue { visitStatementContainer(body, data).discard() @@ -856,11 +1049,116 @@ class ExpressionCodegen( element.render() ) + private fun IrDeclaration.getClassWithDeclaredFunction(): IrClass? { + val parent = this.parentClassOrNull ?: return null + if (!parent.isInterface || (this is IrFunction && this.hasJvmDefault())) return parent + return parent.declarations.singleOrNull { it.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS } as IrClass + } + + private fun IrFunctionAccessExpression.isInvokeOnDefaultArg(expected: IrDeclaration): Boolean { + if (this.symbol.owner.name != OperatorNameConventions.INVOKE) return false + + val dispatch = this.dispatchReceiver as? IrGetValue + val parameter = dispatch?.symbol?.owner as? IrValueParameter + val default = parameter?.defaultValue?.expression as? IrFunctionExpression + + return default?.function == expected + } + + private fun getInlinedAt(originalExpression: IrElement): IrDeclaration? { + for (block in getLocalSmap().map { it.inlinedBlock }.filter { it.isFunctionInlining() }) { + block.inlineCall.getAllArgumentsWithIr().forEach { + val actualArg = (it.second?.attributeOwnerId ?: ((it.first.defaultValue?.expression?.attributeOwnerId as? IrBlock)?.statements?.firstOrNull() as? IrClass)?.attributeOwnerId) as? IrExpression + val extractedAnonymousFunction = if (actualArg?.isAdaptedFunctionReference() == true) ((actualArg as IrBlock).statements.last() as IrFunctionReference).attributeOwnerId else actualArg + if (extractedAnonymousFunction == ((originalExpression as? IrAttributeContainer)?.attributeOwnerId ?: originalExpression)) { + return block.inlineDeclaration + } + } + } + + return null + } + + private fun buildSmapFor(declaration: IrInlinedFunctionBlock, data: BlockInfo): PromisedValue { + val inlineCall = declaration.inlineCall + val callee = declaration.inlineDeclaration + + declaration.inlineCall.markLineNumber(startOffset = true) + mv.nop() + + val localSmaps = getLocalSmap() + if (declaration.isLambdaInlining()) { + val callSite = getLocalSmap().lastOrNull()?.smap?.callSite?.takeIf { inlineCall.isInvokeOnDefaultArg(callee) } + val classSMAP = context.typeToCachedSMAP[context.getLocalClassType(declaration.inlinedElement as IrAttributeContainer)]!! + + val sourceMapper = if (localSmaps.isEmpty()) { + smap + } else { + localSmaps.reversed().firstOrNull { it.inlinedBlock.inlineDeclaration == getInlinedAt(declaration.inlinedElement) }?.smap?.parent + ?: localSmaps.last().smap.parent // if we are in anonymous inlined class and lambda was declared outside + } + addToLocalSmap( + AdditionalIrInlineData( + SourceMapCopier(sourceMapper, classSMAP, callSite), + declaration, + context.getSourceMapper(if (declaration.inlinedElement is IrCallableReference<*>) irFunction.parentAsClass else callee.parentClassOrNull!!), + data.infos.filterIsInstance().lastOrNull() + ) + ) + } else { + val calleeFromActualClass = callee.getClassWithDeclaredFunction()!!.declarations + .asSequence() + .filterIsInstance() + .filter { it.attributeOwnerId == callee } // original callee could be transformed after lowerings, so we must get correct one + .filter { if (inlineCall.usesDefaultArguments()) it.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER else it.origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER } + .filter { it.origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE } // filter functions with $$forInline postfix + .single() + val nodeAndSmap = calleeFromActualClass.let { actualCallee -> + val callToActualCallee = IrCallImpl.fromSymbolOwner(inlineCall.startOffset, inlineCall.endOffset, inlineCall.type, actualCallee.symbol) + val callable = methodSignatureMapper.mapToCallableMethod(callToActualCallee, null) + val callGenerator = getOrCreateCallGenerator(callToActualCallee, data, callable.signature) + (callGenerator as InlineCodegen<*>).compileInline() + } + + val key = callee.parentClassOrNull!! + val newSmap = if (localSmaps.isEmpty()) { + smap + } else { + localSmaps.last().parentSmap + } + val sourcePosition = let { + val sourceInfo = newSmap.sourceInfo!! + val localFileEntry = getLocalSmap().lastOrNull()?.inlinedBlock?.inlineDeclaration?.fileEntry ?: fileEntry + val line = if (inlineCall.startOffset < 0) lastLineNumber else localFileEntry.getLineNumber(inlineCall.startOffset) + 1 +// val file = fileEntry.name.drop(1) + SourcePosition(line, sourceInfo.sourceFileName!!, sourceInfo.pathOrCleanFQN) + } + val sourceMapCopier = SourceMapCopier(newSmap, nodeAndSmap.classSMAP, sourcePosition) + addToLocalSmap( + AdditionalIrInlineData( + sourceMapCopier, declaration, context.getSourceMapper(key), + data.infos.filterIsInstance().lastOrNull() + ) + ) + + } + if (inlineCall.usesDefaultArguments()) { + // $default function has first LN pointing to original callee + callee.markLineNumber(startOffset = true) + mv.nop() + } + + return unitValue + } + override fun visitClass(declaration: IrClass, data: BlockInfo): PromisedValue { if (declaration.origin != JvmLoweredDeclarationOrigin.CONTINUATION_CLASS) { val childCodegen = ClassCodegen.getOrCreate(declaration, context, enclosingFunctionForLocalObjects) childCodegen.generate() closureReifiedMarkers[declaration] = childCodegen.reifiedTypeParametersUsages + if (irFunction.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER || declaration.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL) { + context.typeToCachedSMAP[childCodegen.type] = SMAP(childCodegen.smap.resultMappings) + } } return unitValue } @@ -1169,6 +1467,10 @@ class ExpressionCodegen( mv.nop() val endLabel = Label() val stackElement = unwindBlockStack(endLabel, data) { it.loop == jump.loop } + if ((jump.loop.body as? IrBlock)?.statements?.singleOrNull() is IrInlinedFunctionBlock) { + // There must be another line number because this jump is actually return from inlined function + jump.markLineNumber(startOffset = true) + } if (stackElement == null) { generateGlobalReturnFlagIfPossible(jump, jump.loop.nonLocalReturnLabel(jump is IrBreak)) mv.areturn(Type.VOID_TYPE) @@ -1307,15 +1609,17 @@ class ExpressionCodegen( ) { val gapStart = markNewLinkedLabel() data.localGapScope(tryWithFinallyInfo) { - finallyDepth++ - if (isFinallyMarkerRequired) { - generateFinallyMarker(mv, finallyDepth, true) + stashSmapForGivenTry(tryWithFinallyInfo) { + finallyDepth++ + if (isFinallyMarkerRequired) { + generateFinallyMarker(mv, finallyDepth, true) + } + tryWithFinallyInfo.onExit.accept(this, data).discard() + if (isFinallyMarkerRequired) { + generateFinallyMarker(mv, finallyDepth, false) + } + finallyDepth-- } - tryWithFinallyInfo.onExit.accept(this, data).discard() - if (isFinallyMarkerRequired) { - generateFinallyMarker(mv, finallyDepth, false) - } - finallyDepth-- if (tryCatchBlockEnd != null) { tryWithFinallyInfo.onExit.markLineNumber(startOffset = false) mv.goTo(tryCatchBlockEnd) 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 60611e45933..a34d10f49a6 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.* -import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpressionBody import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type +import kotlin.collections.set class IrFrameMap : FrameMapBase() { private val typeMap = mutableMapOf() @@ -67,19 +68,11 @@ 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 originalDeclarationBeforeInline = declaration.getOriginalDeclaration() ?: declaration - val fileEntry = originalDeclarationBeforeInline.fileParent.fileEntry + val irFile = declaration.fileParentBeforeInline + val type = declaration.getAttributeOwnerBeforeInline()?.let { getLocalClassType(it) } ?: defaultTypeMapper.mapClass(declaration) + + val fileEntry = irFile.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) { @@ -88,9 +81,8 @@ fun JvmBackendContext.getSourceMapper(declaration: IrClass): SourceMapper { } val sourceFileName = when (fileEntry) { is MultifileFacadeFileEntry -> fileEntry.partFiles.singleOrNull()?.name - else -> originalDeclarationBeforeInline.fileParent.name + else -> irFile.name } - val type = declaration.attributeOwnerIdBeforeInline?.let { getLocalClassType(it)!! } ?: defaultTypeMapper.mapClass(declaration) return SourceMapper( SourceInfo( sourceFileName, 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 index 3b7e137ea2b..d900b3dde07 100644 --- 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 @@ -17,6 +17,7 @@ object SingleArgumentInlineFunctionIntrinsic : IntrinsicMethod() { if (inlineLambda != null) { val lambdaInfo = IrExpressionLambdaImpl(codegen, inlineLambda) lambdaInfo.generateLambdaBody(sourceCompiler) + codegen.context.typeToCachedSMAP[lambdaInfo.lambdaClassType] = lambdaInfo.node.classSMAP } return codegen.unitValue diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt index 91dc92877ec..d5ebb37e0d6 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt @@ -209,7 +209,7 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin } this.metadata = function.metadata - context.suspendLambdaToOriginalFunctionMap[attributeOwnerId as IrFunctionReference] = function + context.suspendLambdaToOriginalFunctionMap[attributeOwnerId] = function } private fun IrClass.addInvokeSuspendForLambda( 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 ac67f6a9c43..bd687cc7a6d 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 @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.backend.jvm.caches.BridgeLoweringCache import org.jetbrains.kotlin.backend.jvm.caches.CollectionStubComputer import org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper import org.jetbrains.kotlin.backend.jvm.mapping.MethodSignatureMapper +import org.jetbrains.kotlin.codegen.inline.SMAP import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor @@ -29,7 +30,6 @@ import org.jetbrains.kotlin.ir.builders.irBlock import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.IrFunctionReference import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.IrType @@ -105,6 +105,9 @@ class JvmBackendContext( lateinit var getIntrinsic: (IrFunctionSymbol) -> IntrinsicMarker? + // Store evaluated SMAP for anonymous classes. Used only with IR inliner. + val typeToCachedSMAP = mutableMapOf() + private val localClassType = ConcurrentHashMap() val isCompilingAgainstJdk8OrLater = state.jvmBackendClassResolver.resolveToClassDescriptors( @@ -157,7 +160,7 @@ class JvmBackendContext( override val internalPackageFqn = FqName("kotlin.jvm") - val suspendLambdaToOriginalFunctionMap = ConcurrentHashMap() + val suspendLambdaToOriginalFunctionMap = ConcurrentHashMap() val suspendFunctionOriginalToView = ConcurrentHashMap() val staticDefaultStubs = ConcurrentHashMap() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrInlineUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrInlineUtils.kt index 71f8621ea8d..86cab596749 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrInlineUtils.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrInlineUtils.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.descriptors.DescriptorVisibility import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrBlock +import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression import org.jetbrains.kotlin.ir.expressions.IrFunctionReference import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.util.* @@ -75,9 +76,11 @@ private fun IrFunction.isInlineArrayConstructor(context: JvmBackendContext): Boo it == context.irBuiltIns.arrayClass || it in context.irBuiltIns.primitiveArraysToPrimitiveTypes } -fun IrFunction.isInlineOnly(): Boolean = - (isInline && hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME)) || - (this is IrSimpleFunction && correspondingPropertySymbol?.owner?.hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME) == true) +fun IrDeclaration.isInlineOnly(): Boolean = + this is IrFunction && ( + (isInline && hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME)) || + (this is IrSimpleFunction && correspondingPropertySymbol?.owner?.hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME) == true) + ) fun IrDeclarationWithVisibility.isEffectivelyInlineOnly(): Boolean = this is IrFunction && (isReifiable() || isInlineOnly() || isPrivateInlineSuspend()) @@ -87,3 +90,26 @@ fun IrFunction.isPrivateInlineSuspend(): Boolean = fun IrFunction.isReifiable(): Boolean = typeParameters.any { it.isReified } + +private fun IrAttributeContainer.getDeclarationBeforeInline(): IrDeclaration? { + val original = this.attributeOwnerIdBeforeInline ?: return null + return when (original) { + is IrClass -> return original + is IrFunctionExpression -> original.function + is IrFunctionReference -> original.symbol.owner + else -> null + } +} + +fun IrAttributeContainer.getAttributeOwnerBeforeInline(): IrAttributeContainer? { + if (this.attributeOwnerIdBeforeInline == null) return null + return generateSequence(this) { it.attributeOwnerIdBeforeInline }.last() +} + +val IrDeclaration.fileParentBeforeInline: IrFile + get() { + val original = (this as? IrAttributeContainer)?.getDeclarationBeforeInline() + ?: this.parentClassOrNull?.getDeclarationBeforeInline() + ?: this + return original.fileParent + } \ No newline at end of file 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 76678eb8af2..2bec68e5460 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 @@ -205,7 +205,7 @@ fun IrExpression.implicitCastIfNeededTo(type: IrType) = IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.IMPLICIT_CAST, type, this) fun IrFunctionAccessExpression.usesDefaultArguments(): Boolean = - symbol.owner.valueParameters.any { this.getValueArgument(it.index) == null } + symbol.owner.valueParameters.any { this.getValueArgument(it.index) == null && (!it.isVararg || it.defaultValue != null) } fun IrValueParameter.createStubDefaultValue(): IrExpressionBody = factory.createExpressionBody(