JVM IR: Make inlining in $default stubs compatible with the JVM BE

The JVM BE inlines calls to the underlying function in a $default stub
verbatim, e.g., without renaming LVT entries or regenerating anonymous
objects. This commit introduces the same behavior in the JVM IR BE.

Fixes KT-36769.
This commit is contained in:
Steven Schäfer
2020-09-14 18:14:20 +02:00
committed by max-kammerer
parent bef0437edb
commit 5e27d9b089
10 changed files with 66 additions and 56 deletions
@@ -133,14 +133,13 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
mapDefaultSignature: Boolean, mapDefaultSignature: Boolean,
typeSystem: TypeSystemCommonBackendContext, typeSystem: TypeSystemCommonBackendContext,
registerLineNumberAfterwards: Boolean, registerLineNumberAfterwards: Boolean,
isCallOfFunctionInCorrespondingDefaultDispatch: Boolean
) { ) {
var nodeAndSmap: SMAPAndMethodNode? = null var nodeAndSmap: SMAPAndMethodNode? = null
try { try {
nodeAndSmap = createInlineMethodNode( nodeAndSmap = createInlineMethodNode(
functionDescriptor, methodOwner, jvmSignature, mapDefaultSignature, typeArguments, typeSystem, state, sourceCompiler functionDescriptor, methodOwner, jvmSignature, mapDefaultSignature, typeArguments, typeSystem, state, sourceCompiler
) )
endCall(inlineCall(nodeAndSmap, inlineDefaultLambdas, isCallOfFunctionInCorrespondingDefaultDispatch), registerLineNumberAfterwards) endCall(inlineCall(nodeAndSmap, inlineDefaultLambdas), registerLineNumberAfterwards)
} catch (e: CompilationException) { } catch (e: CompilationException) {
throw e throw e
} catch (e: InlineException) { } catch (e: InlineException) {
@@ -206,7 +205,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
?: error("No stack value for continuation parameter of suspend function") ?: error("No stack value for continuation parameter of suspend function")
} }
protected fun inlineCall(nodeAndSmap: SMAPAndMethodNode, inlineDefaultLambda: Boolean, isCallOfFunctionInCorrespondingDefaultDispatch: Boolean): InlineResult { private fun inlineCall(nodeAndSmap: SMAPAndMethodNode, inlineDefaultLambda: Boolean): InlineResult {
assert(delayedHiddenWriting == null) { "'putHiddenParamsIntoLocals' should be called after 'processAndPutHiddenParameters(true)'" } assert(delayedHiddenWriting == null) { "'putHiddenParamsIntoLocals' should be called after 'processAndPutHiddenParameters(true)'" }
val node = nodeAndSmap.node val node = nodeAndSmap.node
if (inlineDefaultLambda) { if (inlineDefaultLambda) {
@@ -234,7 +233,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
val inliner = MethodInliner( val inliner = MethodInliner(
node, parameters, info, FieldRemapper(null, null, parameters), isSameModule, node, parameters, info, FieldRemapper(null, null, parameters), isSameModule,
"Method inlining " + sourceCompiler.callElementText, "Method inlining " + sourceCompiler.callElementText,
SourceMapCopier(sourceMapper, nodeAndSmap.classSMAP, callSite.takeIf { !isCallOfFunctionInCorrespondingDefaultDispatch }), SourceMapCopier(sourceMapper, nodeAndSmap.classSMAP, callSite),
info.callSiteInfo, if (functionDescriptor.isInlineOnly()) InlineOnlySmapSkipper(codegen) else null, info.callSiteInfo, if (functionDescriptor.isInlineOnly()) InlineOnlySmapSkipper(codegen) else null,
!isInlinedToInlineFunInKotlinRuntime() !isInlinedToInlineFunInKotlinRuntime()
) //with captured ) //with captured
@@ -64,7 +64,7 @@ class PsiInlineCodegen(
} }
try { try {
val registerLineNumber = registerLineNumberAfterwards(resolvedCall) val registerLineNumber = registerLineNumberAfterwards(resolvedCall)
performInline(resolvedCall?.typeArguments?.keys?.toList(), callDefault, callDefault, codegen.typeSystem, registerLineNumber, false) performInline(resolvedCall?.typeArguments?.keys?.toList(), callDefault, callDefault, codegen.typeSystem, registerLineNumber)
} finally { } finally {
state.globalInlineContext.exitFromInlining() state.globalInlineContext.exitFromInlining()
} }
@@ -1168,11 +1168,16 @@ class ExpressionCodegen(
element: IrFunctionAccessExpression, data: BlockInfo, signature: JvmMethodSignature element: IrFunctionAccessExpression, data: BlockInfo, signature: JvmMethodSignature
): IrCallGenerator { ): IrCallGenerator {
if (!element.symbol.owner.isInlineFunctionCall(context) || if (!element.symbol.owner.isInlineFunctionCall(context) ||
classCodegen.irClass.fileParent.fileEntry is MultifileFacadeFileEntry classCodegen.irClass.fileParent.fileEntry is MultifileFacadeFileEntry ||
irFunction.isInvokeSuspendOfContinuation()
) { ) {
return IrCallGenerator.DefaultCallGenerator return IrCallGenerator.DefaultCallGenerator
} }
if (irFunction == context.mapping.defaultArgumentsDispatchFunction[element.symbol.owner]) {
return IrInlineDefaultCodegen
}
val callee = element.symbol.owner val callee = element.symbol.owner
val typeArgumentContainer = if (callee is IrConstructor) callee.parentAsClass else callee val typeArgumentContainer = if (callee is IrConstructor) callee.parentAsClass else callee
val typeArguments = val typeArguments =
@@ -72,14 +72,6 @@ class IrInlineCodegen(
codegen: ExpressionCodegen, codegen: ExpressionCodegen,
blockInfo: BlockInfo blockInfo: BlockInfo
) { ) {
if (codegen.irFunction.isInvokeSuspendOfContinuation()) {
// In order to support java interop of inline suspend functions, we generate continuations for these inline suspend functions.
// These functions should behave as ordinary suspend functions, i.e. we should not inline the content of the inline function
// into continuation.
// Thus, we should put its arguments to stack.
super.genValueAndPut(irValueParameter, argumentExpression, parameterType, codegen, blockInfo)
}
val isInlineParameter = irValueParameter.isInlineParameter() val isInlineParameter = irValueParameter.isInlineParameter()
if (isInlineParameter && isInlineIrExpression(argumentExpression)) { if (isInlineParameter && isInlineIrExpression(argumentExpression)) {
val irReference: IrFunctionReference = val irReference: IrFunctionReference =
@@ -165,7 +157,6 @@ class IrInlineCodegen(
false, false,
codegen.typeMapper.typeSystem, codegen.typeMapper.typeSystem,
registerLineNumberAfterwards = isInsideIfCondition, registerLineNumberAfterwards = isInsideIfCondition,
isCallOfFunctionInCorrespondingDefaultDispatch = codegen.irFunction == codegen.context.mapping.defaultArgumentsDispatchFunction[function]
) )
} finally { } finally {
state.globalInlineContext.exitFromInlining() state.globalInlineContext.exitFromInlining()
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2020 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.codegen
import org.jetbrains.kotlin.codegen.inline.MethodBodyVisitor
import org.jetbrains.kotlin.codegen.inline.SourceMapCopier
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
/**
* A specialization of IrInlineCodegen for calls to the underlying method in a $default handler.
* Such calls are inlined verbatim in the JVM backend (see InlineCodegenForDefaultBody.kt).
* For compatibility we have to do the same thing in the JVM IR backend.
*/
object IrInlineDefaultCodegen : IrCallGenerator {
override fun genValueAndPut(
irValueParameter: IrValueParameter,
argumentExpression: IrExpression,
parameterType: Type,
codegen: ExpressionCodegen,
blockInfo: BlockInfo
) {
// This codegen is only used for calls to the underlying function in a $default stub.
// For such calls we know that we are passing along the value parameters and reusing the same indices.
// There is no need to generate any code.
assert(argumentExpression is IrGetValue || argumentExpression is IrTypeOperatorCall && argumentExpression.argument is IrGetValue)
}
override fun genCall(callableMethod: IrCallableMethod, codegen: ExpressionCodegen, expression: IrFunctionAccessExpression) {
val function = expression.symbol.owner
val nodeAndSmap = codegen.classCodegen.generateMethodNode(function, codegen.delegatedPropertyOptimizer)
val childSourceMapper = SourceMapCopier(codegen.smap, nodeAndSmap.classSMAP)
val argsSize =
(Type.getArgumentsAndReturnSizes(callableMethod.asmMethod.descriptor) ushr 2) - if (function.isStatic) 1 else 0
nodeAndSmap.node.accept(object : MethodBodyVisitor(codegen.visitor) {
override fun visitLocalVariable(name: String, desc: String, signature: String?, start: Label, end: Label, index: Int) {
// We only copy LVT entries for local variables, since we already generated entries for the method parameters,
if (index >= argsSize) super.visitLocalVariable(name, desc, signature, start, end, index)
}
override fun visitLineNumber(line: Int, start: Label?) {
super.visitLineNumber(childSourceMapper.mapLineNumber(line), start)
}
})
}
}
@@ -1,6 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TODO KT-36769 Missing LVT entries for inline function (default) parameters at call site
inline fun test(p: String = "OK"): String { inline fun test(p: String = "OK"): String {
return p return p
} }
-9
View File
@@ -29,12 +29,6 @@ fun box(): String {
return "OK" return "OK"
} }
// JVM_IR steps on line 15 both on the way in and on the way out
// of the massert method. This is consistent with what would
// happen if massert was not inline and we used force-step-into
// to step through the $default method. JVM only hits line 15 on
// the way in.
// LINENUMBERS // LINENUMBERS
// test.kt:24 box // test.kt:24 box
// test.kt:15 box // test.kt:15 box
@@ -43,9 +37,6 @@ fun box(): String {
// test.kt:16 box // test.kt:16 box
// test.kt:17 box // test.kt:17 box
// test.kt:21 box // test.kt:21 box
// LINENUMBERS JVM_IR
// test.kt:15 box
// LINENUMBERS
// test.kt:25 box // test.kt:25 box
// test.kt:6 box // test.kt:6 box
// test.kt:3 getMASSERTIONS_ENABLED // test.kt:3 getMASSERTIONS_ENABLED
@@ -11,11 +11,6 @@ fun foo(i: Int = 1) {
inline fun bar(i: Int = 1) { inline fun bar(i: Int = 1) {
} }
// The JVM_IR backend has line number 11 for the inlined
// default argument handling both before and after the actual
// body of bar. That is consistent with what happens with the
// $default method in the non-inlined case.
// FORCE_STEP_INTO // FORCE_STEP_INTO
// LINENUMBERS // LINENUMBERS
// test.kt:4 box // test.kt:4 box
@@ -25,7 +20,4 @@ inline fun bar(i: Int = 1) {
// test.kt:5 box // test.kt:5 box
// test.kt:11 box // test.kt:11 box
// test.kt:12 box // test.kt:12 box
// LINENUMBERS JVM_IR
// test.kt:11 box
// LINENUMBERS
// test.kt:6 box // test.kt:6 box
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// FILE: test.kt // FILE: test.kt
inline fun foo(stringMaker: () -> String = { "OK" }): String { inline fun foo(stringMaker: () -> String = { "OK" }): String {
return stringMaker() return stringMaker()
@@ -17,19 +17,6 @@ fun box(): String {
return "OK" return "OK"
} }
// The IR Backend does the following:
// test.kt:15
// test.kt:3
// test.kt:4
// test.kt:3
// test.kt:3 <---
// test.kt:16
// test.kt:7
// test.kt:11
// test.kt:8
// test.kt:7 <---
// test.kt:17
// LINENUMBERS // LINENUMBERS
// test.kt:15 box // test.kt:15 box
// test.kt:3 box // test.kt:3 box
@@ -13,18 +13,11 @@ fun box(): String {
return ifoo2() return ifoo2()
} }
// JVM_IR backend has the same stepping behavior as
// the non-inlined case when using force step into to step
// through $default method. JVM does not.
// FORCE_STEP_INTO // FORCE_STEP_INTO
// LINENUMBERS // LINENUMBERS
// test.kt:12 box // test.kt:12 box
// test.kt:3 box // test.kt:3 box
// test.kt:4 box // test.kt:4 box
// LINENUMBERS JVM_IR
// test.kt:3 box
// LINENUMBERS
// test.kt:13 box // test.kt:13 box
// test.kt:7 ifoo2$default (synthetic) // test.kt:7 ifoo2$default (synthetic)
// test.kt:8 ifoo2 // test.kt:8 ifoo2