Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Hitchhiker’s Guide to Compose: Snapshots, Compiler Plugins and Appliers

mvndy_hd
October 22, 2021

Hitchhiker’s Guide to Compose: Snapshots, Compiler Plugins and Appliers

If you are an Android Developer, chances are you are pretty excited about Jetpack Compose! But how does it work? In this talk, we will take you along the journey of a Composable function, from being written, going through the unknown lands of the Kotlin and Compose compilers to being executed and displaying UI!

From Composables all the way down into the compiler plugins, we expose the metaprogramming responsible for all the "magic". Whether your interest is Jetpack Compose, compilers, or code transformations, this talk takes an otherwise complicated topic and makes it digestible for everybody by diving into specific features offered by Compose.

By examining Compose's snapshot system, you'll follow the compiler phases down to intercepting code transformations with IR. By recognizing the patterns that make it hard for Compose to generate efficient code, and this can help us to recognize patterns to use for performance.

mvndy_hd

October 22, 2021
Tweet

More Decks by mvndy_hd

Other Decks in Technology

Transcript

  1. #hg2compose Text(text = name) Layout Layout name by remember {

    mutableStateOf("") } @mvndy_hd @jossiwolf
  2. #hg2compose Text(text = name) Layout Layout name by remember {

    mutableStateOf("Bob") } @mvndy_hd @jossiwolf
  3. #hg2compose Text(text = name) Layout Layout name by remember {

    mutableStateOf("Bob") } @mvndy_hd @jossiwolf
  4. #hg2compose Text(text = name) Layout Layout name by remember {

    mutableStateOf("Bob") } Bob @mvndy_hd @jossiwolf
  5. "Jetpack Compose is Android's modern toolkit for building native UI."

    #hg2compose - developer.android.com @mvndy_hd @jossiwolf
  6. A Composable is a restartable function that emits nodes into

    a tree. #hg2compose Glossary @mvndy_hd @jossiwolf
  7. Recomposition is the process of re-executing a Composable function to

    produce an updated slot table #hg2compose Glossary @mvndy_hd @jossiwolf
  8. The Slot Table is used to store the current state

    of the Composition. #hg2compose Glossary @mvndy_hd @jossiwolf
  9. A Composer connects Composables to Runtime, keeps track of groups

    that are used to build the Slot Table and positions it. #hg2compose Glossary @mvndy_hd @jossiwolf
  10. @Composable fun HitchhikerApp() { var name by remember { mutableStateOf("Bob")

    } HelloWorld(greeting = name) name = "Chet" } @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } #hg2compose @mvndy_hd @jossiwolf
  11. "A Snapshot is a lot like a save point in

    a video game: it represents the state of your entire program at a single point in history." #hg2compose https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn - "Introduction to the Compose Snapshot System" by Zach Klippenstein Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  12. #hg2compose https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn class Dog { var name: MutableState<String> = mutableStateOf(“”)

    } fun main() { val dog = Dog() dog.name.value = "Spot" val snapshot = Snapshot.takeSnapshot() dog.name.value = "Fido" println(dog.name.value) } Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  13. #hg2compose https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn fun main() { val dog = Dog() dog.name.value

    = "Spot" val snapshot = Snapshot.takeSnapshot() dog.name.value = "Fido" println(dog.name.value) // Fido snapshot.enter { println(dog.name.value) / / Spot } println(dog.name.value) // Fido } Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  14. #hg2compose Snapshot.takeSnapshot() dog.name.value = "Fido" 📸 1 2 Dog(name =

    "Spot") Dog(name = "Fido") Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  15. #hg2compose Snapshot.takeSnapshot() dog.name.value = "Fido" 📸 1 2 Dog(name =

    "Spot") Dog(name = "Fido") snapshot.enter { . . . } Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  16. #hg2compose Snapshot.takeSnapshot() dog.name.value = "Fido" 📸 1 2 Dog(name =

    "Spot") Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } ❌ Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  17. #hg2compose Snapshot.takeSnapshot() dog.name.value = "Fido" 📸 1 2 Dog(name =

    "Spot") Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } ❌ Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  18. #hg2compose Snapshot.takeMutableSnapshot() dog.name.value = "Fido" 📸 1 2 Dog(name =

    "Spot") Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } ❌ Snapshots 📸 (and dogs🐕) @mvndy_hd @jossiwolf
  19. #hg2compose @mvndy @jossiwolf Snapshots 📸 (and dogs🐕) Snapshot.takeMutableSnapshot() 📸 1

    2 dog.name.value = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } Dog(name = "Spot")
  20. #hg2compose @mvndy @jossiwolf Snapshots 📸 (and dogs🐕) Snapshot.takeMutableSnapshot() 📸 1

    2 dog.name.value = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } Dog(name = "Spot")
  21. #hg2compose @mvndy @jossiwolf Snapshots 📸 (and dogs🐕) Snapshot.takeMutableSnapshot() 📸 1

    2 dog.name.value = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } Dog(name = "Spot") Dog(name = "Taco") ✔
  22. #hg2compose @mvndy @jossiwolf Snapshots 📸 (and dogs🐕) snapshot.apply() Snapshot.takeMutableSnapshot() dog.name.value

    = "Fido" 📸 1 2 snapshot.enter { dog.name.value = "Taco" } Dog(name = "Spot") Dog(name = "Taco") Dog(name = "Fido") Dog(name = "Taco")
  23. #hg2compose @mvndy @jossiwolf Snapshots 📸 (and dogs🐕) snapshot.apply() Snapshot.takeMutableSnapshot() dog.name.value

    = "Fido" 📸 1 2 snapshot.enter { dog.name.value = "Taco" } Dog(name = "Spot") Dog(name = "Taco") Dog(name = "Fido") Dog(name = "Taco")
  24. #hg2compose @mvndy @jossiwolf https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn fun main() { val dog =

    Dog() dog.name.value = "Spot" val snapshot = Snapshot.takeSnapshot( readObserver = { value -> ... } ) dog.name.value = "Fido" println(dog.name.value) // Fido snapshot.enter { println(dog.name.value) // Spot } } Snapshots 📸 (and dogs🐕)
  25. #hg2compose @mvndy @jossiwolf fun main() { val dog = Dog()

    dog.name.value = "Spot" val snapshot = Snapshot.takeSnapshot( readObserver = { value -> println(value) // MutableState(value=Spot)@ ... } ) dog.name.value = "Fido" println(dog.name.value) // Fido snapshot.enter { println(dog.name.value) // Spot } } Snapshots 📸 (and dogs🐕)
  26. #hg2compose @mvndy @jossiwolf fun main() { val dog = Dog()

    dog.name.value = "Spot" val snapshot = Snapshot.takeSnapshot( readObserver = { value -> println(value) // MutableState(value=Spot)@ ... } ) dog.name.value = "Fido" println(dog.name.value) // Fido snapshot.enter { println(dog.name.value) // Spot } } Snapshots 📸 (and dogs🐕)
  27. #hg2compose @mvndy @jossiwolf fun main() { val dog = Dog()

    dog.name.value = "Spot" val snapshot = Snapshot.takeMutableSnapshot( readObserver = { value -> println(value) // MutableState(value=Spot)@ ... }, writeObserver = { value -> ... } ) dog.name.value = "Fido" snapshot.enter { dog.name.value = "Taco" } } Snapshots 📸 (and dogs🐕)
  28. #hg2compose interface State<out T> { val value: T } package

    androidx.compose.runtime @mvndy_hd @jossiwolf
  29. #hg2compose interface State<out T> { val value: T } interface

    MutableState<T> : State<T> { override var value: T operator fun component1(): T operator fun component2(): (T) -> Unit } package androidx.compose.runtime @mvndy_hd @jossiwolf
  30. #hg2compose /** * Return the current readable state record for

    the [snapshot]. * It is assumed that [this] is the first record of [state] */ fun <T : StateRecord> T.readable(state: StateObject, snapshot: Snapshot): T { // invoke the observer associated with the current snapshot. snapshot.readObserver ?. invoke(state) return readable(this, snapshot.id, snapshot.invalid) ?: readError() } package androidx.compose.runtime @mvndy_hd @jossiwolf
  31. #hg2compose /** * Return the current readable state record for

    the [snapshot]. * It is assumed that [this] is the first record of [state] */ fun <T : StateRecord> T.readable(state: StateObject, snapshot: Snapshot): T { // invoke the observer associated with the current snapshot. snapshot.readObserver ?. invoke(state) return readable(this, snapshot.id, snapshot.invalid) ?: readError() } package androidx.compose.runtime @mvndy_hd @jossiwolf
  32. @Composable fun HitchhikerApp() { var name = remember { mutableStateOf("Bob")

    } HelloWorld(greeting = name.value) } @Composable fun HelloWorld(greeting: String) { println("Hello, $greeting!") } #hg2compose @mvndy_hd @jossiwolf
  33. #hg2compose suspend fun runApp() { val composer = Recomposer() GlobalSnapshotManager.ensureStarted()

    launch(DefaultChoreographerFrameClock) { composer.runRecomposeAndApplyChanges() } Composition(NodeApplier(RootNode()), composer).apply { setContent { Content() } } } @mvndy_hd @jossiwolf
  34. #hg2compose suspend fun runApp() { val composer = Recomposer() GlobalSnapshotManager.ensureStarted()

    launch(DefaultChoreographerFrameClock) { composer.runRecomposeAndApplyChanges() } Composition(NodeApplier(RootNode()), composer).apply { setContent { Content() } } } medium.com/@takahirom/inside-jetpack-compose-2e971675e55e @mvndy_hd @jossiwolf
  35. Recomposers manage the Snapshot and Compositions, i.e. composing initially and

    recomposing. #hg2compose Glossary @mvndy_hd @jossiwolf
  36. #hg2compose suspend fun runApp() { val composer = Recomposer() GlobalSnapshotManager.ensureStarted()

    launch(DefaultChoreographerFrameClock) { composer.runRecomposeAndApplyChanges() } Composition(NodeApplier(RootNode()), composer).apply { setContent { Content() } } } medium.com/@takahirom/inside-jetpack-compose-2e971675e55e @mvndy_hd @jossiwolf
  37. #hg2compose suspend fun runApp() { val composer = Recomposer() GlobalSnapshotManager.ensureStarted()

    launch(DefaultChoreographerFrameClock) { composer.runRecomposeAndApplyChanges() } Composition(NodeApplier(RootNode()), composer).apply { setContent { Content() } } } medium.com/@takahirom/inside-jetpack-compose-2e971675e55e @mvndy_hd @jossiwolf
  38. #hg2compose suspend fun runApp() { val composer = Recomposer() GlobalSnapshotManager.ensureStarted()

    launch(DefaultChoreographerFrameClock) { composer.runRecomposeAndApplyChanges() } Composition(NodeApplier(RootNode()), composer).apply { setContent { Content() } } } medium.com/@takahirom/inside-jetpack-compose-2e971675e55e @mvndy_hd @jossiwolf
  39. #hg2compose suspend fun runApp() { val composer = Recomposer() GlobalSnapshotManager.ensureStarted()

    launch(DefaultChoreographerFrameClock) { composer.runRecomposeAndApplyChanges() } Composition(NodeApplier(RootNode()), composer).apply { setContent { Content() } } } medium.com/@takahirom/inside-jetpack-compose-2e971675e55e @mvndy_hd @jossiwolf
  40. #hg2compose private inline fun <T> composing( composition: ControlledComposition, modifiedValues: IdentityArraySet<Any>?,

    block: () -> T ): T { val snapshot = Snapshot.takeMutableSnapshot( readObserverOf(composition), writeObserverOf(composition, modifiedValues) ) try { return snapshot.enter(block) } finally { applyAndCheck(snapshot) } } Recomposer @mvndy_hd @jossiwolf
  41. #hg2compose private inline fun <T> composing( composition: ControlledComposition, modifiedValues: IdentityArraySet<Any>?,

    block: () -> T ): T { val snapshot = Snapshot.takeMutableSnapshot( readObserverOf(composition), writeObserverOf(composition, modifiedValues) ) try { return snapshot.enter(block) } finally { applyAndCheck(snapshot) } } Recomposer @mvndy_hd @jossiwolf
  42. #hg2compose private inline fun <T> composing( composition: ControlledComposition, modifiedValues: IdentityArraySet<Any>?,

    block: () -> T ): T { val snapshot = Snapshot.takeMutableSnapshot( readObserverOf(composition), writeObserverOf(composition, modifiedValues) ) try { return snapshot.enter(block) } finally { applyAndCheck(snapshot) } } Recomposer @mvndy_hd @jossiwolf
  43. #hg2compose private fun readObserverOf( composition: ControlledComposition ): (Any) -> Unit

    { return { value -> composition.recordReadOf(value) } } Recomposer @mvndy_hd @jossiwolf
  44. #hg2compose // Invalidate any recompose scopes that read this value.

    observations.forEachScopeOf(value) { scope -> if (scope.invalidateForResult(value) == InvalidationResult.IMMINENT) { observationsProcessed.add(value, scope) } } Composition @mvndy_hd @jossiwolf
  45. #hg2compose RecomposeScope /** * Invalidate the group which will cause

    [composition] to request * this scope be recomposed */ fun invalidateForResult(value: Any?): InvalidationResult = composition ?. invalidate(this, value) ?: InvalidationResult.IGNORED @mvndy_hd @jossiwolf
  46. @Composable fun HitchhikerApp() { var name = remember { mutableStateOf("Bob")

    } HelloWorld(greeting = name.value) } @Composable fun HelloWorld(greeting: String) { println("Hello, $greeting!") } #hg2compose S
 N
 A
 P
 S
 H
 O
 T @mvndy_hd @jossiwolf
  47. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  48. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  49. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  50. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  51. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  52. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  53. @Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer

    = %composer.startRestartGroup( <> ) val %dirty = %changed if (%changed and 0b1110 === 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ?. updateScope { %composer: Composer?, %force: Int -> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  54. @Composable fun HelloWorld(names: List<String>) { names.forEach { name -> key(name)

    { Text(name) } } } Moveable Groups @mvndy_hd @jossiwolf
  55. @Composable fun HelloWorld(name: String) { if (name == "Amanda") {

    Text("Hello!") else if (name == "Jossi") { Text("Hallo!") } else { Text("Uhm .. hi?") } } Replaceable Groups @mvndy_hd @jossiwolf
  56. #hg2compose .kt Lexer (Scanner) Syntax Analysis Parsing Phase Generates tokens

    - Generates tokens to create an AST - PSI lays over AST Resolution Codegen Resolution performed on AST tree - PSI gets enhanced with descriptors - Symbol table generated; associated node w/ descriptor via BindingTrace Frontend Backend - Takes optimized IR and performs more analysis, transformations + optimisations speci fi c to target CPU architecture - Multithreading Machine-Dependent Optimisations IR Transform Compiler Analysis Middle End “Lower” - Performs optimisations on IR - Removes dead code - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control- fl ow graph < > Bytecode Target Program FUN BLOCK_BODY CALL VAR IR Unoptimized IR Codegen IR to Bytecode 
 Optimized IR FUN BLOCK_BODY CALL VAR IR Backend The Kotlin compiler @mvndy_hd @jossiwolf
  57. #hg2compose .kt Lexer (Scanner) Syntax Analysis Parsing Phase Generates tokens

    - Generates tokens to create an AST - PSI lays over AST Resolution Codegen Resolution performed on AST tree - PSI gets enhanced with descriptors - Symbol table generated; associated node w/ descriptor via BindingTrace Frontend Backend - Takes optimized IR and performs more analysis, transformations + optimisations speci fi c to target CPU architecture - Multithreading Machine-Dependent Optimisations IR Transformations Compiler Analysis Middle End “Lower” - Performs optimisations on IR - Removes dead code - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control- fl ow graph < > Bytecode Target Program FUN BLOCK_BODY CALL VAR IR Unoptimized IR Codegen IR to Bytecode 
 Optimized IR FUN BLOCK_BODY CALL VAR IR Backend Compose compiler plugin Backend - Takes optimized IR and performs more analysis, transformations + optimisations speci fi c to target CPU architecture - Multithreading Machine-Dependent Optimisations IR Transform Compiler Analysis Middle End “Lower” - Performs optimisations on IR - Removes dead code - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control- fl ow graph < > Bytecode Target Program FUN BLOCK_BODY CALL VAR IR Unoptimized IR Codegen IR to Bytecode 
 Optimized IR FUN BLOCK_BODY CALL VAR IR Backend @mvndy_hd @jossiwolf
  58. #hg2compose .kt Lexer (Scanner) Syntax Analysis Parsing Phase Generates tokens

    - Generates tokens to create an AST - PSI lays over AST Semantic Analysis Codegen Semantic Analysis performed on AST - PSI gets enhanced with descriptors - Symbol table generated; associated node w/ descriptor via BindingTrace Kotlin Compiler Frontend @mvndy_hd @jossiwolf
  59. #hg2compose .kt Lexer (Scanner) Syntax Analysis Parsing Phase Generates tokens

    - Generates tokens to create an AST - PSI lays over AST Resolution Codegen Semantic Analysis performed on AST - PSI gets enhanced with descriptors - Symbol table generated; associated node w/ descriptor via BindingTrace Kotlin Compiler Frontend @mvndy_hd @jossiwolf
  60. Element.FUN Element.BLOCK Element.CALL_EXPRESSION Token.L_BRACE Token.R_BRACE Element.REFERENCE_EXPRESSION Element.VALUE_ARGUMENT_LIST Token.IDENTIFIER Token.LPAR Token.RPAR

    Element.STRING_TEMPLATE Element.LITERAL_STRING_TEMPLATE_ENTRY Token.OPEN_QUOTE Element.REGULAR_STRING_PART Token.OPEN_QUOTE #hg2compose @mvndy_hd @jossiwolf Text("Hello, World!") AST
  61. #hg2compose .kt Lexer (Scanner) Syntax Analysis Parsing Phase Generates tokens

    - Generates tokens to create an AST - PSI lays over AST Resolution Codegen Semantic Analysis performed on AST - PSI gets enhanced with descriptors - Symbol table generated; associated node w/ descriptor via BindingTrace Kotlin Compiler Frontend @mvndy_hd @jossiwolf
  62. #hg2compose .kt Lexer (Scanner) Syntax Analysis Parsing Phase Generates tokens

    - Generates tokens to create an AST - PSI lays over AST Resolution Codegen Semantic Analysis performed on AST - PSI gets enhanced with descriptors - Symbol table generated; associated node w/ descriptor via BindingTrace Kotlin Compiler Frontend @mvndy_hd @jossiwolf
  63. #hg2compose func() IR Transform Compiler Analysis Middle End “Lower” -

    Performs optimisations on IR - Removes dead code - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control- fl ow graph Backend - Takes optimized IR and performs more analysis, transformations + optimisations speci fi c to target CPU architecture - Multithreading func() Optimized IR < > Bytecode Target Program @mvndy_hd @jossiwolf
  64. #hg2compose func() IR Transform Compiler Analysis Middle End “Lower” -

    Performs optimisations on IR - Removes dead code - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control- fl ow graph Backend - Takes optimized IR and performs more analysis, transformations + optimisations speci fi c to target CPU architecture - Multithreading func() Optimized IR < > Bytecode Target Program @mvndy_hd @jossiwolf
  65. FUN name:HelloWorld visibility:public modality:FINAL <> (greeting:kotlin.String, $composer:androidx.compose.runtime.Composer?, $changed:kotlin.Int) returnType:kotlin.Unit annotations:

    Composable VALUE_PARAMETER name:greeting index:0 type:kotlin.String VALUE_PARAMETER name:$composer index:1 type:androidx.compose.runtime.Composer? [assignable] VALUE_PARAMETER name:$changed index:2 type:kotlin.Int BLOCK_BODY BLOCK type=kotlin.Unit origin=null SET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in <root>.HelloWorld' type=kotlin.Unit origin=null CALL 'public abstract fun startRestartGroup (key: kotlin.Int): androidx.compose.runtime.Composer declared in androidx.compose.runtime.Composer' type=androidx.compose.runtime.Composer origin=null $this: GET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in <root>.HelloWorld' type=androidx.compose.runtime.Composer? origin=null key: CONST Int type=kotlin.Int value=734305942 CALL 'public final fun sourceInformation (composer: androidx.compose.runtime.Composer, sourceInformation: kotlin.String): kotlin.Unit declared in androidx.compose.runtime.ComposerKt' type=kotlin.Unit origin=null composer: GET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in <root>.HelloWorld' type=androidx.compose.runtime.Composer? origin=null sourceInformation: CONST String type=kotlin.String value="C(HelloWorld):Test.kt" WHEN type=kotlin.Unit origin=IF BRANCH if: WHEN type=kotlin.Boolean origin=OROR BRANCH if: CALL 'public final fun not (): kotlin.Boolean [operator] declared in kotlin.Boolean' type=kotlin.Boolean origin=null $this: CALL 'public final fun EQEQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=null arg0: CALL 'public final fun and (other: kotlin.Int): kotlin.Int [infix] declared in kotlin.Int' type=kotlin.Int origin=null $this: GET_VAR '$changed: kotlin.Int declared in <root>.HelloWorld' type=kotlin.Int origin=null other: CONST Int type=kotlin.Int value=1 arg1: CONST Int type=kotlin.Int value=0 then: CONST Boolean type=kotlin.Boolean value=true BRANCH if: CONST Boolean type=kotlin.Boolean value=true then: CALL 'public final fun not (): kotlin.Boolean [operator] declared in kotlin.Boolean' type=kotlin.Boolean origin=null $this: CALL 'public abstract fun <get-skipping> (): kotlin.Boolean declared in androidx.compose.runtime.Composer' type=kotlin.Boolean origin=null $this: GET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in <root>.HelloWorld' type=androidx.compose.runtime.Composer? origin=null then: BLOCK type=kotlin.Unit origin=null CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline] declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null message: CONST String type=kotlin.String value="Hello" BRANCH if: CONST Boolean type=kotlin.Boolean value=true then: CALL 'public abstract fun skipToGroupEnd (): kotlin.Unit declared in androidx.compose.runtime.Composer' type=kotlin.Unit origin=null $this: GET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in <root>.HelloWorld' type=androidx.compose.runtime.Composer? origin=null BLOCK type=kotlin.Unit origin=null BLOCK type=kotlin.Unit origin=SAFE_CALL VAR IR_TEMPORARY_VARIABLE name:tmp0_safe_receiver type:androidx.compose.runtime.ScopeUpdateScope? [val] CALL 'public abstract fun endRestartGroup (): androidx.compose.runtime.ScopeUpdateScope? declared in androidx.compose.runtime.Composer' type=androidx.compose.runtime.ScopeUpdateScope? origin=null $this: GET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in <root>.HelloWorld' type=androidx.compose.runtime.Composer? origin=null WHEN type=kotlin.Unit origin=IF BRANCH if: CALL 'public final fun EQEQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=null arg0: GET_VAR 'val tmp0_safe_receiver: androidx.compose.runtime.ScopeUpdateScope? [val] declared in <root>.HelloWorld' type=androidx.compose.runtime.ScopeUpdateScope? origin=null arg1: CONST Null type=kotlin.Any? value=null then: CONST Null type=kotlin.Any? value=null BRANCH if: CONST Boolean type=kotlin.Boolean value=true then: CALL 'public abstract fun updateScope (block: kotlin.Function2<androidx.compose.runtime.Composer, kotlin.Int, kotlin.Unit>): kotlin.Unit declared in androidx.compose.runtime.ScopeUpdateScope' type=kotlin.Unit origin=null $this: GET_VAR 'val tmp0_safe_receiver: androidx.compose.runtime.ScopeUpdateScope? [val] declared in <root>.HelloWorld' type=androidx.compose.runtime.ScopeUpdateScope? origin=null block: BLOCK type=kotlin.Function2<androidx.compose.runtime.Composer, kotlin.Int, kotlin.Unit> origin=LAMBDA FUN LOCAL_FUNCTION_FOR_LAMBDA name : < anonymous> visibility:local modality:FINAL < > ($composer:androidx.compose.runtime.Composer?, $force:kotlin.Int) returnType:kotlin.Unit VALUE_PARAMETER name:$composer index:0 type:androidx.compose.runtime.Composer? VALUE_PARAMETER name:$force index:1 type:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing from='local final fun <anonymous> ($composer: androidx.compose.runtime.Composer?, $force: kotlin.Int): kotlin.Unit declared in <root>.HelloWorld' CALL 'public final fun HelloWorld (greeting: kotlin.String, $composer: androidx.compose.runtime.Composer?, $changed: kotlin.Int): kotlin.Unit declared in <root>' type=kotlin.Unit origin=null greeting: GET_VAR 'greeting: kotlin.String declared in <root>.HelloWorld' type=kotlin.String origin=null $composer: GET_VAR '$composer: androidx.compose.runtime.Composer? declared in <root>.HelloWorld.<anonymous>' type=androidx.compose.runtime.Composer? origin=null $changed: CALL 'public final fun or (other: kotlin.Int): kotlin.Int [infix] declared in kotlin.Int' type=kotlin.Int origin=null $this: GET_VAR '$changed: kotlin.Int declared in <root>.HelloWorld' type=kotlin.Int origin=null other: CONST Int type=kotlin.Int value=1 @mvndy_hd @jossiwolf
  66. @Composable fun HelloWorld(%composer: Composer?, %changed: Int) { %composer = %composer.startRestartGroup(

    <> ) if (%changed !== 0 || !%composer.skipping) { Text("Hello World”) } else { %composer.skipToGroupEnd() } %composer.endRestartGroup()? .updateScope { %composer: Composer?, %force: Int -> HelloWorld(%composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf
  67. A Composer connects Composables to Runtime, keeps track of groups

    that are used to build the Slot Table and positions it. #hg2compose @mvndy_hd @jossiwolf
  68. #hg2compose var name by remember { mutableStateOf("Bob") } @Composable fun

    Greeting(greeting: String) { Text("Hello, $greeting!") } Greeting(name) name = "Jossi" // Hello, Bob! // Hello, Jossi! Slot Table by Example @mvndy_hd @jossiwolf
  69. #hg2compose EMPTY EMPTY Group(4) State(“Bob”) var name by remember {

    mutableStateOf("Bob") } Slot Table by Example EMPTY EMPTY EMPTY EMPTY EMPTY @mvndy_hd @jossiwolf
  70. #hg2compose Group(4) State(“Bob”) Group(5) var name by remember { mutableStateOf("Bob")

    } Slot Table by Example EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY @Composable fun Greeting(greeting: String) { } Greeting(name) @mvndy_hd @jossiwolf
  71. #hg2compose EMPTY EMPTY Group(4) State(“Bob”) Group(5) Group(6) Group(7) Group(8) “Hello,

    Bob!” var name by remember { mutableStateOf("Bob") } Slot Table by Example @Composable fun Greeting(greeting: String) { Text("Hello, $greeting!”) } Greeting(name) @mvndy_hd @jossiwolf
  72. #hg2compose EMPTY EMPTY Group(4) State(“Bob”) Group(5) Group(6) Group(7) Group(8) “Hello,

    Jossi!” var name by remember { mutableStateOf("Bob") } @Composable fun Greeting(greeting: String) { Text("Hello, $greeting!”) } Greeting(name) name = "Jossi" Slot Table by Example @mvndy_hd @jossiwolf
  73. #hg2compose Text(text = name) Layout Layout name by remember {

    mutableStateOf("Bob") } Bob @mvndy_hd @jossiwolf
  74. #hg2compose @mvndy_hd @jossiwolf interface Applier<N> { val current: N fun

    onBeginChanges() {} fun onEndChanges() {} fun down(node: N) fun up() fun insertTopDown(index: Int, instance: N) fun insertBottomUp(index: Int, instance: N) fun remove(index: Int, count: Int) fun move(from: Int, to: Int, count: Int) fun clear() }
  75. 🏃Efficient Codegen Tips🏃 #hg2compose The point of Compose transformations is

    to reduce as many groups + group executions as possible - but you can help too! @mvndy_hd @jossiwolf
  76. #hg2compose Use key to make recomposition of lists cheaper! This

    helps Compose generate moveable groups, avoiding recomposition i.e. when the position of a list item changes. @mvndy_hd @jossiwolf
  77. #hg2compose Make sure to read state values at the lowest

    node of the tree possible You only want to recompose where you actually need the state :) @mvndy_hd @jossiwolf
  78. • Jorge Castillo for writing "Compose Internals" • Leland Richardson

    for sitting down with us and patiently answering our questions! #hg2compose Thank you!!! @mvndy_hd @jossiwolf