Skip to content

Instantly share code, notes, and snippets.

@wgjuher
Last active June 2, 2023 12:41
Show Gist options
  • Save wgjuher/896c16d08f75264c988ba80bb5231c68 to your computer and use it in GitHub Desktop.
Save wgjuher/896c16d08f75264c988ba80bb5231c68 to your computer and use it in GitHub Desktop.
This gist explain how compose compiler work under the hood
val SLOTS_PER_INT = 10
val BITS_PER_SLOT = 3
val SameOrUnknown = ParamState.Same.bits or ParamState.Unknown.bits
println("Bits: ${valueOfBits(ParamState.Same.bits and SameOrUnknown)}")
println("base bit masks")
ParamState.values().forEach(::println)
println("forced masks (mask 'and' ForceUpdate(${SameOrUnknown.toBits()})) ")
ParamState.values()
.forEach {
val newValue = valueOfBits(bitsForSlot(SameOrUnknown, 0) and it.bitsForSlot(0))
println("$it -> $newValue")
}
//initial compose values
val changed = ParamState.Different.bitsForSlot(0)
val composer = Composer()
print("invoke 'a' function with state \$changed: ${valueOfBits(changed)}", changed)
a(12, composer, changed)
//simplified decompiled 'a' function original in DecompiledComposeFunctions
fun a(x: Int, composer: Composer, changed: Int) {
composer.startRestartGroup("a")
var dirty = changed
val slotState = changed and ParamState.Mask.bitsForSlot(0)
print("check (changed 'and' mask)", slotState)
if (slotState == ParamState.Uncertain.bitsForSlot(0)) {
println("ask composer if 'a' is different")
dirty = changed or
(if (composer.changed(x)) ParamState.Different else ParamState.Same).bitsForSlot(0)
}
print("dirty: ${valueOfBits(dirty)}", dirty)
if (dirty and sameOrUnknownForced(0) == ParamState.Same.bitsForSlot(0) && composer.getSkipping()) {
composer.skipToGroupEnd()
} else {
println("invoke 'a' body")
b(x = x, y = 123, composer = composer, changed =
(ParamState.Mask.bitsForSlot(0) and dirty) or ParamState.Static.bitsForSlot(1)
)
}
composer.endRestartGroup()
}
//simplified decompiled 'b' function original in DecompiledComposeFunctions
fun b(x: Int, y: Int, composer: Composer, changed: Int) {
composer.startRestartGroup("b")
var dirty = changed
var slotState = changed and ParamState.Mask.bitsForSlot(0)
print("check changed 'and' mask for slot 0", slotState)
if (slotState == ParamState.Uncertain.bits) {
println("ask composer if 'a' is different")
dirty = changed or
(if (composer.changed(x)) ParamState.Different else ParamState.Same).bitsForSlot(0)
}
slotState = changed and ParamState.Mask.bitsForSlot(1)
print("check changed 'and' mask for slot 1", slotState)
if (slotState == ParamState.Uncertain.bits) {
println("ask composer if 'b' is different")
dirty = dirty or
(if (composer.changed(y, false)) ParamState.Different else ParamState.Same).bitsForSlot(1)
}
print("dirty: ${valueOfBits(dirty)}", dirty)
if (dirty and sameOrUnknownForced(1) == ParamState.Same.bitsForSlot(1) && composer.getSkipping()) {
composer.skipToGroupEnd()
} else {
println("invoke 'b' body")
}
composer.endRestartGroup()
}
//Helper functions
fun print(name: String, int: Int) {
println("$name bits ${int.toBits()}")
}
fun sameOrUnknownForced(slot: Int = 0): Int {
var result = bitsForSlot(SameOrUnknown, 0)
for (i in 1..slot) {
result = result or bitsForSlot(SameOrUnknown, slot)
}
return result or 0b1 //add 1 as smallest bit to force update
}
fun bitsForSlot(bits: Int, slot: Int): Int {
val realSlot = slot.rem(SLOTS_PER_INT)
return bits shl (realSlot * BITS_PER_SLOT + 1)
}
fun valueOfBits(bits: Int): String = bits.toParamState(bits.toBits().length / 3).toString()
fun ParamState.bitsForSlot(slot: Int) = bitsForSlot(bits, slot)
fun Int.toBits() = toUInt().toString(radix = 2)
fun Int.toParamState(slots: Int) = (0 until slots.coerceAtLeast(1))
.map { slot -> getSlotState(this, slot) }
.mapNotNull { bits -> ParamState.values().firstOrNull { it.bits == bits } }
fun getSlotState(changed: Int, slot: Int) =
(changed and ParamState.Mask.bitsForSlot(slot)) shr (BITS_PER_SLOT * slot + 1)
//mock Composer - only print to console
class Composer {
private val stack = LinkedList<String>()
fun changed(param: Any, overrideReturn: Boolean = true): Boolean = overrideReturn
fun getSkipping(overrideReturn: Boolean = true) = overrideReturn
fun startRestartGroup(name: String) {
println("start restart '$name' group")
stack.push(name)
}
fun endRestartGroup() {
println("end restart '${stack.pop()}' group")
}
fun skipToGroupEnd() {
println("skip '${stack.peek()}' group")
}
}
//original masks
//https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt#163
enum class ParamState(val bits: Int) {
Uncertain(0b000),
Same(0b001),
Different(0b010),
Static(0b011),
Unknown(0b100),
Mask(0b111);
override fun toString(): String {
return "$name(${bits.toUInt().toString(radix = 2)})"
}
}
@Composable
public static final void A(final int x, @Nullable Composer $composer, final int $changed) {
$composer = $composer.startRestartGroup(-1673623075);
ComposerKt.sourceInformation($composer, "C(A)34@804L15:Stability.kt#p4r5jq");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(x) ? 4 : 2);
}
if (($dirty & 11) == 2 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
B(x, LiveLiterals$StabilityKt.INSTANCE.Int$arg-1$call-B$fun-A(), $composer, 14 & $dirty);
}
ScopeUpdateScope var10000 = $composer.endRestartGroup();
if (var10000 != null) {
var10000.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityKt.A(x, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void B(final int x, final int y, @Nullable Composer $composer, final int $changed) {
$composer = $composer.startRestartGroup(480008249);
ComposerKt.sourceInformation($composer, "C(B):Stability.kt#p4r5jq");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(x) ? 4 : 2);
}
if (($changed & 112) == 0) {
$dirty |= $composer.changed(y) ? 32 : 16;
}
if (($dirty & 91) == 18 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
System.out.print(x + LiveLiterals$StabilityKt.INSTANCE.String$1$str$arg-0$call-print$fun-B() + y);
}
ScopeUpdateScope var10000 = $composer.endRestartGroup();
if (var10000 != null) {
var10000.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityKt.B(x, y, $composer, $changed | 1);
}
}));
}
}
@Composable
fun A(x: Int){
B(x = x, y = 5)
}
@Composable
fun B(x: Int, y: Int){
print("$x : $y")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment