Support default bound callable reference inlining
#KT-6884 Fixed
This commit is contained in:
@@ -61,7 +61,7 @@ open class FieldRemapper(
|
||||
|
||||
val insnNode = capturedFieldAccess[currentInstruction] as FieldInsnNode
|
||||
if (canProcess(insnNode.owner, insnNode.name, true)) {
|
||||
insnNode.name = "$$$" + insnNode.name
|
||||
insnNode.name = InlineCodegenUtil.CAPTURED_FIELD_FOLD_PREFIX + getFieldNameForFolding(insnNode)
|
||||
insnNode.opcode = Opcodes.GETSTATIC
|
||||
|
||||
node.remove(InsnSequence(capturedFieldAccess[0], insnNode))
|
||||
@@ -71,6 +71,8 @@ open class FieldRemapper(
|
||||
return null
|
||||
}
|
||||
|
||||
protected open fun getFieldNameForFolding(insnNode: FieldInsnNode): String = insnNode.name
|
||||
|
||||
@JvmOverloads
|
||||
open fun findField(fieldInsnNode: FieldInsnNode, captured: Collection<CapturedParamInfo> = parameters.captured): CapturedParamInfo? {
|
||||
for (valueDescriptor in captured) {
|
||||
|
||||
@@ -820,7 +820,7 @@ public class InlineCodegen extends CallGenerator {
|
||||
private void putClosureParametersOnStack() {
|
||||
for (LambdaInfo next : expressionMap.values()) {
|
||||
//closure parameters for bounded callable references are generated inplace
|
||||
if (next.isBoundCallableReference) continue;
|
||||
if (next instanceof ExpressionLambda && next.isBoundCallableReference()) continue;
|
||||
putClosureParametersOnStack(next, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,25 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.inline
|
||||
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
|
||||
|
||||
class InlinedLambdaRemapper(
|
||||
lambdaInternalName: String,
|
||||
originalLambdaInternalName: String,
|
||||
parent: FieldRemapper,
|
||||
methodParams: Parameters
|
||||
) : FieldRemapper(lambdaInternalName, parent, methodParams) {
|
||||
methodParams: Parameters,
|
||||
val isDefaultBoundCallableReference: Boolean
|
||||
) : FieldRemapper(originalLambdaInternalName, parent, methodParams) {
|
||||
|
||||
public override fun canProcess(fieldOwner: String, fieldName: String, isFolding: Boolean) =
|
||||
isFolding && super.canProcess(fieldOwner, fieldName, true)
|
||||
isFolding && (isMyBoundReceiverForDefaultLambda(fieldOwner, fieldName) || super.canProcess(fieldOwner, fieldName, true))
|
||||
|
||||
private fun isMyBoundReceiverForDefaultLambda(fieldOwner: String, fieldName: String) =
|
||||
isDefaultBoundCallableReference && fieldName == AsmUtil.BOUND_REFERENCE_RECEIVER && fieldOwner == originalLambdaInternalName
|
||||
|
||||
override fun getFieldNameForFolding(insnNode: FieldInsnNode): String =
|
||||
if (isMyBoundReceiverForDefaultLambda(insnNode.owner, insnNode.name)) AsmUtil.RECEIVER_NAME else insnNode.name
|
||||
|
||||
override fun findField(fieldInsnNode: FieldInsnNode, captured: Collection<CapturedParamInfo>) =
|
||||
parent!!.findField(fieldInsnNode, captured)
|
||||
|
||||
@@ -39,8 +39,11 @@ import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
abstract class LambdaInfo(@JvmField val isCrossInline: Boolean, @JvmField val isBoundCallableReference: Boolean) : LabelOwner {
|
||||
abstract class LambdaInfo(@JvmField val isCrossInline: Boolean) : LabelOwner {
|
||||
|
||||
abstract val isBoundCallableReference: Boolean
|
||||
|
||||
abstract val lambdaClassType: Type
|
||||
|
||||
@@ -85,7 +88,10 @@ class DefaultLambda(
|
||||
val parameterDescriptor: ValueParameterDescriptor,
|
||||
val offset: Int,
|
||||
val needReification: Boolean
|
||||
) : LambdaInfo(parameterDescriptor.isCrossinline, false) {
|
||||
) : LambdaInfo(parameterDescriptor.isCrossinline) {
|
||||
|
||||
override var isBoundCallableReference by Delegates.notNull<Boolean>()
|
||||
private set
|
||||
|
||||
val parameterOffsetsInDefault: MutableList<Int> = arrayListOf()
|
||||
|
||||
@@ -132,11 +138,18 @@ class DefaultLambda(
|
||||
"Can't find non-default constructor <init>$descriptor for default lambda $lambdaClassType"
|
||||
}
|
||||
|
||||
capturedVars = constructor?.findCapturedFieldAssignmentInstructions()?.map {
|
||||
fieldNode ->
|
||||
capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc))
|
||||
}?.toList() ?: emptyList()
|
||||
capturedVars =
|
||||
if (isFunctionReference || isPropertyReference)
|
||||
constructor?.desc?.let { Type.getArgumentTypes(it) }?.singleOrNull()?.let {
|
||||
listOf(capturedParamDesc(AsmUtil.RECEIVER_NAME, it))
|
||||
} ?: emptyList()
|
||||
else
|
||||
constructor?.findCapturedFieldAssignmentInstructions()?.map {
|
||||
fieldNode ->
|
||||
capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc))
|
||||
}?.toList() ?: emptyList()
|
||||
|
||||
isBoundCallableReference = (isFunctionReference || isPropertyReference) && capturedVars.isNotEmpty()
|
||||
|
||||
invokeMethod = Method(
|
||||
(if (isPropertyReference) OperatorNameConventions.GET else OperatorNameConventions.INVOKE).asString(),
|
||||
@@ -160,8 +173,8 @@ class ExpressionLambda(
|
||||
expression: KtExpression,
|
||||
private val typeMapper: KotlinTypeMapper,
|
||||
isCrossInline: Boolean,
|
||||
isBoundCallableReference: Boolean
|
||||
) : LambdaInfo(isCrossInline, isBoundCallableReference) {
|
||||
override val isBoundCallableReference: Boolean
|
||||
) : LambdaInfo(isCrossInline) {
|
||||
|
||||
override val lambdaClassType: Type
|
||||
|
||||
@@ -186,7 +199,7 @@ class ExpressionLambda(
|
||||
val variableDescriptor =
|
||||
bindingContext.get(BindingContext.VARIABLE, functionWithBodyOrCallableReference) as? VariableDescriptorWithAccessors ?:
|
||||
throw AssertionError("""Reference expression not resolved to variable descriptor with accessors: ${expression.getText()}""")
|
||||
classDescriptor = CodegenBinding.anonymousClassForCallable(bindingContext, variableDescriptor!!)
|
||||
classDescriptor = CodegenBinding.anonymousClassForCallable(bindingContext, variableDescriptor)
|
||||
lambdaClassType = typeMapper.mapClass(classDescriptor)
|
||||
val getFunction = PropertyReferenceCodegen.findGetFunction(variableDescriptor)
|
||||
invokeMethodDescriptor = PropertyReferenceCodegen.createFakeOpenDescriptor(getFunction, classDescriptor)
|
||||
|
||||
@@ -204,7 +204,10 @@ class MethodInliner(
|
||||
addInlineMarker(this, true)
|
||||
val lambdaParameters = info.addAllParameters(nodeRemapper)
|
||||
|
||||
val newCapturedRemapper = InlinedLambdaRemapper(info.lambdaClassType.internalName, nodeRemapper, lambdaParameters)
|
||||
val newCapturedRemapper = InlinedLambdaRemapper(
|
||||
info.lambdaClassType.internalName, nodeRemapper, lambdaParameters,
|
||||
info is DefaultLambda && info.isBoundCallableReference
|
||||
)
|
||||
|
||||
setLambdaInlining(true)
|
||||
val lambdaSMAP = info.node.classSMAP
|
||||
@@ -256,7 +259,7 @@ class MethodInliner(
|
||||
for (capturedParamDesc in info.allRecapturedParameters) {
|
||||
visitFieldInsn(
|
||||
Opcodes.GETSTATIC, capturedParamDesc.containingLambdaName,
|
||||
"$$$" + capturedParamDesc.fieldName, capturedParamDesc.type.descriptor
|
||||
CAPTURED_FIELD_FOLD_PREFIX + capturedParamDesc.fieldName, capturedParamDesc.type.descriptor
|
||||
)
|
||||
}
|
||||
super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf)
|
||||
@@ -347,7 +350,7 @@ class MethodInliner(
|
||||
lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach {
|
||||
(_, captured) ->
|
||||
super.visitFieldInsn(
|
||||
Opcodes.PUTSTATIC, captured.containingLambdaName, "$$$" + captured.fieldName, captured.type.descriptor
|
||||
Opcodes.PUTSTATIC, captured.containingLambdaName, CAPTURED_FIELD_FOLD_PREFIX + captured.fieldName, captured.type.descriptor
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -570,7 +573,7 @@ class MethodInliner(
|
||||
return when {
|
||||
insnNode.opcode == Opcodes.ALOAD ->
|
||||
getLambdaIfExists((insnNode as VarInsnNode).`var`)
|
||||
insnNode is FieldInsnNode && insnNode.name.startsWith("$$$") ->
|
||||
insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) ->
|
||||
findCapturedField(insnNode, nodeRemapper).lambda
|
||||
else ->
|
||||
null
|
||||
@@ -706,7 +709,9 @@ class MethodInliner(
|
||||
|
||||
@JvmStatic
|
||||
fun findCapturedField(node: FieldInsnNode, fieldRemapper: FieldRemapper): CapturedParamInfo {
|
||||
assert(node.name.startsWith("$$$")) { "Captured field template should start with $$$ prefix" }
|
||||
assert(node.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX)) {
|
||||
"Captured field template should start with $CAPTURED_FIELD_FOLD_PREFIX prefix"
|
||||
}
|
||||
val fin = FieldInsnNode(node.opcode, node.owner, node.name.substring(3), node.desc)
|
||||
val field = fieldRemapper.findField(fin) ?: throw IllegalStateException(
|
||||
"Couldn't find captured field ${node.owner}.${node.name} in ${fieldRemapper.originalLambdaInternalName}"
|
||||
|
||||
@@ -59,7 +59,8 @@ public class RemapVisitor extends MethodBodyVisitor {
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
|
||||
if (name.startsWith("$$$") && (nodeRemapper instanceof RegeneratedLambdaFieldRemapper || nodeRemapper.isRoot())) {
|
||||
if (name.startsWith(InlineCodegenUtil.CAPTURED_FIELD_FOLD_PREFIX) &&
|
||||
(nodeRemapper instanceof RegeneratedLambdaFieldRemapper || nodeRemapper.isRoot())) {
|
||||
FieldInsnNode fin = new FieldInsnNode(opcode, owner, name, desc);
|
||||
StackValue inline = nodeRemapper.getFieldForInline(fin, null);
|
||||
assert inline != null : "Captured field should have not null stackValue " + fin;
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// FILE: 1.kt
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
// SKIP_INLINE_CHECK_IN: inlineFun$default
|
||||
package test
|
||||
|
||||
class A(val value: String) {
|
||||
fun ok() = value
|
||||
}
|
||||
|
||||
inline fun inlineFun(a: A, lambda: () -> String = a::ok): String {
|
||||
return lambda()
|
||||
}
|
||||
|
||||
// FILE: 2.kt
|
||||
|
||||
import test.*
|
||||
|
||||
fun box(): String {
|
||||
return inlineFun(A("OK"))
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// FILE: 1.kt
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
// SKIP_INLINE_CHECK_IN: inlineFun$default
|
||||
package test
|
||||
|
||||
class A(val ok: String)
|
||||
|
||||
inline fun inlineFun(a: A, lambda: () -> String = a::ok): String {
|
||||
return lambda()
|
||||
}
|
||||
|
||||
// FILE: 2.kt
|
||||
|
||||
import test.*
|
||||
|
||||
fun box(): String {
|
||||
return inlineFun(A("OK"))
|
||||
}
|
||||
@@ -961,6 +961,18 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("boundFunctionReference.kt")
|
||||
public void testBoundFunctionReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("boundPropertyReference.kt")
|
||||
public void testBoundPropertyReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultLambdaInNoInline.kt")
|
||||
public void testDefaultLambdaInNoInline() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
|
||||
|
||||
+12
@@ -961,6 +961,18 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("boundFunctionReference.kt")
|
||||
public void testBoundFunctionReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("boundPropertyReference.kt")
|
||||
public void testBoundPropertyReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultLambdaInNoInline.kt")
|
||||
public void testDefaultLambdaInNoInline() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
|
||||
|
||||
Reference in New Issue
Block a user