[JVM IR] Move CachedSyntheticDeclarations to backend.common

This will allow us to use this class in KLIB-based backends (KT-64865)
This commit is contained in:
Sergej Jaskiewicz
2024-02-02 21:47:54 +01:00
committed by Space Team
parent 25be39dcc6
commit 919abe483a
2 changed files with 5 additions and 4 deletions
@@ -0,0 +1,429 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.inline
import org.jetbrains.kotlin.backend.common.BackendContext
import org.jetbrains.kotlin.backend.common.ScopeWithIr
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedString
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
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.
*
* TODO: Right now this class is only used in the JVM backend. There is an ongoing effort to also generate synthetic
* accessors on KLIB-based backends ([KT-64865](https://youtrack.jetbrains.com/issue/KT-64865)).
* Please consider this fact when you want to change its implementation.
*/
open class SyntheticAccessorGenerator<Context : BackendContext>(protected val context: Context) {
private data class FieldKey(val fieldSymbol: IrFieldSymbol, val parent: IrDeclarationParent, val superQualifierSymbol: IrClassSymbol?)
private data class FunctionKey(
val functionSymbol: IrFunctionSymbol,
val parent: IrDeclarationParent,
val superQualifierSymbol: IrClassSymbol?
)
private val functionMap = ConcurrentHashMap<FunctionKey, IrFunctionSymbol>()
private val getterMap = ConcurrentHashMap<FieldKey, IrSimpleFunctionSymbol>()
private val setterMap = ConcurrentHashMap<FieldKey, IrSimpleFunctionSymbol>()
fun getSyntheticFunctionAccessor(expression: IrFunctionAccessExpression, scopes: List<ScopeWithIr>): IrFunctionSymbol {
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(
symbol: IrFunctionSymbol,
scopes: List<ScopeWithIr>,
dispatchReceiverType: IrType?,
superQualifierSymbol: IrClassSymbol?
): IrFunctionSymbol {
// Find the right container to insert the accessor. Simply put, when we call a function on a class A,
// we also need to put its accessor into A. However, due to the way that calls are implemented in the
// IR we generally need to look at the type of the dispatchReceiver *argument* in order to find the
// correct class. Consider the following code:
//
// fun run(f : () -> Int): Int = f()
//
// open class A {
// private fun f() = 0
// fun g() = run { this.f() }
// }
//
// class B : A {
// override fun g() = 1
// fun h() = run { super.g() }
// }
//
// We have calls to the private methods A.f from a generated Lambda subclass for the argument to `run`
// in class A and a super call to A.g from a generated Lambda subclass in class B.
//
// In the first case, we need to produce an accessor in class A to access the private member of A.
// Both the parent of the function f and the type of the dispatch receiver point to the correct class.
// In the second case we need to call A.g from within class B, since this is the only way to invoke
// a method of a superclass on the JVM. However, the IR for the call to super.g points directly to the
// function g in class A. Confusingly, the `superQualifier` on this call also points to class A.
// The only way to compute the actual enclosing class for the call is by looking at the type of the
// dispatch receiver argument, which points to B.
//
// Beyond this, there can be accessors that are needed because other lowerings produce code calling
// private methods (e.g., local functions for lambdas are private and called from generated
// SAM wrapper classes). In this case we rely on the parent field of the called function.
//
// Finally, we need to produce accessors for calls to protected static methods coming from Java,
// which we put in the closest enclosing class which has access to the method in question.
val parent = symbol.owner.accessorParent(dispatchReceiverType?.classOrNull?.owner ?: symbol.owner.parent, scopes)
// The key in the cache/map needs to be BOTH the symbol of the function being accessed AND the parent
// of the accessor. Going from the above example, if we have another class C similar to B:
//
// class C : A {
// override fun g() = 2
// fun i() = run { super.g() }
// }
//
// For the call to super.g in function i, the accessor to A.g must be produced in C. Therefore, we
// cannot use the function symbol (A.g in the example) by itself as the key since there should be
// one accessor per dispatch receiver (i.e., parent of the accessor).
return functionMap.getOrPut(FunctionKey(symbol, parent, superQualifierSymbol)) {
when (symbol) {
is IrConstructorSymbol ->
symbol.owner.makeConstructorAccessor().symbol
is IrSimpleFunctionSymbol ->
symbol.owner.makeSimpleFunctionAccessor(superQualifierSymbol, dispatchReceiverType, parent, scopes).symbol
}
}
}
protected fun IrConstructor.makeConstructorAccessor(
originForConstructorAccessor: IrDeclarationOrigin = IrDeclarationOrigin.SYNTHETIC_ACCESSOR
): IrConstructor {
val source = this
return factory.buildConstructor {
origin = originForConstructorAccessor
name = source.name
visibility = DescriptorVisibilities.PUBLIC
}.also { accessor ->
accessor.parent = source.parent
accessor.copyTypeParametersFrom(source, IrDeclarationOrigin.SYNTHETIC_ACCESSOR)
accessor.copyValueParametersToStatic(source, IrDeclarationOrigin.SYNTHETIC_ACCESSOR)
if (source.constructedClass.modality == Modality.SEALED) {
for (accessorValueParameter in accessor.valueParameters) {
accessorValueParameter.annotations = emptyList()
}
}
accessor.returnType = source.returnType.remapTypeParameters(source, accessor)
accessor.addValueParameter(
"constructor_marker".synthesizedString,
context.ir.symbols.defaultConstructorMarker.defaultType.makeNullable(),
IrDeclarationOrigin.DEFAULT_CONSTRUCTOR_MARKER,
)
accessor.body = context.irFactory.createExpressionBody(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
createConstructorCall(accessor, source.symbol)
)
}
}
private fun createConstructorCall(accessor: IrConstructor, targetSymbol: IrConstructorSymbol) =
IrDelegatingConstructorCallImpl.fromSymbolOwner(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
context.irBuiltIns.unitType,
targetSymbol, targetSymbol.owner.parentAsClass.typeParameters.size + targetSymbol.owner.typeParameters.size
).also {
copyAllParamsToArgs(it, accessor)
}
protected open fun accessorModality(parent: IrDeclarationParent): Modality = Modality.FINAL
private fun IrSimpleFunction.makeSimpleFunctionAccessor(
superQualifierSymbol: IrClassSymbol?, dispatchReceiverType: IrType?, parent: IrDeclarationParent, scopes: List<ScopeWithIr>
): IrSimpleFunction {
val source = this
return factory.buildFun {
startOffset = parent.startOffset
endOffset = parent.startOffset
origin = IrDeclarationOrigin.SYNTHETIC_ACCESSOR
name = source.accessorName(superQualifierSymbol, scopes)
visibility = DescriptorVisibilities.PUBLIC
modality = accessorModality(parent)
isSuspend = source.isSuspend // synthetic accessors of suspend functions are handled in codegen
}.also { accessor ->
accessor.parent = parent
accessor.copyAttributes(source)
accessor.copyTypeParametersFrom(source, IrDeclarationOrigin.SYNTHETIC_ACCESSOR)
accessor.copyValueParametersToStatic(source, IrDeclarationOrigin.SYNTHETIC_ACCESSOR, dispatchReceiverType)
accessor.returnType = source.returnType.remapTypeParameters(source, accessor)
accessor.body = context.irFactory.createExpressionBody(
accessor.startOffset, accessor.startOffset,
createSimpleFunctionCall(accessor, source.symbol, superQualifierSymbol)
)
}
}
private fun createSimpleFunctionCall(accessor: IrFunction, targetSymbol: IrSimpleFunctionSymbol, superQualifierSymbol: IrClassSymbol?) =
IrCallImpl.fromSymbolOwner(
accessor.startOffset,
accessor.endOffset,
accessor.returnType,
targetSymbol, targetSymbol.owner.typeParameters.size,
superQualifierSymbol = superQualifierSymbol
).also {
copyAllParamsToArgs(it, accessor)
}
fun getSyntheticGetter(expression: IrGetField, scopes: List<ScopeWithIr>): IrSimpleFunctionSymbol {
val dispatchReceiverType = expression.receiver?.type
val dispatchReceiverClassSymbol = dispatchReceiverType?.classifierOrNull as? IrClassSymbol
val symbol = expression.symbol
val parent = symbol.owner.accessorParent(dispatchReceiverClassSymbol?.owner ?: symbol.owner.parent, scopes) as IrClass
return getterMap.getOrPut(FieldKey(symbol, parent, expression.superQualifierSymbol)) {
makeGetterAccessorSymbol(symbol, parent, expression.superQualifierSymbol)
}
}
private fun makeGetterAccessorSymbol(
fieldSymbol: IrFieldSymbol,
parent: IrClass,
superQualifierSymbol: IrClassSymbol?
): IrSimpleFunctionSymbol =
context.irFactory.buildFun {
startOffset = parent.startOffset
endOffset = parent.startOffset
origin = IrDeclarationOrigin.SYNTHETIC_ACCESSOR
name = fieldSymbol.owner.accessorNameForGetter(superQualifierSymbol)
visibility = DescriptorVisibilities.PUBLIC
modality = Modality.FINAL
returnType = fieldSymbol.owner.type
}.also { accessor ->
accessor.parent = parent
if (!fieldSymbol.owner.isStatic) {
// Accessors are always to one's own fields.
accessor.addValueParameter(
"\$this", parent.defaultType, IrDeclarationOrigin.SYNTHETIC_ACCESSOR
)
}
accessor.body = createAccessorBodyForGetter(fieldSymbol.owner, accessor, superQualifierSymbol)
}.symbol
private fun createAccessorBodyForGetter(
targetField: IrField,
accessor: IrSimpleFunction,
superQualifierSymbol: IrClassSymbol?
): IrBody {
val maybeDispatchReceiver =
if (targetField.isStatic) null
else IrGetValueImpl(accessor.startOffset, accessor.endOffset, accessor.valueParameters[0].symbol)
return context.irFactory.createExpressionBody(
accessor.startOffset, accessor.endOffset,
IrGetFieldImpl(
accessor.startOffset, accessor.endOffset,
targetField.symbol,
targetField.type,
maybeDispatchReceiver,
superQualifierSymbol = superQualifierSymbol
)
)
}
fun getSyntheticSetter(expression: IrSetField, scopes: List<ScopeWithIr>): IrSimpleFunctionSymbol {
val dispatchReceiverType = expression.receiver?.type
val dispatchReceiverClassSymbol = dispatchReceiverType?.classifierOrNull as? IrClassSymbol
val symbol = expression.symbol
val parent = symbol.owner.accessorParent(dispatchReceiverClassSymbol?.owner ?: symbol.owner.parent, scopes) as IrClass
return setterMap.getOrPut(FieldKey(symbol, parent, expression.superQualifierSymbol)) {
makeSetterAccessorSymbol(symbol, parent, expression.superQualifierSymbol)
}
}
private fun makeSetterAccessorSymbol(
fieldSymbol: IrFieldSymbol,
parent: IrClass,
superQualifierSymbol: IrClassSymbol?
): IrSimpleFunctionSymbol =
context.irFactory.buildFun {
startOffset = parent.startOffset
endOffset = parent.startOffset
origin = IrDeclarationOrigin.SYNTHETIC_ACCESSOR
name = fieldSymbol.owner.accessorNameForSetter(superQualifierSymbol)
visibility = DescriptorVisibilities.PUBLIC
modality = Modality.FINAL
returnType = context.irBuiltIns.unitType
}.also { accessor ->
accessor.parent = parent
if (!fieldSymbol.owner.isStatic) {
// Accessors are always to one's own fields.
accessor.addValueParameter(
"\$this", parent.defaultType, IrDeclarationOrigin.SYNTHETIC_ACCESSOR
)
}
accessor.addValueParameter("<set-?>", fieldSymbol.owner.type, IrDeclarationOrigin.SYNTHETIC_ACCESSOR)
accessor.body = createAccessorBodyForSetter(fieldSymbol.owner, accessor, superQualifierSymbol)
}.symbol
private fun createAccessorBodyForSetter(
targetField: IrField,
accessor: IrSimpleFunction,
superQualifierSymbol: IrClassSymbol?
): IrBody {
val maybeDispatchReceiver =
if (targetField.isStatic) null
else IrGetValueImpl(accessor.startOffset, accessor.endOffset, accessor.valueParameters[0].symbol)
val value = IrGetValueImpl(
accessor.startOffset, accessor.endOffset,
accessor.valueParameters[if (targetField.isStatic) 0 else 1].symbol
)
return context.irFactory.createExpressionBody(
accessor.startOffset, accessor.endOffset,
IrSetFieldImpl(
accessor.startOffset, accessor.endOffset,
targetField.symbol,
maybeDispatchReceiver,
value,
context.irBuiltIns.unitType,
superQualifierSymbol = superQualifierSymbol
)
)
}
private fun copyAllParamsToArgs(
call: IrFunctionAccessExpression,
syntheticFunction: IrFunction
) {
var typeArgumentOffset = 0
if (syntheticFunction is IrConstructor) {
call.passTypeArgumentsFrom(syntheticFunction.parentAsClass)
typeArgumentOffset = syntheticFunction.parentAsClass.typeParameters.size
}
call.passTypeArgumentsFrom(syntheticFunction, offset = typeArgumentOffset)
var offset = 0
val delegateTo = call.symbol.owner
delegateTo.dispatchReceiverParameter?.let {
call.dispatchReceiver =
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, syntheticFunction.valueParameters[offset++].symbol)
}
delegateTo.extensionReceiverParameter?.let {
call.extensionReceiver =
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, syntheticFunction.valueParameters[offset++].symbol)
}
delegateTo.valueParameters.forEachIndexed { i, _ ->
call.putValueArgument(
i,
IrGetValueImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
syntheticFunction.valueParameters[i + offset].symbol
)
)
}
}
/**
* In case of Java `protected static`, access could be done from a public inline function in the same package,
* or a subclass of the Java class. Both cases require an accessor, which we cannot add to a Java class.
*/
protected open fun IrDeclarationWithVisibility.accessorParent(parent: IrDeclarationParent, scopes: List<ScopeWithIr>) = parent
protected open fun mapFunctionName(function: IrSimpleFunction): String = function.name.asString()
protected open fun functionAccessorSuffix(
function: IrSimpleFunction,
superQualifier: IrClassSymbol?,
scopes: List<ScopeWithIr>,
): String = function.run {
when {
// Accessors for top level functions never need a suffix.
isTopLevel -> ""
// Accessor for _s_uper-qualified call
superQualifier != null -> "\$s" + superQualifier.owner.syntheticAccessorToSuperSuffix()
// Access to protected members that need an accessor must be because they are inherited,
// hence accessed on a _s_upertype. If what is accessed is static, we can point to different
// parts of the inheritance hierarchy and need to distinguish with a suffix.
isStatic && visibility.isProtected -> "\$s" + parentAsClass.syntheticAccessorToSuperSuffix()
else -> ""
}
}
private fun IrSimpleFunction.accessorName(superQualifier: IrClassSymbol?, scopes: List<ScopeWithIr>): Name {
return Name.identifier("access\$${mapFunctionName(this)}${functionAccessorSuffix(this, superQualifier, scopes)}")
}
protected open fun fieldGetterName(field: IrField): String = "<get-${field.name}>"
private fun IrField.accessorNameForGetter(superQualifierSymbol: IrClassSymbol?): Name {
val getterName = fieldGetterName(this)
return Name.identifier("access\$$getterName\$${fieldAccessorSuffix(this, superQualifierSymbol)}")
}
protected open fun fieldSetterName(field: IrField): String = "<set-${field.name}>"
private fun IrField.accessorNameForSetter(superQualifierSymbol: IrClassSymbol?): Name {
val setterName = fieldSetterName(this)
return Name.identifier("access\$$setterName\$${fieldAccessorSuffix(this, superQualifierSymbol)}")
}
/**
* For both _reading_ and _writing_ field accessors, the suffix that includes some of [field]'s important properties.
*/
protected open fun fieldAccessorSuffix(field: IrField, superQualifierSymbol: IrClassSymbol?): String = field.run {
if (superQualifierSymbol != null) {
return "p\$s${superQualifierSymbol.owner.syntheticAccessorToSuperSuffix()}"
}
// Accesses to static protected fields that need an accessor must be due to being inherited, hence accessed on a
// _s_upertype. If the field is static, the super class the access is on can be different, and therefore
// we generate a suffix to distinguish access to field with different receiver types in the super hierarchy.
return "p" + if (isStatic && visibility.isProtected) "\$s" + parentAsClass.syntheticAccessorToSuperSuffix() else ""
}
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()
protected open val DescriptorVisibility.isProtected
get() = this == DescriptorVisibilities.PROTECTED
}