While showing your basic programming skills by implementing FizzBuzz is nice, I thought Let's bring it to the next level. In this repository you'll find a functional (but very basic) JVM implementation which
is able to interpret (run) the class file produced by javac
from the following source code, i.e.
$ cat fizz-buzz/Main.java
public class Main {
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.println(i);
}
}
}
}
$ # Compile file
$ javac -d fizz-buzz fizz-buzz/Main.java
# Run fizz-JVM
$ kotlin -cp build/libs/fizz-jvm-1.0-SNAPSHOT.jar com.mlesniak.jvm.MainKt fizz-buzz/Main.class
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
...
After understanding the class file format and loading the class file and bytecode, I set the program counter for the instruction set to 0, wrote an empty when
statement with a default branch throwing an
error about an unknown opcode and implemented all opcodes until the example ran without errors.
You need to have Gradle and Kotlin installed. Compile everything with
./gradlew build
and run the interpreter with
kotlin -cp build/libs/fizz-jvm-1.0-SNAPSHOT.jar com.mlesniak.jvm.MainKt fizz-buzz/Main.class
A lot. Anything outside the scope of the trivial FizzBuzz example will not work, i.e. no other methods, no function calls, no additional local variables. Nevertheless, it was a great learning experience to peek under the hood of the JVM's classfile loading, the bytecode instruction set, and execution structure.
Or, to state it otherwise: this implementation is able to execute the following instruction set
$ javap -c -p -s -constants fizz-buzz/Main.class
<... ommitted ...>
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
0: iconst_1
1: istore_1
2: iload_1
3: bipush 100
5: if_icmpgt 78
8: iload_1
9: iconst_3
10: irem
11: ifne 31
14: iload_1
15: iconst_5
16: irem
17: ifne 31
20: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc #13 // String FizzBuzz
25: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: goto 72
31: iload_1
32: iconst_3
33: irem
34: ifne 48
37: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
40: ldc #21 // String Fizz
42: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: goto 72
48: iload_1
49: iconst_5
50: irem
51: ifne 65
54: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
57: ldc #23 // String Buzz
59: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
62: goto 72
65: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
68: iload_1
69: invokevirtual #25 // Method java/io/PrintStream.println:(I)V
72: iinc 1, 1
75: goto 2
78: return
}