JVM_IR: further refine synthetic accessor generation

References to protected members from crossinline lambdas in the same
package do not need accessors.
This commit is contained in:
pyos
2020-01-28 14:18:44 +01:00
committed by max-kammerer
parent 21d3adf084
commit bda5b0d5a9
17 changed files with 154 additions and 60 deletions
@@ -12,16 +12,11 @@ import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrExpression
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
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.visitors.acceptChildrenVoid
internal open class IrInlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitorVoidWithContext() {
val inlineReferences = mutableSetOf<IrCallableReference>()
// 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<IrFunction, IrDeclaration?>()
override fun visitElement(element: IrElement) = element.acceptChildrenVoid(this)
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
@@ -35,37 +30,30 @@ internal open class IrInlineReferenceLocator(private val context: JvmBackendCont
if (!isInlineIrExpression(valueArgument))
continue
if (valueArgument is IrPropertyReference) {
handleInlineFunctionCallableReferenceParam(valueArgument)
continue
}
val reference = when (valueArgument) {
is IrFunctionReference -> valueArgument
is IrBlock -> valueArgument.statements.filterIsInstance<IrFunctionReference>().singleOrNull()
else -> null
} ?: continue
handleInlineFunctionCallableReferenceParam(reference)
if (valueArgument is IrBlock && valueArgument.origin.isLambda) {
val declaration = if (parameter.isCrossinline) null else currentScope!!.irElement as IrDeclaration
handleInlineFunctionLambdaParam(reference, function, declaration)
val reference = valueArgument.statements.last() as IrFunctionReference
visitInlineLambda(reference, function, parameter, currentScope!!.irElement as IrDeclaration)
} else if (valueArgument is IrCallableReference) {
visitInlineReference(valueArgument)
}
}
}
return super.visitFunctionAccess(expression)
}
open fun handleInlineFunctionCallableReferenceParam(valueArgument: IrCallableReference) {
inlineReferences.add(valueArgument)
}
open fun visitInlineReference(argument: IrCallableReference) {}
open fun handleInlineFunctionLambdaParam(lambdaReference: IrFunctionReference, callee: IrFunction, callSite: IrDeclaration?) {
lambdaToCallSite[lambdaReference.symbol.owner] = callSite
}
open fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) =
visitInlineReference(argument)
companion object {
fun scan(context: JvmBackendContext, element: IrElement) =
IrInlineReferenceLocator(context).apply { element.accept(this, null) }
fun scan(context: JvmBackendContext, element: IrElement): Set<IrCallableReference> =
mutableSetOf<IrCallableReference>().apply {
element.accept(object : IrInlineReferenceLocator(context) {
override fun visitInlineReference(argument: IrCallableReference) {
add(argument)
}
}, null)
}
}
}
@@ -641,17 +641,18 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
private fun markSuspendLambdas(irElement: IrElement): Pair<List<SuspendLambdaInfo>, List<IrFunction>> {
val suspendLambdas = arrayListOf<SuspendLambdaInfo>()
val capturesCrossinline = mutableSetOf<IrCallableReference>()
val visitor = object : IrInlineReferenceLocator(context) {
val inlineReferences = mutableSetOf<IrCallableReference>()
irElement.acceptChildrenVoid(object : IrInlineReferenceLocator(context) {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun handleInlineFunctionCallableReferenceParam(valueArgument: IrCallableReference) {
super.handleInlineFunctionCallableReferenceParam(valueArgument)
val getValue = (valueArgument as? IrGetValue) ?: return
override fun visitInlineReference(argument: IrCallableReference) {
inlineReferences.add(argument)
val getValue = (argument as? IrGetValue) ?: return
val parameter = getValue.symbol.owner as? IrValueParameter ?: return
if (parameter.isCrossinline) {
capturesCrossinline += valueArgument
capturesCrossinline += argument
}
}
@@ -676,9 +677,8 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
)
}
}
}
irElement.acceptChildrenVoid(visitor)
return suspendLambdas to visitor.inlineReferences.map { it.symbol.owner as IrFunction }
})
return suspendLambdas to inlineReferences.map { it.symbol.owner as IrFunction }
}
private class SuspendLambdaInfo(
@@ -56,7 +56,7 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
private fun IrFunctionReference.isSuspendCallableReference(): Boolean = isSuspend && origin == null
override fun lower(irFile: IrFile) {
ignoredFunctionReferences.addAll(IrInlineReferenceLocator.scan(context, irFile).inlineReferences)
ignoredFunctionReferences.addAll(IrInlineReferenceLocator.scan(context, irFile))
irFile.transformChildrenVoid(this)
}
@@ -14,12 +14,8 @@ import org.jetbrains.kotlin.ir.builders.createTmpVariable
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrCallableReference
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
@@ -44,16 +40,11 @@ internal class FakeInliningLocalVariablesLowering(val context: JvmBackendContext
}
}
override fun handleInlineFunctionCallableReferenceParam(valueArgument: IrCallableReference) {
// Do not record inline function callable reference parameters. They will not be used.
}
override fun handleInlineFunctionLambdaParam(lambdaReference: IrFunctionReference, callee: IrFunction, callSite: IrDeclaration?) {
// Do not record lambda parameters. Instead deal with them now.
val lambda = lambdaReference.symbol.owner
override fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) {
val lambda = argument.symbol.owner
if (lambda.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) {
val argumentToFunctionName = context.methodSignatureMapper.mapFunctionName(callee)
val lambdaReferenceName = context.getLocalClassType(lambdaReference)!!.internalName.substringAfterLast("/")
val lambdaReferenceName = context.getLocalClassType(argument)!!.internalName.substringAfterLast("/")
val localName = "${JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT}-$argumentToFunctionName-$lambdaReferenceName"
lambda.addFakeLocalVariable(localName)
}
@@ -50,9 +50,7 @@ internal class InlineCallableReferenceToLambdaPhase(val context: JvmBackendConte
private var inlinableReferences = mutableSetOf<IrCallableReference>()
override fun lower(irFile: IrFile) {
IrInlineReferenceLocator.scan(context, irFile).let {
inlinableReferences.addAll(it.inlineReferences)
}
inlinableReferences.addAll(IrInlineReferenceLocator.scan(context, irFile))
irFile.transformChildrenVoid(this)
}
@@ -41,11 +41,18 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass {
data class LambdaCallSite(val scope: IrDeclaration, val crossinline: Boolean)
private val pendingAccessorsToAdd = mutableListOf<IrFunction>()
private val inlineLambdaToCallSite = mutableMapOf<IrFunction, IrDeclaration?>()
private val inlineLambdaToCallSite = mutableMapOf<IrFunction, LambdaCallSite>()
override fun lower(irFile: IrFile) {
inlineLambdaToCallSite.putAll(IrInlineReferenceLocator.scan(context, irFile).lambdaToCallSite)
irFile.accept(object : IrInlineReferenceLocator(context) {
override fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) {
inlineLambdaToCallSite[argument.symbol.owner] = LambdaCallSite(scope, parameter.isCrossinline)
}
}, null)
irFile.transformChildrenVoid(this)
for (accessor in pendingAccessorsToAdd) {
@@ -509,16 +516,18 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
this == JavaVisibilities.PROTECTED_AND_PACKAGE ||
this == JavaVisibilities.PROTECTED_STATIC_VISIBILITY
private fun IrDeclaration.getAccessContext(withSuper: Boolean): IrDeclarationContainer? = when {
private fun IrDeclaration.getAccessContext(allowSamePackage: Boolean): IrDeclarationContainer? = when {
this is IrDeclarationContainer -> this
// For inline lambdas, we can navigate to the only call site directly.
this in inlineLambdaToCallSite -> inlineLambdaToCallSite[this]?.getAccessContext(withSuper)
// For inline lambdas, we can navigate to the only call site directly. Crossinline lambdas might be inlined
// into other classes in the same package, so private/super require accessors anyway.
this in inlineLambdaToCallSite ->
inlineLambdaToCallSite[this]!!.takeIf { allowSamePackage || !it.crossinline }?.scope?.getAccessContext(allowSamePackage)
// Accesses from inline functions can actually be anywhere; even private inline functions can be
// inlined into a different class, e.g. a callable reference. For protected inline functions
// calling methods on `super` we also need an accessor to satisfy INVOKESPECIAL constraints.
// TODO scan nested classes for calls to private inline functions?
this is IrFunction && isInline -> null
else -> (parent as? IrDeclaration)?.getAccessContext(withSuper)
else -> (parent as? IrDeclaration)?.getAccessContext(allowSamePackage)
}
private fun IrSymbol.isAccessible(withSuper: Boolean, thisObjReference: IrClassSymbol?): Boolean {
@@ -547,7 +556,8 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
// 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
val contextDeclarationContainer =
(currentScope!!.irElement as IrDeclaration).getAccessContext(declaration.visibility.isProtected && !withSuper) ?: return false
val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer.getPackageFragment()?.fqName
val fromSubclassOfReceiversClass = contextDeclarationContainer is IrClass && symbolDeclarationContainer is IrClass &&
@@ -0,0 +1,19 @@
// FILE: protectedPack/J.java
package protectedPack;
public class J {
protected String foo = "OK";
}
// FILE: 1.kt
package protectedPack
inline fun foo(crossinline bar: () -> String) = object {
fun baz() = bar()
}.baz()
fun box(): String {
return foo { J().foo!! }
}
@@ -0,0 +1,31 @@
// !LANGUAGE: -ProhibitProtectedCallFromInline
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND_MULTI_MODULE: JVM
// FILE: 1.kt
package test
inline fun runCrossinline(crossinline f: () -> String) = f()
open class Base {
protected open val FOO = "O"
protected open fun test() = "K"
}
open class P : Base() {
inline fun protectedProp(crossinline f: (String) -> String): String =
runCrossinline { f(FOO) }
inline fun protectedFun(crossinline f: (String) -> String): String =
runCrossinline { f(test()) }
}
// FILE: 2.kt
import test.*
fun box() : String {
val p = P()
return p.protectedProp { it } + p.protectedFun { it }
}
@@ -0,0 +1,17 @@
package a
open class A {
protected fun protectedFun(): String = "OK"
}
inline fun foo(crossinline bar: () -> String) = object {
fun baz() = bar()
}.baz()
class BSamePackage: A() {
fun test(): String = foo {
protectedFun()
}
}
// 0 INVOKESTATIC a/BSamePackage.access
@@ -1147,6 +1147,11 @@ public class BlackBoxAgainstJavaCodegenTestGenerated extends AbstractBlackBoxAga
runTest("compiler/testData/codegen/boxAgainstJava/visibility/protectedAndPackage/protectedPropertyInPackage.kt");
}
@TestMetadata("protectedPropertyInPackageFromCrossinline.kt")
public void testProtectedPropertyInPackageFromCrossinline() throws Exception {
runTest("compiler/testData/codegen/boxAgainstJava/visibility/protectedAndPackage/protectedPropertyInPackageFromCrossinline.kt");
}
@TestMetadata("protectedStaticClass.kt")
public void testProtectedStaticClass() throws Exception {
runTest("compiler/testData/codegen/boxAgainstJava/visibility/protectedAndPackage/protectedStaticClass.kt");
@@ -4320,6 +4320,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/privateInCrossInline.kt");
}
@TestMetadata("protectedMembersFromSuper.kt")
public void testProtectedMembersFromSuper() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/protectedMembersFromSuper.kt");
}
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/superCall.kt");
@@ -254,6 +254,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/maxStackAfterOptimizations.kt");
}
@TestMetadata("noAccessorForProtectedInSamePackageCrossinline.kt")
public void testNoAccessorForProtectedInSamePackageCrossinline() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/noAccessorForProtectedInSamePackageCrossinline.kt");
}
@TestMetadata("noFlagAnnotations.kt")
public void testNoFlagAnnotations() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/noFlagAnnotations.kt");
@@ -4320,6 +4320,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/privateInCrossInline.kt");
}
@TestMetadata("protectedMembersFromSuper.kt")
public void testProtectedMembersFromSuper() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/protectedMembersFromSuper.kt");
}
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/superCall.kt");
@@ -1148,6 +1148,11 @@ public class IrBlackBoxAgainstJavaCodegenTestGenerated extends AbstractIrBlackBo
runTest("compiler/testData/codegen/boxAgainstJava/visibility/protectedAndPackage/protectedPropertyInPackage.kt");
}
@TestMetadata("protectedPropertyInPackageFromCrossinline.kt")
public void testProtectedPropertyInPackageFromCrossinline() throws Exception {
runTest("compiler/testData/codegen/boxAgainstJava/visibility/protectedAndPackage/protectedPropertyInPackageFromCrossinline.kt");
}
@TestMetadata("protectedStaticClass.kt")
public void testProtectedStaticClass() throws Exception {
runTest("compiler/testData/codegen/boxAgainstJava/visibility/protectedAndPackage/protectedStaticClass.kt");
@@ -4070,6 +4070,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/privateInCrossInline.kt");
}
@TestMetadata("protectedMembersFromSuper.kt")
public void testProtectedMembersFromSuper() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/protectedMembersFromSuper.kt");
}
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/superCall.kt");
@@ -254,6 +254,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/maxStackAfterOptimizations.kt");
}
@TestMetadata("noAccessorForProtectedInSamePackageCrossinline.kt")
public void testNoAccessorForProtectedInSamePackageCrossinline() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/noAccessorForProtectedInSamePackageCrossinline.kt");
}
@TestMetadata("noFlagAnnotations.kt")
public void testNoFlagAnnotations() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/noFlagAnnotations.kt");
@@ -4070,6 +4070,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/privateInCrossInline.kt");
}
@TestMetadata("protectedMembersFromSuper.kt")
public void testProtectedMembersFromSuper() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/protectedMembersFromSuper.kt");
}
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
runTest("compiler/testData/codegen/boxInline/syntheticAccessors/withinInlineLambda/superCall.kt");