KT-14513 Don't generate delegated property metadata when unused

If the delegated property operators involved are inline, and delegated
property metadata parameter is not used (which is often the case, e.g.,
'lazy'), we can skip those properties in metadata generation.

NOT implemented: special case when only 'kProperty.name' is used by the
corresponding delegated property operators.

Also a sneak fix for KT-34060.
This commit is contained in:
Dmitry Petrov
2019-09-27 14:24:11 +03:00
committed by Dmitry Petrov
parent 92ba298e68
commit a633a33627
29 changed files with 1054 additions and 25 deletions
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2019 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.codegen
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.inline.InlineCodegen
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.FunctionImportedFromObject
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
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) {
private val bindingContext = state.bindingContext
private val bindingTrace = state.bindingTrace
private val typeMapper = state.typeMapper
fun isDelegatedPropertyMetadataRequired(descriptor: VariableDescriptorWithAccessors): Boolean {
val provideDelegateResolvedCall = bindingContext[BindingContext.PROVIDE_DELEGATE_RESOLVED_CALL, descriptor]
val getValueResolvedCall = descriptor.getter?.let { bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it] }
val setValueResolvedCall = descriptor.setter?.let { bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it] }
return provideDelegateResolvedCall != null && isDelegatedPropertyMetadataRequired(provideDelegateResolvedCall) ||
getValueResolvedCall != null && isDelegatedPropertyMetadataRequired(getValueResolvedCall) ||
setValueResolvedCall != null && isDelegatedPropertyMetadataRequired(setValueResolvedCall)
}
private fun isDelegatedPropertyMetadataRequired(operatorCall: ResolvedCall<FunctionDescriptor>): Boolean {
val calleeDescriptor = operatorCall.resultingDescriptor.getActualCallee().original
if (!calleeDescriptor.isInline) return true
val metadataParameter = calleeDescriptor.valueParameters[1]
if (true == bindingContext[BindingContext.UNUSED_DELEGATED_PROPERTY_OPERATOR_PARAMETER, metadataParameter]) {
return false
}
if (calleeDescriptor !is DescriptorWithContainerSource) return true
val cachedResult = bindingTrace[CodegenBinding.PROPERTY_METADATA_REQUIRED_FOR_OPERATOR_CALL, calleeDescriptor]
if (cachedResult != null) {
return cachedResult
}
return isDelegatedPropertyMetadataRequiredForFunctionFromBinaries(calleeDescriptor).also {
bindingTrace.record(CodegenBinding.PROPERTY_METADATA_REQUIRED_FOR_OPERATOR_CALL, calleeDescriptor, it)
}
}
private fun FunctionDescriptor.getActualCallee(): FunctionDescriptor =
if (this is FunctionImportedFromObject)
callableFromObject
else
this
private fun isDelegatedPropertyMetadataRequiredForFunctionFromBinaries(calleeDescriptor: FunctionDescriptor): Boolean {
assert(calleeDescriptor is DescriptorWithContainerSource) {
"Function descriptor from binaries expected: $calleeDescriptor"
}
val metadataParameterIndex = getMetadataParameterIndex(calleeDescriptor)
val methodNode = InlineCodegen.createSpecialInlineMethodNodeFromBinaries(calleeDescriptor, state)
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()
}
private fun getMetadataParameterIndex(calleeDescriptor: FunctionDescriptor): Int {
assert(calleeDescriptor.valueParameters.size >= 2) {
"Unexpected delegated property operator (should have at least 2 value parameters): $calleeDescriptor"
}
var index = 0
calleeDescriptor.dispatchReceiverParameter?.let {
index += typeMapper.mapType(it.type).size
}
calleeDescriptor.extensionReceiverParameter?.let {
index += typeMapper.mapType(it.type).size
}
index += typeMapper.mapType(calleeDescriptor.valueParameters[0].type).size
return index
}
}
@@ -49,8 +49,7 @@ import static org.jetbrains.kotlin.codegen.AsmUtil.getVisibilityForBackingField;
import static org.jetbrains.kotlin.codegen.FunctionCodegen.processInterfaceMethod;
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isConstOrHasJvmFieldAnnotation;
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isJvmInterface;
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.DELEGATED_PROPERTIES_WITH_METADATA;
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.DELEGATED_PROPERTY_METADATA_OWNER;
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.*;
import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.*;
import static org.jetbrains.kotlin.diagnostics.Errors.EXPECTED_FUNCTION_SOURCE_WITH_DEFAULT_ARGUMENTS_NOT_FOUND;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject;
@@ -569,6 +568,10 @@ public class PropertyCodegen {
@NotNull VariableDescriptorWithAccessors descriptor,
@NotNull BindingContext bindingContext
) {
if (Boolean.TRUE == bindingContext.get(DELEGATED_PROPERTY_WITH_OPTIMIZED_METADATA, descriptor)) {
return StackValue.constant(null, K_PROPERTY_TYPE);
}
Type owner = bindingContext.get(DELEGATED_PROPERTY_METADATA_OWNER, descriptor);
assert owner != null : "Delegated property owner not found: " + descriptor;
@@ -88,6 +88,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
private final SwitchCodegenProvider switchCodegenProvider;
private final LanguageVersionSettings languageVersionSettings;
private final ClassBuilderMode classBuilderMode;
private final DelegatedPropertiesCodegenHelper delegatedPropertiesCodegenHelper;
public CodegenAnnotatingVisitor(@NotNull GenerationState state) {
this.bindingTrace = state.getBindingTrace();
@@ -97,6 +98,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
this.switchCodegenProvider = new SwitchCodegenProvider(state);
this.languageVersionSettings = state.getLanguageVersionSettings();
this.classBuilderMode = state.getClassBuilderMode();
this.delegatedPropertiesCodegenHelper = new DelegatedPropertiesCodegenHelper(state);
}
@NotNull
@@ -509,15 +511,20 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
ClassDescriptor classDescriptor = recordClassForCallable(delegate, variableDescriptor, Collections.singleton(supertype), name);
recordClosure(classDescriptor, name);
Type containerType = getMetadataOwner(property);
List<VariableDescriptorWithAccessors> descriptors = bindingTrace.get(DELEGATED_PROPERTIES_WITH_METADATA, containerType);
if (descriptors == null) {
descriptors = new ArrayList<>(1);
bindingTrace.record(DELEGATED_PROPERTIES_WITH_METADATA, containerType, descriptors);
}
descriptors.add(variableDescriptor);
if (delegatedPropertiesCodegenHelper.isDelegatedPropertyMetadataRequired(variableDescriptor)) {
Type containerType = getMetadataOwner(property);
List<VariableDescriptorWithAccessors> descriptors = bindingTrace.get(DELEGATED_PROPERTIES_WITH_METADATA, containerType);
if (descriptors == null) {
descriptors = new ArrayList<>(1);
bindingTrace.record(DELEGATED_PROPERTIES_WITH_METADATA, containerType, descriptors);
}
descriptors.add(variableDescriptor);
bindingTrace.record(DELEGATED_PROPERTY_METADATA_OWNER, variableDescriptor, containerType);
bindingTrace.record(DELEGATED_PROPERTY_METADATA_OWNER, variableDescriptor, containerType);
}
else {
bindingTrace.record(DELEGATED_PROPERTY_WITH_OPTIMIZED_METADATA, variableDescriptor);
}
}
super.visitProperty(property);
@@ -75,6 +75,10 @@ public class CodegenBinding {
Slices.createSimpleSlice();
public static final WritableSlice<VariableDescriptor, VariableDescriptor> LOCAL_VARIABLE_PROPERTY_METADATA =
Slices.createSimpleSlice();
public static final WritableSlice<FunctionDescriptor, Boolean> PROPERTY_METADATA_REQUIRED_FOR_OPERATOR_CALL =
Slices.createSimpleSlice();
public static final WritableSlice<VariableDescriptorWithAccessors, Boolean> DELEGATED_PROPERTY_WITH_OPTIMIZED_METADATA =
Slices.createSimpleSlice();
public static final WritableSlice<FunctionDescriptor, String> CALL_LABEL_FOR_LAMBDA_ARGUMENT = Slices.createSimpleSlice();
@@ -534,12 +534,41 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
else
mangleSuspendInlineFunctionAsmMethodIfNeeded(functionDescriptor, jvmSignature.asmMethod)
val methodId = MethodId(methodOwner.internalName, asmMethod)
val directMember = getDirectMemberAndCallableFromObject(functionDescriptor)
if (!isBuiltInArrayIntrinsic(functionDescriptor) && directMember !is DescriptorWithContainerSource) {
return sourceCompilerForInline.doCreateMethodNodeFromSource(functionDescriptor, jvmSignature, callDefault, asmMethod)
}
return getCompiledMethodNodeInner(functionDescriptor, directMember, asmMethod, methodOwner, state, jvmSignature)
}
internal fun createSpecialInlineMethodNodeFromBinaries(functionDescriptor: FunctionDescriptor, state: GenerationState): MethodNode {
val directMember = getDirectMemberAndCallableFromObject(functionDescriptor)
assert(directMember is DescriptorWithContainerSource) {
"Function is not in binaries: $functionDescriptor"
}
assert(directMember is FunctionDescriptor && directMember.isOperator) {
"Operator function expected: $directMember"
}
val methodOwner = state.typeMapper.mapImplementationOwner(functionDescriptor)
val jvmSignature = state.typeMapper.mapSignatureWithGeneric(functionDescriptor, OwnerKind.IMPLEMENTATION)
val asmMethod = mangleSuspendInlineFunctionAsmMethodIfNeeded(functionDescriptor, jvmSignature.asmMethod)
return getCompiledMethodNodeInner(functionDescriptor, directMember, asmMethod, methodOwner, state, jvmSignature).node
}
private fun getCompiledMethodNodeInner(
functionDescriptor: FunctionDescriptor,
directMember: CallableMemberDescriptor,
asmMethod: Method,
methodOwner: Type,
state: GenerationState,
jvmSignature: JvmMethodSignature
): SMAPAndMethodNode {
val methodId = MethodId(methodOwner.internalName, asmMethod)
val resultInCache = state.inlineCache.methodNodeById.getOrPut(methodId) {
val result = doCreateMethodNodeFromCompiled(directMember, state, asmMethod)
?: if (functionDescriptor.isSuspend)