JVM: fix remapping of new T inside regenerated copies of T

In general, `InliningContext.findAnonymousTransformationInfo` was not
reliable because it mapped each type to *some* info for that type,
preferring ones with `shouldRegenerate == true` if those exist. Thus, it
returned incorrect results if one type was regenerated multiple times,
e.g. in a nested inlining context or because of a `finally` (which
duplicates anonymous objects). The solution is to avoid a global map and
attach the current transformation info directly to the current inlining
context.
This commit is contained in:
pyos
2020-04-14 18:03:53 +02:00
committed by max-kammerer
parent 3f8ffb5ea7
commit 456469eb3b
10 changed files with 153 additions and 63 deletions
@@ -27,7 +27,8 @@ class RegeneratedClassContext(
nameGenerator: NameGenerator, nameGenerator: NameGenerator,
typeRemapper: TypeRemapper, typeRemapper: TypeRemapper,
lambdaInfo: LambdaInfo?, lambdaInfo: LambdaInfo?,
override val callSiteInfo: InlineCallSiteInfo override val callSiteInfo: InlineCallSiteInfo,
override val transformationInfo: TransformationInfo
) : InliningContext( ) : InliningContext(
parent, expressionMap, state, nameGenerator, typeRemapper, lambdaInfo, true parent, expressionMap, state, nameGenerator, typeRemapper, lambdaInfo, true
) { ) {
@@ -48,7 +49,8 @@ open class InliningContext(
var generateAssertField = false var generateAssertField = false
private val internalNameToAnonymousObjectTransformationInfo = hashMapOf<String, AnonymousObjectTransformationInfo>() open val transformationInfo: TransformationInfo?
get() = null
var isContinuation: Boolean = false var isContinuation: Boolean = false
@@ -57,12 +59,13 @@ open class InliningContext(
val root: RootInliningContext val root: RootInliningContext
get() = if (isRoot) this as RootInliningContext else parent!!.root get() = if (isRoot) this as RootInliningContext else parent!!.root
fun findAnonymousObjectTransformationInfo(internalName: String, searchInParent: Boolean = true): AnonymousObjectTransformationInfo? = private val regeneratedAnonymousObjects = hashSetOf<String>()
internalNameToAnonymousObjectTransformationInfo[internalName]
?: if (searchInParent) parent?.findAnonymousObjectTransformationInfo(internalName, searchInParent) else null
fun recordIfNotPresent(internalName: String, info: AnonymousObjectTransformationInfo) { fun isRegeneratedAnonymousObject(internalName: String): Boolean =
internalNameToAnonymousObjectTransformationInfo.putIfAbsent(internalName, info) internalName in regeneratedAnonymousObjects || (parent != null && parent.isRegeneratedAnonymousObject(internalName))
fun recordRegeneratedAnonymousObject(internalName: String) {
regeneratedAnonymousObjects.add(internalName)
} }
fun subInlineLambda(lambdaInfo: LambdaInfo): InliningContext = fun subInlineLambda(lambdaInfo: LambdaInfo): InliningContext =
@@ -82,10 +85,11 @@ open class InliningContext(
fun subInlineWithClassRegeneration( fun subInlineWithClassRegeneration(
generator: NameGenerator, generator: NameGenerator,
newTypeMappings: MutableMap<String, String?>, newTypeMappings: MutableMap<String, String?>,
callSiteInfo: InlineCallSiteInfo callSiteInfo: InlineCallSiteInfo,
transformationInfo: TransformationInfo
): InliningContext = RegeneratedClassContext( ): InliningContext = RegeneratedClassContext(
this, expressionMap, state, generator, TypeRemapper.createFrom(typeRemapper, newTypeMappings), this, expressionMap, state, generator, TypeRemapper.createFrom(typeRemapper, newTypeMappings),
lambdaInfo, callSiteInfo lambdaInfo, callSiteInfo, transformationInfo
) )
@JvmOverloads @JvmOverloads
@@ -73,6 +73,9 @@ class MethodInliner(
if (!inliningContext.isInliningLambda) { if (!inliningContext.isInliningLambda) {
inliningContext.root.state.globalInlineContext.recordTypeFromInlineFunction(info.oldClassName) inliningContext.root.state.globalInlineContext.recordTypeFromInlineFunction(info.oldClassName)
} }
if (info.shouldRegenerate(isSameModule)) {
inliningContext.recordRegeneratedAnonymousObject(info.oldClassName)
}
transformations.add(info) transformations.add(info)
} }
@@ -171,7 +174,8 @@ class MethodInliner(
val childInliningContext = inliningContext.subInlineWithClassRegeneration( val childInliningContext = inliningContext.subInlineWithClassRegeneration(
inliningContext.nameGenerator, inliningContext.nameGenerator,
currentTypeMapping, currentTypeMapping,
inlineCallSiteInfo inlineCallSiteInfo,
transformationInfo!!
) )
val transformer = transformationInfo!!.createTransformer( val transformer = transformationInfo!!.createTransformer(
childInliningContext, isSameModule, fakeContinuationName childInliningContext, isSameModule, fakeContinuationName
@@ -305,51 +309,33 @@ class MethodInliner(
inlineOnlySmapSkipper?.markCallSiteLineNumber(remappingMethodAdapter) inlineOnlySmapSkipper?.markCallSiteLineNumber(remappingMethodAdapter)
} else if (isAnonymousConstructorCall(owner, name)) { //TODO add method } else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
//TODO add proper message //TODO add proper message
assert(transformationInfo is AnonymousObjectTransformationInfo) { var info = transformationInfo as? AnonymousObjectTransformationInfo ?: throw AssertionError(
"<init> call doesn't correspond to object transformation info for '$owner.$name': $transformationInfo" "<init> call doesn't correspond to object transformation info for '$owner.$name': $transformationInfo"
)
val objectConstructsItself = inlineCallSiteInfo.ownerClassName == info.oldClassName
if (objectConstructsItself) {
// inline fun f() -> new f$1 -> fun something() in class f$1 -> new f$1
// ^-- fetch the info that was created for this instruction
info = inliningContext.parent?.transformationInfo as? AnonymousObjectTransformationInfo
?: throw AssertionError("anonymous object $owner constructs itself, but we have no info on in")
} }
val parent = inliningContext.parent if (info.shouldRegenerate(isSameModule)) {
val shouldRegenerate = transformationInfo!!.shouldRegenerate(isSameModule)
val isContinuation = parent != null && parent.isContinuation
if (shouldRegenerate || isContinuation) {
assert(shouldRegenerate || inlineCallSiteInfo.ownerClassName == transformationInfo!!.oldClassName) { "Only coroutines can call their own constructors" }
//put additional captured parameters on stack
var info = transformationInfo as AnonymousObjectTransformationInfo
val oldInfo = inliningContext.findAnonymousObjectTransformationInfo(owner)
if (oldInfo != null && isContinuation) {
info = oldInfo
}
val isContinuationCreate = isContinuation && oldInfo != null && resultNode.name == "create" &&
resultNode.desc.endsWith(")" + languageVersionSettings.continuationAsmType().descriptor)
for (capturedParamDesc in info.allRecapturedParameters) { for (capturedParamDesc in info.allRecapturedParameters) {
if (capturedParamDesc.fieldName == AsmUtil.THIS && isContinuationCreate) { val realDesc = if (objectConstructsItself && capturedParamDesc.fieldName == AsmUtil.THIS) {
// Common inliner logic doesn't support cases when transforming anonymous object can // The captures in `info` are relative to the parent context, so a normal `this` there
// be instantiated by itself. // is a captured outer `this` here.
// To support such cases workaround with 'oldInfo' is used. CapturedParamDesc(Type.getObjectType(owner), AsmUtil.CAPTURED_THIS_FIELD, capturedParamDesc.type)
// But it corresponds to outer context and a bit inapplicable for nested 'create' method context. } else capturedParamDesc
// 'This' in outer context corresponds to outer instance in current visitFieldInsn(
visitFieldInsn( Opcodes.GETSTATIC, realDesc.containingLambdaName,
Opcodes.GETSTATIC, owner, FieldRemapper.foldName(realDesc.fieldName), realDesc.type.descriptor
FieldRemapper.foldName(AsmUtil.CAPTURED_THIS_FIELD), capturedParamDesc.type.descriptor )
)
} else {
visitFieldInsn(
Opcodes.GETSTATIC, capturedParamDesc.containingLambdaName,
FieldRemapper.foldName(capturedParamDesc.fieldName), capturedParamDesc.type.descriptor
)
}
} }
super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf) super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf)
//TODO: add new inner class also for other contexts //TODO: add new inner class also for other contexts
if (inliningContext.parent is RegeneratedClassContext) { if (inliningContext.parent is RegeneratedClassContext) {
inliningContext.parent.typeRemapper.addAdditionalMappings( inliningContext.parent.typeRemapper.addAdditionalMappings(info.oldClassName, info.newClassName)
transformationInfo!!.oldClassName, transformationInfo!!.newClassName
)
} }
transformationInfo = null transformationInfo = null
@@ -826,8 +812,7 @@ class MethodInliner(
private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean { private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean {
if (type == null || type.sort != Type.OBJECT) return false if (type == null || type.sort != Type.OBJECT) return false
val info = inliningContext.findAnonymousObjectTransformationInfo(type.internalName) return inliningContext.isRegeneratedAnonymousObject(type.internalName)
return info != null && info.shouldRegenerate(true)
} }
private fun buildConstructorInvocation( private fun buildConstructorInvocation(
@@ -849,7 +834,7 @@ class MethodInliner(
// inline lambda cannot possibly reference it, while V is not yet bound so regenerating the object while inlining // inline lambda cannot possibly reference it, while V is not yet bound so regenerating the object while inlining
// the lambda into f() is pointless. // the lambda into f() is pointless.
val inNonDefaultLambda = inliningContext.isInliningLambda && inliningContext.lambdaInfo !is DefaultLambda val inNonDefaultLambda = inliningContext.isInliningLambda && inliningContext.lambdaInfo !is DefaultLambda
val info = AnonymousObjectTransformationInfo( return AnonymousObjectTransformationInfo(
anonymousType, needReification && !inNonDefaultLambda, lambdaMapping, anonymousType, needReification && !inNonDefaultLambda, lambdaMapping,
inliningContext.classRegeneration, inliningContext.classRegeneration,
isAlreadyRegenerated(anonymousType), isAlreadyRegenerated(anonymousType),
@@ -858,19 +843,6 @@ class MethodInliner(
inliningContext.nameGenerator, inliningContext.nameGenerator,
capturesAnonymousObjectThatMustBeRegenerated capturesAnonymousObjectThatMustBeRegenerated
) )
val memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType)
if (memoizeAnonymousObject == null ||
//anonymous object could be inlined in several context without transformation (keeps same class name)
// and on further inlining such code some of such cases would be transformed and some not,
// so we should distinguish one classes from another more clearly
!memoizeAnonymousObject.shouldRegenerate(isSameModule) &&
info.shouldRegenerate(isSameModule)
) {
inliningContext.recordIfNotPresent(anonymousType, info)
}
return info
} }
private fun isAlreadyRegenerated(owner: String): Boolean { private fun isAlreadyRegenerated(owner: String): Boolean {
@@ -0,0 +1,27 @@
// WITH_RUNTIME
// WITH_COROUTINES
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt
package test
inline fun foo(crossinline x: () -> Unit) = suspend {
try { } finally {
// This object is regenerated twice (normal return & "catch Throwable, execute finally, and rethrow")
// It doesn't *need* to be, but this should work regardless.
{ x() }()
}
}
// FILE: 2.kt
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
import test.*
var result = "fail"
fun box(): String {
suspend {
foo { result = "OK" }()
}.startCoroutine(EmptyContinuation)
return result
}
@@ -0,0 +1,27 @@
// WITH_RUNTIME
// WITH_COROUTINES
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt
package test
inline fun foo(crossinline x: () -> Unit) = suspend {
try { } finally {
// This object is regenerated twice (normal return & "catch Throwable, execute finally, and rethrow")
// It doesn't *need* to be, but this should work regardless.
suspend { x() }()
}
}
// FILE: 2.kt
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
import test.*
var result = "fail"
fun box(): String {
suspend {
foo { result = "OK" }()
}.startCoroutine(EmptyContinuation)
return result
}
@@ -3953,6 +3953,16 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines"); runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines");
} }
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedAnonymousObject.kt");
}
@TestMetadata("twiceRegeneratedSuspendLambda.kt")
public void testTwiceRegeneratedSuspendLambda() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedSuspendLambda.kt");
}
@TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference") @TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3953,6 +3953,16 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines"); runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines");
} }
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedAnonymousObject.kt");
}
@TestMetadata("twiceRegeneratedSuspendLambda.kt")
public void testTwiceRegeneratedSuspendLambda() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedSuspendLambda.kt");
}
@TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference") @TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3848,6 +3848,16 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines"); runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines");
} }
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedAnonymousObject.kt");
}
@TestMetadata("twiceRegeneratedSuspendLambda.kt")
public void testTwiceRegeneratedSuspendLambda() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedSuspendLambda.kt");
}
@TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference") @TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3848,6 +3848,16 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines"); runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines");
} }
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedAnonymousObject.kt");
}
@TestMetadata("twiceRegeneratedSuspendLambda.kt")
public void testTwiceRegeneratedSuspendLambda() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedSuspendLambda.kt");
}
@TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference") @TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3418,6 +3418,16 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines"); runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines");
} }
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedAnonymousObject.kt");
}
@TestMetadata("twiceRegeneratedSuspendLambda.kt")
public void testTwiceRegeneratedSuspendLambda() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedSuspendLambda.kt");
}
@TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference") @TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3418,6 +3418,16 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines"); runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/tryCatchStackTransform.kt", "kotlin.coroutines");
} }
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedAnonymousObject.kt");
}
@TestMetadata("twiceRegeneratedSuspendLambda.kt")
public void testTwiceRegeneratedSuspendLambda() throws Exception {
runTest("compiler/testData/codegen/boxInline/suspend/twiceRegeneratedSuspendLambda.kt");
}
@TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference") @TestMetadata("compiler/testData/codegen/boxInline/suspend/callableReference")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)