From fa54b2cb8192d1f99cac27096ee49a47c82d8a8b Mon Sep 17 00:00:00 2001 From: pyos Date: Wed, 2 Oct 2019 11:21:45 +0200 Subject: [PATCH] JVM_IR: improve scope detection in accessor generation. * Sometimes, there's a class after the last function in the scope stack: that's the local type's (non-inline) primary constructor. * In an inline (but not crossinline) lambda, accessors are needed if and only if they would be needed in the function that the lambda is defined in. --- .../jvm/lower/CallableReferenceLowering.kt | 59 +++++++++++++------ .../jvm/lower/SyntheticAccessorLowering.kt | 22 ++++--- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt index 93528b0f507..fc40697a0b8 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt @@ -7,6 +7,7 @@ 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.IrElementVisitorVoidWithContext import org.jetbrains.kotlin.backend.common.ir.copyTo import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.backend.common.lower.createIrBuilder @@ -20,6 +21,7 @@ import org.jetbrains.kotlin.backend.jvm.ir.irArray import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.addConstructor @@ -33,10 +35,10 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.utils.addIfNotNull internal val callableReferencePhase = makeIrFilePhase( ::CallableReferenceLowering, @@ -44,18 +46,19 @@ internal val callableReferencePhase = makeIrFilePhase( description = "Handle callable references" ) -// Originally copied from K/Native -internal class CallableReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() { - // This pass ignores suspend function references and function references used in inline arguments to inline functions. - private val ignoredFunctionReferences = mutableSetOf() +private val IrStatementOrigin?.isLambda + get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION - private val IrFunctionReference.isIgnored: Boolean - get() = !type.isFunctionOrKFunction() || ignoredFunctionReferences.contains(this) +internal class InlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitorVoidWithContext() { + val inlineReferences = mutableSetOf() - override fun lower(irFile: IrFile) = irFile.transformChildrenVoid(this) + // For crossinline lambdas, the call site is null as it's probably in a separate class somewhere. + // All other lambdas are guaranteed to be inlined into the scope they are declared in. + val lambdaToCallSite = mutableMapOf() - // Mark function references appearing as inlined arguments to inline functions. - override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression { + override fun visitElement(element: IrElement) = element.acceptChildrenVoid(this) + + override fun visitFunctionAccess(expression: IrFunctionAccessExpression) { val function = expression.symbol.owner if (function.isInlineFunctionCall(context)) { for (parameter in function.valueParameters) { @@ -66,16 +69,41 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext) if (!isInlineIrExpression(valueArgument)) continue - if (valueArgument is IrFunctionReference) { - ignoredFunctionReferences.add(valueArgument) - } else if (valueArgument is IrBlock) { - ignoredFunctionReferences.addIfNotNull(valueArgument.statements.filterIsInstance().singleOrNull()) + val reference = when (valueArgument) { + is IrFunctionReference -> valueArgument + is IrBlock -> valueArgument.statements.filterIsInstance().singleOrNull() + else -> null + } ?: continue + + inlineReferences.add(reference) + if (valueArgument is IrBlock && valueArgument.origin.isLambda) { + lambdaToCallSite[reference.symbol.owner] = + if (parameter.isCrossinline) null else currentScope!!.irElement as IrDeclaration } } } return super.visitFunctionAccess(expression) } + companion object { + fun scan(context: JvmBackendContext, element: IrElement) = + InlineReferenceLocator(context).apply { element.accept(this, null) } + } +} + +// Originally copied from K/Native +internal class CallableReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() { + // This pass ignores suspend function references and function references used in inline arguments to inline functions. + private val ignoredFunctionReferences = mutableSetOf() + + private val IrFunctionReference.isIgnored: Boolean + get() = !type.isFunctionOrKFunction() || ignoredFunctionReferences.contains(this) + + override fun lower(irFile: IrFile) { + ignoredFunctionReferences.addAll(InlineReferenceLocator.scan(context, irFile).inlineReferences) + irFile.transformChildrenVoid(this) + } + override fun visitBlock(expression: IrBlock): IrExpression { if (!expression.origin.isLambda) return super.visitBlock(expression) @@ -403,9 +431,6 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext) } } - private val IrStatementOrigin?.isLambda - get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION - private val currentDeclarationParent get() = allScopes.last { it.irElement is IrDeclarationParent }.irElement as IrDeclarationParent } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt index 9493390cf2d..2db2d699d5e 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt @@ -45,8 +45,10 @@ internal val syntheticAccessorPhase = makeIrFilePhase( private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass { private val pendingTransformations = mutableListOf>() + private val inlineLambdaToCallSite = mutableMapOf() override fun lower(irFile: IrFile) { + inlineLambdaToCallSite.putAll(InlineReferenceLocator.scan(context, irFile).lambdaToCallSite) irFile.transformChildrenVoid(this) pendingTransformations.forEach { it() } } @@ -437,6 +439,16 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem this == JavaVisibilities.PROTECTED_AND_PACKAGE || this == JavaVisibilities.PROTECTED_STATIC_VISIBILITY + private fun IrDeclaration.getAccessContext(withSuper: Boolean): IrDeclarationContainer? = when { + this is IrDeclarationContainer -> this + // Accesses from public inline functions can actually be anywhere. For protected inline functions + // calling methods on `super` we need an accessor to satisfy INVOKESPECIAL constraints. + this is IrFunction && isInline && !visibility.isPrivate && (withSuper || !visibility.isProtected) -> null + // For inline lambdas, we can navigate to the only call site directly. + this in inlineLambdaToCallSite -> inlineLambdaToCallSite[this]?.getAccessContext(withSuper) + else -> (parent as? IrDeclaration)?.getAccessContext(withSuper) + } + private fun IrSymbol.isAccessible(withSuper: Boolean, thisObjReference: IrClassSymbol?): Boolean { /// We assume that IR code that reaches us has been checked for correctness at the frontend. /// This function needs to single out those cases where Java accessibility rules differ from Kotlin's. @@ -454,15 +466,9 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem // If local variables are accessible by Kotlin rules, they also are by Java rules. val symbolDeclarationContainer = (declaration.parent as? IrDeclarationContainer) as? IrElement ?: return true + val contextDeclarationContainer = (currentScope!!.irElement as IrDeclaration).getAccessContext(withSuper) ?: return false - // Within inline functions, we have to assume the worst. - val function = currentFunction?.irElement as IrFunction? - if (function?.isInline == true && !function.visibility.isPrivate && (withSuper || !function.visibility.isProtected)) - return false - - val contextDeclarationContainer = allScopes.lastOrNull { it.irElement is IrDeclarationContainer }?.irElement - - val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer?.getPackageFragment()?.fqName + val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer.getPackageFragment()?.fqName return when { declaration.visibility.isPrivate && symbolDeclarationContainer != contextDeclarationContainer -> false declaration.visibility.isProtected && !samePackage &&