0% found this document useful (0 votes)
4 views

The Java Virtual Machine & the Kotlin Compiler

The document provides an overview of Kotlin and its relationship with the Java Virtual Machine (JVM), detailing the compilation process and memory organization. It explains how Kotlin can be compiled into JVM bytecode, its interoperability with Java, and its use in Android development. Additionally, it covers aspects of garbage collection, just-in-time compilation, and the structure of the Kotlin compiler.

Uploaded by

Triveni Patle
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

The Java Virtual Machine & the Kotlin Compiler

The document provides an overview of Kotlin and its relationship with the Java Virtual Machine (JVM), detailing the compilation process and memory organization. It explains how Kotlin can be compiled into JVM bytecode, its interoperability with Java, and its use in Android development. Additionally, it covers aspects of garbage collection, just-in-time compilation, and the structure of the Kotlin compiler.

Uploaded by

Triveni Patle
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 103

Kotlin

The Java
Virtual Machine
& the Kotlin
Compiler
@kotlin | Developed by JetBrains
The Java language

● Was created in 1995.

● Is an OOP language with strong static typing.

● Has Just-in-time (JIT) compilation.

● Uses the Java Virtual Machine (JVM).

● Has a garbage collector, meaning you can allocate memory and it will be freed automatically.
Compilation process – Java vs C
Java bytecode
public class examples/Main {

public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
public class Main { LOCALVARIABLE this Lorg/examples/Main; L0 L1 0
MAXSTACK = 1
public static void main(String[] args) {
MAXLOCALS = 1
System.out.print("Hello, World!");
} public static main([Ljava/lang/String;)V
} L0
LINENUMBER 5 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Hello, World!"
ICONST_0
ANEWARRAY java/lang/Object
INVOKEVIRTUAL java/io/PrintStream.print
(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
POP
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
The JVM under the hood

JVM

Class loading
services Interpreter

JIT-compiler
.сlass files Memory management
with bytecodes bytecode => machine code
(heap, GC)
translation services
Memory organisation

JVM memory is divided into two parts:


● Static memory, or non-heap memory, is created when the JVM starts, and it primarily stores
class structures, fields, method data, and the code for methods and constructors.
○ Loaded classes
○ Stacks of all threads
○ Service memory for the JVM itself
○ Small – roughly 1024 KB for stacks
● Heap memory is the runtime data area shared among all JVM threads, and it is used to
allocate memory for all Java objects.
○ All objects created during a program’s execution
○ Large – from several MB up to several TB
Weak generational hypothesis

According to the weak generational hypothesis, objects


tend to die young.

total size of objects


A related assumption is that, as an object lives longer,
the likelihood will be that it will continue to live
increases.

lifetime
Generations

The memory of a program can be divided into two generations:

● Young
● Old

Garbage collection (GC) can be similarly divided:


● Small GC clears only objects from young generation
● Full GC clears objects from both generations
Dead objects

Stack

Young generation Old generation

A D
C

B E
Serial garbage collector
The first garbage collector created was the serial garbage collector, which is single-threaded, and the parallel
and CMS garbage collectors are based on it.

● In the serial garbage collector, the heap is divided into 4 areas:


○ Eden – Roughly 8/10 of the young generation
○ Survivor 0 – Roughly 1/10 of the young generation
○ Survivor 1 – Roughly 1/10 of the young generation
○ Tenured – Roughly 2/3 of the heap

● (Almost) All objects are created in the Eden area.

Young generation Old generation

Eden S0 S1 Tenured
How garbage collection works
Current state:

No available space in Eden

Before GC:

Memory to free, dead objects

Surviving objects

After GC:
How garbage collection works
Current state:

No available space in Eden

Before GC:

After GC:
How garbage collection works
Current state:

No available space in Eden

Before GC:

After GC:
How garbage collection works
Current state:

No available space in Eden

Before GC:

After GC:

Not enough space to move objects from Eden to Survivor 1


Just-in-time compilation

● Program profiling occurs at runtime.

● Pieces of code are compiled for a specific platform to optimize the execution time.

Interpreting a command is much slower than executing it directly on the processor.

Why, then, do we need the interpreter?


Just-in-time compilation

Why, then, do we need the interpreter?

Interpreter JIT-compiler

Starts working almost instantly. Kicks in after a long delay (needs time for
vs
● ●
optimizations).
● The performance of the executable
code is poor. ● The performance of the executable
(compiled) code is high.
Just-in-time compilation

What sorts of JIT code are worth compiling?

Code that will take a long time to run or code that runs frequently, because the compilation
overhead will be covered by the profit from having optimized execution.
Just-in-time compilation

How can we understand which pieces of code will take a long


time to execute?

Some guy named Alan Turing said it is impossible.

But empirically it is possible. If a piece of code took a long


enough time to execute once, then most likely the same will
be true in the future.
The JVM under the hood

Why does the compiler need to access the interpreter?

JVM

Class loading
services Interpreter

JIT-compiler
.сlass files Memory management
with bytecodes bytecode => machine code
(heap, GC)
translation services
The JVM under the hood

Why does the compiler need to access the interpreter?

class PiUtils {
private static final double PI = 3.141592653589;

public static double getPiSquared() {


return PI * PI;
}
}

public static double getPiSquared() {


return 9.869604401084375;
}
The JVM under the hood

Why does the compiler need to access the interpreter?

class PiUtils {
private static final double PI = 4;

public static double getPiSquared() {


return PI * PI;
}
}
Reflection

public static double getPiSquared() {


return 9.869604401084375;
}
The Kotlin language

● Can be compiled into JVM bytecode.

● Is interoperable with Java.

○ Can work in Java projects.

○ Can use Java libraries.

● Is safe and concise.

● Provides the ability to integrate into the compilation process (compiler plugins).

● Is the main development language for Android applications, according to Google.


Kotlin compiler

Parser Frontend Backend

.kt files
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

JVM JavaScript Native Other


(WASM, Python, etc.?)
Parsing in a nutshell
Source code List of tokens
'2 * 7 + 3' '2' '*' '7' '+' '3'

CST Sum AST


Sum
left operand right operand

Product '+' '3' Product 3

left operand right operand

'2' '*' '7' 2 7


Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Splitting the program into tokens JavaScript Native


JVM Other
(keywords, identifiers, etc.). (WASM, Python, etc.?)
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Converting the list of tokens JavaScript Native


JVM Other
into CST or AST (WASM, Python, etc.?)
Kotlin compiler: PSI
Kotlin compiler: PSI
fun hello(user: String) = ==.

FUN

'fun' ' ' 'hello' VALUE_PARAMETER_LIST ' ' '=' ' ' ==.

'(' VALUE_PARAMETER ')'

'user' ':' ' ' ==.

'String'
Kotlin compiler: PSI
fun hello(user: String) = println("Hello, $user")

CALL_EXPRESSION

REFERENCE_EXPRESSION VALUE_ARGUMENT_LIST

'println' '(' VALUE_ARGUMENT ')'

STRING_TEMPLATE

'\"' LITERAL_STRING_TEMPLATE_ENTRY SHORT_STRING_TEMPLATE_ENTRY '\"'

'Hello, ' '$' REFERENCE_EXPRESSION

'user'
Kotlin compiler: PSI
fun hello(user: String) = println("Hello, $user")

CALL_EXPRESSION

REFERENCE_EXPRESSION VALUE_ARGUMENT_LIST

'println' '(' VALUE_ARGUMENT ')'

STRING_TEMPLATE

'\"' LITERAL_STRING_TEMPLATE_ENTRY SHORT_STRING_TEMPLATE_ENTRY '\"'

'Hello, ' '$' REFERENCE_EXPRESSION

'user'
Kotlin compiler: PSI
fun hello(user: String) = println("Hello, $user")

CALL_EXPRESSION

REFERENCE_EXPRESSION VALUE_ARGUMENT_LIST

'println' '(' VALUE_ARGUMENT ')'

STRING_TEMPLATE

'\"' LITERAL_STRING_TEMPLATE_ENTRY SHORT_STRING_TEMPLATE_ENTRY '\"'

'Hello, ' '$' REFERENCE_EXPRESSION

'user'
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

The so-called FIR tree is


being built,. It’s an
JVM JavaScript Native Other
analogue of the PSI,
(WASM, Python, etc.?)
but it’s mutable.
FIR? Another tree!
fun hello(user: String) = println("Hello, $user")

SimpleFunction (name = hello)

valueParameters body returnTypeRef

ValueParameter (name = user) ==. ResolvedTypeRef(=kotlin/Unit)

returnTypeRef

ResolvedTypeRef(=kotlin/String)
FIR? Another tree!
fun hello(user: String) = println("Hello, $user")

SimpleFunction (name = hello)

valueParameters body returnTypeRef

ValueParameter (name = user) ==. ResolvedTypeRef(=kotlin/Unit)

returnTypeRef

ResolvedTypeRef(=kotlin/String)
FIR? Another tree!
fun hello(user: String) = println("Hello, $user")

StringConcatenationCall

argumentList typeRef

ArgumentList ResolvedTypeRef (=kotlin/String)

arguments arguments

ConstExpression (value = "Hello, ", kind = String) FunctionCall

typeRef explictReceiver calleeReference typeRef

ResolvedTypeRef(=kotlin/String) QualifiedAccessExpression ResolvedNameReference (name = toString) ResolvedTypeRef (=kotlin/String)

calleeReference typeRef

ResolvedNameReference (name = user) ResolvedTypeRef (=kotlin/String)


FIR? Another tree!
fun hello(user: String) = println("Hello, $user")

StringConcatenationCall

argumentList typeRef

ArgumentList ResolvedTypeRef (=kotlin/String)

arguments arguments

ConstExpression (value = "Hello, ", kind = String) FunctionCall

typeRef explictReceiver calleeReference typeRef

ResolvedTypeRef(=kotlin/String) QualifiedAccessExpression ResolvedNameReference (name = toString) ResolvedTypeRef (=kotlin/String)

calleeReference typeRef

ResolvedNameReference (name = user) ResolvedTypeRef (=kotlin/String)


FIR? Another tree!
fun hello(user: String) = println("Hello, $user")

StringConcatenationCall

argumentList typeRef

ArgumentList ResolvedTypeRef (=kotlin/String)

arguments arguments

ConstExpression (value = "Hello, ", kind = String) FunctionCall

typeRef explictReceiver calleeReference typeRef

ResolvedTypeRef(=kotlin/String) QualifiedAccessExpression ResolvedNameReference (name = toString) ResolvedTypeRef (=kotlin/String)

calleeReference typeRef

ResolvedNameReference (name = user) ResolvedTypeRef (=kotlin/String)


FIR? Another tree!
fun hello(user: String) = println("Hello, $user")

StringConcatenationCall

argumentList typeRef

ArgumentList ResolvedTypeRef (=kotlin/String)

arguments arguments

ConstExpression (value = "Hello, ", kind = String) FunctionCall

typeRef explictReceiver calleeReference typeRef

ResolvedTypeRef(=kotlin/String) QualifiedAccessExpression ResolvedNameReference (name = toString) ResolvedTypeRef (=kotlin/String)

calleeReference typeRef

ResolvedNameReference (name = user) ResolvedTypeRef (=kotlin/String)


FIR: desugaring

if (b) { when {
println("Hello") b => println("Hello")
}
}

val <iterator> = list.iterator()


for (s in list) { while (<iterator>.hasNext()) {
println(s) val s = <iterator>.next()
} println(s)
}

val <destruct> = "a" to "b"


val (a, b) = "a" to "b" val a = <destruct>.component1()
val b = <destruct>.component2()
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Resolving the FQ names

JVM JavaScript Native Other


(WASM, Python, etc.?)
Kotlin compiler: resolve

fun myFunction() { fun myFunction() {

} }

Library A Library B

Short FQ name: myFunction Short FQ name: myFunction


Resolved FQ name: org.libraryA.myFunction Resolved FQ name: org.libraryB.myFunction
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Infers all types and


resolves functions bodies
JVM JavaScript Native Other
(WASM, Python, etc.?)
Kotlin compiler
fun hello(user: String) = println("Hello, $user")

SimpleFirFunction (name = hello) body ==.

valueParameters

returnTypeRef ValueParameter (name = user) ConstExpression (value = "Hello, ", kind = String)

returnTypeRef typeRef

ImplicitTypeRef UserTypeRef (="String") ResolvedTypeRef (=kotlin/String)

Type inference Type resolution

ResolvedTypeRef (=kotlin/Unit) ResolvedTypeRef (=kotlin/String)


Java interoperability: nullability

● Java nullable types in Kotlin

Java sources Kotlin sources

public class Main { var a: ???? = foo()


public static String foo() {
// TODO
}
}
Java interoperability: nullability

● Java nullable types in Kotlin

Java sources Kotlin sources

public class Main { var a: ???? = foo()


public static String foo() {
// TODO String!
}
}
Java interoperability: nullability

● Java nullable types in Kotlin


● String! is the type range: [String=.String?]

Java sources Kotlin sources

public class Main { var a: ???? = foo()


public static String foo() {
// TODO String!
}
}
Java interoperability: nullability

● Java nullable types in Kotlin


● Nullability annotations @NotNull and @Nullable

Java sources Kotlin sources

public class Main { var a: String = foo()


@NotNull
public static String foo() {
// TODO
}
}
Java interoperability: collection mapping

● Java collection types in Kotlin

Java sources Kotlin sources

public class Main { var a: ???? = foo()


@NotNull
public static List<@NotNull String> foo() {
// TODO
}
}
Java interoperability: collection mapping

● Java collection types in Kotlin


● (Mutable)List<T> is the type range: [MutableList<T>=.List<T=]

Java sources Kotlin sources

public class Main { var a: ???? = foo()


@NotNull
public static List<@NotNull String> foo() {
// TODO (Mutable)List<String>
}
}
Kotlin compiler: control- and data-flow analysis
● Variable initialization analysis val a: Int

● Return analysis
while(true) {
● Smart cast analysis
if (Random.nextBoolean()) {
a = 15
break
Each variable is initialized before being }
used.
}
Each immutable variable is not reassigned
after initialization.
println(a) // It compiles!
Kotlin compiler: control- and data-flow analysis
● Variable initialization analysis fun bar(): Int {
print("Again")
● Return analysis
while (true) {
● Smart cast analysis
print(" and again")
}
} // It compiles!
If the return type is not Unit, then the
function won’t return control unless it
returns something. fun baz(): Long {
error("YOLO! :)")
} // It compiles!
Kotlin compiler: control- and data-flow analysis
fun Any=.printFirstElement() {
● Variable initialization analysis
when (this) {
● Return analysis
is List==> => get(0)
● Smart cast analysis
is Iterable==> => iterator().next()
}
}

If type check is successful, then the


checked value is automatically casted to fun String=.length(): Int =
the corresponding type.
if (this == null) 0
else length

fun Int=.isEven(): Boolean =


this == null =& this % 2 == 0
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo()
Enter if branch Type operator: x as A
}

Jump: break
Kotlin compiler: control- and data-flow analysis
interface A { Enter function bar

fun foo()
Enter while loop
}

Evaluate loop
condition
fun bar(x: Any, b: Boolean) {
true false
while (true) {
if (b) { Exit loop block Enter loop block Exit loop

x as A
Exit if Enter if Function call: x.foo()
break
} false
Evaluate if condition Exit function bar
} true

x.foo() x is A Enter if branch Type operator: x as A


}

Jump: break
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Almost all checks


you've ever seen in
JVM JavaScript Native Other
IntelliJ IDEA.
(WASM, Python, etc.?)
Kotlin compiler: diagnostics
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

On the backend, we
DO NOT resolve, but
JVM JavaScript Native Other
only use the received
(WASM, Python, etc.?)
information
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

IR, a representation slightly


different from FIR.
JVM JavaScript Native Other
(WASM, Python, etc.?)
It is still common for all platforms.
Kotlin compiler: resolved tree

fun hello(user: String) = println("Hello, $user")


Kotlin compiler: resolved tree

public fun hello(user: kotlin.String): kotlin.Unit


defined in example in file Example.kt

fun hello(user: String) = println("Hello, $user")


Kotlin compiler: resolved tree

Reference to value-parameter user: kotlin.String


defined in example.hello

fun hello(user: String) = println("Hello, $user")


Kotlin compiler: resolved tree

Type: kotlin.String

fun hello(user: String) = println("Hello, $user")


Kotlin compiler: resolved tree

Type: kotlin.String

fun hello(user: String) = println("Hello, $user")


Kotlin compiler: resolved tree

Type: kotlin.Unit

fun hello(user: String) = println("Hello, $user")


Kotlin compiler: resolved tree

Call: kotlin.io.println(kotlin.String)

fun hello(user: String) = println("Hello, $user")


IR? Yet another tree!
The code:
// file 'src/kotlin/example.kt'

package helloWorld

fun hello(user: String) = println("Hello, $user")

fun main(args: Array<String>) {


val user = args[0]
hello(user)
}
IR? Yet another tree!
Its IR: FILE fqName:helloWorld fileName:/example.kt

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String) FUN name:main visibility:public modality: FINAL =>(args:kotlin.Array<kotlin.String>)
returnType:kotlin.Unit returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY VALUE_PARAMETER name:args index:0 type:kotlin.Array<kotlin.String> BLOCK_BODY

RETURN type=kotlin.Nothing VAR name:user type:kotlin.String [val]


from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): CALL 'public final fun get (index: kotlin.Int): T of CALL 'public final fun hello (user: kotlin.String):
kotlin.Unit [inline] declared in kotlin.io.ConsoleKt' kotlin.Array [operator] declared in kotlin.Array' kotlin.Unit declared in helloworld'
type=kotlin.Unit origin=null type kotlin.String origin=null type kotlin.Unit origin=null

message: index:

$this: user:
STRING_CONCATENATION type=kotlin.String
CONST Int type=kotlin.Int value=0

GET_VAR 'val user: kotlin.String [val]


GET_VAR 'args: kotlin.Array<kotlin.String> declared in helloworld.main'
declared in helloworld.main' type=kotlin.String origin=null
CONST String type=kotlin.String value="Hello, " GET VAR 'user: kotlin.String type=kotlin.Array<kotLin.String> origin=null
declared in helloWorld.hello'
type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Back-end intermediate representation: a closer look
fun hello(user: String) = println("Hello, $user")

FUN name:hello visibility:public modality:FINAL =>(user:kotlin.String)


returnType:kotlin.Unit

VALUE_PARAMETER name:user index:0 type:kotlin.String BLOCK_BODY

RETURN type=kotlin.Nothing
from='public final fun hello (user: kotlin.String): kotlin.Unit declared in helloworld'

CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline]


declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null

message:

STRING_CONCATENATION type=kotlin.String

CONST String type=kotlin.String value="Hello, "


GET VAR 'user: kotlin.String declared in
helloworld.hello' type=kotlin.String origin=null
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Platform-specific code,
such as bytecode for the
JVM JavaScript Native Other
JVM
(WASM, Python, etc.?)
Kotlin compiler
Parser
Lexer PSI or Lighter AST builder

.kt files
Frontend
Diagnostics Type inference Resolution

Backend
IR generator IR optimizer

Platform-specific code,
such as bytecode for the
JVM JavaScript Native Other
JVM
(WASM, Python, etc.?)
KLibs

JAR analogues – store a serialized IR for the subsequent use of cross-platform libraries.

JVM code

Kotlin/JVM Classes

Common code KLib

Kotlin/Native Native objects

Native code
Compiler plugins

You can extend any compile phase via compiler plugins. To do so, you need to implement compiler
extensions and register them.

To register extensions, you need to inherit from CompilerPluginRegistrar (


ComponentRegistrar for previous versions).

Don’t forget to use supportsK2 = true if you need to support the FIR frontend
Compiler plugins: FIR extensions
To get the full list of the extensions, please see: package org.jetbrains.kotlin.fir.extensions (link).

Consider an example:
=*
* Generates top level class
*
* package foo.bar
*
* public final class MyClass {
* fun foo(): String = "Hello world"
* }
=/
Compiler plugins: FIR extensions

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {


companion object {
=/ foo.bar.MyClass
val MY_CLASS_ID = ClassId(
FqName.fromSegments(listOf("foo", "bar")),
Name.identifier("MyClass")
)
=/ foo.bar.MyClass.foo
val FOO_ID = CallableId(MY_CLASS_ID, Name.identifier("foo"))
}
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol==>? {
==.
}
==.
}
Compiler plugins: FIR extensions

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {


companion object {
=/ foo.bar.MyClass
val MY_CLASS_ID = ClassId(
FqName.fromSegments(listOf("foo", "bar")),
Name.identifier("MyClass")
)
=/ foo.bar.MyClass.foo
val FOO_ID = CallableId(MY_CLASS_ID, Name.identifier("foo"))
}
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol==>? {
==.
}
==.
}
Compiler plugins: FIR extensions

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {


companion object {
=/ foo.bar.MyClass
val MY_CLASS_ID = ClassId(
FqName.fromSegments(listOf("foo", "bar")),
Name.identifier("MyClass")
)
=/ foo.bar.MyClass.foo
val FOO_ID = CallableId(MY_CLASS_ID, Name.identifier("foo"))
}
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol==>? {
==.
}
==.
}
Compiler plugins: FIR extensions

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {


companion object {
=/ foo.bar.MyClass
val MY_CLASS_ID = ClassId(
FqName.fromSegments(listOf("foo", "bar")),
Name.identifier("MyClass")
)
=/ foo.bar.MyClass.foo
val FOO_ID = CallableId(MY_CLASS_ID, Name.identifier("foo"))
}
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol==>? {
==.
}
==.
}
Compiler plugins: FIR extensions

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {


companion object {
=/ foo.bar.MyClass
val MY_CLASS_ID = ClassId(
FqName.fromSegments(listOf("foo", "bar")),
Name.identifier("MyClass")
)
=/ foo.bar.MyClass.foo
val FOO_ID = CallableId(MY_CLASS_ID, Name.identifier("foo"))
}
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol==>? {
==.
}
==.
}
Compiler plugins: FIR extensions

You use a special key to mark everything generated by the compiler and can transfer any
information between frontend and backend. So it also helps to find the new declaration to generate
it’s IR.

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {

object Key : GeneratedDeclarationKey()

==.

}
Compiler plugins: FIR extensions

Don’t forget to register all new declarations.

class SimpleClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {


==.
override fun getTopLevelClassIds(): Set<ClassId> {
return setOf(MY_CLASS_ID)
}

override fun hasPackage(packageFqName: FqName): Boolean {


return packageFqName == MY_CLASS_ID.packageFqName
}

}
Compiler plugins: IR extensions

Actually, the compiler has only one extension for IRs: IrGenerationExtension.
You just need to implement some transformers and accept them:

class SimpleIrGenerationExtension: IrGenerationExtension {


override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val transformers = listOf(SimpleIrBodyGenerator(pluginContext))
for (transformer in transformers) {
moduleFragment.acceptChildrenVoid(transformer)
}
}
}

Don’t forget to check the key (use the interestedIn function)!


Compiler plugins: popular plugins

● kotlinx.serialization — Generates visitor code for serializable classes.


● all-open — Marks all classes as open classes.
● kapt — An annotation processor.
● ksp — An API for developing lightweight compiler plugins.
● Jetpack Compose — Generates efficient UI from its declarative description.
● Arrow Meta — Compiler plugin API that empowers all Arrow libraries.
● ...
Thanks!

@kotlin | Developed by JetBrains

You might also like