Status Update
Comments
nj...@google.com <nj...@google.com>
ch...@google.com <ch...@google.com> #2
yu...@gmail.com <yu...@gmail.com> #3
Branch: androidx-main
commit e7b4b06e67951ab47157d06ff314a5c8b2e8fdc6
Author: Andrei Shikov <ashikov@google.com>
Date: Mon Jan 08 15:54:51 2024
Count recursive local declarations as captures
Local declarations were already counted as captures for composable lambdas before, but the traversal ordering missed the recursive captures, converting some of them into singletons.
Test: Compiler test
Fixes: 318745941
Change-Id: I9097d1be71fb67b73e5027f723fd187c4272f6b4
M compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
M compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
M compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
yu...@gmail.com <yu...@gmail.com> #4
I have find a sample code to reproduce that
setContent {
val dialogState = remember { DialogState() }
dialogState.Intercept()
LaunchedEffect(Unit) {
delay(1000)
dialogState.bgWork {
delay(5000)
}
dialogState.showSelectActions {
onSelect("a") {
}
onSelect("b") {
}
}
}
}
You can find implementation of DialogState
here
yu...@gmail.com <yu...@gmail.com> #5
Let me sort it out
The first crash
Reproduce: navigate to a gallery and navigate back
The reason is we have some code assume the forget order of RememberObserver(DisposableEffect), and 52835066c9044647a65dfb566f03131aab46e793
changed the order. Seems this is a intended behaviour.
I will fix it from my side. My bad
The second crash
Reproduce: #4 comment
StackTrace:
We got a wrong object from SlotTable during recompose
yu...@gmail.com <yu...@gmail.com> #6
And interestingly, if we insert delay(1000) between two dialog suspend usage
dialogState.bgWork {
delay(5000)
}
delay(1000)
dialogState.showSelectActions {
onSelect("a") {
}
onSelect("b") {
}
}
It will not crash
ch...@google.com <ch...@google.com> #7
As for the order of `onForgotten`, it may be you are seeing and issue that I fixed in a subsequent version as this, for the snapshot builds, was broken for a few weeks when using the non-skipping group optimization even indirectly.
ch...@google.com <ch...@google.com> #8
ch...@google.com <ch...@google.com> #9
I tried cutting down the example myself and was not able to reproduce this.
The primary dependency chagnes I made were:
compose-material3 = { module = "androidx.compose.material3:material3", version = "1.3.0-SNAPSHOT" }
compose-foundation = { module = "androidx.compose.foundation:foundation-android", version = "1.7.0-SNAPSHOT" }
I only included the part of the Dialog.kt that was needed by the above,
class DialogState {
var content: (@Composable () -> Unit)? by mutableStateOf(null)
@Composable
fun Intercept() = content?.invoke()
fun dismiss() {
content = null
}
suspend inline fun <R> dialog(crossinline block: @Composable (CancellableContinuation<R>) -> Unit) = suspendCancellableCoroutine<R> { cont ->
cont.invokeOnCancellation { dismiss() }
val realContinuation = object : CancellableContinuation<R> by cont {
override fun resumeWith(result: Result<R>) {
dismiss()
cont.resumeWith(result)
}
}
content = { block(realContinuation) }
}
suspend inline fun showSelectActions(
title: String? = null,
builder: ActionScope.() -> Unit,
) {
val (items, actions) = buildList { builder(ActionScope { action, that -> add(action to that) }) }.unzip()
val selected = showSelectItem(items, title)
actions[selected].invoke()
}
suspend fun showSelectItem(
items: List<String>,
title: String?,
respectDefaultWidth: Boolean = true,
): Int = showNoButton(respectDefaultWidth) {
Column(modifier = Modifier.padding(vertical = 8.dp)) {
if (title != null) {
Text(
text = title,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
style = MaterialTheme.typography.headlineSmall,
)
}
LazyColumn {
itemsIndexed(items) { index, text ->
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.tertiary) {
Text(
text = text,
modifier = Modifier.clickable { dismissWith(index) }.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 16.dp),
style = MaterialTheme.typography.titleMedium,
)
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
suspend fun <R> showNoButton(respectDefaultWidth: Boolean = true, block: @Composable DismissDialogScope<R>.() -> Unit): R {
return dialog { cont ->
val impl = remember(cont) {
DismissDialogScope<R> {
cont.resume(it)
}
}
BasicAlertDialog(
onDismissRequest = { cont.cancel() },
properties = DialogProperties(usePlatformDefaultWidth = respectDefaultWidth),
content = {
Surface(
modifier = with(Modifier) { if (!respectDefaultWidth) defaultMinSize(280.dp) else width(280.dp) },
shape = AlertDialogDefaults.shape,
color = AlertDialogDefaults.containerColor,
tonalElevation = AlertDialogDefaults.TonalElevation,
content = { block(impl) },
)
},
)
}
}
}
fun interface ActionScope {
fun onSelect(action: String, that: suspend () -> Unit)
}
fun interface DismissDialogScope<R> {
fun dismissWith(value: R)
}
This shows the "Please wait" and then selection between "a" and "b" as I expected.
yu...@gmail.com <yu...@gmail.com> #10
This requires compose compiler 1.5.11-dev-k2.0.0-Beta4-21f5e479a96
I will upload a sample project soon
yu...@gmail.com <yu...@gmail.com> #11
yu...@gmail.com <yu...@gmail.com> #12
ch...@google.com <ch...@google.com> #13
Sorry! I didn't see you updated this. I must have missed the notification. Trying now.
ch...@google.com <ch...@google.com> #14
I have the exception reproducing locally, investigating.
ch...@google.com <ch...@google.com> #15
The issue is in the call to dialog
when it inlines the crossinline
lambda. This lambda instances are getting the same key at both call sights and, when the second dialog shows, it believes it is recomposing the first and then throws as it didn't create the state in this slot table.
The simple work-around now is to change dialog
's parameter to noinline
instead of crossinline
. This ensures that the keys produced for the lambdas are different.
I am investigating what change caused this.
ch...@google.com <ch...@google.com> #16
The code generation for crossinline
lambda seems to have always been wrong but the extra groups the compiler create that the change removes prevented crashes in most cases. By removing otherwise unnecessary groups, the missing group around a crossinline
lambda now causes a crash.
The work-around above, changing the lambda to noinline
, is valid because it avoids crossinline
and, therefore, avoids the crash.
The key symptom is that currentComposer.inserting
is never true
for the crossinline
lambda passed into the second dialog which indicates it is recomposing over state it didn't produce.
A group is required because a crossinline
lambda can be inlined into a lambda instance. The instance created gets the same key but its content has effectively changed as the crossinline
lambda acts as a branch point. The runtime assumes that when a different branch of code is executed, it executes in a block with a different key than the previous branch taken. As there is no branch group, the code is recomposed over state it didn't create, which is never valid. Recomposing over state it didn't create will not always create an exception and this was the case for the code generated prior to enabling the optimization to remove otherwise unnecessary groups.
ap...@google.com <ap...@google.com> #17
Branch: androidx-main
commit 7862dc2cf51ebc69f6760b0c6cf8a2133f11cd94
Author: Chuck Jazdzewski <chuckj@google.com>
Date: Mon Feb 26 15:02:15 2024
Fix code generation for composable crossinline lambda
If a crossinline lambda was called in a lambda instance then
it was possible for the composition to get out of sync potentially
causing crashes. This is hard to reproduce without
"nonSkippingGroupOptimization" enabled as the group around
remember causes most exceptions avoided, however the code was
incorrect as and subtle crashes were still possible.
Fixes: 325502738
Test: ./gradlew :compose:c:c-h:i-t:tDUT :compose:c:c-h:r-t:jvmTest
Change-Id: Icb2fd2f5a946144cc105737c4a4051f02a901b78
M compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda[useFir = false].txt
M compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda[useFir = true].txt
M compose/compiler/compiler-hosted/runtime-tests/build.gradle
M compose/compiler/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/CompositionTests.kt
M compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
M compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
pr...@google.com <pr...@google.com> #18
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.compose.compiler:compiler-hosted:1.5.11
Description
Sorry that I cannot find a minimal sample code yet
Got following crash
Seems that we get a wrong object in SlotTable during recompose
Reproduce with
Navigate to a random gallery, and navigate back, then we will crash.