diff --git a/bridge.go b/bridge.go index 16e86d7..8d13abd 100644 --- a/bridge.go +++ b/bridge.go @@ -3,8 +3,8 @@ package wasm import ( "encoding/binary" "fmt" - "log" "math" + "reflect" "sync" "syscall" "unsafe" @@ -76,6 +76,13 @@ func BridgeFromFile(name, file string, imports *wasmer.Imports) (*Bridge, error) } func (b *Bridge) addValues() { + var goObj *object + goObj = propObject("jsGo", map[string]interface{}{ + "_makeFuncWrapper": wasmFunc(func(args []interface{}) (interface{}, error) { + return &funcWrapper{id: args[0]}, nil + }), + "_pendingEvent": nil, + }) b.values = []interface{}{ math.NaN(), float64(0), @@ -84,8 +91,8 @@ func (b *Bridge) addValues() { false, &object{ props: map[string]interface{}{ - "Object": &object{name: "Object", props: map[string]interface{}{}}, - "Array": &object{name: "Array", props: map[string]interface{}{}}, + "Object": propObject("Object", nil), + "Array": propObject("Array", nil), "Int8Array": typedArray("Int8Array"), "Int16Array": typedArray("Int16Array"), "Int32Array": typedArray("Int32Array"), @@ -94,49 +101,67 @@ func (b *Bridge) addValues() { "Uint32Array": typedArray("Uint32Array"), "Float32Array": typedArray("Float32Array"), "Float64Array": typedArray("Float64Array"), - "process": &object{name: "process", props: map[string]interface{}{}}, - "fs": &object{name: "fs", props: map[string]interface{}{ - "constants": &object{name: "constants", props: map[string]interface{}{ + "process": propObject("process", nil), + "fs": propObject("fs", map[string]interface{}{ + "constants": propObject("constants", map[string]interface{}{ "O_WRONLY": syscall.O_WRONLY, "O_RDWR": syscall.O_RDWR, "O_CREAT": syscall.O_CREAT, "O_TRUNC": syscall.O_TRUNC, "O_APPEND": syscall.O_APPEND, "O_EXCL": syscall.O_EXCL, - }}, - }}, + }), + + "write": wasmFunc(func(args []interface{}) (interface{}, error) { + fd := int(args[0].(float64)) + offset := int(args[2].(float64)) + length := int(args[3].(float64)) + buf := args[1].(*array).data()[offset : offset+length] + pos := args[4] + callback := args[5].(*funcWrapper) + var err error + var n int + if pos != nil { + position := int64(pos.(float64)) + n, err = syscall.Pwrite(fd, buf, position) + } else { + n, err = syscall.Write(fd, buf) + } + + if err != nil { + return nil, err + } + + return b.makeFuncWrapper(callback.id, goObj, &[]interface{}{nil, n}) + }), + }), }, }, //global - &object{ - name: "mem", - props: map[string]interface{}{ - "buffer": &arrayBuffer{data: b.mem()}, - }, - }, - &object{name: "jsGo", props: map[string]interface{}{}}, // jsGo + propObject("mem", map[string]interface{}{ + "buffer": &buffer{data: b.mem()}}, + ), + goObj, // jsGo } } // Run start the wasm instance. -func (b *Bridge) Run() error { +func (b *Bridge) Run(init chan bool, done chan error) { defer b.instance.Close() run := b.instance.Exports["run"] - resume := b.instance.Exports["resume"] _, err := run(0, 0) if err != nil { - return err + init <- false + done <- err + return } + init <- true + // use channel from wasm exit for !b.vmExit { - _, err = resume() - if err != nil { - return err - } } - fmt.Printf("WASM exited with code: %v\n", b.exitCode) - return nil + done <- nil } func (b *Bridge) mem() []byte { @@ -147,22 +172,37 @@ func (b *Bridge) getSP() int32 { spFunc := b.instance.Exports["getsp"] val, err := spFunc() if err != nil { - log.Fatal("failed to get sp", err) + panic("failed to get sp") } return val.ToI32() } +func (b *Bridge) setUint8(offset int32, v uint8) { + mem := b.mem() + mem[offset] = byte(v) +} + func (b *Bridge) setInt64(offset int32, v int64) { mem := b.mem() binary.LittleEndian.PutUint64(mem[offset:], uint64(v)) } +func (b *Bridge) setInt32(offset int32, v int32) { + mem := b.mem() + binary.LittleEndian.PutUint32(mem[offset:], uint32(v)) +} + func (b *Bridge) getInt64(offset int32) int64 { mem := b.mem() return int64(binary.LittleEndian.Uint64(mem[offset:])) } +func (b *Bridge) getInt32(offset int32) int32 { + mem := b.mem() + return int32(binary.LittleEndian.Uint32(mem[offset:])) +} + func (b *Bridge) setUint32(offset int32, v uint32) { mem := b.mem() binary.LittleEndian.PutUint32(mem[offset:], v) @@ -209,25 +249,23 @@ func (b *Bridge) loadSliceOfValues(addr int32) []interface{} { arrLen := int(b.getInt64(addr + 8)) vals := make([]interface{}, arrLen, arrLen) for i := 0; i < int(arrLen); i++ { - _, vals[i] = b.loadValue(int32(arr + i*8)) + vals[i] = b.loadValue(int32(arr + i*8)) } return vals } -// TODO remove id once debugging is done -func (b *Bridge) loadValue(addr int32) (uint32, interface{}) { +func (b *Bridge) loadValue(addr int32) interface{} { f := b.getFloat64(addr) if f == 0 { - return 0, undefined + return undefined } if !math.IsNaN(f) { - return 0, f + return f } - id := b.getUint32(addr) - return id, b.values[id] + return b.values[b.getUint32(addr)] } func (b *Bridge) storeValue(addr int32, v interface{}) { @@ -284,13 +322,17 @@ func (b *Bridge) storeValue(addr int32, v interface{}) { } typeFlag := 0 - switch v.(type) { - case string: + rv := reflect.TypeOf(v) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + switch rv.Kind() { + case reflect.String: typeFlag = 1 - case *object, *arrayBuffer: //TODO symbol maybe? + case reflect.Struct, reflect.Slice: //TODO symbol maybe? typeFlag = 2 default: - log.Fatalf("unknown type: %T", v) + panic(fmt.Sprintf("unknown type: %T", v)) // TODO function } b.setUint32(addr+4, uint32(nanHead|typeFlag)) @@ -300,15 +342,73 @@ func (b *Bridge) storeValue(addr int32, v interface{}) { type object struct { name string // TODO for debugging props map[string]interface{} + new func(args []interface{}) interface{} } +func propObject(name string, prop map[string]interface{}) *object { + return &object{name: name, props: prop} +} + +type array struct { + buf *buffer + offset int + length int +} + +func (a *array) data() []byte { + return a.buf.data[a.offset : a.offset+a.length] +} func typedArray(name string) *object { return &object{ - name: name, - props: map[string]interface{}{}, + name: name, + new: func(args []interface{}) interface{} { + return &array{ + buf: args[0].(*buffer), + offset: int(args[1].(float64)), + length: int(args[2].(float64)), + } + }, } } -type arrayBuffer struct { +type buffer struct { data []byte } + +type wasmFunc func(args []interface{}) (interface{}, error) + +func (b *Bridge) resume() error { + res := b.instance.Exports["resume"] + _, err := res() + return err +} + +type funcWrapper struct { + id interface{} +} + +func (b *Bridge) makeFuncWrapper(id, this interface{}, args *[]interface{}) (interface{}, error) { + goObj := b.values[7].(*object) + event := propObject("_pendingEvent", map[string]interface{}{ + "id": id, + "this": nil, + "args": args, + }) + + goObj.props["_pendingEvent"] = event + err := b.resume() + if err != nil { + return nil, err + } + + return event.props["result"], nil +} + +func (b *Bridge) CallFunc(fn string, args *[]interface{}) (interface{}, error) { + fw, ok := b.values[5].(*object).props[fn] + if !ok { + return nil, fmt.Errorf("missing function: %v", fn) + } + + return b.makeFuncWrapper(fw.(*funcWrapper).id, b.values[7], args) +} diff --git a/imports.go b/imports.go index e12a2df..8c6d464 100644 --- a/imports.go +++ b/imports.go @@ -29,7 +29,8 @@ import "C" import ( "crypto/rand" "fmt" - "log" + "reflect" + "syscall" "time" "unsafe" @@ -38,7 +39,7 @@ import ( //export debug func debug(ctx unsafe.Pointer, sp int32) { - log.Println(sp) + fmt.Println(sp) } //export wexit @@ -50,7 +51,12 @@ func wexit(ctx unsafe.Pointer, sp int32) { //export wwrite func wwrite(ctx unsafe.Pointer, sp int32) { - log.Fatal("wasm write", sp) + b := getBridge(ctx) + fd := int(b.getInt64(sp + 8)) + p := int(b.getInt64(sp + 16)) + l := int(b.getInt32(sp + 24)) + syscall.Write(fd, b.mem()[p:p+l]) + fmt.Println("wasm write", fd, p, l) } //export nanotime @@ -61,17 +67,23 @@ func nanotime(ctx unsafe.Pointer, sp int32) { //export walltime func walltime(ctx unsafe.Pointer, sp int32) { - log.Fatal("wall time") + b := getBridge(ctx) + t := time.Now().Unix() + sec := t / 1000 + nanos := (t % 1000) * 1000000 + b.setInt64(sp+8, sec) + b.setInt32(sp+16, int32(nanos)) + } //export scheduleCallback func scheduleCallback(ctx unsafe.Pointer, sp int32) { - log.Fatal("schedule callback") + panic("schedule callback") } //export clearScheduledCallback func clearScheduledCallback(ctx unsafe.Pointer, sp int32) { - log.Fatal("clear scheduled callback") + panic("clear scheduled callback") } //export getRandomData @@ -80,24 +92,23 @@ func getRandomData(ctx unsafe.Pointer, sp int32) { _, err := rand.Read(s) // TODO how to pass error? if err != nil { - log.Fatal("failed: getRandomData", err) + panic("failed: getRandomData") } } //export stringVal func stringVal(ctx unsafe.Pointer, sp int32) { - log.Fatal("stringVal") + panic("stringVal") } //export valueGet func valueGet(ctx unsafe.Pointer, sp int32) { b := getBridge(ctx) str := b.loadString(sp + 16) - id, val := b.loadValue(sp + 8) + val := b.loadValue(sp + 8) sp = b.getSP() obj, ok := val.(*object) if !ok { - fmt.Println("valueGet", str, id) b.storeValue(sp+32, val) return } @@ -105,70 +116,123 @@ func valueGet(ctx unsafe.Pointer, sp int32) { res, ok := obj.props[str] if !ok { // TODO - log.Fatal("missing property", val, str) + panic(fmt.Sprintln("missing property", str, val)) } - fmt.Println("valueGet", str, id, obj.name) + fmt.Println("valueGet", str, obj.name) b.storeValue(sp+32, res) } //export valueSet func valueSet(ctx unsafe.Pointer, sp int32) { - str := getBridge(ctx).loadString(sp + 16) - log.Fatal("valueSet", str) + b := getBridge(ctx) + val := b.loadValue(sp + 8) + obj := val.(*object) + prop := b.loadString(sp + 16) + propVal := b.loadValue(sp + 32) + obj.props[prop] = propVal + fmt.Println("valueSet", obj, prop, propVal) } //export valueIndex func valueIndex(ctx unsafe.Pointer, sp int32) { - log.Fatal("valueIndex") + b := getBridge(ctx) + l := b.loadValue(sp + 8) + i := b.getInt64(sp + 16) + rv := reflect.ValueOf(l) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + + iv := rv.Index(int(i)) + b.storeValue(sp+24, iv.Interface()) + fmt.Println("valueIndex:", iv) } //export valueSetIndex func valueSetIndex(ctx unsafe.Pointer, sp int32) { - log.Fatal("valueSetIndex") + panic("valueSetIndex") } //export valueCall func valueCall(ctx unsafe.Pointer, sp int32) { - str := getBridge(ctx).loadString(sp + 16) - log.Fatal("valueCall", str) + b := getBridge(ctx) + v := b.loadValue(sp + 8) + str := b.loadString(sp + 16) + args := b.loadSliceOfValues(sp + 32) + fmt.Println("valueCall: ", v.(*object).name, str, args) + f, ok := v.(*object).props[str].(wasmFunc) + if !ok { + panic(fmt.Sprintln("valueCall: prop not found in ", v, str)) + } + + sp = b.getSP() + res, err := f(args) + if err != nil { + b.storeValue(sp+56, err.Error()) + b.setUint8(sp+64, 0) + return + } + + b.storeValue(sp+56, res) + b.setUint8(sp+64, 1) } //export valueInvoke func valueInvoke(ctx unsafe.Pointer, sp int32) { - log.Fatal("valueInvoke") + panic("valueInvoke") } //export valueNew func valueNew(ctx unsafe.Pointer, sp int32) { b := getBridge(ctx) - id, val := b.loadValue(sp + 8) + val := b.loadValue(sp + 8) args := b.loadSliceOfValues(sp + 16) - log.Fatal("valueNew ", id, val, args) + res := val.(*object).new(args) + sp = b.getSP() + b.storeValue(sp+40, res) + b.setUint8(sp+48, 1) + fmt.Println("valueNew ", val, args) } //export valueLength func valueLength(ctx unsafe.Pointer, sp int32) { - log.Fatal("valueLength") + b := getBridge(ctx) + val := b.loadValue(sp + 8) + rv := reflect.ValueOf(val) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + b.setInt64(sp+16, int64(rv.Len())) + fmt.Println("valueLength:", rv.Len()) } //export valuePrepareString func valuePrepareString(ctx unsafe.Pointer, sp int32) { - log.Fatal("valuePrepareString") + b := getBridge(ctx) + val := b.loadValue(sp + 8) + var str string + if val != nil { + str = val.(string) + } + + b.storeValue(sp+16, str) + b.setInt64(sp+24, int64(len(str))) + fmt.Println("valuePrepareString", val, str) } //export valueLoadString func valueLoadString(ctx unsafe.Pointer, sp int32) { - log.Fatal("valueLoadString") + panic("valueLoadString") } //export scheduleTimeoutEvent func scheduleTimeoutEvent(ctx unsafe.Pointer, sp int32) { - log.Fatal("scheduleTimeoutEvent") + panic("scheduleTimeoutEvent") } //export clearTimeoutEvent func clearTimeoutEvent(ctx unsafe.Pointer, sp int32) { - log.Fatal("clearTimeoutEvent") + panic("clearTimeoutEvent") } // addImports adds go Bridge imports in "go" namespace. diff --git a/simple/caller/main.go b/simple/caller/main.go index 621d786..6ca6b2e 100644 --- a/simple/caller/main.go +++ b/simple/caller/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" wasmgo "github.com/vedhavyas/wasm" @@ -12,7 +13,11 @@ func main() { log.Fatal(err) } - if err := b.Run(); err != nil { - log.Fatal(err) - } + init, done := make(chan bool), make(chan error) + go b.Run(init, done) + <-init + res, err := b.CallFunc("printWasm", &[]interface{}{"success call"}) + fmt.Println(res, err) + err = <-done + fmt.Println("wasm exited", err) } diff --git a/simple/prog/main.go b/simple/prog/main.go index 90077cd..57fd7d9 100644 --- a/simple/prog/main.go +++ b/simple/prog/main.go @@ -7,17 +7,19 @@ import ( "syscall/js" ) +// TODO: log seems to cause an issue func printWasm(this js.Value, v []js.Value) interface{} { fmt.Println("Hello from WASM", v) return nil } func main() { - c := make(chan struct{}, 0) - fmt.Println("WASM Go Initialized") + ch := make(chan bool) + //fmt.Println("WASM Go Initialized") // register functions - js.Global().Set("printWasm", js.FuncOf(printWasm)) - fmt.Println("Done...") - <-c + fun := js.FuncOf(printWasm) + js.Global().Set("printWasm", fun) + //fmt.Println("Done...") + <-ch } diff --git a/simple/prog/main.wasm b/simple/prog/main.wasm index 805cb83..6960b85 100755 Binary files a/simple/prog/main.wasm and b/simple/prog/main.wasm differ