SlideShare a Scribd company logo
implementing
virtual machines
in
Go & C
Eleanor McHugh
@feyeleanor
slideshare://feyeleanor github://feyeleanor
just enough
CANSI
to get going
embrace
gopher
our inner
I made my own machine
Yes, we're building steam
I hate the same routine
— Building Steam, Abney Park
software is where
machine meets thought
and as programmers we
think a lot about thinking
whilst thinking very little
about machines
we style ourselves
philosophers & wizards
when machines need us
to be engineers & analysts
so this is a talk about
machine philosophy
framed in a language we
humans can understand
let's learn to understand
our Turing machines
by building new Turing
machines in their image
system virtualisation
hardware emulation
program execution
hardware
hardware
timely
stateful
conversational
main memory
main memory
stacks
heap
opcodes
stacks
sequential
bounded depth
push & pop
array stacks
contiguous
bounded depth
fixed allocation
c: array stack
contiguous
bounded depth
fixed allocation
#include <stdlib.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 depth;
};
STACK *NewStack() {
STACK *s;
s = malloc(sizeof(STACK));
s->depth = 0;
return s;
}
STACK_STATUS push(STACK *s, int data) {
if (s->depth < STACK_MAX) {
s->data[s->depth++] = data;
return STACK_OK;
}
return STACK_OVERFLOW;
}
STACK_STATUS pop(STACK *s, int *r) {
if (s->depth > -1) {
*r = s->data[s->depth - 1];
s->depth--;
return STACK_OK;
}
return STACK_UNDERFLOW;
}
int main() {
int l, r;
STACK *s = NewStack();
push(s, 1);
push(s, 3);
printf("depth = %dn", s->depth);
pop(s, &l);
pop(s, &r);
printf("%d + %d = %dn", l, r, l + r);
printf("depth = %dn", s->depth);
}
go: slice stack
contiguous
bounded depth
fixed allocation
package main
import "fmt"
type stack []int
func (s *stack) Push(data int) {
*s = append(*s, data)
}
func (s *stack) Pop() (r int) {
sp := len(*s) - 1
r = (*s)[sp]
*s = (*s)[:sp]
return
}
func main() {
s := new(stack)
s.Push(1)
s.Push(3)
fmt.Printf("depth = %dn", len(s))
l := s.Pop()
r := s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", len(s))
}
go: slice stack
contiguous
bounded depth
fixed allocation
package main
import "fmt"
type stack []int
func (s stack) Pop() (int, stack) {
sp := len(s) - 1
return s[sp], s[:sp]
}
func main() {
s := make(stack, 0)
s = append(s, 1, 3)
fmt.Printf("depth = %dn", len(s))
var l, r int
l, s = s.Pop()
r, s = s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", len(s))
}
cactus stacks
singly-linked
sequential
per item allocation
c: cactus stack
singly-linked
sequential
per-item allocation
#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 depth(STACK *s) {
int r = 0;
for (STACK *t = s; t != NULL; t = t->next) {
r++;
}
return r;
}
void gc(STACK **old, int items) {
STACK *t;
for (; items > 0 && *old != NULL; items--) {
t = *old;
*old = (*old)->next;
free(t);
}
}
int sum(STACK *tos) {
int a = 0;
for (int p = 0; tos != NULL;) {
tos = pop(tos, &p);
a += p;
}
return a;
}
void print_sum(STACK *s) {
printf("%d items: sum = %dn", depth(s), sum(s));
}
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);
print_sum(s1);
print_sum(s2);
print_sum(s3);
}
go: cactus stack
nil is empty
grows on push
automatic GC
package main
import "fmt"
type stack struct {
int
*stack
}
func (s stack) Push(v int) (r stack) {
r = stack{v,&s}
return
}
func (s stack) Pop() (v int, r stack) {
return s.int, *s.stack
}
func (s stack) Depth() (r int) {
for t := s.stack; t != nil; t = t.stack {
r++
}
return
}
func (s *stack) PrintSum() {
fmt.Printf("sum(%v): %vn", s.Depth(), s.Sum())
}
func (s stack) Sum() (r int) {
for t, n := s, 0; t.stack != nil; r += n {
n, t = t.Pop()
}
return
}
func main() {
s1 := new(stack).Push(7)
s2 := s1.Push(7).Push(11)
s1 = s1.Push(2).Push(9).Push(4)
s3 := s1.Push(17)
s1 = s1.Push(3)
s1.PrintSum()
s2.PrintSum()
s3.PrintSum()
}
caches
discontiguous
label-addressable
outside memory
c: hash map
data array
associative arrays
search
#include <limits.h>
struct map {
int size;
assoc_array_t **chains;
};
typedef struct map map_t;
map_t *map_new(int size) {
map_t *m = malloc(sizeof(map_t));
m->chains = malloc(sizeof(assoc_array_t*) * size);
for (int i = 0; i < size; i++) {
m->chains[i] = NULL;
}
m->size = size;
return m;
}
int map_chain(map_t *m, char *k) {
unsigned long int b;
for (int i = strlen(k) - 1; b < ULONG_MAX && i > 0; i--) {
b = b << 8;
b += k[i];
}
return b % m->size;
}
char *map_get(map_t *m, char *k) {
search_t *s = search_find(m->chains[map_chain(m, k)], k);
if (s != NULL) {
return s->value;
}
return NULL;
}
void map_set(map_t *m, char *k, char *v) {
int b = map_chain(m, k);
assoc_array_t *a = m->chains[b];
search_t *s = search_find(a, k);
if (s->value != NULL) {
s->cursor->value = strdup(v);
} else {
assoc_array_t *n = assoc_array_new(k, v);
if (s->cursor == a) {
n->next = s->cursor;
m->chains[b] = n;
} else if (s->cursor == NULL) {
s->memo->next = n;
} else {
n->next = s->cursor;
s->memo->next = n;
}
}
free(s);
}
c: hash map
data array
search
associative arrays
struct search {
char *term, *value;
assoc_array_t *cursor, *memo;
};
typedef struct search search_t;
search_t *search_new(assoc_array_t *a, char *k) {
search_t *s = malloc(sizeof(search_t));
s->term = k;
s->value = NULL;
s->cursor = a;
s->memo = NULL;
return s;
}
void search_step(search_t *s) {
s->value = assoc_array_get_if(s->cursor, s->term);
}
int searching(search_t *s) {
return s->value == NULL && s->cursor != NULL;
}
search_t *search_find(assoc_array_t *a, char *k) {
search_t *s = search_new(a, k);
for (search_step(s); searching(s); search_step(s)) {
s->memo = s->cursor;
s->cursor = s->cursor->next;
}
return s;
}
c: hash map
data array
search
associative arrays
#include <stdlib.h>
#include <string.h>
struct assoc_array {
char *key;
void *value;
struct assoc_array *next;
};
typedef struct assoc_array assoc_array_t;
assoc_array_t *assoc_array_new(char *k, char *v) {
assoc_array_t *a = malloc(sizeof(assoc_array_t));
a->key = strdup(k);
a->value = strdup(v);
a->next = NULL;
return a;
}
char *assoc_array_get_if(assoc_array_t *a, char *k) {
char *r = NULL;
if (a != NULL && strcmp(a->key, k) == 0) {
r = strdup(a->value);
}
return r;
}
c: hash map
data array
search
associative arrays
#include <stdio.h>
#include "map.h"
int main( int argc, char **argv ) {
map_t *m = map_new(1024);
map_set(m, "apple", "rosy");
printf("%sn", map_get(m, "apple"));
map_set(m, "blueberry", "sweet");
printf("%sn", map_get(m, "blueberry"));
map_set(m, "cherry", "pie");
printf("%sn", map_get(m, "cherry"));
map_set(m, "cherry", "tart");
printf("%sn", map_get(m, "cherry"));
printf("%sn", map_get(m, “tart"));
}
rosy
sweet
pie
tart
(null)
go: hash map
associative array
searcher
slice of arrays
package main
type AssocArray struct {
Key string
Value interface{}
Next *AssocArray
};
func Find(a *AssocArray, k string) (s *Search) {
s = &Search{ Term: k, Cursor: a }
for s.Step(); s.Searching(); s.Step() {
s.Memo = s.Cursor
s.Cursor = s.Cursor.Next
}
return
}
func (a *AssocArray) GetIf(k string) (r interface{}) {
if a != nil && a.Key == k {
r = a.Value
}
return
}
type Search struct {
Term string
Value interface{}
Cursor, Memo *AssocArray
};
func (s *Search) Step() *Search {
s.Value = s.Cursor.GetIf(s.Term)
return s
}
func (s *Search) Searching() bool {
return s.Value == nil && s.Cursor != nil
}
go: hash map
associative array
searcher
slice of arrays
package main
type AssocArray struct {
Key string
Value interface{}
Next *AssocArray
};
func Find(a *AssocArray, k string) (s *Search) {
s = &Search{ Term: k, Cursor: a }
for s.Step(); s.Searching(); s.Step() {
s.Memo = s.Cursor
s.Cursor = s.Cursor.Next
}
return
}
func (a *AssocArray) GetIf(k string) (r interface{}) {
if a != nil && a.Key == k {
r = a.Value
}
return
}
type Search struct {
Term string
Value interface{}
Cursor, Memo *AssocArray
};
func (s *Search) Step() *Search {
s.Value = s.Cursor.GetIf(s.Term)
return s
}
func (s *Search) Searching() bool {
return s.Value == nil && s.Cursor != nil
}
go: hash map
associative array
searcher
slice of arrays
type Map []*AssocArray
func (m Map) Chain(k string) int {
var c uint
for i := len(k) - 1; i > 0; i-- {
c = c << 8
c += (uint)(k[i])
}
return int(c) % len(m)
}
func (m Map) Set(k string, v interface{}) {
c := m.Chain(k)
a := m[c]
s := Find(a, k)
if s.Value != nil {
s.Cursor.Value = v
} else {
n := &AssocArray{ Key: k, Value: v }
switch {
case s.Cursor == a:
n.Next = s.Cursor
m[c] = n
case s.Cursor == nil:
s.Memo.Next = n
default:
n.Next = s.Cursor
s.Memo.Next = n
}
}
}
func (m Map) Get(k string) (r interface{}) {
if s := Find(m[m.Chain(k)], k); s != nil {
r = s.Value
}
return
}
go: hash map
hashmap
vs
native map
package main
import "fmt"
func main() {
v := make(map[string] interface{})
v["apple"] = "rosy"
v["blueberry"] = "sweet"
v["cherry"] = "pie"
fmt.Printf("%vn", v["apple"])
fmt.Printf("%vn", v["blueberry"])
fmt.Printf("%vn", v["cherry"])
v["cherry"] = "tart"
fmt.Printf("%vn", v["cherry"])
fmt.Printf("%vn", v["tart"])
m := make(Map, 1024)
m.Set("apple", "rosy")
m.Set("blueberry", "sweet")
m.Set("cherry", "pie")
fmt.Printf("%vn", m.Get("apple"))
fmt.Printf("%vn", m.Get("blueberry"))
fmt.Printf("%vn", m.Get("cherry"))
m.Set("cherry", "tart")
fmt.Printf("%vn", m.Get("cherry"))
fmt.Printf("%vn", m.Get("tart"))
}
rosy
sweet
pie
tart
(null)
rosy
sweet
pie
tart
(null)
heaps
word-aligned
contiguous
byte-addressable
go: slice heap
array of pointers
reflect
unsafe
package main
import "fmt"
import r "reflect"
import "unsafe"
type Memory []uintptr
var _BYTE_SLICE = r.TypeOf([]byte(nil))
var _MEMORY = r.TypeOf(Memory{})
var _MEMORY_BYTES = int(_MEMORY.Elem().Size())
func (m Memory) newHeader() (h r.SliceHeader) {
h = *(*r.SliceHeader)(unsafe.Pointer(&m))
h.Len = len(m) * _MEMORY_BYTES
h.Cap = cap(m) * _MEMORY_BYTES
return
}
func (m *Memory) Serialise() (b []byte) {
h := m.newHeader()
b = make([]byte, h.Len)
copy(b, *(*[]byte)(unsafe.Pointer(&h)))
return
}
func (m *Memory) Overwrite(i interface{}) {
switch i := i.(type) {
case Memory:
copy(*m, i)
case []byte:
h := m.newHeader()
b := *(*[]byte)(unsafe.Pointer(&h))
copy(b, i)
}
}
func (m *Memory) Bytes() (b []byte) {
h := m.newHeader()
return *(*[]byte)(unsafe.Pointer(&h))
}
func main() {
m := make(Memory, 2)
b := m.Bytes()
s := m.Serialise()
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
m.Overwrite(Memory{3, 5})
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
s = m.Serialise()
m.Overwrite([]byte{8, 7, 6, 5, 4, 3, 2, 1})
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
}
go: slice heap
array of pointers
reflect
unsafe
package main
import r "reflect"
import "unsafe"
type Memory []uintptr
var _BYTE_SLICE = r.TypeOf([]byte(nil))
var _MEMORY = r.TypeOf(Memory{})
var _MEMORY_BYTES = int(_MEMORY.Elem().Size())
func (m Memory) newHeader() (h r.SliceHeader) {
h = *(*r.SliceHeader)(unsafe.Pointer(&m))
h.Len = len(m) * _MEMORY_BYTES
h.Cap = cap(m) * _MEMORY_BYTES
return
}
func (m *Memory) Serialise() (b []byte) {
h := m.newHeader()
b = make([]byte, h.Len)
copy(b, *(*[]byte)(unsafe.Pointer(&h)))
return
}
func (m *Memory) Overwrite(i interface{}) {
switch i := i.(type) {
case Memory:
copy(*m, i)
case []byte:
h := m.newHeader()
b := *(*[]byte)(unsafe.Pointer(&h))
copy(b, i)
}
}
func (m *Memory) Bytes() (b []byte) {
h := m.newHeader()
return *(*[]byte)(unsafe.Pointer(&h))
}
func main() {
m := make(Memory, 2)
b := m.Bytes()
s := m.Serialise()
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
m.Overwrite(Memory{3, 5})
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
s = m.Serialise()
m.Overwrite([]byte{8, 7, 6, 5, 4, 3, 2, 1})
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
}
go: slice heap
array of pointers
reflect
unsafe
package main
import r "reflect"
import "unsafe"
type Memory []uintptr
var _BYTE_SLICE = r.TypeOf([]byte(nil))
var _MEMORY = r.TypeOf(Memory{})
var _MEMORY_BYTES = int(_MEMORY.Elem().Size())
func (m Memory) newHeader() (h r.SliceHeader) {
h = *(*r.SliceHeader)(unsafe.Pointer(&m))
h.Len = len(m) * _MEMORY_BYTES
h.Cap = cap(m) * _MEMORY_BYTES
return
}
func (m *Memory) Serialise() (b []byte) {
h := m.newHeader()
b = make([]byte, h.Len)
copy(b, *(*[]byte)(unsafe.Pointer(&h)))
return
}
func (m *Memory) Overwrite(i interface{}) {
switch i := i.(type) {
case Memory:
copy(*m, i)
case []byte:
h := m.newHeader()
b := *(*[]byte)(unsafe.Pointer(&h))
copy(b, i)
}
}
func (m *Memory) Bytes() (b []byte) {
h := m.newHeader()
return *(*[]byte)(unsafe.Pointer(&h))
}
func main() {
m := make(Memory, 2)
b := m.Bytes()
s := m.Serialise()
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
m.Overwrite(Memory{3, 5})
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
s = m.Serialise()
m.Overwrite([]byte{8, 7, 6, 5, 4, 3, 2, 1})
fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m)
fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b)
fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s)
}
despatch loops
fetch instruction
decode
execute
c: switch
portable
slow
byte tokens
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
#define READ_OPCODE *PC++
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
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 = %dn, 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: direct call
portable
pointer to function
multi-byte
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
#define READ_OPCODE *PC++
typedef void (*opcode)();
STACK *S;
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 = %dn", l, r, S->data);
}
void op_exit() {
exit(0);
}
void interpret(opcode *program) {
PC = program;
while (1) {
(READ_OPCODE)();
}
}
int main() {
opcode program [] = {
op_push, (opcode)(long)13,
op_push, (opcode)(long)28,
op_add_and_print,
op_exit
};
interpret(program);
}
c: indirect thread
gcc/clang specific
local jumps
indirect loading
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
typedef enum {
PUSH = 0, ADD, PRINT, EXIT
} opcodes;
void interpret(int *program) {
static void *opcodes [] = {
&&op_push, &&op_add, &&op_print, &&op_exit
};
int l, r;
STACK *S;
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 = %dn", l, r, S->data);
goto *opcodes[*PC++];
op_exit:
return;
}
int main() {
int program [] = {
PUSH, 13,
PUSH, 28,
ADD,
PRINT,
EXIT
};
interpret(program);
}
c: indirect thread
gcc/clang specific
local jumps
indirect loading
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
#define READ_OPCODE *PC++
#define EXECUTE_OPCODE 
goto *opcodes[READ_OPCODE];
#define PRIMITIVE(name, body) 
name: 
body; 
EXECUTE_OPCODE
typedef enum { PUSH = 0, ADD, PRINT, 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;
EXECUTE_OPCODE;
PRIMITIVE(op_push, 
S = push(S, READ_OPCODE) 
)
PRIMITIVE(op_print, 
printf("%d + %d = %dn", l, r, S->data) 
)
PRIMITIVE(op_add, 
S = pop(S, &l); 
S = pop(S, &r); 
S = push(S, l + r) 
)
PRIMITIVE(op_exit, 
return
)
}
int main() {
int program [] = {
PUSH, 13,
PUSH, 28,
ADD,
PRINT,
EXIT
};
interpret(program);
}
c: direct thread
void **compile(int *PC, int words, void *despatch_table[]) {
static void *compiler [] = {
&&c_push, &&c_add, &&c_print, &&c_exit
};
if (words < 1) return NULL;
void **program = malloc(sizeof(void *) * words);
void **cp = program;
goto *compiler[*PC++];
c_number:
*cp++ = (void *)(long)*PC++;
words—;
if (words == 0) return program;
goto *compiler[*PC++];
c_push:
*cp++ = despatch_table[PUSH];
*cp++ = (void *)(long)*PC++;
words—;
goto c_number;
c_add:
*cp++ = despatch_table[ADD];
words--;
if (words == 0) return program;
goto *compiler[*PC++];
c_print:
*cp++ = despatch_table[PRINT];
words--;
if (words == 0) return program;
goto *compiler[*PC++];
c_exit:
*cp++ = despatch_table[EXIT];
words--;
if (words == 0) return program;
goto *compiler[*PC++];
}
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 = %dn", l, r, S->data);
goto **program++;
op_exit:
return;
}
c: direct thread
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
#define COMPILE_NEXT_OPCODE goto *compiler[*PC++];
#define COMPILE_NUMBER 
*cp++ = (void *)(long)*PC++; 
words--;
#define PRIMITIVE_ADDRESS(op) *cp++ = despatch_table[op];
#define COMPILE_PRIMITIVE(name, body) 
c_##name: 
PRIMITIVE_ADDRESS(name) 
body; 
words--; 
if (words == 0) return program; 
COMPILE_NEXT_OPCODE
void **compile(int *PC, int words, void *despatch_table[]) {
static void *compiler [] = {
&&c_push, &&c_add, &&c_print, &&c_exit
};
if (words < 1) return NULL;
void **program = malloc(sizeof(void *) * words);
void **cp = program;
COMPILE_NEXT_OPCODE
COMPILE_PRIMITIVE(PUSH, COMPILE_NUMBER)
COMPILE_PRIMITIVE(ADD, ;)
COMPILE_PRIMITIVE(PRINT, ;)
COMPILE_PRIMITIVE(EXIT, ;)
}
#define NEXT_OPCODE goto **program++;
#define PRIMITIVE(name, body) 
op_##name: 
body; 
NEXT_OPCODE
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);
NEXT_OPCODE
PRIMITIVE(push, S = push(S, (int)(long)*program++))
PRIMITIVE(add, 
S = pop(S, &l); 
S = pop(S, &r); 
S = push(S, l + r); 
)
PRIMITIVE(print, printf("%d + %d = %dn", l, r, S->data))
PRIMITIVE(exit, return)
}
int main() {
int program[] = {
PUSH, 13,
PUSH, 28,
ADD,
PRINT,
EXIT
};
interpret(program, 7);
}
go: switch
constants
package main
import "fmt"
type stack struct {
int
*stack
}
func (s *stack) Push(v int) *stack {
return &stack{v, s}
}
func (s *stack) Pop() (int, *stack) {
return s.int, s.stack
}
type OPCODE int
const (
PUSH = OPCODE(iota)
ADD
PRINT
EXIT
)
func main() {
interpret([]interface{}{
PUSH, 13,
PUSH, 28,
ADD,
PRINT,
EXIT,
})
}
func interpret(p []interface{}) {
var l, r int
S := 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 = %vn", l, r, S.int)
case EXIT:
return
}
} else {
return
}
}
}
go: direct call
function pointers
package main
import "fmt"
import "os"
type stack struct {
int
*stack
}
func (s *stack) Push(v int) *stack {
return &stack{v, s}
}
func (s *stack) Pop() (int, *stack) {
return s.int, s.stack
}
func main() {
p := new(Interpreter)
p.Load(
p.Push, 13,
p.Push, 28,
p.Add,
p.Print,
p.Exit,
)
p.Run()
}
type Interpreter struct {
S *stack
l, r, PC int
m []interface{}
}
func (i *Interpreter) read_program() (r interface{}) {
return i.m[i.PC]
}
func (i *Interpreter) Load(p ...interface{}) {
i.m = p
}
func (i *Interpreter) Run() {
for {
i.read_program().(func())()
i.PC++
}
}
func (i *Interpreter) Push() {
i.PC++
i.S = i.S.Push(i.read_program().(int))
}
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 = %vn", i.l, i.r, i.S.int)
}
func (i *Interpreter) Exit() {
os.Exit(0)
}
go: direct call
function literals
package main
import "fmt"
import "os"
type stack struct {
int
*stack
}
func (s *stack) Push(v int) *stack {
return &stack{v, s}
}
func (s *stack) Pop() (int, *stack) {
return s.int, s.stack
}
type Primitive func(*Interpreter)
type Interpreter struct {
S *stack
l, r, PC int
m []Primitive
}
func (i *Interpreter) read_program() Primitive {
return i.m[i.PC]
}
func (i *Interpreter) Run() {
for {
i.read_program()(i)
i.PC++
}
}
func main() {
p := &Interpreter{
m: []Primitive{
func(i *Interpreter) {
i.S = i.S.Push(13)
},
func(i *Interpreter) {
i.S = i.S.Push(28)
},
func(i *Interpreter) {
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) {
fmt.Printf("%v + %v = %vn", i.l, i.r, i.S.int)
},
func (i *Interpreter) {
os.Exit(0)
},
},
}
p.Run()
}
go: direct call
closures
package main
import "fmt"
import "os"
type stack struct {
int
*stack
}
func (s *stack) Push(v int) *stack {
return &stack{v, s}
}
func (s *stack) Pop() (int, *stack) {
return s.int, s.stack
}
type Primitive func(*Interpreter)
type Interpreter struct {
S *stack
l, r, PC int
m []Primitive
}
func (i *Interpreter) read_program() Primitive {
return i.m[i.PC]
}
func (i *Interpreter) Run() {
for {
i.read_program()
i.PC++
}
}
func main() {
var p *Interpreter
p := &Interpreter{
m: []Primitive{
func() {
p.S = p.S.Push(13)
},
func() {
p.S = p.S.Push(28)
},
func() {
p.l, p.S = p.S.Pop()
p.r, p.S = p.S.Pop()
p.S = p.S.Push(p.l + p.r)
},
func() {
fmt.Printf("%v + %v = %vn", p.l, p.r, p.S.int)
},
func () {
os.Exit(0)
},
},
}
p.Run()
}
go: direct call
closures
compilation
package main
import "fmt"
import "os"
type stack struct {
int
*stack
}
func (s *stack) Push(v int) *stack {
return &stack{v, s}
}
func (s *stack) Pop() (int, *stack) {
return s.int, s.stack
}
type Primitive func()
type Label string
type labels []Label
type VM struct {
PC int
m []interface{}
labels
}
func (v *VM) Load(program ...interface{}) {
v.labels = make(labels)
v.PC = -1
for _, token := range program {
v.assemble(token)
}
}
func (v *VM) assemble(token interface{}) {
switch t := token.(type) {
case Label:
if i, ok := v.labels[t]; ok {
v.m = append(v.m, i)
} else {
v.labels[t] = v.PC
}
default:
v.m = append(v.m, token)
v.PC++
}
}
func (v *VM) Run() {
v.PC = -1
for {
v.PC++
v.read_program().(Primitive)()
}
}
func (v *VM) read_program() interface{} {
return v.m[v.PC]
}
type Interpreter struct {
VM
S *stack
l, r int
}
go: direct call
closures
compilation
func main() {
p := new(Interpreter)
p.Load(
Label("start"),
Primitive(func() { p.S = p.S.Push(13) }),
Primitive(func() { p.S = p.S.Push(28) }),
Primitive(func() {
p.l, p.S = p.S.Pop()
p.r, p.S = p.S.Pop()
p.S = p.S.Push(p.l + p.r)
}),
Primitive(func() {
fmt.Printf("%v + %v = %vn", p.l, p.r, p.S.int)
}),
Primitive(func() { os.Exit(0) }),
Label("end"),
)
p.Run()
}
processing units
registers
operands
local caching
go: vm harness
closures
compilation
package main
import "fmt"
import "os"
type stack struct {
int
*stack
}
func (s *stack) Push(v int) *stack {
return &stack{v, s}
}
func (s *stack) Pop() (int, *stack) {
return s.int, s.stack
}
type Primitive func()
type Label string
type labels map[Label]int
type VM struct {
PC int
m []interface{}
labels
}
func (v *VM) Load(program ...interface{}) {
v.labels = make(labels)
v.PC = -1
for _, token := range program {
v.assemble(token)
}
}
func (v *VM) assemble(token interface{}) {
switch t := token.(type) {
case Label:
if i, ok := v.labels[t]; ok {
v.m = append(v.m, i)
} else {
v.labels[t] = v.PC
}
default:
v.m = append(v.m, token)
v.PC++
}
}
func (v *VM) Run() {
v.PC = -1
for {
v.PC++
v.read_program().(Primitive)()
}
}
func (v *VM) read_program() interface{} {
return v.m[v.PC]
}
stack machine
zero operands
type StackMachine struct {
*stack
VM
}
func (s *StackMachine) Push() {
s.PC++
s.S = s.S.Push(s.read_program().(int))
}
func (s *StackMachine) Add() {
s.l, s.S = s.S.Pop()
s.r, s.S = s.S.Pop()
s.S = s.S.Push(s.l + s.r)
}
func (s *StackMachine) JumpIfNotZero() {
s.PC++
if s.int != 0 {
s.PC = s.m[s.PC].(int)
}
}
func main() {
s := new(StackMachine)
print_state := Primitive(func() {
fmt.Printf("pc[%v]: %v, TOS: %vn",
s.PC,
s.m[s.PC],
s.int,
)
})
s.Load(
Primitive(s.Push), 13,
Label("dec"),
Primitive(func() {
s.stack = s.stack.Push(-1)
}),
Primitive(s.Add),
print_state,
Primitive(s.JumpIfNotZero), Label("dec"),
print_state,
Primitive(func() {
os.Exit(0)
}),
).Run()
}
the accumulator
single register
single operand
type AccMachine struct {
int
VM
}
func (a *AccMachine) Clear() {
a.int = 0
}
func (a *AccMachine) LoadValue() {
a.PC++
a.int = a.read_program().(int)
}
func (a *AccMachine) Add() {
a.PC++
a.int += a.read_program().(int)
}
func (a *AccMachine) JumpIfNotZero() {
a.PC++
if a.int != 0 {
a.PC = a.m[a.PC].(int)
}
}
func main() {
a := new(AccMachine)
print_state := Primitive(func() {
fmt.Printf("pc[%v]: %v, Acc: %vn",
a.PC,
a.m[a.PC],
a.int,
)
})
a.Load(
Primitive(a.Clear),
Primitive(a.LoadValue), 13,
Label("dec"),
Primitive(a.Add), -1,
print_state,
Primitive(a.JumpIfNotZero), Label("dec"),
print_state,
Primitive(func() {
os.Exit(0)
}),
).Run()
}
register machine
multi-register
multi-operand
type RegMachine struct {
R []int
VM
}
func (r *RegMachine) Clear() {
r.R = make([]int, 2, 2)
}
func (r *RegMachine) read_value() int {
r.PC++
return r.read_program().(int)
}
func (r *RegMachine) LoadValue() {
r.R[r.read_value()] = r.read_value()
}
func (r *RegMachine) Add() {
i := r.read_value()
j := r.read_value()
r.R[i] += r.R[j]
}
func (r *RegMachine) JumpIfNotZero() {
if r.R[r.read_value()] != 0 {
r.PC = r.read_value()
} else {
r.PC++
}
}
func main() {
r := new(RegMachine)
print_state := Primitive(func() {
fmt.Printf("pc[%v]: %v, Acc: %vn",
r.PC,
r.m[r.PC],
r.R,
)
})
r.Load(
Primitive(r.Clear),
Primitive(r.LoadValue), 0, 13,
Primitive(r.LoadValue), 1, -1,
Label("dec"),
Primitive(r.Add), 0, 1,
print_state,
Primitive(r.JumpIfNotZero), 0, Label("dec"),
print_state,
Primitive(func() {
os.Exit(0)
}),
).Run()
}
vector & matrices
hypercubes
graphs
quaternions
any datatype can be a register
Go & cgo: integrating
existing C code with Go
Andreas Krennmair <ak@synflood.at>
Golang User Group Berlin
Twitter: @der_ak
cgo
inline c
Feb 5, 2013 at 10:28PM
Caleb DoxseyCaleb Doxsey // BlogBlog // Go & AssemblyGo & Assembly
Go & AssemblyGo & Assembly
Caleb Doxsey
One of my favorite parts about Go is its unwavering focus on utility. Sometimes we place
so much emphasis on language design that we forget all the other things programming
involves. For example:
Go's compiler is fast
Go comes with a robust standard library
Go works on a multitude of platforms
Go comes with a complete set of documentation available from the command line /
a local web server / the internet
All Go code is statically compiled so deployment is trivial
The entirety of the Go source code is available for perusal in an easy format online
(like this)
Go has a well defined (and documented) grammar for parsing. (unlike C++ or Ruby)
Go comes with a package management tool. go get X (for example go get
code.google.com/p/go.net/websocket )
assembly
SIMD
branch prediction
cache misses
memory latency
This repository Pull requests Issues Gist
No description or website provided. — Edit
00 memory experiments with Fiddle::Pointer exploring use of C-style memory mana… 5 days ago
01 stacks stacks in Go 5 days ago
02 hashes roll-your-own hash maps in Go 5 days ago
03 dispatcher Go versions of dispatch loops 5 days ago
04 architecture implementations of popular VM architectural styles 5 days ago
LICENSE Initial commit 5 days ago
README.md added links for slides and video of talks based on this code 5 days ago
Search
feyeleanor / vm-fragments
Code Issues 0 Pull requests 0 Projects 0 Wiki Pulse Graphs Settings
13 commits 1 branch 0 releases 1 contributor MIT
Clone or downloadClone or downloadCreate new file Upload files Find filemasterBranch: New pull request
Latest commit 9ee7fe0 5 days agofeyeleanor implementations of popular VM architectural styles
README.md
vm-fragments
A collection of code fragments for talks I've given around implementing simple virtual machine internals in C, Go and Ruby.
The slides for these talks are available at:
1 01Unwatch Star Fork
source code
Implementing virtual machines in go & c 2018 redux
implementing
virtual machines
in
Go & C
Eleanor McHugh
@feyeleanor
slideshare://feyeleanor github://feyeleanor

More Related Content

Implementing virtual machines in go & c 2018 redux

  • 1. implementing virtual machines in Go & C Eleanor McHugh @feyeleanor slideshare://feyeleanor github://feyeleanor
  • 4. I made my own machine Yes, we're building steam I hate the same routine — Building Steam, Abney Park
  • 6. and as programmers we think a lot about thinking
  • 7. whilst thinking very little about machines
  • 9. when machines need us to be engineers & analysts
  • 10. so this is a talk about machine philosophy
  • 11. framed in a language we humans can understand
  • 12. let's learn to understand our Turing machines
  • 13. by building new Turing machines in their image
  • 23. c: array stack contiguous bounded depth fixed allocation #include <stdlib.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 depth; }; STACK *NewStack() { STACK *s; s = malloc(sizeof(STACK)); s->depth = 0; return s; } STACK_STATUS push(STACK *s, int data) { if (s->depth < STACK_MAX) { s->data[s->depth++] = data; return STACK_OK; } return STACK_OVERFLOW; } STACK_STATUS pop(STACK *s, int *r) { if (s->depth > -1) { *r = s->data[s->depth - 1]; s->depth--; return STACK_OK; } return STACK_UNDERFLOW; } int main() { int l, r; STACK *s = NewStack(); push(s, 1); push(s, 3); printf("depth = %dn", s->depth); pop(s, &l); pop(s, &r); printf("%d + %d = %dn", l, r, l + r); printf("depth = %dn", s->depth); }
  • 24. go: slice stack contiguous bounded depth fixed allocation package main import "fmt" type stack []int func (s *stack) Push(data int) { *s = append(*s, data) } func (s *stack) Pop() (r int) { sp := len(*s) - 1 r = (*s)[sp] *s = (*s)[:sp] return } func main() { s := new(stack) s.Push(1) s.Push(3) fmt.Printf("depth = %dn", len(s)) l := s.Pop() r := s.Pop() fmt.Printf("%d + %d = %dn", l, r, l+r) fmt.Printf("depth = %dn", len(s)) }
  • 25. go: slice stack contiguous bounded depth fixed allocation package main import "fmt" type stack []int func (s stack) Pop() (int, stack) { sp := len(s) - 1 return s[sp], s[:sp] } func main() { s := make(stack, 0) s = append(s, 1, 3) fmt.Printf("depth = %dn", len(s)) var l, r int l, s = s.Pop() r, s = s.Pop() fmt.Printf("%d + %d = %dn", l, r, l+r) fmt.Printf("depth = %dn", len(s)) }
  • 27. c: cactus stack singly-linked sequential per-item allocation #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 depth(STACK *s) { int r = 0; for (STACK *t = s; t != NULL; t = t->next) { r++; } return r; } void gc(STACK **old, int items) { STACK *t; for (; items > 0 && *old != NULL; items--) { t = *old; *old = (*old)->next; free(t); } } int sum(STACK *tos) { int a = 0; for (int p = 0; tos != NULL;) { tos = pop(tos, &p); a += p; } return a; } void print_sum(STACK *s) { printf("%d items: sum = %dn", depth(s), sum(s)); } 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); print_sum(s1); print_sum(s2); print_sum(s3); }
  • 28. go: cactus stack nil is empty grows on push automatic GC package main import "fmt" type stack struct { int *stack } func (s stack) Push(v int) (r stack) { r = stack{v,&s} return } func (s stack) Pop() (v int, r stack) { return s.int, *s.stack } func (s stack) Depth() (r int) { for t := s.stack; t != nil; t = t.stack { r++ } return } func (s *stack) PrintSum() { fmt.Printf("sum(%v): %vn", s.Depth(), s.Sum()) } func (s stack) Sum() (r int) { for t, n := s, 0; t.stack != nil; r += n { n, t = t.Pop() } return } func main() { s1 := new(stack).Push(7) s2 := s1.Push(7).Push(11) s1 = s1.Push(2).Push(9).Push(4) s3 := s1.Push(17) s1 = s1.Push(3) s1.PrintSum() s2.PrintSum() s3.PrintSum() }
  • 30. c: hash map data array associative arrays search #include <limits.h> struct map { int size; assoc_array_t **chains; }; typedef struct map map_t; map_t *map_new(int size) { map_t *m = malloc(sizeof(map_t)); m->chains = malloc(sizeof(assoc_array_t*) * size); for (int i = 0; i < size; i++) { m->chains[i] = NULL; } m->size = size; return m; } int map_chain(map_t *m, char *k) { unsigned long int b; for (int i = strlen(k) - 1; b < ULONG_MAX && i > 0; i--) { b = b << 8; b += k[i]; } return b % m->size; } char *map_get(map_t *m, char *k) { search_t *s = search_find(m->chains[map_chain(m, k)], k); if (s != NULL) { return s->value; } return NULL; } void map_set(map_t *m, char *k, char *v) { int b = map_chain(m, k); assoc_array_t *a = m->chains[b]; search_t *s = search_find(a, k); if (s->value != NULL) { s->cursor->value = strdup(v); } else { assoc_array_t *n = assoc_array_new(k, v); if (s->cursor == a) { n->next = s->cursor; m->chains[b] = n; } else if (s->cursor == NULL) { s->memo->next = n; } else { n->next = s->cursor; s->memo->next = n; } } free(s); }
  • 31. c: hash map data array search associative arrays struct search { char *term, *value; assoc_array_t *cursor, *memo; }; typedef struct search search_t; search_t *search_new(assoc_array_t *a, char *k) { search_t *s = malloc(sizeof(search_t)); s->term = k; s->value = NULL; s->cursor = a; s->memo = NULL; return s; } void search_step(search_t *s) { s->value = assoc_array_get_if(s->cursor, s->term); } int searching(search_t *s) { return s->value == NULL && s->cursor != NULL; } search_t *search_find(assoc_array_t *a, char *k) { search_t *s = search_new(a, k); for (search_step(s); searching(s); search_step(s)) { s->memo = s->cursor; s->cursor = s->cursor->next; } return s; }
  • 32. c: hash map data array search associative arrays #include <stdlib.h> #include <string.h> struct assoc_array { char *key; void *value; struct assoc_array *next; }; typedef struct assoc_array assoc_array_t; assoc_array_t *assoc_array_new(char *k, char *v) { assoc_array_t *a = malloc(sizeof(assoc_array_t)); a->key = strdup(k); a->value = strdup(v); a->next = NULL; return a; } char *assoc_array_get_if(assoc_array_t *a, char *k) { char *r = NULL; if (a != NULL && strcmp(a->key, k) == 0) { r = strdup(a->value); } return r; }
  • 33. c: hash map data array search associative arrays #include <stdio.h> #include "map.h" int main( int argc, char **argv ) { map_t *m = map_new(1024); map_set(m, "apple", "rosy"); printf("%sn", map_get(m, "apple")); map_set(m, "blueberry", "sweet"); printf("%sn", map_get(m, "blueberry")); map_set(m, "cherry", "pie"); printf("%sn", map_get(m, "cherry")); map_set(m, "cherry", "tart"); printf("%sn", map_get(m, "cherry")); printf("%sn", map_get(m, “tart")); } rosy sweet pie tart (null)
  • 34. go: hash map associative array searcher slice of arrays package main type AssocArray struct { Key string Value interface{} Next *AssocArray }; func Find(a *AssocArray, k string) (s *Search) { s = &Search{ Term: k, Cursor: a } for s.Step(); s.Searching(); s.Step() { s.Memo = s.Cursor s.Cursor = s.Cursor.Next } return } func (a *AssocArray) GetIf(k string) (r interface{}) { if a != nil && a.Key == k { r = a.Value } return } type Search struct { Term string Value interface{} Cursor, Memo *AssocArray }; func (s *Search) Step() *Search { s.Value = s.Cursor.GetIf(s.Term) return s } func (s *Search) Searching() bool { return s.Value == nil && s.Cursor != nil }
  • 35. go: hash map associative array searcher slice of arrays package main type AssocArray struct { Key string Value interface{} Next *AssocArray }; func Find(a *AssocArray, k string) (s *Search) { s = &Search{ Term: k, Cursor: a } for s.Step(); s.Searching(); s.Step() { s.Memo = s.Cursor s.Cursor = s.Cursor.Next } return } func (a *AssocArray) GetIf(k string) (r interface{}) { if a != nil && a.Key == k { r = a.Value } return } type Search struct { Term string Value interface{} Cursor, Memo *AssocArray }; func (s *Search) Step() *Search { s.Value = s.Cursor.GetIf(s.Term) return s } func (s *Search) Searching() bool { return s.Value == nil && s.Cursor != nil }
  • 36. go: hash map associative array searcher slice of arrays type Map []*AssocArray func (m Map) Chain(k string) int { var c uint for i := len(k) - 1; i > 0; i-- { c = c << 8 c += (uint)(k[i]) } return int(c) % len(m) } func (m Map) Set(k string, v interface{}) { c := m.Chain(k) a := m[c] s := Find(a, k) if s.Value != nil { s.Cursor.Value = v } else { n := &AssocArray{ Key: k, Value: v } switch { case s.Cursor == a: n.Next = s.Cursor m[c] = n case s.Cursor == nil: s.Memo.Next = n default: n.Next = s.Cursor s.Memo.Next = n } } } func (m Map) Get(k string) (r interface{}) { if s := Find(m[m.Chain(k)], k); s != nil { r = s.Value } return }
  • 37. go: hash map hashmap vs native map package main import "fmt" func main() { v := make(map[string] interface{}) v["apple"] = "rosy" v["blueberry"] = "sweet" v["cherry"] = "pie" fmt.Printf("%vn", v["apple"]) fmt.Printf("%vn", v["blueberry"]) fmt.Printf("%vn", v["cherry"]) v["cherry"] = "tart" fmt.Printf("%vn", v["cherry"]) fmt.Printf("%vn", v["tart"]) m := make(Map, 1024) m.Set("apple", "rosy") m.Set("blueberry", "sweet") m.Set("cherry", "pie") fmt.Printf("%vn", m.Get("apple")) fmt.Printf("%vn", m.Get("blueberry")) fmt.Printf("%vn", m.Get("cherry")) m.Set("cherry", "tart") fmt.Printf("%vn", m.Get("cherry")) fmt.Printf("%vn", m.Get("tart")) } rosy sweet pie tart (null) rosy sweet pie tart (null)
  • 39. go: slice heap array of pointers reflect unsafe package main import "fmt" import r "reflect" import "unsafe" type Memory []uintptr var _BYTE_SLICE = r.TypeOf([]byte(nil)) var _MEMORY = r.TypeOf(Memory{}) var _MEMORY_BYTES = int(_MEMORY.Elem().Size()) func (m Memory) newHeader() (h r.SliceHeader) { h = *(*r.SliceHeader)(unsafe.Pointer(&m)) h.Len = len(m) * _MEMORY_BYTES h.Cap = cap(m) * _MEMORY_BYTES return } func (m *Memory) Serialise() (b []byte) { h := m.newHeader() b = make([]byte, h.Len) copy(b, *(*[]byte)(unsafe.Pointer(&h))) return } func (m *Memory) Overwrite(i interface{}) { switch i := i.(type) { case Memory: copy(*m, i) case []byte: h := m.newHeader() b := *(*[]byte)(unsafe.Pointer(&h)) copy(b, i) } } func (m *Memory) Bytes() (b []byte) { h := m.newHeader() return *(*[]byte)(unsafe.Pointer(&h)) } func main() { m := make(Memory, 2) b := m.Bytes() s := m.Serialise() fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) m.Overwrite(Memory{3, 5}) fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) s = m.Serialise() m.Overwrite([]byte{8, 7, 6, 5, 4, 3, 2, 1}) fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) }
  • 40. go: slice heap array of pointers reflect unsafe package main import r "reflect" import "unsafe" type Memory []uintptr var _BYTE_SLICE = r.TypeOf([]byte(nil)) var _MEMORY = r.TypeOf(Memory{}) var _MEMORY_BYTES = int(_MEMORY.Elem().Size()) func (m Memory) newHeader() (h r.SliceHeader) { h = *(*r.SliceHeader)(unsafe.Pointer(&m)) h.Len = len(m) * _MEMORY_BYTES h.Cap = cap(m) * _MEMORY_BYTES return } func (m *Memory) Serialise() (b []byte) { h := m.newHeader() b = make([]byte, h.Len) copy(b, *(*[]byte)(unsafe.Pointer(&h))) return } func (m *Memory) Overwrite(i interface{}) { switch i := i.(type) { case Memory: copy(*m, i) case []byte: h := m.newHeader() b := *(*[]byte)(unsafe.Pointer(&h)) copy(b, i) } } func (m *Memory) Bytes() (b []byte) { h := m.newHeader() return *(*[]byte)(unsafe.Pointer(&h)) } func main() { m := make(Memory, 2) b := m.Bytes() s := m.Serialise() fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) m.Overwrite(Memory{3, 5}) fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) s = m.Serialise() m.Overwrite([]byte{8, 7, 6, 5, 4, 3, 2, 1}) fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) }
  • 41. go: slice heap array of pointers reflect unsafe package main import r "reflect" import "unsafe" type Memory []uintptr var _BYTE_SLICE = r.TypeOf([]byte(nil)) var _MEMORY = r.TypeOf(Memory{}) var _MEMORY_BYTES = int(_MEMORY.Elem().Size()) func (m Memory) newHeader() (h r.SliceHeader) { h = *(*r.SliceHeader)(unsafe.Pointer(&m)) h.Len = len(m) * _MEMORY_BYTES h.Cap = cap(m) * _MEMORY_BYTES return } func (m *Memory) Serialise() (b []byte) { h := m.newHeader() b = make([]byte, h.Len) copy(b, *(*[]byte)(unsafe.Pointer(&h))) return } func (m *Memory) Overwrite(i interface{}) { switch i := i.(type) { case Memory: copy(*m, i) case []byte: h := m.newHeader() b := *(*[]byte)(unsafe.Pointer(&h)) copy(b, i) } } func (m *Memory) Bytes() (b []byte) { h := m.newHeader() return *(*[]byte)(unsafe.Pointer(&h)) } func main() { m := make(Memory, 2) b := m.Bytes() s := m.Serialise() fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) m.Overwrite(Memory{3, 5}) fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) s = m.Serialise() m.Overwrite([]byte{8, 7, 6, 5, 4, 3, 2, 1}) fmt.Println("m (cells) =", len(m), "of", cap(m), ":", m) fmt.Println("b (bytes) =", len(b), "of", cap(b), ":", b) fmt.Println("s (bytes) =", len(s), "of", cap(s), ":", s) }
  • 43. c: switch portable slow byte tokens #include <stdio.h> #include <stdlib.h> #include "stack.h" #define READ_OPCODE *PC++ typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes; STACK *S; 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 = %dn, l, r, S->data); break; case EXIT: return; } } } int main() { int program [] = { (int)PUSH, 13, (int)PUSH, 28, (int)ADD, PRINT, EXIT, }; interpret(program); }
  • 44. c: direct call portable pointer to function multi-byte #include <stdio.h> #include <stdlib.h> #include "stack.h" #define READ_OPCODE *PC++ typedef void (*opcode)(); STACK *S; 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 = %dn", l, r, S->data); } void op_exit() { exit(0); } void interpret(opcode *program) { PC = program; while (1) { (READ_OPCODE)(); } } int main() { opcode program [] = { op_push, (opcode)(long)13, op_push, (opcode)(long)28, op_add_and_print, op_exit }; interpret(program); }
  • 45. c: indirect thread gcc/clang specific local jumps indirect loading #include <stdio.h> #include <stdlib.h> #include "stack.h" typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes; void interpret(int *program) { static void *opcodes [] = { &&op_push, &&op_add, &&op_print, &&op_exit }; int l, r; STACK *S; 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 = %dn", l, r, S->data); goto *opcodes[*PC++]; op_exit: return; } int main() { int program [] = { PUSH, 13, PUSH, 28, ADD, PRINT, EXIT }; interpret(program); }
  • 46. c: indirect thread gcc/clang specific local jumps indirect loading #include <stdio.h> #include <stdlib.h> #include "stack.h" #define READ_OPCODE *PC++ #define EXECUTE_OPCODE goto *opcodes[READ_OPCODE]; #define PRIMITIVE(name, body) name: body; EXECUTE_OPCODE typedef enum { PUSH = 0, ADD, PRINT, 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; EXECUTE_OPCODE; PRIMITIVE(op_push, S = push(S, READ_OPCODE) ) PRIMITIVE(op_print, printf("%d + %d = %dn", l, r, S->data) ) PRIMITIVE(op_add, S = pop(S, &l); S = pop(S, &r); S = push(S, l + r) ) PRIMITIVE(op_exit, return ) } int main() { int program [] = { PUSH, 13, PUSH, 28, ADD, PRINT, EXIT }; interpret(program); }
  • 47. c: direct thread void **compile(int *PC, int words, void *despatch_table[]) { static void *compiler [] = { &&c_push, &&c_add, &&c_print, &&c_exit }; if (words < 1) return NULL; void **program = malloc(sizeof(void *) * words); void **cp = program; goto *compiler[*PC++]; c_number: *cp++ = (void *)(long)*PC++; words—; if (words == 0) return program; goto *compiler[*PC++]; c_push: *cp++ = despatch_table[PUSH]; *cp++ = (void *)(long)*PC++; words—; goto c_number; c_add: *cp++ = despatch_table[ADD]; words--; if (words == 0) return program; goto *compiler[*PC++]; c_print: *cp++ = despatch_table[PRINT]; words--; if (words == 0) return program; goto *compiler[*PC++]; c_exit: *cp++ = despatch_table[EXIT]; words--; if (words == 0) return program; goto *compiler[*PC++]; } 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 = %dn", l, r, S->data); goto **program++; op_exit: return; }
  • 48. c: direct thread #include <stdio.h> #include <stdlib.h> #include "stack.h" #define COMPILE_NEXT_OPCODE goto *compiler[*PC++]; #define COMPILE_NUMBER *cp++ = (void *)(long)*PC++; words--; #define PRIMITIVE_ADDRESS(op) *cp++ = despatch_table[op]; #define COMPILE_PRIMITIVE(name, body) c_##name: PRIMITIVE_ADDRESS(name) body; words--; if (words == 0) return program; COMPILE_NEXT_OPCODE void **compile(int *PC, int words, void *despatch_table[]) { static void *compiler [] = { &&c_push, &&c_add, &&c_print, &&c_exit }; if (words < 1) return NULL; void **program = malloc(sizeof(void *) * words); void **cp = program; COMPILE_NEXT_OPCODE COMPILE_PRIMITIVE(PUSH, COMPILE_NUMBER) COMPILE_PRIMITIVE(ADD, ;) COMPILE_PRIMITIVE(PRINT, ;) COMPILE_PRIMITIVE(EXIT, ;) } #define NEXT_OPCODE goto **program++; #define PRIMITIVE(name, body) op_##name: body; NEXT_OPCODE 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); NEXT_OPCODE PRIMITIVE(push, S = push(S, (int)(long)*program++)) PRIMITIVE(add, S = pop(S, &l); S = pop(S, &r); S = push(S, l + r); ) PRIMITIVE(print, printf("%d + %d = %dn", l, r, S->data)) PRIMITIVE(exit, return) } int main() { int program[] = { PUSH, 13, PUSH, 28, ADD, PRINT, EXIT }; interpret(program, 7); }
  • 49. go: switch constants package main import "fmt" type stack struct { int *stack } func (s *stack) Push(v int) *stack { return &stack{v, s} } func (s *stack) Pop() (int, *stack) { return s.int, s.stack } type OPCODE int const ( PUSH = OPCODE(iota) ADD PRINT EXIT ) func main() { interpret([]interface{}{ PUSH, 13, PUSH, 28, ADD, PRINT, EXIT, }) } func interpret(p []interface{}) { var l, r int S := 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 = %vn", l, r, S.int) case EXIT: return } } else { return } } }
  • 50. go: direct call function pointers package main import "fmt" import "os" type stack struct { int *stack } func (s *stack) Push(v int) *stack { return &stack{v, s} } func (s *stack) Pop() (int, *stack) { return s.int, s.stack } func main() { p := new(Interpreter) p.Load( p.Push, 13, p.Push, 28, p.Add, p.Print, p.Exit, ) p.Run() } type Interpreter struct { S *stack l, r, PC int m []interface{} } func (i *Interpreter) read_program() (r interface{}) { return i.m[i.PC] } func (i *Interpreter) Load(p ...interface{}) { i.m = p } func (i *Interpreter) Run() { for { i.read_program().(func())() i.PC++ } } func (i *Interpreter) Push() { i.PC++ i.S = i.S.Push(i.read_program().(int)) } 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 = %vn", i.l, i.r, i.S.int) } func (i *Interpreter) Exit() { os.Exit(0) }
  • 51. go: direct call function literals package main import "fmt" import "os" type stack struct { int *stack } func (s *stack) Push(v int) *stack { return &stack{v, s} } func (s *stack) Pop() (int, *stack) { return s.int, s.stack } type Primitive func(*Interpreter) type Interpreter struct { S *stack l, r, PC int m []Primitive } func (i *Interpreter) read_program() Primitive { return i.m[i.PC] } func (i *Interpreter) Run() { for { i.read_program()(i) i.PC++ } } func main() { p := &Interpreter{ m: []Primitive{ func(i *Interpreter) { i.S = i.S.Push(13) }, func(i *Interpreter) { i.S = i.S.Push(28) }, func(i *Interpreter) { 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) { fmt.Printf("%v + %v = %vn", i.l, i.r, i.S.int) }, func (i *Interpreter) { os.Exit(0) }, }, } p.Run() }
  • 52. go: direct call closures package main import "fmt" import "os" type stack struct { int *stack } func (s *stack) Push(v int) *stack { return &stack{v, s} } func (s *stack) Pop() (int, *stack) { return s.int, s.stack } type Primitive func(*Interpreter) type Interpreter struct { S *stack l, r, PC int m []Primitive } func (i *Interpreter) read_program() Primitive { return i.m[i.PC] } func (i *Interpreter) Run() { for { i.read_program() i.PC++ } } func main() { var p *Interpreter p := &Interpreter{ m: []Primitive{ func() { p.S = p.S.Push(13) }, func() { p.S = p.S.Push(28) }, func() { p.l, p.S = p.S.Pop() p.r, p.S = p.S.Pop() p.S = p.S.Push(p.l + p.r) }, func() { fmt.Printf("%v + %v = %vn", p.l, p.r, p.S.int) }, func () { os.Exit(0) }, }, } p.Run() }
  • 53. go: direct call closures compilation package main import "fmt" import "os" type stack struct { int *stack } func (s *stack) Push(v int) *stack { return &stack{v, s} } func (s *stack) Pop() (int, *stack) { return s.int, s.stack } type Primitive func() type Label string type labels []Label type VM struct { PC int m []interface{} labels } func (v *VM) Load(program ...interface{}) { v.labels = make(labels) v.PC = -1 for _, token := range program { v.assemble(token) } } func (v *VM) assemble(token interface{}) { switch t := token.(type) { case Label: if i, ok := v.labels[t]; ok { v.m = append(v.m, i) } else { v.labels[t] = v.PC } default: v.m = append(v.m, token) v.PC++ } } func (v *VM) Run() { v.PC = -1 for { v.PC++ v.read_program().(Primitive)() } } func (v *VM) read_program() interface{} { return v.m[v.PC] } type Interpreter struct { VM S *stack l, r int }
  • 54. go: direct call closures compilation func main() { p := new(Interpreter) p.Load( Label("start"), Primitive(func() { p.S = p.S.Push(13) }), Primitive(func() { p.S = p.S.Push(28) }), Primitive(func() { p.l, p.S = p.S.Pop() p.r, p.S = p.S.Pop() p.S = p.S.Push(p.l + p.r) }), Primitive(func() { fmt.Printf("%v + %v = %vn", p.l, p.r, p.S.int) }), Primitive(func() { os.Exit(0) }), Label("end"), ) p.Run() }
  • 56. go: vm harness closures compilation package main import "fmt" import "os" type stack struct { int *stack } func (s *stack) Push(v int) *stack { return &stack{v, s} } func (s *stack) Pop() (int, *stack) { return s.int, s.stack } type Primitive func() type Label string type labels map[Label]int type VM struct { PC int m []interface{} labels } func (v *VM) Load(program ...interface{}) { v.labels = make(labels) v.PC = -1 for _, token := range program { v.assemble(token) } } func (v *VM) assemble(token interface{}) { switch t := token.(type) { case Label: if i, ok := v.labels[t]; ok { v.m = append(v.m, i) } else { v.labels[t] = v.PC } default: v.m = append(v.m, token) v.PC++ } } func (v *VM) Run() { v.PC = -1 for { v.PC++ v.read_program().(Primitive)() } } func (v *VM) read_program() interface{} { return v.m[v.PC] }
  • 57. stack machine zero operands type StackMachine struct { *stack VM } func (s *StackMachine) Push() { s.PC++ s.S = s.S.Push(s.read_program().(int)) } func (s *StackMachine) Add() { s.l, s.S = s.S.Pop() s.r, s.S = s.S.Pop() s.S = s.S.Push(s.l + s.r) } func (s *StackMachine) JumpIfNotZero() { s.PC++ if s.int != 0 { s.PC = s.m[s.PC].(int) } } func main() { s := new(StackMachine) print_state := Primitive(func() { fmt.Printf("pc[%v]: %v, TOS: %vn", s.PC, s.m[s.PC], s.int, ) }) s.Load( Primitive(s.Push), 13, Label("dec"), Primitive(func() { s.stack = s.stack.Push(-1) }), Primitive(s.Add), print_state, Primitive(s.JumpIfNotZero), Label("dec"), print_state, Primitive(func() { os.Exit(0) }), ).Run() }
  • 58. the accumulator single register single operand type AccMachine struct { int VM } func (a *AccMachine) Clear() { a.int = 0 } func (a *AccMachine) LoadValue() { a.PC++ a.int = a.read_program().(int) } func (a *AccMachine) Add() { a.PC++ a.int += a.read_program().(int) } func (a *AccMachine) JumpIfNotZero() { a.PC++ if a.int != 0 { a.PC = a.m[a.PC].(int) } } func main() { a := new(AccMachine) print_state := Primitive(func() { fmt.Printf("pc[%v]: %v, Acc: %vn", a.PC, a.m[a.PC], a.int, ) }) a.Load( Primitive(a.Clear), Primitive(a.LoadValue), 13, Label("dec"), Primitive(a.Add), -1, print_state, Primitive(a.JumpIfNotZero), Label("dec"), print_state, Primitive(func() { os.Exit(0) }), ).Run() }
  • 59. register machine multi-register multi-operand type RegMachine struct { R []int VM } func (r *RegMachine) Clear() { r.R = make([]int, 2, 2) } func (r *RegMachine) read_value() int { r.PC++ return r.read_program().(int) } func (r *RegMachine) LoadValue() { r.R[r.read_value()] = r.read_value() } func (r *RegMachine) Add() { i := r.read_value() j := r.read_value() r.R[i] += r.R[j] } func (r *RegMachine) JumpIfNotZero() { if r.R[r.read_value()] != 0 { r.PC = r.read_value() } else { r.PC++ } } func main() { r := new(RegMachine) print_state := Primitive(func() { fmt.Printf("pc[%v]: %v, Acc: %vn", r.PC, r.m[r.PC], r.R, ) }) r.Load( Primitive(r.Clear), Primitive(r.LoadValue), 0, 13, Primitive(r.LoadValue), 1, -1, Label("dec"), Primitive(r.Add), 0, 1, print_state, Primitive(r.JumpIfNotZero), 0, Label("dec"), print_state, Primitive(func() { os.Exit(0) }), ).Run() }
  • 61. Go & cgo: integrating existing C code with Go Andreas Krennmair <ak@synflood.at> Golang User Group Berlin Twitter: @der_ak cgo inline c
  • 62. Feb 5, 2013 at 10:28PM Caleb DoxseyCaleb Doxsey // BlogBlog // Go & AssemblyGo & Assembly Go & AssemblyGo & Assembly Caleb Doxsey One of my favorite parts about Go is its unwavering focus on utility. Sometimes we place so much emphasis on language design that we forget all the other things programming involves. For example: Go's compiler is fast Go comes with a robust standard library Go works on a multitude of platforms Go comes with a complete set of documentation available from the command line / a local web server / the internet All Go code is statically compiled so deployment is trivial The entirety of the Go source code is available for perusal in an easy format online (like this) Go has a well defined (and documented) grammar for parsing. (unlike C++ or Ruby) Go comes with a package management tool. go get X (for example go get code.google.com/p/go.net/websocket ) assembly
  • 64. This repository Pull requests Issues Gist No description or website provided. — Edit 00 memory experiments with Fiddle::Pointer exploring use of C-style memory mana… 5 days ago 01 stacks stacks in Go 5 days ago 02 hashes roll-your-own hash maps in Go 5 days ago 03 dispatcher Go versions of dispatch loops 5 days ago 04 architecture implementations of popular VM architectural styles 5 days ago LICENSE Initial commit 5 days ago README.md added links for slides and video of talks based on this code 5 days ago Search feyeleanor / vm-fragments Code Issues 0 Pull requests 0 Projects 0 Wiki Pulse Graphs Settings 13 commits 1 branch 0 releases 1 contributor MIT Clone or downloadClone or downloadCreate new file Upload files Find filemasterBranch: New pull request Latest commit 9ee7fe0 5 days agofeyeleanor implementations of popular VM architectural styles README.md vm-fragments A collection of code fragments for talks I've given around implementing simple virtual machine internals in C, Go and Ruby. The slides for these talks are available at: 1 01Unwatch Star Fork source code
  • 66. implementing virtual machines in Go & C Eleanor McHugh @feyeleanor slideshare://feyeleanor github://feyeleanor