JVM: rename this$0 when regenerating nested objects too

In the old backend, this was unnecessary because nested objects would
reference their lambdas' captures through the original this$0. On
JVM_IR, using loose capture fields means a name/descriptor clash can
occur on any level of nesting, not just the top.
This commit is contained in:
pyos
2021-02-03 14:52:14 +01:00
committed by max-kammerer
parent ec89cb2313
commit 1310a65f0c
13 changed files with 180 additions and 56 deletions
@@ -462,6 +462,29 @@ class AnonymousObjectTransformer(
val indexToFunctionalArgument = transformationInfo.functionalArguments
val capturedParams = HashSet<Int>()
// Possible cases where we need to add each lambda's captures separately:
//
// 1. Top-level object in an inline lambda that is *not* being inlined into another object. In this case, we
// have no choice but to add a separate field for each captured variable. `capturedLambdas` is either empty
// (already have the fields) or contains the parent lambda object (captures used to be read from it, but
// the object will be removed and its contents inlined).
//
// 2. Top-level object in a named inline function. Again, there's no option but to add separate fields.
// `capturedLambdas` contains all lambdas used by this object and nested objects.
//
// 3. Nested object, either in an inline lambda or an inline function. This case has two subcases:
// * The object's captures are passed as separate arguments (e.g. KT-28064 style object that used to be in a lambda);
// we *could* group them into `this$0` now, but choose not to. Lambdas are replaced by their captures to match.
// * The object's captures are already grouped into `this$0`; this includes captured lambda parameters (for objects in
// inline functions) and a reference to the outer object or lambda (for objects in lambdas), so `capturedLambdas` is
// empty anyway.
//
// The only remaining case is a top-level object inside a (crossinline) lambda that is inlined into another object.
// Then, the reference to the soon-to-be-removed lambda class containing the captures (and it exists, or else the object
// would not have needed regeneration in the first place) is simply replaced with a reference to the outer object, and
// that object will contain loose fields for everything we need to capture.
val topLevelInCrossinlineLambda = parentFieldRemapper is InlinedLambdaRemapper && !parentFieldRemapper.parent!!.isRoot
//load captured parameters and patch instruction list
// NB: there is also could be object fields
val toDelete = arrayListOf<AbstractInsnNode>()
@@ -470,10 +493,12 @@ class AnonymousObjectTransformer(
val parameterAload = fieldNode.previous as VarInsnNode
val varIndex = parameterAload.`var`
val functionalArgument = indexToFunctionalArgument[varIndex]
val newFieldName = if (isThis0(fieldName) && shouldRenameThis0(parentFieldRemapper, indexToFunctionalArgument.values))
getNewFieldName(fieldName, true)
else
fieldName
// If an outer `this` is already captured by this object, rename it if any inline lambda will capture
// one of the same type, causing the code below to create a clash. Note that the values can be different.
// TODO: this is only really necessary if there will be a name *and* type clash.
val shouldRename = !topLevelInCrossinlineLambda && isThis0(fieldName) &&
indexToFunctionalArgument.values.any { it is LambdaInfo && it.capturedVars.any { it.fieldName == fieldName } }
val newFieldName = if (shouldRename) addUniqueField(fieldName + INLINE_FUN_THIS_0_SUFFIX) else fieldName
val info = capturedParamBuilder.addCapturedParam(
Type.getObjectType(transformationInfo.oldClassName), fieldName, newFieldName,
Type.getType(fieldNode.desc), functionalArgument is LambdaInfo, null
@@ -508,35 +533,17 @@ class AnonymousObjectTransformer(
//For all inlined lambdas add their captured parameters
//TODO: some of such parameters could be skipped - we should perform additional analysis
val allRecapturedParameters = ArrayList<CapturedParamDesc>()
if (parentFieldRemapper !is InlinedLambdaRemapper || parentFieldRemapper.parent!!.isRoot) {
// Possible cases:
//
// 1. Top-level object in an inline lambda that is *not* being inlined into another object. In this case, we
// have no choice but to add a separate field for each captured variable. `capturedLambdas` is either empty
// (already have the fields) or contains the parent lambda object (captures used to be read from it, but
// the object will be removed and its contents inlined).
//
// 2. Top-level object in a named inline function. Again, there's no option but to add separate fields.
// `capturedLambdas` contains all lambdas used by this object and nested objects.
//
// 3. Nested object, either in an inline lambda or an inline function. This case has two subcases:
// * The object's captures are passed as separate arguments (e.g. KT-28064 style object that used to be in a lambda);
// we could group them into `this$0` now, but choose not to. Lambdas are replaced by their captures.
// * The object's captures are already grouped into `this$0`; this includes captured lambda parameters (for objects in
// inline functions) and a reference to the outer object or lambda (for objects in lambdas), so `capturedLambdas` is
// empty and the choice doesn't matter.
//
val alreadyAdded = HashMap<String, CapturedParamInfo>()
if (!topLevelInCrossinlineLambda) {
val capturedOuterThisTypes = mutableSetOf<String>()
for (info in capturedLambdas) {
for (desc in info.capturedVars) {
val key = desc.fieldName + "$$$" + desc.type.className
val alreadyAddedParam = alreadyAdded[key]
val recapturedParamInfo = capturedParamBuilder.addCapturedParam(
desc,
alreadyAddedParam?.newFieldName ?: getNewFieldName(desc.fieldName, false),
alreadyAddedParam != null
)
// Merge all outer `this` of the same type captured by inlined lambdas, since they have to be the same
// object. Outer `this` captured by the original object itself should have been renamed above,
// and can have a different value even if the same type is captured by a lambda.
val recapturedParamInfo = if (isThis0(desc.fieldName))
capturedParamBuilder.addCapturedParam(desc, desc.fieldName, !capturedOuterThisTypes.add(desc.type.className))
else
capturedParamBuilder.addCapturedParam(desc, addUniqueField(desc.fieldName + INLINE_TRANSFORMATION_SUFFIX), false)
if (info is ExpressionLambda && info.isCapturedSuspend(desc)) {
recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend
}
@@ -551,10 +558,6 @@ class AnonymousObjectTransformer(
allRecapturedParameters.add(desc)
constructorParamBuilder.addCapturedParam(recapturedParamInfo, recapturedParamInfo.newFieldName).remapValue = composed
if (isThis0(desc.fieldName)) {
alreadyAdded.put(key, recapturedParamInfo)
}
}
}
} else if (capturedLambdas.isNotEmpty()) {
@@ -579,24 +582,6 @@ class AnonymousObjectTransformer(
return constructorAdditionalFakeParams
}
private fun shouldRenameThis0(parentFieldRemapper: FieldRemapper, values: Collection<FunctionalArgument>): Boolean {
return if (isFirstDeclSiteLambdaFieldRemapper(parentFieldRemapper)) {
values.any { it is LambdaInfo && it.capturedVars.any { isThis0(it.fieldName) } }
} else false
}
private fun getNewFieldName(oldName: String, originalField: Boolean): String {
if (AsmUtil.CAPTURED_THIS_FIELD == oldName) {
return if (!originalField) {
oldName
} else {
//rename original 'this$0' in declaration site lambda (inside inline function) to use this$0 only for outer lambda/object access on call site
addUniqueField(oldName + INLINE_FUN_THIS_0_SUFFIX)
}
}
return addUniqueField(oldName + INLINE_TRANSFORMATION_SUFFIX)
}
private fun addUniqueField(name: String): String {
val existNames = fieldNames.getOrPut(name) { LinkedList() }
val suffix = if (existNames.isEmpty()) "" else "$" + existNames.size
@@ -604,7 +589,4 @@ class AnonymousObjectTransformer(
existNames.add(newName)
return newName
}
private fun isFirstDeclSiteLambdaFieldRemapper(parentRemapper: FieldRemapper): Boolean =
parentRemapper !is RegeneratedLambdaFieldRemapper && parentRemapper !is InlinedLambdaRemapper
}
@@ -647,6 +647,18 @@ public class FirBlackBoxInlineCodegenTestGenerated extends AbstractFirBlackBoxIn
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -0,0 +1,14 @@
// FILE: 1.kt
package test
class C(val x: String) {
fun f(y: String) = C(y).g { x }
inline fun g(crossinline h: () -> String) =
{ { h() + x }() }()
}
// FILE: 2.kt
import test.*
fun box() = C("O").f("K")
@@ -0,0 +1,14 @@
// FILE: 1.kt
package test
class C(val x: String) {
inline fun f(crossinline h: () -> String) = C("").g { x + h() }
inline fun g(crossinline h: () -> String) =
{ { h() + x }() }()
}
// FILE: 2.kt
import test.*
fun box() = C("O").f { "K" }
@@ -647,6 +647,18 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -647,6 +647,18 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -647,6 +647,18 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -647,6 +647,18 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -647,6 +647,18 @@ public class JvmIrAgainstOldBoxInlineTestGenerated extends AbstractJvmIrAgainstO
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -647,6 +647,18 @@ public class JvmOldAgainstIrBoxInlineTestGenerated extends AbstractJvmOldAgainst
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@Test
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@Test
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@Test
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
@@ -506,6 +506,16 @@ public class IrJsCodegenInlineES6TestGenerated extends AbstractIrJsCodegenInline
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/twoDifferentDispatchReceivers.kt");
@@ -506,6 +506,16 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/twoDifferentDispatchReceivers.kt");
@@ -506,6 +506,16 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_3.kt");
}
@TestMetadata("kt8668_nested.kt")
public void testKt8668_nested() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested.kt");
}
@TestMetadata("kt8668_nested_2.kt")
public void testKt8668_nested_2() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/kt8668_nested_2.kt");
}
@TestMetadata("twoDifferentDispatchReceivers.kt")
public void testTwoDifferentDispatchReceivers() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers/twoDifferentDispatchReceivers.kt");