[JVM IR] Add KDocs for methods related to synthetic accessor generation
This commit is contained in:
committed by
Space Team
parent
524dd6794a
commit
86587a3bf2
+107
-3
@@ -41,6 +41,19 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileL
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Whether `this` is accessible in [currentScope], according to the platform rules, and with respect to function inlining.
|
||||
*
|
||||
* @param context The backend context.
|
||||
* @param currentScope The scope in which `this` is to be accessed.
|
||||
* @param inlineScopeResolver The helper that allows to find the places from which private inline functions are called (useful if
|
||||
* `this` is accessed from a private inline function).
|
||||
* @param withSuper If an access to this symbol (like [IrCall]) has a `super` qualifier, the access rules will be stricter.
|
||||
* @param thisObjReference If this is a member access, the class symbol of the receiver.
|
||||
* @param fromOtherClassLoader If `this` is a protected declaration being accessed from the same package but not from a subclass,
|
||||
* setting this parameter to `true` marks this declaration as inaccessible, since JVM `protected`, unlike Kotlin `protected`,
|
||||
* permits accesses from the same package, _provided the call is not across class loader boundaries_.
|
||||
*/
|
||||
fun IrSymbol.isAccessible(
|
||||
context: JvmBackendContext,
|
||||
currentScope: ScopeWithIr?,
|
||||
@@ -84,8 +97,6 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileL
|
||||
return when {
|
||||
jvmVisibility == Opcodes.ACC_PRIVATE -> ownerClass == scopeClassOrPackage
|
||||
!withSuper && samePackage && jvmVisibility == 0 /* package only */ -> true
|
||||
// JVM `protected`, unlike Kotlin `protected`, permits accesses from the same package,
|
||||
// provided the call is not across class loader boundaries.
|
||||
!withSuper && samePackage && !fromOtherClassLoader -> true
|
||||
// Super calls and cross-package protected accesses are both only possible from a subclass of the declaration
|
||||
// owner. Also, the target of a non-static call must be assignable to the current class. This is a verification
|
||||
@@ -220,7 +231,7 @@ private class SyntheticAccessorTransformer(
|
||||
override fun visitGetField(expression: IrGetField): IrExpression {
|
||||
val dispatchReceiverType = expression.receiver?.type
|
||||
val dispatchReceiverClassSymbol = dispatchReceiverType?.classifierOrNull as? IrClassSymbol
|
||||
if (expression.symbol.isAccessible(false, dispatchReceiverClassSymbol)) {
|
||||
if (expression.symbol.isAccessible(withSuper = false, dispatchReceiverClassSymbol)) {
|
||||
return super.visitExpression(expression)
|
||||
}
|
||||
|
||||
@@ -303,6 +314,45 @@ private class SyntheticAccessorTransformer(
|
||||
return super.visitBlock(expression)
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a call to the synthetic accessor [accessorSymbol] to replace the call expression [oldExpression].
|
||||
*
|
||||
* Before:
|
||||
* ```kotlin
|
||||
* class C protected constructor(val value: Int) {
|
||||
*
|
||||
* protected fun protectedFun(a: Int): String = a.toString()
|
||||
*
|
||||
* internal inline fun foo(x: Int) {
|
||||
* println(protectedFun(x))
|
||||
* }
|
||||
*
|
||||
* internal inline fun copy(): C = C(value)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```kotlin
|
||||
* class C protected constructor(val value: Int) {
|
||||
*
|
||||
* public constructor(
|
||||
* value: Int,
|
||||
* constructor_marker: kotlin.jvm.internal.DefaultConstructorMarker?
|
||||
* ) : this(value)
|
||||
*
|
||||
* protected fun protectedFun(a: Int): String = a.toString()
|
||||
*
|
||||
* public static fun access$protectedFun($this: C, a: Int): String =
|
||||
* $this.protectedFun(a)
|
||||
*
|
||||
* internal inline fun foo(x: Int) {
|
||||
* println(C.access$protectedFun(this, x))
|
||||
* }
|
||||
*
|
||||
* internal inline fun copy(): C = C(value, null)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun modifyFunctionAccessExpression(
|
||||
oldExpression: IrFunctionAccessExpression,
|
||||
accessorSymbol: IrFunctionSymbol
|
||||
@@ -342,6 +392,31 @@ private class SyntheticAccessorTransformer(
|
||||
private fun createAccessorMarkerArgument() =
|
||||
IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.ir.symbols.defaultConstructorMarker.defaultType.makeNullable())
|
||||
|
||||
/**
|
||||
* Produces a call to the synthetic accessor [accessorSymbol] to replace the field _read_ expression [oldExpression].
|
||||
*
|
||||
* Before:
|
||||
* ```kotlin
|
||||
* class C {
|
||||
* protected /*field*/ val myField: Int
|
||||
*
|
||||
* internal inline fun foo(): Int = myField + 1
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```kotlin
|
||||
* class C {
|
||||
* protected /*field*/ val myField: Int
|
||||
*
|
||||
* public static fun access$getMyField$p($this: C): Int =
|
||||
* $this.myField
|
||||
*
|
||||
* internal inline fun foo(): Int =
|
||||
* C.access$getMyField$p(this) + 1
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun modifyGetterExpression(
|
||||
oldExpression: IrGetField,
|
||||
accessorSymbol: IrSimpleFunctionSymbol
|
||||
@@ -358,6 +433,35 @@ private class SyntheticAccessorTransformer(
|
||||
return call
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a call to the synthetic accessor [accessorSymbol] to replace the field _write_ expression [oldExpression].
|
||||
*
|
||||
* Before:
|
||||
* ```kotlin
|
||||
* class C {
|
||||
* protected var myField: Int = 0
|
||||
*
|
||||
* internal inline fun foo(x: Int) {
|
||||
* myField = x
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```kotlin
|
||||
* class C {
|
||||
* protected var myField: Int = 0
|
||||
*
|
||||
* public static fun access$setMyField$p($this: C, <set-?>: Int) {
|
||||
* $this.myField = <set-?>
|
||||
* }
|
||||
*
|
||||
* internal inline fun foo(x: Int) {
|
||||
* access$setMyField$p(this, x)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun modifySetterExpression(
|
||||
oldExpression: IrSetField,
|
||||
accessorSymbol: IrSimpleFunctionSymbol
|
||||
|
||||
+9
-7
@@ -28,6 +28,11 @@ import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Generates visible synthetic accessor functions for symbols that are otherwise inaccessible, for example,
|
||||
* when inlining a function that references a private method of a class outside of that class, or generating a class for a lambda
|
||||
* expression that uses a `super` qualifier in its body.
|
||||
*/
|
||||
class CachedSyntheticDeclarations(private val context: JvmBackendContext) {
|
||||
private data class FieldKey(val fieldSymbol: IrFieldSymbol, val parent: IrDeclarationParent, val superQualifierSymbol: IrClassSymbol?)
|
||||
|
||||
@@ -42,19 +47,16 @@ class CachedSyntheticDeclarations(private val context: JvmBackendContext) {
|
||||
private val setterMap = ConcurrentHashMap<FieldKey, IrSimpleFunctionSymbol>()
|
||||
|
||||
fun getSyntheticFunctionAccessor(expression: IrFunctionAccessExpression, scopes: List<ScopeWithIr>): IrFunctionSymbol {
|
||||
return createAccessor(expression, scopes)
|
||||
return if (expression is IrCall)
|
||||
createAccessor(expression.symbol, scopes, expression.dispatchReceiver?.type, expression.superQualifierSymbol)
|
||||
else
|
||||
createAccessor(expression.symbol, scopes, null, null)
|
||||
}
|
||||
|
||||
fun getSyntheticFunctionAccessor(reference: IrFunctionReference, scopes: List<ScopeWithIr>): IrFunctionSymbol {
|
||||
return createAccessor(reference.symbol, scopes, reference.dispatchReceiver?.type, null)
|
||||
}
|
||||
|
||||
private fun createAccessor(expression: IrFunctionAccessExpression, scopes: List<ScopeWithIr>): IrFunctionSymbol =
|
||||
if (expression is IrCall)
|
||||
createAccessor(expression.symbol, scopes, expression.dispatchReceiver?.type, expression.superQualifierSymbol)
|
||||
else
|
||||
createAccessor(expression.symbol, scopes, null, null)
|
||||
|
||||
private fun createAccessor(
|
||||
symbol: IrFunctionSymbol,
|
||||
scopes: List<ScopeWithIr>,
|
||||
|
||||
+112
-30
@@ -14,10 +14,11 @@ import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.util.getPackageFragment
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
/**
|
||||
* An IR visitor that also has a customization point for handling lambdas passed to inline functions (see [visitInlineLambda]).
|
||||
*/
|
||||
abstract class IrInlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitor<Unit, IrDeclaration?> {
|
||||
override fun visitElement(element: IrElement, data: IrDeclaration?) =
|
||||
element.acceptChildren(this, if (element is IrDeclaration && element !is IrVariable) element else data)
|
||||
@@ -33,24 +34,69 @@ abstract class IrInlineReferenceLocator(private val context: JvmBackendContext)
|
||||
return super.visitFunctionAccess(expression, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by this visitor whenever a lambda is passed to an inline function.
|
||||
*
|
||||
* @param argument The lambda expression passed as an argument to [callee].
|
||||
* @param callee The inline function.
|
||||
* @param parameter The parameter of [callee] to which the lambda is passed.
|
||||
* @param scope The declaration in scope of which [callee] is being called.
|
||||
*/
|
||||
abstract fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration)
|
||||
}
|
||||
|
||||
/**
|
||||
* An IR visitor that collects scopes in which inline functions/lambdas are called.
|
||||
*
|
||||
* Usage: call [findInlineCallSites] on an [IrFile], and then use [findContainer] to determine the class or package from which an inline
|
||||
* function is accessible.
|
||||
*/
|
||||
class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocator(context) {
|
||||
private class CallSite(val parent: IrElement?, val approximateToPackage: Boolean)
|
||||
|
||||
/**
|
||||
* Describes a scope in which an inline function is called.
|
||||
*
|
||||
* @property scope [IrDeclaration] or [IrDeclarationParent] in which a function is called
|
||||
* @property approximateToPackage Whether the inline function being called can be inlined into some other class in the same package
|
||||
* (for exmaple, if it's a crossinline lambda), thus forcing the [findContainer] to return that package instead of a class.
|
||||
*/
|
||||
private class CallSite(val scope: IrElement?, val approximateToPackage: Boolean)
|
||||
|
||||
/**
|
||||
* For every private inline function/lambda stores the innermost _common_ scope in which it is called.
|
||||
* For example, for function `foo` in the following code, the innermost common scope would be the class `Nested`.
|
||||
* ```kotlin
|
||||
* package com.example
|
||||
*
|
||||
* class C {
|
||||
* private inline fun foo() = 42
|
||||
*
|
||||
* class Nested {
|
||||
* private inline fun bar() = C().foo()
|
||||
* private inline fun baz() = C().foo()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Populated by [findContainer].
|
||||
*/
|
||||
private val inlineCallSites = mutableMapOf<IrFunction, CallSite>()
|
||||
private val inlineFunctionCallSites = mutableMapOf<IrFunction, Set<IrElement>>()
|
||||
|
||||
/**
|
||||
* For each private inline function stores the set of innermost scopes in which that private inline function is called.
|
||||
*/
|
||||
private val privateInlineFunctionCallSites = mutableMapOf<IrFunction, Set<IrDeclaration>>()
|
||||
|
||||
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())
|
||||
inlineCallSites[argument.symbol.owner] =
|
||||
CallSite(scope, approximateToPackage = parameter.isCrossinline && !callee.isCoroutineIntrinsic())
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: IrDeclaration?) {
|
||||
if (declaration.isPrivateInline) {
|
||||
inlineFunctionCallSites.putIfAbsent(declaration, mutableSetOf())
|
||||
privateInlineFunctionCallSites.putIfAbsent(declaration, mutableSetOf())
|
||||
}
|
||||
super.visitSimpleFunction(declaration, data)
|
||||
}
|
||||
@@ -58,7 +104,7 @@ class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocat
|
||||
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)
|
||||
(privateInlineFunctionCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data)
|
||||
}
|
||||
super.visitCall(expression, data)
|
||||
}
|
||||
@@ -67,7 +113,7 @@ class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocat
|
||||
if (expression is IrInlinedFunctionBlock && expression.isFunctionInlining()) {
|
||||
val callee = expression.inlineDeclaration
|
||||
if (callee is IrSimpleFunction && callee.isPrivateInline && data != null) {
|
||||
(inlineFunctionCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data)
|
||||
(privateInlineFunctionCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,14 +126,33 @@ class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocat
|
||||
private fun IrFunction.isCoroutineIntrinsic(): Boolean =
|
||||
isBuiltInSuspendCoroutine() || isBuiltInSuspendCoroutineUninterceptedOrReturn()
|
||||
|
||||
/**
|
||||
* The class from which all accesses in [scope] will be done after bytecode generation.
|
||||
* If [scope] is a crossinline lambda, this is not possible, as the lambda may be inlined
|
||||
* into some other class; in that case, return at least the package fragment.
|
||||
*
|
||||
* @param scope [IrDeclaration] or [IrDeclarationParent].
|
||||
* @return [IrClass] or at least [IrPackageFragment] from which all accesses in the current scope will be done after bytecode
|
||||
* generation, or `null` if the space of potential call sites is unconstrained, e.g. if [scope] is referenced from an internal inline
|
||||
* function.
|
||||
*/
|
||||
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]
|
||||
/**
|
||||
* The class from which all accesses in [scope] will be done after bytecode generation.
|
||||
* If [scope] is a crossinline lambda, this is not possible, as the lambda may be inlined
|
||||
* into some other class; in that case, return at least the package fragment.
|
||||
*
|
||||
* @param scope [IrDeclaration] or [IrDeclarationParent]
|
||||
* @param approximateToPackage Whether the inline function being called can be inlined into some other class in the same package,
|
||||
* for example, if it's a crossinline lambda.
|
||||
* @return [IrClass] or at least [IrPackageFragment] from which all accesses in the current scope will be done after bytecode
|
||||
* generation, or `null` if the space of potential call sites is unconstrained, e.g. if [scope] is referenced from an internal inline
|
||||
* function.
|
||||
*/
|
||||
private tailrec fun findContainer(scope: IrElement?, approximateToPackage: Boolean): IrDeclarationContainer? {
|
||||
val callSite = inlineCallSites[scope]
|
||||
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
|
||||
@@ -99,7 +164,7 @@ class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocat
|
||||
// object { val x = f() } // this call is done in C$g$1$1
|
||||
// }
|
||||
// }
|
||||
callSite != null -> findContainer(callSite.parent, approximateToPackage || callSite.approximateToPackage)
|
||||
callSite != null -> findContainer(callSite.scope, approximateToPackage || callSite.approximateToPackage)
|
||||
// Inline functions can be inlined into anywhere. Not even private inline functions are safe:
|
||||
// class C {
|
||||
// fun f() {}
|
||||
@@ -113,25 +178,25 @@ class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocat
|
||||
// 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
|
||||
scope is IrFunction && scope.isInline -> {
|
||||
val callSites = privateInlineFunctionCallSites[scope] ?: 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)
|
||||
inlineCallSites[scope] = CallSite(scope = null, approximateToPackage = false)
|
||||
val commonCallSite = when {
|
||||
callSites.isEmpty() -> CallSite(context.parent, false)
|
||||
callSites.size == 1 -> CallSite(callSites.single(), false)
|
||||
callSites.isEmpty() -> CallSite(scope.parent, approximateToPackage = false)
|
||||
callSites.size == 1 -> CallSite(callSites.single(), approximateToPackage = 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)
|
||||
CallSite(single ?: scope.parent, approximateToPackage = single == null)
|
||||
}
|
||||
}
|
||||
inlineCallSites[context] = commonCallSite
|
||||
findContainer(commonCallSite.parent, approximateToPackage || commonCallSite.approximateToPackage)
|
||||
inlineCallSites[scope] = commonCallSite
|
||||
findContainer(commonCallSite.scope, 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):
|
||||
@@ -147,22 +212,39 @@ class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocat
|
||||
// }
|
||||
// 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
|
||||
scope is IrClass && !approximateToPackage -> scope
|
||||
// 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)
|
||||
scope is IrDeclaration -> findContainer(scope.parent, approximateToPackage)
|
||||
// The only non-declaration parent should be the package.
|
||||
else -> context as? IrPackageFragment
|
||||
else -> scope as? IrPackageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls [onLambda] for each place in the IR subtree where a lambda is passed to an inline function.
|
||||
*
|
||||
* @param context The backend context
|
||||
* @param onLambda The closure to execute for each such lambda. Accepts the lambda expression, the inline function being called,
|
||||
* the parameter of that inline function to which the lambda is passed, and the scope in which the inline function is called.
|
||||
*/
|
||||
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)
|
||||
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,
|
||||
)
|
||||
|
||||
/**
|
||||
* Runs [IrInlineScopeResolver] on this [IrFile] and returns the scope resolver instance.
|
||||
*/
|
||||
fun IrFile.findInlineCallSites(context: JvmBackendContext) =
|
||||
IrInlineScopeResolver(context).apply { accept(this, null) }
|
||||
|
||||
Reference in New Issue
Block a user