implementing software machines in go and c

38
implementing software machines in Go and C Eleanor McHugh @feyeleanor

Upload: eleanor-mchugh

Post on 19-Jan-2017

285 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Implementing Software Machines in Go and C

implementing software machines in Go and C

Eleanor McHugh

@feyeleanor

Page 2: Implementing Software Machines in Go and C

this is a talk about VMssystem virtualisation

hardware emulation

abstract virtual machines

Page 3: Implementing Software Machines in Go and C

system virtualisation

Page 4: Implementing Software Machines in Go and C

this is a talk about VMssystem virtualisation

hardware emulation

abstract virtual machines

Page 5: Implementing Software Machines in Go and C

hardware emulation

Page 6: Implementing Software Machines in Go and C

this is a talk about VMssystem virtualisation

hardware emulation

abstract virtual machines

Page 7: Implementing Software Machines in Go and C

program execution

Page 8: Implementing Software Machines in Go and C

this is a talk about VMssystem virtualisation

hardware emulation

abstract virtual machines

Page 9: Implementing Software Machines in Go and C

inspired by hardwarediscrete components

processors

storage

communications

Page 10: Implementing Software Machines in Go and C

software machinestimely

stateful

scriptable

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

#include <stdio.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.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 main() {int l, r;STACK *s = NewStack();push(s, 1);push(s, 3);pop(s, &l);pop(s, &r);printf("%d + %d = %d\n", l, r, l + r);

}

c: array stack

Page 15: Implementing Software Machines in Go and C

package main

import "fmt"

type stack_status int

const (STACK_OK = stack_status(iota)STACK_OVERFLOWSTACK_UNDERFLOW

)

type stack struct {data []int

}

func (s *stack) Push(data int) {s.data = append(s.data, data)

}

func (s *stack) Pop() (int, stack_status) {if s == nil || len(s.data) < 1 {

return 0, STACK_UNDERFLOW}sp := len(s.data) - 1r := s.data[sp]s.data = s.data[:sp]return r, STACK_OK

}

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

}

go: array stack

Page 16: Implementing Software Machines in Go and C

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

}

c: functional cactus stack

Page 17: Implementing Software Machines in Go and C

package main

import "fmt"

type stack struct {data inttail *stack

}

func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return

}

func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail

}

func main() {var l, r intvar s *stacks = s.Push(1).Push(3)l, s = s.Pop()r, s = s.Pop()fmt.Printf("%d + %d = %d\n", l, r, l+r)

}

go: functional cactus stack

Page 18: Implementing Software Machines in Go and C

#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 sum(STACK *tos) {int a, p;

a = 0;for (; tos != NULL;) {

tos = pop(tos, &p);a += p;

}return a;

}

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);printf("sum = %d\n", sum(s1));printf("sum = %d\n", sum(s2));printf("sum = %d\n", sum(s3));

}

c: functional cactus stack

Page 19: Implementing Software Machines in Go and C

package main

import "fmt"

type stack struct {data inttail *stack

}

func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return

}

func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail

}

func (s *stack) Sum() (r int) {for p := 0; s != nil; r += p {

p, s = s.Pop()}return

}

func main() {var s1, s2, s3 *stacks1 = s1.Push(7)s2 = s1.Push(7).Push(11)

s1 = s1.Push(2).Push(9).Push(4)s3 = s1.Push(17)

s1 = s1.Push(3)

fmt.Printf("sum = %v\n", s1.Sum())fmt.Printf("sum = %v\n", s2.Sum())fmt.Printf("sum = %v\n", s3.Sum())

}

go: functional cactus stack

Page 20: Implementing Software Machines in Go and C

dispatch loopsfetch

decode

execute

Page 21: Implementing Software Machines in Go and C

dispatch loopsread next instruction via a program counter

determine the operation to perform

execute the operation and adjust machine state

Page 22: Implementing Software Machines in Go and C

switch interpreterinstructions stored sequentially in memory

each represented by a token or opcode

available in all implementation languages

tokens can be compact - often single bytes

Page 23: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;

STACK *S;

void interpret(int *PC) {int l, r;while (1) {

switch(*PC++) {case PUSH:

S = push(S, *PC++);break;

case ADD:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);break;

case PRINT:printf(“%d + %d = %d\n, l, r, S->data);break;

case EXIT:return;

}}

}

int main() {int program [] = {

(int)PUSH, 13,(int)PUSH, 28,(int)ADD,PRINT,EXIT,

};interpret(program);

}

c: switch interpreter

Page 24: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;

STACK *S;

#define READ_OPCODE *PC++

void interpret(int *PC) {int l, r;while (1) {

switch(READ_OPCODE) {case PUSH:

S = push(S, READ_OPCODE);break;

case ADD:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);break;

case PRINT:printf(“%d + %d = %d\n, l, r, S->data);break;

case EXIT:return;

}}

}

int main() {int program [] = {

(int)PUSH, 13,(int)PUSH, 28,(int)ADD,PRINT,EXIT,

};interpret(program);

}

c: switch interpreter

Page 25: Implementing Software Machines in Go and C

package main

import "fmt"

func main() {var program = []interface{}{

PUSH, 13,PUSH, 28,ADD,PRINT,EXIT,

}interpret(program)

}

type stack struct {data inttail *stack

}

func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return

}

func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail

}

type OPCODE int

const (PUSH = OPCODE(iota)ADDPRINTEXIT

)

func interpret(p []interface{}) {var l, r intS := new(stack)

for PC := 0; ; PC++ {if op, ok := p[PC].(OPCODE); ok {

switch op {case PUSH:

PC++S = S.Push(p[PC].(int))

case ADD:l, S, = S.Pop()r, S = S.Pop()S = S.Push(l + r)

case PRINT:fmt.Printf("%v + %v = %v\n", l, r, S.data)

case EXIT:return

}} else {

return}

}}

go: switch interpreter

Page 26: Implementing Software Machines in Go and C

direct call threadinginstructions stored sequentially in memory

each represented by a pointer to a function

not available in all languages

instructions each require a machine word

Page 27: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef void (*opcode)();

STACK *S;opcode *PC;

void op_push() {S = push(S, (int)(long)(*PC++));

}

void op_add_and_print() {int l, r;S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);printf("%d + %d = %d\n", l, r, S->data);

}

void op_exit() {exit(0);

}

int main() {opcode program [] = {

op_push, (opcode)(long)13,op_push, (opcode)(long)28,op_add_and_print,op_exit

};PC = program;while (1) {

(*PC++)();}

}

c: direct call-threaded interpreter

Page 28: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef void (*opcode)();

STACK *S;opcode *PC;

#define READ_OPCODE *PC++

void op_push() {S = push(S, (int)(long)(READ_OPCODE));

}

void op_add_and_print() {int l, r;S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);printf("%d + %d = %d\n", l, r, S->data);

}

void op_exit() {exit(0);

}

int main() {opcode program [] = {

op_push, (opcode)(long)13,op_push, (opcode)(long)28,op_add_and_print,op_exit

};PC = program;while (1) {

(READ_OPCODE)();}

}

c: direct call-threaded interpreter

Page 29: Implementing Software Machines in Go and C

package main

import "fmt"import "os"

func main() {p := new(Interpreter)p.m = []interface{}{

p.Push, 13,p.Push, 28,p.Add,p.Print,p.Exit,

}p.Run()

}

type stack struct {data inttail *stack

}

func (s *stack) Push(v int) (r *stack) {r = &stack{data: v, tail: s}return

}

func (s *stack) Pop() (v int, r *stack) {return s.data, s.tail

}

type Interpreter struct {S *stackl, r, PC intm []interface{}

}

func (i *Interpreter) opcode() func() {return i.m[i.PC].(func())

}

func (i *Interpreter) operand() int {return i.m[i.PC].(int)

}

func (i *Interpreter) Run() {for {

i.opcode()()i.PC++

}}

func (i *Interpreter) Push() {i.PC++i.S = i.S.Push(i.operand())

}

func (i *Interpreter) Add() {i.l, i.S = i.S.Pop()i.r, i.S = i.S.Pop()i.S = i.S.Push(i.l + i.r)

}

func (i *Interpreter) Print() {fmt.Printf("%v + %v = %v\n", i.l, i.r, i.S.data)

}

func (i *Interpreter) Exit() {os.Exit(0)

}

go: direct call-threaded interpreter

Page 30: Implementing Software Machines in Go and C

indirect threadinginstructions stored sequentially in memory

each represented by a local jump label

gcc/clang specific C extension

instructions indirectly load successor

Page 31: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef enum { PUSH = 0, ADD, EXIT } opcodes;

STACK *S;

void interpret(int *program) {static void *opcodes [] = {

&&op_push,&&op_add,&&op_print,&&op_exit

};

int l, r;int *PC = program;goto *opcodes[*PC++];

op_push:S = push(S, *PC++);goto *opcodes[*PC++];

op_add:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);goto *opcodes[*PC++];

op_print:printf("%d + %d = %d\n", l, r, S->data);goto *opcodes[*PC++];

op_exit:return;

}

int main() {int program [] = {

PUSH, 13,PUSH, 28,ADD,EXIT

};interpret(program);

}

c: indirect-threaded interpreter

Page 32: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));

r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;

STACK *S;

#define READ_OPCODE *PC++

#define EXECUTE_OPCODE goto *opcodes[READ_OPCODE];

#define PRIMITIVE(name, body) \name: \

body; \EXECUTE_OPCODE

void interpret(int *program) {static void *opcodes [] = {

&&op_push,&&op_add,&&op_print,&&op_exit

};

int l, r;int *PC = program;EXECUTE_OPCODE;

PRIMITIVE(op_push, S = push(S, READ_OPCODE))

PRIMITIVE(op_add, \S = pop(S, &l); \S = pop(S, &r); \S = push(S, l + r); \

)

PRIMITIVE(op_print, printf("%d + %d = %d\n", l, r, S->data))

PRIMITIVE(op_exit, return)}

int main() {int program [] = {

PUSH, 13,PUSH, 28,ADD,PRINT,EXIT

};interpret(program);

}

c: indirect-threaded interpreter

Page 33: Implementing Software Machines in Go and C

direct threadinginstructions stored sequentially in memory

each represented by a local jump label

gcc/clang specific C extension

instructions directly load successors

Page 34: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;

STACK *S;

void **compile(int *PC, int words, void *despatch_table[]) {static void *compiler [] = {

&&comp_push,&&comp_add,&&comp_print,&&comp_exit

};

if (words < 1)return NULL;

void **program = malloc(sizeof(void *) * words);void **cp = program;goto *compiler[*PC++];

comp_push:*cp++ = despatch_table[PUSH];*cp++ = (void *)(long)*PC++;words -= 2;if (words == 0) return program;

goto *compiler[*PC++];

comp_add:*cp++ = despatch_table[ADD];words--;if (words == 0) return program;

goto *compiler[*PC++];

comp_print:*cp++ = despatch_table[PRINT];words--;if (words == 0) return program;

goto *compiler[*PC++];

comp_exit:*cp++ = despatch_table[EXIT];words--;if (words == 0) return program;

goto *compiler[*PC++];}

c: direct-threaded interpreter (1)

Page 35: Implementing Software Machines in Go and C

void interpret(int *PC, int words) {static void *despatch_table[] = {

&&op_push,&&op_add,&&op_print,&&op_exit

};

int l, r;void **program = compile(PC, words, despatch_table);if (program == NULL)

exit(1);goto **program++;

op_push:S = push(S, (int)(long)*program++);goto **program++;

op_add:S = pop(S, &l);S = pop(S, &r);S = push(S, l + r);goto **program++;

op_print:printf("%d + %d = %d\n", l, r, S->data);goto **program++;

op_exit:return;

}

int main() {int program[] = {

PUSH, 13,PUSH, 28,ADD,PRINT,EXIT

};interpret(program, 7);

}

c: direct-threaded interpreter (2)

Page 36: Implementing Software Machines in Go and C

#include <stdio.h>#include <stdlib.h>

typedef struct stack STACK;struct stack {

int data;STACK *next;

};

STACK *push(STACK *s, int data) {STACK *r = malloc(sizeof(STACK));r->data = data;r->next = s;return r;

}

STACK *pop(STACK *s, int *r) {if (s == NULL)

exit(1);*r = s->data;return s->next;

}

typedef enum { PUSH = 0, ADD, PRINT, EXIT } opcodes;

STACK *S;

#define COMPILE(body) \COMPILE_NEXT_OPCODE \body

#define COMPILE_NEXT_OPCODE \if (words < 1) \

return program; \goto *compiler[*PC++];

#define DESCRIBE_PRIMITIVE(name, body) \name: \

body; \COMPILE_NEXT_OPCODE

#define WRITE_OPCODE(value) \*cp++ = value; \words--;

void **compile(int *PC, int words, void *despatch_table[]) {static void *compiler[] = {

&&push,&&add,&&print,&&exit

};

void **program = malloc(sizeof(void *) * words);void **cp = program;COMPILE( \

DESCRIBE_PRIMITIVE(push, \WRITE_OPCODE(despatch_table[PUSH]) \WRITE_OPCODE((void *)(long)*PC++)) \

DESCRIBE_PRIMITIVE(add, \WRITE_OPCODE(despatch_table[ADD])) \

DESCRIBE_PRIMITIVE(print, \WRITE_OPCODE(despatch_table[PRINT])) \

DESCRIBE_PRIMITIVE(exit, \WRITE_OPCODE(despatch_table[EXIT])) \

)}

c: direct-threaded interpreter (1)

Page 37: Implementing Software Machines in Go and C

#define READ_OPCODE *program++

#define EXECUTE_OPCODE goto *READ_OPCODE;

#define PRIMITIVE(name, body) \name: \

body; \EXECUTE_OPCODE

void interpret(int *PC, int words) {static void *despatch_table[] = {

&&push,&&add,&&print,&&exit

};

int l, r;void **program = compile(PC, words, despatch_table);if (program == NULL)

exit(1);EXECUTE( \

PRIMITIVE(push, S = push(S, (int)(long)READ_OPCODE)) \PRIMITIVE(add, \

S = pop(S, &l); \S = pop(S, &r); \S = push(S, l + r)) \

PRIMITIVE(print, printf("%d + %d = %d\n", l, r, S->data)) \PRIMITIVE(exit, return) \

)}

int main() {int program[] = {

PUSH, 13,PUSH, 28,ADD,PRINT,EXIT

};interpret(program, 7);

}

c: direct-threaded interpreter (2)

Page 38: Implementing Software Machines in Go and C