Soil consists of three parts of state: registers, memory, and byte code.
Soil is not a von Neumann machine – byte code and memory live in separate worlds. Byte code can only read/write the memory, not byte code itself. You can't reflect on the byte code itself, for example, to store pointers to instructions. This gives Soil implementations the freedom to compile the byte code to machine code on startup.
Soil binaries are files that contain byte code and initial memory.
Soil uses two types of integers. A byte is 8 bits and a word is 8 bytes. Data types in Soil are little endian. Registers and pointers are word-sized.
Soil has 8 registers, all of which hold 64 bits.
| name | description |
|---|---|
sp |
stack pointer |
st |
status register |
a |
general-purpose register |
b |
general-purpose register |
c |
general-purpose register |
d |
general-purpose register |
e |
general-purpose register |
f |
general-purpose register |
Initially, sp holds the memory size.
All other registers are initialized to zero.
Soil uses byte-addressed memory. The memory size is defined by the VM implementation.
The Soil VM is sandboxed and only allows interactions with the outside world via system calls ("syscalls"). Syscalls are identified by a byte-sized syscall number. The canonical set of syscalls is defined in another part of the specification.
Byte code consists of a sequence of instructions.
Soil executes the instructions in sequence, starting from the first. Some instructions alter control flow by jumping to other instructions.
These are all instructions:
Does nothing.
End program execution with an error.
If a panic occurs, catches it, resets sp, and jumps to the catch address.
Ends a scope started by trystart.
Sets to to from.
Sets to to from.
Sets to to from.
Interprets from as an address and sets to to the word at that address in memory.
Interprets from as an address and sets to to the byte at that address in memory.
Interprets to as an address and sets the 64 bits at that address in memory to from.
Interprets to as an address and sets the 8 bits at that address in memory to from.
Decreases sp by 8, then runs store sp reg.
Runs load reg sp, then increases sp by 8.
Continues executing at the toth byte.
Runs jump to if st is not 0.
Runs jump target. Saves the formerly next instruction on an internal stack so that ret returns.
Returns to the instruction after the matching call.
Performs a syscall. Behavior depends on the syscall. The syscall can access all registers and memory.
Saves left - right in st.
If st is 0, sets st to 1, otherwise to 0.
If st is less than 0, sets st to 1; otherwise, sets it to 0.
If st is greater than 0, sets st to 1; otherwise, sets it to 0.
If st is 0 or less, sets st to 1; otherwise, sets it to 0.
If st is 0 or greater, sets st to 1; otherwise, sets it to 0.
If st is 0, sets st to 0; otherwise, sets it to 1.
Compares left and right by subtracting right from left and saving the result in st.
If st is 0, sets st to 1; otherwise, sets it to 0.
If st is less than 0, sets st to 1; otherwise, sets it to 0.
If st is greater than 0, sets st to 1; otherwise, sets it to 0.
If st is 0 or less, sets st to 1; otherwise, sets it to 0.
If st is 0 or greater, sets st to 1; otherwise, sets it to 0.
If st is 0, sets st to 0; otherwise, sets it to 1.
Interprets reg as an integer and sets it to a float of about the same value. TODO: specify edge cases.
Interprets reg as a float and sets it to its integer representation, rounded down. TODO: specify edge cases.
Adds from to to.
Subtracts from from to.
Multiplies from and to, saving the result in to.
Divides dividend by divisor, saving the quotient in dividend.
Divides dividend by divisor, saving the remainder in dividend.
Adds from to to, interpreting both as floats.
Subtracts from from to, interpreting both as floats.
Multiplies from and to, interpreting both as floats, and saves the result in to.
Divides dividend by divisor, interpreting both as floats, and saves the quotient in dividend.
Performs a binary AND on to and from, saving the result in to.
Performs a binary OR on to and from, saving the result in to.
Performs a binary XOR on to and from, saving the result in to.
Inverts the bits of to.