The next iteration of the talk I gave at Progscon, this introduces examples of Map implementation (useful for caches etc.) and outlines for addition of processor core code in a later talk.
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
64. direct threading
instructions stored sequentially in memory
each represented by a local jump label
gcc/clang specific C extension
instructions directly load successors
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