diff --git a/bridge.go b/bridge.go index ed549c7..e6fc555 100644 --- a/bridge.go +++ b/bridge.go @@ -18,8 +18,6 @@ import ( "github.com/wasmerio/go-ext-wasm/wasmer" ) -const release_call = "_release_" - var ( undefined = &struct{}{} bridges = map[string]*Bridge{} @@ -60,30 +58,6 @@ type Bridge struct { cancF context.CancelFunc } -// releaseRef removes the ref from the refs. -// Returns the idx and true if remove was successful. -func (b *Bridge) releaseRef(v interface{}) (int, bool) { - idx, ok := b.refs[v] - if !ok { - return 0, false - } - - delete(b.refs, v) - return idx, true -} - -// releaseVal removes val from the valueMap -// Returns the value and true if remove was successful -func (b *Bridge) releaseVal(idx int) (interface{}, bool) { - v, ok := b.valueMap[idx] - if !ok { - return nil, false - } - - delete(b.valueMap, idx) - return v, true -} - func BridgeFromBytes(name string, bytes []byte, imports *wasmer.Imports) (*Bridge, error) { b := new(Bridge) if imports == nil { @@ -142,16 +116,9 @@ func (b *Bridge) addValues() { "Object": &object{name: "Object", new: func(args []interface{}) interface{} { return &object{name: "ObjectInner", props: map[string]interface{}{}} }}, - "Array": propObject("Array", nil), - "Int8Array": typedArray, - "Int16Array": typedArray, - "Int32Array": typedArray, - "Uint8Array": typedArray, - "Uint16Array": typedArray, - "Uint32Array": typedArray, - "Float32Array": typedArray, - "Float64Array": typedArray, - "process": propObject("process", nil), + "Array": propObject("Array", nil), + "Uint8Array": typedArray, + "process": propObject("process", nil), "Date": &object{name: "Date", new: func(args []interface{}) interface{} { t := time.Now() return &object{name: "DateInner", props: map[string]interface{}{ @@ -168,7 +135,7 @@ func (b *Bridge) addValues() { "crypto": propObject("crypto", map[string]interface{}{ "getRandomValues": Func(func(args []interface{}) (interface{}, error) { arr := args[0].(*array) - return rand.Read(arr.data()) + return rand.Read(arr.buf) }), }), "AbortController": &object{name: "AbortController", new: func(args []interface{}) interface{} { @@ -189,7 +156,7 @@ func (b *Bridge) addValues() { return obj }}, "fetch": Func(func(args []interface{}) (interface{}, error) { - // TODO(ved): implement fetch + // Fixme(ved): implement fetch log.Fatalln(args) return nil, nil }), @@ -207,7 +174,7 @@ func (b *Bridge) addValues() { fd := int(args[0].(float64)) offset := int(args[2].(float64)) length := int(args[3].(float64)) - buf := args[1].(*array).data()[offset : offset+length] + buf := args[1].(*array).buf[offset : offset+length] pos := args[4] callback := args[5].(*funcWrapper) var err error @@ -228,10 +195,7 @@ func (b *Bridge) addValues() { }), }, }, //global - 6: propObject("mem", map[string]interface{}{ - "buffer": &buffer{data: b.mem()}}, - ), - 7: goObj, // jsGo + 6: goObj, // jsGo } } @@ -421,35 +385,34 @@ func (b *Bridge) storeValue(addr int32, v interface{}) { return } - rv := reflect.TypeOf(v) - if !rv.Comparable() { - panic(fmt.Sprintf("%T is not comparable", v)) + rt := reflect.TypeOf(v) + if rt.Kind() == reflect.Ptr { + rt = rt.Elem() } - if rv.Kind() == reflect.Ptr { - rv = rv.Elem() + rv := v + if !rt.Comparable() { + // since some types like Func cant be set as key, we will use their reflect value + // as key to insert for refs[key] so that we can avoid any duplicates + rv = reflect.ValueOf(v) } - ref, ok := b.refs[v] + ref, ok := b.refs[rv] if !ok { b.valuesMu.RLock() b.valueMap[b.valueIDX] = v ref = b.valueIDX - b.refs[v] = ref + b.refs[rv] = ref b.valueIDX++ b.valuesMu.RUnlock() } typeFlag := 0 - switch rv.Kind() { + switch rt.Kind() { case reflect.String: typeFlag = 1 - case reflect.Struct, reflect.Slice: - typeFlag = 2 case reflect.Func: typeFlag = 3 - default: - panic(fmt.Sprintf("unknown type: %T", v)) } b.setUint32(addr+4, uint32(nanHead|typeFlag)) b.setUint32(addr, uint32(ref)) @@ -466,30 +429,20 @@ func propObject(name string, prop map[string]interface{}) *object { } type array struct { - buf *buffer - offset int - length int -} - -func (a *array) data() []byte { - return a.buf.data[a.offset : a.offset+a.length] + buf []byte } var typedArray = &object{ name: "TypedArray", new: func(args []interface{}) interface{} { + l := int(args[0].(float64)) return &array{ - buf: args[0].(*buffer), - offset: int(args[1].(float64)), - length: int(args[2].(float64)), + buf: make([]byte, l, l), } }, } -type buffer struct { - data []byte -} - +// TODO make this a wrapper that takes an inner `this` js object type Func func(args []interface{}) (interface{}, error) func (b *Bridge) resume() error { @@ -503,9 +456,7 @@ type funcWrapper struct { } func (b *Bridge) makeFuncWrapper(id, this interface{}, args *[]interface{}) (interface{}, error) { - b.valuesMu.RLock() - goObj := b.valueMap[7].(*object) - b.valuesMu.RUnlock() + goObj := this.(*object) event := propObject("_pendingEvent", map[string]interface{}{ "id": id, "this": goObj, @@ -528,10 +479,8 @@ func (b *Bridge) CallFunc(fn string, args []interface{}) (interface{}, error) { if !ok { return nil, fmt.Errorf("missing function: %v", fn) } - - this := b.valueMap[7] + this := b.valueMap[6] b.valuesMu.RUnlock() - return b.makeFuncWrapper(fw.(*funcWrapper).id, this, &args) } @@ -542,28 +491,13 @@ func (b *Bridge) SetFunc(fname string, fn Func) error { return nil } -func (b *Bridge) releaseFunc(v interface{}) Func { - return Func(func(args []interface{}) (interface{}, error) { - b.valuesMu.Lock() - defer b.valuesMu.Unlock() - - idx, ok := b.releaseRef(v) - if !ok { - return nil, nil - } - - b.releaseVal(idx) - return nil, nil - }) -} - func Bytes(v interface{}) ([]byte, error) { arr, ok := v.(*array) if !ok { return nil, fmt.Errorf("got %T instead of bytes", v) } - return arr.data(), nil + return arr.buf, nil } func String(v interface{}) (string, error) { @@ -575,7 +509,7 @@ func String(v interface{}) (string, error) { return str, nil } -func Error(v interface{}) (errVal error, err error) { +func Error(v interface{}) (errVal, err error) { str, ok := v.(string) if !ok { return nil, fmt.Errorf("got %T instead of error", v) @@ -584,16 +518,8 @@ func Error(v interface{}) (errVal error, err error) { return errors.New(str), nil } -func UintArray(v interface{}) *[]uint { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Slice { - panic("not a slice") - } - - buf := make([]uint, rv.Len(), rv.Len()) - for i := 0; i < rv.Len(); i++ { - buf[i] = uint(rv.Index(i).Uint()) - } - - return &buf +func FromBytes(v []byte) interface{} { + buf := make([]byte, len(v), len(v)) + copy(buf, v) + return &array{buf: buf} } diff --git a/examples/function-caller/main.go b/examples/function-caller/main.go index 3769496..9f801d8 100644 --- a/examples/function-caller/main.go +++ b/examples/function-caller/main.go @@ -60,7 +60,7 @@ func main() { } log.Println(bytes) - res, err = b.CallFunc("bytes", []interface{}{wasm.UintArray(bytes)}) + res, err = b.CallFunc("bytes", []interface{}{wasm.FromBytes(bytes)}) if err != nil { panic(err) } diff --git a/examples/function-wasm/main.go b/examples/function-wasm/main.go index 3060131..0be6bdb 100644 --- a/examples/function-wasm/main.go +++ b/examples/function-wasm/main.go @@ -7,8 +7,6 @@ import ( "errors" "log" "syscall/js" - - "github.com/vedhavyas/go-wasm/go-converts" ) func addition(this js.Value, args []js.Value) interface{} { @@ -27,7 +25,10 @@ func getBytes(this js.Value, args []js.Value) interface{} { if err != nil { panic(err) } - return js.TypedArrayOf(r) + + v := js.Global().Get("Uint8Array").New(len(r)) + js.CopyBytesToJS(v, r) + return v } func getError(this js.Value, args []js.Value) interface{} { @@ -37,8 +38,11 @@ func getError(this js.Value, args []js.Value) interface{} { func receiveSendBytes(this js.Value, args []js.Value) interface{} { b := args[0] - buf := converts.ToBytes(b) - return js.TypedArrayOf(buf) + buf := make([]byte, b.Length(), b.Length()) + js.CopyBytesToGo(buf, b) + v := js.Global().Get("Uint8Array").New(len(buf)) + js.CopyBytesToJS(v, buf) + return v } func main() { diff --git a/examples/function-wasm/main.wasm b/examples/function-wasm/main.wasm index b05d196..270327c 100755 Binary files a/examples/function-wasm/main.wasm and b/examples/function-wasm/main.wasm differ diff --git a/examples/http-wasm/main.wasm b/examples/http-wasm/main.wasm index 7ed70a1..fcde89b 100755 Binary files a/examples/http-wasm/main.wasm and b/examples/http-wasm/main.wasm differ diff --git a/go-converts/wasm.go b/go-converts/wasm.go deleted file mode 100644 index 73697fc..0000000 --- a/go-converts/wasm.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build js,wasm - -package converts - -import "syscall/js" - -func ToBytes(v js.Value) []byte { - buf := make([]byte, v.Length(), v.Length()) - for i := 0; i < v.Length(); i++ { - sv := v.Index(i) - buf[i] = byte(sv.Int()) - } - - return buf -} - -// Free frees the value for GC -func free(v js.Value) { - v.Call("_release_") -} diff --git a/imports.go b/imports.go index 9b77c70..1fe750c 100644 --- a/imports.go +++ b/imports.go @@ -24,6 +24,8 @@ extern void valuePrepareString(void *context, int32_t a); extern void valueLoadString(void *context, int32_t a); extern void scheduleTimeoutEvent(void *context, int32_t a); extern void clearTimeoutEvent(void *context, int32_t a); +extern void copyBytesToGo (void *context, int32_t a); +extern void copyBytesToJS (void *context, int32_t a); */ import "C" import ( @@ -156,18 +158,11 @@ func valueSetIndex(_ unsafe.Pointer, _ int32) { func valueCall(ctx unsafe.Pointer, sp int32) { b := getBridge(ctx) v := b.loadValue(sp + 8) - var f Func - var args []interface{} str := b.loadString(sp + 16) - if str == release_call { - f = b.releaseFunc(v) - } else { - args = b.loadSliceOfValues(sp + 32) - var ok bool - f, ok = v.(*object).props[str].(Func) - if !ok { - panic(fmt.Sprintln("valueCall: prop not found in ", v, str)) - } + args := b.loadSliceOfValues(sp + 32) + f, ok := v.(*object).props[str].(Func) + if !ok { + panic(fmt.Sprintf("valueCall: prop not found in %v, %s", v.(*object).name, str)) } sp = b.getSP() res, err := f(args) @@ -217,7 +212,17 @@ func valueLength(ctx unsafe.Pointer, sp int32) { if rv.Kind() == reflect.Ptr { rv = rv.Elem() } - b.setInt64(sp+16, int64(rv.Len())) + var l int + switch { + case rv.Kind() == reflect.Slice: + l = rv.Len() + case rv.Type() == reflect.TypeOf(array{}): + l = len(val.(*array).buf) + default: + panic(fmt.Sprintf("valueLength on %T", val)) + } + + b.setInt64(sp+16, int64(l)) } //export valuePrepareString @@ -251,6 +256,34 @@ func clearTimeoutEvent(_ unsafe.Pointer, _ int32) { panic("clearTimeoutEvent") } +//export copyBytesToJS +func copyBytesToJS(ctx unsafe.Pointer, sp int32) { + b := getBridge(ctx) + dst, ok := b.loadValue(sp + 8).(*array) + if !ok { + b.setUint8(sp+48, 0) + return + } + src := b.loadSlice(sp + 16) + n := copy(dst.buf, src[:len(dst.buf)]) + b.setInt64(sp+40, int64(n)) + b.setUint8(sp+48, 1) +} + +//export copyBytesToGo +func copyBytesToGo(ctx unsafe.Pointer, sp int32) { + b := getBridge(ctx) + dst := b.loadSlice(sp + 8) + src, ok := b.loadValue(sp + 32).(*array) + if !ok { + b.setUint8(sp+48, 0) + return + } + n := copy(dst, src.buf[:len(dst)]) + b.setInt64(sp+40, int64(n)) + b.setUint8(sp+48, 1) +} + // addImports adds go Bridge imports in "go" namespace. func (b *Bridge) addImports(imps *wasmer.Imports) error { imps = imps.Namespace("go") @@ -280,6 +313,8 @@ func (b *Bridge) addImports(imps *wasmer.Imports) error { {"syscall/js.valueLength", valueLength, C.valueLength}, {"syscall/js.valuePrepareString", valuePrepareString, C.valuePrepareString}, {"syscall/js.valueLoadString", valueLoadString, C.valueLoadString}, + {"syscall/js.copyBytesToGo", copyBytesToGo, C.copyBytesToGo}, + {"syscall/js.copyBytesToJS", copyBytesToJS, C.copyBytesToJS}, } var err error