From bb5a639153b483ef130ae2992fb2e73eba4cf5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Sch=C3=A4fer?= Date: Thu, 5 Mar 2020 11:35:51 +0100 Subject: [PATCH] JVM IR: Turn static callable references into singletons --- .../ir/FirBlackBoxCodegenTestGenerated.java | 10 ++ .../jetbrains/kotlin/backend/jvm/JvmLower.kt | 1 + .../backend/jvm/lower/StaticLambdaLowering.kt | 110 ++++++++++++++++++ .../function/staticFunctionReference.kt | 22 ++++ .../codegen/box/closures/staticLambda.kt | 20 ++++ .../unit/override5_ir.txt | 13 +-- .../codegen/BlackBoxCodegenTestGenerated.java | 10 ++ .../LightAnalysisModeTestGenerated.java | 10 ++ .../ir/IrBlackBoxCodegenTestGenerated.java | 10 ++ 9 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/StaticLambdaLowering.kt create mode 100644 compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt create mode 100644 compiler/testData/codegen/box/closures/staticLambda.kt diff --git a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java index a1443759ef0..e573ffa75e1 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java @@ -2376,6 +2376,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/callableReference/function/specialCalls.kt"); } + @TestMetadata("staticFunctionReference.kt") + public void testStaticFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt"); + } + @TestMetadata("topLevelFromClass.kt") public void testTopLevelFromClass() throws Exception { runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromClass.kt"); @@ -4162,6 +4167,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/closures/simplestClosureAndBoxing.kt"); } + @TestMetadata("staticLambda.kt") + public void testStaticLambda() throws Exception { + runTest("compiler/testData/codegen/box/closures/staticLambda.kt"); + } + @TestMetadata("subclosuresWithinInitializers.kt") public void testSubclosuresWithinInitializers() throws Exception { runTest("compiler/testData/codegen/box/closures/subclosuresWithinInitializers.kt"); diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index ce4087a1c67..963e9d7303a 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -301,6 +301,7 @@ private val jvmFilePhases = returnableBlocksPhase then localDeclarationsPhase then jvmLocalClassExtractionPhase then + staticLambdaPhase then jvmDefaultConstructorPhase then diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/StaticLambdaLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/StaticLambdaLowering.kt new file mode 100644 index 00000000000..c6488593843 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/StaticLambdaLowering.kt @@ -0,0 +1,110 @@ +/* + * 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.lower + +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +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.ir.IrElement +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irExprBody +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classifierOrNull +import org.jetbrains.kotlin.ir.util.constructedClass +import org.jetbrains.kotlin.ir.util.primaryConstructor +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +internal val staticLambdaPhase = makeIrFilePhase( + ::StaticLambdaLowering, + name = "StaticLambdaPhase", + description = "Turn static callable references into singletons" +) + +class StaticLambdaLowering(val backendContext: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoid() { + override fun lower(irFile: IrFile) = irFile.transformChildrenVoid() + + override fun visitClass(declaration: IrClass): IrStatement { + declaration.transformChildrenVoid() + if (declaration.isSyntheticSingleton) { + declaration.declarations += backendContext.declarationFactory.getFieldForObjectInstance(declaration).also { field -> + field.initializer = backendContext.createIrBuilder(field.symbol).run { + irExprBody(irCall(declaration.primaryConstructor!!)) + } + } + } + return declaration + } + + override fun visitConstructorCall(expression: IrConstructorCall): IrExpression { + val constructor = expression.symbol.owner + if (!constructor.constructedClass.isSyntheticSingleton) + return super.visitConstructorCall(expression) + + val instanceField = backendContext.declarationFactory.getFieldForObjectInstance(constructor.constructedClass) + return IrGetFieldImpl(expression.startOffset, expression.endOffset, instanceField.symbol, expression.type) + } + + // Recognize callable references with no value or type arguments. The only type arguments in Kotlin stem from usages of + // reified type parameters, which we unfortunately don't record as parameters so we have to check the body of the class. + private val IrClass.isSyntheticSingleton: Boolean + get() = (origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL || origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL) + && primaryConstructor!!.valueParameters.isEmpty() + && !containsReifiedTypeParameters + + // Check whether there is any usage of reified type parameters in the body of the given class. This method does not + // distinguish between reified type parameters declared in inline functions inside the class and those coming from the + // outside. This is sufficient, because we only apply this function to callable references where this does not matter. + private val IrClass.containsReifiedTypeParameters: Boolean + get() = containsReifiedTypeParametersCache.getOrPut(this) { + var containsReified = false + acceptChildrenVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + if (!containsReified) + element.acceptChildrenVoid(this) + } + + override fun visitMemberAccess(expression: IrMemberAccessExpression) { + for (i in 0 until expression.typeArgumentsCount) { + if (expression.getTypeArgument(i)?.isReified == true) { + containsReified = true + break + } + } + super.visitMemberAccess(expression) + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall) { + if (expression.typeOperand.isReified) + containsReified = true + super.visitTypeOperator(expression) + } + + override fun visitClassReference(expression: IrClassReference) { + if (expression.classType.isReified) + containsReified = true + super.visitClassReference(expression) + } + + private val IrType.isReified: Boolean + get() = classifierOrNull?.safeAs()?.owner?.isReified == true + }) + return containsReified + } + + private val containsReifiedTypeParametersCache = mutableMapOf() +} diff --git a/compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt b/compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt new file mode 100644 index 00000000000..8d539d5059f --- /dev/null +++ b/compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt @@ -0,0 +1,22 @@ +// TARGET_BACKEND: JVM + +var capturedRef: ((Int) -> Int)? = null + +fun ref(x: Int) = x + +fun updateCapturedRef(): Boolean { + val r = ::ref + if (capturedRef == null) { + capturedRef = r + } else if (capturedRef !== r) { + return false + } + return true +} + +fun box(): String { + updateCapturedRef() + if (!updateCapturedRef()) + return "FAIL" + return "OK" +} diff --git a/compiler/testData/codegen/box/closures/staticLambda.kt b/compiler/testData/codegen/box/closures/staticLambda.kt new file mode 100644 index 00000000000..92f4b0ebfb5 --- /dev/null +++ b/compiler/testData/codegen/box/closures/staticLambda.kt @@ -0,0 +1,20 @@ +// TARGET_BACKEND: JVM + +var capturedLambda: ((Int) -> Int)? = null + +fun captureLambda(): Boolean { + val lambda = { x: Int -> x + 1 } + if (capturedLambda == null) { + capturedLambda = lambda + } else if (capturedLambda !== lambda) { + return false + } + return true +} + +fun box(): String { + captureLambda() + if (!captureLambda()) + return "FAIL" + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt index 468edcfb5b6..a650083850b 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt @@ -3,19 +3,10 @@ public interface Base { public abstract @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object } -@kotlin.Metadata -public final class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1$2 { - inner class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1 - inner class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1$2 - public method (): void - public synthetic bridge @org.jetbrains.annotations.Nullable method invoke(): java.lang.Object - public final method invoke(): void -} - @kotlin.Metadata public final class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1 { inner class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1 - inner class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1$2 + inner class Override5Kt$inlineMe$1$generic$2 public method (): void public @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object } @@ -46,8 +37,10 @@ public final class Override5Kt$inlineMe$1$generic$1 { @kotlin.Metadata public final class Override5Kt$inlineMe$1$generic$2 { + public final static @org.jetbrains.annotations.NotNull field INSTANCE: Override5Kt$inlineMe$1$generic$2 inner class Override5Kt$inlineMe$1 inner class Override5Kt$inlineMe$1$generic$2 + static method (): void public method (): void public synthetic bridge @org.jetbrains.annotations.Nullable method invoke(): java.lang.Object public final method invoke(): void diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 087c891deb2..00711153317 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -2396,6 +2396,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/callableReference/function/specialCalls.kt"); } + @TestMetadata("staticFunctionReference.kt") + public void testStaticFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt"); + } + @TestMetadata("topLevelFromClass.kt") public void testTopLevelFromClass() throws Exception { runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromClass.kt"); @@ -4182,6 +4187,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/closures/simplestClosureAndBoxing.kt"); } + @TestMetadata("staticLambda.kt") + public void testStaticLambda() throws Exception { + runTest("compiler/testData/codegen/box/closures/staticLambda.kt"); + } + @TestMetadata("subclosuresWithinInitializers.kt") public void testSubclosuresWithinInitializers() throws Exception { runTest("compiler/testData/codegen/box/closures/subclosuresWithinInitializers.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 3c18e68c329..1a8e1241b70 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -2396,6 +2396,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/callableReference/function/specialCalls.kt"); } + @TestMetadata("staticFunctionReference.kt") + public void testStaticFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt"); + } + @TestMetadata("topLevelFromClass.kt") public void testTopLevelFromClass() throws Exception { runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromClass.kt"); @@ -4182,6 +4187,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/closures/simplestClosureAndBoxing.kt"); } + @TestMetadata("staticLambda.kt") + public void testStaticLambda() throws Exception { + runTest("compiler/testData/codegen/box/closures/staticLambda.kt"); + } + @TestMetadata("subclosuresWithinInitializers.kt") public void testSubclosuresWithinInitializers() throws Exception { runTest("compiler/testData/codegen/box/closures/subclosuresWithinInitializers.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index b436eeb0c14..d145a999a78 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -2376,6 +2376,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/callableReference/function/specialCalls.kt"); } + @TestMetadata("staticFunctionReference.kt") + public void testStaticFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/callableReference/function/staticFunctionReference.kt"); + } + @TestMetadata("topLevelFromClass.kt") public void testTopLevelFromClass() throws Exception { runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromClass.kt"); @@ -4162,6 +4167,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/closures/simplestClosureAndBoxing.kt"); } + @TestMetadata("staticLambda.kt") + public void testStaticLambda() throws Exception { + runTest("compiler/testData/codegen/box/closures/staticLambda.kt"); + } + @TestMetadata("subclosuresWithinInitializers.kt") public void testSubclosuresWithinInitializers() throws Exception { runTest("compiler/testData/codegen/box/closures/subclosuresWithinInitializers.kt");