JVM_IR: optimize property accesses in inline lambdas and private funs

This commit is contained in:
pyos
2021-07-07 14:37:02 +02:00
committed by Alexander Udalov
parent fc013c6b9f
commit e9d5d9da48
4 changed files with 179 additions and 187 deletions
@@ -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
@@ -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()
@@ -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) }
+13
View File
@@ -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