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,
typeSystem: TypeSystemCommonBackendContext,
registerLineNumberAfterwards: Boolean,
isCallOfFunctionInCorrespondingDefaultDispatch: Boolean
) {
var nodeAndSmap: SMAPAndMethodNode? = null
try {
nodeAndSmap = createInlineMethodNode(
functionDescriptor, methodOwner, jvmSignature, mapDefaultSignature, typeArguments, typeSystem, state, sourceCompiler
)
endCall(inlineCall(nodeAndSmap, inlineDefaultLambdas, isCallOfFunctionInCorrespondingDefaultDispatch), registerLineNumberAfterwards)
endCall(inlineCall(nodeAndSmap, inlineDefaultLambdas), registerLineNumberAfterwards)
} catch (e: CompilationException) {
throw e
} catch (e: InlineException) {
@@ -206,7 +205,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
?: 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)'" }
val node = nodeAndSmap.node
if (inlineDefaultLambda) {
@@ -234,7 +233,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
val inliner = MethodInliner(
node, parameters, info, FieldRemapper(null, null, parameters), isSameModule,
"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,
!isInlinedToInlineFunInKotlinRuntime()
) //with captured
@@ -64,7 +64,7 @@ class PsiInlineCodegen(
}
try {
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 {
state.globalInlineContext.exitFromInlining()
}
@@ -1168,11 +1168,16 @@ class ExpressionCodegen(
element: IrFunctionAccessExpression, data: BlockInfo, signature: JvmMethodSignature
): IrCallGenerator {
if (!element.symbol.owner.isInlineFunctionCall(context) ||
classCodegen.irClass.fileParent.fileEntry is MultifileFacadeFileEntry
classCodegen.irClass.fileParent.fileEntry is MultifileFacadeFileEntry ||
irFunction.isInvokeSuspendOfContinuation()
) {
return IrCallGenerator.DefaultCallGenerator
}
if (irFunction == context.mapping.defaultArgumentsDispatchFunction[element.symbol.owner]) {
return IrInlineDefaultCodegen
}
val callee = element.symbol.owner
val typeArgumentContainer = if (callee is IrConstructor) callee.parentAsClass else callee
val typeArguments =
@@ -72,14 +72,6 @@ class IrInlineCodegen(
codegen: ExpressionCodegen,
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()
if (isInlineParameter && isInlineIrExpression(argumentExpression)) {
val irReference: IrFunctionReference =
@@ -165,7 +157,6 @@ class IrInlineCodegen(
false,
codegen.typeMapper.typeSystem,
registerLineNumberAfterwards = isInsideIfCondition,
isCallOfFunctionInCorrespondingDefaultDispatch = codegen.irFunction == codegen.context.mapping.defaultArgumentsDispatchFunction[function]
)
} finally {
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 {
return p
}
-9
View File
@@ -29,12 +29,6 @@ fun box(): String {
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
// test.kt:24 box
// test.kt:15 box
@@ -43,9 +37,6 @@ fun box(): String {
// test.kt:16 box
// test.kt:17 box
// test.kt:21 box
// LINENUMBERS JVM_IR
// test.kt:15 box
// LINENUMBERS
// test.kt:25 box
// test.kt:6 box
// test.kt:3 getMASSERTIONS_ENABLED
@@ -11,11 +11,6 @@ fun foo(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
// LINENUMBERS
// test.kt:4 box
@@ -25,7 +20,4 @@ inline fun bar(i: Int = 1) {
// test.kt:5 box
// test.kt:11 box
// test.kt:12 box
// LINENUMBERS JVM_IR
// test.kt:11 box
// LINENUMBERS
// test.kt:6 box
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// FILE: test.kt
inline fun foo(stringMaker: () -> String = { "OK" }): String {
return stringMaker()
@@ -17,19 +17,6 @@ fun box(): String {
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
// test.kt:15 box
// test.kt:3 box
@@ -13,18 +13,11 @@ fun box(): String {
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
// LINENUMBERS
// test.kt:12 box
// test.kt:3 box
// test.kt:4 box
// LINENUMBERS JVM_IR
// test.kt:3 box
// LINENUMBERS
// test.kt:13 box
// test.kt:7 ifoo2$default (synthetic)
// test.kt:8 ifoo2