Implementing Software Machines in C and Go

85
implementing software machines in Go & C Eleanor McHugh @feyeleanor

Transcript of Implementing Software Machines in C and Go

Page 1: Implementing Software Machines in C and Go

implementing software

machines in

Go & CEleanor McHugh

@feyeleanor

Page 2: Implementing Software Machines in C and Go

this is a talk about VMssystem virtualisation

hardware emulation

abstract virtual machines

Page 3: Implementing Software Machines in C and Go

system virtualisation

Page 4: Implementing Software Machines in C and Go

this is a talk about VMssystem virtualisation

hardware emulation

abstract virtual machines

Page 5: Implementing Software Machines in C and Go

hardware emulation

Page 6: Implementing Software Machines in C and Go

this is a talk about VMssystem virtualisation

? hardware emulation ?

abstract virtual machines

Page 7: Implementing Software Machines in C and Go

program execution

Page 8: Implementing Software Machines in C and Go

this is a talk about VMssystem virtualisation

? hardware emulation ?

abstract virtual machines

Page 9: Implementing Software Machines in C and Go

inspired by hardwarediscrete components

processors

storage

communications

Page 10: Implementing Software Machines in C and Go

software machinestimely

stateful

scriptable

Page 11: Implementing Software Machines in C and Go
Page 12: Implementing Software Machines in C and Go

memorystoring data & instructions

addressing

protection

Page 13: Implementing Software Machines in C and Go
Page 14: Implementing Software Machines in C and Go

go: heapword-aligned

contiguous

byte-addressable

Page 15: Implementing Software Machines in C and Go

package memoryimport 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_BYTESh.Cap = cap(m) * _MEMORY_BYTESreturn

}

func (m *Memory) Bytes() (b []byte) {h := m.newHeader()return *(*[]byte)(unsafe.Pointer(&h))

}

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)

}}

Page 16: Implementing Software Machines in C and Go

package memoryimport 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_BYTESh.Cap = cap(m) * _MEMORY_BYTESreturn

}

func (m *Memory) Bytes() (b []byte) {h := m.newHeader()return *(*[]byte)(unsafe.Pointer(&h))

}

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)

}}

Page 17: Implementing Software Machines in C and Go

package memoryimport 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_BYTESh.Cap = cap(m) * _MEMORY_BYTESreturn

}

func (m *Memory) Bytes() (b []byte) {h := m.newHeader()return *(*[]byte)(unsafe.Pointer(&h))

}

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)

}}

Page 18: Implementing Software Machines in C and Go

package mainimport "fmt"

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 run 01.goAllegra:00 memory eleanor$ ./01m (cells) = 2 of 2 : [0 0]b (bytes) = 16 of 16 : [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]s (bytes) = 16 of 16 : [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]m (cells) = 2 of 2 : [3 5]b (bytes) = 16 of 16 : [3 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0]s (bytes) = 16 of 16 : [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]m (cells) = 2 of 2 : [72623859790382856 5]b (bytes) = 16 of 16 : [8 7 6 5 4 3 2 1 5 0 0 0 0 0 0 0]s (bytes) = 16 of 16 : [3 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0]

Page 19: Implementing Software Machines in C and Go
Page 20: Implementing Software Machines in C and Go
Page 21: Implementing Software Machines in C and Go

c: array stackuses fixed amount of memory

[grow | shrink]ing stack requires explicit realloc()

stack pointer is an offset into the array

Page 22: Implementing Software Machines in C and Go

#include <stdio.h>#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 size;

};

STACK *NewStack() {STACK *s;s = malloc(sizeof(STACK));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 depth(STACK *s) {return s->size;

}

int main() {int l, r;STACK *s = NewStack();push(s, 1);push(s, 3);printf("depth = %d\n", depth(s));pop(s, &l);pop(s, &r);printf("%d + %d = %d\n", l, r, l + r);printf("depth = %d\n", depth(s));

}

c: array stack

$ cc 01.c$ ./a.out depth = 23 + 1 = 4depth = 0

Page 23: Implementing Software Machines in C and Go

go: slice stackmaps directly to [] slice types

[grow | shrink]ing stack automatic via append()

doesn’t require initialization

Page 24: Implementing Software Machines in C and Go

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 (s *stack) Depth() int {return len(s.data)

}

func main() {s := new(stack)s.Push(1)s.Push(3)fmt.Printf("depth = %d\n", s.Depth())l, _ := s.Pop()r, _ := s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)fmt.Printf("depth = %d\n", s.Depth())

}

go: slice stack

$ go run 01a.godepth = 23 + 1 = 4depth = 0

Page 25: Implementing Software Machines in C and Go

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 (s *stack) Depth() int {return len(s.data)

}

func main() {s := new(stack)s.Push(1)s.Push(3)fmt.Printf("depth = %d\n", s.Depth())l, _ := s.Pop()r, _ := s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)fmt.Printf("depth = %d\n", s.Depth())

}

go: slice stack

$ go run 01a.godepth = 23 + 1 = 4depth = 0

Page 26: Implementing Software Machines in C and Go

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 (s *stack) Depth() int {return len(s.data)

}

func main() {s := new(stack)s.Push(1)s.Push(3)fmt.Printf("depth = %d\n", s.Depth())l, _ := s.Pop()r, _ := s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)fmt.Printf("depth = %d\n", s.Depth())

}

go: slice stack

$ go run 01a.godepth = 23 + 1 = 4depth = 0

Page 27: Implementing Software Machines in C and Go

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) - 1r = (*s)[sp]*s = (*s)[:sp]return

}

func (s stack) Depth() int {return len(s)

}

func main() {s := new(stack)s.Push(1)s.Push(3)fmt.Printf("depth = %d\n", s.Depth())l := s.Pop()r := s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)fmt.Printf("depth = %d\n", s.Depth())

}

go: slice stack

$ go run 01b.godepth = 23 + 1 = 4depth = 0

Page 28: Implementing Software Machines in C and Go
Page 29: Implementing Software Machines in C and Go

c: cactus stackdoesn’t require initialization

automatic stack growth on push()

potentially leaks memory on pop()

Page 30: Implementing Software Machines in C and Go

#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 main() {int l, r;STACK *s = push(NULL, 1);s = push(s, 3);printf("depth = %d\n", depth(s));

STACK *t = pop(pop(s, &r), &l);printf("%d + %d = %d\n", l, r, l + r);printf("depth pre-gc = %d\n", depth(s));

gc(&s, 2);printf("depth post-gc = %d\n", depth(s));

}

c: functional cactus stack

$ cc 02.c$ ./a.out depth = 21 + 3 = 4depth pre-gc = 2depth post-gc = 0

Page 31: Implementing Software Machines in C and Go

#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 main() {int l, r;STACK *s = push(NULL, 1);s = push(s, 3);printf("depth = %d\n", depth(s));

STACK *t = pop(pop(s, &r), &l);printf("%d + %d = %d\n", l, r, l + r);printf("depth pre-gc = %d\n", depth(s));

gc(&s, 2);printf("depth post-gc = %d\n", depth(s));

}

c: functional cactus stack

$ cc 02.c$ ./a.out depth = 21 + 3 = 4depth pre-gc = 2depth post-gc = 0

Page 32: Implementing Software Machines in C and Go

#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;

}

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 = %d\n", 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);

}

c: cactus stack in action

$ cc 03.c$ ./a.out 5 items: sum = 253 items: sum = 255 items: sum = 39

Page 33: Implementing Software Machines in C and Go

go: cactus stackdoesn’t need initialization

automatic stack growth on push()

garbage collection removes need for free()

Page 34: Implementing Software Machines in C and Go

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) Depth() (r int) {for t := s; t != nil; t = t.tail {

r++}return

}

func main() {var l, r intvar s *stack

s = s.Push(1).Push(3)fmt.Printf("depth = %d\n", s.Depth())l, s = s.Pop()r, s = s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)fmt.Printf("depth = %d\n", s.Depth())

}

go: functional cactus stack

$ go run 02a.godepth = 23 + 1 = 4depth = 0

Page 35: Implementing Software Machines in C and Go

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) Depth() (r int) {for t := s.tail; t != nil; t = t.tail {

r++}return

}

func main() {var l, r intvar s stack

s = s.Push(1).Push(3)fmt.Printf("depth = %d\n", s.Depth())l, s = s.Pop()r, s = s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)fmt.Printf("depth = %d\n", s.Depth())

}

go: functional cactus stack

$ go run 02b.godepth = 23 + 1 = 4depth = 0

Page 36: Implementing Software Machines in C and Go

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) Depth() (r int) {for t := s.tail; t != nil; t = t.tail {

r++}return

}

func (s *stack) append(n int) {t := sfor ; t.tail != nil; t = t.tail {}*t = stack{data: n, tail: new(stack)}

}

func main() {var l, r intvar s stack

s = s.Push(1).Push(3)fmt.Printf("depth = %d\n", s.Depth())s.append(20)fmt.Printf("depth = %d\n", s.Depth())

l, s = s.Pop()r, s = s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)

l, s = s.Pop()fmt.Printf("l = %d\n", l)fmt.Printf("depth = %d\n", s.Depth())

s.append(5)l, s = s.Pop()fmt.Printf("l = %d\n", l)fmt.Printf("depth = %d\n", s.Depth())

}

go: functional cactus stack

$ go run 02c.godepth = 2depth = 33 + 1 = 4l = 20depth = 0l = 5depth = 0

Page 37: Implementing Software Machines in C and Go

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) Depth() (r int) {for t := s.tail; t != nil; t = t.tail {

r++}return

}

func (s stack) Sum() (r int) {for t := &s; t.tail != nil; t = t.tail {

r += t.data}return

}

func (s *stack) PrintSum() {fmt.Printf("%d items: sum = %d\n", s.Depth(), s.Sum())

}

func main() {s1 := 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()

}

go: cactus stack in action

$ go run 03a.go 5 items: sum = 253 items: sum = 255 items: sum = 39

Page 38: Implementing Software Machines in C and Go

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) Depth() (r int) {for t := s.tail; t != nil; t = t.tail {

r++}return

}

func (s stack) Sum() (r int) {for t, n := s, 0; t.tail != nil; r += n {

n, t = t.Pop()}return

}

func (s *stack) PrintSum() {fmt.Printf("%d items: sum = %d\n", s.Depth(), s.Sum())

}

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()

}

go: cactus stack in action

$ go run 03b.go 5 items: sum = 253 items: sum = 255 items: sum = 39

Page 39: Implementing Software Machines in C and Go

c: map

Page 40: Implementing Software Machines in C and Go

#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: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 41: Implementing Software Machines in C and Go

#include <stdlib.h>#include "assoc_array.c"

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: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 42: Implementing Software Machines in C and Go

#include <stdio.h>#include <stdlib.h>#include <limits.h>#include <string.h>#include "search.c"

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);

}

int main( int argc, char **argv ) {map_t *m = map_new(1024);map_set(m, "apple", "rosy");printf("%s\n", map_get(m, "apple"));map_set(m, "blueberry", "sweet");printf("%s\n", map_get(m, "blueberry"));map_set(m, "cherry", "pie");printf("%s\n", map_get(m, "cherry"));map_set(m, "cherry", "tart");printf("%s\n", map_get(m, "cherry"));printf("%s\n", map_get(m, "tart"));return 0;

}

c: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 43: Implementing Software Machines in C and Go

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;

}

c: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 44: Implementing Software Machines in C and Go

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;

}

c: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 45: Implementing Software Machines in C and Go

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;

}

c: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 46: Implementing Software Machines in C and Go

go: map

Page 47: Implementing Software Machines in C and Go

package assoc_array

type AssocArray struct {Key stringValue interface{}Next *AssocArray

};

func (a *AssocArray) GetIf(k string) (r interface{}) {if a != nil && a.Key == k {

r = a.Value}return

}

go: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 48: Implementing Software Machines in C and Go

package searchimport . "assoc_array"

type Search struct {Term stringValue 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

}

func Find(a *AssocArray, k string) (s *Search) {s = &Search{ Term: k, Cursor: a }for s.Step(); s.Searching(); s.Step() {

s.Memo = s.Cursors.Cursor = s.Cursor.Next

}return

} go: map

$ cc 04.c$ ./a.outrosysweetpietart(null)

Page 49: Implementing Software Machines in C and Go

package mainimport "fmt"import . "assoc_array"import "search"

type Map []*AssocArray

func (m Map) Set(k string, v interface{}) {c := m.Chain(k)a := m[c]s := search.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.Cursorm[c] = n

case s.Cursor == nil:s.Memo.Next = n

default:n.Next = s.Cursors.Memo.Next = n

}}

}

func (m Map) Chain(k string) int {var c uintfor i := len(k) - 1; i > 0; i-- {

c = c << 8c += (uint)(k[i])

}return int(c) % len(m)

}

func (m Map) Get(k string) (r interface{}) {if s := search.Find(m[m.Chain(k)], k); s != nil {

r = s.Value}return

}

func main() {m := make(Map, 1024)m.Set("apple", "rosy")fmt.Printf("%v\n", m.Get("apple"))m.Set("blueberry", "sweet")fmt.Printf("%v\n", m.Get("blueberry"))m.Set("cherry", "pie")fmt.Printf("%v\n", m.Get("cherry"))m.Set("cherry", "tart")fmt.Printf("%v\n", m.Get("cherry"))fmt.Printf("%v\n", m.Get("tart"))

}

go: map

$ go run 04.gorosysweetpietart<nil>

Page 50: Implementing Software Machines in C and Go

package mainimport "fmt"

func main() {m := make(map[string] interface{})m["apple"] = "rosy"fmt.Printf("%v\n", m["apple"])

m["blueberry"] = "sweet"fmt.Printf("%v\n", m["blueberry"])

m["cherry"] = "pie"fmt.Printf("%v\n", m["cherry"])

m["cherry"] = "tart"fmt.Printf("%v\n", m["cherry"])

fmt.Printf("%v\n", m["tart"])}

go: map

$ go run 05.gorosysweetpietart<nil>

Page 51: Implementing Software Machines in C and Go

dispatch loopsfetch

decode

execute

Page 52: Implementing Software Machines in C and Go

dispatch loopsread next instruction via a program counter

determine the operation to perform

execute the operation and adjust machine state

Page 53: Implementing Software Machines in C and Go

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

Page 54: Implementing Software Machines in C and Go

#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

Page 55: Implementing Software Machines in C and Go

#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

Page 56: Implementing Software Machines in C and Go

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

Page 57: Implementing Software Machines in C and Go

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

Page 58: Implementing Software Machines in C and Go

#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

Page 59: Implementing Software Machines in C and Go

#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

Page 60: Implementing Software Machines in C and Go

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

Page 61: Implementing Software Machines in C and Go

indirect threadinginstructions stored sequentially in memory

each represented by a local jump label

gcc/clang specific C extension

instructions indirectly load successor

Page 62: Implementing Software Machines in C and Go

#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

Page 63: Implementing Software Machines in C and Go

#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

Page 64: Implementing Software Machines in C and Go

direct threadinginstructions stored sequentially in memory

each represented by a local jump label

gcc/clang specific C extension

instructions directly load successors

Page 65: Implementing Software Machines in C and Go

#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)

Page 66: Implementing Software Machines in C and Go

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)

Page 67: Implementing Software Machines in C and Go

#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)

Page 68: Implementing Software Machines in C and Go

#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)

Page 69: Implementing Software Machines in C and Go

timingclock pulse

synchronisation

Page 70: Implementing Software Machines in C and Go

package clockimport "syscall"

type Clock struct {Period int64Count chan int64Control chan boolactive bool

}

func (c *Clock) Start() {if !c.active {

go func() {c.active = truefor i := int64(0); ; i++ {

select {case status := <- c.Control:

c.active = statusdefault:

if c.active {c.Count <- i

}syscall.Sleep(c.Period)

}}

}()}

}

package mainimport . “clock”

func main() {c := Clock{1000, make(chan int64), make(chan bool), false}c.Start()

for i := 0; i < 3; i++ {println("pulse value", <-c.Count, "from clock")

}

println("disabling clock")c.Control <- falsesyscall.Sleep(1000000)println("restarting clock")c.Control <- trueprintln("pulse value", <-c.Count, "from clock")

}

produces:pulse value 0 from clockpulse value 1 from clockpulse value 2 from clockdisabling clockrestarting clockpulse value 106 from clock

Page 71: Implementing Software Machines in C and Go

instructionsoperations

operands

execution

Page 72: Implementing Software Machines in C and Go

package instructions

type Op func(o []int)

type Executable interface {Opcode() intOperands() []intExecute(Op)

}

const INVALID_OPCODE = -1

type Instr []intfunc (i Instr) Opcode() int {

if len(i) == 0 {return INVALID_OPCODE

}return i[0]

}

func (i Instr) Operands() (r []int) {if len(i) > 1 {

r = i[1:]}return

}

func (i Instr) Execute(op Operation) {op(i.Operands())

}

Page 73: Implementing Software Machines in C and Go

package assemblerimport "fmt"import . "instructions"

type Asm struct {opcodes map[string] intnames map[int] string

}

func NewAsm(names... string) (a Asm) {a = Asm{

make(map[string] int),make(map[int] string),

}a.Define(names...)return

}

func (a Asm) Asm(n string, p... int) (i Instr) {if opcode, ok := a.opcodes[name]; ok {

i = make(Instruction, len(params) + 1)i[0] = opcodecopy(i[1:], params)

}return

}

func (a Asm) Define(n... string) {for _, name := range names {

a.opcodes[name] = len(a.names)a.names[len(a.names)] = name

}}

func (a Asm) Disasm(e Executable) (s string) {if name, ok := a.names[e.Opcode()]; ok {

s = nameif params := e.Operands(); len(params) > 0 {

s = fmt.Sprintf("%v\t%v", s, params[0])for _, v := range params[1:] {

s = fmt.Sprintf("%v, %v", s, v)}

}} else {

s = "INVALID"}return

}

Page 74: Implementing Software Machines in C and Go

package mainimport "fmt"import "assembler"

type Program []Executablefunc (p Program) Rip(a Asm) {

for _, v := range p {fmt.Println(a.Rip(v))

}}

func main() {a := NewAsm("noop", "load", "store")p := Program{

a.Asm("noop"),a.Asm("load", 1),a.Asm("store", 1, 2),a.Asm("invalid", 3, 4, 5),

}p.Disasm(a)for _, v := range p {

if len(v.Operands()) == 2 {v.Execute(func(o []int) {

o[0] += o[1]})println("op =“,

v.Opcode(),"result =“,v.Operands()[0],

)}

}}

produces:noopload 1store 1, 2unknownop = 2 result = 3

Page 75: Implementing Software Machines in C and Go

CISCsemantically rich instructions

complex memory addressing modes

compact binary code

Page 76: Implementing Software Machines in C and Go

RISCseparate IO and data processing

register-to-register instructions

load/store memory access

Page 77: Implementing Software Machines in C and Go

VLIWmultiple operations per instruction

compiler statically determines parallelism

simplifies control logic

Page 78: Implementing Software Machines in C and Go

processor coresends & receives signals on external buses

maintains internal computation state

executes sequences of instructions

Page 79: Implementing Software Machines in C and Go

accumulator machine1-operand instructions

data from memory combined with accumulator

result stored in accumulator

Page 80: Implementing Software Machines in C and Go

stack machine0-operand instructions

data popped from stack

results pushed on stack

Page 81: Implementing Software Machines in C and Go

register machinemulti-operand instructions

data read from memory into registers

operator combines memory and store ops

Page 82: Implementing Software Machines in C and Go

transport triggeringregister machine architecture

exposes internal buses as components

operations are side-effects of internal writes

Page 83: Implementing Software Machines in C and Go

vector machinemulti-operand instruction

data vectors read from memory into registers

operator combines registers and store ops

Page 84: Implementing Software Machines in C and Go

superscalarmultiple execution units

processor caching

out-of-order execution

Page 85: Implementing Software Machines in C and Go