A simple virtual machine written in Go that started out as a way to pass time during the holidays.
The GVM is a very simple virtual machine where you can play with with integer registers, a call stack and a stack.
The GVM executes GVM binary files, which are produced by the compiler.
These are files written with little endian encoding, containing a header used by the GVM to check binary compatibility between itself and the compiled file version. The header is then followed by the number of elements in the code array, followed by the code array itself.
The GVM's assembly language is what you use to write code that will be compiled into actual bytecode. The proposed extension is 'gsm', though, of course, this makes no difference.
There are some quality of life simplifications for the written language that make it not an exact map to the GVM compiled bytecode, but all the exceptions are laid out below.
There are a few examples of GSM programs located in the examples
folder at
the root of this repository. They hopefully can serve as self-explanatory and
reasonable examples of GSM usage.
Integer registers are referred to as r<n>
where <n>
indicates the number
of the register. For example r1
refers to the first integer register and
r16
refers to the sixteenth integer register.
Labels are used to simplify control flow, being essentially an abstraction for the following instruction address.
They are written as tokens ending with :
, with the name of the label being
everything before the :
.
One can also use sublabels. The basic idea is that after any given label lab
is defined, any labels starting with a .
will be a sublabel of lab
, being
effectively a short alias for lab.<sublabel>
and the sublabel. For example,
in the code:
main:
const r1 2
.second
const r2 5
jmp .second
the label .second
is effectively an independent label named main.second
.
Note that this implies a scope generated by common labels. To illustrate this, consider the example below:
main1:
const r1 2
.second
const r2 5
jmp .second
main2:
const r1 2
.second
const r2 5
jmp .second
which is effectively the same as
main1:
const r1 2
main1.second
const r2 5
jmp main1.second
main2:
const r1 2
main2.second
const r2 5
jmp main2.second
Note also that if a label named main
is defined, an instruction is added
implicitly by the compiler to the start of the code sequence that jumps to
this label, so that it can be used as a logical entry point for the program.
Comments start with the character ;
. As with usual comments, both the
character ;
and everything that follows it will be ignored by the compiler.
Instruction | Param | Param | Description |
---|---|---|---|
halt |
Stops program execution. | ||
const |
c |
r |
Writes the constant c to register r . |
push |
r |
Pushes the value from register r onto the stack. |
|
pop |
r |
Pops the value at the top of the stack into register r . |
|
inc |
r |
Increases the value of register r by 1. |
|
dec |
r |
Decreases the value of register r by 1. |
|
mov |
r1 |
r2 |
Copies the value of register r1 to register r2 . |
add |
r1 |
r2 |
Adds the value of register r1 to register r2 . |
sub |
r1 |
r2 |
Subtracts the value of register r1 from the value of register r2 . |
mul |
r1 |
r2 |
Multiplies the value of register r1 to the value of register r2 |
div |
r1 |
r2 |
Divides the value of register r2 by the value of register r1 , saving the result in register r2 . |
rem |
r1 |
r2 |
Stores the remainder of the division of the value of register r2 by the value of register r1 in register r2 . |
cmp |
r1 |
r2 |
Stores a comparison between registers r1 and r2 . |
jmp |
l |
Jumps to label l . |
|
jeq |
l |
Jumps to label l if in last comparison r1 = r2 . |
|
jne |
l |
Jumps to label l if in last comparison r1 != r2 . |
|
jgt |
l |
Jumps to label l if in last comparison r1 > r2 . |
|
jlt |
l |
Jumps to label l if in last comparison r1 < r2 . |
|
jge |
l |
Jumps to label l if in last comparison r1 >= r2 . |
|
jle |
l |
Jumps to label l if in last comparison r1 <= r2 . |
|
jerr |
l |
Jumps to label l if the error flag is set. Empties the error flag. |
|
show |
r |
Displays the content of register r to standard output. |
|
call |
l |
Jumps to the label l while pushing current position to the callstack. |
|
ret |
Jumps back to the last position in the callstack popping the value. | ||
noop |
Does nothing. | ||
iarg |
r |
Attempts to interpret the i -th argument as an integer and push to the stack, where i is the value of register r . In case of an error, the error flag is set. |