Kotlin Bytecode for the JVM - How does the language work

s1m0nw1
2,721 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

Kotlin on the JVM - How can it provide so many features?

Disclaimer: My articles are published under "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)".

© Copyright: Simon Wirtz, 2017 https://blog.simon-wirtz.de/kotlin-on-the-jvm-byte-code-generation/

Feel free to share.

Introduction

What exactly is a "JVM language"? Isn't only Java meant to be run on the JVM? Kotlin provides many features which aren't available in Java such as a proper function type, extension functions or data classes. How is this even possible? I've taken a deeper look at how Kotlin is made possible and what "JVM language" actually means. I hope to help some people understanding a few things better :)

For a more detailed introduction to Kotlin's features you can have a look at my recent posts like this one.

The Java Virtual Machine

A quick and simple definition: The Java Virtual Machine is used by computers to run Java bytecode. Actually there's a lot more to learn about this complex tool, which is described very deeply in Oracle's Spec. As you might already know, the JVM is an abstract virtual computer running on various operating systems. In fact, the JVM is what makes Java "platform independent", because it acts as an abstraction between the executed code and the OS. Just like any real computer, the JVM provides a defined set of Instructions which can be used by a program and are translated to machine specific instructions by the JVM itself later on.

As described in the JVM Spec, the Java Virtual Machine doesn't know anything about the programming language Java. However, it defines the binary format class which is a file containing machine instructions (bytecodes) to be executed beside some more information. This is a very interesting point, because it actually means, that:

  1. the JVM isn't only dedicated to Java as a programming language.
  2. you are free to choose a technology for creating JVM programs as long as you provide proper class files that are compliant to the very strict constraints.
  3. regardless of programming languages, any Java bytecode can interoperate with other Java bytecode on the JVM.

Creation of Class Files

The process of creating class files from human-readable source code is what a compiler does. One example is Oracle's Java Compiler shipped with the JDK (javac) that is capable of compiling .java files to .class files. In Addition to Java, many other JVM languages have emerged in the last few years, which are supposed to provide an alternative abstraction for us developers to create programs for the JVM. One of these languages is Kotlin.

Kotlin Bytecode Generation, Version 1.1

As stated in the official FAQs "Kotlin produces Java compatible bytecode", which means that the Kotlin compiler is capable of transforming all the nice features into JVM compatible instructions and this can even be observed using IntelliJ IDEA tools.
Let's look at some examples:

//File.kt
fun foobar(){}

This simple top level function defined in a .kt file can be investigated with IntelliJ:
"Tools → Kotlin → Show Kotlin Bytecode" will open a new window inside the IDE providing a live preview of the Java bytecode the compiler would create for the current .kt file.

public final class de/swirtz/kotlin/FileKt {

  public final static foobar()V
   L0
    LINENUMBER 4 L0
    LDC "foobar got called"
    ASTORE 0
   L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L2
   L3
    LINENUMBER 5 L3
    RETURN
   L4
    MAXSTACK = 2
    MAXLOCALS = 1

  // compiled from: File.kt
}

I'm afraid only a few people can actually read these files, which is why we can also choose the option "Decompile". Afterwards we'll be presented a Java class representing the functionality previously described with Kotlin:

public final class FileKt {
   public static final void foobar() {
      String var0 = "foobar got called";
      System.out.println(var0);
   }
}

As you can see and probably already know, a Kotlin top level class is compiled into a final Java class with a static function (This structure looks like what extension functions mean to replace: utility classes). Having said that, it becomes obvious how we can call a Kotlin top level function from Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
//declaration would normally be in kt file like this: fun foobar(){}
final class FileKt {
public static final void foobar() {
System.out.println("foobar got called");
}
}
public class Main {
public static void main(String[] args) {
FileKt.foobar();
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Let's see a more difficult one:

class MyClass(val i: Int)

fun MyClass.myExtension(value: String) = value.length

This one shows a simple class MyClass with a property of type Int as well as a top level extension function. First we should have a look at what the class is compiled to, which is quite interesting as we used a primary constructor and the val keyword here.

public final class MyClass {
   private final int i;

   public final int getI() {
      return this.i;
   }

   public MyClass(int i) {
      this.i = i;
   }
}

As we would expect: the property is a final member being assigned in the single constructor. Yet, so much simpler in Kotlin :)

public final class FileKt {
   public static final int myExtension(@NotNull MyClass $receiver, @NotNull String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(value, "value");
      return value.length();
   }
}

The extension function itself is compiled to a static method with its receiver object as a parameter in the Java code. One thing we can also observe in the example is the use of a class called Intrinsics. This one is part of the Kotlin stdlib and is used because the parameters are required to be not null.
Let's see what would happen if we changed the inital extension function's parameter to value: String? and of course access length in a safe way.

public final class FileKt {
   @Nullable
   public static final Integer myExtension(@NotNull MyClass $receiver, @Nullable String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return value != null?Integer.valueOf(value.length()):null;
   }
}

Checking value is not necessary anymore since we told the compiler that null is an acceptable thing to point to.
The next example is a bit more tricky. It's the one with the greatest difference between Kotlin and Java code:

fun loopWithRange(){
    for(i in 5 downTo 1 step 2){
        print(i)
    }
}
 public static final void loopWithRange() {
      IntProgression var10000 = RangesKt.step(RangesKt.downTo(5, 1), 2);
      int i = var10000.getFirst(); //i: 5
      int var1 = var10000.getLast(); //var1: 1
      int var2 = var10000.getStep(); //var2: -2
      if(var2 > 0) {
         if(i > var1) {
            return;
         }
      } else if(i < var1) {
         return;
      }

      while(true) {
         System.out.print(i);
         if(i == var1) {
            return;
         }

         i += var2;
      }
   }

Although the Java code is still quite understandable, probably nobody would write it in real life, because a simple for could do it, too. We need to consider that downTo and step are infix notations, which are function calls actually. In order to provide this flexibility, a little more code seems to be necessary.

What do you think? Doesn't like nice although the Kotlin code is brilliant, right?

Conclusion

I think, most of the time you don't really care about what the Kotlin compiler produces for us. Yet, I find observing it really interesting and helpful as it supports answering my initial questions in some way. Of course, Kotlin is much more than just abstracting Java's operators since it also provides so many extensions to existing Java classes like List or String. Nevertheless, we also saw, that sometimes the compiled Java code is more verbose as it had to be. Could this impact performance? Yes indeed, it does have minor effects. Have a look at this presentation by Dmitry Jemerov if you're interested in more "Kotlin -> Java bytecode" examples considering perfomance, too.

Let me know what you think about it and get in touch if you like!

Simon

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content