The document discusses various Kotlin language features and how they are compiled down to bytecode. It explains concepts like lowering, de-sugaring, and decompiling Kotlin programs. Specific language features summarized include free functions, nested functions, extension methods, data classes, object declarations, companion objects, and lambdas with receivers. The document shows how each feature compiles by decompiling example Kotlin code.
2. – The K2 compiler previewed in 1.7
– Why you should care about it
– Looking inside the black box
– The concept of ‘lowering’
– Quick note on Android
– Other game changers
agenda
what’s new in Kotlin 1.7
6. – The interop story is so good
– Call into legacy Java code easily
– Call into JavaScript easily
– Call in C and Swift easily
– Really concise, yet clear syntax
– Less is more
– “Borrows” the best bits
– Less baggage
why kotlin?
so much to like
Null Safety
String Templates
Default parameters
Extensions
Free Functions
Coroutines
Single Expression Functions
Reified generics
Data classes and Properties
Type Inference
Smart Casts
Operator overloading
7. This is what we want Kotlin to become in the
future. The universal solution for every
platform.
ANDREY
BRESLAV
Former Lead Language Designer of Kotlin, JetBrains
9. the pre 1.7 approach
no unified back end
Front
End
Abstract Syntax
Tree (AST)
1101011111
001010110
101010110
Semantic Info
JVM
Back End
JS
Back End
Native
Back End
10. the 1.7 approach
unified back end
Back Ends
Front
End
Front IR
JVM Specific
JS Specific
LVVM Specific
Shared
IR
12. the 1.7 approach
– Consistency across different platforms
– Faster adoption of language features
– A universal API for compiler plugins
– Richer and more reliable tooling
– Improvements in compile times
benefits for developers
20. IrLowering: IR lowering
ValidateIrBeforeLowering: Validate IR before lowering
ProcessOptionalAnnotations: Record metadata of ...
ExpectDeclarationsRemoving: Remove expect declaration from ...
SerializeIr: If specified by compiler options ...
ScriptsToClasses: Put script declarations into classes ...
FileClass: Put file level function ...
JvmStaticInObject: Make JvmStatic functions ...
RepeatedAnnotation: Enclose repeated annotations ...
PerformByIrFile: Perform phases by IrFile ...
TypeAliasAnnotationMethodsLowering: Generate method stubs ...
FunctionExpression: Transform IrFunctionExpression to ...
JvmOverloadsAnnotation: Handle JvmOverloads annotations ...
MainMethodGeneration: Generate main bridges to ...
MakePropertyDelegateMethodsStatic: Make `$delegate` methods for ...
RenameFields: Rename private fields ...
FakeInliningLocalVariablesLowering: Add fake locals to identify the ...
GenerateMultifileFacades: Generate JvmMultifileClass facades ...
ResolveInlineCalls: Statically resolve calls to ...
BytecodeInliningPreparation: Label all loops for non-local ...
ValidateIrAfterLowering: Validate IR after lowering ...
lots more lowerings
22. summary of commands
kotlinc –X
kotlinc –Xlist-phases
kotlinc –Xphases-to-dump-after=ValidateIrAfterLowering <PATH>
list experimental arguments
list compiler phases
dump output when a phase is complete
23.
24.
25.
26. the Visitor Pattern in Kotlinc
interface IrElementVisitor<out R, in D> {
fun visitElement(element: IrElement, data: D): R
fun visitDeclaration(declaration: IrDeclarationBase, data: D): R
fun visitClass(declaration: IrClass, data: D): R
fun visitFunction(declaration: IrFunction, data: D): R
fun visitConstructor(declaration: IrConstructor, data: D): R
fun visitProperty(declaration: IrProperty, data: D): R
}
one visit method for every type of node
28. – Many language features are ‘syntactical sugar’
– They don’t add a capability but provide a convenience
– For example, a foreach loop rather than an Iterator object
lowering vs. de-sugaring
understanding the difference
29. – These language features can be de-sugared
– We rewrite more complex constructs as simpler ones
– Lowering is where we do this within the same language
lowering vs. de-sugaring
understanding the difference
30. – The Kotlin compiler uses lowerings internally
– To restrict its work to a subset of the language
– We cannot (yet) drill into these in detail
– But we can look at de-sugaring in general
lowering vs. de-sugaring
understanding the difference
31. – Nothing is ever hidden in software
– We can decompile bytecode from the Kotlin compiler
– This lets us see how Kotlin features work ‘under the hood’
– Similar options exist for other platforms
decompiling Kotlin programs
via the IntelliJ tools
32.
33. introducing javap
the java bytecode decompiler
% javap
Usage: javap <options> <classes>
where possible options include:
...
-p -private Show all classes and members
-c Disassemble the code
-s Print internal type signatures
...
--module-path <path> Specify where to find application modules
--system <jdk> Specify where to find system modules
...
-cp <path> Specify where to find user class files
...
36. public final class ProgramKt {
public static final void printMsg(String);
Code:
15: return
public static final void main();
Code:
5: return
public static void main(String[]);
Code:
3: return
}
free functions
46. class Person(val name: String)
fun Person.sayHello() = println("Hello from $name")
fun String.times(num: Int) = (1..num).joinToString { "$this" }
fun main() {
val person = Person("Jane")
person.sayHello()
println("Dave".times(3))
}
extension methods
Hello from Jane
Dave, Dave, Dave
47. public static final void sayHello(Person);
Code:
...
25: return
public static final String times(String, int);
Code:
...
40: return
extension methods
49. destructuring
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("Lucy", 36)
val (x, y) = person
println(x)
println(y)
}
Lucy
36
50. destructuring
public final Person {
private final String name;
private final int age;
public Person(String, int);
public final String getName();
public final int getAge();
public final String component1();
public final int component2();
public final Person copy(String, int);
public static Person copy$default(Person, String, int, int, Object);
public java.lang.String toString();
public int hashCode();
public boolean equals(java.lang.Object);
}
51. destructuring
public final class ProgramKt {
public static final void main();
Code:
...
15: invokevirtual #18 // Person.component1:()String;
18: astore_2
19: aload_1
20: invokevirtual #22 // Person.component2:()I
23: istore_3
...
44: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #46 // Method main:()V
3: return
}
54. object declarations
public final class Math {
public static final Math INSTANCE;
private Math();
public final int add(int, int);
static {};
Code:
0: new #2 // class Math
3: dup
4: invokespecial #17 // Method "<init>":()V
7: putstatic #20 // Field INSTANCE:Math;
10: return
}
55. object declarations
public final class ProgramKt {
public static final void main();
Code:
0: getstatic #12 // Field Math.INSTANCE:Math;
3: bipush 12
5: bipush 34
7: invokevirtual #16 // Method Math.add:(II)I
10: istore_0
11: iconst_0
12: istore_1
13: getstatic #22 // Field System.out;
16: iload_0
17: invokevirtual #28 // Method PrintStream.println:(I)V
20: return
public static void main(String[]);
}
57. class Employee(val name: String, val dept: String) {
companion object {
fun buildForHR(name: String) = Employee(name, "HR")
fun buildForIT(name: String) = Employee(name, "IT")
}
override fun toString() = "$name working in $dept"
}
fun main() {
val emp1 = Employee.buildForHR("Dave")
val emp2 = Employee.buildForIT("Jane")
println(emp1)
println(emp2)
}
companion objects
Dave working in HR
Jane working in IT
58. public final class Employee {
public static final Employee$Companion Companion;
private final String name;
private final String dept;
public Employee(String, String);
public final String getName();
public final String getDept();
public String toString();
static {};
Code:
0: new #45 // class Employee$Companion
3: dup
4: aconst_null
5: invokespecial #48 // Employee$Companion."<init>"
8: putstatic #52 // Field Companion:Employee$Companion;
11: return
}
companion objects
60. class Employee(val name: String, val dept: String) {
companion object EmployeeFactory {
fun buildForHR(name: String) = Employee(name, "HR")
fun buildForIT(name: String) = Employee(name, "IT")
}
override fun toString() = "$name working in $dept"
}
fun main() {
val emp1 = Employee.buildForHR("Dave")
val emp2 = Employee.buildForIT("Jane")
println(emp1)
println(emp2)
}
companion objects (renamed)
Dave working in HR
Jane working in IT
61. public final class Employee {
private final Ljava/lang/String; name
private final Ljava/lang/String; dept
public final static Employee$EmployeeFactory; EmployeeFactory
public toString()Ljava/lang/String;
public final getName()Ljava/lang/String;
public final getDept()Ljava/lang/String;
public <init>(Ljava/lang/String;Ljava/lang/String;)V
// access flags 0x8
static <clinit>()V
NEW Employee$EmployeeFactory
DUP
ACONST_NULL
INVOKESPECIAL Employee$EmployeeFactory.<init> (...)V
PUTSTATIC Employee.EmployeeFactory : Employee$EmployeeFactory;
RETURN
MAXSTACK = 3
MAXLOCALS = 0
}
companion objects (renamed)
63. class Person(val name: String, job: String) {
var job: String by LoggingDelegate(job)
}
fun main() {
val person = Person("Jane", "Junior Developer")
person.job = "Senior Developer"
println(person.job)
}
Delegated Properties
Creating logger for property 'job'
Writing to 'job'
Reading from 'job'
Senior Developer
64. class LoggingDelegate<T>(var value: T) : ReadWriteProperty<Any?, T> {
operator fun provideDelegate(
thisRef: Any?,
prop: KProperty<*>
): ReadWriteProperty<Any?, T> {
println("Creating logger for property '${prop.name}'")
return this
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
println("Writing to '${property.name}'")
this.value = value
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("Reading from '${property.name}'")
return value
}
}
Delegated Properties
65. public final class Person {
...
private final Lkotlin/properties/ReadWriteProperty; job$delegate
public <init>(Ljava/lang/String;Ljava/lang/String;)V
...
NEW language/delegated/properties/LoggingDelegate
...
Delegated Properties
69. data class Person(val name: String, var age: Int)
fun demo(person: Person, action: Person.() -> Unit) = person.apply(action)
fun main() {
val person = Person("Jane", 25)
demo(person) {
age += 10
println(this)
}
}
lambdas with receivers
Person(name=Jane, age=35)
70. public final class ProgramKt {
public static final Person demo(
Person,
kotlin.jvm.functions.Function1<? super Person, kotlin.Unit>
);
Code:
...
21: invokeinterface #24,2 //Function1.invoke:(Object;)Object;
...
public static final void main();
Code:
...
13: getstatic #42 // Field ProgramKt$main$1.INSTANCE:ProgramKt$main$1;
16: checkcast #20 // class kotlin/jvm/functions/Function1
19: invokestatic #44 // Method demo:(Person;Function1;)Person;
...
lambdas with receivers
71. public interface Function1<P1, R> extends Function<R> {
public abstract R invoke(P1);
}
lambdas with receivers
javap -cp kotlin-stdlib-1.5.10.jar -c -p kotlin.jvm.functions.Function1
public interface kotlin.Function<R> {
}
javap -cp kotlin-stdlib-1.5.10.jar -c -p kotlin.jvm.functions.Function
72. public interface Function2<P1, P2, R> extends Function<R> {
public abstract R invoke(P1, P2);
}
lambdas with receivers
javap -cp kotlin-stdlib-1.5.10.jar -c -p kotlin.jvm.functions.Function2
public interface Function3<P1, P2, P3, R> extends Function<R> {
public abstract R invoke(P1, P2, P3);
}
javap -cp kotlin-stdlib-1.5.10.jar -c -p kotlin.jvm.functions.Function3
75. fun demo(
person: Person,
action: EvilLambda
) = person.action(1, 2, 3, 4 ... 14, 15, 16, 17, 18, 19, 20, 21)
fun main() {
val person = Person("Jane", 25)
demo(person) { a, b, c, d ... n, o, p, q, r, s, t, u ->
age += 10
println(this)
}
}
lambdas with receivers (21+1 params)
79. fun demo(
person: Person,
action: EvilLambda
) = person.action(1, 2, 3, 4 ... 14, 15, 16, 17, 18, 19, 20, 21, 22)
fun main() {
val person = Person("Jane", 25)
demo(person) { a, b, c, d ... n, o, p, q, r, s, t, u, v ->
age += 10
println(this)
}
}
lambdas with receivers (22+1 params)
82. – A new predictive back gesture
– Enables users to see where ‘back’ will take them
Android 13
opt-in on back behaviour
83. – Open-source application, in Kotlin and Compose
– Designed to show (Googles view on) best practices
Android 13
‘Now in Android’
84. – API is now stable
– Performance improvements are promised
– Recomposition highlighter to find poorly optimised views
– Lazy Layouts for efficiently showing lists of items
Android 13
Compose 1.2 Beta
85. – Allows classes and methods to be precompiled
– These are bundled with your release to speed startup
– Most crucial user journeys profiled via Macrobenchmark
Android 13
Baseline Profiles
86. – Edit and observe changes to composable functions
– Will significantly simplify and speed up UI development
– Works on previews, emulators and devices
– But still a work in progress
Android 13
Live Edit
88. game changer 1
multiplatform libraries
COMMON
KOTLIN
KOTLIN ANDROID
KOTLIN JS
KOTLIN NATIVE
KOTLIN JVM
NATIVE
ARTEFACT
JS BUNDLE
JAR
89. – Union Types with ‘|’ syntax
– Improvements to Sealed Types
– Extended Operator Overloading
– Name based Destructuring
– Collection Literals
– Structure Literals (Tuples?)
game changer 2
potential new language features
90. – Brings Google’s UI Toolkit to web
– In theory, enables reuse of UI code across stack
game changer 3
compose for web
https://compose-web.ui.pages.jetbrains.team/
Div(attrs = attrs) {
Label {
Input(
type = InputType.Checkbox,
attrs = {}
)
Span {
content()
}}}
92. – Kotlin is emerging from a period of consolidation
– Which opens the door to lots of good things
– Better tooling, new features, faster builds etc...
– Android continues to innovate on top of Kotlin
– Compose could be ‘one DSL to rule them all’
conclusions
...great things lie ahead