SlideShare a Scribd company logo
implementing
software
machines
in
Go & C
Eleanor McHugh
@feyeleanor
this is a talk about VMs
system virtualisation
hardware emulation
abstract virtual machines
system virtualisation
this is a talk about VMs
system virtualisation
hardware emulation
abstract virtual machines
hardware emulation
this is a talk about VMs
system virtualisation
? hardware emulation ?
abstract virtual machines
program execution
this is a talk about VMs
system virtualisation
? hardware emulation ?
abstract virtual machines
inspired by hardware
discrete components
processors
storage
communications
software machines
timely
stateful
scriptable
Implementing Software Machines in C and Go
memory
storing data & instructions
addressing
protection
Implementing Software Machines in C and Go
go: heap
word-aligned
contiguous
byte-addressable
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)
}
}
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)
}
}
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)
}
}
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]
Implementing Software Machines in C and Go
Implementing Software Machines in C and Go
c: array stack
uses 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 = %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
go: slice stack
maps 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_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
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
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
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
Implementing Software Machines in C and Go
c: cactus stack
doesn’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 = %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
#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
#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
go: cactus stack
doesn’t need initialization
automatic stack growth on push()
garbage collection removes need for free()
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
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
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
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
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
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.out
rosy
sweet
pie
tart
(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.out
rosy
sweet
pie
tart
(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("%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)
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)
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)
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)
go: map
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)
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)
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>
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>
dispatch loops
fetch
decode
execute
dispatch loops
read next instruction via a program counter
determine the operation to perform
execute the operation and adjust machine state
switch interpreter
instructions 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 = %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
#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
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
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
#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
#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
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
indirect threading
instructions 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 = %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
#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
direct threading
instructions 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 = %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)
#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 = %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)
timing
clock pulse
synchronisation
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
instructions
operations
operands
execution
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())
}
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
}
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
CISC
semantically rich instructions
complex memory addressing modes
compact binary code
RISC
separate IO and data processing
register-to-register instructions
load/store memory access
VLIW
multiple operations per instruction
compiler statically determines parallelism
simplifies control logic
processor core
sends & receives signals on external buses
maintains internal computation state
executes sequences of instructions
accumulator machine
1-operand instructions
data from memory combined with accumulator
result stored in accumulator
stack machine
0-operand instructions
data popped from stack
results pushed on stack
register machine
multi-operand instructions
data read from memory into registers
operator combines memory and store ops
transport triggering
register machine architecture
exposes internal buses as components
operations are side-effects of internal writes
vector machine
multi-operand instruction
data vectors read from memory into registers
operator combines registers and store ops
superscalar
multiple execution units
processor caching
out-of-order execution
Implementing Software Machines in C and Go

More Related Content

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
  • 9. inspired by hardware discrete components processors storage communications
  • 12. memory storing data & instructions addressing protection
  • 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
  • 53. switch interpreter instructions stored sequentially in memory each represented by a token or opcode available in all implementation languages tokens can be compact - often single bytes
  • 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
  • 61. indirect threading instructions stored sequentially in memory each represented by a local jump label gcc/clang specific C extension instructions indirectly load successor
  • 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
  • 64. direct threading instructions stored sequentially in memory each represented by a local jump label gcc/clang specific C extension instructions directly load successors
  • 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
  • 75. CISC semantically rich instructions complex memory addressing modes compact binary code
  • 76. RISC separate IO and data processing register-to-register instructions load/store memory access
  • 77. VLIW multiple operations per instruction compiler statically determines parallelism simplifies control logic
  • 78. processor core sends & receives signals on external buses maintains internal computation state executes sequences of instructions
  • 79. accumulator machine 1-operand instructions data from memory combined with accumulator result stored in accumulator
  • 80. stack machine 0-operand instructions data popped from stack results pushed on stack
  • 81. register machine multi-operand instructions data read from memory into registers operator combines memory and store ops
  • 82. transport triggering register machine architecture exposes internal buses as components operations are side-effects of internal writes
  • 83. vector machine multi-operand instruction data vectors read from memory into registers operator combines registers and store ops
  • 84. superscalar multiple execution units processor caching out-of-order execution