JVM_IR: read inline fun bytecode to detect unused property references
I.e. optimize `$$delegatedProperties` in the same way as the old backend. This is more reliable than trying to match bytecode.
This commit is contained in:
+2
-19
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.codegen
|
||||
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.inline.loadCompiledInlineFunction
|
||||
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
|
||||
import org.jetbrains.kotlin.codegen.optimization.nullCheck.usesLocalExceptParameterNullCheck
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
@@ -17,10 +17,6 @@ import org.jetbrains.kotlin.resolve.FunctionImportedFromObject
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
|
||||
|
||||
class DelegatedPropertiesCodegenHelper(private val state: GenerationState) {
|
||||
|
||||
@@ -75,20 +71,7 @@ class DelegatedPropertiesCodegenHelper(private val state: GenerationState) {
|
||||
val asmMethod = state.typeMapper.mapAsmMethod(calleeDescriptor)
|
||||
val isMangled = requiresFunctionNameManglingForReturnType(calleeDescriptor)
|
||||
val methodNode = loadCompiledInlineFunction(containerId, asmMethod, calleeDescriptor.isSuspend, isMangled, state).node
|
||||
return isMetadataParameterUsedInCompiledMethodBody(metadataParameterIndex, methodNode)
|
||||
}
|
||||
|
||||
private fun isMetadataParameterUsedInCompiledMethodBody(metadataParameterIndex: Int, methodNode: MethodNode): Boolean =
|
||||
methodNode.instructions.toArray().any { insn ->
|
||||
insn is VarInsnNode && insn.opcode == Opcodes.ALOAD && insn.`var` == metadataParameterIndex &&
|
||||
!isParameterNullCheckArgument(insn)
|
||||
}
|
||||
|
||||
private fun isParameterNullCheckArgument(insn: AbstractInsnNode): Boolean {
|
||||
val next1 = insn.next
|
||||
val next2 = next1.next
|
||||
return next1 != null && next2 != null &&
|
||||
next1.opcode == Opcodes.LDC && next2.isCheckParameterIsNotNull()
|
||||
return methodNode.usesLocalExceptParameterNullCheck(metadataParameterIndex)
|
||||
}
|
||||
|
||||
private fun getMetadataParameterIndex(calleeDescriptor: FunctionDescriptor): Int {
|
||||
|
||||
+8
@@ -436,6 +436,14 @@ internal fun AbstractInsnNode.isCheckNotNull() =
|
||||
desc == "(Ljava/lang/Object;)V"
|
||||
}
|
||||
|
||||
fun MethodNode.usesLocalExceptParameterNullCheck(index: Int): Boolean =
|
||||
instructions.toArray().any {
|
||||
it is VarInsnNode && it.opcode == Opcodes.ALOAD && it.`var` == index && !it.isParameterCheckedForNull()
|
||||
}
|
||||
|
||||
internal fun AbstractInsnNode.isParameterCheckedForNull(): Boolean =
|
||||
next?.takeIf { it.opcode == Opcodes.LDC }?.next?.isCheckParameterIsNotNull() == true
|
||||
|
||||
internal fun AbstractInsnNode.isCheckParameterIsNotNull() =
|
||||
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
|
||||
owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
|
||||
|
||||
+5
-19
@@ -120,9 +120,6 @@ class ClassCodegen private constructor(
|
||||
if (generated) return
|
||||
generated = true
|
||||
|
||||
// We remove reads of `$$delegatedProperties` (and the field itself) if they are not in fact used for anything.
|
||||
val delegatedProperties = irClass.fields.singleOrNull { it.origin == JvmLoweredDeclarationOrigin.GENERATED_PROPERTY_REFERENCE }
|
||||
val delegatedPropertyOptimizer = if (delegatedProperties != null) DelegatedPropertyOptimizer() else null
|
||||
// Generating a method node may cause the addition of a field with an initializer if an inline function
|
||||
// call uses `assert` and the JVM assertions mode is enabled. To avoid concurrent modification errors,
|
||||
// there is a very specific generation order.
|
||||
@@ -130,19 +127,14 @@ class ClassCodegen private constructor(
|
||||
// 1. Any method other than `<clinit>` can add a field and a `<clinit>` statement:
|
||||
for (method in irClass.declarations.filterIsInstance<IrFunction>()) {
|
||||
if (method.name.asString() != "<clinit>") {
|
||||
generateMethod(method, smap, delegatedPropertyOptimizer)
|
||||
generateMethod(method, smap)
|
||||
}
|
||||
}
|
||||
// 2. `<clinit>` itself can add a field, but the statement is generated via the `return init` hack:
|
||||
irClass.functions.find { it.name.asString() == "<clinit>" }?.let { generateMethod(it, smap, delegatedPropertyOptimizer) }
|
||||
// 3. Now we have all the fields (`$$delegatedProperties` might be redundant if all reads were optimized out):
|
||||
irClass.functions.find { it.name.asString() == "<clinit>" }?.let { generateMethod(it, smap) }
|
||||
// 3. Now we have all the fields, including `$assertionsDisabled` if needed:
|
||||
for (field in irClass.fields) {
|
||||
if (field !== delegatedProperties ||
|
||||
delegatedPropertyOptimizer?.needsDelegatedProperties == true ||
|
||||
irClass.isCompanion
|
||||
) {
|
||||
generateField(field)
|
||||
}
|
||||
generateField(field)
|
||||
}
|
||||
// 4. Generate nested classes at the end, to ensure that when the companion's metadata is serialized
|
||||
// everything moved to the outer class has already been recorded in `globalSerializationBindings`.
|
||||
@@ -357,19 +349,13 @@ class ClassCodegen private constructor(
|
||||
return SMAPAndMethodNode(cloneMethodNode(node), smap)
|
||||
}
|
||||
|
||||
private fun generateMethod(method: IrFunction, classSMAP: SourceMapper, delegatedPropertyOptimizer: DelegatedPropertyOptimizer?) {
|
||||
private fun generateMethod(method: IrFunction, classSMAP: SourceMapper) {
|
||||
if (method.isFakeOverride) {
|
||||
jvmSignatureClashDetector.trackFakeOverrideMethod(method)
|
||||
return
|
||||
}
|
||||
|
||||
val (node, smap) = generateMethodNode(method)
|
||||
if (delegatedPropertyOptimizer != null) {
|
||||
delegatedPropertyOptimizer.transform(node)
|
||||
if (method.name.asString() == "<clinit>" && !irClass.isCompanion) {
|
||||
delegatedPropertyOptimizer.transformClassInitializer(node)
|
||||
}
|
||||
}
|
||||
node.preprocessSuspendMarkers(
|
||||
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE || method.isEffectivelyInlineOnly(),
|
||||
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
|
||||
|
||||
-338
@@ -1,338 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.jvm.codegen
|
||||
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
|
||||
class DelegatedPropertyOptimizer {
|
||||
private var usedDelegatedProperties = mutableSetOf<Int>()
|
||||
private var useOfDelegatesPropertiesArray = false
|
||||
|
||||
val needsDelegatedProperties: Boolean
|
||||
get() = useOfDelegatesPropertiesArray || usedDelegatedProperties.isNotEmpty()
|
||||
|
||||
// Remove unused loads of cached KProperties from the given method and record uses of cached KProperties.
|
||||
fun transform(methodNode: MethodNode) {
|
||||
class KPropertyRanges(val propertyIndex: Int, val ranges: MutableList<Pair<AbstractInsnNode, AbstractInsnNode>> = mutableListOf())
|
||||
|
||||
val variableToDelegatedPropertyIndex = mutableMapOf<Int, KPropertyRanges>()
|
||||
|
||||
// We look for bytecode patterns of the form
|
||||
//
|
||||
// getstatic $$delegatedProperties:[Lkotlin/reflect/KProperty;
|
||||
// iconst <n>
|
||||
// aaload
|
||||
// (checkcast ...)
|
||||
// astore <x>
|
||||
//
|
||||
// If the variable `x` is used, we mark the delegated property at index `n` as used,
|
||||
// otherwise we delete the code in question. This is the pattern that results from an
|
||||
// inline call with a cached KProperty argument. If the `astore` instruction is missing
|
||||
// then that's a different use of the KProperty at index `n` and we mark it as used.
|
||||
// Finally, if there is any other code which loads the `$$delegatedProperties` array,
|
||||
// we mark it as generally unsafe to remove.
|
||||
for (insn in methodNode.instructions) {
|
||||
if (!insn.isDelegatedPropertiesArray)
|
||||
continue
|
||||
var index = 0
|
||||
var current = matchRange(insn) {
|
||||
index = iconst()
|
||||
aaload()
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
// There's an unknown use of the $$delegatedProperties array, we need to ensure that we don't remove it.
|
||||
useOfDelegatesPropertiesArray = true
|
||||
return
|
||||
}
|
||||
|
||||
var slot = 0
|
||||
current = matchRange(current) {
|
||||
slot = astore()
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
// There's an actual use of the delegated property at `index`, mark it.
|
||||
usedDelegatedProperties.add(index)
|
||||
} else {
|
||||
variableToDelegatedPropertyIndex.getOrPut(slot) {
|
||||
KPropertyRanges(index)
|
||||
}.ranges += insn to current
|
||||
}
|
||||
}
|
||||
|
||||
if (variableToDelegatedPropertyIndex.isEmpty())
|
||||
return
|
||||
|
||||
// Mark all of the KProperty variables which are used in the current method.
|
||||
methodNode.instructions.asSequence().filterIsInstance<VarInsnNode>().filter { it.opcode == Opcodes.ALOAD }.forEach {
|
||||
variableToDelegatedPropertyIndex[it.`var`]?.let { kPropertyRange ->
|
||||
usedDelegatedProperties.add(kPropertyRange.propertyIndex)
|
||||
variableToDelegatedPropertyIndex.remove(it.`var`)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the unused KProperty loads.
|
||||
val locals = methodNode.localVariables.mapTo(mutableSetOf()) { it.index }
|
||||
for (kPropertyRanges in variableToDelegatedPropertyIndex.values) {
|
||||
for ((from, to) in kPropertyRanges.ranges) {
|
||||
// Stores to variables with LVT entries need to be preserved. We store `null` for
|
||||
// unused KProperties, for compatibility with the JVM BE.
|
||||
val storeInsn = to as VarInsnNode
|
||||
if (storeInsn.`var` !in locals) {
|
||||
methodNode.instructions.removeRange(from, to)
|
||||
} else {
|
||||
methodNode.instructions.removeRange(from, to.previous)
|
||||
methodNode.instructions.insertBefore(to, InsnNode(Opcodes.ACONST_NULL))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the initialization of unused cached KProperties, or remove the $$delegatedProperties field initialization
|
||||
// completely, if it is not used.
|
||||
fun transformClassInitializer(methodNode: MethodNode) {
|
||||
if (useOfDelegatesPropertiesArray)
|
||||
return
|
||||
|
||||
for (insn in methodNode.instructions) {
|
||||
// Parse the array allocation
|
||||
//
|
||||
// LDC <length>
|
||||
// ANEWARRAY kotlin/reflect/KProperty
|
||||
// ASTORE <x>
|
||||
//
|
||||
val size = insn.intValue ?: continue
|
||||
var slot = 0
|
||||
var current = matchRange(insn) {
|
||||
anewarray("kotlin/reflect/KProperty")
|
||||
slot = astore()
|
||||
} ?: continue
|
||||
|
||||
// Note that there can be cases where some property references are used, in which case we
|
||||
// can only remove the initialization code for some of the references, not the whole
|
||||
// $$delegatedProperties array.
|
||||
val rangesToRemove = if (usedDelegatedProperties.isNotEmpty()) {
|
||||
mutableListOf<Pair<AbstractInsnNode, AbstractInsnNode>>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Parse the array element initializers
|
||||
for (i in 0 until size) {
|
||||
var index = 0
|
||||
val start = current.next
|
||||
// The code to initialize a single array element looks like this
|
||||
//
|
||||
// ALOAD <x>
|
||||
//
|
||||
// LDC <index>
|
||||
//
|
||||
// NEW kotlin/jvm/internal/(Mutable)PropertyReference[012]Impl
|
||||
// DUP
|
||||
// LDC <current>.class
|
||||
// LDC "<module>"
|
||||
// INVOKESTATIC kotlin/jvm/internal/Reflection.getOrCreateKotlinPackage (Ljava/lang/Class;Ljava/lang/String;)Lkotlin/reflect/KDeclarationContainer;
|
||||
// LDC "<property name>"
|
||||
// LDC "<property getter signature>"
|
||||
// INVOKESPECIAL kotlin/jvm/internal/(Mutable)PropertyReference[012]Impl.<init> (Lkotlin/reflect/KDeclarationContainer;Ljava/lang/String;Ljava/lang/String;)V
|
||||
// INVOKESTATIC kotlin/jvm/internal/Reflection.(mutable)property[012] (Lkotlin/jvm/internal/PropertyReference[012];)Lkotlin/reflect/KProperty[012];
|
||||
//
|
||||
// AASTORE
|
||||
//
|
||||
// With the caveat that we might create a class instead of a package as the `KDeclarationContainer`,
|
||||
// and with optimized references the Java class is passed to the constructor without wrapping.
|
||||
current = matchRange(current) {
|
||||
aload(slot)
|
||||
index = iconst()
|
||||
new(PROPERTY_REFERENCE_CLASSES)
|
||||
dup()
|
||||
ldc() // java class
|
||||
either(
|
||||
{
|
||||
optional { ldc() } // module name, if package
|
||||
invokestatic("kotlin/jvm/internal/Reflection") // getOrCreateKotlinPackage or getOrCreateKotlinClass
|
||||
ldc() // name
|
||||
ldc() // signature
|
||||
invokespecial("<init>", "(Lkotlin/reflect/KDeclarationContainer;Ljava/lang/String;Ljava/lang/String;)V")
|
||||
}, {
|
||||
ldc() // name
|
||||
ldc() // signature
|
||||
iconst() // flags
|
||||
invokespecial("<init>", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;I)V")
|
||||
}
|
||||
)
|
||||
invokestatic("kotlin/jvm/internal/Reflection") // (property|mutableProperty)[012]
|
||||
aastore()
|
||||
} ?: break
|
||||
|
||||
if (rangesToRemove != null && index !in usedDelegatedProperties) {
|
||||
rangesToRemove.add(start to current)
|
||||
}
|
||||
}
|
||||
|
||||
// At this point the code sets the $$delegatedProperties field to the constructed array.
|
||||
//
|
||||
// ALOAD <x>
|
||||
// PUTSTATIC <current>.$$delegatedProperties : [Lkotlin/reflect/KProperty;
|
||||
current = matchRange(current) {
|
||||
aload(slot)
|
||||
putstatic(JvmAbi.DELEGATED_PROPERTIES_ARRAY_NAME, "[Lkotlin/reflect/KProperty;")
|
||||
} ?: continue
|
||||
|
||||
if (rangesToRemove == null) {
|
||||
// Remove the whole array initialization
|
||||
methodNode.instructions.removeRange(insn, current)
|
||||
} else {
|
||||
for ((from, to) in rangesToRemove) {
|
||||
methodNode.instructions.removeRange(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
// There is only one initializer for the $$delegatedProperties array.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Match a half-open range of instructions from start (exclusive!) and returns either null if the instructions
|
||||
// did not match or the last instruction which matched.
|
||||
private fun matchRange(start: AbstractInsnNode, body: InstructionMatcher.() -> Unit): AbstractInsnNode? =
|
||||
InstructionMatcher(start).apply(body).current
|
||||
|
||||
companion object {
|
||||
val PROPERTY_REFERENCE_CLASSES = setOf(
|
||||
"kotlin/jvm/internal/PropertyReference0Impl",
|
||||
"kotlin/jvm/internal/PropertyReference1Impl",
|
||||
"kotlin/jvm/internal/PropertyReference2Impl",
|
||||
"kotlin/jvm/internal/MutablePropertyReference0Impl",
|
||||
"kotlin/jvm/internal/MutablePropertyReference1Impl",
|
||||
"kotlin/jvm/internal/MutablePropertyReference2Impl",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Match a straight-line segment of instructions, while ignoring irrelevant instructions such as labels, linenumbers,
|
||||
// frames, nops, and casts.
|
||||
private class InstructionMatcher(var current: AbstractInsnNode?) {
|
||||
fun peek(): AbstractInsnNode? {
|
||||
var insn = current?.next ?: return null
|
||||
while (!insn.isMeaningful || insn.opcode == Opcodes.NOP || insn.opcode == Opcodes.CHECKCAST) {
|
||||
insn = insn.next ?: return null
|
||||
}
|
||||
return insn
|
||||
}
|
||||
|
||||
inline fun match(predicate: (AbstractInsnNode) -> Boolean) {
|
||||
current = peek()?.takeIf(predicate)
|
||||
}
|
||||
|
||||
inline fun <T> parse(default: T, op: (AbstractInsnNode) -> T?): T {
|
||||
var value = default
|
||||
match {
|
||||
op(it)?.let {
|
||||
value = it
|
||||
true
|
||||
} ?: false
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
inline fun optional(block: () -> Unit) {
|
||||
either(block) {}
|
||||
}
|
||||
|
||||
inline fun either(first: () -> Unit, second: () -> Unit) {
|
||||
val start = current
|
||||
first()
|
||||
if (current == null) {
|
||||
current = start
|
||||
second()
|
||||
}
|
||||
}
|
||||
|
||||
fun iconst(): Int = parse(0) { it.intValue }
|
||||
|
||||
fun aaload() {
|
||||
match { it.opcode == Opcodes.AALOAD }
|
||||
}
|
||||
|
||||
fun aastore() {
|
||||
match { it.opcode == Opcodes.AASTORE }
|
||||
}
|
||||
|
||||
fun anewarray(desc: String) {
|
||||
match { it.opcode == Opcodes.ANEWARRAY && it is TypeInsnNode && it.desc == desc }
|
||||
}
|
||||
|
||||
fun aload(slot: Int) {
|
||||
match { it.opcode == Opcodes.ALOAD && it is VarInsnNode && it.`var` == slot }
|
||||
}
|
||||
|
||||
fun astore(): Int =
|
||||
parse(0) { insn -> insn.safeAs<VarInsnNode>()?.takeIf { it.opcode == Opcodes.ASTORE }?.`var` }
|
||||
|
||||
fun putstatic(name: String, desc: String) {
|
||||
match { it.opcode == Opcodes.PUTSTATIC && it is FieldInsnNode && it.name == name && it.desc == desc }
|
||||
}
|
||||
|
||||
fun dup() {
|
||||
match { it.opcode == Opcodes.DUP }
|
||||
}
|
||||
|
||||
fun ldc() {
|
||||
match { it.opcode == Opcodes.LDC }
|
||||
}
|
||||
|
||||
fun invokestatic(owner: String) {
|
||||
match { it.opcode == Opcodes.INVOKESTATIC && it is MethodInsnNode && it.owner == owner }
|
||||
}
|
||||
|
||||
fun invokespecial(name: String, desc: String) {
|
||||
match { it.opcode == Opcodes.INVOKESPECIAL && it is MethodInsnNode && it.name == name && it.desc == desc }
|
||||
}
|
||||
|
||||
fun new(classNames: Set<String>) {
|
||||
match { it.opcode == Opcodes.NEW && it is TypeInsnNode && it.desc in classNames }
|
||||
}
|
||||
}
|
||||
|
||||
private fun InsnList.safeRemove(insn: AbstractInsnNode) {
|
||||
if (insn !is LabelNode && insn !is LineNumberNode)
|
||||
remove(insn)
|
||||
}
|
||||
|
||||
private fun InsnList.removeRange(from: AbstractInsnNode, to: AbstractInsnNode) {
|
||||
var current = from
|
||||
do {
|
||||
val next = current.next
|
||||
safeRemove(current)
|
||||
current = next
|
||||
} while (current != to)
|
||||
safeRemove(to)
|
||||
}
|
||||
|
||||
private val AbstractInsnNode.isDelegatedPropertiesArray: Boolean
|
||||
get() = opcode == Opcodes.GETSTATIC && this is FieldInsnNode
|
||||
&& name == JvmAbi.DELEGATED_PROPERTIES_ARRAY_NAME && desc == "[Lkotlin/reflect/KProperty;"
|
||||
|
||||
private val AbstractInsnNode.intValue: Int?
|
||||
get() = when (opcode) {
|
||||
Opcodes.ICONST_0 -> 0
|
||||
Opcodes.ICONST_1 -> 1
|
||||
Opcodes.ICONST_2 -> 2
|
||||
Opcodes.ICONST_3 -> 3
|
||||
Opcodes.ICONST_4 -> 4
|
||||
Opcodes.ICONST_5 -> 5
|
||||
Opcodes.ICONST_M1 -> -1
|
||||
Opcodes.BIPUSH, Opcodes.SIPUSH -> safeAs<IntInsnNode>()?.operand
|
||||
Opcodes.LDC -> safeAs<LdcInsnNode>()?.cst as? Int
|
||||
else -> null
|
||||
}
|
||||
+56
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.parentClassId
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.irArrayOf
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.needsAccessor
|
||||
@@ -21,15 +22,20 @@ import org.jetbrains.kotlin.backend.jvm.lower.FunctionReferenceLowering.Companio
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.FunctionReferenceLowering.Companion.calculateOwnerKClass
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.FunctionReferenceLowering.Companion.kClassToJavaClass
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledReturnType
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.isInlineClassFieldGetter
|
||||
import org.jetbrains.kotlin.codegen.inline.loadCompiledInlineFunction
|
||||
import org.jetbrains.kotlin.codegen.optimization.nullCheck.usesLocalExceptParameterNullCheck
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
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.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
||||
@@ -41,11 +47,13 @@ import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
||||
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
||||
import org.jetbrains.kotlin.ir.types.typeWith
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.SpecialNames
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal val propertyReferencePhase = makeIrFilePhase(
|
||||
::PropertyReferenceLowering,
|
||||
@@ -255,6 +263,54 @@ private class PropertyReferenceLowering(val context: JvmBackendContext) : IrElem
|
||||
override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference): IrExpression =
|
||||
cachedKProperty(expression)
|
||||
|
||||
private fun IrSimpleFunction.usesParameter(index: Int): Boolean {
|
||||
parentClassId?.let { containerId ->
|
||||
// This function was imported from a jar. Didn't run the inline class lowering yet though - have to map manually.
|
||||
val replaced = context.inlineClassReplacements.getReplacementFunction(this) ?: this
|
||||
val signature = context.methodSignatureMapper.mapSignatureSkipGeneric(replaced)
|
||||
val localIndex = signature.valueParameters.take(index + if (replaced.extensionReceiverParameter != null) 1 else 0)
|
||||
.sumOf { it.asmType.size } + (if (replaced.dispatchReceiverParameter != null) 1 else 0)
|
||||
// Null checks are removed during inlining, so we can ignore them.
|
||||
return loadCompiledInlineFunction(containerId, signature.asmMethod, isSuspend, hasMangledReturnType, context.state)
|
||||
.node.usesLocalExceptParameterNullCheck(localIndex)
|
||||
}
|
||||
|
||||
var result = false
|
||||
accept(object : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) =
|
||||
if (result) Unit else element.acceptChildren(this, null)
|
||||
|
||||
override fun visitGetValue(expression: IrGetValue) {
|
||||
if (expression.symbol == valueParameters[index].symbol) result = true
|
||||
}
|
||||
}, null)
|
||||
return result
|
||||
}
|
||||
|
||||
// Assuming that the only functions that take PROPERTY_REFERENCE_FOR_DELEGATE-kind references are getValue,
|
||||
// setValue, and provideDelegate, there is only one valid index for each symbol, so we don't need it in the key.
|
||||
private val usesPropertyParameterCache = ConcurrentHashMap<IrSymbol, Boolean>()
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
if (!expression.symbol.owner.isInline) {
|
||||
// Can't optimize if the function can start using the reference later.
|
||||
// TODO: optimize if callee is in the same file? this requires removing the null check from it,
|
||||
// or at least providing *some* non-null fake property value.
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
for (index in expression.symbol.owner.valueParameters.indices) {
|
||||
val value = expression.getValueArgument(index)
|
||||
if (value is IrCallableReference<*> && value.origin == IrStatementOrigin.PROPERTY_REFERENCE_FOR_DELEGATE) {
|
||||
if (!usesPropertyParameterCache.getOrPut(expression.symbol) { expression.symbol.owner.usesParameter(index) }) {
|
||||
// Don't generate an entry in `$$delegatedProperties`, it won't be used anyway.
|
||||
expression.putValueArgument(index, IrConstImpl.constNull(value.startOffset, value.endOffset, value.type))
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
private fun cachedKProperty(expression: IrCallableReference<*>): IrExpression {
|
||||
expression.transformChildrenVoid()
|
||||
if (expression.origin != IrStatementOrigin.PROPERTY_REFERENCE_FOR_DELEGATE)
|
||||
|
||||
-2
@@ -55,8 +55,6 @@ final class H2$Companion$property$2 {
|
||||
@kotlin.Metadata
|
||||
public final class H2$Companion {
|
||||
// source: 'delegatedPropertiesInCompanionObject.kt'
|
||||
synthetic final static field $$delegatedProperties: kotlin.reflect.KProperty[]
|
||||
static method <clinit>(): void
|
||||
private method <init>(): void
|
||||
public synthetic method <init>(p0: kotlin.jvm.internal.DefaultConstructorMarker): void
|
||||
public final @org.jetbrains.annotations.NotNull method getProperty(): java.lang.String
|
||||
|
||||
Reference in New Issue
Block a user