Support default bound callable reference inlining

#KT-6884 Fixed
This commit is contained in:
Mikhael Bogdanov
2017-05-16 11:02:30 +02:00
parent 8a083a41d5
commit fd561c6cb9
10 changed files with 112 additions and 21 deletions
@@ -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;
@@ -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"))
}
@@ -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");
@@ -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");