Support unbound callable function references in inliner

This commit is contained in:
Mikhael Bogdanov
2019-05-22 15:06:05 +02:00
parent 0b59d0b416
commit 3c093f321d
13 changed files with 326 additions and 16 deletions
@@ -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
@@ -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
@@ -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
@@ -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
)
}
}
}
@@ -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
@@ -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
@@ -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
@@ -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,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
fun box(): String {
return if (call(10, ::calc) == 5) "OK" else "fail"
}
@@ -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
@@ -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")
@@ -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")