C0VM is a virtual machine, influenced by Java Virtual Machine (JVM) and the LLVM.
C0VM is much simpler than the JVM, following the design of C0, a safe subset of C. There is no bytecode verification; in this way the machine bears a closer resemblance to the LLVM. It is fully functional design and should be able to execute arbitrary C0 code.
Compiled code to be executed by the C0 virtual machine is represented in a byte code format (.bc0).
This file contains numerical and string constants as well as byte code for the functions defined in the C0 source.
C0 has types: int, bool, char, string, t[], and t*. In C0VM, we will represent primitive types as 32-bit signed integers (abbr. "w32") and the reference types as void pointers (abbr. "*")
| C0 type | COVM type | C type |
|---|---|---|
| int | w32 | int32_t |
| bool | w32 | int32_t |
| char | w32 | int32_t |
| t[] | * | void* |
| t* | * | void* |
The C0VM defines several types of data that are used during the execution of a program. The first three are the operand stack, bytecode and the program counter).
The C0VM is a stack machine, similar in design to the JVM. Arithmetic ops and other instructions pop their operands from an operand stack and push their result back to the operand stack. The C0VM is defined in terms of operand stack S, a stack of only C0 values.
In the C0VM, instructions for the current C0 function are stored in an array of bytes. Functions are stored in the array bc0->function_pool. The main function is always stored at the index 0.
The program counter pc holds the address of the program instruction currently being executed. It always starts at 0 when you begin executing a function. Unless a non-local transfer of control occurs (goto, conditional branch, function call or return), the program counter is incremented by the number of bytes in the current instruction before the next instruction before the next instruction is fetched and interpreted.
Locals variable are stored in a c0_value array. Every struct function_info has a field num_vars which specifies how big this array needs to be in order to store all the locals of that function. The four components S, P, pc, and V are everything that we need to know in order to represent the current state of any function.
The C0VM has a call stack consisting of frames, each one containing local variables, a local operand stack, and a return address. The call stack grows when a function is called and shrink when a function returns, deallocating the frame during the return.
Every frame consists of the four components that represent the current state of some function call that got interrupted in order to call another function. It contains the operand stack S for computing expression values, a pointer P to the function's byte code, a return address pc, which is the address of the next instruction in the interpreted function, and an array of locals V. At any point of execution there is a current frame as well as a calling frame. The latter becomes the current frame when a function returns to its caller.
Numerical constants requiring more than 8 bits and all strings constants will be allocated in constant pools. There is integer pool and string pool. They never change during program execution.
Functions are kept in function pool and native pool (for library fns).
C0VM allocates strings, arrays and cells directly using calls to xmalloc and xcalloc.