A virtual machine (VM) is a high level abstraction on top of the native operating system, that emulates a physical machine. A virtual machine enables the same platform to run on multiple operating systems and hardware architectures. The Interpreters for Java and Python can be taken as examples, where the code is compiled into their VM specific bytecode. Here, we are talking about process virtual machines and not system virtual machines.
There are basically two main ways to implement a virtual machine
- Stack based: ex. Java VM
- Register based: ex. Dalvik VM
The memory structure where the operands are stored is a stack data structure. Operations are carried out by popping data from the stack, processing them and pushing in back the results in LIFO (Last in First Out) fashion. In a stack based virtual machine, the operation of adding two numbers would usually be carried out in the following manner (where 20, 7, and ‘result’ are the operands):
- POP 20
- POP 7
- ADD 20,7, result
- PUSH result
Because of the PUSH and POP operations, four lines of instructions is needed to carry out an addition operation. An advantage of the stack based model is that the operands are addressed implicitly by the stack pointer (SP in above image). In stack based VM’s, all the arithmetic and logic operations are carried out via Pushing and Popping the operands and results in the stack.
- Virtual machine does not need to know the operand addresses explicitly, as calling the stack pointer will give (Pop) the next operand.
In the register based implementation of a virtual machine, the data structure where the operands are stored is based on the registers of the CPU.
- ADD R1, R2, R3; # Add contents of R1 and R2, store result in R3
There is no POP or PUSH operations, so the instruction for adding is just one line. But unlike the stack, we need to explicitly mention the addresses of the operands as R1, R2, and R3.
- Overhead of pushing to and popping from a stack is non-existent, and instructions in a register based VM execute faster within the instruction dispatch loop.
- it allows for some optimizations that cannot be done in the stack based approach. One such instance is when there are common sub expressions in the code, the register model can calculate it once and store the result in a register for future use when the sub expression comes up again, which reduces the cost of recalculating the expression.
- Average register instruction is larger than an average stack instruction, as we need to specify the operand addresses explicitly.