Fix inlining of local objects. (#1920)
This commit is contained in:
+49
-2
@@ -784,6 +784,32 @@ internal class DeepCopyIrTreeWithDescriptors(val targetDescriptor: FunctionDescr
|
|||||||
|
|
||||||
class SubstitutedDescriptor(val inlinedFunction: FunctionDescriptor, val descriptor: DeclarationDescriptor)
|
class SubstitutedDescriptor(val inlinedFunction: FunctionDescriptor, val descriptor: DeclarationDescriptor)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches recursively for value that is corresponds to [key],
|
||||||
|
* then to typeMapper(this[key]) and so on until typeMapper or Map.get returns null.
|
||||||
|
* Returns null if there is no corresponding value for given [key] inside the map
|
||||||
|
* Throws IllegalStateException if recursion led to cycle.
|
||||||
|
*/
|
||||||
|
private fun <K, V> Map<K, V>.deepSearch(key: K, typeMapper: (V) -> K?): V? {
|
||||||
|
var result: V? = null
|
||||||
|
// Prevent cycles by remembering visited keys.
|
||||||
|
val visitedKeys = mutableSetOf<K?>()
|
||||||
|
var keyToVisit = key
|
||||||
|
do {
|
||||||
|
if (keyToVisit in visitedKeys) {
|
||||||
|
throw IllegalStateException("Detected cycle inside $this")
|
||||||
|
} else {
|
||||||
|
visitedKeys += keyToVisit
|
||||||
|
}
|
||||||
|
val value = this[keyToVisit]
|
||||||
|
if (value != null) {
|
||||||
|
result = value
|
||||||
|
keyToVisit = typeMapper(value) ?: return result
|
||||||
|
}
|
||||||
|
} while (value != null)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
internal class DescriptorSubstitutorForExternalScope(
|
internal class DescriptorSubstitutorForExternalScope(
|
||||||
val globalSubstituteMap: MutableMap<DeclarationDescriptor, SubstitutedDescriptor>,
|
val globalSubstituteMap: MutableMap<DeclarationDescriptor, SubstitutedDescriptor>,
|
||||||
val context: Context
|
val context: Context
|
||||||
@@ -808,8 +834,29 @@ internal class DescriptorSubstitutorForExternalScope(
|
|||||||
|
|
||||||
val oldDescriptor = declaration.descriptor
|
val oldDescriptor = declaration.descriptor
|
||||||
val oldClassDescriptor = oldDescriptor.type.constructor.declarationDescriptor as? ClassDescriptor
|
val oldClassDescriptor = oldDescriptor.type.constructor.declarationDescriptor as? ClassDescriptor
|
||||||
val substitutedDescriptor = oldClassDescriptor?.let { globalSubstituteMap[it] }
|
|
||||||
if (substitutedDescriptor == null || allScopes.any { it.scope.scopeOwner == substitutedDescriptor.inlinedFunction })
|
// This recursive search inside the map requires some explanation.
|
||||||
|
// Take a look at the following code:
|
||||||
|
//
|
||||||
|
// inline fun exec(f: () -> Unit) = f()
|
||||||
|
//
|
||||||
|
// inline fun test2() {
|
||||||
|
// val sr = object {} // <- problematic place
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun box() {
|
||||||
|
// exec {
|
||||||
|
// test2()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Here test2 will be inlined twice, so descriptor of the object will be changed twice (ex. from A to B and from B to C).
|
||||||
|
// If we use ordinary access to the map (globalSubstituteMap[A]) then we get wrong descriptor (B).
|
||||||
|
// So we have to search recursively inside the map to find correct descriptor.
|
||||||
|
val typeMapper: (SubstitutedDescriptor) -> ClassDescriptor? = { it.descriptor as? ClassDescriptor }
|
||||||
|
val substitutedDescriptor = oldClassDescriptor?.let { globalSubstituteMap.deepSearch(it, typeMapper) }
|
||||||
|
// should be true for empty allScopes
|
||||||
|
if (substitutedDescriptor == null || allScopes.all { it.scope.scopeOwner != substitutedDescriptor.inlinedFunction })
|
||||||
return
|
return
|
||||||
val newDescriptor = IrTemporaryVariableDescriptorImpl(
|
val newDescriptor = IrTemporaryVariableDescriptorImpl(
|
||||||
containingDeclaration = oldDescriptor.containingDeclaration,
|
containingDeclaration = oldDescriptor.containingDeclaration,
|
||||||
|
|||||||
@@ -2526,6 +2526,11 @@ task inline_defaultArgs(type: RunKonanTest) {
|
|||||||
source = "codegen/inline/defaultArgs.kt"
|
source = "codegen/inline/defaultArgs.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task inline_twiceInlinedObject(type: RunKonanTest) {
|
||||||
|
goldValue = "Ok\n"
|
||||||
|
source = "codegen/inline/twiceInlinedObject.kt"
|
||||||
|
}
|
||||||
|
|
||||||
task classDeclarationInsideInline(type: RunKonanTest) {
|
task classDeclarationInsideInline(type: RunKonanTest) {
|
||||||
source = "codegen/inline/classDeclarationInsideInline.kt"
|
source = "codegen/inline/classDeclarationInsideInline.kt"
|
||||||
goldValue = "test1: 1.0\n1\ntest2\n"
|
goldValue = "test1: 1.0\n1\ntest2\n"
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package codegen.inline.twiceInlinedObject
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
inline fun exec(f: () -> Unit) = f()
|
||||||
|
|
||||||
|
inline fun test2() {
|
||||||
|
val obj = object {
|
||||||
|
fun sayOk() = println("Ok")
|
||||||
|
}
|
||||||
|
obj.sayOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun noExec(f: () -> Unit) { }
|
||||||
|
|
||||||
|
@Test fun runTest() {
|
||||||
|
exec {
|
||||||
|
test2()
|
||||||
|
}
|
||||||
|
noExec {
|
||||||
|
test2()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user