ProxOS
A bytecode VM and assembler. I am writing all of it myself, which is most of the point.
What this is, honestly
01ProxOS is a bytecode VM, an assembly language called PrASM with an assembler for it, a binary format called PRX that the two agree on, and a CLI that runs either from a normal operating system. There is supposed to be a kernel eventually. It is in the project layout and there is no code behind it yet.
It is not state of the art, it does nothing that has not been done better somewhere else, and it is genuinely underpowered. A first fit allocator on a fixed 1024 integer heap and a GOTO based branching system are not going to impress anyone who works on real runtimes.
I build it because it is fun and because I can do the whole thing myself in a reasonable amount of time, with no AI involved. After specifying Kyro and having it built for me, I wanted something where I write every line. That is basically the entire reason it exists.
Why a VM
02I kept reading about operating systems and realizing I understood the vocabulary without understanding the machine. I could describe what a loader does. I could not have written one.
What I wanted to get at was the boundary between a program as a file on disk and a program as something running. On a real system that boundary is Mach-O or ELF, a loader, and a CPU, which is three large things at once. A bytecode VM lets me own all three at a size I can hold in my head.
The comment at the top of vm.h says the format works "similarly to MachO or Elf formats in terms of its function and place in the OS." PRX is not a serious executable format. It sits in the same slot as one, and building it taught me what that slot is for.
How it works
03Every instruction is at least two integers wide, so an operator taking no arguments still gets a padding integer. Operators taking two arguments carry a 0x80 flag on top of their base opcode. That is the bit of the format I actually like, because it means the decoder never needs a table of instruction widths. The encoding tells it.
Execution is a stack of frames, each holding ten registers plus a return register. CAL pushes a frame and copies the current registers into it, which is how a program passes arguments since nothing else in the design does.
The allocator is first fit, and if a request does not fit the heap compacts and retries once. Compaction is the part that made me think, because once you slide live chunks down, every reference into them is wrong. So a register carries an is_ref flag alongside its value, and compaction walks the registers and rewrites the ones flagged as references. That is a small amount of code and it is the first time I really got why a garbage collector has to know which words are pointers.
PrASM SOURCE (.prasm)
│
▼
ASSEMBLER near 1:1 translation
│
▼
PRX BYTECODE (.prx) 4-int magic + label count
│
▼
PROX VM
│
┌────────────┼────────────┐
▼ ▼ ▼
STACK REGISTERS HEAP
frames 10 + return 1024 intsWhere it stands
04The VM runs, the assembler assembles, and the CLI drives both. Two test suites are wired into CTest, and there are two example programs, one that prints hi and one that counts down through a branch, calls a subroutine, and uses the heap.
The kernel has not been started. That is the next real piece of work and it is bigger than everything above it put together.
; prints hi and a newline SET R0 104 PRC R0 SET R0 105 PRC R0 SET R0 10 PRC R0