Virtual Machine

The virtual machine (VM) is the heart of the Saffire system. Bytecode generated by the AST will be run by the VM.

Bytecode

Stack

The VM is a stack-based machine. Instead of using (CPU) registers, it uses a stack system on which it can push and pop objects.

Operators

Since a VM should be easy in working, the actual commands (operators) should be easy and few. Each operator consists of 0 or more operands to work with. How many operands is based on the actual operator number. Each operator is 1 byte in length. If the last bit is not set (which is the case for operators number 0x00 through 0x7F), the number of operands is zero. This allows for 127 0-operand operators.

If the last bit is set, the operator has 1 operand. This operator number 0x80 through 0xBF.

If the last two bits are set, the operator uses 2 operands. These are operator numbers 0xC0 through 0xDF.

If the last three bits are set, the operator uses 3 operands. These are operator numbers 0xE0 through 0xFE.

The last operator number, 0xFF is reserved. This can be used to increase the number of operators in case this will be needed. However, it will be very unlikely that more operators will ever be implemented.

The actual operators (opcodes) can be found in /src/components/vm/_generated_vm_opcodes.c, which is a generated file by reading the /src/components/vm/vm_opcodes.dat.

Blocks and more blocks

There are many different components named “blocks” in the VM.

  • stackframe
  • frameblock

frameblock

A frameblock is a while-block, or a foreach-block. These blocks can be nested. A maximum depth of 20 blocks is possible, so you could not nest more than 20 while-loops for instance.

i = 1;
b = 2;
while (b < 10) {         +---+
  b += 1;                    |
  io.println("i: ", i);      |-- Block 1
  i += 1;                    |
}                        +---+

for (i=0; i!=10; i++) {     +---------------------+
  for (j=0; j!=10; j++) {       +--+              |
    io.println("j: ", j);          |-- block 2    |---- block 1
  }                             +--+              |
}                           +---------------------+

Every time a while or foreach loop, or even an try/catch block is found, a SETUP_LOOP, or SETUP_EXCEPTION is emitted. These will push a new frameblock onto the stack, which some information about the loop. For instance:

  • Is it a loop or exception block?
  • It holds the instruction pointer of the start of the block (for continue)
  • It holds the instruction pointer of an “else” block (if any).
  • For exceptions, it holds the IP where all the catches begin.
  • For exceptions, it holds the IP where finally begins.
  • For exceptions, it holds the IP where finally ends.
  • For exceptions, a flag that tells the system we are inside the finally block or not.
  • A stackpointer.
  • A “visited” flag.
  • An iteration block.

We need to keep track of the start of a block, in case we use “continue”. We need to keep track of the else block, in case we need to goto the else block (breakelse)

In case of exceptions, we need to know where the catches begin. As soon as an exception is thrown, we jump to this part of the bytecode and start checking the catches. If we have a finally block, this is where it begins (we can jump to here from either a catchblock, or when the try-catch block ends).

The stackpointer is needed to save the stackpointer at the start of the loop. When we break out the loop, we must make sure that all objects pushed onto the stack in the loop are cleaned. SInce we do not keep track of this. But by saving th stackpointer, all we need to do is pop objects until the stackpointer is equal again.

A “visited” flag is just for the JUMP_IF_FALSE_AND_FIRST and JUMP_IF_TRUE_AND_FIRST. It’s a simple check that is needed in order to make the “foreach / else” work. If the FIRST iteration of a foreach is false, we jump to the else block. But if it hits false on a second (or more) iteration, it should not. This flag is set to one once at least we have one iteration done in a foreach-loop.

The iteration block stores information inside the current frameblock during ITER_FETCH. This stores information that can later be used for generating the meta object in a foreach. This assumes that ITER_FETCH is always called AFTER a SETUP_LOOP, as we must have a frameblock.

stackframe

A stackframe is