Support unbound callable function references in inliner
This commit is contained in:
@@ -484,4 +484,4 @@ fun IrClass.addFakeOverrides() {
|
||||
}
|
||||
|
||||
fun IrValueParameter.isInlineParameter() =
|
||||
!isNoinline && !type.isNullable() && type.isFunctionOrKFunction()
|
||||
!isNoinline && !type.isNullable() && (type.isFunction() || type.isSuspendFunctionTypeOrSubtype())
|
||||
@@ -106,6 +106,7 @@ val jvmPhases = namedIrFilePhase<JvmBackendContext>(
|
||||
lateinitPhase then
|
||||
|
||||
moveOrCopyCompanionObjectFieldsPhase then
|
||||
inlineCallableReferenceToLambdaPhase then
|
||||
propertyReferencePhase then
|
||||
constPhase then
|
||||
propertiesToFieldsPhase then
|
||||
|
||||
+9
-7
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.jvm.codegen
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.ir.isInlineParameter
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.inline.*
|
||||
@@ -14,7 +15,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.util.getArguments
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||
@@ -161,11 +162,12 @@ class IrExpressionLambdaImpl(
|
||||
}
|
||||
|
||||
fun isInlineIrExpression(argumentExpression: IrExpression) =
|
||||
argumentExpression is IrBlock &&
|
||||
(argumentExpression.origin == IrStatementOrigin.LAMBDA || argumentExpression.origin == IrStatementOrigin.ANONYMOUS_FUNCTION)
|
||||
when (argumentExpression) {
|
||||
is IrBlock -> (argumentExpression.origin == IrStatementOrigin.LAMBDA || argumentExpression.origin == IrStatementOrigin.ANONYMOUS_FUNCTION)
|
||||
//TODO: support bound CR
|
||||
is IrCallableReference -> argumentExpression.dispatchReceiver == null && argumentExpression.extensionReceiver == null
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun IrFunction.isInlineFunctionCall(context: JvmBackendContext) =
|
||||
(!context.state.isInlineDisabled || typeParameters.any { it.isReified }) && isInline
|
||||
|
||||
fun IrValueParameter.isInlineParameter() =
|
||||
!isNoinline && !type.isNullable() && type.isFunctionOrKFunction()
|
||||
(!context.state.isInlineDisabled || typeParameters.any { it.isReified }) && isInline
|
||||
+1
-1
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.backend.common.descriptors.isFunctionOrKFunctionType
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedName
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.ir.isInlineParameter
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||
import org.jetbrains.kotlin.backend.common.lower.irIfThen
|
||||
@@ -30,7 +31,6 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineFunctionCall
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrExpression
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineParameter
|
||||
import org.jetbrains.kotlin.codegen.PropertyReferenceCodegen
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.ScopeWithIr
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyValueParametersToStatic
|
||||
import org.jetbrains.kotlin.backend.common.ir.isInlineParameter
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineFunctionCall
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrExpression
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
internal val inlineCallableReferenceToLambdaPhase = makeIrFilePhase(
|
||||
::InlineCallableReferenceToLambdaPhase,
|
||||
name = "InlineCallableReferenceToLambdaPhase",
|
||||
description = "Transform callable reference to inline lambda"
|
||||
)
|
||||
|
||||
// This lowering transforms CR passed to inline function to lambda which would be inlined
|
||||
//
|
||||
// inline fun foo(inlineParameter: (A) -> B): B {
|
||||
// return inlineParameter()
|
||||
// }
|
||||
//
|
||||
// foo(::smth) -> foo { a -> smth(a) }
|
||||
//
|
||||
internal class InlineCallableReferenceToLambdaPhase(val context: JvmBackendContext) : FileLoweringPass {
|
||||
private val inlinableCR = mutableSetOf<IrCallableReference>()
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.transformChildrenVoid(object : IrElementTransformerVoidWithContext() {
|
||||
|
||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
|
||||
val callee = expression.symbol.owner
|
||||
if (callee.isInlineFunctionCall(context)) {
|
||||
for (valueParameter in callee.valueParameters) {
|
||||
if (valueParameter.isInlineParameter()) {
|
||||
expression.getValueArgument(valueParameter.index)?.let { argument ->
|
||||
if (argument is IrCallableReference && isInlineIrExpression(argument)) {
|
||||
inlinableCR.add(argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitFunctionAccess(expression)
|
||||
}
|
||||
|
||||
|
||||
override fun visitPropertyReference(expression: IrPropertyReference): IrExpression {
|
||||
if (expression !in inlinableCR) {
|
||||
return super.visitPropertyReference(expression)
|
||||
}
|
||||
|
||||
//Use getter if field is absent...
|
||||
val field =
|
||||
expression.field?.owner ?: return functionReferenceToLambda(currentScope!!, expression, expression.getter!!.owner)
|
||||
|
||||
//..else use field itself
|
||||
val irBuilder =
|
||||
context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset)
|
||||
|
||||
return irBuilder.irBlock(expression, IrStatementOrigin.LAMBDA) {
|
||||
val newLambda = buildFun {
|
||||
setSourceRange(expression)
|
||||
origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
|
||||
name = Name.identifier("stub_for_inline")
|
||||
visibility = Visibilities.LOCAL
|
||||
returnType = field.type
|
||||
isSuspend = false
|
||||
}.apply {
|
||||
|
||||
val receiver =
|
||||
if (field.isStatic) null
|
||||
else addValueParameter("receiver", field.parentAsClass.defaultType)
|
||||
|
||||
val lambdaBodyBuilder = this@InlineCallableReferenceToLambdaPhase.context.createIrBuilder(this.symbol)
|
||||
body = lambdaBodyBuilder.irBlockBody(startOffset, endOffset) {
|
||||
+irReturn(irGetField(if (receiver != null) irGet(receiver) else null, field))
|
||||
}
|
||||
}
|
||||
+newLambda
|
||||
|
||||
+IrFunctionReferenceImpl(
|
||||
expression.startOffset, expression.endOffset, field.type,
|
||||
newLambda.symbol, newLambda.symbol.descriptor, 0,
|
||||
IrStatementOrigin.LAMBDA
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
|
||||
if (inlinableCR.contains(expression)) {
|
||||
val referencedFunction = expression.symbol.owner
|
||||
return functionReferenceToLambda(currentScope!!, expression, referencedFunction)
|
||||
}
|
||||
|
||||
return super.visitFunctionReference(expression)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun functionReferenceToLambda(
|
||||
scope: ScopeWithIr,
|
||||
expression: IrCallableReference,
|
||||
referencedFunction: IrFunction
|
||||
): IrExpression {
|
||||
val irBuilder =
|
||||
context.createIrBuilder(scope.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset)
|
||||
|
||||
return irBuilder.irBlock(expression, IrStatementOrigin.LAMBDA) {
|
||||
val newLambda = buildFun {
|
||||
setSourceRange(expression)
|
||||
origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
|
||||
name = Name.identifier("stub_for_inline")
|
||||
visibility = Visibilities.LOCAL
|
||||
returnType = referencedFunction.returnType
|
||||
isSuspend = false
|
||||
}.apply {
|
||||
if (referencedFunction is IrConstructor) {
|
||||
copyTypeParametersFrom(referencedFunction.parentAsClass)
|
||||
}
|
||||
copyTypeParametersFrom(referencedFunction)
|
||||
copyValueParametersToStatic(referencedFunction, origin)
|
||||
val lambdaBodyBuilder = this@InlineCallableReferenceToLambdaPhase.context.createIrBuilder(this.symbol)
|
||||
body = lambdaBodyBuilder.irBlockBody(startOffset, endOffset) {
|
||||
var shift = 0
|
||||
+irReturn(
|
||||
irCall(referencedFunction.symbol).also { call ->
|
||||
|
||||
for (it in this@apply.typeParameters) {
|
||||
call.putTypeArgument(it.index, expression.getTypeArgument(it.index))
|
||||
}
|
||||
|
||||
referencedFunction.dispatchReceiverParameter?.let {
|
||||
call.dispatchReceiver = irGet(valueParameters[shift++])
|
||||
}
|
||||
referencedFunction.extensionReceiverParameter?.let {
|
||||
call.extensionReceiver = irGet(valueParameters[shift++])
|
||||
}
|
||||
for (it in referencedFunction.valueParameters.indices) {
|
||||
call.putValueArgument(it, irGet(valueParameters[shift++]))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+newLambda
|
||||
|
||||
+IrFunctionReferenceImpl(
|
||||
expression.startOffset, expression.endOffset, referencedFunction.returnType,
|
||||
newLambda.symbol, newLambda.symbol.descriptor, referencedFunction.typeParameters.size,
|
||||
IrStatementOrigin.LAMBDA
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: JClass.java
|
||||
|
||||
public class JClass {
|
||||
public int field;
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
fun box(): String {
|
||||
return if (call(10, JClass()::field) == 5) "OK" else "fail"
|
||||
}
|
||||
|
||||
inline fun call(p: Int, s: () -> Int): Int {
|
||||
return s()
|
||||
}
|
||||
|
||||
// 1 NEW JClass
|
||||
// 1 NEW
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun box(): String {
|
||||
return if (call(10, A()::calc) == 5) "OK" else "fail"
|
||||
}
|
||||
|
||||
class A {
|
||||
fun calc(p: Int): Int {
|
||||
return p / 2
|
||||
}
|
||||
}
|
||||
|
||||
inline fun call(p: Int, s: (Int) -> Int): Int {
|
||||
return s(p)
|
||||
}
|
||||
|
||||
// 1 NEW A
|
||||
// 1 NEW
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun box(): String {
|
||||
return if (call(A(10)::calc) == 5) "OK" else "fail"
|
||||
}
|
||||
|
||||
class A(val p: Int) {
|
||||
val calc: Int
|
||||
get() = p / 2
|
||||
}
|
||||
|
||||
inline fun call( s: () -> Int): Int {
|
||||
return s()
|
||||
}
|
||||
|
||||
// 1 NEW A
|
||||
// 1 NEW
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// FILE: JClass.java
|
||||
|
||||
public class JClass {
|
||||
public static int field;
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
fun box(): String {
|
||||
return if (call(10, JClass::field) == 5) "OK" else "fail"
|
||||
}
|
||||
|
||||
inline fun call(p: Int, s: () -> Int): Int {
|
||||
return s()
|
||||
}
|
||||
|
||||
// 0 NEW
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun box(): String {
|
||||
return if (call(10, ::calc) == 5) "OK" else "fail"
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
fun box(): String {
|
||||
return if (call(10, Int::calc) == 5) "OK" else "fail"
|
||||
}
|
||||
|
||||
val Int.calc: Int
|
||||
get() = this / 2
|
||||
|
||||
inline fun call(p: Int, s: (Int) -> Int): Int {
|
||||
return s(p)
|
||||
}
|
||||
|
||||
// 0 NEW
|
||||
+28
-3
@@ -689,15 +689,40 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/callableReference"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inline.kt")
|
||||
public void testInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/inline.kt");
|
||||
@TestMetadata("boundFieldReferenceInInline.kt")
|
||||
public void testBoundFieldReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/boundFieldReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("boundFunReferenceInInline.kt")
|
||||
public void testBoundFunReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/boundFunReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("boundPropertyReferenceInInline.kt")
|
||||
public void testBoundPropertyReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/boundPropertyReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nameIntrinsicWithImplicitThis.kt")
|
||||
public void testNameIntrinsicWithImplicitThis() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/nameIntrinsicWithImplicitThis.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unboundFieldReferenceInInline.kt")
|
||||
public void testUnboundFieldReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/unboundFieldReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unboundFunReferenceInInline.kt")
|
||||
public void testUnboundFunReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/unboundFunReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unboundPropertyReferenceInInline.kt")
|
||||
public void testUnboundPropertyReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/unboundPropertyReferenceInInline.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/bytecodeText/capturedVarsOptimization")
|
||||
|
||||
+28
-3
@@ -689,15 +689,40 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/callableReference"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inline.kt")
|
||||
public void testInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/inline.kt");
|
||||
@TestMetadata("boundFieldReferenceInInline.kt")
|
||||
public void testBoundFieldReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/boundFieldReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("boundFunReferenceInInline.kt")
|
||||
public void testBoundFunReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/boundFunReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("boundPropertyReferenceInInline.kt")
|
||||
public void testBoundPropertyReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/boundPropertyReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nameIntrinsicWithImplicitThis.kt")
|
||||
public void testNameIntrinsicWithImplicitThis() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/nameIntrinsicWithImplicitThis.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unboundFieldReferenceInInline.kt")
|
||||
public void testUnboundFieldReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/unboundFieldReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unboundFunReferenceInInline.kt")
|
||||
public void testUnboundFunReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/unboundFunReferenceInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unboundPropertyReferenceInInline.kt")
|
||||
public void testUnboundPropertyReferenceInInline() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/callableReference/unboundPropertyReferenceInInline.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/bytecodeText/capturedVarsOptimization")
|
||||
|
||||
Reference in New Issue
Block a user