Slide 1

Slide 1 text

@mvndy_hd KotlinConf’23 Amsterdam Crash Course on the Kotlin Compiler

Slide 2

Slide 2 text

So you want to get to know the Kotlin compiler? @mvndy_hd @amanda@androiddev.social

Slide 3

Slide 3 text

@mvndy_hd @amanda@androiddev.social

Slide 4

Slide 4 text

@mvndy_hd @amanda@androiddev.social

Slide 5

Slide 5 text

Navigating the compiler is hard ! Documentation feels like it’s in bits and pieces !

Slide 6

Slide 6 text

I put together a gear kit @mvndy_hd @amanda@androiddev.social

Slide 7

Slide 7 text

Reverse engineering popular plugins Gear kit Kotlin compiler map Exchanging research notes Reverse engineering popular plugins @mvndy_hd @amanda@androiddev.social

Slide 8

Slide 8 text

.kt @mvndy_hd @amanda@androiddev.social

Slide 9

Slide 9 text

.kt Compiling - changes data format Lowering - simplifies data format

Slide 10

Slide 10 text

.kt bytecode .kt Compiler plugins can intercept any phase of the compiler

Slide 11

Slide 11 text

@mvndy_hd @amanda@androiddev.social

Slide 12

Slide 12 text

Frontend .kt *.class *.js *.so Backend @mvndy_hd @amanda@androiddev.social

Slide 13

Slide 13 text

K2 K1 Frontend .kt *.class *.js *.so Backend FE10- Fir-

Slide 14

Slide 14 text

Backend PSI BindingContext Frontend .kt *.class *.js *.so K1 Psi2Ir FE10- @mvndy_hd @amanda@androiddev.social

Slide 15

Slide 15 text

Backend PSI BindingContext Frontend .kt *.class *.js *.so K1 Psi2Ir Program Structure Interface (PSI) - layer in IntelliJ to help parse files and create syntactic/semantic code models FE10- @mvndy_hd @amanda@androiddev.social

Slide 16

Slide 16 text

Backend PSI BindingContext Frontend .kt *.class *.js *.so K1 Psi2Ir Program Structure Interface (PSI) - layer in IntelliJ to help parsing files and creating syntactic/semantic code models Binding Context - Big map which contains information of program in pair with PSI FE10- Descriptors hold context, scope, containing info, overrides, companion object etc.

Slide 17

Slide 17 text

.kt Optimized target code Tokens *.class *.js *.so src fun two() = 2 K1 Frontend: Lexical Analysis

Slide 18

Slide 18 text

.kt Optimized target code Tokens *.class *.js *.so src fun K1 Frontend: Lexical Analysis KtModifierKeywordToken.FUN_KEYWORD KtToken.Identifier KtSingleValueToken.LPAR KtSingleValueToken.RPAR KtToken.Identifier two ( ) = 2 KtSingleValueToken.EQ

Slide 19

Slide 19 text

.kt AST/PSI Optimized target code Tokens *.class *.js *.so src K1 Frontend: Parsing Abstract Syntax Tree (AST) Made of nodes that have direct mapping to the text ranges in src fun two() = 2

Slide 20

Slide 20 text

.kt AST/PSI Optimized target code Tokens *.class *.js *.so src K1 Frontend: Syntax Analysis Element.FUN Token.fun Element.VALUE_ARGUMENT_LIST Element.IDENTIFER Token.LPAR Token.RPAR Token.EQ Element.IDENTIFER_CONSTANT Token.INTEGER_LITERAL fun two() = 2

Slide 21

Slide 21 text

.kt AST/PSI Optimized target code Tokens *.class *.js *.so src K1 Frontend: Syntax Analysis FUN PsiElement(fun) PsiWhiteSpace PsiElement(IDENTIFIER) VALUE_PARAMETER_LIST PsiElement(LPAR) PsiElement(RPAR) PsiWhiteSpace PsiElement(EQ) PsiWhiteSpace INTEGER_CONSTANT PsiElement(INTEGER_LITERAL) fun two() = 2 PSIViewer

Slide 22

Slide 22 text

.kt AST/PSI Optimized target code Tokens *.class *.js *.so src K1 Frontend: Resolution fun two() = 2 FUN PsiElement(fun) PsiWhiteSpace PsiElement(IDENTIFIER) VALUE_PARAMETER_LIST PsiElement(LPAR) PsiElement(RPAR) PsiWhiteSpace PsiElement(EQ) PsiWhiteSpace INTEGER_CONSTANT PsiElement(INTEGER_LITERAL) BindingContext Descriptors

Slide 23

Slide 23 text

Backend PSI BindingContext Frontend .kt *.class *.js *.so K1 Psi2Ir @mvndy_hd @amanda@androiddev.social

Slide 24

Slide 24 text

Backend FIR Frontend .kt *.class *.js *.so K2 Fir2Ir Fir- @mvndy_hd @amanda@androiddev.social

Slide 25

Slide 25 text

Backend FIR Frontend .kt *.class *.js *.so K2 Fir2Ir Frontend Intermediate Representation (FIR) Fir- @mvndy_hd @amanda@androiddev.social

Slide 26

Slide 26 text

.kt AST/PSI Optimized target code Tokens *.class *.js *.so src K2 Frontend: Psi2Fir private val kotlinFiles = mutableListOf(1, 2, 3)

Slide 27

Slide 27 text

.kt AST/PSI Optimized target code Tokens *.class *.js *.so src K2 Frontend: Parsing PROPERTY MODIFIER_LIST PsiElement(private) PsiElement(val) PsiElement(IDENTIFIER) PsiElement(EQ) CALL_EXPRESSION REFERNCE_EXPRESSION PsiElement(IDENTIFIER) VALUE_ARGUMENT INTEGER_CONSTANT PsiElement(INTEGER) PsiElement(COMMA) VALUE_ARGUMENT INTEGER_CONSTANT PsiElement(INTEGER) PsiElement(COMMA) VALUE_ARGUMENT INTEGER_CONSTANT PsiElement(INTEGER) private val kotlinFiles = mutableListOf(1, 2, 3)

Slide 28

Slide 28 text

.kt AST/PSI Raw FIR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } *.class *.js *.so src K2 Frontend: Psi2Fir FILE: mutableList.kt private? final? Val kotlinFiles: = ^kotlinFiles MutableList( vararg(Int(1), Int(2), Int(3) ) private val kotlinFiles = mutableListOf(1, 2, 3)

Slide 29

Slide 29 text

.kt AST/PSI Raw FIR FIR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit *.class *.js *.so src K2 Frontend: Resolution FILE: mutableList.kt private? final? Val kotlinFiles: = ^kotlinFiles MutableList( vararg(Int(1), Int(2), Int(3) ) private val kotlinFiles = mutableListOf(1, 2, 3) Resolve Imports Super types Sealed class inheritors Types Status Contracts Implicit Types Body Resolve

Slide 30

Slide 30 text

.kt AST/PSI Raw FIR FIR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit *.class *.js *.so src K2 Frontend: Resolution private val kotlinFiles = mutableListOf(1, 2, 3) FILE: mutableList.kt lvar listVar: R|kotlin/collections/MutableList | = R|kotlin/collections/mutableListOf |(vararg(Int(1), Int(2), Int(3)))

Slide 31

Slide 31 text

.kt AST/PSI Raw FIR FIR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit *.class *.js *.so src K2 Frontend: Resolution CHECKERS private val kotlinFiles = mutableListOf(1, 2, 3) FILE: mutableList.kt lvar listVar: R|kotlin/collections/MutableList | = R|kotlin/collections/mutableListOf |(vararg(Int(1), Int(2), Int(3)))

Slide 32

Slide 32 text

.kt AST/PSI Raw FIR FIR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit *.class *.js *.so src K2 Frontend: Resolution VALIDATED private val kotlinFiles = mutableListOf(1, 2, 3) FILE: mutableList.kt lvar listVar: R|kotlin/collections/MutableList | = R|kotlin/collections/mutableListOf |(vararg(Int(1), Int(2), Int(3)))

Slide 33

Slide 33 text

Backend FIR Frontend .kt *.class *.js *.so K2 Fir2Ir Fir-

Slide 34

Slide 34 text

K2 K2 JVM JS WASM Native Backend .kt *.class *.js *.so K1 FIR

Slide 35

Slide 35 text

.kt *.class *.js *.so Frontend JVM IR backend Isakova, Sletvana. “What Everyone Must Know about the New Kotlin K2 Compiler.” YouTube, JetBrains, 7 Oct. 2021, https://www.youtube.com/watch?v=iTdJJq_LyoY&t=1785s&ab_channel=KotlinbyJetBrains. JS IR backend Native IR backend IR Generator + optimizer JVM bytecode generator + optimizer JavaScript generator + optimizer LLVM bitcode generator + optimizer IR IR IR Syntax tree + semantic info

Slide 36

Slide 36 text

K2 K2 JVM JS WASM Native Backend .kt *.class *.js *.so K1 FIR JVM

Slide 37

Slide 37 text

.kt AST/PSI Raw FIR FIR IR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing *.class *.js *.so src JVM Backend: IR Codegen private val kotlinFiles = mutableListOf(1, 2, 3) PROPERTY list visibility:public modality:FINAL [val] FIELD PROPERTY_BACKING_FIELD name:mutableListFun type:kotlin.Function1, kotlin.collections.MutableList> visibility:private [final,static] EXPRESSION_BODY FUN_EXPR type=kotlin.Function1, kotlin.collections.MutableList> origin=ANONYMOUS_FUNCTION FUN LOCAL_FUNCTION name: visibility:local modality:FINAL <> (l:kotlin.collections.MutableList) returnType:kotlin.collections.MutableList VALUE_PARAMETER name:l index:0 type:kotlin.collections.MutableList BLOCK_BODY RETURN type=kotlin.Nothing from='local final fun (l: kotlin.collections.MutableList): kotlin.collections.MutableList declared in .mutableListFun' CALL 'public final fun CHECK_NOT_NULL (arg0: T0 of kotlin.internal.ir.CHECK_NOT_NULL?): {T0 of kotlin.internal.ir.CHECK_NOT_NULL & Any} declared in kotlin.internal.ir' type=kotlin.Nothing origin=EXCLEXCL : kotlin.Nothing arg0: CONST Null type=kotlin.Nothing? value=null

Slide 38

Slide 38 text

.kt AST/PSI Raw FIR FIR IR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing *.class *.js *.so src JVM Backend: IR Codegen Intermediate Representation (IR) - abstracted representation for CPU- level architecture - IR Analysis produces control flow, call stacks PROPERTY list visibility:public modality:FINAL [val] FIELD PROPERTY_BACKING_FIELD name:mutableListFun type:kotlin.Function1, kotlin.collections.MutableList> visibility:private [final,static] EXPRESSION_BODY FUN_EXPR type=kotlin.Function1, kotlin.collections.MutableList> origin=ANONYMOUS_FUNCTION FUN LOCAL_FUNCTION name: visibility:local modality:FINAL <> (l:kotlin.collections.MutableList) returnType:kotlin.collections.MutableList VALUE_PARAMETER name:l index:0 type:kotlin.collections.MutableList BLOCK_BODY RETURN type=kotlin.Nothing from='local final fun (l: kotlin.collections.MutableList): kotlin.collections.MutableList declared in .mutableListFun' CALL 'public final fun CHECK_NOT_NULL (arg0: T0 of kotlin.internal.ir.CHECK_NOT_NULL?): {T0 of kotlin.internal.ir.CHECK_NOT_NULL & Any} declared in kotlin.internal.ir' type=kotlin.Nothing origin=EXCLEXCL : kotlin.Nothing arg0: CONST Null type=kotlin.Nothing? value=null

Slide 39

Slide 39 text

.kt AST/PSI Raw FIR FIR IR Lowered IR Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing *.class *.js *.so FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing from='public fi nal fun two (): kotlin.Int d eclared in ' CONST Int type=kotlin.Int src JVM Backend: IR Lowerings Lowered IR - Improve operations around performance, concurrency - Resource + storage decisions 3^2 3*3

Slide 40

Slide 40 text

.kt AST/PSI Raw FIR FIR IR Lowered IR Target code Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing *.class *.js *.so FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing from='public fi nal fun two (): kotlin.Int d eclared in ' CONST Int type=kotlin.Int *.class *.js *.so src JVM Backend: Bytecode Codegen private val kotlinFiles = mutableListOf(1, 2, 3) // access flags 0x12 // signature Ljava/util/ArrayList // declaration: kotlinFiles extends // java.util.ArrayList private final Ljava/util/ArrayList; kotlinFiles

Slide 41

Slide 41 text

Reverse Engineering Plugins sam-with- receiver compose- compiler- plugin @mvndy_hd @amanda@androiddev.social

Slide 42

Slide 42 text

Reverse Engineering Plugins sam-with- receiver compose- compiler- plugin kotlin/plugins/sam-with-receiver @mvndy_hd @amanda@androiddev.social

Slide 43

Slide 43 text

@Retention(RetentionPolicy.RUNTIME) public @interface SamWithReceiver1 { } @SamWithReceiver1 public interface Job { void execute(String context, int other); } public class Manager { public void doJob(Job job) {} } sam-with-receiver example @mvndy_hd @amanda@androiddev.social

Slide 44

Slide 44 text

import lib.Manager fun main(args: Array) { println("Hello World!") val manager = Manager() manager.doJob { other -> println("Context: $this | Other: $other") } manager.doJob { context, other -> println("Context: $context | Other: $other") } } } sam-with-receiver not configured SAM interface @mvndy_hd @amanda@androiddev.social

Slide 45

Slide 45 text

import lib.Manager fun main(args: Array) { println("Hello World!") val manager = Manager() manager.doJob { other -> println("Context: $this | Other: $other") } manager.doJob { context, other -> println("Context: $context | Other: $other") } } } sam-with-receiver not configured Anonymous class @mvndy_hd @amanda@androiddev.social

Slide 46

Slide 46 text

import lib.Manager fun main(args: Array) { println("Hello World!") val manager = Manager() manager.doJob { other -> println("Context: $this | Other: $other") } manager.doJob { context, other -> println("Context: $context | Other: $other") } } } Required: (String!, Int) -> Unit Found: (String!) -> (Int) sam-with-receiver not configured

Slide 47

Slide 47 text

plugins { id 'org.jetbrains.kotlin.plugin.sam.with.receiver' version '1.8.10' } samWithReceiver { annotation("lib.SamWithReceiver1") } @mvndy_hd @amanda@androiddev.social

Slide 48

Slide 48 text

import lib.Manager fun main(args: Array) { println("Hello World!") val manager = Manager() manager.doJob { other -> println("Context: $this | Other: $other") } manager.doJob { context, other -> println("Context: $context | Other: $other") } } } Required: (String!.(Int) -> Unit)! Found: String!.(Int, Any) -> Unit sam-with-receiver configured

Slide 49

Slide 49 text

sam-with-receiver @mvndy_hd @amanda@androiddev.social sam-with-receiver.cli sam-with-receiver.common sam-with-receiver.k1 sam-with-receiver.k2 FirSamWithReceiverConventionTransformer FirSamWithReceiverConventionRegistrar C C build.gradle.kts

Slide 50

Slide 50 text

dependencies { …(project(“:compiler:fir:cones”)) …(project(“:compiler:fir:tree”)) …(project(“:compiler:fir:resolve”)) …(project(“:compiler:fir:checkers”)) … …(project(“:compiler:fir:entrypoint”)) compileOnly(intellijCore()) … } all FIR-related modules are in :compiler:fir all compiler modules lays in :compiler

Slide 51

Slide 51 text

dependencies { …(project(“:compiler:fir:cones”)) …(project(“:compiler:fir:tree”)) …(project(“:compiler:fir:resolve”)) …(project(“:compiler:fir:checkers”)) … …(project(“:compiler:fir:entrypoint”)) compileOnly(intellijCore()) … } - all nodes of FIR tree - classes for FIR type system - resolution main logic - for diagnostics - FIR compiler entry @mvndy_hd @amanda@androiddev.social

Slide 52

Slide 52 text

CommandLineProcessor Extension Extension Extension Plugin Subplugin ComponentRegistrar Most, Kevin. “Kotlinconf 2018 - Writing Your First Kotlin Compiler Plugin by Kevin Most.” YouTube, JetBrains, 12 Oct. 2018, https://www.youtube.com/watch?v=w-GMlaziIyo&ab_channel=JetBrains.

Slide 53

Slide 53 text

sam-with-receiver @mvndy_hd @amanda@androiddev.social sam-with-receiver.cli sam-with-receiver.common sam-with-receiver.k1 sam-with-receiver.k2 FirSamWithReceiverConventionTransformer FirSamWithReceiverConventionRegistrar C C build.gradle.kts

Slide 54

Slide 54 text

class FirSamWithReceiverExtensionRegistrar( private val annotations: List ) : FirExtensionRegistrar() { override fun ExtensionRegistrarContext.configurePlugin() { +::FirSamWithReceiverConventionTransformer.bind(annotations) } }

Slide 55

Slide 55 text

.kt AST/PSI Raw FIR FIR IR Lowered IR Target code Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing *.class *.js *.so FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing from='public fi nal fun two (): kotlin.Int d eclared in ' CONST Int type=kotlin.Int *.class *.js *.so src class FirSamWithReceiverExtensionRegistrar( private val annotations: List ) : FirExtensionRegistrar() { override fun ExtensionRegistrarContext.configurePlugin() { +::FirSamWithReceiverConventionTransformer.bind(annotations) } } FirExtensionRegistrar() +::FirSamWithReceiverConventionTransformer.bind(annotations)

Slide 56

Slide 56 text

class FirSamWithReceiverConventionTransformer( private val annotations: List, session: FirSession ) : FirSamConversionTransformerExtension(session) { override fun getCustomFunctionalTypeForSamConversion( function: FirSimpleFunction ): ConeLookupTagBasedType? { val containingClassSymbol = function.containingClassLookupTag()? .toFirRegularClassSymbol(session) ?: return null return runIf( containingClassSymbol.resolvedAnnotationClassIds .any { it.asSingleFqName().asString() in annotations } ) { val parameterTypes = function.valueParameters.map { it.returnTypeRef.coneType } if (parameterTypes.isEmpty()) return null createFunctionalType( parameters = parameterTypes.subList(1, parameterTypes.size), receiverType = parameterTypes[0], rawReturnType = function.returnTypeRef.coneType, isSuspend = function.isSuspend ) } } } class FirSamWithReceiverConventionTransformer( ) : FirSamConversionTransformerExtension(session) { override fun getCustomFunctionalTypeForSamConversion( ): ConeLookupTagBasedType? { val containingClassSymbol = function.containingClassLookupTag()? .toFirRegularClassSymbol(session) ?: return null return runIf( containingClassSymbol.resolvedAnnotationClassIds .any { it.asSingleFqName().asString() in annotations } } } Looks for registered SAM interface

Slide 57

Slide 57 text

class FirSamWithReceiverConventionTransformer( private val annotations: List, session: FirSession ) : FirSamConversionTransformerExtension(session) { override fun getCustomFunctionalTypeForSamConversion( function: FirSimpleFunction ): ConeLookupTagBasedType? { val containingClassSymbol = function.containingClassLookupTag()? .toFirRegularClassSymbol(session) ?: return null return runIf( containingClassSymbol.resolvedAnnotationClassIds .any { it.asSingleFqName().asString() in annotations } ) { val parameterTypes = function.valueParameters.map { it.returnTypeRef.coneType } if (parameterTypes.isEmpty()) return null createFunctionalType( parameters = parameterTypes.subList(1, parameterTypes.size), receiverType = parameterTypes[0], rawReturnType = function.returnTypeRef.coneType, isSuspend = function.isSuspend ) } } } class FirSamWithReceiverConventionTransformer( ) : FirSamConversionTransformerExtension(session) { override fun getCustomFunctionalTypeForSamConversion( return runIf( ) { val parameterTypes = function.valueParameters.map { it.returnTypeRef.coneType } if (parameterTypes.isEmpty()) return null createFunctionalType( parameters = parameterTypes.subList(1, parameterTypes.size), receiverType = parameterTypes[0], rawReturnType = function.returnTypeRef.coneType, isSuspend = function.isSuspend ) } } } Changes args so first param is receiver

Slide 58

Slide 58 text

import lib.Manager fun main(args: Array) { println("Hello World!") val manager = Manager() manager.doJob { other -> println("Context: $this | Other: $other") } manager.doJob { context, other -> println("Context: $context | Other: $other") } } } sam-with-receiver configured

Slide 59

Slide 59 text

Reverse Engineering Plugins sam-with- receiver compose- compiler- plugin androidx/compose/compiler

Slide 60

Slide 60 text

@Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } @mvndy_hd @amanda@androiddev.social

Slide 61

Slide 61 text

Group(5) EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } HelloWorld(“KotlinConf”) @mvndy_hd @amanda@androiddev.social @Composable fun HelloWorld(greeting: String) { }

Slide 62

Slide 62 text

Group(5) Group(6) Group(7) Group(8) “Hello KotlinConf!” EMPTY EMPTY @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } HelloWorld(“KotlinConf”) @mvndy_hd @amanda@androiddev.social Text("Hello, $greeting!")

Slide 63

Slide 63 text

@Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } @mvndy_hd @amanda@androiddev.social

Slide 64

Slide 64 text

@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) } }

Slide 65

Slide 65 text

@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) } }

Slide 66

Slide 66 text

@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) } }

Slide 67

Slide 67 text

@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) } }

Slide 68

Slide 68 text

@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) } }

Slide 69

Slide 69 text

.kt AST/PSI Raw FIR FIR IR Lowered IR Target code Optimized target code Tokens FILE: funAssignment.kt public? fi nal? fun two(): { ^two IntegerLiteral(2) } package public fun two(): kotlin.Unit FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing *.class *.js *.so FILE fqName: fi leName :/funAssignment.kt FUN name:two visibility: public modality:FINAL <> () returnType:kotlin.Int BLOCK_BODY RETURN type=kotlin.Nothing from='public fi nal fun two (): kotlin.Int d eclared in ' CONST Int type=kotlin.Int *.class *.js *.so src class ComposeComponentRegistrar : @Suppress(“DEPRECATION”) ComponentRegistrar { override fun registerProjectComponents( project: MockProject, configuration: CompilerConfiguration ) { if (checkCompilerVersion(configuration)) { registerCommonExtensions(project) IrGenerationExtension.registerExtension(…) } }

Slide 70

Slide 70 text

class ComposeIrGenerationExtension(…) : IrGenerationExtension { … override fun generate(…) { … // Verify that our transformations didn't break something if (validateIr) validateIr(moduleFragment, pluginContext.irBuiltIns) } }

Slide 71

Slide 71 text

class ComposeIrGenerationExtension(…) : IrGenerationExtension { … override fun generate(…) { … // Input check. This should always pass… if (validateIr) validateIr(moduleFragment, pluginContext.irBuiltIns) // create a symbol remapper to be used across all transforms val symbolRemapper = ComposableSymbolRemapper() … ClassStabilityTransformer(…).lower(moduleFragment) fun A(@Composable f() -> Unit) fun A(Composer<*>, Int -> Unit)

Slide 72

Slide 72 text

// transform all composable functions to have an extra synthetic composer // parameter. ComposerParamTransformer( pluginContext, symbolRemapper, decoysEnabled, metrics, ).lower(moduleFragment) … // transform calls to the currentComposer to just use the local parameter from // previous transform ComposerIntrinsicTransformer(pluginContext, decoysEnabled).lower(moduleFragment) ComposeIrGenerationExtension.kt

Slide 73

Slide 73 text

// $changed[n] for (i in 0 until changedParamCount(realValueParams, ownerFn.thisParamCount)) { if (argIndex < ownerFn.valueParameters.size) { it.putValueArgument( argIndex++, irConst(0) ) } else { error("1. expected value parameter count to be higher: ${this.dumpSrc()}") } } ComposerParamTransformer.kt @mvndy_hd @amanda@androiddev.social

Slide 74

Slide 74 text

// $changed[n] for (i in 0 until changedParamCount(realValueParams, ownerFn.thisParamCount)) { if (argIndex < ownerFn.valueParameters.size) { it.putValueArgument( argIndex++, irConst(0) ) } else { error("1. expected value parameter count to be higher: ${this.dumpSrc()}") } } ComposerParamTransformer.kt @mvndy_hd @amanda@androiddev.social

Slide 75

Slide 75 text

val IrFunction.thisParamCount get() = contextReceiverParametersCount + (if (dispatchReceiverParameter != null) 1 else 0) + (if (extensionReceiverParameter != null) 1 else 0) fun changedParamCount(realValueParams: Int, thisParams: Int): Int { val totalParams = realValueParams + thisParams if (totalParams == 0) return 1 // There is always at least 1 changed param return ceil( totalParams.toDouble() / SLOTS_PER_INT.toDouble() ).toInt() } ComposableFunctionBodyTransformer.kt Calculates the number of 'changed' params needed

Slide 76

Slide 76 text

@Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } @mvndy_hd @amanda@androiddev.social

Slide 77

Slide 77 text

1 MODULE_FRAGMENT name: 2 FILE fqName: fileName:/Test.kt 3 FUN name:HellowWorld visibility:public modality:FINAL <> (greeting:kotlin.String) returnType: Kotlin.unit 4 annotations: 5 Composable 6 VALUE_PARAMETER name: greeting index:0 type:kotlin.String 7 BLOCK_BODY 1 MODULE_FRAGMENT name: 2 FILE fqName: fileName:/Test.kt 3 FUN name:HelloWorld visibility:public modality:FINAL <> (greeting:kotlin.String, $composer:androidx.compose.runtime.Composer?, $changed:kotlin.Int) returnType: Kotlin.unit 4 annotations: 5 Composable 6 VALUE_PARAMETER name:greeting index:0 type:kotlin.String 7 VALUE_PARAMETER name:$composer index:1 type:androidx.compose.runtime.Composer?, [assignable] 8 VALUE_PARAMETER name:$changed index:2 type:kotlin.Int 9 BLOCK_BODY 10 BLOCK type=kotlin.Unit origin=null 11 SET_VAR ‘$composer: android.compose.runtime.Composer? [assignable] declared in .HelloWorld’ type=kotlin.Unit origin=null 12 CALL `public abstract fun startRestartGroup (key: Kotlin:Int): androids.compose.runtime.Composer declared in androidx.compose.runtime.Composer’ type=androidx.compose.runtime.Composer? origin=null 13 $this: GET_VAR ‘$composer: androidx.compose.runtime.Composer? [assignment] declared in .Hello-World’ type:androidx.compose.runtime.Composer? origin=null 14 key: CONST Int type=kotlin.Int value=73430592 15 CALL ‘public final fun sourceInformation(composer: androidx.compose.runtime.ComposerKt’ type=kotlin.Unit origin=null 16 composer: GET_VAR ‘$composer: androidx.compose.runtime.Composer? [assignable] declared in .HelloWorld’ type=kotlin.Unit origin=null 17 sourceInformation: CONST String type=kotlin.String value=“((HelloWorld):Test.kt” 18 WHEN type=kotlin.Boolean origin=IF 19 BRANCH 20 if: WHEN type=kotlin.Boolean origin=OROR 21 BRANCH 22 if: CALL ‘public final fun not (): kotlin.Boolean [operator] declared in kotlin.Boolean’ type=kotlin.Boolean origin=null 23 $this: CALL ‘public final fun EQEQEQ (arg0: kotlin.Any?, arg1: kotlin.Any?): kotlinBoolean declared in kotlin.internal.ir' type=kotlinBoolean \. origin=null 24 arg0: CALL ‘public final fun and (other: kotlin.Int): kotlin.Int [infix] declared in kotlin.Int type=kotlin.Int origin=null 25 $this: GET_VAR ‘$changed: kotlin.Int declared in .HelloWorld’ type=kotlin.Int origin=null 26 other: CONST Int type=kotlin.Int value=1 27 arg1: CONST Int type=kotlin.Int value=0 28 then: CONST Boolean type=kotlin.Boolean value=true 29 BRANCH 30 if: CONST Boolean type=kotlin.Boolean value=true @mvndy_hd @amanda@androiddev.social

Slide 78

Slide 78 text

@mvndy_hd @amanda@androiddev.social Exchange notes! What now?

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

Thank you,
 and don’t forget
 to vote @mvndy_hd KotlinConf’23 Amsterdam