Java is a general purpose high-level programming language, initially developed to address the problems of building software for networked consumer devices. It was designed to support multiple host architectures, in such a way that compiled Java code could be transported over a network, from one machine to another. It is the ease with which compiled Java code can be transferred over a network that has made Java a particularily attractive programming language for Web browsers.
The Java Virtual Machine (JVM) is the cornerstone of the Java programming language. It is the component of Java technology that is responsible for the cross-platform delivery, and for the small size of compiled Java code. The task of a Java compiler is to convert the high-level Java language into JVM bytecodes, and these bytecodes are the compiled form of the language that is then transferred over the network.
The JVM is an abstract computing machine. Like a real computing machine, it has an instruction set and uses various regions of memory. However, the JVM does not assume any particular implementation technology or host platform. The particular implementation technology that we will investigate in this assignment is direct interpretation (simulation).
For this assignment, we will build an interpreter for a set of byte codes, much like the JVM (although, based on a smaller set of instructions, to make it do-able). Just as SPIM provides an interpreter for the MIPS instruction set, we will build an interpreter that processes the bytecodes described in the below table.
Instruction | Bytecode | Byte Format | Stack Ops | Description |
---|---|---|---|---|
IADD | 0x60 | IADD | POP v1, POP v2, PUSH result | Integer add |
ISUB | 0x64 | ISUB | POP v1, POP v2, PUSH result | Integer subtd |
IMUL | 0x68 | IMUL | POP v1, POP v2, PUSH result | Integer multiply |
IDIV | 0x72 | IDIV | POP v1, POP v2, PUSH result | Integer divide |
ILOAD | 0x15 | ILOAD index | PUSH result | Load from local variable at index |
ISTORE | 0x36 | ISTORE index | POP v1 | Store to local variable at index |
PUSH | 0x10 | PUSH immed1 | PUSH v1 | rPush value |
POP | 0x57 | POP | POP v1 | Pop value, discard |
IFEQ | 0x99 | IFEQ offset1 | POP v1 | Branch on v1 == 0 |
IFNE | 0x9a | IFNE offset1 | POP v1 | Branch v1 != 0 |
IFLT | 0x9b | IFLT offset1 | POP v1 | Branch on v1 < 0 |
IFLE | 0x9e | IFLE offset1 | POP v1 | Branch on v1 <= 0 |
IFGT | 0x9d | IFGT offset1 | POP v1 | Branch on v1 > 0 |
IFGE | 0x9c | IFGE offset1 | POP v1 | Branch on v1 >= 0 |
GOTO | 0xa7 | GOTO offset1 | none | Unconditional branch |
IRETURN | 0xac | IRETURN | POP v1 | Integer return |
The local variable instructions (ILOAD,ISTORE) are each followed by an 8-bit unsigned index value which determines the location within the local variable array that should be read or written. You may assume that there are no more than 256 local variables, and you do not have to check that the index is valid (although a real JVM interpreter would have to perform this check). Each entry in the local variable array is a 8-bit word.
The PUSH instruction is followed by a 8-bit signed immediate value. The immediate value is sign extended to a 8-bit integer, and then pushed on to the stack. The 8-bit result of the POP instruction is simply discarded.
The branch instructions (GOTO,IFNE,IFEQ,IFLE,IFLT,IFGE,IFGT) are followed by a byte which specifies a signed offset relative to the start address of the branch instruction. All of the comparison operations for the above conditional branch instructions are signed comparisons.
The IRETURN instruction signals the end of the computation (our simplified model of the JVM only handles interpreting a single Java method). The 8-bit value at the top of the stack is popped and then used as the return value.