Inside Zero and Shark: The Java stack

This article will be a little generic — nothing about HotSpot, nothing about Zero — but before we can understand Zero’s calling convention we need to go up a level and understand the calling convention of Java itself, in which arguments and results are passed on the stack. Lets have a look at an example to see how that works:

class HelloUser {
  public static void main(String[] args) {
    System.out.print("Hello ");
    System.out.println(System.getProperty("user.name"));
  }
}

We’re going to have to disassemble it to see what’s happening:

public static void main(java.lang.String[]);
    0:  getstatic       [Field java/lang/System.out:Ljava/io/PrintStream;]
    3:  ldc             [String "Hello "]
    5:  invokevirtual   [Method java/io/PrintStream.print:(Ljava/lang/String;)V]
    8:  getstatic       [Field java/lang/System.out:Ljava/io/PrintStream;]
   11:  ldc             [String "user.name"]
   13:  invokestatic    [Method java/lang/System.getProperty:(Ljava/lang/String;)Ljava/lang/String;]
   16:  invokevirtual   [Method java/io/PrintStream.println:(Ljava/lang/String;)V]
   19:  return

The getstatic instruction gets a value from a static field of a class (in this case the out field of the System class) and pushes it onto the stack. The ldc instruction loads a constant (the string "Hello ") and pushes that onto the stack. So far we have this:

 
 
 
 
 
 
 
System.out
 
 
"Hello "
System.out
Before 0: getstatic Before 3: ldc Before 5: invokevirtual

The next instruction is an invokevirtual, which is going to call the method java.io.PrintStream.print. This takes two arguments, the implicit argument this, and the string to print, so the interpreter pops two values from the stack, stores them as the callee’s first two local variables, and starts to execute the callee. When the callee returns the stack will be empty:

 
 
 
 
Before 8: getstatic

We now have another getstatic and another ldc:

 
 
 
System.out
 
 
"user.name"
System.out
Before 11: ldc Before 13: invokestatic

The next instruction is an invokestatic, another method call. This is calling java.lang.System.getProperty, which takes only one argument, the name of the property to get (static methods have no this). Presently there are two values on the stack, but the interpreter doesn’t care about that. It simply pops the top value from the stack, stores it as the callee’s first local variable, and starts to execute the callee. This time, however, the callee returns a value, the user’s name, so when it returns it will have pushed that onto the stack:

 
 
"gbenson"
System.out
Before 16: invokevirtual

Now we’re ready for the final call, another invokevirtual. That extra value on the stack may have seemed odd before, but now it makes sense; it’s the first argument for this call! The interpreter pops two values from the stack, stores them as the callee’s first two local variables, and starts to execute the callee. This method returns nothing, so when the callee returns the stack will be empty. HelloUser.main returns nothing, so the stack is now exactly as it should be for us to execute the return instruction:

 
 
 
 
Before 19: return

Next time we’ll see how all this works in HotSpot and Zero.