Slides from Amanda Hinchman's and Jossi Wolf's talk "A Hitchhiker's Guide to Compose" given at Droidcon Berlin 2021. Make sure to check out 2022's updated version on our profiles!
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
"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
#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
#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
SET_VAR '$composer: androidx.compose.runtime.Composer? [assignable] declared in .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 .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 .HelloWorld' type=androidx.compose.runtime.Composer? origin=null
RETURN type=kotlin.Nothing from='local final fun ($composer: androidx.compose.runtime.Composer?, $force: kotlin.Int): kotlin.Unit declared in .HelloWorld'
CALL 'public final fun HelloWorld (greeting: kotlin.String, $composer: androidx.compose.runtime.Composer?, $changed: kotlin.Int): kotlin.Unit declared in ' type=kotlin.Unit origin=null
greeting: GET_VAR 'greeting: kotlin.String declared in .HelloWorld' type=kotlin.String origin=null
$composer: GET_VAR '$composer: androidx.compose.runtime.Composer? declared in .HelloWorld.' 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 .HelloWorld' type=kotlin.Int origin=null
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
#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
#hg2compose Group(4) State(“Bob”) Group(5) var name by remember { mutableStateOf("Bob") } Slot Table by Example EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY @Composable
#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
🏃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
#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
#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