bds Virtual Machine
Under the hood, bds runs a simple virtual machine called bdsVm.
This virtual machine has a set of instructions (i.e. assembly) and the respective OpCodes (analogous to a real CPU).
So, any bds program is compiled to bdsVm assembly and then executed by the bdsVm.
Why bdsVm?
The main reason for using a custom virtual machine is to be able to perform absolute serializetion (i.e. checkpoints). Other virtual machines (or CPUs) do not have this feature, so we needed to create out own VM mainly due to the need to generate and recover from checkpoints.
Checkpoints quite important in bds and are used in many operations, most notably when performing "improper tasks".
For instance when trying to continue execution on another server in the Cloud.
bdsVm model
The bdsVm is a stack machine.
That means that operations are performed on a "stack" instead of using "registers".
The main reason for using a stack model is simplicity, it is quite easy to implement a virtual machine using a stack model.
As opposed to other models, stack machines can be slower.
bdsVm information
Runing bds using the "debug" flag (-d), will show the corresponsing assembly language compilation.
For example, here is a simple bds program:
a := 2
b := 40
c := a + b
println "c=$c"
If we execute it using -d we'll see:
$ bds -d z.bds
...
# Assembly: Start
0 node 1258
# ProgramUnit : int a := 2
main:
2 scopepush
3 node 1260
# VariableInitImplicit : a := 2
5 pushi 2
7 varpop 'a'
9 node 1263
# VariableInitImplicit : b := 40
11 pushi 40
13 varpop 'b'
15 node 1266
# VariableInitImplicit : c := a + b
17 load 'a'
19 load 'b'
21 addi
22 varpop 'c'
24 node 1271
# Println : println "c=$c"
26 pushs 'c='
28 load 'c'
30 adds
31 println
32 halt
The comment lines (starting with #) show exactly the bds code that created the corresponding assembly instructions.
For example the line VariableInitImplicit : a := 2 means that there was a variable declared implicitly (i.e. the variable type is inferred from the initialization value)
The corresponding assembly code is:
# VariableInitImplicit : a := 2
5 pushi 2 # Push the value '2' to the stack
7 varpop 'a' # Create a variable called 'a' and set the value from the latest element in the stack (i.e. '2')
The node instructions refer to the node number in the Abstract Syntax Tree (AST) that was created from the Lexer+Parser.
Essentially, they are only references to the exact program file and line number and they are just used to know which file/line number we are corrently executing.
This is used for printing error messages if something fails during execution.
bdsVm state
If you run a program with -d command line option, bds will also show the execution details:
For each assembly instruction, bds -d will show:
- Current bds code being executed
- Current bdsVm instruction being executed
- Current stack
Example, using the same example program as before:
a := 2
b := 40
c := a + b
println "c=$c"
If we execute using -d, we can see the stack whenever it changes
Note that the output has been edited for clarity and some comments added to clarify the bdsVm assembly:
1 main:
2 scopepush # Create a new variable's scope
3 node 1260
4 # VariableInitImplicit : a := 2
5 pushi 2 # stack: [2 ]
7 varpop 'a' # Create a new variable 'a' and set using latest stack entry ('2')
9 node 1263
10 # VariableInitImplicit : b := 40
11 pushi 40 # stack: [40 ]
13 varpop 'b' # Create a new variable 'b' and set using latest stack entry ('40')
15 node 1266
16 # VariableInitImplicit : c := a + b
17 load 'a' # Load variable's "a" value to the stack. stack: [2 ]
19 load 'b' # Load variable's "b" value to the stack. stack: [2, 40 ]
21 addi # Add two integers values from stack, push result to stack. stack: [42 ]
22 varpop 'c' # Create a new variable 'c' and set using latest stack entry ('42')
24 node 1271
25 # Println : println "c=$c"
26 pushs 'c=' # Push string "c=" to the stack. stack: ['c=' ]
28 load 'c' # Load variable's "c" value to the stack. stack: ['c=', 42 ]
30 adds # Add two strings (concatenate) and push to the stack. stack: ['c=42' ]
31 println # Print latest value from the stack
32 halt # Halt bdsVm execution
bdsVm examples
Here are some "easy" examples of how bds code is compiled into bdsVm assembly
bds code: a = 4
pushi 4
load a
set
bds code: a = 2 + 3
pushi 2
pushi 3
addi
load a
set
bds code: a[i] = b[j] + 7
# Stack
push 7
load j
load b # b, j, 7
reflist # b[j], 7
addi
load i # i, b[j]+7
load a # a, i, b[j]+7
reflist # a[i], b[j]+7
set # -
bds code: a{'hi' + 1} = 'bye'
# Stack
pushs 'bye'
pushs 'hi'
pushi 1
adds # 'hi1', 'bye'
load a # a, 'hi1', 'bye'
refdict # a{'hi1'}, 'bye'
set # -
bds code: z.a[7]{'hi'} = 42
In this case z is an object
# Stack
pushi 42 # 42
pushs 'hi' # 'hi', 42
pushi 7 # 7, 'hi', 42
pushs 'a' # 'a', 7, 'hi', 42
load z # z, 'a', 7, 'hi', 42
reffield # z.a, 7, 'hi', 42
reflist # z.a[7], 'hi', 42
refdict # z.a[7]{'hi}, 42
set
bds code: Function call
bds code:
int f(int x) {
return x+1
}
z = f(7)
bdsVm code:
f:
load x
push 1
addi # x+1 is the return value
ret # Return from function:
# - Remove scope (restore old scope)
# - pop PC from call-stack (jump to that position)
main:
pushi 7
call f # Function call:
# - create new scope
# - add arguments as scope variables
# - push PC to call-stack
load z
set