JVM IR: add jvmLocalClassExtractionPhase to lift out local classes from initializers

Otherwise a local class in a field initializer or anonymous init block
is copied into each constructor of the containing class (because
InitializersLowering calls deepCopy).

Since the code structure no longer resembles the original source code
here, record a custom EnclosingMethod mapping before moving such
classes, and use it in codegen.
This commit is contained in:
Alexander Udalov
2019-10-31 16:56:04 +01:00
parent c5159d9cbe
commit 6be9101675
13 changed files with 182 additions and 20 deletions
@@ -1,8 +1,14 @@
/*
* 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.common.lower
import org.jetbrains.kotlin.backend.common.BackendContext
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.addChild
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
@@ -12,8 +18,7 @@ import org.jetbrains.kotlin.ir.declarations.IrScript
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
//This lower takes part of old LocalDeclarationLowering job to pop up local classes from functions
class LocalClassPopupLowering(val context: BackendContext) : FileLoweringPass {
open class LocalClassPopupLowering(val context: BackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) {
val extractedLocalClasses = arrayListOf<Pair<IrClass, IrDeclarationContainer>>()
@@ -21,9 +26,10 @@ class LocalClassPopupLowering(val context: BackendContext) : FileLoweringPass {
override fun visitClassNew(declaration: IrClass): IrStatement {
val newDeclaration = super.visitClassNew(declaration)
if (newDeclaration !is IrClass || !newDeclaration.isLocalNotInner()) {
return newDeclaration
}
if (newDeclaration !is IrClass) return newDeclaration
val currentScope = allScopes[allScopes.lastIndex - 1]
if (!shouldPopUp(declaration, currentScope)) return newDeclaration
val newContainer = allScopes.asReversed().drop(1/*skip self*/).firstOrNull {
//find first class local or not;
@@ -39,4 +45,7 @@ class LocalClassPopupLowering(val context: BackendContext) : FileLoweringPass {
newContainer.addChild(local)
}
}
}
protected open fun shouldPopUp(klass: IrClass, currentScope: ScopeWithIr?): Boolean =
klass.isLocalNotInner()
}
@@ -27,12 +27,8 @@ import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrLocalDelegatedPropertySymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.util.ReferenceSymbolTable
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.name.FqName
@@ -65,7 +61,6 @@ class JvmBackendContext(
val irIntrinsics = IrIntrinsicMethods(irBuiltIns, ir.symbols)
// TODO: also store info for EnclosingMethod
internal class LocalClassInfo(val internalName: String)
private val localClassInfo = mutableMapOf<IrAttributeContainer, LocalClassInfo>()
@@ -77,6 +72,8 @@ class JvmBackendContext(
localClassInfo[container.attributeOwnerId] = value
}
internal val customEnclosingFunction = mutableMapOf<IrAttributeContainer, IrFunction>()
// TODO cache these at ClassCodegen level. Currently, sharing this map between classes in a module is required
// because IrSourceCompilerForInline constructs a new (Fake)ClassCodegen for every call to
// an inline function in the same module. Thus, if two inline functions happen to have the same name
@@ -145,6 +145,12 @@ internal val localDeclarationsPhase = makeIrFilePhase<CommonBackendContext>(
prerequisite = setOf(callableReferencePhase, sharedVariablesPhase)
)
private val jvmLocalClassExtractionPhase = makeIrFilePhase(
::JvmLocalClassPopupLowering,
name = "JvmLocalClassExtraction",
description = "Move local classes from field initializers and anonymous init blocks into the containing class"
)
private val defaultArgumentStubPhase = makeIrFilePhase(
::JvmDefaultArgumentStubGenerator,
name = "DefaultArgumentsStubGenerator",
@@ -193,7 +199,9 @@ private val initializersPhase = makeIrFilePhase(
error("No anonymous initializers should remain at this stage")
}
})
})
}),
// Depends on local class extraction, because otherwise local classes in initializers will be copied into each constructor.
prerequisite = setOf(jvmLocalClassExtractionPhase)
)
private val returnableBlocksPhase = makeIrFilePhase(
@@ -248,6 +256,7 @@ private val jvmFilePhases =
assertionPhase then
returnableBlocksPhase then
localDeclarationsPhase then
jvmLocalClassExtractionPhase then
jvmOverloadsAnnotationPhase then
jvmDefaultConstructorPhase then
@@ -122,7 +122,7 @@ open class ClassCodegen protected constructor(
val nestedClasses = irClass.declarations.mapNotNull { declaration ->
if (declaration is IrClass) {
ClassCodegen(declaration, context, this)
ClassCodegen(declaration, context, this, withinInline = withinInline)
} else null
}
@@ -411,9 +411,9 @@ open class ClassCodegen protected constructor(
// or constructor, the name and type of the function is recorded as well.
if (parentClassCodegen != null) {
val outerClassName = parentClassCodegen.type.internalName
// TODO: LocalDeclarationsLowering could have moved this class out of its enclosing method.
if (parentFunction != null) {
val method = methodSignatureMapper.mapAsmMethod(parentFunction)
val enclosingFunction = context.customEnclosingFunction[irClass.attributeOwnerId] ?: parentFunction
if (enclosingFunction != null) {
val method = methodSignatureMapper.mapAsmMethod(enclosingFunction)
visitor.visitOuterClass(outerClassName, method.name, method.descriptor)
} else if (irClass.isAnonymousObject) {
visitor.visitOuterClass(outerClassName, null, null)
@@ -35,7 +35,6 @@ import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
import org.jetbrains.kotlin.resolve.inline.INLINE_ONLY_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature
@@ -110,10 +109,12 @@ val IrType.isExtensionFunctionType: Boolean
get() = isFunctionTypeOrSubtype() && hasAnnotation(KotlinBuiltIns.FQ_NAMES.extensionFunctionType)
/* Borrowed from MemberCodegen.java */
/* Borrowed with modifications from MemberCodegen.java */
fun writeInnerClass(innerClass: IrClass, typeMapper: IrTypeMapper, context: JvmBackendContext, v: ClassBuilder) {
val outerClassInternalName = innerClass.parent.safeAs<IrClass>()?.let { typeMapper.classInternalName(it) }
val outerClassInternalName =
if (context.customEnclosingFunction[innerClass.attributeOwnerId] != null) null
else innerClass.parent.safeAs<IrClass>()?.let(typeMapper::classInternalName)
val innerName = innerClass.name.takeUnless { it.isSpecial }?.asString()
val innerClassInternalName = typeMapper.classInternalName(innerClass)
v.visitInnerClass(innerClassInternalName, outerClassInternalName, innerName, innerClass.calculateInnerClassAccessFlags(context))
@@ -0,0 +1,47 @@
/*
* 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.ScopeWithIr
import org.jetbrains.kotlin.backend.common.lower.LocalClassPopupLowering
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrAnonymousInitializer
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class JvmLocalClassPopupLowering(context: JvmBackendContext) : LocalClassPopupLowering(context) {
// On JVM, we only pop up local classes in field initializers and anonymous init blocks, so that InitializersLowering would not copy
// them to each constructor. (Moving all local classes is not possible because of cases where they use reified type parameters,
// or capture crossinline lambdas.)
// Upon moving such class, we record customEnclosingFunction for it to be the class constructor. This is needed because otherwise
// the class will not get any EnclosingMethod in the codegen later, since it won't be local anymore.
override fun shouldPopUp(klass: IrClass, currentScope: ScopeWithIr?): Boolean {
// On JVM, lambdas have package-private visibility after LocalDeclarationsLowering, so we have to check something else.
val isLocal = super.shouldPopUp(klass, currentScope) ||
klass.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL ||
klass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL ||
klass.origin == JvmLoweredDeclarationOrigin.GENERATED_PROPERTY_REFERENCE
if (!isLocal) return false
val container = when (val element = currentScope?.irElement) {
is IrAnonymousInitializer -> element.parentAsClass.takeUnless { element.isStatic }
is IrField -> element.parentAsClass.takeUnless { element.isStatic }
else -> null
} ?: return false
// In case there's no primary constructor, it's unclear which constructor should be the enclosing one, so we select the first.
(context as JvmBackendContext).customEnclosingFunction[klass.attributeOwnerId] =
container.primaryConstructor ?: container.declarations.firstIsInstanceOrNull()
?: error("Class in a non-static initializer found, but container has no constructors: ${container.render()}")
return true
}
}
@@ -0,0 +1,47 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
class C {
val f by foo {
{}
}
}
fun foo(f: () -> Any): Any = f()
operator fun Any.getValue(thiz: Any?, metadata: Any?): Any = this
fun box(): String {
// This is the class for lambda inside the `foo` call (`{}`)
val innerLambda = C().f.javaClass
val emInner = innerLambda.getEnclosingMethod()
if (emInner?.getName() != "invoke") return "Fail: incorrect enclosing method for inner lambda: $emInner"
val ecInner = innerLambda.getEnclosingClass()
if (ecInner?.getName() != "C\$f\$2") return "Fail: incorrect enclosing class for inner lambda: $ecInner"
val ectorInner = innerLambda.getEnclosingConstructor()
if (ectorInner != null) return "Fail: inner lambda should not have enclosing constructor: $ectorInner"
val dcInner = innerLambda.getDeclaringClass()
if (dcInner != null) return "Fail: inner lambda should not have declaring class: $dcInner"
// This is the class for lambda that is passed as an argument to `foo`
val outerLambda = ecInner
val emOuter = outerLambda.getEnclosingMethod()
if (emOuter != null) return "Fail: outer lambda should not have enclosing method: $emOuter"
val ecOuter = outerLambda.getEnclosingClass()
if (ecOuter?.getName() != "C") return "Fail: incorrect enclosing class for outer lambda: $ecOuter"
val ectorOuter = outerLambda.getEnclosingConstructor()
if (ectorOuter == null) return "Fail: outer lambda _should_ have enclosing constructor"
val dcOuter = outerLambda.getDeclaringClass()
if (dcOuter != null) return "Fail: outer lambda should not have declaring class: $dcOuter"
return "OK"
}
@@ -0,0 +1,27 @@
// TARGET_BACKEND: JVM
// FILE: A.kt
class A {
val o = object {
@JvmName("jvmGetO")
fun getO(): String = "O"
}
val k = object {
@get:JvmName("jvmGetK")
val k: String = "K"
}
}
// FILE: B.kt
import kotlin.reflect.full.*
fun box(): String {
val a = A()
val obj1 = a.o
val o = obj1::class.declaredMemberFunctions.single().call(obj1) as String
val obj2 = a.k
val k = obj2::class.declaredMemberProperties.single().call(obj2) as String
return o + k
}
@@ -23105,6 +23105,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPackage.kt");
}
@TestMetadata("lambdaInPropertyDelegate.kt")
public void testLambdaInPropertyDelegate() throws Exception {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPropertyDelegate.kt");
}
@TestMetadata("lambdaInPropertyGetter.kt")
public void testLambdaInPropertyGetter() throws Exception {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPropertyGetter.kt");
@@ -223,6 +223,11 @@ public class CompileKotlinAgainstKotlinTestGenerated extends AbstractCompileKotl
runTest("compiler/testData/compileKotlinAgainstKotlin/kt21775.kt");
}
@TestMetadata("metadataForMembersInLocalClassInInitializer.kt")
public void testMetadataForMembersInLocalClassInInitializer() throws Exception {
runTest("compiler/testData/compileKotlinAgainstKotlin/metadataForMembersInLocalClassInInitializer.kt");
}
@TestMetadata("multifileClassInlineFunctionAccessingProperty.kt")
public void testMultifileClassInlineFunctionAccessingProperty() throws Exception {
runTest("compiler/testData/compileKotlinAgainstKotlin/multifileClassInlineFunctionAccessingProperty.kt");
@@ -21922,6 +21922,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPackage.kt");
}
@TestMetadata("lambdaInPropertyDelegate.kt")
public void testLambdaInPropertyDelegate() throws Exception {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPropertyDelegate.kt");
}
@TestMetadata("lambdaInPropertyGetter.kt")
public void testLambdaInPropertyGetter() throws Exception {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPropertyGetter.kt");
@@ -21670,6 +21670,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPackage.kt");
}
@TestMetadata("lambdaInPropertyDelegate.kt")
public void testLambdaInPropertyDelegate() throws Exception {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPropertyDelegate.kt");
}
@TestMetadata("lambdaInPropertyGetter.kt")
public void testLambdaInPropertyGetter() throws Exception {
runTest("compiler/testData/codegen/box/reflection/enclosing/lambdaInPropertyGetter.kt");
@@ -218,6 +218,11 @@ public class IrCompileKotlinAgainstKotlinTestGenerated extends AbstractIrCompile
runTest("compiler/testData/compileKotlinAgainstKotlin/kt21775.kt");
}
@TestMetadata("metadataForMembersInLocalClassInInitializer.kt")
public void testMetadataForMembersInLocalClassInInitializer() throws Exception {
runTest("compiler/testData/compileKotlinAgainstKotlin/metadataForMembersInLocalClassInInitializer.kt");
}
@TestMetadata("multifileClassInlineFunctionAccessingProperty.kt")
public void testMultifileClassInlineFunctionAccessingProperty() throws Exception {
runTest("compiler/testData/compileKotlinAgainstKotlin/multifileClassInlineFunctionAccessingProperty.kt");