GoLightly Building VM-based language runtimes in Go Eleanor McHugh http://slides.games-with-brains.net/ portrait of an artist... physics major software reliability network scaling questionable taste in or.tel dynamic languages an http://feyele embedded controllers h g u H c M r o Elean Languages Project ? fluid-dynamics simulation ? cockpit autopilot controller ? paramilitary cockpit C4I ? broadcast automation ? encrypted RDBM ? Unix kernel scripting Language Project ICON fluid-dynamics simulation ASM cockpit autopilot controller VB5 paramilitary cockpit C4I VB5, ASM, K&R C broadcast automation RUBY, DNS encrypted RDBM RUBY Unix kernel scripting software a tutorial on Google Go wizardry simulating machines in software wild romance golightly Elric sent his mind into twisting tunnels of logic, across endless plains of ideas, through mountains of symbolism and endless universes of alternate truths; he sent his mind out further and further and as it went he sent with it the words which issued from his writhing lips -- words that few of his contemporaries would understand... - Elric of Melniboné, Michael Moorcock golightly agnostic heterogenous virtualisation networks go... a systems language by google productivity, performance, concurrency lighter than Java, safer than C ...lightly clean abstractions geared to performance non-viral open source license inspiration processor design sensor and control networks field-programmable gate arrays perspiration iterative empirical development explore -> implement -> test -> benchmark evolve towards elegance principles decoupling improves scalability coherence simplifies organisation optimisations are application specific agnostic no blessed programming languages flexible platform abstractions write once, run everywhere it matters heterogeneous a system comprises many components components may differ in purpose and design but they cooperate to solve problems virtualisation design discrete Turing machines implement these machines in software compile programs to run on them networks machines cooperate by sending messages machine states can be serialised as messages messages transcend process and host boundaries caveat lector danger! we’re entering strange territory our map is missing major landmarks and will be riddled with inaccuracies so please tread carefully try not to disturb the local wildlife and don’t be put off by the pages of go an elegant language a statically-typed compiled language object-oriented static type declaration dynamic type inference garbage collection concurrency via communication (CSP) hello world package main import “fmt” const( HELLO string = “hello” WORLD string = “world” ) func main() { fmt.Println(HELLO, WORLD) } objects boolean, numeric, array value structure, interface reference pointer, slice, string, map, channel function function, method, closure underlying type expressed type method set underlying type method set expressed type embedded types user-defined type package Integer type Int int func (i *Int) Add(x int) { *i += Int(x) } package Integer func (b Buffer) Clone() Buffer { s := make(Buffer, len(b)) type Buffer []Int copy(s, b) return s func (b Buffer) Eq(o Buffer) (r bool) { } if len(b) == len(o) { for i := len(b) - 1; i > 0; i-- { func (b Buffer) Move(i, n int) { if b[i] != o[i] { if n > len(b) - i { return n = len(b) - i } } } segment_to_move := b[:i].Clone() r = true copy(b, b[i:i + n]) } copy(b[n:i + n], return segment_to_move) } } func (b Buffer) Swap(i, j int) { b[i], b[j] = b[j], b[i] } package main import “Integer” func main() { i := Integer.Buffer{0, 1, 2, 3, 4, 5} b := i.Clone() b.Swap(1, 2) b.Move(3, 2) b[0].Add(3) println(“b[0:2] = {”, b[0], “,”, b[1], “}”) } produces: b[0:2] = { 6, 4 } testing include $(GOROOT)/src/Make.inc TARG=integer GOFILES=integer.go include $(GOROOT)/src/Make.pkg package Integer type Int int func (i *Int) Add(x int) { *i += Int(x) } func (b Buffer) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b Buffer) Clone() Buffer { s := make(Buffer, len(b)) copy(s, b) type Buffer []Int return s func (b Buffer) Eq(o Buffer) (r bool) { } if len(b) == len(o) { for i := len(b) - 1; i > 0; i-- { func (b Buffer) Move(i, n int) { if b[i] != o[i] { if n > len(b) - i { return n = len(b) - i } } } segment_to_move := b[:i].Clone() r = true copy(b, b[i:i + n]) } copy(b[n:i + n], return segment_to_move) } } package Integer import “testing” func TestSwap(t *testing.T) { i := Buffer{0, 1, 2, 3, 4, 5} b := i.Clone() b.Swap(1, 2) if !b[1:3].Eq(Buffer{2, 1}) { t.Fatalf("b[0:5] = %v", b) } } func TestMove(t *testing.T) { i := Buffer{0, 1, 2, 3, 4, 5} b := i.Clone() b.Move(3, 2) if !b.Eq(Buffer{3, 4, 0, 1, 2, 5}) { t.Fatalf("b[0:5] = %v", b) } } func TestAdd(t *testing.T) { i := Buffer{0, 1, 2, 3, 4, 5} b := i.Clone() b[0].Add(3) if b[0] != i[0] + 3 { t.Fatalf("b[0:5] = %v", b) } } type embedding package Vector import . “Integer” type Vector struct { Buffer } func (v *Vector) Clone() Vector { return Vector{v.Buffer.Clone()} } func (v *Vector) Slice(i, j int) Buffer { return v.Buffer[i:j] } include $(GOROOT)/src/Make.inc TARG=integer GOFILES=\ integer.go\ vector.go include $(GOROOT)/src/Make.pkg package Integer import “testing” func TestVectorSwap(t *testing.T) { i := Vector{Buffer{0, 1, 2, 3, 4, 5}} v := i.Clone() v.Swap(1, 2) r := Vector{Buffer{0, 2, 1, 3, 4, 5}} switch { case !v.Match(&r): fallthrough case !v.Buffer.Match(r.Buffer): t.Fatalf("b[0:5] = %v", v) } } benchmarking package integer import "testing" func BenchmarkVectorClone6(b *testing.B) { v := Vector{Buffer{0, 1, 2, 3, 4, 5}} for i := 0; i < b.N; i++ { _ = v.Clone() } } func BenchmarkVectorSwap(b *testing.B) { b.StopTimer() v := Vector{Buffer{0, 1, 2, 3, 4, 5}} b.StartTimer() for i := 0; i < b.N; i++ { v.Swap(1, 2) } } $ gotest -bench="Benchmark" rm -f _test/scripts.a 6g -o _gotest_.6 integer.go vector.go nominal_typing_test.go embedded_typing_benchmark_test.go embedded_typing_test.go rm -f _test/scripts.a gopack grc _test/scripts.a _gotest_.6 PASS integer.BenchmarkVectorSwap 200000000 8 ns/op integer.BenchmarkVectorClone6 10000000 300 ns/op dynamism & reflection package adder type Adder interface { Add(j int) Subtract(j int) Result() interface{} } type Calculator interface { Adder Reset() } type AddingMachine struct { Memory interface{} Adder } package adder type IAdder []int func (i IAdder) Add(j int) { i[0] += i[j] } func (i IAdder) Subtract(j int) { i[0] -= i[j] } func (i IAdder) Result() interface{} { return i[0] } func (i IAdder) Reset() { i[0] = *new(int) } package adder import "testing" func TestIAdder(t *testing.T) { error := "Result %v != %v" i := IAdder{0, 1, 2} i.Add(1) if i.Result().(int) != 1 { t.Fatalf(error, i.Result(), 1) } i.Subtract(2) if i.Result().(int) != -1 { t.Fatalf(error, i.Result()), -1 } var r Calculator = IAdder{-1, 1, 2} for n, v := range r.(IAdder) { if i[n] != v { t.Fatalf("Adder %v should be %v", i, r) } } r.Reset() if r.Result().(int) != *new(int) { t.Fatalf(error, r.Result(), *new(int)) } } package adder type FAdder []float32 func (f FAdder) Add(j int) { f[0] += f[j] } func (f FAdder) Subtract(j int) { f[0] -= f[j] } func (f FAdder) Result() interface{} { return f[0] } func (f FAdder) Reset() { f[0] = *new(float32) } package adder import "testing" func TestFAdder(t *testing.T) { error := "Result %v != %v" f := FAdder{0.0, 1.0, 2.0} f.Add(1) if f.Result().(float32) != 1.0 { t.Fatalf(error, f.Result(), 1.0) } f.Subtract(2) if i.Result().(float32) != -1.0 { t.Fatalf(error, i.Result()), -1.0 } var r Calculator = FAdder{-1.0, 1.0, 2.0} for n, v := range r.(FAdder) { if f[n] != v { t.Fatalf("Adder %v should be %v", f, r) } } r.Reset() if r.Result().(float32) != *new(float32) { t.Fatalf(error, r.Result(), *new(float32)) } } package adder import "testing" func TestAddingMachine(t *testing.T) { error := "Result %v != %v" a := &AddingMachine{ Adder: FAdder{0.0, 1.0, 2.0} } a.Add(1) if f, ok := a.Result().(float32); !ok { t.Fatal("Result should be a float32") } else if f != 1.0 { t.Fatalf(error, a.Result(), 1.0) } a.Subtract(2) if a.Result().(float32) != -1.0 { t.Fatalf(error, a.Result(), -1.0) } r := FAdder{-1.0, 1.0, 2.0} for n, v := range a.Adder.(FAdder) { if r[n] != v { t.Fatalf("Adder %v should be %v", a, r) } } } package generalise import "reflect" func Allocate(i interface{}, limit... int) (n interface{}) { v := reflect.NewValue(i) switch v := v.(type) { case *reflect.SliceValue: l := v.Cap() if len(limit) > 0 { l = limit[0] } t := v.Type().(*reflect.SliceType) n = reflect.MakeSlice(t, l, l).Interface() case *reflect.MapValue: t := v.Type().(*reflect.MapType) n = reflect.MakeMap(t).Interface() } return } package generalise import . "reflect" func SwapSlices(i interface{}, d, s, n int) { if v, ok := NewValue(i).(*SliceValue); ok { source := v.Slice(s, s + n) destination := v.Slice(d, d + n) temp := NewValue(Allocate(i, n)).(*SliceValue) Copy(temp, destination) Copy(destination, source) Copy(source, temp) } else { panic(i) } } package generalise import . "reflect" func Duplicate(i interface{}) (clone interface{}) { if clone = Allocate(i); clone != nil { switch clone := NewValue(clone).(type) { case *SliceValue: s := NewValue(i).(*SliceValue) Copy(clone, s) case *MapValue: m := NewValue(i).(*MapValue) for _, k := range m.Keys() { clone.SetElem(k, m.Elem(k)) } } } return } package generalise import "testing" func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() return } func TestAllocate(t *testing.T) { var s2 []int s1 := []int{0, 1, 2} m := map[int] int{1: 1, 2: 2, 3: 3} switch { case throwsPanic(func() { s2 = Allocate(s1, 1).([]int) }): t.Fatal("Unable to allocate new slice") case len(s2) != 1: fallthrough case cap(s2) != 1: t.Fatal("New slice should be %v not %v", make([]int, 0, 1), s2) case throwsPanic(func() { Allocate(m) }): t.Fatal("Unable to allocate new map") } } func TestDuplicate(t *testing.T) { error := "Duplicating %v produced %v" s1 := []int{0, 1, 2} var s2 []int m1 := map[int]int{1: 1, 2: 2, 3: 3} var m2 map[int]int switch { case throwsPanic(func() { s2 = Duplicate(s1).([]int) }): t.Fatalf("Unable to duplicate slice %v\n", s1) case len(s1) != len(s2): fallthrough case cap(s1) != cap(s2): fallthrough case s1[0] != s2[0] || s1[1] != s2[1] || s1[2] != s2[2]: t.Fatalf(error, s1, s2) case throwsPanic(func() { m2 = Duplicate(m1).(map[int]int) }): t.Fatalf("Unable to duplicate map %v\n", m1) case len(m1) != len(m2): fallthrough case m1[1] != m2[1] || m1[2] != m2[2] || m1[3] != m2[3]: t.Fatalf(error, m1, m2) } } func TestSwapSlices(t *testing.T) { error := "%v became %v but should be %v" s1 := []int{0, 1, 2, 3, 4, 5} s2 := Duplicate(s1).([]int) r := []int{3, 4, 5, 0, 1, 2} m := map[int] int{1: 1, 2: 2} switch { case !throwsPanic(func() { SwapSlices(m, 0, 3, 3) }): t.Fatalf("Successfully swapped slices %v\n", m) case throwsPanic(func() { SwapSlices(s2, 0, 3, 3) }): t.Fatalf("Unable to swap slices %v\n", s2) case len(s2) != len(r): t.Fatalf(error, s1, s2, r) default: for i, v := range s2 { if r[i] != v { t.Fatalf(error, s1, s2, r) } } } } mutability package raw import . "reflect" import "unsafe" var _BYTE_SLICE Type var _STRING Type func init() { _BYTE_SLICE = Typeof([]byte(nil)) _STRING = Typeof("") } type MemoryBlock interface { ByteSlice() []byte } func SliceHeader(i interface{}) (Header *SliceHeader, Size, Align int) { value := NewValue(i) switch value := value.(type) { case *SliceValue: Header = (*SliceHeader)(unsafe.Pointer(value.UnsafeAddr())) t := value.Type().(*SliceType).Elem() Size = int(t.Size()) Align = t.Align() case nil: panic(i) case *InterfaceValue: Header, Size, Align = SliceHeader(value.Elem()) case *PtrValue: Header, Size, Align = SliceHeader(value.Elem()) } return } func Scale(oldHeader *SliceHeader, oldESize, newESize int) (h *SliceHeader) { if oldHeader != nil { s := float64(oldESize) / float64(newESize) h = &SliceHeader{ Data: oldHeader.Data } h.Len = int(float64(oldHeader.Len) * s) h.Cap = int(float64(oldHeader.Cap) * s) } return } func ByteSlice(i interface{}) []byte { switch b := i.(type) { case []byte: return b case MemoryBlock: return b.ByteSlice() case nil: return []byte{} } var header *SliceHeader value := NewValue(i) switch value := value.(type) { case nil: return []byte{} case Type: panic(i) case *MapValue: panic(i) case *ChanValue: panic(i) case *InterfaceValue: return ByteSlice(value.Elem()) case *PtrValue: if value := value.Elem(); value == nil { return ByteSlice(nil) } else { size := int(value.Type().Size()) header = &SliceHeader{ value.UnsafeAddr(), size, size } } case *SliceValue: h, s, _ := SliceHeader(i) header = Scale(h, s, 1) case *StringValue: s := value.Get() h := *(*StringHeader)(unsafe.Pointer(&s)) header = &SliceHeader{ h.Data, h.Len, h.Len } default: size := int(value.Type().Size()) header = &SliceHeader{ value.UnsafeAddr(), size, size } } return unsafe.Unreflect(_BYTE_SLICE, unsafe.Pointer(header)).([]byte) } concurrency goroutines concurrent threads of control each may be a function call or method call scheduled automatically by the runtime moved between threads as blocking occurs package main import "fmt" func main() { var c chan int c = make(chan int) go func() { for { fmt.Print(<-c) } }() for { select { case c <- 0: case c <- 1: } } } produces: 01100111010110... package main import "fmt" func main() { var c chan int c = make(chan int, 16) go func() { for { fmt.Print(<-c) } }() go func() { select { case c <- 0: case c <- 1: } }() for {} } produces: 01100111010110... package generalise import . "reflect" type SignalSource func(status chan bool) func (s SignalSource) Wait() { done := make(chan bool) defer close(done) go s(done) <-done } func (s SignalSource) WaitAll(count int) { done := make(chan bool) defer close(done) go s(done) for i := 0; i < count; i++ { <- done } } package generalise type Iteration func(k, x interface{}) func (i Iteration) apply(k, v interface{}, c chan bool) { go func() { i(k, v) c <- true }() } package generalise import . "reflect" func (f Iteration) Each(c interface{}) { switch c := NewValue(c).(type) { case *SliceValue: SignalSource(func(done chan bool) { for i := 0; i < c.Len(); i++ { f.apply(i, c.Elem(i).Interface(), done) } }).WaitAll(c.Len()) case *MapValue: SignalSource(func(done chan bool) { for _, k := range c.Keys() { f.apply(k, c.Elem(k).Interface(), done) } }).WaitAll(c.Len()) } } package generalise import . "reflect" type Results chan interface{} type Combination func(x, y interface{}) interface{} func (f Combination) Reduce(c, s interface{}) (r Results) { r = make(Results) go func() { Iteration(func(k, x interface{}) { s = f(s, x) }).Each(c) r <- s }() return } package generalise import . "reflect" type Transformation func(x interface{}) interface{} func (t Transformation) GetValue(x interface{}) Value { return NewValue(t(x)) } func (t Transformation) Map(c interface{}) interface{} { switch n := NewValue(Allocate(c)).(type) { case *SliceValue: SignalSource(func(done chan bool) { Iteration(func(k, x interface{}) { n.Elem(k.(int)).SetValue(t.GetValue(x)) }).Each(c) done <- true }).Wait() return n.Interface() case *MapValue: SignalSource(func(done chan bool) { Iteration(func(k, x interface{}) { n.SetElem(NewValue(k), t.GetValue(x)) }).Each(c) done <- true }).Wait() return n.Interface() } return Duplicate(c) } func (t Transformation) Map(c interface{}) interface{} { var i Iteration n := NewValue(Allocate(c)) switch n := n.(type) { case *SliceValue: i = Iteration(func(k, x interface{}) { n.Elem(k.(int)).SetValue(t.GetValue(x)) }) case *MapValue: i = Iteration(func(k, x interface{}) { n.SetElem(NewValue(k), t.GetValue(x)) }) } if i == nil { return Duplicate(c) } SignalSource(func(done chan bool) { i.Each(c) done <- true }).Wait() return n.Interface() } package main import “fmt” import . “generalise” func main() { m := "%v = %v, sum = %v\n" s := []int{0, 1, 2, 3, 4, 5} sum := func(x, y interface{}) interface{} { return x.(int) + y.(int) } d := Transformation( func(x interface{}) interface{} { return x.(int) * 2 } ).Map(s) x := <- Combination(sum).Reduce(s, 0) fmt.Printf("s", s, x.(int)) x = <- Combination(sum).Reduce(d, 0) fmt.Printf("d", d, x.(int)) } produces: s = [0 1 2 3 4 5], sum = 15 c = [0 2 4 6 8 10], sum = 30 integration include $(GOROOT)/src/Make.inc TARG=sqlite3 CGOFILES=\ sqlite3.go\ database.go ifeq ($(GOOS),darwin) CGO_LDFLAGS=/usr/lib/libsqlite3.0.dylib else CGO_LDFLAGS=-lsqlite3 endif include $(GOROOT)/src/Make.pkg package sqlite3 // #include <sqlite3.h> import "C" import "fmt" import "os" type Database struct { handle *C.sqlite3 Filename string Flags C.int } func (db *Database) Error() os.Error { return Errno(C.sqlite3_errcode(db.handle)) } const( OK = Errno(iota) ERROR CANTOPEN = Errno(14) ) var errText = map[Errno]string { ERROR: "SQL error or missing database", CANTOPEN: "Unable to open the database file", } type Errno int func (e Errno) String() (err string) { if err = errText[e]; err == "" { err = fmt.Sprintf("errno %v", int(e)) } return } func (db *Database) Open(flags... int) (e os.Error) { db.Flags = 0 for _, v := range flags { db.Flags = db.Flags | C.int(v) } f := C.CString(db.Filename) if err := Errno(C.sqlite3_open_v2(f, &db.handle, db.Flags, nil)); err != OK { e = err } else if db.handle == nil { e = CANTOPEN } return } func (db *Database) Close() { C.sqlite3_close(db.handle) db.handle = nil } func Open(filename string, flags... int) (db *Database, e os.Error) { defer func() { if x := recover(); x != nil { db.Close() db = nil e = ERROR } }() db = &Database{ Filename: filename } if len(flags) == 0 { e = db.Open( C.SQLITE_OPEN_FULLMUTEX, C.SQLITE_OPEN_READWRITE, C.SQLITE_OPEN_CREATE) } else { e = db.Open(flags...) } return } func (db *Database) Prepare(sql string, values... interface{}) (s *Statement, e os.Error) { s = &Statement{ db: db, timestamp: time.Nanoseconds() } rv := Errno(C.sqlite3_prepare_v2(db.handle, C.CString(sql), -1, &s.cptr, nil)) switch { case rv != OK: return nil, rv case len(values) > 0: e, _ = s.BindAll(values...) } return } func (db *Database) Execute(sql string, f... func(*Statement, ...interface{})) (c int, e os.Error) { var st *Statement switch st, e = db.Prepare(sql); e { case nil: c, e = st.All(f...) case OK: e = nil } return } software machines synchronisation package clock import "syscall" type Clock struct { Period int64 Count chan int64 Control chan bool active bool } package clock import "syscall" func (c *Clock) Start() { if !c.active { go func() { c.active = true for i := int64(0); ; i++ { select { case c.active = <- c.Control: 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") } OSX 10.6.2 Intel Atom 270 @ 1.6GHz: pulse value 0 from clock pulse value 1 from clock pulse value 2 from clock disabling clock restarting clock pulse value 106 from clock OSX 10.6.7 Intel Core 2 Duo @ 2.4GHz: pulse value 0 from clock pulse value 1 from clock pulse value 2 from clock disabling clock restarting clock pulse value 154 from clock memory package raw import . "reflect" type Slice struct { *SliceValue } func (s *Slice) Set(i int, value interface{}) { s.Elem(i).SetValue(NewValue(value)) } func (s *Slice) Overwrite(offset int, source interface{}) { switch source := source.(type) { case *Slice: s.Overwrite(offset, *source) case Slice: reflect.Copy(s.SliceValue.Slice(offset, s.Len()), source.SliceValue) default: switch v := NewValue(source).(type) { case *SliceValue: s.Overwrite(offset, Slice{v}) default: s.Set(offset, v.Interface()) } } } package main import . "fmt" import . "raw" func main() { report := "%v (%v) = %v of %v: %v\n" m := make([]int, 2) Printf(report, "m", "cells", len(m), cap(m), m) b := ByteSlice(m) Printf(report, "b", "bytes", len(b), cap(b), b) Overwrite(m, []byte{0, 0, 0, 1, 0, 0, 0, 1}) Printf(report, "m", "cells", len(m), cap(m), m) } produces: m (cells) = 2 of 2: [0 0] b (bytes) = 8 of 2: [0 0 0 0 0 0 0 0] n (cells) = 2 of 8: [16777216 16777216] subtleties von Neumann & Harvard architectures indirection bits byte-ordering instruction set package instructions import "fmt" type Operation func(o []int) type Executable interface { Opcode() int Operands() []int Execute(op Operation) } const INVALID_OPCODE = -1 type Program []Executable func (p Program) Disassemble(a Assembler) { for _, v := range p { fmt.Println(a.Disassemble(v)) } } package instructions type Instruction []int func (i Instruction) Opcode() int { if len(i) == 0 { return INVALID_OPCODE } return i[0] } func (i Instruction) Operands() []int { if len(i) < 2 { return []int{} } return i[1:] } func (i Instruction) Execute(op Operation) { op(i.Operands()) } package instructions type Assembler struct { opcodes map[string] int names map[int] string } func NewAssember(names... string) (a Assembler) { a = Assembler{ make(map[string] int), make(map[int] string) } a.Define(names...) return } func (a Assembler) Define(names... string) { for _, name := range names { a.opcodes[name] = len(a.names) a.names[len(a.names)] = name } } package instructions func (a Assembler) Assemble(name string, params... int) (i Instruction) { i = make(Instruction, len(params) + 1) switch opcode, ok := a.opcodes[name]; { case ok: i[0] = opcode default: i[0] = INVALID_OPCODE } copy(i[1:], params) return } package instructions import "fmt" func (a Assembler) Disassemble(e Executable) (s string) { if name, ok := a.names[e.Opcode()]; ok { s = name if params := e.Operands(); len(params) > 0 { s = fmt.Sprintf("%v\t%v", s, params[0]) for _, v := range params[1:] { s = fmt.Sprintf("%v, %v", s, v) } } } else { s = "unknown" } return } package main import . “instructions” func main() { a := NewAssembler("noop", "load", "store") p := Program{ a.Assemble("noop"), a.Assemble("load", 1), a.Assemble("store", 1, 2), a.Assemble("invalid", 3, 4, 5) } p.Disassemble(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 package processor import . "instructions" const const const const const const PROCESSOR_READY PROCESSOR_BUSY CALL_STACK_UNDERFLOW CALL_STACK_OVERFLOW ILLEGAL_OPERATION INVALID_ADDRESS = = = = = = 0 1 2 4 8 16 type Processor interface { Run(p []Executable) } type Core struct { Running bool PC, Flags, Ticks int CS, M []int OP Executable I chan Executable } "Loaded OpCode" "Interrupts" package processor import . "instructions" func NewCore(CSS, MSS int, I chan Executable) *Core { return &Core{ CS: make([]int, CSS)}, M: make([]int, MSS), I: I } } func (c *Core) Reset() { c.Running = false c.Flags = PROCESSOR_READY } func (c *Core) Goto(addr int) { c.PC = addr } package processor func (c *Core) Call(addr int) { top := len(c.CS) if top >= cap(c.CS) - 1 { panic(CALL_STACK_OVERFLOW) } c.CS = c.CS[:top + 1] c.CS[top] = c.PC c.PC = addr } func (c *Core) TailCall(addr int) { c.CS[len(c.CS) - 1] = c.PC c.PC = addr } func (c *Core) Return() { top := len(c.CS) top == 0 { panic(CALL_STACK_UNDERFLOW) } c.PC, c.CS = c.CS[top - 1], c.CS[:top] } package processor import . "instructions" func (c *Core) Run(p []Executable, dispatchers... func(c *Core)) { defer func() { c.Running = false if x := recover(); x != nil { c.Flags &= x.(int) } }() switch { case c.Running: panic(PROCESSOR_BUSY) case len(dispatchers) == 0: panic(PROCESSOR_READY) default: c.Running = true c.BusyLoop(dispatchers...) } } func (c *Core) LoadInstruction(program []Executable) { if c.PC >= len(program) { panic(PROCESSOR_READY) } c.Executable = program[c.PC] c.PC++ } package processor import . "instructions" func (c *Core) BusyLoop(p []Executable, dispatchers... func(c *Core)) { select { case interrupt <- c.I: op, pc := c.OP, c.PC for c.PC = 0; c.Running; c.Ticks++ { for _, f := range dispatchers { f(c) } c.LoadInstruction(p) } c.OP, c.PC = op, pc default: for c.PC = 0; c.Running; c.Ticks++ { c.LoadInstruction(p) for _, f := range dispatchers { f(c) } } } } package processor import . "instructions" func (c *Core) RunExclusive(p []Executable, tracers... func(c *Core)) { defer func() { c.Running = false if x := recover(); x != nil { c.Flags &= x.(int) } }() if c.Running { panic(PROCESSOR_BUSY) } case len(dispatchers) == 0: panic(PROCESSOR_READY) c.Running = true for c.PC = 0; c.Running; c.Ticks++ { c.LoadInstruction(p) for _, f := range tracers { f(c) } } } package main import . "processor" import . "instructions" const( CALL = iota GOTO MOVE RETURN ) c := NewCore(10, 8, nil) var dispatcher = func(c *Core) { switch c.Opcode() { case CALL: c.Execute(func(o []int) { c.Call(o[0]) }) case GOTO: c.Execute(func(o []int) { c.Goto(o[0]) }) case MOVE: c.Execute(func(o []int) { c.Goto(c.PC + o[0]) }) case RETURN: c.Execute(func(o []int) { c.Return() }) default: panic(ILLEGAL_OPERATION) } } func main() { p := []Executable{ Instruction{CALL, 2}, Instruction{GOTO, 5}, Instruction{MOVE, 2}, Instruction{RETURN}, Instruction{MOVE, -1} } c.RunExclusive(p, dispatcher) fmt.Printf("Instructions Executed: %v\nPC = %v\n", c.Ticks, c.PC) if c.Flags | PROCESSOR_READY == PROCESSOR_READY { fmt.Println("Core Ready") } else { fmt.Println("Core Error:", c.Flags) } } produces: Instructions Executed: 2 PC = 5 Core Ready accumulator machine 1-operand instructions data from memory combined with accumulator result stored in accumulator package accmachine import . "processor" const ( CONSTANT = iota LOAD_VALUE STORE_VALUE ADD ) type AccMachine struct { Core AC int } func NewAccMachine(CSSize, MSize int, I chan Executable) *AccMachine { return &AccMachine{ Core: NewCore(CSSize, MSize, I) } } package accmachine import . "processor" func (a *AccMachine) Run(program []Executable) { a.RunExclusive(program, func() { switch a.Opcode() { case CONSTANT: a.Execute(func(o []int) { a.AC = o[0] }) case LOAD_VALUE: a.Execute(func(o []int) { a.AC = a.M[o[0]] }) case STORE_VALUE: a.Execute(func(o []int) { a.M[o[0]] = a.AC }) case ADD: a.Execute(func(o []int) { a.AC += a.M[o[0]] }) default: panic(ILLEGAL_OPERATION) } }) } package main import . "accmachine" import . "instructions" func main() { a := NewAccMachine(10, 8, nil) p := []Executable{ Instruction{CONSTANT, 27}, Instruction{STORE_VALUE, 0}, Instruction{CONSTANT, 13}, Instruction{STORE_VALUE, 1}, Instruction{CONSTANT, 10}, Instruction{ADD, 1}, Instruction{ADD, 0}, Instruction{STORE_VALUE, 2} } a.Run(p) fmt.Println("accumulated value =", a.AC) } produces: accumulated value = 50 stack machine 0-operand instructions data popped from stack results pushed on stack package smachine import . "processor" const ( CONSTANT = iota PUSH_VALUE POP_VALUE ADD ) type StackMachine struct { Core DS []int } func NewStackM(CSSize, DSSize, MSize int, I chan Executable) *StackMachine { return &StackMachine{ DS: make([]int, 0, DSSize), Core: NewCore(CSSize, MSize, I) } } package smachine import . "processor" func (s *StackMachine) Push(v int) { top := len(s.DS) s.DS, s.DS[top] = s.DS[:top + 1], v } func (s *StackMachine) Pop(addr int) { top := len(s.DS) - 1 s.M[addr], s.DS = s.DS[top], s.DS[:top] } package smachine import . "processor" func (s *StackMachine) Run(program []Executable) { s.RunExclusive(program, func() { switch s.Opcode() { case CONSTANT: s.Execute(func(o []int) { s.Push(o[0]) }) case PUSH_VALUE: s.Execute(func(o []int) { s.Push(s.M[o[0]]) }) case POP_VALUE: s.Execute(func(o []int) { s.Pop(s.M[o[0]]) }) case ADD: s.Execute(func(o []int) { l := len(s.DS) s.DS[l - 2] += s.DS[l - 1] s.DS = s.DS[:l - 1] }) default: panic(ILLEGAL_OPERATION) } }) } package main import . "smachine" import . "instructions" func main() { s := NewStackM(10, 10, 8, nil) p := []Executable{ Instruction{CONSTANT, 27}, Instruction{CONSTANT, 13}, Instruction{CONSTANT, 10}, Instruction{ADD}, Instruction{ADD} } s.Run(p) fmt.Println("data stack =", s.DS) } produces: registers = [50 13 10 0 0 0 0 0 0 0] register machine multi-operand instructions data read from memory into registers operator combines registers and stores package rmachine import . "processor" const ( CONSTANT = iota LOAD_VALUE STORE_VALUE ADD ) type RMachine struct { Core R []int } func NewRMachine(CSSize, RSize, MSize int, I chan Executable) *RMachine { return &RMachine{ Core: NewCore(CSSize, MSize, I), R: make([]int, RSize) } } package rmachine import . "processor" func (r *RMachine) Run(program []Executable) { r.RunExclusive(program, func() { switch r.Opcode() { case CONSTANT: r.Execute(func(o []int) { r.R[o[0]] = o[1] }) case LOAD_VALUE: r.Execute(func(o []int) { r.R[o[0]] = r.M[o[1]] }) case STORE_VALUE: r.Execute(func(o []int) { r.M[o[0]] = r.R[o[1]] }) case ADD: r.Execute(func(o []int) { r.R[o[0]] += r.R[o[1]] }) default: panic(ILLEGAL_OPERATION) } }) } package main import . "rmachine" import . "instructions" func main() { r := NewRMachine(10, 10, 8, nil) p := []Executable{ Instruction{CONSTANT, 0, 27}, Instruction{CONSTANT, 1, 13}, Instruction{CONSTANT, 2, 10}, Instruction{ADD, 0, 1}, Instruction{ADD, 0, 2} } r.Run(p) fmt.Println("registers =", r.R) } produces: registers = [50 13 10 0 0 0 0 0 0 0] transport triggering register machine architecture exposes internal buses and components operations are side-effects of internal writes vector machine multi-operand instructions data vectors read from memory into registers operations combine registers package vmachine import . "processor" const ( CONSTANT = iota LOAD_VALUE STORE_VALUE ADD ) type VMachine struct { Core R [][]int } func NewVMachine(CSSize, RSize, MSize int, I chan Executable) *VMachine { return &VectorMachine{ Core: NewCore(CSSize, MSize), make([][]int, RSize) } } package vmachine import . "processor" func (v *VMachine) Load(r int, m []int) { v.R[r] = make([]int, len(m)) copy(v.R[r], m) } package vmachine import . "processor" func (v *VMachine) Run(program []Executable) { v.RunExclusive(program, func() { switch v.Opcode() { case CONSTANT: v.Execute(func(o []int) { v.Load(o[0], o[1:]) }) case STORE_VALUE: v.Execute(func(o []int) { copy(v.M[o[0]:], v.R[o[1]]) }) case LOAD_VALUE: v.Execute(func(o []int) { v.Load(o[0], v.M[o[1]:o[1] + o[2]]) }) case ADD: v.Execute(func(o []int) { a, b := v.R[o[0]], v.R[o[1]] count := len(a) if len(b) < len(a) { count := len(b) } for ; count > 0; count-- { a[i] += b[i] } }) default: panic(ILLEGAL_OPERATION) } }) } package main import . "vmachine" import . "instructions" func main() { r := NewVMachine(10, 10, 8, nil) p := []Executable{ Instruction{CONSTANT, 0, 27}, Instruction{CONSTANT, 1, 13}, Instruction{CONSTANT, 2, 10}, Instruction{ADD, 0, 1}, Instruction{ADD, 0, 2} } r.Run(p) fmt.Println("registers =", r.R) } produces: vectors = [[50 50 50] [13 10 27] [10 27 13] [] [] [] [] [] [] []] hypercube n-dimensional multi-operand instructions data matrices read from memory into registers operations combine registers superscalar multiple execution units processor caching out-of-order execution close to the machine transport buses peripheral drivers hardware acceleration finding out more http://golang.org/ http://feyeleanor.tel/ http://golightly.games-with-brains.net/ http://github.com/feyeleanor/ twitter://#golightly wikipedia or google