implementing software machines in go and c
TRANSCRIPT
implementing software machines in Go and C
Eleanor McHugh
@feyeleanor
this is a talk about VMssystem virtualisation
hardware emulation
abstract virtual machines
system virtualisation
this is a talk about VMssystem virtualisation
hardware emulation
abstract virtual machines
hardware emulation
this is a talk about VMssystem virtualisation
hardware emulation
abstract virtual machines
program execution
this is a talk about VMssystem virtualisation
hardware emulation
abstract virtual machines
inspired by hardwarediscrete components
processors
storage
communications
software machinestimely
stateful
scriptable
#include <stdio.h>#define STACK_MAX 100
typedef enum {STACK_OK = 0,STACK_OVERFLOW,STACK_UNDERFLOW
} STACK_STATUS;
typedef struct stack STACK;struct stack {
int data[STACK_MAX];int size;
};
STACK *NewStack() {STACK s;s.size = 0;return &s;
}
STACK_STATUS push(STACK *s, int data) {if (s->size < STACK_MAX) {
s->data[s->size++] = data;return STACK_OK;
}return STACK_OVERFLOW;
}
STACK_STATUS pop(STACK *s, int *r) {if (s->size > 0) {
*r = s->data[s->size - 1];s->size--;return STACK_OK;
}return STACK_UNDERFLOW;
}
int main() {int l, r;STACK *s = NewStack();push(s, 1);push(s, 3);pop(s, &l);pop(s, &r);printf("%d + %d = %d\n", l, r, l + r);
}
c: array stack
package main
import "fmt"
type stack_status int
const (STACK_OK = stack_status(iota)STACK_OVERFLOWSTACK_UNDERFLOW
)
type stack struct {data []int
}
func (s *stack) Push(data int) {s.data = append(s.data, data)
}
func (s *stack) Pop() (int, stack_status) {if s == nil || len(s.data) < 1 {
return 0, STACK_UNDERFLOW}sp := len(s.data) - 1r := s.data[sp]s.data = s.data[:sp]return r, STACK_OK
}
func main() {s := new(stack)s.Push(1)s.Push(3)l, _ := s.Pop()r, _ := s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)
}
go: array stack
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
int main() {int l, r;STACK *s = push(NULL, 1);s = push(s, 3);pop(pop(s, &r), &l);printf("%d + %d = %d\n", l, r, l + r);
}
c: functional cactus stack
package main
import "fmt"
type stack struct {data inttail *stack
}
func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return
}
func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail
}
func main() {var l, r intvar s *stacks = s.Push(1).Push(3)l, s = s.Pop()r, s = s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)
}
go: functional cactus stack
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
int sum(STACK *tos) {int a, p;
a = 0;for (; tos != NULL;) {
tos = pop(tos, &p);a += p;
}return a;
}
int main() {STACK *s1 = push(NULL, 7);
STACK *s2 = push(push(s1, 7), 11);
s1 = push(push(push(s1, 2), 9), 4);
STACK *s3 = push(s1, 17);
s1 = push(s1, 3);printf("sum = %d\n", sum(s1));printf("sum = %d\n", sum(s2));printf("sum = %d\n", sum(s3));
}
c: functional cactus stack
package main
import "fmt"
type stack struct {data inttail *stack
}
func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return
}
func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail
}
func (s *stack) Sum() (r int) {for p := 0; s != nil; r += p {
p, s = s.Pop()}return
}
func main() {var s1, s2, s3 *stacks1 = s1.Push(7)s2 = s1.Push(7).Push(11)
s1 = s1.Push(2).Push(9).Push(4)s3 = s1.Push(17)
s1 = s1.Push(3)
fmt.Printf("sum = %v\n", s1.Sum())fmt.Printf("sum = %v\n", s2.Sum())fmt.Printf("sum = %v\n", s3.Sum())
}
go: functional cactus stack
dispatch loopsfetch
decode
execute
dispatch loopsread next instruction via a program counter
determine the operation to perform
execute the operation and adjust machine state
switch interpreterinstructions stored sequentially in memory
each represented by a token or opcode
available in all implementation languages
tokens can be compact - often single bytes
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
void interpret(int *PC) {int l, r;while (1) {
switch(*PC++) {case PUSH:
S = push(S, *PC++);break;
case ADD:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);break;
case PRINT:printf(“%d + %d = %d\n, l, r, S->data);break;
case EXIT:return;
}}
}
int main() {int program [] = {
(int)PUSH, 13,(int)PUSH, 28,(int)ADD,PRINT,EXIT,
};interpret(program);
}
c: switch interpreter
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
#define READ_OPCODE *PC++
void interpret(int *PC) {int l, r;while (1) {
switch(READ_OPCODE) {case PUSH:
S = push(S, READ_OPCODE);break;
case ADD:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);break;
case PRINT:printf(“%d + %d = %d\n, l, r, S->data);break;
case EXIT:return;
}}
}
int main() {int program [] = {
(int)PUSH, 13,(int)PUSH, 28,(int)ADD,PRINT,EXIT,
};interpret(program);
}
c: switch interpreter
package main
import "fmt"
func main() {var program = []interface{}{
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT,
}interpret(program)
}
type stack struct {data inttail *stack
}
func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return
}
func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail
}
type OPCODE int
const (PUSH = OPCODE(iota)ADDPRINTEXIT
)
func interpret(p []interface{}) {var l, r intS := new(stack)
for PC := 0; ; PC++ {if op, ok := p[PC].(OPCODE); ok {
switch op {case PUSH:
PC++S = S.Push(p[PC].(int))
case ADD:l, S, = S.Pop()r, S = S.Pop()S = S.Push(l + r)
case PRINT:fmt.Printf("%v + %v = %v\n", l, r, S.data)
case EXIT:return
}} else {
return}
}}
go: switch interpreter
direct call threadinginstructions stored sequentially in memory
each represented by a pointer to a function
not available in all languages
instructions each require a machine word
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef void (*opcode)();
STACK *S;opcode *PC;
void op_push() {S = push(S, (int)(long)(*PC++));
}
void op_add_and_print() {int l, r;S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);printf("%d + %d = %d\n", l, r, S->data);
}
void op_exit() {exit(0);
}
int main() {opcode program [] = {
op_push, (opcode)(long)13,op_push, (opcode)(long)28,op_add_and_print,op_exit
};PC = program;while (1) {
(*PC++)();}
}
c: direct call-threaded interpreter
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef void (*opcode)();
STACK *S;opcode *PC;
#define READ_OPCODE *PC++
void op_push() {S = push(S, (int)(long)(READ_OPCODE));
}
void op_add_and_print() {int l, r;S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);printf("%d + %d = %d\n", l, r, S->data);
}
void op_exit() {exit(0);
}
int main() {opcode program [] = {
op_push, (opcode)(long)13,op_push, (opcode)(long)28,op_add_and_print,op_exit
};PC = program;while (1) {
(READ_OPCODE)();}
}
c: direct call-threaded interpreter
package main
import "fmt"import "os"
func main() {p := new(Interpreter)p.m = []interface{}{
p.Push, 13,p.Push, 28,p.Add,p.Print,p.Exit,
}p.Run()
}
type stack struct {data inttail *stack
}
func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return
}
func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail
}
type Interpreter struct {S *stackl, r, PC intm []interface{}
}
func (i *Interpreter) opcode() func() {return i.m[i.PC].(func())
}
func (i *Interpreter) operand() int {return i.m[i.PC].(int)
}
func (i *Interpreter) Run() {for {
i.opcode()()i.PC++
}}
func (i *Interpreter) Push() {i.PC++i.S = i.S.Push(i.operand())
}
func (i *Interpreter) Add() {i.l, i.S = i.S.Pop()i.r, i.S = i.S.Pop()i.S = i.S.Push(i.l + i.r)
}
func (i *Interpreter) Print() {fmt.Printf("%v + %v = %v\n", i.l, i.r, i.S.data)
}
func (i *Interpreter) Exit() {os.Exit(0)
}
go: direct call-threaded interpreter
indirect threadinginstructions stored sequentially in memory
each represented by a local jump label
gcc/clang specific C extension
instructions indirectly load successor
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, EXIT } opcodes;
STACK *S;
void interpret(int *program) {static void *opcodes [] = {
&&op_push,&&op_add,&&op_print,&&op_exit
};
int l, r;int *PC = program;goto *opcodes[*PC++];
op_push:S = push(S, *PC++);goto *opcodes[*PC++];
op_add:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);goto *opcodes[*PC++];
op_print:printf("%d + %d = %d\n", l, r, S->data);goto *opcodes[*PC++];
op_exit:return;
}
int main() {int program [] = {
PUSH, 13,PUSH, 28,ADD,EXIT
};interpret(program);
}
c: indirect-threaded interpreter
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));
r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
#define READ_OPCODE *PC++
#define EXECUTE_OPCODE goto *opcodes[READ_OPCODE];
#define PRIMITIVE(name, body) \name: \
body; \EXECUTE_OPCODE
void interpret(int *program) {static void *opcodes [] = {
&&op_push,&&op_add,&&op_print,&&op_exit
};
int l, r;int *PC = program;EXECUTE_OPCODE;
PRIMITIVE(op_push, S = push(S, READ_OPCODE))
PRIMITIVE(op_add, \S = pop(S, &l); \S = pop(S, &r); \S = push(S, l + r); \
)
PRIMITIVE(op_print, printf("%d + %d = %d\n", l, r, S->data))
PRIMITIVE(op_exit, return)}
int main() {int program [] = {
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT
};interpret(program);
}
c: indirect-threaded interpreter
direct threadinginstructions stored sequentially in memory
each represented by a local jump label
gcc/clang specific C extension
instructions directly load successors
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
void **compile(int *PC, int words, void *despatch_table[]) {static void *compiler [] = {
&&comp_push,&&comp_add,&&comp_print,&&comp_exit
};
if (words < 1)return NULL;
void **program = malloc(sizeof(void *) * words);void **cp = program;goto *compiler[*PC++];
comp_push:*cp++ = despatch_table[PUSH];*cp++ = (void *)(long)*PC++;words -= 2;if (words == 0) return program;
goto *compiler[*PC++];
comp_add:*cp++ = despatch_table[ADD];words--;if (words == 0) return program;
goto *compiler[*PC++];
comp_print:*cp++ = despatch_table[PRINT];words--;if (words == 0) return program;
goto *compiler[*PC++];
comp_exit:*cp++ = despatch_table[EXIT];words--;if (words == 0) return program;
goto *compiler[*PC++];}
c: direct-threaded interpreter (1)
void interpret(int *PC, int words) {static void *despatch_table[] = {
&&op_push,&&op_add,&&op_print,&&op_exit
};
int l, r;void **program = compile(PC, words, despatch_table);if (program == NULL)
exit(1);goto **program++;
op_push:S = push(S, (int)(long)*program++);goto **program++;
op_add:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);goto **program++;
op_print:printf("%d + %d = %d\n", l, r, S->data);goto **program++;
op_exit:return;
}
int main() {int program[] = {
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT
};interpret(program, 7);
}
c: direct-threaded interpreter (2)
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
#define COMPILE(body) \COMPILE_NEXT_OPCODE \body
#define COMPILE_NEXT_OPCODE \if (words < 1) \
return program; \goto *compiler[*PC++];
#define DESCRIBE_PRIMITIVE(name, body) \name: \
body; \COMPILE_NEXT_OPCODE
#define WRITE_OPCODE(value) \*cp++ = value; \words--;
void **compile(int *PC, int words, void *despatch_table[]) {static void *compiler[] = {
&&push,&&add,&&print,&&exit
};
void **program = malloc(sizeof(void *) * words);void **cp = program;COMPILE( \
DESCRIBE_PRIMITIVE(push, \WRITE_OPCODE(despatch_table[PUSH]) \WRITE_OPCODE((void *)(long)*PC++)) \
DESCRIBE_PRIMITIVE(add, \WRITE_OPCODE(despatch_table[ADD])) \
DESCRIBE_PRIMITIVE(print, \WRITE_OPCODE(despatch_table[PRINT])) \
DESCRIBE_PRIMITIVE(exit, \WRITE_OPCODE(despatch_table[EXIT])) \
)}
c: direct-threaded interpreter (1)
#define READ_OPCODE *program++
#define EXECUTE_OPCODE goto *READ_OPCODE;
#define PRIMITIVE(name, body) \name: \
body; \EXECUTE_OPCODE
void interpret(int *PC, int words) {static void *despatch_table[] = {
&&push,&&add,&&print,&&exit
};
int l, r;void **program = compile(PC, words, despatch_table);if (program == NULL)
exit(1);EXECUTE( \
PRIMITIVE(push, S = push(S, (int)(long)READ_OPCODE)) \PRIMITIVE(add, \
S = pop(S, &l); \S = pop(S, &r); \S = push(S, l + r)) \
PRIMITIVE(print, printf("%d + %d = %d\n", l, r, S->data)) \PRIMITIVE(exit, return) \
)}
int main() {int program[] = {
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT
};interpret(program, 7);
}
c: direct-threaded interpreter (2)