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:
committed by
Dmitry Petrov
parent
92ba298e68
commit
a633a33627
+110
@@ -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;
|
||||
|
||||
|
||||
+15
-8
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user