[K/N] Optimize code generation for function references

In before, each function reference created 5 functions needed only
for equals/hashcode properties. It contributed arround 3.5% of code size
on several sample projects. This is now replaced by passing constant
object to base class constructor.
This commit is contained in:
Pavel Kunyavskiy
2023-02-10 10:34:10 +01:00
committed by Space Team
parent 7d8a1e9c1c
commit 00c900cb49
5 changed files with 77 additions and 40 deletions
@@ -377,6 +377,7 @@ internal abstract class KonanSymbols(
private fun reflectionClass(name: String) = irBuiltIns.findClass(Name.identifier(name), StandardNames.KOTLIN_REFLECT_FQ_NAME)!!
val kFunctionImpl = internalClass("KFunctionImpl")
val kFunctionDescription = internalClass("KFunctionDescription")
val kSuspendFunctionImpl = internalClass("KSuspendFunctionImpl")
val kMutableProperty0 = reflectionClass("KMutableProperty0")
@@ -1895,27 +1895,43 @@ internal class CodeGeneratorVisitor(
intrinsicGenerator.evaluateConstantConstructorFields(value, value.valueArguments.map { evaluateConstantValue(it) })
} else {
val fields = context.getLayoutBuilder(constructedClass).getFields(llvm)
val valueParameters = value.constructor.owner.valueParameters.associateBy { it.name.toString() }
val constructor = value.constructor.owner
val valueParameters = constructor.valueParameters.associateBy { it.name.toString() }
// support of initilaization of object in following case:
// open class Base(val field: ...)
// Child(val otherField: ...) : Base(constantValue)
//
// Child(constantValue) could be initialized constantly. This is required for function references.
val delegatedCallConstants = constructor.body?.statements
?.filterIsInstance<IrDelegatingConstructorCall>()
?.singleOrNull()
?.getArgumentsWithIr()
?.filter { it.second is IrConstantValue }
?.associate { it.first.name.toString() to it.second }
.orEmpty()
fields.map { field ->
if (field.isConst) {
val init = field.irField!!.initializer?.expression
require(field.name !in valueParameters) {
"Constant field ${field.name} of class ${constructedClass.name} shouldn't be a constructor parameter"
}
when (init) {
is IrConst<*> -> evaluateConst(init)
is IrConstantValue -> evaluateConstantValue(init)
null -> error("Constant field ${field.name} of class ${constructedClass.name} should have initializer")
else -> error("Unexpected constant initializer type: ${init::class}")
val init = if (field.isConst) {
field.irField!!.initializer?.expression.also {
require(field.name !in valueParameters) {
"Constant field ${field.name} of class ${constructedClass.name} shouldn't be a constructor parameter"
}
}
} else {
val index = valueParameters[field.name]?.index
?: error("Bad statically initialized object: field ${field.name} value not set in ${constructedClass.name}")
evaluateConstantValue(value.valueArguments[index])
if (index != null)
value.valueArguments[index]
else
delegatedCallConstants[field.name]
}
when (init) {
is IrConst<*> -> evaluateConst(init)
is IrConstantValue -> evaluateConstantValue(init)
null -> error("Bad statically initialized object: field ${field.name} value not set in ${constructedClass.name}")
else -> error("Unexpected constant initializer type: ${init::class}")
}
}.also {
require(it.size == value.valueArguments.size + fields.count { it.isConst }) {
"Bad statically initialized object of class ${constructedClass.name}: too many fields"
require(it.size == value.valueArguments.size + fields.count { it.isConst } + delegatedCallConstants.size) {
"Bad statically initialized object of class ${constructedClass.name}: not all arguments are used"
}
}
}
@@ -310,6 +310,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
}
private val kFunctionImplSymbol = symbols.kFunctionImpl
private val kFunctionDescriptionSymbol = symbols.kFunctionDescription
private val kFunctionImplConstructorSymbol = kFunctionImplSymbol.constructors.single()
private val kSuspendFunctionImplSymbol = symbols.kSuspendFunctionImpl
private val kSuspendFunctionImplConstructorSymbol = kSuspendFunctionImplSymbol.constructors.single()
@@ -384,15 +385,6 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
addOverrideInner(name) { _ -> value() }
}
val kTypeGenerator = KTypeGenerator(context, irFile, functionReference)
addOverride("computeReturnType") { with(kTypeGenerator) { irKType(referencedFunction.returnType) } }
addOverride("computeArity") { irInt(unboundFunctionParameters.size + if (functionReferenceTarget.isSuspend) 1 else 0) }
addOverride("computeFlags") { irInt(getFlags()) }
val name = ((functionReferenceTarget as? IrSimpleFunction)?.attributeOwnerId as? IrSimpleFunction)?.name
?: functionReferenceTarget.name
addOverride("computeName") { irString(name.asString()) }
addOverride("computeFqName") { irString(getFqName()) }
listOfNotNull(
functionReference.symbol.owner.dispatchReceiverParameter,
@@ -435,11 +427,15 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
body = context.createIrBuilder(symbol, startOffset, endOffset).irBlockBody {
val superConstructor = when {
this@FunctionReferenceBuilder.isSuspend -> kSuspendFunctionImplConstructorSymbol.owner
isLambda -> irBuiltIns.anyClass.owner.constructors.single()
this@FunctionReferenceBuilder.isSuspend -> kSuspendFunctionImplConstructorSymbol.owner
else -> kFunctionImplConstructorSymbol.owner
}
+irDelegatingConstructorCall(superConstructor)
+irDelegatingConstructorCall(superConstructor).apply {
if (!isLambda) {
putValueArgument(0, getDescription())
}
}
+IrInstanceInitializerCallImpl(startOffset, endOffset, functionReferenceClass.symbol, irBuiltIns.unitType)
// Save all arguments to fields.
boundFunctionParameters.forEachIndexed { index, parameter ->
@@ -465,6 +461,21 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
return BuiltFunctionReference(clazz, expression)
}
private fun IrBuilderWithScope.getDescription() : IrConstantValue {
val kTypeGenerator = KTypeGenerator(this@FunctionReferenceBuilder.context, irFile, functionReference)
return irConstantObject(
kFunctionDescriptionSymbol.owner,
mapOf(
"flags" to irConstantPrimitive(irInt(getFlags())),
"arity" to irConstantPrimitive(irInt(getArity())),
"fqName" to irConstantPrimitive(irString(getFqName())),
"name" to irConstantPrimitive(irString(getName().asString())),
"returnType" to with(kTypeGenerator) { irKType(referencedFunction.returnType) }
)
)
}
// this value is used only for hashCode and equals, to distinguish different wrappers on same functions
private fun getFlags() =
listOfNotNull(
@@ -481,6 +492,12 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
else
functionReferenceTarget.computeFullName()
private fun getName() =
((functionReferenceTarget as? IrSimpleFunction)?.attributeOwnerId as? IrSimpleFunction)?.name
?: functionReferenceTarget.name
private fun getArity() = unboundFunctionParameters.size + if (functionReferenceTarget.isSuspend) 1 else 0
private fun isFunInterfaceConstructorAdapter() =
referencedFunction.origin == IrDeclarationOrigin.ADAPTER_FOR_FUN_INTERFACE_CONSTRUCTOR
@@ -8,20 +8,23 @@ package kotlin.native.internal
import kotlin.reflect.KFunction
import kotlin.reflect.KType
@FixmeReflection
internal abstract class KFunctionImpl<out R>: KFunction<R> {
final override val returnType get() = computeReturnType()
val flags get() = computeFlags()
val arity get() = computeArity()
val fqName get() = computeFqName()
val receiver get() = computeReceiver()
final override val name get() = computeName()
internal class KFunctionDescription(
val flags: Int,
val arity: Int,
val fqName: String,
val name: String,
val returnType: KType
)
@FixmeReflection
internal abstract class KFunctionImpl<out R>(val description: KFunctionDescription): KFunction<R> {
final override val returnType get() = description.returnType
val flags get() = description.flags
val arity get() = description.arity
val fqName get() = description.fqName
val receiver get() = computeReceiver()
final override val name get() = description.name
abstract fun computeReturnType() : KType
abstract fun computeFlags() : Int
abstract fun computeArity() : Int
abstract fun computeFqName() : String
abstract fun computeName() : String
open fun computeReceiver(): Any? = null
override fun equals(other: Any?): Boolean {
@@ -10,6 +10,6 @@ import kotlin.reflect.KFunction
import kotlin.reflect.KClass
@FixmeReflection
internal abstract class KSuspendFunctionImpl<out R>: KFunctionImpl<R>() {
internal abstract class KSuspendFunctionImpl<out R>(description: KFunctionDescription): KFunctionImpl<R>(description) {
override fun toString() = "suspend function $name"
}