$30 off During Our Annual Pro Sale. View Details »

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. KOTLIN UNDER THE
    HOOD
    VIENNA KOTLIN MEETUP, 2017-11-20
    Christoph Leiter

    View Slide

  2. INTRO
    We will look at what the compiler does
    Basics
    Methods
    Classes
    Advanced
    Used current Kotlin 1.1.60
    Not focused on performance

    View Slide

  3. BASICS

    View Slide

  4. PRIMITIVE TYPES
    fun primitiveType() {
    val a = 1
    val b: Int? = 3
    println(a)
    println(b)
    }

    View Slide

  5. 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

    View Slide

  6. PRIMITIVE TYPES
    public static final void primitiveType() {
    int a = 1;
    Integer b = Integer.valueOf(3);
    System.out.println(a);
    System.out.println(b);
    }

    View Slide

  7. 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;
    }

    View Slide

  8. 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);
    }
    }

    View Slide

  9. 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;
    }
    }

    View Slide

  10. COMPARISONS
    fun comparisons(a: String, b: String) {
    if (a == b) println("Equals")
    if (a === b) println("Same instance")
    }

    View Slide

  11. 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);
    }
    }

    View Slide

  12. COMPARISONS
    Automatically optimized for null case
    public static boolean areEqual(Object first, Object second) {
    return first == null ? second == null : first.equals(second);
    }

    View Slide

  13. 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"

    View Slide

  14. WHEN EXPRESSION
    fun whenExpression(i: Int) =
    when (i) {
    0 -> "zero"
    1 -> "one"
    in 2..9 -> "under ten"
    else -> "something else"
    }

    View Slide

  15. 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;
    }

    View Slide

  16. 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);
    }

    View Slide

  17. 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. ()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

    View Slide

  18. 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
    }

    View Slide

  19. 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;
    }
    }

    View Slide

  20. METHODS

    View Slide

  21. 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);
    }

    View Slide

  22. 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()
    }

    View Slide

  23. 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);

    View Slide

  24. 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
    }

    View Slide

  25. 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);
    }
    }
    }

    View Slide

  26. 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())
    }
    }

    View Slide

  27. COLLECTION API
    fun mapList(list: List) =
    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

    View Slide

  28. var4 = $receiver$iv.iterator();

    View Slide

  29. 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

    View Slide

  30. 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);
    }

    View Slide

  31. 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;
    }

    View Slide

  32. 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);
    }

    View Slide

  33. NESTED FUNCTION
    fun nestedFunction(i : Int) {
    fun nested(x: Int) = i * x
    nested(42)
    }
    public static final void nestedFunction(final int i) {
    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);
    }

    View Slide

  34. CLASSES

    View Slide

  35. 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

    View Slide

  36. SINGLETON OBJECTS
    object Obj
    public final class Obj {
    public static final Obj INSTANCE;
    private Obj() {
    INSTANCE = (Obj)this;
    }
    static {
    new Obj();
    }
    }

    View Slide

  37. DATA CLASS
    data class D(
    val a: Int,
    val b: String
    ) {
    init {
    require(a > 0)
    }
    }

    View Slide

  38. 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) {

    View Slide

  39. 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")
    }
    }

    View Slide

  40. 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;
    }

    View Slide

  41. ADVANCED

    View Slide

  42. A.K.A. RANDOM STUFF

    View Slide

  43. 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);
    }

    View Slide

  44. REIFIED GENERIC TYPES
    inline fun 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
    );
    }

    View Slide

  45. REIFIED GENERIC TYPES
    Let's call this function:
    fun callGenericType() {
    genericType()
    }
    public static final void callGenericType() {
    String var0 = "Type of T is " +
    Reflection.getOrCreateKotlinClass(String.class);
    System.out.println(var0);
    }

    View Slide

  46. LAMBDA
    fun execute(lambda: () -> Unit) {
    lambda()
    }
    fun simpleLambda() {
    val x = 1
    execute {
    println(x)
    }
    }

    View Slide

  47. 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);
    }
    }));
    }

    View Slide

  48. 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"
    }
    }

    View Slide

  49. 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";
    }
    }));
    }

    View Slide

  50. 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

    View Slide