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.binding.CodegenBinding
|
||||||
import org.jetbrains.kotlin.codegen.inline.loadCompiledInlineFunction
|
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.GenerationState
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
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.calls.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
|
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
|
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) {
|
class DelegatedPropertiesCodegenHelper(private val state: GenerationState) {
|
||||||
|
|
||||||
@@ -75,20 +71,7 @@ class DelegatedPropertiesCodegenHelper(private val state: GenerationState) {
|
|||||||
val asmMethod = state.typeMapper.mapAsmMethod(calleeDescriptor)
|
val asmMethod = state.typeMapper.mapAsmMethod(calleeDescriptor)
|
||||||
val isMangled = requiresFunctionNameManglingForReturnType(calleeDescriptor)
|
val isMangled = requiresFunctionNameManglingForReturnType(calleeDescriptor)
|
||||||
val methodNode = loadCompiledInlineFunction(containerId, asmMethod, calleeDescriptor.isSuspend, isMangled, state).node
|
val methodNode = loadCompiledInlineFunction(containerId, asmMethod, calleeDescriptor.isSuspend, isMangled, state).node
|
||||||
return isMetadataParameterUsedInCompiledMethodBody(metadataParameterIndex, methodNode)
|
return methodNode.usesLocalExceptParameterNullCheck(metadataParameterIndex)
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMetadataParameterIndex(calleeDescriptor: FunctionDescriptor): Int {
|
private fun getMetadataParameterIndex(calleeDescriptor: FunctionDescriptor): Int {
|
||||||
|
|||||||
+8
@@ -436,6 +436,14 @@ internal fun AbstractInsnNode.isCheckNotNull() =
|
|||||||
desc == "(Ljava/lang/Object;)V"
|
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() =
|
internal fun AbstractInsnNode.isCheckParameterIsNotNull() =
|
||||||
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
|
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
|
||||||
owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
|
owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
|
||||||
|
|||||||
+5
-19
@@ -120,9 +120,6 @@ class ClassCodegen private constructor(
|
|||||||
if (generated) return
|
if (generated) return
|
||||||
generated = true
|
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
|
// 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,
|
// call uses `assert` and the JVM assertions mode is enabled. To avoid concurrent modification errors,
|
||||||
// there is a very specific generation order.
|
// 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:
|
// 1. Any method other than `<clinit>` can add a field and a `<clinit>` statement:
|
||||||
for (method in irClass.declarations.filterIsInstance<IrFunction>()) {
|
for (method in irClass.declarations.filterIsInstance<IrFunction>()) {
|
||||||
if (method.name.asString() != "<clinit>") {
|
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:
|
// 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) }
|
irClass.functions.find { it.name.asString() == "<clinit>" }?.let { generateMethod(it, smap) }
|
||||||
// 3. Now we have all the fields (`$$delegatedProperties` might be redundant if all reads were optimized out):
|
// 3. Now we have all the fields, including `$assertionsDisabled` if needed:
|
||||||
for (field in irClass.fields) {
|
for (field in irClass.fields) {
|
||||||
if (field !== delegatedProperties ||
|
generateField(field)
|
||||||
delegatedPropertyOptimizer?.needsDelegatedProperties == true ||
|
|
||||||
irClass.isCompanion
|
|
||||||
) {
|
|
||||||
generateField(field)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 4. Generate nested classes at the end, to ensure that when the companion's metadata is serialized
|
// 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`.
|
// 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)
|
return SMAPAndMethodNode(cloneMethodNode(node), smap)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateMethod(method: IrFunction, classSMAP: SourceMapper, delegatedPropertyOptimizer: DelegatedPropertyOptimizer?) {
|
private fun generateMethod(method: IrFunction, classSMAP: SourceMapper) {
|
||||||
if (method.isFakeOverride) {
|
if (method.isFakeOverride) {
|
||||||
jvmSignatureClashDetector.trackFakeOverrideMethod(method)
|
jvmSignatureClashDetector.trackFakeOverrideMethod(method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val (node, smap) = generateMethodNode(method)
|
val (node, smap) = generateMethodNode(method)
|
||||||
if (delegatedPropertyOptimizer != null) {
|
|
||||||
delegatedPropertyOptimizer.transform(node)
|
|
||||||
if (method.name.asString() == "<clinit>" && !irClass.isCompanion) {
|
|
||||||
delegatedPropertyOptimizer.transformClassInitializer(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.preprocessSuspendMarkers(
|
node.preprocessSuspendMarkers(
|
||||||
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE || method.isEffectivelyInlineOnly(),
|
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE || method.isEffectivelyInlineOnly(),
|
||||||
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
|
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.common.phaser.makeIrFilePhase
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
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.createJvmIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.irArrayOf
|
import org.jetbrains.kotlin.backend.jvm.ir.irArrayOf
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.needsAccessor
|
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.calculateOwnerKClass
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.FunctionReferenceLowering.Companion.kClassToJavaClass
|
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.InlineClassAbi
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledReturnType
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.isInlineClassFieldGetter
|
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.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
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.IrFunctionReferenceImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
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.impl.makeTypeProjection
|
||||||
import org.jetbrains.kotlin.ir.types.typeWith
|
import org.jetbrains.kotlin.ir.types.typeWith
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
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.JavaDescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.name.SpecialNames
|
import org.jetbrains.kotlin.name.SpecialNames
|
||||||
import org.jetbrains.kotlin.types.Variance
|
import org.jetbrains.kotlin.types.Variance
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
internal val propertyReferencePhase = makeIrFilePhase(
|
internal val propertyReferencePhase = makeIrFilePhase(
|
||||||
::PropertyReferenceLowering,
|
::PropertyReferenceLowering,
|
||||||
@@ -255,6 +263,54 @@ private class PropertyReferenceLowering(val context: JvmBackendContext) : IrElem
|
|||||||
override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference): IrExpression =
|
override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference): IrExpression =
|
||||||
cachedKProperty(expression)
|
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 {
|
private fun cachedKProperty(expression: IrCallableReference<*>): IrExpression {
|
||||||
expression.transformChildrenVoid()
|
expression.transformChildrenVoid()
|
||||||
if (expression.origin != IrStatementOrigin.PROPERTY_REFERENCE_FOR_DELEGATE)
|
if (expression.origin != IrStatementOrigin.PROPERTY_REFERENCE_FOR_DELEGATE)
|
||||||
|
|||||||
-2
@@ -55,8 +55,6 @@ final class H2$Companion$property$2 {
|
|||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public final class H2$Companion {
|
public final class H2$Companion {
|
||||||
// source: 'delegatedPropertiesInCompanionObject.kt'
|
// source: 'delegatedPropertiesInCompanionObject.kt'
|
||||||
synthetic final static field $$delegatedProperties: kotlin.reflect.KProperty[]
|
|
||||||
static method <clinit>(): void
|
|
||||||
private method <init>(): void
|
private method <init>(): void
|
||||||
public synthetic method <init>(p0: kotlin.jvm.internal.DefaultConstructorMarker): void
|
public synthetic method <init>(p0: kotlin.jvm.internal.DefaultConstructorMarker): void
|
||||||
public final @org.jetbrains.annotations.NotNull method getProperty(): java.lang.String
|
public final @org.jetbrains.annotations.NotNull method getProperty(): java.lang.String
|
||||||
|
|||||||
Reference in New Issue
Block a user