golightly - building language runtimes with google go

advertisement
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
Download