Implementing Software Machines in C and Go
- 2. this is a talk about VMs
system virtualisation
hardware emulation
abstract virtual machines
- 4. this is a talk about VMs
system virtualisation
hardware emulation
abstract virtual machines
- 6. this is a talk about VMs
system virtualisation
? hardware emulation ?
abstract virtual machines
- 8. this is a talk about VMs
system virtualisation
? hardware emulation ?
abstract virtual machines
- 15. package memory
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) 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)
}
}
- 16. package memory
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) 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)
}
}
- 17. package memory
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) 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)
}
}
- 18. package main
import "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.go
Allegra:00 memory eleanor$ ./01
m (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]
- 21. c: array stack
uses fixed amount of memory
[grow | shrink]ing stack requires explicit realloc()
stack pointer is an offset into the array
- 22. #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 = %dn", depth(s));
pop(s, &l);
pop(s, &r);
printf("%d + %d = %dn", l, r, l + r);
printf("depth = %dn", depth(s));
}
c: array stack
$ cc 01.c
$ ./a.out
depth = 2
3 + 1 = 4
depth = 0
- 23. go: slice stack
maps directly to [] slice types
[grow | shrink]ing stack automatic via append()
doesn’t require initialization
- 24. package main
import "fmt"
type stack_status int
const (
STACK_OK = stack_status(iota)
STACK_OVERFLOW
STACK_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) - 1
r := 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 = %dn", s.Depth())
l, _ := s.Pop()
r, _ := s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", s.Depth())
}
go: slice stack
$ go run 01a.go
depth = 2
3 + 1 = 4
depth = 0
- 25. package main
import "fmt"
type stack_status int
const (
STACK_OK = stack_status(iota)
STACK_OVERFLOW
STACK_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) - 1
r := 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 = %dn", s.Depth())
l, _ := s.Pop()
r, _ := s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", s.Depth())
}
go: slice stack
$ go run 01a.go
depth = 2
3 + 1 = 4
depth = 0
- 26. package main
import "fmt"
type stack_status int
const (
STACK_OK = stack_status(iota)
STACK_OVERFLOW
STACK_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) - 1
r := 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 = %dn", s.Depth())
l, _ := s.Pop()
r, _ := s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", s.Depth())
}
go: slice stack
$ go run 01a.go
depth = 2
3 + 1 = 4
depth = 0
- 27. 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 (s stack) Depth() int {
return len(s)
}
func main() {
s := new(stack)
s.Push(1)
s.Push(3)
fmt.Printf("depth = %dn", s.Depth())
l := s.Pop()
r := s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", s.Depth())
}
go: slice stack
$ go run 01b.go
depth = 2
3 + 1 = 4
depth = 0
- 29. c: cactus stack
doesn’t require initialization
automatic stack growth on push()
potentially leaks memory on pop()
- 30. #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 = %dn", depth(s));
STACK *t = pop(pop(s, &r), &l);
printf("%d + %d = %dn", l, r, l + r);
printf("depth pre-gc = %dn", depth(s));
gc(&s, 2);
printf("depth post-gc = %dn", depth(s));
}
c: functional cactus stack
$ cc 02.c
$ ./a.out
depth = 2
1 + 3 = 4
depth pre-gc = 2
depth post-gc = 0
- 31. #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 = %dn", depth(s));
STACK *t = pop(pop(s, &r), &l);
printf("%d + %d = %dn", l, r, l + r);
printf("depth pre-gc = %dn", depth(s));
gc(&s, 2);
printf("depth post-gc = %dn", depth(s));
}
c: functional cactus stack
$ cc 02.c
$ ./a.out
depth = 2
1 + 3 = 4
depth pre-gc = 2
depth post-gc = 0
- 32. #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 = %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);
}
c: cactus stack in action
$ cc 03.c
$ ./a.out
5 items: sum = 25
3 items: sum = 25
5 items: sum = 39
- 33. go: cactus stack
doesn’t need initialization
automatic stack growth on push()
garbage collection removes need for free()
- 34. package main
import "fmt"
type stack struct {
data int
tail *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 int
var s *stack
s = s.Push(1).Push(3)
fmt.Printf("depth = %dn", s.Depth())
l, s = s.Pop()
r, s = s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", s.Depth())
}
go: functional cactus stack
$ go run 02a.go
depth = 2
3 + 1 = 4
depth = 0
- 35. package main
import "fmt"
type stack struct {
data int
tail *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 int
var s stack
s = s.Push(1).Push(3)
fmt.Printf("depth = %dn", s.Depth())
l, s = s.Pop()
r, s = s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
fmt.Printf("depth = %dn", s.Depth())
}
go: functional cactus stack
$ go run 02b.go
depth = 2
3 + 1 = 4
depth = 0
- 36. package main
import "fmt"
type stack struct {
data int
tail *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 := s
for ; t.tail != nil; t = t.tail {}
*t = stack{data: n, tail: new(stack)}
}
func main() {
var l, r int
var s stack
s = s.Push(1).Push(3)
fmt.Printf("depth = %dn", s.Depth())
s.append(20)
fmt.Printf("depth = %dn", s.Depth())
l, s = s.Pop()
r, s = s.Pop()
fmt.Printf("%d + %d = %dn", l, r, l+r)
l, s = s.Pop()
fmt.Printf("l = %dn", l)
fmt.Printf("depth = %dn", s.Depth())
s.append(5)
l, s = s.Pop()
fmt.Printf("l = %dn", l)
fmt.Printf("depth = %dn", s.Depth())
}
go: functional cactus stack
$ go run 02c.go
depth = 2
depth = 3
3 + 1 = 4
l = 20
depth = 0
l = 5
depth = 0
- 37. package main
import "fmt"
type stack struct {
data int
tail *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 = %dn", 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 = 25
3 items: sum = 25
5 items: sum = 39
- 38. package main
import "fmt"
type stack struct {
data int
tail *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 = %dn", 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 = 25
3 items: sum = 25
5 items: sum = 39
- 40. #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.out
rosy
sweet
pie
tart
(null)
- 41. #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.out
rosy
sweet
pie
tart
(null)
- 42. #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("%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"));
return 0;
}
c: map
$ cc 04.c
$ ./a.out
rosy
sweet
pie
tart
(null)
- 43. 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.out
rosy
sweet
pie
tart
(null)
- 44. 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.out
rosy
sweet
pie
tart
(null)
- 45. 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.out
rosy
sweet
pie
tart
(null)
- 47. package assoc_array
type AssocArray struct {
Key string
Value 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.out
rosy
sweet
pie
tart
(null)
- 48. package search
import . "assoc_array"
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
}
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
}
go: map
$ cc 04.c
$ ./a.out
rosy
sweet
pie
tart
(null)
- 49. package main
import "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.Cursor
m[c] = n
case s.Cursor == nil:
s.Memo.Next = n
default:
n.Next = s.Cursor
s.Memo.Next = n
}
}
}
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) 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("%vn", m.Get("apple"))
m.Set("blueberry", "sweet")
fmt.Printf("%vn", m.Get("blueberry"))
m.Set("cherry", "pie")
fmt.Printf("%vn", m.Get("cherry"))
m.Set("cherry", "tart")
fmt.Printf("%vn", m.Get("cherry"))
fmt.Printf("%vn", m.Get("tart"))
}
go: map
$ go run 04.go
rosy
sweet
pie
tart
<nil>
- 50. package main
import "fmt"
func main() {
m := make(map[string] interface{})
m["apple"] = "rosy"
fmt.Printf("%vn", m["apple"])
m["blueberry"] = "sweet"
fmt.Printf("%vn", m["blueberry"])
m["cherry"] = "pie"
fmt.Printf("%vn", m["cherry"])
m["cherry"] = "tart"
fmt.Printf("%vn", m["cherry"])
fmt.Printf("%vn", m["tart"])
}
go: map
$ go run 05.go
rosy
sweet
pie
tart
<nil>
- 52. dispatch loops
read next instruction via a program counter
determine the operation to perform
execute the operation and adjust machine state
- 54. #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 = %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: switch interpreter
- 55. #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 = %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: switch interpreter
- 56. package main
import "fmt"
func main() {
var program = []interface{}{
PUSH, 13,
PUSH, 28,
ADD,
PRINT,
EXIT,
}
interpret(program)
}
type stack struct {
data int
tail *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)
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.data)
case EXIT:
return
}
} else {
return
}
}
}
go: switch interpreter
- 57. direct call threading
instructions stored sequentially in memory
each represented by a pointer to a function
not available in all languages
instructions each require a machine word
- 58. #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 = %dn", 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
- 59. #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 = %dn", 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
- 60. 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 int
tail *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 *stack
l, r, PC int
m []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 = %vn", i.l, i.r, i.S.data)
}
func (i *Interpreter) Exit() {
os.Exit(0)
}
go: direct call-threaded interpreter
- 62. #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 = %dn", 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
- 63. #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 = %dn", 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
- 65. #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)
- 66. 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;
}
int main() {
int program[] = {
PUSH, 13,
PUSH, 28,
ADD,
PRINT,
EXIT
};
interpret(program, 7);
}
c: direct-threaded interpreter (2)
- 67. #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)
- 68. #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 = %dn", 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)
- 70. package clock
import "syscall"
type Clock struct {
Period int64
Count chan int64
Control chan bool
active bool
}
func (c *Clock) Start() {
if !c.active {
go func() {
c.active = true
for i := int64(0); ; i++ {
select {
case status := <- c.Control:
c.active = status
default:
if c.active {
c.Count <- i
}
syscall.Sleep(c.Period)
}
}
}()
}
}
package main
import . “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 <- false
syscall.Sleep(1000000)
println("restarting clock")
c.Control <- true
println("pulse value", <-c.Count, "from clock")
}
produces:
pulse value 0 from clock
pulse value 1 from clock
pulse value 2 from clock
disabling clock
restarting clock
pulse value 106 from clock
- 72. package instructions
type Op func(o []int)
type Executable interface {
Opcode() int
Operands() []int
Execute(Op)
}
const INVALID_OPCODE = -1
type Instr []int
func (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())
}
- 73. package assembler
import "fmt"
import . "instructions"
type Asm struct {
opcodes map[string] int
names 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] = opcode
copy(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 = name
if params := e.Operands(); len(params) > 0 {
s = fmt.Sprintf("%vt%v", s, params[0])
for _, v := range params[1:] {
s = fmt.Sprintf("%v, %v", s, v)
}
}
} else {
s = "INVALID"
}
return
}
- 74. package main
import "fmt"
import "assembler"
type Program []Executable
func (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:
noop
load 1
store 1, 2
unknown
op = 2 result = 3
- 78. processor core
sends & receives signals on external buses
maintains internal computation state
executes sequences of instructions