5 - Call yourself a method!
What to implement
Operations
OP_INVOKEVIRTUAL
OP_IRETURN
In order to pass test5, correctly implement all of the instructions listed above.
Note: ideally, if you pass all of the basic tests, you should also pass most, if not all, of the advanced tests.
OP_INVOKEVIRTUAL
OP_IRETURN
In order to pass test5, correctly implement all of the instructions listed above.
Note: ideally, if you pass all of the basic tests, you should also pass most, if not all, of the advanced tests.
Please make sure you (re)read the chapter on the IJVM from the book Structured Computer Organization for a more in-depth explanation of IJVM method invocation.
Introduction
In this chapter, you will be tasked with implementing methods. Method invocation in the IJVM can be a bit tricky, as it has some strange behaviour that is left over from the JVM (Java Virtual Machine). For example, when invoking a method, the caller has to push an OBJREF
(object reference) onto the stack as the first parameter. Since the IJVM does not support different objects, pushing this reference has no meaningful functionality. Nevertheless, it's still there just to keep (some kind of) compatibility with Java.
To invoke a method, the caller first pushes the method arguments onto the stack and executes the INVOKEVIRTUAL instruction. This instruction itself has one argument, which is the index of an element in the constant pool. The value of this constant is the address of the start of a section of the program text called the method area, which stores the number of argument and the number of local variables of the method and its method text.
The method area first contains two shorts, the first one signifying the number of arguments the method expects (including OBJREF), and the second one being the number of local variables. The fifth byte in the method area is the actual first instruction to be executed, so adjust your program counter accordingly!
The figure above is an example of executing INVOKEVIRTUAL 0x2
. The pointer to the method is looked up at constant index 0x2
(with value 0x500
). The method area is found at offset 0x500
in the program text. Then the number of arguments and local variables is read. After this the frame is set up, and the first instruction of the method starts executing.
You might wonder why the IJVM employs this roundabout behaviour, where we get a pointer from an index in the constant pool, instead of directly getting the pointer. This behaviour is left over from the JVM, where it makes dynamic linking possible. On the JVM, if a method is not from the present program but from an external library, then the constant pool initially contains a symbolic reference of the external method, i.e. the name of the library and the method, instead of the actual adress of the method (because there is no actual adress yet, the library still has to be loaded). When such a symbolic reference is encountered the library is loaded into memory, and the symbolic reference in the constant pool is changed to a pointer to the method in memory. This indirection makes this possible.
Setting up a local frame
When a method is invoked, a new local frame is created. Have a look at the figure below to see what the memory should look like after a method invocation:
A complete example of how all of this works, is demonstrated in this interactive webpage by TA Zain Munir.
The memory layout of your implementation does not necessarily need to match that of the figure or example above, it is also fine to employ an alternative layout if you find this easier.
Returning from a method
At the end of a method, the IRETURN instruction is called. When this happens, the program counter, the stack pointer and the frame pointer (lv) are restored to their previous values. Then, the top of the stack of the current frame is returned by overwriting the link pointer with the return value, and then pointing the stack pointer to this location. Another way of looking at this is that the pushed arguments and the link pointer are removed from the stack of the previous frame, and then a return value is placed at the top of the stack:
In this process, the link pointer is used to find the prev_lv and prev_pc. In the figure, the prev_pc and prev_lv are located directly below the word pointed at by sp, but this is not always true. The stack (above the previous lv) may contain surplus elements next to the return value. If this happens, this is not an error and the topmost element is the return value. Hence, in the diagram above there may be an arbitrary number of elements between the previous lv and the return value. Because of this, the link pointer is needed to find the prev_lv and prev_pc as they are not always directly below the return value.
The suggested approach
Before you start implementing any instructions, make sure you keep track of the current frame using a frame pointer lv and are accessing the local variables as an offset from that.
Once you've made sure you keep track of the current frame, start implementing the INVOKEVIRTUAL and IRETURN instructions.
OpCode | Instruction | Args | Description |
---|---|---|---|
0xB6 | INVOKEVIRTUAL | short | Invoke a method |
0xAC | IRETURN | N/A | Return from the current method |
Done? Did you pass all basic tests and at least 4 of the advanced tests? Hurray! Your program is sufficient to pass the course.