implementing software machines in c and go
TRANSCRIPT
implementing software
machines in
Go & CEleanor McHugh
@feyeleanor
this is a talk about VMssystem virtualisation
hardware emulation
abstract virtual machines
system virtualisation
this is a talk about VMssystem virtualisation
hardware emulation
abstract virtual machines
hardware emulation
this is a talk about VMssystem virtualisation
? hardware emulation ?
abstract virtual machines
program execution
this is a talk about VMssystem virtualisation
? hardware emulation ?
abstract virtual machines
inspired by hardwarediscrete components
processors
storage
communications
software machinestimely
stateful
scriptable
memorystoring data & instructions
addressing
protection
go: heapword-aligned
contiguous
byte-addressable
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)
}}
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)
}}
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)
}}
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]
c: array stackuses fixed amount of memory
[grow | shrink]ing stack requires explicit realloc()
stack pointer is an offset into the array
#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
go: slice stackmaps directly to [] slice types
[grow | shrink]ing stack automatic via append()
doesn’t require initialization
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
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
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
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
c: cactus stackdoesn’t require initialization
automatic stack growth on push()
potentially leaks memory on pop()
#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
#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
#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
go: cactus stackdoesn’t need initialization
automatic stack growth on push()
garbage collection removes need for free()
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
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
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
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
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
c: map
#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)
#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)
#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)
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)
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)
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)
go: map
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)
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)
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>
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>
dispatch loopsfetch
decode
execute
dispatch loopsread next instruction via a program counter
determine the operation to perform
execute the operation and adjust machine state
switch interpreterinstructions stored sequentially in memory
each represented by a token or opcode
available in all implementation languages
tokens can be compact - often single bytes
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
void interpret(int *PC) {int l, r;while (1) {
switch(*PC++) {case PUSH:
S = push(S, *PC++);break;
case ADD:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);break;
case PRINT:printf(“%d + %d = %d\n, l, r, S->data);break;
case EXIT:return;
}}
}
int main() {int program [] = {
(int)PUSH, 13,(int)PUSH, 28,(int)ADD,PRINT,EXIT,
};interpret(program);
}
c: switch interpreter
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
#define READ_OPCODE *PC++
void interpret(int *PC) {int l, r;while (1) {
switch(READ_OPCODE) {case PUSH:
S = push(S, READ_OPCODE);break;
case ADD:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);break;
case PRINT:printf(“%d + %d = %d\n, l, r, S->data);break;
case EXIT:return;
}}
}
int main() {int program [] = {
(int)PUSH, 13,(int)PUSH, 28,(int)ADD,PRINT,EXIT,
};interpret(program);
}
c: switch interpreter
package main
import "fmt"
func main() {var program = []interface{}{
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT,
}interpret(program)
}
type stack struct {data inttail *stack
}
func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return
}
func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail
}
type OPCODE int
const (PUSH = OPCODE(iota)ADDPRINTEXIT
)
func interpret(p []interface{}) {var l, r intS := new(stack)
for PC := 0; ; PC++ {if op, ok := p[PC].(OPCODE); ok {
switch op {case PUSH:
PC++S = S.Push(p[PC].(int))
case ADD:l, S, = S.Pop()r, S = S.Pop()S = S.Push(l + r)
case PRINT:fmt.Printf("%v + %v = %v\n", l, r, S.data)
case EXIT:return
}} else {
return}
}}
go: switch interpreter
direct call threadinginstructions stored sequentially in memory
each represented by a pointer to a function
not available in all languages
instructions each require a machine word
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef void (*opcode)();
STACK *S;opcode *PC;
void op_push() {S = push(S, (int)(long)(*PC++));
}
void op_add_and_print() {int l, r;S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);printf("%d + %d = %d\n", l, r, S->data);
}
void op_exit() {exit(0);
}
int main() {opcode program [] = {
op_push, (opcode)(long)13,op_push, (opcode)(long)28,op_add_and_print,op_exit
};PC = program;while (1) {
(*PC++)();}
}
c: direct call-threaded interpreter
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef void (*opcode)();
STACK *S;opcode *PC;
#define READ_OPCODE *PC++
void op_push() {S = push(S, (int)(long)(READ_OPCODE));
}
void op_add_and_print() {int l, r;S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);printf("%d + %d = %d\n", l, r, S->data);
}
void op_exit() {exit(0);
}
int main() {opcode program [] = {
op_push, (opcode)(long)13,op_push, (opcode)(long)28,op_add_and_print,op_exit
};PC = program;while (1) {
(READ_OPCODE)();}
}
c: direct call-threaded interpreter
package main
import "fmt"import "os"
func main() {p := new(Interpreter)p.m = []interface{}{
p.Push, 13,p.Push, 28,p.Add,p.Print,p.Exit,
}p.Run()
}
type stack struct {data inttail *stack
}
func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return
}
func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail
}
type Interpreter struct {S *stackl, r, PC intm []interface{}
}
func (i *Interpreter) opcode() func() {return i.m[i.PC].(func())
}
func (i *Interpreter) operand() int {return i.m[i.PC].(int)
}
func (i *Interpreter) Run() {for {
i.opcode()()i.PC++
}}
func (i *Interpreter) Push() {i.PC++i.S = i.S.Push(i.operand())
}
func (i *Interpreter) Add() {i.l, i.S = i.S.Pop()i.r, i.S = i.S.Pop()i.S = i.S.Push(i.l + i.r)
}
func (i *Interpreter) Print() {fmt.Printf("%v + %v = %v\n", i.l, i.r, i.S.data)
}
func (i *Interpreter) Exit() {os.Exit(0)
}
go: direct call-threaded interpreter
indirect threadinginstructions stored sequentially in memory
each represented by a local jump label
gcc/clang specific C extension
instructions indirectly load successor
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, EXIT } opcodes;
STACK *S;
void interpret(int *program) {static void *opcodes [] = {
&&op_push,&&op_add,&&op_print,&&op_exit
};
int l, r;int *PC = program;goto *opcodes[*PC++];
op_push:S = push(S, *PC++);goto *opcodes[*PC++];
op_add:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);goto *opcodes[*PC++];
op_print:printf("%d + %d = %d\n", l, r, S->data);goto *opcodes[*PC++];
op_exit:return;
}
int main() {int program [] = {
PUSH, 13,PUSH, 28,ADD,EXIT
};interpret(program);
}
c: indirect-threaded interpreter
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));
r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
#define READ_OPCODE *PC++
#define EXECUTE_OPCODE goto *opcodes[READ_OPCODE];
#define PRIMITIVE(name, body) \name: \
body; \EXECUTE_OPCODE
void interpret(int *program) {static void *opcodes [] = {
&&op_push,&&op_add,&&op_print,&&op_exit
};
int l, r;int *PC = program;EXECUTE_OPCODE;
PRIMITIVE(op_push, S = push(S, READ_OPCODE))
PRIMITIVE(op_add, \S = pop(S, &l); \S = pop(S, &r); \S = push(S, l + r); \
)
PRIMITIVE(op_print, printf("%d + %d = %d\n", l, r, S->data))
PRIMITIVE(op_exit, return)}
int main() {int program [] = {
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT
};interpret(program);
}
c: indirect-threaded interpreter
direct threadinginstructions stored sequentially in memory
each represented by a local jump label
gcc/clang specific C extension
instructions directly load successors
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
void **compile(int *PC, int words, void *despatch_table[]) {static void *compiler [] = {
&&comp_push,&&comp_add,&&comp_print,&&comp_exit
};
if (words < 1)return NULL;
void **program = malloc(sizeof(void *) * words);void **cp = program;goto *compiler[*PC++];
comp_push:*cp++ = despatch_table[PUSH];*cp++ = (void *)(long)*PC++;words -= 2;if (words == 0) return program;
goto *compiler[*PC++];
comp_add:*cp++ = despatch_table[ADD];words--;if (words == 0) return program;
goto *compiler[*PC++];
comp_print:*cp++ = despatch_table[PRINT];words--;if (words == 0) return program;
goto *compiler[*PC++];
comp_exit:*cp++ = despatch_table[EXIT];words--;if (words == 0) return program;
goto *compiler[*PC++];}
c: direct-threaded interpreter (1)
void interpret(int *PC, int words) {static void *despatch_table[] = {
&&op_push,&&op_add,&&op_print,&&op_exit
};
int l, r;void **program = compile(PC, words, despatch_table);if (program == NULL)
exit(1);goto **program++;
op_push:S = push(S, (int)(long)*program++);goto **program++;
op_add:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);goto **program++;
op_print:printf("%d + %d = %d\n", l, r, S->data);goto **program++;
op_exit:return;
}
int main() {int program[] = {
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT
};interpret(program, 7);
}
c: direct-threaded interpreter (2)
#include <stdio.h>#include <stdlib.h>
typedef struct stack STACK;struct stack {
int data;STACK *next;
};
STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;
}
STACK *pop(STACK *s, int *r) {if (s == NULL)
exit(1);*r = s->data;return s->next;
}
typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;
STACK *S;
#define COMPILE(body) \COMPILE_NEXT_OPCODE \body
#define COMPILE_NEXT_OPCODE \if (words < 1) \
return program; \goto *compiler[*PC++];
#define DESCRIBE_PRIMITIVE(name, body) \name: \
body; \COMPILE_NEXT_OPCODE
#define WRITE_OPCODE(value) \*cp++ = value; \words--;
void **compile(int *PC, int words, void *despatch_table[]) {static void *compiler[] = {
&&push,&&add,&&print,&&exit
};
void **program = malloc(sizeof(void *) * words);void **cp = program;COMPILE( \
DESCRIBE_PRIMITIVE(push, \WRITE_OPCODE(despatch_table[PUSH]) \WRITE_OPCODE((void *)(long)*PC++)) \
DESCRIBE_PRIMITIVE(add, \WRITE_OPCODE(despatch_table[ADD])) \
DESCRIBE_PRIMITIVE(print, \WRITE_OPCODE(despatch_table[PRINT])) \
DESCRIBE_PRIMITIVE(exit, \WRITE_OPCODE(despatch_table[EXIT])) \
)}
c: direct-threaded interpreter (1)
#define READ_OPCODE *program++
#define EXECUTE_OPCODE goto *READ_OPCODE;
#define PRIMITIVE(name, body) \name: \
body; \EXECUTE_OPCODE
void interpret(int *PC, int words) {static void *despatch_table[] = {
&&push,&&add,&&print,&&exit
};
int l, r;void **program = compile(PC, words, despatch_table);if (program == NULL)
exit(1);EXECUTE( \
PRIMITIVE(push, S = push(S, (int)(long)READ_OPCODE)) \PRIMITIVE(add, \
S = pop(S, &l); \S = pop(S, &r); \S = push(S, l + r)) \
PRIMITIVE(print, printf("%d + %d = %d\n", l, r, S->data)) \PRIMITIVE(exit, return) \
)}
int main() {int program[] = {
PUSH, 13,PUSH, 28,ADD,PRINT,EXIT
};interpret(program, 7);
}
c: direct-threaded interpreter (2)
timingclock pulse
synchronisation
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
instructionsoperations
operands
execution
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())
}
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
}
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
CISCsemantically rich instructions
complex memory addressing modes
compact binary code
RISCseparate IO and data processing
register-to-register instructions
load/store memory access
VLIWmultiple operations per instruction
compiler statically determines parallelism
simplifies control logic
processor coresends & receives signals on external buses
maintains internal computation state
executes sequences of instructions
accumulator machine1-operand instructions
data from memory combined with accumulator
result stored in accumulator
stack machine0-operand instructions
data popped from stack
results pushed on stack
register machinemulti-operand instructions
data read from memory into registers
operator combines memory and store ops
transport triggeringregister machine architecture
exposes internal buses as components
operations are side-effects of internal writes
vector machinemulti-operand instruction
data vectors read from memory into registers
operator combines registers and store ops
superscalarmultiple execution units
processor caching
out-of-order execution