Successfully reported this slideshow.

GoLightly: Building VM-Based Language Runtimes with Google Go

4,219 views

Published on

Expanded and updated version of my Strange Loop 2010 presentation on how to program in Google's Go language with an emphasis on the kind of tricks you might want to perform when implementing a language runtime.

Published in: Technology
  • Be the first to comment

GoLightly: Building VM-Based Language Runtimes with Google Go

  1. 1. GoLightlyBuilding VM-based language runtimes in Go Eleanor McHugh http://slides.games-with-brains.net/
  2. 2. portrait of an artist...physics major http:/ feyeleembedded controllers /software reliabilitydynamic languagesnetwork scaling or.tel anquestionable taste in music Elean or McHugh
  3. 3. Languages Project ? fluid-dynamics simulation ? cockpit autopilot controller ? paramilitary cockpit C4I ? broadcast automation ? encrypted RDBM ? Unix kernel scripting
  4. 4. Language Project ICON fluid-dynamics simulation ASM cockpit autopilot controller VB5 paramilitary cockpit C4IVB5, ASM, K&R C broadcast automation RUBY, DNS encrypted RDBM RUBY Unix kernel scripting
  5. 5. softwarea tutorial on Google Go
  6. 6. wizardrysimulating machines in software
  7. 7. wild romance golightly
  8. 8. Elric sent his mind into twisting tunnels of logic,across endless plains of ideas, through mountains of symbolism and endless universes of alternatetruths; 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
  9. 9. golightlyagnostic heterogenous virtualisation networks
  10. 10. go... a systems language by googleproductivity, performance, concurrency lighter than Java, safer than C
  11. 11. ...lightly clean abstractions geared to performancenon-viral open source license
  12. 12. inspiration processor design sensor and control networksfield-programmable gate arrays
  13. 13. perspiration iterative empirical developmentexplore -> implement -> test -> benchmark evolve towards elegance
  14. 14. principles decoupling improves scalability coherence simplifies organisationoptimisations are application specific
  15. 15. agnostic no blessed programming languages flexible platform abstractionswrite once, run everywhere it matters
  16. 16. heterogeneous a system comprises many componentscomponents may differ in purpose and design but they cooperate to solve problems
  17. 17. virtualisation design discrete Turing machinesimplement these machines in software compile programs to run on them
  18. 18. networks machines cooperate by sending messages machine states can be serialised as messagesmessages transcend process and host boundaries
  19. 19. caveat lectordanger! we’re entering strange territoryour map is missing major landmarksand will be riddled with inaccuraciesso please tread carefullytry not to disturb the local wildlifeand don’t be put off by the pages of code
  20. 20. go
  21. 21. an elegant languagea statically-typed compiled languageobject-orientedstatic type declarationdynamic type inferencegarbage collectionconcurrency via communication (CSP)
  22. 22. hello world
  23. 23. package mainimport “fmt”const( HELLO string = “hello” WORLD string = “world”)func main() { fmt.Println(HELLO, WORLD)}
  24. 24. objects
  25. 25. boolean, numeric, array value structure, interfacereference pointer, slice, string, map, channelfunction function, method, closure
  26. 26. underlying method type set expressed type
  27. 27. underlying method type set expressed type embedded types
  28. 28. user-defined type
  29. 29. package Integertype Int intfunc (i *Int) Add(x int) { *i += Int(x)}
  30. 30. package Integer func (b Buffer) Clone() Buffer { s := make(Buffer, len(b))type Buffer []Int copy(s, b) return sfunc (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]}
  31. 31. package mainimport “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 }
  32. 32. testing
  33. 33. include $(GOROOT)/src/Make.incTARG=integerGOFILES=integer.goinclude $(GOROOT)/src/Make.pkg
  34. 34. package Integer func (b Buffer) Swap(i, j int) { b[i], b[j] = b[j], b[i]type Int int }func (i *Int) Add(x int) { *i += Int(x) func (b Buffer) Clone() Buffer {} s := make(Buffer, len(b)) copy(s, b)type Buffer []Int return sfunc (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)} }
  35. 35. package Integer func TestAdd(t *testing.T) {import “testing” i := Buffer{0, 1, 2, 3, 4, 5} b := i.Clone()func TestSwap(t *testing.T) { b[0].Add(3) i := Buffer{0, 1, 2, 3, 4, 5} if b[0] != i[0] + 3 { b := i.Clone() t.Fatalf("b[0:5] = %v", b) 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) }}
  36. 36. type embedding
  37. 37. package Vectorimport . “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]}
  38. 38. include $(GOROOT)/src/Make.incTARG=integerGOFILES= integer.go vector.goinclude $(GOROOT)/src/Make.pkg
  39. 39. package Integerimport “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) }}
  40. 40. benchmarking
  41. 41. package integerimport "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) }}
  42. 42. $ gotest -bench="Benchmark"rm -f _test/scripts.a6g -o _gotest_.6 integer.go vector.go nominal_typing_test.goembedded_typing_benchmark_test.go embedded_typing_test.gorm -f _test/scripts.agopack grc _test/scripts.a _gotest_.6PASSinteger.BenchmarkVectorSwap! 200000000! 8 ns/opinteger.BenchmarkVectorClone6! 10000000! 300 ns/op
  43. 43. dynamism & reflection
  44. 44. package addertype Adder interface { Add(j int) Subtract(j int) Result() interface{}}type Calculator interface { Adder Reset()}type AddingMachine struct { Memory interface{} Adder}
  45. 45. package addertype IAdder []intfunc (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)}
  46. 46. package adderimport "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)) }}
  47. 47. package addertype FAdder []float32func (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)}
  48. 48. package adderimport "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)) }}
  49. 49. package adderimport "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) } }}
  50. 50. package generaliseimport "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}
  51. 51. package generaliseimport . "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) }}
  52. 52. package generaliseimport . "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}
  53. 53. package generaliseimport "testing"func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() return}
  54. 54. 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") }}
  55. 55. 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 %vn", 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 %vn", 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) }}
  56. 56. 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 %vn", m) case throwsPanic(func() { SwapSlices(s2, 0, 3, 3) }): t.Fatalf("Unable to swap slices %vn", 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) } } }}
  57. 57. mutability
  58. 58. package rawimport . "reflect"import "unsafe"var _BYTE_SLICE! Typevar _STRING! ! Type !func init() { _BYTE_SLICE = Typeof([]byte(nil)) _STRING = Typeof("")}type MemoryBlock interface { ByteSlice() []byte}
  59. 59. 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}
  60. 60. 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}
  61. 61. 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 } }
  62. 62. 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)}
  63. 63. concurrency
  64. 64. goroutinesconcurrent threads of controleach may be a function call or method call
  65. 65. package mainimport "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...
  66. 66. package mainimport "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...
  67. 67. package generaliseimport . "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 }}
  68. 68. package generalisetype Iteration func(k, x interface{})func (i Iteration) apply(k, v interface{}, c chan bool) { go func() { i(k, v) c <- true }()}
  69. 69. package generaliseimport . "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()) }}
  70. 70. package generaliseimport . "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}
  71. 71. package generaliseimport . "reflect"type Transformation func(x interface{}) interface{}func (t Transformation) GetValue(x interface{}) Value { return NewValue(t(x))}
  72. 72. 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)}
  73. 73. 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()}
  74. 74. package mainimport “fmt”import . “generalise”func main() { m := "%v = %v, sum = %vn" 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
  75. 75. integration
  76. 76. include $(GOROOT)/src/Make.incTARG=sqlite3CGOFILES= sqlite3.go database.goifeq ($(GOOS),darwin)CGO_LDFLAGS=/usr/lib/libsqlite3.0.dylibelseCGO_LDFLAGS=-lsqlite3endifinclude $(GOROOT)/src/Make.pkg
  77. 77. 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))}
  78. 78. 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 intfunc (e Errno) String() (err string) { if err = errText[e]; err == "" { err = fmt.Sprintf("errno %v", int(e)) } return}
  79. 79. 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}
  80. 80. 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}
  81. 81. 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}
  82. 82. software machines
  83. 83. synchronisation
  84. 84. package clockimport "syscall"type Clock struct { Period! ! ! int64 Count! ! ! chan int64 Control!! ! chan bool active! ! ! bool}
  85. 85. package clockimport "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) } } }() }}
  86. 86. package mainimport . “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")}
  87. 87. 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 clockOSX 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
  88. 88. memory
  89. 89. package rawimport . "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())! ! ! ! ! } }}
  90. 90. package mainimport . "fmt"import . "raw"func main() { report := "%v (%v) = %v of %v: %vn" 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]
  91. 91. subtletiesvon Neumann & Harvard architectures indirection bits byte-ordering
  92. 92. instruction set
  93. 93. package instructionsimport "fmt"type Operation func(o []int)type Executable interface { Opcode() int Operands() []int Execute(op Operation)}const INVALID_OPCODE = -1type Program []Executablefunc (p Program) Disassemble(a Assembler) { for _, v := range p { fmt.Println(a.Disassemble(v)) }}
  94. 94. package instructionstype Instruction []intfunc (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())}
  95. 95. package instructionstype 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 }}
  96. 96. package instructionsfunc (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}
  97. 97. package instructionsimport "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("%vt%v", s, params[0]) for _, v := range params[1:] { s = fmt.Sprintf("%v, %v", s, v) } } } else { s = "unknown" } return}
  98. 98. package mainimport . “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]) } }}
  99. 99. produces: noop load! ! 1 store!1, 2 unknown op = 2 result = 3
  100. 100. CISC semantically rich instructionscomplex memory addressing modes compact binary code
  101. 101. RISCseparate IO and data processingregister-to-register instructions load/store memory access
  102. 102. VLIW multiple operations per instructioncompiler statically determines parallelism simplifies control logic
  103. 103. processor core
  104. 104. package processorimport . "instructions"const PROCESSOR_READY! ! ! ! = 0const PROCESSOR_BUSY! ! ! ! = 1const CALL_STACK_UNDERFLOW! = 2const CALL_STACK_OVERFLOW! ! = 4const ILLEGAL_OPERATION! ! ! = 8const INVALID_ADDRESS!! ! ! = 16type Processor interface {! Run(p []Executable)}type Core struct { Running! ! ! ! bool PC, Flags, Ticks! int CS, M! ! ! ! []int OP! ! ! ! ! Executable! ! ! "Loaded OpCode" I! ! ! ! ! chan Executable! ! "Interrupts"}
  105. 105. package processorimport . "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}
  106. 106. package processorfunc (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]}
  107. 107. package processorimport . "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++}
  108. 108. package processorimport . "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) } } }}
  109. 109. package processorimport . "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) } }}
  110. 110. package mainimport . "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) }}
  111. 111. 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: %vnPC = %vn", 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
  112. 112. accumulator machine 1-operand instructionsdata from memory combined with accumulator result stored in accumulator
  113. 113. package accmachineimport . "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) }}
  114. 114. package accmachineimport . "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) } })}
  115. 115. package mainimport . "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
  116. 116. stack machine 0-operand instructionsdata popped from stackresults pushed on stack
  117. 117. package smachineimport . "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)!}}
  118. 118. package smachineimport . "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]}
  119. 119. package smachineimport . "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) } })}
  120. 120. package mainimport . "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]
  121. 121. register machine multi-operand instructionsdata read from memory into registersoperator combines registers and stores
  122. 122. package rmachineimport . "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)! }}
  123. 123. package rmachineimport . "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) } })}
  124. 124. package mainimport . "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]
  125. 125. transport triggering register machine architecture exposes internal buses and componentsoperations are side-effects of internal writes
  126. 126. vector machine multi-operand instructionsdata vectors read from memory into registers operations combine registers
  127. 127. package vmachineimport . "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)! ! }}
  128. 128. package vmachineimport . "processor"func (v *VMachine) Load(r int, m []int) {! v.R[r] = make([]int, len(m))! copy(v.R[r], m)}
  129. 129. package vmachineimport . "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) } })}
  130. 130. package mainimport . "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] [] [] [] [] [] [] []]
  131. 131. hypercube n-dimensional multi-operand instructionsdata matrices read from memory into registers operations combine registers
  132. 132. superscalarmultiple execution units processor cachingout-of-order execution
  133. 133. close to the machine transport buses peripheral drivers hardware acceleration
  134. 134. finding out morehttp://golang.org/http://feyeleanor.tel/http://golightly.games-with-brains.net/http://github.com/feyeleanor/twitter://#golightlywikipedia or google

×