Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Kotlin Under the Hood

Kotlin Under the Hood

Christoph Leiter

November 20, 2017
Tweet

More Decks by Christoph Leiter

Other Decks in Programming

Transcript

  1. INTRO We will look at what the compiler does Basics

    Methods Classes Advanced Used current Kotlin 1.1.60 Not focused on performance
  2. PRIMITIVE TYPES fun primitiveType() { val a = 1 val

    b: Int? = 3 println(a) println(b) }
  3. PRIMITIVE TYPES // access flags 0x19 public final static primitiveType()V

    L0 LINENUMBER 4 L0 ICONST_1 ISTORE 0 L1 LINENUMBER 5 L1 ICONST_3 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; ASTORE 1 L2 LINENUMBER 7 L2 L3 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ILOAD 0 INVOKEVIRTUAL java/io/PrintStream.println (I)V L4 L5 LINENUMBER 8 L5 L6
  4. PRIMITIVE TYPES public static final void primitiveType() { int a

    = 1; Integer b = Integer.valueOf(3); System.out.println(a); System.out.println(b); }
  5. NON NULLABLE PARAMETER Intrinsics you say? fun nonNullableParameter(s: String) =

    s @NotNull public static final String nonNullableParameter(@NotNull String s) Intrinsics.checkParameterIsNotNull(s, "s"); return s; }
  6. INTRINSICS Part of the Kotlin runtime Resides in kotlin.jvm.internal Used

    by the compiler to reference commonly needed methods public static void checkParameterIsNotNull(Object value, String pa if (value == null) { throwParameterIsNullException(paramName); } }
  7. NON NULL IN PRIVATE METHOD Not needed in private methods

    since the Kotlin compiler can guarantee null is never passed. class NonNullInPrivateMethod { private fun a(s: String) = s } public final class NonNullInPrivateMethod { private final String a(String s) { return s; } }
  8. COMPARISONS fun comparisons(a: String, b: String) { if (a ==

    b) println("Equals") if (a === b) println("Same instance") }
  9. COMPARISONS public static final void comparisons(@NotNull String a, @NotNull S

    Intrinsics.checkParameterIsNotNull(a, "a"); Intrinsics.checkParameterIsNotNull(b, "b"); String var2; if (Intrinsics.areEqual(a, b)) { var2 = "Equals"; System.out.println(var2); } if (a == b) { var2 = "Same instance"; System.out.println(var2); } }
  10. COMPARISONS Automatically optimized for null case public static boolean areEqual(Object

    first, Object second) { return first == null ? second == null : first.equals(second); }
  11. IF EXPRESSION We have some alternative to write this method

    All the same fun ifExpression(b: Boolean): String { return if (b) "foo" else "buz" } @NotNull public static final String ifExpression(boolean b) { return b ? "foo" : "buz"; } fun ifMethodExpression(b: Boolean): String = if (b) "foo" else "buz" fun ifMethodExpressionWithoutReturnType(b: Boolean) = if (b) "foo" else "buz"
  12. WHEN EXPRESSION fun whenExpression(i: Int) = when (i) { 0

    -> "zero" 1 -> "one" in 2..9 -> "under ten" else -> "something else" }
  13. WHEN EXPRESSION @NotNull public static final String whenExpression(int i) {

    String var10000; if (i == 0) { var10000 = "zero"; } else if (i == 1) { var10000 = "one"; } else { if (2 <= i) { if (9 >= i) { var10000 = "under ten"; return var10000; } } var10000 = "something else"; } return var10000; }
  14. STRING INTERPOLATION fun stringInterpolation(a: Int, b: Int?, s: String) =

    "Result is: $a $b $s ${a * (b ?: 3)}" @NotNull public static final String stringInterpolation (int a, @Nullable Integer b, @NotNull String s) { Intrinsics.checkParameterIsNotNull(s, "s"); return "Result is: " + a + ' ' + b + ' ' + s + ' ' + a * (b != null ? b.intValue() : 3); }
  15. STRING INTERPOLATION // access flags 0x19 public final static stringInterpolation(ILjava/lang/Integer;Ljava/

    @Lorg/jetbrains/annotations/NotNull;() // invisible @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 2 L0 ALOAD 2 LDC "s" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotN L1 LINENUMBER 40 L1 NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V LDC "Result is: " INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String; ILOAD 0 INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/Strin BIPUSH 32 INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/Strin ALOAD 1
  16. TYPE CHECKS & CASTS fun typeCheck(v: Any?) { if (v

    is String) println("String has length ${v.length}") val t = v as? String val u = v as String }
  17. TYPE CHECKS & CASTS public static final void typeCheck(@Nullable Object

    v) { String t; if (v instanceof String) { t = "String has length " + ((String)v).length(); System.out.println(t); } Object var10000 = v; if (!(v instanceof String)) { var10000 = null; } t = (String)var10000; if (v == null) { throw new TypeCastException("null cannot be cast to non-null } else { String u = (String)v; } }
  18. NAMED ARGUMENTS fun namedArguments(a: String, b: Int, c: Long) {

    println("$a $b $c") } fun namedArgumentsCall() { namedArguments(c = 12L, a = "foo", b = 42) } public static final void namedArgumentsCall() { byte var0 = 42; String var1 = "foo"; long var2 = 12L; namedArguments(var1, var0, var2); }
  19. DEFAULT ARGUMENTS fun defaultArguments(a: Int = 42, b: String =

    "Hello", c: Boolean fun defaultArgumentsCall() { defaultArguments(a = 12, b = "Bye", c = true) defaultArguments(a = 12, b = "Bye") defaultArguments(a = 12, c = true) defaultArguments() }
  20. DEFAULT ARGUMENTS public static final int defaultArguments(int a, @NotNull String

    b, Intrinsics.checkParameterIsNotNull(b, "b"); return a; } // $FF: synthetic method // $FF: bridge method public static int defaultArguments$default(int var0, String var1, if ((var3 & 1) != 0) { var0 = 42; } if ((var3 & 2) != 0) { var1 = "Hello"; } if ((var3 & 4) != 0) { var2 = true; } return defaultArguments(var0, var1, var2);
  21. DEFAULT ARGUMENTS WITH @JVMOVERLOADS @JvmOverloads fun defaultArguments(a: Int = 42,

    b: String = "Hello", c: Boolean @JvmOverloads public static final int defaultArguments(int a, @NotNull String b) return defaultArguments$default(a, b, false, 4, (Object)null); } @JvmOverloads public static final int defaultArguments(int a) { return defaultArguments$default(a, (String)null, false, 6, (Obj } @JvmOverloads public static final int defaultArguments() { return defaultArguments$default(0, (String)null, false, 7, (Obj }
  22. PRECONDITIONS fun requires(i: Int) { require(i > 10) if (i

    == 3) println("Nope") } public static final void requires(int i) { boolean var1 = i > 10; if (!var1) { String var2 = "Failed requirement."; throw (Throwable)(new IllegalArgumentException(var2.toString } else { if (i == 3) { String var3 = "Nope"; System.out.println(var3); } } }
  23. INLINE FUNCTIONS Kotlin can inline functions marked with inline Basically

    copy pastes method at compile time to call site. Absolutely no overhead at runtime @kotlin.internal.InlineOnly public inline fun require(value: Boolean): Unit = require(value) { "Failed requirement." } @kotlin.internal.InlineOnly public inline fun require(value: Boolean, lazyMessage: () -> Any): if (!value) { val message = lazyMessage() throw IllegalArgumentException(message.toString()) } }
  24. COLLECTION API fun mapList(list: List<String>) = list.filter { it.length >=

    3 }.map { it.toUpperCase() } @NotNull public static final List mapList(@NotNull List list) { Intrinsics.checkParameterIsNotNull(list, "list"); Iterable $receiver$iv = (Iterable)list; Collection destination$iv$iv = (Collection)(new ArrayList()); Iterator var4 = $receiver$iv.iterator(); Object item$iv$iv; String it; while(var4.hasNext()) { item$iv$iv = var4.next(); it = (String)item$iv$iv; if (it.length() >= 3) { destination$iv$iv.add(item$iv$iv); } } $receiver$iv = (Iterable)((List)destination$iv$iv); destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.co
  25. COLLECTION API Long story short: There's no method call overhead

    for the collection API since everything is inlined anyway. But there's an overhead for object allocations
  26. TAIL RECURSION Let's try to make this tail recursive fun

    factorial(n: Long): Long = if (n == 1L) 1 else n * factorial(n - 1) public static final long factorial(long n) { return n == 1L ? 1L : n * factorial(n - 1L); } fun factorial2(n: Long, total: Long = 1): Long = if (n == 1L) total else factorial2(n - 1, total * n) public static final long factorial2(long n, long total) { return n == 1L ? total : factorial2(n - 1L, total * n); }
  27. TAIL RECURSION Adding tailrec keyword tailrec fun factorialTailrec(n: Long, total:

    Long = 1): Long = if (n == 1L) total else factorialTailrec(n - 1, total * n) public static final long factorialTailrec(long n, long total) { while(n != 1L) { long var10000 = n - 1L; total *= n; n = var10000; } return total; }
  28. EXTENSION FUNCTIONS What's this $receiver thingy? fun Int.clamp(lo: Int, hi:

    Int) = when { this < lo -> lo this > hi -> hi else -> this } public static final int clamp(int $receiver, int lo, int hi) { return $receiver < lo ? lo : ($receiver > hi ? hi : $receiver); } fun callClamp() { 6.clamp(5, 10) } public static final void callClamp() { clamp(6, 5, 10); }
  29. NESTED FUNCTION fun nestedFunction(i : Int) { fun nested(x: Int)

    = i * x nested(42) } public static final void nestedFunction(final int i) { <undefinedtype> nested$ = new Function1() { // $FF: synthetic method // $FF: bridge method public Object invoke(Object var1) { return this.invoke(((Number)var1).intValue()); } public final int invoke(int x) { return i * x; } }; nested$.invoke(42); }
  30. PROPERTY @Metadata used by compiler and kotlin-reflect class Property {

    var a: String = "" } @Metadata( mv = {1, 1, 8}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u d2 = {"LProperty;", "", "()V", "a", "", "getA", "()Ljava/lang/S ) public final class Property { @NotNull private String a = ""; @NotNull public final String getA() { t thi
  31. SINGLETON OBJECTS object Obj public final class Obj { public

    static final Obj INSTANCE; private Obj() { INSTANCE = (Obj)this; } static { new Obj(); } }
  32. DATA CLASS data class D( val a: Int, val b:

    String ) { init { require(a > 0) } }
  33. DATA CLASS public final class D { private final int

    a; @NotNull private final String b; public final int getA() { return this.a; } @NotNull public final String getB() { return this.b; } public D(int a, @NotNull String b) { Intrinsics.checkParameterIsNotNull(b, "b"); super(); this.a = a; this.b = b; boolean var3 = this.a > 0; if (!var3) {
  34. INIT BLOCKS class InitBlocks(s: String) { init { println("block 1")

    } var x: String init { println("block 2") x = "x" } var y = "y" init { println("block 3 $x $y") } }
  35. INIT BLOCKS public final class InitBlocks { @NotNull private String

    x; @NotNull private String y; @NotNull public final String getX() { return this.x; } public final void setX(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, ""); this.x = var1; } @NotNull public final String getY() { return this.y; }
  36. COMPILE TIME REFERENCES Boring but efficient and refactoring safe class

    CompileTimeReferences { var n: String = "name" fun x() { } } fun useCompileTimeReference() { println(CompileTimeReferences::n.name) println(CompileTimeReferences::x.name) } public static final void useCompileTimeReference() { String var0 = "n"; System.out.println(var0); var0 = "x"; System.out.println(var0); }
  37. REIFIED GENERIC TYPES inline fun <reified T> genericType() { println("Type

    of T is ${T::class}") } private static final void genericType() { StringBuilder var10000 = (new StringBuilder()).append("Type of Intrinsics.reifiedOperationMarker(4, "T"); String var1 = var10000.append(Reflection.getOrCreateKotlinClass System.out.println(var1); } public static void reifiedOperationMarker(int id, String typeParam throwUndefinedForReified(); } public static void throwUndefinedForReified() { throwUndefinedForReified( "This function has a reified type parameter and thus can o ); }
  38. REIFIED GENERIC TYPES Let's call this function: fun callGenericType() {

    genericType<String>() } public static final void callGenericType() { String var0 = "Type of T is " + Reflection.getOrCreateKotlinClass(String.class); System.out.println(var0); }
  39. LAMBDA fun execute(lambda: () -> Unit) { lambda() } fun

    simpleLambda() { val x = 1 execute { println(x) } }
  40. LAMBDA public static final void simpleLambda() { final int x

    = 1; execute((Function0)(new Function0() { // $FF: synthetic method // $FF: bridge method public Object invoke() { this.invoke(); return Unit.INSTANCE; } public final void invoke() { int var1 = x; System.out.println(var1); } })); }
  41. MUTATING LAMBDA Note that you can not do that easily

    in Java fun mutatingLambda() { var x = 1 var y = Any() execute { x = 2 y = "y" } }
  42. MUTATING LAMBDA public static final void mutatingLambda() { final IntRef

    x = new IntRef(); x.element = 1; final ObjectRef y = new ObjectRef(); y.element = new Object(); execute((Function0)(new Function0() { // $FF: synthetic method // $FF: bridge method public Object invoke() { this.invoke(); return Unit.INSTANCE; } public final void invoke() { x.element = 2; y.element = "y"; } })); }
  43. SUMMARY It's good to know how Kotlin internals work Helps

    with debugging tricky errors Most of the stuff is pretty straight forward Created byte code might look inefficient sometimes, but JIT takes care of that It's easy to look at bytecode in IntelliJ Tools -> Kotlin -> Show Kotlin Bytecode -> Decompile