JVM_IR: optimize property accesses in inline lambdas and private funs
This commit is contained in:
+35
-61
@@ -13,8 +13,9 @@ import org.jetbrains.kotlin.backend.common.lower.loops.isInductionVariable
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredStatementOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
@@ -109,42 +110,20 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
||||
AsmUtil.isPrimitive(context.typeMapper.mapType(this))
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.transformChildren(Transformer(), null)
|
||||
irFile.transformChildren(Transformer(irFile.findInlineCallSites(context)), null)
|
||||
}
|
||||
|
||||
inner class Transformer : IrElementTransformer<IrClass?> {
|
||||
|
||||
private inner class Transformer(private val inlineScopeResolver: IrInlineScopeResolver) : IrElementTransformer<IrDeclaration?> {
|
||||
private val dontTouchTemporaryVals = HashSet<IrVariable>()
|
||||
|
||||
// Thread the current class through the transformations in order to replace
|
||||
// final default accessor calls with direct backing field access when
|
||||
// possible.
|
||||
override fun visitClass(declaration: IrClass, data: IrClass?): IrStatement {
|
||||
declaration.transformChildren(this, declaration)
|
||||
return declaration
|
||||
}
|
||||
override fun visitDeclaration(declaration: IrDeclarationBase, data: IrDeclaration?): IrStatement =
|
||||
super.visitDeclaration(declaration, declaration)
|
||||
|
||||
// For some functions, we clear the current class field since the code could end up
|
||||
// in another class then the one it is nested under in the IR.
|
||||
// TODO: replace this with the code from SyntheticAccessorLowering that returns the current class
|
||||
// or package accounting for all inline functions and lambdas.
|
||||
override fun visitFunction(declaration: IrFunction, data: IrClass?): IrStatement {
|
||||
val codeMightBeGeneratedInDifferentClass = declaration.isSuspend ||
|
||||
declaration.isInline ||
|
||||
declaration.origin == JvmLoweredDeclarationOrigin.INLINE_LAMBDA
|
||||
declaration.transformChildren(this, data.takeUnless { codeMightBeGeneratedInDifferentClass })
|
||||
return declaration
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: IrClass?): IrExpression {
|
||||
override fun visitCall(expression: IrCall, data: IrDeclaration?): IrExpression {
|
||||
expression.transformChildren(this, data)
|
||||
|
||||
if (expression.symbol.owner.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) {
|
||||
if (data == null) return expression
|
||||
val simpleFunction = (expression.symbol.owner as? IrSimpleFunction) ?: return expression
|
||||
val property = simpleFunction.correspondingPropertySymbol?.owner ?: return expression
|
||||
if (property.isLateinit) return expression
|
||||
return optimizePropertyAccess(expression, simpleFunction, property, data)
|
||||
return optimizePropertyAccess(expression, data)
|
||||
}
|
||||
|
||||
if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
|
||||
@@ -212,39 +191,33 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizePropertyAccess(
|
||||
expression: IrCall,
|
||||
accessor: IrSimpleFunction,
|
||||
property: IrProperty,
|
||||
currentClass: IrClass
|
||||
): IrExpression {
|
||||
if (accessor.parentAsClass == currentClass &&
|
||||
property.backingField?.parentAsClass == currentClass &&
|
||||
accessor.modality == Modality.FINAL &&
|
||||
!accessor.isExternal
|
||||
) {
|
||||
val backingField = property.backingField!!
|
||||
val receiver = expression.dispatchReceiver
|
||||
return context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset).irBlock(expression) {
|
||||
if (backingField.isStatic && receiver != null && receiver !is IrGetValue) {
|
||||
// If the field is static, evaluate the receiver for potential side effects.
|
||||
+receiver.coerceToUnit(context.irBuiltIns, this@JvmOptimizationLowering.context.typeSystem)
|
||||
}
|
||||
if (accessor.valueParameters.isNotEmpty()) {
|
||||
+irSetField(
|
||||
receiver.takeUnless { backingField.isStatic },
|
||||
backingField,
|
||||
expression.getValueArgument(expression.valueArgumentsCount - 1)!!
|
||||
)
|
||||
} else {
|
||||
+irGetField(receiver.takeUnless { backingField.isStatic }, backingField)
|
||||
}
|
||||
private fun optimizePropertyAccess(expression: IrCall, data: IrDeclaration?): IrExpression {
|
||||
val accessor = expression.symbol.owner as? IrSimpleFunction ?: return expression
|
||||
if (accessor.modality != Modality.FINAL || accessor.isExternal) return expression
|
||||
val property = accessor.correspondingPropertySymbol?.owner ?: return expression
|
||||
if (property.isLateinit) return expression
|
||||
val backingField = property.backingField ?: return expression
|
||||
val scope = data?.let(inlineScopeResolver::findContainer) ?: return expression
|
||||
if (scope != accessor.parent || scope != backingField.parent) return expression
|
||||
val receiver = expression.dispatchReceiver
|
||||
return context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset).irBlock(expression) {
|
||||
if (backingField.isStatic && receiver != null && receiver !is IrGetValue) {
|
||||
// If the field is static, evaluate the receiver for potential side effects.
|
||||
+receiver.coerceToUnit(context.irBuiltIns, this@JvmOptimizationLowering.context.typeSystem)
|
||||
}
|
||||
if (accessor.valueParameters.isNotEmpty()) {
|
||||
+irSetField(
|
||||
receiver.takeUnless { backingField.isStatic },
|
||||
backingField,
|
||||
expression.getValueArgument(expression.valueArgumentsCount - 1)!!
|
||||
)
|
||||
} else {
|
||||
+irGetField(receiver.takeUnless { backingField.isStatic }, backingField)
|
||||
}
|
||||
}
|
||||
return expression
|
||||
}
|
||||
|
||||
override fun visitWhen(expression: IrWhen, data: IrClass?): IrExpression {
|
||||
override fun visitWhen(expression: IrWhen, data: IrDeclaration?): IrExpression {
|
||||
val isCompilerGenerated = expression.origin == null
|
||||
expression.transformChildren(this, data)
|
||||
// Remove all branches with constant false condition.
|
||||
@@ -390,13 +363,13 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBlockBody(body: IrBlockBody, data: IrClass?): IrBody {
|
||||
override fun visitBlockBody(body: IrBlockBody, data: IrDeclaration?): IrBody {
|
||||
body.transformChildren(this, data)
|
||||
removeUnnecessaryTemporaryVariables(body.statements)
|
||||
return body
|
||||
}
|
||||
|
||||
override fun visitContainerExpression(expression: IrContainerExpression, data: IrClass?): IrExpression {
|
||||
override fun visitContainerExpression(expression: IrContainerExpression, data: IrDeclaration?): IrExpression {
|
||||
val safeCall = parseSafeCall(expression)
|
||||
if (safeCall != null) {
|
||||
// Don't optimize out temporary values for safe calls (yet), so that safe call-based equality checks can be optimized.
|
||||
@@ -450,6 +423,7 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
||||
isConst = false, isLateinit = false
|
||||
)
|
||||
newLoopVariable.initializer = inductionVariable.initializer
|
||||
newLoopVariable.parent = inductionVariable.parent
|
||||
|
||||
loopInitialization.statements[inductionVariableIndex] = newLoopVariable
|
||||
loopVariableContainer.statements.removeAt(loopVariableIndex)
|
||||
@@ -519,7 +493,7 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitGetValue(expression: IrGetValue, data: IrClass?): IrExpression {
|
||||
override fun visitGetValue(expression: IrGetValue, data: IrDeclaration?): IrExpression {
|
||||
// Replace IrGetValue of an immutable temporary variable with a constant
|
||||
// initializer with the constant initializer.
|
||||
val variable = expression.symbol.owner
|
||||
|
||||
+13
-126
@@ -11,17 +11,16 @@ import org.jetbrains.kotlin.backend.common.descriptors.synthesizedString
|
||||
import org.jetbrains.kotlin.backend.common.ir.*
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.fileParent
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
|
||||
import org.jetbrains.kotlin.backend.jvm.hasMangledParameters
|
||||
import org.jetbrains.kotlin.backend.jvm.intrinsics.receiverAndArgs
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.isAssertionsDisabledField
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
@@ -36,54 +35,13 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
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, LambdaCallSite>()
|
||||
private val inlineFunctionToCallSites = mutableMapOf<IrFunction, Set<IrElement>>()
|
||||
|
||||
internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileLoweringPass {
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.accept(object : IrInlineReferenceLocator(context) {
|
||||
override fun visitInlineLambda(
|
||||
argument: IrFunctionReference,
|
||||
callee: IrFunction,
|
||||
parameter: IrValueParameter,
|
||||
scope: IrDeclaration
|
||||
) {
|
||||
// suspendCoroutine and suspendCoroutineUninterceptedOrReturn accept crossinline lambdas to disallow non-local returns,
|
||||
// but these lambdas are effectively inline
|
||||
inlineLambdaToCallSite[argument.symbol.owner] =
|
||||
LambdaCallSite(scope, parameter.isCrossinline && !callee.isCoroutineIntrinsic())
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: IrDeclaration?) {
|
||||
if (declaration.isPrivateInline) {
|
||||
inlineFunctionToCallSites.putIfAbsent(declaration, mutableSetOf())
|
||||
}
|
||||
super.visitSimpleFunction(declaration, data)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: IrDeclaration?) {
|
||||
val callee = expression.symbol.owner
|
||||
if (callee.isPrivateInline && callee.fileParent == irFile && data != null) {
|
||||
(inlineFunctionToCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data)
|
||||
}
|
||||
super.visitCall(expression, data)
|
||||
}
|
||||
|
||||
private inline val IrSimpleFunction.isPrivateInline
|
||||
get() = isInline && DescriptorVisibilities.isPrivate(visibility)
|
||||
}, null)
|
||||
|
||||
irFile.transformChildrenVoid(this)
|
||||
inlineLambdaToCallSite.clear()
|
||||
inlineFunctionToCallSites.clear()
|
||||
|
||||
val pendingAccessorsToAdd = mutableListOf<IrFunction>()
|
||||
irFile.transformChildrenVoid(SyntheticAccessorTransformer(context, irFile.findInlineCallSites(context), pendingAccessorsToAdd))
|
||||
for (accessor in pendingAccessorsToAdd) {
|
||||
assert(accessor.fileOrNull == irFile) {
|
||||
"SyntheticAccessorLowering should not attempt to modify other files!\n" +
|
||||
@@ -92,9 +50,14 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
}
|
||||
(accessor.parent as IrDeclarationContainer).declarations.add(accessor)
|
||||
}
|
||||
pendingAccessorsToAdd.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private class SyntheticAccessorTransformer(
|
||||
val context: JvmBackendContext,
|
||||
val inlineScopeResolver: IrInlineScopeResolver,
|
||||
val pendingAccessorsToAdd: MutableList<IrFunction>
|
||||
) : IrElementTransformerVoidWithContext() {
|
||||
private data class FieldKey(val fieldSymbol: IrFieldSymbol, val parent: IrDeclarationParent, val superQualifierSymbol: IrClassSymbol?)
|
||||
|
||||
private data class FunctionKey(
|
||||
@@ -259,7 +222,7 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
// We have a protected member.
|
||||
// It is accessible from a synthetic proxy class (created by LambdaMetafactory)
|
||||
// if it belongs to the current class.
|
||||
return getScopeClassOrPackage() == owner.parentAsClass
|
||||
return inlineScopeResolver.findContainer(currentScope!!.irElement) == owner.parentAsClass
|
||||
}
|
||||
|
||||
override fun visitGetField(expression: IrGetField): IrExpression {
|
||||
@@ -762,7 +725,7 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
}
|
||||
|
||||
val ownerClass = declaration.parent as? IrClass ?: return true // locals are always accessible
|
||||
val scopeClassOrPackage = getScopeClassOrPackage() ?: return false
|
||||
val scopeClassOrPackage = inlineScopeResolver.findContainer(currentScope!!.irElement) ?: return false
|
||||
val samePackage = ownerClass.getPackageFragment()?.fqName == scopeClassOrPackage.getPackageFragment()?.fqName
|
||||
return when {
|
||||
jvmVisibility == 0 /* package only */ -> samePackage
|
||||
@@ -776,84 +739,8 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
(thisObjReference == null || thisObjReference.owner.isSubclassOf(scopeClassOrPackage))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScopeClassOrPackage(): IrDeclarationContainer? =
|
||||
getScopeClassOrPackage(currentScope?.irElement, approximateToPackage = false)
|
||||
|
||||
// Get the class from which all accesses in the current scope will be done after bytecode generation.
|
||||
// If the current scope is a crossinline lambda, this is not possible, as the lambda maybe inlined
|
||||
// into some other class; in that case, get at least the package.
|
||||
private tailrec fun getScopeClassOrPackage(context: IrElement?, approximateToPackage: Boolean): IrDeclarationContainer? {
|
||||
val callSite = inlineLambdaToCallSite[context]
|
||||
return when {
|
||||
// Crossinline lambdas can be inlined into some other class in the same package. However,
|
||||
// classes within crossinline lambdas should not be regenerated, so if we've already found
|
||||
// a class *before* reaching this lambda, it's valid:
|
||||
// class C {
|
||||
// fun f() {}
|
||||
// fun g() = inlineFunctionWithCrossinlineArgument {
|
||||
// f() // this call is done in some unknown class within C's package
|
||||
// object { val x = f() } // this call is done in C$g$1$1
|
||||
// }
|
||||
// }
|
||||
callSite != null -> getScopeClassOrPackage(callSite.scope, approximateToPackage || callSite.crossinline)
|
||||
// Inline functions can be inlined into anywhere. Not even private inline functions are safe:
|
||||
// class C {
|
||||
// fun f() {}
|
||||
// private inline fun g1() = f() // `f` is called from C?
|
||||
// fun g2() = { g1() } // ...or from C$g2$1 in the same package?
|
||||
// inline fun g3() = g1() // ...or from some other package that calls g3?
|
||||
// }
|
||||
// However, for private ones we at least know where they're called, so just like inline lambdas,
|
||||
// we can navigate there.
|
||||
//
|
||||
// TODO: this has some weird effects for inline functions in local classes, e.g. they
|
||||
// access the capture fields (package-private) through accessors; this may or may not
|
||||
// be necessary - local types should in theory not be usable outside the current file.
|
||||
context is IrFunction && context.isInline -> {
|
||||
val callSites = inlineFunctionToCallSites[context] ?: return null
|
||||
when {
|
||||
callSites.isEmpty() -> getScopeClassOrPackage(context.parent, approximateToPackage)
|
||||
callSites.size == 1 -> getScopeClassOrPackage(callSites.single(), approximateToPackage)
|
||||
else -> {
|
||||
// TODO: cache the results
|
||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||
val results = callSites.map { getScopeClassOrPackage(it, approximateToPackage = false) ?: return null }
|
||||
// If all call sites are within a single class, use it. Otherwise, all scopes must be within
|
||||
// the current file's package.
|
||||
val single = results.first().takeIf { results.all { other -> it === other } }
|
||||
getScopeClassOrPackage(single ?: context.parent, approximateToPackage || single == null)
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: if this class is an object local to an inline function, it could be regenerated,
|
||||
// so the scope depends on the declaration accessed (see KT-48508):
|
||||
// class C {
|
||||
// fun f1()
|
||||
// inline fun inlineFun() = object {
|
||||
// fun f2() {}
|
||||
// fun g1() {
|
||||
// f1() // this access can be anywhere
|
||||
// f2() // can pretend this access is from C$foo$1
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Further complicating things, the accessor for `f1` cannot be in `C$inlineFun$1`, as otherwise
|
||||
// the accessor itself will be regenerated (and thus not work) at `inlineFun` call sites.
|
||||
context is IrClass && !approximateToPackage -> context
|
||||
// Inline lambdas have already been moved out to the containing class, but we still need to check
|
||||
// the containing function (again, see above), so navigate there instead.
|
||||
context is IrDeclaration -> getScopeClassOrPackage(context.parent, approximateToPackage)
|
||||
// The only non-declaration parent should be the package.
|
||||
else -> context as? IrPackageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrFunction.isCoroutineIntrinsic(): Boolean =
|
||||
(name.asString() == "suspendCoroutine" && getPackageFragment()?.fqName == FqName("kotlin.coroutines")) ||
|
||||
(name.asString() == "suspendCoroutineUninterceptedOrReturn" && getPackageFragment()?.fqName == FqName("kotlin.coroutines.intrinsics"))
|
||||
|
||||
private fun IrClass.syntheticAccessorToSuperSuffix(): String =
|
||||
// TODO: change this to `fqNameUnsafe.asString().replace(".", "_")` as soon as we're ready to break compatibility with pre-KT-21178 code
|
||||
name.asString().hashCode().toString()
|
||||
|
||||
+118
@@ -8,11 +8,15 @@ package org.jetbrains.kotlin.backend.jvm.ir
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineFunctionCall
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.unwrapInlineLambda
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
||||
import org.jetbrains.kotlin.ir.util.getPackageFragment
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
abstract class IrInlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitor<Unit, IrDeclaration?> {
|
||||
override fun visitElement(element: IrElement, data: IrDeclaration?) =
|
||||
@@ -32,9 +36,123 @@ abstract class IrInlineReferenceLocator(private val context: JvmBackendContext)
|
||||
abstract fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration)
|
||||
}
|
||||
|
||||
class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocator(context) {
|
||||
private class CallSite(val parent: IrElement?, val approximateToPackage: Boolean)
|
||||
|
||||
private val inlineCallSites = mutableMapOf<IrFunction, CallSite>()
|
||||
private val inlineFunctionCallSites = mutableMapOf<IrFunction, Set<IrElement>>()
|
||||
|
||||
override fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) {
|
||||
// suspendCoroutine and suspendCoroutineUninterceptedOrReturn accept crossinline lambdas to disallow non-local returns,
|
||||
// but these lambdas are effectively inline
|
||||
inlineCallSites[argument.symbol.owner] = CallSite(scope, parameter.isCrossinline && !callee.isCoroutineIntrinsic())
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: IrDeclaration?) {
|
||||
if (declaration.isPrivateInline) {
|
||||
inlineFunctionCallSites.putIfAbsent(declaration, mutableSetOf())
|
||||
}
|
||||
super.visitSimpleFunction(declaration, data)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: IrDeclaration?) {
|
||||
val callee = expression.symbol.owner
|
||||
if (callee.isPrivateInline && data != null) {
|
||||
(inlineFunctionCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data)
|
||||
}
|
||||
super.visitCall(expression, data)
|
||||
}
|
||||
|
||||
private inline val IrSimpleFunction.isPrivateInline
|
||||
get() = isInline && DescriptorVisibilities.isPrivate(visibility)
|
||||
|
||||
private fun IrFunction.isCoroutineIntrinsic(): Boolean =
|
||||
(name.asString() == "suspendCoroutine" && getPackageFragment()?.fqName == FqName("kotlin.coroutines")) ||
|
||||
(name.asString() == "suspendCoroutineUninterceptedOrReturn" && getPackageFragment()?.fqName == FqName("kotlin.coroutines.intrinsics"))
|
||||
|
||||
fun findContainer(scope: IrElement): IrDeclarationContainer? =
|
||||
findContainer(scope, approximateToPackage = false)
|
||||
|
||||
// Get the class from which all accesses in the current scope will be done after bytecode generation.
|
||||
// If the current scope is a crossinline lambda, this is not possible, as the lambda maybe inlined
|
||||
// into some other class; in that case, get at least the package.
|
||||
private tailrec fun findContainer(context: IrElement?, approximateToPackage: Boolean): IrDeclarationContainer? {
|
||||
val callSite = inlineCallSites[context]
|
||||
return when {
|
||||
// Crossinline lambdas can be inlined into some other class in the same package. However,
|
||||
// classes within crossinline lambdas should not be regenerated, so if we've already found
|
||||
// a class *before* reaching this lambda, it's valid:
|
||||
// class C {
|
||||
// fun f() {}
|
||||
// fun g() = inlineFunctionWithCrossinlineArgument {
|
||||
// f() // this call is done in some unknown class within C's package
|
||||
// object { val x = f() } // this call is done in C$g$1$1
|
||||
// }
|
||||
// }
|
||||
callSite != null -> findContainer(callSite.parent, approximateToPackage || callSite.approximateToPackage)
|
||||
// Inline functions can be inlined into anywhere. Not even private inline functions are safe:
|
||||
// class C {
|
||||
// fun f() {}
|
||||
// private inline fun g1() = f() // `f` is called from C?
|
||||
// fun g2() = { g1() } // ...or from C$g2$1 in the same package?
|
||||
// inline fun g3() = g1() // ...or from some other package that calls g3?
|
||||
// }
|
||||
// However, for private ones we at least know where they're called, so just like inline lambdas,
|
||||
// we can navigate there.
|
||||
//
|
||||
// TODO: this has some weird effects for inline functions in local classes, e.g. they
|
||||
// access the capture fields (package-private) through accessors; this may or may not
|
||||
// be necessary - local types should in theory not be usable outside the current file.
|
||||
context is IrFunction && context.isInline -> {
|
||||
val callSites = inlineFunctionCallSites[context] ?: return null
|
||||
// Mark to avoid infinite recursion on self-recursive inline functions (those are only
|
||||
// detected reliably by codegen; frontend only filters out simple cases).
|
||||
inlineCallSites[context] = CallSite(null, approximateToPackage = false)
|
||||
val commonCallSite = when {
|
||||
callSites.isEmpty() -> CallSite(context.parent, false)
|
||||
callSites.size == 1 -> CallSite(callSites.single(), false)
|
||||
else -> {
|
||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||
val results = callSites.map { findContainer(it, approximateToPackage = false) ?: return null }
|
||||
// If all call sites are within a single class, use it. Otherwise, all scopes must be within
|
||||
// the current file's package.
|
||||
val single = results.first().takeIf { results.all { other -> it === other } }
|
||||
CallSite(single ?: context.parent, single == null)
|
||||
}
|
||||
}
|
||||
inlineCallSites[context] = commonCallSite
|
||||
findContainer(commonCallSite.parent, approximateToPackage || commonCallSite.approximateToPackage)
|
||||
}
|
||||
// TODO: if this class is an object local to an inline function, it could be regenerated,
|
||||
// so the scope depends on the declaration accessed (see KT-48508):
|
||||
// class C {
|
||||
// fun f1()
|
||||
// inline fun inlineFun() = object {
|
||||
// fun f2() {}
|
||||
// fun g1() {
|
||||
// f1() // this access can be anywhere
|
||||
// f2() // can pretend this access is from C$foo$1
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Further complicating things, the accessor for `f1` cannot be in `C$inlineFun$1`, as otherwise
|
||||
// the accessor itself will be regenerated (and thus not work) at `inlineFun` call sites.
|
||||
context is IrClass && !approximateToPackage -> context
|
||||
// Inline lambdas have already been moved out to the containing class, but we still need to check
|
||||
// the containing function (again, see above), so navigate there instead.
|
||||
context is IrDeclaration -> findContainer(context.parent, approximateToPackage)
|
||||
// The only non-declaration parent should be the package.
|
||||
else -> context as? IrPackageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun IrFile.findInlineLambdas(
|
||||
context: JvmBackendContext, crossinline onLambda: (IrFunctionReference, IrFunction, IrValueParameter, IrDeclaration) -> Unit
|
||||
) = accept(object : IrInlineReferenceLocator(context) {
|
||||
override fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) =
|
||||
onLambda(argument, callee, parameter, scope)
|
||||
}, null)
|
||||
|
||||
fun IrFile.findInlineCallSites(context: JvmBackendContext) =
|
||||
IrInlineScopeResolver(context).apply { accept(this, null) }
|
||||
|
||||
@@ -33,9 +33,22 @@ class Example
|
||||
b3 = 1
|
||||
b4 = 1
|
||||
b5 = 1
|
||||
foo { a1 = 1 }
|
||||
bar()
|
||||
}
|
||||
|
||||
private inline fun bar() { a1 = 1 }
|
||||
}
|
||||
|
||||
inline fun foo(x: () -> Unit) = x()
|
||||
|
||||
// Every property should be accessed directly in this example because they all are final class properties with default accessors
|
||||
// 0 INVOKESPECIAL Example\.set
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// ...except the access in `private inline fun`.
|
||||
// 2 INVOKEVIRTUAL Example\.set
|
||||
|
||||
// JVM_IR_TEMPLATES
|
||||
// ...including the access in `private inline fun` which we know is only called from `init`.
|
||||
// 0 INVOKEVIRTUAL Example\.set
|
||||
|
||||
Reference in New Issue
Block a user