go-wasm/bridge.go

528 lines
11 KiB
Go
Raw Permalink Normal View History

2019-08-07 13:47:59 +03:00
package wasm
import (
2019-08-22 05:24:07 +03:00
"context"
2019-08-22 20:05:05 +03:00
"crypto/rand"
2019-08-07 13:47:59 +03:00
"encoding/binary"
2019-08-22 20:05:05 +03:00
"errors"
2019-08-13 18:11:23 +03:00
"fmt"
2019-08-22 05:24:07 +03:00
"log"
2019-08-13 18:11:23 +03:00
"math"
2019-09-23 23:22:23 +03:00
"net/http"
2019-08-20 04:13:53 +03:00
"reflect"
2019-08-13 18:11:23 +03:00
"sync"
2019-08-15 21:12:20 +03:00
"syscall"
2019-08-20 23:47:40 +03:00
"time"
2019-08-13 18:11:23 +03:00
"unsafe"
2019-08-07 13:47:59 +03:00
"github.com/wasmerio/go-ext-wasm/wasmer"
)
2019-09-24 00:23:27 +03:00
var (
undefined = &struct{}{}
bridges = map[string]*Bridge{}
mu sync.RWMutex // to protect bridges
)
2019-08-22 05:24:07 +03:00
type bctx struct{ n string }
2019-08-13 18:11:23 +03:00
2020-01-04 15:17:26 +03:00
func getCtxData(b *Bridge) (*bctx, error) {
2019-08-13 18:11:23 +03:00
mu.Lock()
defer mu.Unlock()
2019-08-21 00:29:13 +03:00
if _, ok := bridges[b.name]; ok {
return nil, fmt.Errorf("bridge with name %s already exists", b.name)
}
2019-08-13 18:11:23 +03:00
bridges[b.name] = b
2020-01-04 15:17:26 +03:00
return &bctx{n: b.name}, nil
2019-08-13 18:11:23 +03:00
}
2019-08-07 13:47:59 +03:00
2019-08-13 18:11:23 +03:00
func getBridge(ctx unsafe.Pointer) *Bridge {
ictx := wasmer.IntoInstanceContext(ctx)
2020-01-04 15:17:26 +03:00
c := (ictx.Data()).(*bctx)
2019-08-13 18:11:23 +03:00
mu.RLock()
defer mu.RUnlock()
return bridges[c.n]
}
type Bridge struct {
name string
2019-08-07 13:47:59 +03:00
instance wasmer.Instance
exitCode int
2019-09-23 23:36:03 +03:00
valueIDX int
valueMap map[int]interface{}
2019-08-15 21:12:20 +03:00
refs map[interface{}]int
2019-09-23 23:36:03 +03:00
valuesMu sync.RWMutex
2019-08-21 00:09:38 +03:00
memory []byte
2019-08-21 00:29:13 +03:00
exited bool
2019-08-22 05:24:07 +03:00
cancF context.CancelFunc
2019-08-07 13:47:59 +03:00
}
2019-08-15 21:12:20 +03:00
func BridgeFromBytes(name string, bytes []byte, imports *wasmer.Imports) (*Bridge, error) {
b := new(Bridge)
2019-08-07 13:47:59 +03:00
if imports == nil {
imports = wasmer.NewImports()
}
2019-08-13 18:11:23 +03:00
b.name = name
err := b.addImports(imports)
2019-08-07 13:47:59 +03:00
if err != nil {
2019-08-15 21:12:20 +03:00
return nil, err
2019-08-07 13:47:59 +03:00
}
inst, err := wasmer.NewInstanceWithImports(bytes, imports)
if err != nil {
2019-08-15 21:12:20 +03:00
return nil, err
2019-08-07 13:47:59 +03:00
}
2019-08-21 00:29:13 +03:00
ctx, err := getCtxData(b)
if err != nil {
return nil, err
}
2019-08-07 13:47:59 +03:00
b.instance = inst
2019-08-21 00:29:13 +03:00
inst.SetContextData(ctx)
2019-08-15 21:12:20 +03:00
b.addValues()
b.refs = make(map[interface{}]int)
2019-09-23 23:36:03 +03:00
b.valueIDX = 8
2019-08-15 21:12:20 +03:00
return b, nil
2019-08-07 13:47:59 +03:00
}
2019-08-15 21:12:20 +03:00
func BridgeFromFile(name, file string, imports *wasmer.Imports) (*Bridge, error) {
2019-08-13 18:11:23 +03:00
bytes, err := wasmer.ReadBytes(file)
if err != nil {
2019-08-15 21:12:20 +03:00
return nil, err
2019-08-13 18:11:23 +03:00
}
2019-08-15 21:12:20 +03:00
return BridgeFromBytes(name, bytes, imports)
}
func (b *Bridge) addValues() {
2019-08-20 04:13:53 +03:00
var goObj *object
goObj = propObject("jsGo", map[string]interface{}{
2019-08-20 22:20:20 +03:00
"_makeFuncWrapper": Func(func(args []interface{}) (interface{}, error) {
2019-08-20 04:13:53 +03:00
return &funcWrapper{id: args[0]}, nil
}),
"_pendingEvent": nil,
})
2019-09-23 23:36:03 +03:00
b.valueMap = map[int]interface{}{
0: math.NaN(),
1: float64(0),
2: nil,
3: true,
4: false,
5: &object{
2019-08-15 21:12:20 +03:00
props: map[string]interface{}{
2019-09-23 23:22:23 +03:00
"Object": &object{name: "Object", new: func(args []interface{}) interface{} {
return &object{name: "ObjectInner", props: map[string]interface{}{}}
}},
2020-01-05 20:00:02 +03:00
"Array": arrayObject("Array"),
"Uint8Array": arrayObject("Uint8Array"),
2020-01-05 19:56:36 +03:00
"process": propObject("process", nil),
2019-08-20 23:47:40 +03:00
"Date": &object{name: "Date", new: func(args []interface{}) interface{} {
t := time.Now()
return &object{name: "DateInner", props: map[string]interface{}{
"time": t,
"getTimezoneOffset": Func(func(args []interface{}) (interface{}, error) {
_, offset := t.Zone()
// make it negative and return in minutes
offset = (offset / 60) * -1
return offset, nil
}),
}}
}},
2019-08-22 20:05:05 +03:00
"crypto": propObject("crypto", map[string]interface{}{
"getRandomValues": Func(func(args []interface{}) (interface{}, error) {
arr := args[0].(*array)
2020-01-05 19:56:36 +03:00
return rand.Read(arr.buf)
2019-08-22 20:05:05 +03:00
}),
}),
2019-09-23 23:22:23 +03:00
"AbortController": &object{name: "AbortController", new: func(args []interface{}) interface{} {
return &object{name: "AbortControllerInner", props: map[string]interface{}{
"signal": propObject("signal", map[string]interface{}{}),
}}
}},
"Headers": &object{name: "Headers", new: func(args []interface{}) interface{} {
headers := http.Header{}
obj := &object{name: "HeadersInner", props: map[string]interface{}{
"headers": headers,
"append": Func(func(args []interface{}) (interface{}, error) {
headers.Add(args[0].(string), args[1].(string))
return nil, nil
}),
}}
return obj
}},
"fetch": Func(func(args []interface{}) (interface{}, error) {
2020-01-05 19:56:36 +03:00
// Fixme(ved): implement fetch
2019-09-23 23:22:23 +03:00
log.Fatalln(args)
return nil, nil
}),
2019-08-20 04:13:53 +03:00
"fs": propObject("fs", map[string]interface{}{
"constants": propObject("constants", map[string]interface{}{
2019-08-15 21:12:20 +03:00
"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,
2019-08-20 04:13:53 +03:00
}),
2019-08-20 22:20:20 +03:00
"write": Func(func(args []interface{}) (interface{}, error) {
2019-08-20 04:13:53 +03:00
fd := int(args[0].(float64))
offset := int(args[2].(float64))
length := int(args[3].(float64))
2020-01-05 19:56:36 +03:00
buf := args[1].(*array).buf[offset : offset+length]
2019-08-20 04:13:53 +03:00
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})
}),
}),
2019-08-15 21:12:20 +03:00
},
2020-07-30 15:30:01 +03:00
}, // global
2020-01-05 19:56:36 +03:00
6: goObj, // jsGo
2019-08-15 21:12:20 +03:00
}
2019-08-13 18:11:23 +03:00
}
2019-08-21 00:29:13 +03:00
func (b *Bridge) check() {
if b.exited {
panic("WASM instance already exited")
}
}
2019-08-07 13:47:59 +03:00
// Run start the wasm instance.
2019-08-22 05:24:07 +03:00
func (b *Bridge) Run(ctx context.Context, init chan error) {
2019-08-21 00:29:13 +03:00
b.check()
2019-08-07 13:47:59 +03:00
defer b.instance.Close()
run := b.instance.Exports["run"]
_, err := run(0, 0)
if err != nil {
2019-08-20 04:35:19 +03:00
init <- err
2019-08-20 04:13:53 +03:00
return
2019-08-07 13:47:59 +03:00
}
2019-08-22 05:24:07 +03:00
ctx, cancF := context.WithCancel(ctx)
b.cancF = cancF
2019-08-20 04:35:19 +03:00
init <- nil
2019-08-22 05:24:07 +03:00
select {
case <-ctx.Done():
log.Printf("stopping WASM[%s] instance...\n", b.name)
b.exited = true
return
}
2019-08-07 13:47:59 +03:00
}
2019-08-13 18:11:23 +03:00
func (b *Bridge) mem() []byte {
2019-08-21 00:09:38 +03:00
if b.memory == nil {
b.memory = b.instance.Memory.Data()
}
return b.memory
2019-08-07 13:47:59 +03:00
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) getSP() int32 {
spFunc := b.instance.Exports["getsp"]
val, err := spFunc()
if err != nil {
2019-08-20 04:13:53 +03:00
panic("failed to get sp")
2019-08-15 21:12:20 +03:00
}
return val.ToI32()
}
2019-08-20 04:13:53 +03:00
func (b *Bridge) setUint8(offset int32, v uint8) {
mem := b.mem()
mem[offset] = byte(v)
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) setInt64(offset int32, v int64) {
2019-08-07 13:47:59 +03:00
mem := b.mem()
binary.LittleEndian.PutUint64(mem[offset:], uint64(v))
}
2019-08-20 04:13:53 +03:00
func (b *Bridge) setInt32(offset int32, v int32) {
mem := b.mem()
binary.LittleEndian.PutUint32(mem[offset:], uint32(v))
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) getInt64(offset int32) int64 {
mem := b.mem()
return int64(binary.LittleEndian.Uint64(mem[offset:]))
}
2019-08-20 04:13:53 +03:00
func (b *Bridge) getInt32(offset int32) int32 {
mem := b.mem()
return int32(binary.LittleEndian.Uint32(mem[offset:]))
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) setUint32(offset int32, v uint32) {
mem := b.mem()
binary.LittleEndian.PutUint32(mem[offset:], v)
}
func (b *Bridge) setUint64(offset int32, v uint64) {
2019-08-13 18:11:23 +03:00
mem := b.mem()
binary.LittleEndian.PutUint64(mem[offset:], v)
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) getUnit64(offset int32) uint64 {
2019-08-13 18:11:23 +03:00
mem := b.mem()
return binary.LittleEndian.Uint64(mem[offset+0:])
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) setFloat64(offset int32, v float64) {
2019-08-13 18:11:23 +03:00
uf := math.Float64bits(v)
b.setUint64(offset, uf)
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) getFloat64(offset int32) float64 {
2019-08-13 18:11:23 +03:00
uf := b.getUnit64(offset)
return math.Float64frombits(uf)
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) getUint32(offset int32) uint32 {
2019-08-13 18:11:23 +03:00
return binary.LittleEndian.Uint32(b.mem()[offset+0:])
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) loadSlice(addr int32) []byte {
2019-08-07 13:47:59 +03:00
mem := b.mem()
array := binary.LittleEndian.Uint64(mem[addr+0:])
length := binary.LittleEndian.Uint64(mem[addr+8:])
return mem[array : array+length]
}
2019-08-15 21:12:20 +03:00
func (b *Bridge) loadString(addr int32) string {
2019-08-07 13:47:59 +03:00
d := b.loadSlice(addr)
return string(d)
}
2019-08-13 18:11:23 +03:00
2019-08-15 21:12:20 +03:00
func (b *Bridge) loadSliceOfValues(addr int32) []interface{} {
arr := int(b.getInt64(addr + 0))
arrLen := int(b.getInt64(addr + 8))
vals := make([]interface{}, arrLen, arrLen)
for i := 0; i < int(arrLen); i++ {
2019-08-20 04:13:53 +03:00
vals[i] = b.loadValue(int32(arr + i*8))
2019-08-15 21:12:20 +03:00
}
return vals
}
2019-08-20 04:13:53 +03:00
func (b *Bridge) loadValue(addr int32) interface{} {
2019-08-13 18:11:23 +03:00
f := b.getFloat64(addr)
if f == 0 {
2019-08-20 04:13:53 +03:00
return undefined
2019-08-13 18:11:23 +03:00
}
if !math.IsNaN(f) {
2019-08-20 04:13:53 +03:00
return f
2019-08-13 18:11:23 +03:00
}
2019-08-22 05:24:07 +03:00
b.valuesMu.RLock()
defer b.valuesMu.RUnlock()
2019-09-23 23:36:03 +03:00
return b.valueMap[int(b.getUint32(addr))]
2019-08-15 21:12:20 +03:00
}
func (b *Bridge) storeValue(addr int32, v interface{}) {
const nanHead = 0x7FF80000
if i, ok := v.(int); ok {
v = float64(i)
}
if i, ok := v.(uint); ok {
v = float64(i)
}
if v, ok := v.(float64); ok {
if math.IsNaN(v) {
b.setUint32(addr+4, nanHead)
b.setUint32(addr, 0)
return
}
if v == 0 {
b.setUint32(addr+4, nanHead)
b.setUint32(addr, 1)
return
}
b.setFloat64(addr, v)
return
}
switch v {
case undefined:
b.setFloat64(addr, 0)
return
case nil:
b.setUint32(addr+4, nanHead)
b.setUint32(addr, 2)
return
case true:
b.setUint32(addr+4, nanHead)
b.setUint32(addr, 3)
return
case false:
b.setUint32(addr+4, nanHead)
b.setUint32(addr, 4)
return
}
2020-01-05 19:56:36 +03:00
rt := reflect.TypeOf(v)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
2019-08-23 03:24:00 +03:00
}
2020-01-05 19:56:36 +03:00
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)
2019-08-20 22:20:20 +03:00
}
2020-01-05 19:56:36 +03:00
ref, ok := b.refs[rv]
2019-08-15 21:12:20 +03:00
if !ok {
2019-08-22 05:24:07 +03:00
b.valuesMu.RLock()
2019-09-23 23:36:03 +03:00
b.valueMap[b.valueIDX] = v
ref = b.valueIDX
2020-01-05 19:56:36 +03:00
b.refs[rv] = ref
2019-09-23 23:36:03 +03:00
b.valueIDX++
2019-08-22 05:24:07 +03:00
b.valuesMu.RUnlock()
2019-08-15 21:12:20 +03:00
}
typeFlag := 0
2020-01-05 19:56:36 +03:00
switch rt.Kind() {
2019-08-20 04:13:53 +03:00
case reflect.String:
2019-08-15 21:12:20 +03:00
typeFlag = 1
2019-08-20 22:20:20 +03:00
case reflect.Func:
typeFlag = 3
2019-08-15 21:12:20 +03:00
}
b.setUint32(addr+4, uint32(nanHead|typeFlag))
b.setUint32(addr, uint32(ref))
}
type object struct {
2019-08-21 00:29:13 +03:00
name string // for debugging
2019-08-15 21:12:20 +03:00
props map[string]interface{}
2019-08-20 04:13:53 +03:00
new func(args []interface{}) interface{}
}
func propObject(name string, prop map[string]interface{}) *object {
return &object{name: name, props: prop}
}
type array struct {
2020-01-05 19:56:36 +03:00
buf []byte
2019-08-20 04:13:53 +03:00
}
2019-08-23 03:24:00 +03:00
2020-01-05 20:00:02 +03:00
func arrayObject(name string) *object {
return &object{
name: name,
new: func(args []interface{}) interface{} {
l := int(args[0].(float64))
return &array{
buf: make([]byte, l, l),
}
},
}
2019-08-15 21:12:20 +03:00
}
2020-01-05 19:56:36 +03:00
// TODO make this a wrapper that takes an inner `this` js object
2019-08-20 22:20:20 +03:00
type Func func(args []interface{}) (interface{}, error)
2019-08-20 04:13:53 +03:00
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) {
2020-01-05 19:56:36 +03:00
goObj := this.(*object)
2019-08-20 04:13:53 +03:00
event := propObject("_pendingEvent", map[string]interface{}{
"id": id,
2019-08-22 20:05:05 +03:00
"this": goObj,
2019-08-20 04:13:53 +03:00
"args": args,
})
goObj.props["_pendingEvent"] = event
err := b.resume()
if err != nil {
return nil, err
}
return event.props["result"], nil
}
2019-08-20 22:20:20 +03:00
func (b *Bridge) CallFunc(fn string, args []interface{}) (interface{}, error) {
2019-08-21 00:29:13 +03:00
b.check()
2019-08-22 05:24:07 +03:00
b.valuesMu.RLock()
2019-09-23 23:36:03 +03:00
fw, ok := b.valueMap[5].(*object).props[fn]
2019-08-20 04:13:53 +03:00
if !ok {
return nil, fmt.Errorf("missing function: %v", fn)
}
2020-01-05 19:56:36 +03:00
this := b.valueMap[6]
2019-08-22 05:24:07 +03:00
b.valuesMu.RUnlock()
return b.makeFuncWrapper(fw.(*funcWrapper).id, this, &args)
2019-08-20 22:20:20 +03:00
}
func (b *Bridge) SetFunc(fname string, fn Func) error {
2019-08-22 05:24:07 +03:00
b.valuesMu.RLock()
defer b.valuesMu.RUnlock()
2019-09-23 23:36:03 +03:00
b.valueMap[5].(*object).props[fname] = &fn
2019-08-20 22:20:20 +03:00
return nil
2019-08-20 04:13:53 +03:00
}
2019-08-22 20:05:05 +03:00
func Bytes(v interface{}) ([]byte, error) {
arr, ok := v.(*array)
if !ok {
return nil, fmt.Errorf("got %T instead of bytes", v)
}
2020-01-05 19:56:36 +03:00
return arr.buf, nil
2019-08-22 20:05:05 +03:00
}
2019-09-23 23:22:23 +03:00
func String(v interface{}) (string, error) {
str, ok := v.(string)
if !ok {
return "", fmt.Errorf("got %t instead of string", v)
}
return str, nil
}
2020-01-05 19:56:36 +03:00
func Error(v interface{}) (errVal, err error) {
2019-08-22 20:05:05 +03:00
str, ok := v.(string)
if !ok {
return nil, fmt.Errorf("got %T instead of error", v)
}
return errors.New(str), nil
}
2019-08-23 20:19:32 +03:00
2020-01-05 19:56:36 +03:00
func FromBytes(v []byte) interface{} {
buf := make([]byte, len(v), len(v))
copy(buf, v)
return &array{buf: buf}
2019-08-23 20:19:32 +03:00
}