diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatedPropertiesCodegenHelper.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatedPropertiesCodegenHelper.kt index c7b89491154..46929cacb89 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatedPropertiesCodegenHelper.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatedPropertiesCodegenHelper.kt @@ -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 { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt index 5b68a9efa8c..a66f3a012b3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt @@ -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(Opcodes.INVOKESTATIC) { owner == IntrinsicMethods.INTRINSICS_CLASS_NAME && diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt index a2fc149b4aa..6a95d4d3d10 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt @@ -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 `` can add a field and a `` statement: for (method in irClass.declarations.filterIsInstance()) { if (method.name.asString() != "") { - generateMethod(method, smap, delegatedPropertyOptimizer) + generateMethod(method, smap) } } // 2. `` itself can add a field, but the statement is generated via the `return init` hack: - irClass.functions.find { it.name.asString() == "" }?.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() == "" }?.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() == "" && !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 diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/DelegatedPropertyOptimizer.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/DelegatedPropertyOptimizer.kt deleted file mode 100644 index 9cbbbf4ffe3..00000000000 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/DelegatedPropertyOptimizer.kt +++ /dev/null @@ -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() - 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> = mutableListOf()) - - val variableToDelegatedPropertyIndex = mutableMapOf() - - // We look for bytecode patterns of the form - // - // getstatic $$delegatedProperties:[Lkotlin/reflect/KProperty; - // iconst - // aaload - // (checkcast ...) - // astore - // - // 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().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 - // ANEWARRAY kotlin/reflect/KProperty - // ASTORE - // - 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>() - } 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 - // - // LDC - // - // NEW kotlin/jvm/internal/(Mutable)PropertyReference[012]Impl - // DUP - // LDC .class - // LDC "" - // INVOKESTATIC kotlin/jvm/internal/Reflection.getOrCreateKotlinPackage (Ljava/lang/Class;Ljava/lang/String;)Lkotlin/reflect/KDeclarationContainer; - // LDC "" - // LDC "" - // INVOKESPECIAL kotlin/jvm/internal/(Mutable)PropertyReference[012]Impl. (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("", "(Lkotlin/reflect/KDeclarationContainer;Ljava/lang/String;Ljava/lang/String;)V") - }, { - ldc() // name - ldc() // signature - iconst() // flags - invokespecial("", "(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 - // PUTSTATIC .$$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 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()?.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) { - 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()?.operand - Opcodes.LDC -> safeAs()?.cst as? Int - else -> null - } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt index 816a05b477c..52de0ea7582 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt @@ -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() + + 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) diff --git a/compiler/testData/codegen/bytecodeListing/delegatedPropertiesInCompanionObject_ir.txt b/compiler/testData/codegen/bytecodeListing/delegatedPropertiesInCompanionObject_ir.txt index e4f0b669092..c9df7343630 100644 --- a/compiler/testData/codegen/bytecodeListing/delegatedPropertiesInCompanionObject_ir.txt +++ b/compiler/testData/codegen/bytecodeListing/delegatedPropertiesInCompanionObject_ir.txt @@ -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 (): void private method (): void public synthetic method (p0: kotlin.jvm.internal.DefaultConstructorMarker): void public final @org.jetbrains.annotations.NotNull method getProperty(): java.lang.String