This is presentation from WG.NET (May 2019), where I'm discussing different aspects of virtualization, mainly in the context of programming languages. We'll covering up what stack vs. register based virtual machines are, what is interpreter and compiler and how to build our own bytecode interpreter for a toy programming language.
26. SAMPLE
PROGRAM
// int fib(n) {
// if(n == 0) return 0;
LoadArgument 0 // 0 - load last function argument N
Constant 0 // 2 - put 0
Equals // 4 - check equality: N == 0
JumpIfFalse 10 // 5 - if they are NOT equal, goto 10
Constant 0 // 7 - otherwise put 0
Return // 9 - and return it
// if(n < 3) return 1;
LoadArgument 0 // 10 - load last function argument N
Constant 3 // 12 - put 3
LessThan // 14 - check if 3 is less than N
JumpIfFalse 20 // 15 - if 3 is NOT less than N, goto 20
Constant 1 // 17 - otherwise put 1
Return // 19 - and return it
// else return fib(n-1) + fib(n-2);
LoadArgument 0 // 20 - load last function argument N
Constant 1 // 22 - put 1
Sub // 24 - calculate: N-1, result is on the stack
Call fib 1 // 25 - call fib function with 1 arg. from the stack
LoadArgument 0 // 28 - load N again
Constant 2 // 30 - put 2
Sub // 32 - calculate: N-2, result is on the stack
Call fib 1 // 33 - call fib function with 1 arg. from the stack
Add // 36 - since 2 fibs pushed their ret values on the stack, just add them
Return // 37 - return from procedure
// entrypoint - main function
Constant 6 // 38 - put 6
Call fib 1 // 40 - call function: fib(arg) where arg = 6;
Print // 43 - print result
Halt // 44 - stop program
27. Virtual Machine
typedef struct {
int* code; // program opcodes
int* stack; // virtual stack
int pc; // program counter
int sp; // stack pointer
int fp; // frame pointer
} VM;
// push value on top of the stack
#define PUSH(vm, v) vm->stack[++vm->sp] = v
// pop value from top of the stack
#define POP(vm) vm->stack[vm->sp--]
// get next bytecode
#define NEXT(vm) vm->code[vm->pc++]
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
28. Interpreter Loop
void (*op_handles[])(VM*) = {
// opcode function pointers
};
void vm_run(VM* vm) {
do {
int opcode = NEXT(vm);
if (opcode == Halt) {
return;
}
op_handles[opcode](vm);
} while(1);
}
29. Constant X
void op_constant(VM* vm) {
int x = NEXT(vm);
PUSH(vm, x);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
30. Constant X
void op_constant(VM* vm) {
int x = NEXT(vm);
PUSH(vm, x);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
x
31. Constant X
void op_constant(VM* vm) {
int x = NEXT(vm);
PUSH(vm, x);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
x
32. Print
void op_print(VM* vm) {
int x = POP(vm);
printf(“%dn”, x);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
x
33. Print
void op_print(VM* vm) {
int x = POP(vm);
printf(“%dn”, x);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
x
34. Print
void op_print(VM* vm) {
int x = POP(vm);
printf(“%dn”, x);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
x
STDOUT
35. Add / Sub / Mul / Div
void op_add(VM* vm) {
int b = POP(vm);
int a = POP(vm);
//TODO: check overflow
PUSH(vm, a + b);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a
b
36. Add / Sub / Mul / Div
void op_add(VM* vm) {
int b = POP(vm);
int a = POP(vm);
//TODO: check overflow
PUSH(vm, a + b);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a
b
37. Add / Sub / Mul / Div
void op_add(VM* vm) {
int b = POP(vm);
int a = POP(vm);
//TODO: check overflow
PUSH(vm, a + b);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a
b
38. Add / Sub / Mul / Div
void op_add(VM* vm) {
int b = POP(vm);
int a = POP(vm);
//TODO: check overflow
PUSH(vm, a + b);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a + b
39. Equal / Less / etc.
void op_eq(VM* vm) {
int b = POP(vm);
int a = POP(vm);
PUSH(vm, a == b ? 1 : 0);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a
b
40. Equal / Less / etc.
void op_eq(VM* vm) {
int b = POP(vm);
int a = POP(vm);
PUSH(vm, a == b ? 1 : 0);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a
b
41. Equal / Less / etc.
void op_eq(VM* vm) {
int b = POP(vm);
int a = POP(vm);
PUSH(vm, a == b ? 1 : 0);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
a
b
42. Equal / Less / etc.
void op_eq(VM* vm) {
int b = POP(vm);
int a = POP(vm);
PUSH(vm, a == b ? 1 : 0);
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
1
43. Conditional
Branches
void op_jmp_eq(VM* vm) {
int address = NEXT(vm);
if (POP(vm)) {
vm->pc = address;
}
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
1
44. Conditional
Branches
void op_jmp_eq(VM* vm) {
int address = NEXT(vm);
if (POP(vm)) {
vm->pc = address;
}
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
1
addr
45. Conditional
Branches
void op_jmp_eq(VM* vm) {
int address = NEXT(vm);
if (POP(vm)) {
vm->pc = address;
}
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
1
addr
46. Conditional
Branches
void op_jmp_eq(VM* vm) {
int address = NEXT(vm);
if (POP(vm)) {
vm->pc = address;
}
}
stack
20 00 42
00 51 04
7e 42 01
05 20 00
20 00 42
01 7d 10
00 7e 0b
code
stack pointer
program pointer
addr