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
|
val insnNode = capturedFieldAccess[currentInstruction] as FieldInsnNode
|
||||||
if (canProcess(insnNode.owner, insnNode.name, true)) {
|
if (canProcess(insnNode.owner, insnNode.name, true)) {
|
||||||
insnNode.name = "$$$" + insnNode.name
|
insnNode.name = InlineCodegenUtil.CAPTURED_FIELD_FOLD_PREFIX + getFieldNameForFolding(insnNode)
|
||||||
insnNode.opcode = Opcodes.GETSTATIC
|
insnNode.opcode = Opcodes.GETSTATIC
|
||||||
|
|
||||||
node.remove(InsnSequence(capturedFieldAccess[0], insnNode))
|
node.remove(InsnSequence(capturedFieldAccess[0], insnNode))
|
||||||
@@ -71,6 +71,8 @@ open class FieldRemapper(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun getFieldNameForFolding(insnNode: FieldInsnNode): String = insnNode.name
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
open fun findField(fieldInsnNode: FieldInsnNode, captured: Collection<CapturedParamInfo> = parameters.captured): CapturedParamInfo? {
|
open fun findField(fieldInsnNode: FieldInsnNode, captured: Collection<CapturedParamInfo> = parameters.captured): CapturedParamInfo? {
|
||||||
for (valueDescriptor in captured) {
|
for (valueDescriptor in captured) {
|
||||||
|
|||||||
@@ -820,7 +820,7 @@ public class InlineCodegen extends CallGenerator {
|
|||||||
private void putClosureParametersOnStack() {
|
private void putClosureParametersOnStack() {
|
||||||
for (LambdaInfo next : expressionMap.values()) {
|
for (LambdaInfo next : expressionMap.values()) {
|
||||||
//closure parameters for bounded callable references are generated inplace
|
//closure parameters for bounded callable references are generated inplace
|
||||||
if (next.isBoundCallableReference) continue;
|
if (next instanceof ExpressionLambda && next.isBoundCallableReference()) continue;
|
||||||
putClosureParametersOnStack(next, null);
|
putClosureParametersOnStack(next, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,25 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.inline
|
package org.jetbrains.kotlin.codegen.inline
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
import org.jetbrains.kotlin.codegen.StackValue
|
import org.jetbrains.kotlin.codegen.StackValue
|
||||||
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
|
||||||
|
|
||||||
class InlinedLambdaRemapper(
|
class InlinedLambdaRemapper(
|
||||||
lambdaInternalName: String,
|
originalLambdaInternalName: String,
|
||||||
parent: FieldRemapper,
|
parent: FieldRemapper,
|
||||||
methodParams: Parameters
|
methodParams: Parameters,
|
||||||
) : FieldRemapper(lambdaInternalName, parent, methodParams) {
|
val isDefaultBoundCallableReference: Boolean
|
||||||
|
) : FieldRemapper(originalLambdaInternalName, parent, methodParams) {
|
||||||
|
|
||||||
public override fun canProcess(fieldOwner: String, fieldName: String, isFolding: Boolean) =
|
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>) =
|
override fun findField(fieldInsnNode: FieldInsnNode, captured: Collection<CapturedParamInfo>) =
|
||||||
parent!!.findField(fieldInsnNode, captured)
|
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.commons.Method
|
||||||
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
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
|
abstract val lambdaClassType: Type
|
||||||
|
|
||||||
@@ -85,7 +88,10 @@ class DefaultLambda(
|
|||||||
val parameterDescriptor: ValueParameterDescriptor,
|
val parameterDescriptor: ValueParameterDescriptor,
|
||||||
val offset: Int,
|
val offset: Int,
|
||||||
val needReification: Boolean
|
val needReification: Boolean
|
||||||
) : LambdaInfo(parameterDescriptor.isCrossinline, false) {
|
) : LambdaInfo(parameterDescriptor.isCrossinline) {
|
||||||
|
|
||||||
|
override var isBoundCallableReference by Delegates.notNull<Boolean>()
|
||||||
|
private set
|
||||||
|
|
||||||
val parameterOffsetsInDefault: MutableList<Int> = arrayListOf()
|
val parameterOffsetsInDefault: MutableList<Int> = arrayListOf()
|
||||||
|
|
||||||
@@ -132,11 +138,18 @@ class DefaultLambda(
|
|||||||
"Can't find non-default constructor <init>$descriptor for default lambda $lambdaClassType"
|
"Can't find non-default constructor <init>$descriptor for default lambda $lambdaClassType"
|
||||||
}
|
}
|
||||||
|
|
||||||
capturedVars = constructor?.findCapturedFieldAssignmentInstructions()?.map {
|
capturedVars =
|
||||||
fieldNode ->
|
if (isFunctionReference || isPropertyReference)
|
||||||
capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc))
|
constructor?.desc?.let { Type.getArgumentTypes(it) }?.singleOrNull()?.let {
|
||||||
}?.toList() ?: emptyList()
|
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(
|
invokeMethod = Method(
|
||||||
(if (isPropertyReference) OperatorNameConventions.GET else OperatorNameConventions.INVOKE).asString(),
|
(if (isPropertyReference) OperatorNameConventions.GET else OperatorNameConventions.INVOKE).asString(),
|
||||||
@@ -160,8 +173,8 @@ class ExpressionLambda(
|
|||||||
expression: KtExpression,
|
expression: KtExpression,
|
||||||
private val typeMapper: KotlinTypeMapper,
|
private val typeMapper: KotlinTypeMapper,
|
||||||
isCrossInline: Boolean,
|
isCrossInline: Boolean,
|
||||||
isBoundCallableReference: Boolean
|
override val isBoundCallableReference: Boolean
|
||||||
) : LambdaInfo(isCrossInline, isBoundCallableReference) {
|
) : LambdaInfo(isCrossInline) {
|
||||||
|
|
||||||
override val lambdaClassType: Type
|
override val lambdaClassType: Type
|
||||||
|
|
||||||
@@ -186,7 +199,7 @@ class ExpressionLambda(
|
|||||||
val variableDescriptor =
|
val variableDescriptor =
|
||||||
bindingContext.get(BindingContext.VARIABLE, functionWithBodyOrCallableReference) as? VariableDescriptorWithAccessors ?:
|
bindingContext.get(BindingContext.VARIABLE, functionWithBodyOrCallableReference) as? VariableDescriptorWithAccessors ?:
|
||||||
throw AssertionError("""Reference expression not resolved to variable descriptor with accessors: ${expression.getText()}""")
|
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)
|
lambdaClassType = typeMapper.mapClass(classDescriptor)
|
||||||
val getFunction = PropertyReferenceCodegen.findGetFunction(variableDescriptor)
|
val getFunction = PropertyReferenceCodegen.findGetFunction(variableDescriptor)
|
||||||
invokeMethodDescriptor = PropertyReferenceCodegen.createFakeOpenDescriptor(getFunction, classDescriptor)
|
invokeMethodDescriptor = PropertyReferenceCodegen.createFakeOpenDescriptor(getFunction, classDescriptor)
|
||||||
|
|||||||
@@ -204,7 +204,10 @@ class MethodInliner(
|
|||||||
addInlineMarker(this, true)
|
addInlineMarker(this, true)
|
||||||
val lambdaParameters = info.addAllParameters(nodeRemapper)
|
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)
|
setLambdaInlining(true)
|
||||||
val lambdaSMAP = info.node.classSMAP
|
val lambdaSMAP = info.node.classSMAP
|
||||||
@@ -256,7 +259,7 @@ class MethodInliner(
|
|||||||
for (capturedParamDesc in info.allRecapturedParameters) {
|
for (capturedParamDesc in info.allRecapturedParameters) {
|
||||||
visitFieldInsn(
|
visitFieldInsn(
|
||||||
Opcodes.GETSTATIC, capturedParamDesc.containingLambdaName,
|
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)
|
super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf)
|
||||||
@@ -347,7 +350,7 @@ class MethodInliner(
|
|||||||
lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach {
|
lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach {
|
||||||
(_, captured) ->
|
(_, captured) ->
|
||||||
super.visitFieldInsn(
|
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 {
|
return when {
|
||||||
insnNode.opcode == Opcodes.ALOAD ->
|
insnNode.opcode == Opcodes.ALOAD ->
|
||||||
getLambdaIfExists((insnNode as VarInsnNode).`var`)
|
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
|
findCapturedField(insnNode, nodeRemapper).lambda
|
||||||
else ->
|
else ->
|
||||||
null
|
null
|
||||||
@@ -706,7 +709,9 @@ class MethodInliner(
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun findCapturedField(node: FieldInsnNode, fieldRemapper: FieldRemapper): CapturedParamInfo {
|
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 fin = FieldInsnNode(node.opcode, node.owner, node.name.substring(3), node.desc)
|
||||||
val field = fieldRemapper.findField(fin) ?: throw IllegalStateException(
|
val field = fieldRemapper.findField(fin) ?: throw IllegalStateException(
|
||||||
"Couldn't find captured field ${node.owner}.${node.name} in ${fieldRemapper.originalLambdaInternalName}"
|
"Couldn't find captured field ${node.owner}.${node.name} in ${fieldRemapper.originalLambdaInternalName}"
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ public class RemapVisitor extends MethodBodyVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
|
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);
|
FieldInsnNode fin = new FieldInsnNode(opcode, owner, name, desc);
|
||||||
StackValue inline = nodeRemapper.getFieldForInline(fin, null);
|
StackValue inline = nodeRemapper.getFieldForInline(fin, null);
|
||||||
assert inline != null : "Captured field should have not null stackValue " + fin;
|
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);
|
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")
|
@TestMetadata("defaultLambdaInNoInline.kt")
|
||||||
public void testDefaultLambdaInNoInline() throws Exception {
|
public void testDefaultLambdaInNoInline() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
|
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);
|
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")
|
@TestMetadata("defaultLambdaInNoInline.kt")
|
||||||
public void testDefaultLambdaInNoInline() throws Exception {
|
public void testDefaultLambdaInNoInline() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user