github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/gojs/syscall.go (about)

     1  package gojs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/bananabytelabs/wazero/api"
     8  	"github.com/bananabytelabs/wazero/internal/gojs/custom"
     9  	"github.com/bananabytelabs/wazero/internal/gojs/goarch"
    10  	"github.com/bananabytelabs/wazero/internal/gojs/goos"
    11  	"github.com/bananabytelabs/wazero/sys"
    12  )
    13  
    14  // FinalizeRef implements js.finalizeRef, which is used as a
    15  // runtime.SetFinalizer on the given reference.
    16  //
    17  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L61
    18  var FinalizeRef = goos.NewFunc(custom.NameSyscallFinalizeRef, finalizeRef)
    19  
    20  func finalizeRef(ctx context.Context, _ api.Module, stack goos.Stack) {
    21  	r := stack.ParamRef(0)
    22  
    23  	id := uint32(r) // 32-bits of the ref are the ID
    24  
    25  	getState(ctx).values.Decrement(id)
    26  }
    27  
    28  // StringVal implements js.stringVal, which is used to load the string for
    29  // `js.ValueOf(x)`. For example, this is used when setting HTTP headers.
    30  //
    31  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L212
    32  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L305-L308
    33  var StringVal = goos.NewFunc(custom.NameSyscallStringVal, stringVal)
    34  
    35  func stringVal(ctx context.Context, mod api.Module, stack goos.Stack) {
    36  	x := stack.ParamString(mod.Memory(), 0)
    37  
    38  	r := storeValue(ctx, x)
    39  
    40  	stack.SetResultRef(0, r)
    41  }
    42  
    43  // ValueGet implements js.valueGet, which is used to load a js.Value property
    44  // by name, e.g. `v.Get("address")`. Notably, this is used by js.handleEvent to
    45  // get the pending event.
    46  //
    47  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L295
    48  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L311-L316
    49  var ValueGet = goos.NewFunc(custom.NameSyscallValueGet, valueGet)
    50  
    51  func valueGet(ctx context.Context, mod api.Module, stack goos.Stack) {
    52  	v := stack.ParamVal(ctx, 0, LoadValue)
    53  	p := stack.ParamString(mod.Memory(), 1 /*, 2 */)
    54  
    55  	var result interface{}
    56  	if g, ok := v.(goos.GetFunction); ok {
    57  		result = g.Get(p)
    58  	} else if e, ok := v.(error); ok {
    59  		switch p {
    60  		case "message": // js (GOOS=js) error, can be anything.
    61  			result = e.Error()
    62  		case "code": // syscall (GOARCH=wasm) error, must match key in mapJSError in fs_js.go
    63  			result = ToErrno(e).Error()
    64  		default:
    65  			panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p))
    66  		}
    67  	} else {
    68  		panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p))
    69  	}
    70  
    71  	r := storeValue(ctx, result)
    72  	stack.SetResultRef(0, r)
    73  }
    74  
    75  // ValueSet implements js.valueSet, which is used to store a js.Value property
    76  // by name, e.g. `v.Set("address", a)`. Notably, this is used by js.handleEvent
    77  // set the event result.
    78  //
    79  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L309
    80  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L318-L322
    81  var ValueSet = goos.NewFunc(custom.NameSyscallValueSet, valueSet)
    82  
    83  func valueSet(ctx context.Context, mod api.Module, stack goos.Stack) {
    84  	v := stack.ParamVal(ctx, 0, LoadValue)
    85  	p := stack.ParamString(mod.Memory(), 1 /*, 2 */)
    86  	x := stack.ParamVal(ctx, 3, LoadValue)
    87  
    88  	if p := p; v == getState(ctx) {
    89  		switch p {
    90  		case "_pendingEvent":
    91  			if x == nil { // syscall_js.handleEvent
    92  				s := v.(*State)
    93  				s._lastEvent = s._pendingEvent
    94  				s._pendingEvent = nil
    95  				return
    96  			}
    97  		}
    98  	} else if e, ok := v.(*event); ok { // syscall_js.handleEvent
    99  		switch p {
   100  		case "result":
   101  			e.result = x
   102  			return
   103  		}
   104  	} else if m, ok := v.(*object); ok {
   105  		m.properties[p] = x // e.g. opt.Set("method", req.Method)
   106  		return
   107  	}
   108  	panic(fmt.Errorf("TODO: valueSet(v=%v, p=%s, x=%v)", v, p, x))
   109  }
   110  
   111  // ValueDelete is stubbed as it isn't used in Go's main source tree.
   112  //
   113  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L321
   114  var ValueDelete = goarch.StubFunction(custom.NameSyscallValueDelete)
   115  
   116  // ValueIndex implements js.valueIndex, which is used to load a js.Value property
   117  // by index, e.g. `v.Index(0)`. Notably, this is used by js.handleEvent to read
   118  // event arguments
   119  //
   120  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L334
   121  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L331-L334
   122  var ValueIndex = goos.NewFunc(custom.NameSyscallValueIndex, valueIndex)
   123  
   124  func valueIndex(ctx context.Context, _ api.Module, stack goos.Stack) {
   125  	v := stack.ParamVal(ctx, 0, LoadValue)
   126  	i := stack.ParamUint32(1)
   127  
   128  	result := v.(*objectArray).slice[i]
   129  
   130  	r := storeValue(ctx, result)
   131  	stack.SetResultRef(0, r)
   132  }
   133  
   134  // ValueSetIndex is stubbed as it is only used for js.ValueOf when the input is
   135  // []interface{}, which doesn't appear to occur in Go's source tree.
   136  //
   137  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L348
   138  var ValueSetIndex = goarch.StubFunction(custom.NameSyscallValueSetIndex)
   139  
   140  // ValueCall implements js.valueCall, which is used to call a js.Value function
   141  // by name, e.g. `document.Call("createElement", "div")`.
   142  //
   143  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L394
   144  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L343-L358
   145  var ValueCall = goos.NewFunc(custom.NameSyscallValueCall, valueCall)
   146  
   147  func valueCall(ctx context.Context, mod api.Module, stack goos.Stack) {
   148  	mem := mod.Memory()
   149  	vRef := stack.ParamRef(0)
   150  	m := stack.ParamString(mem, 1 /*, 2 */)
   151  	args := stack.ParamVals(ctx, mem, 3 /*, 4 */, LoadValue)
   152  	// 5 = padding
   153  
   154  	v := LoadValue(ctx, vRef)
   155  	c, isCall := v.(jsCall)
   156  	if !isCall {
   157  		panic(fmt.Errorf("TODO: valueCall(v=%v, m=%s, args=%v)", v, m, args))
   158  	}
   159  
   160  	var res goos.Ref
   161  	var ok bool
   162  	if result, err := c.call(ctx, mod, vRef, m, args...); err != nil {
   163  		res = storeValue(ctx, err)
   164  	} else {
   165  		res = storeValue(ctx, result)
   166  		ok = true
   167  	}
   168  
   169  	stack.Refresh(mod)
   170  	stack.SetResultRef(0, res)
   171  	stack.SetResultBool(1, ok)
   172  }
   173  
   174  // ValueInvoke is stubbed as it isn't used in Go's main source tree.
   175  //
   176  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L413
   177  var ValueInvoke = goarch.StubFunction(custom.NameSyscallValueInvoke)
   178  
   179  // ValueNew implements js.valueNew, which is used to call a js.Value, e.g.
   180  // `array.New(2)`.
   181  //
   182  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L432
   183  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L378-L392
   184  var ValueNew = goos.NewFunc(custom.NameSyscallValueNew, valueNew)
   185  
   186  func valueNew(ctx context.Context, mod api.Module, stack goos.Stack) {
   187  	mem := mod.Memory()
   188  	vRef := stack.ParamRef(0)
   189  	args := stack.ParamVals(ctx, mem, 1 /*, 2 */, LoadValue)
   190  	// 3 = padding
   191  
   192  	var res goos.Ref
   193  	var ok bool
   194  	switch vRef {
   195  	case goos.RefArrayConstructor:
   196  		result := &objectArray{}
   197  		res = storeValue(ctx, result)
   198  		ok = true
   199  	case goos.RefUint8ArrayConstructor:
   200  		var result interface{}
   201  		a := args[0]
   202  		if n, ok := a.(float64); ok {
   203  			result = goos.WrapByteArray(make([]byte, uint32(n)))
   204  		} else if _, ok := a.(*goos.ByteArray); ok {
   205  			// In case of wrapping, increment the counter of the same ref.
   206  			//	uint8arrayWrapper := uint8Array.New(args[0])
   207  			result = stack.ParamRefs(mem, 1)[0]
   208  		} else {
   209  			panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", vRef, args))
   210  		}
   211  		res = storeValue(ctx, result)
   212  		ok = true
   213  	case goos.RefObjectConstructor:
   214  		result := &object{properties: map[string]interface{}{}}
   215  		res = storeValue(ctx, result)
   216  		ok = true
   217  	case goos.RefJsDateConstructor:
   218  		res = goos.RefJsDate
   219  		ok = true
   220  	default:
   221  		panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", vRef, args))
   222  	}
   223  
   224  	stack.Refresh(mod)
   225  	stack.SetResultRef(0, res)
   226  	stack.SetResultBool(1, ok)
   227  }
   228  
   229  // ValueLength implements js.valueLength, which is used to load the length
   230  // property of a value, e.g. `array.length`.
   231  //
   232  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L372
   233  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L395-L398
   234  var ValueLength = goos.NewFunc(custom.NameSyscallValueLength, valueLength)
   235  
   236  func valueLength(ctx context.Context, _ api.Module, stack goos.Stack) {
   237  	v := stack.ParamVal(ctx, 0, LoadValue)
   238  
   239  	len := len(v.(*objectArray).slice)
   240  
   241  	stack.SetResultUint32(0, uint32(len))
   242  }
   243  
   244  // ValuePrepareString implements js.valuePrepareString, which is used to load
   245  // the string for `o.String()` (via js.jsString) for string, boolean and
   246  // number types. Notably, http.Transport uses this in RoundTrip to coerce the
   247  // URL to a string.
   248  //
   249  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L531
   250  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L401-L406
   251  var ValuePrepareString = goos.NewFunc(custom.NameSyscallValuePrepareString, valuePrepareString)
   252  
   253  func valuePrepareString(ctx context.Context, _ api.Module, stack goos.Stack) {
   254  	v := stack.ParamVal(ctx, 0, LoadValue)
   255  
   256  	s := valueString(v)
   257  
   258  	sRef := storeValue(ctx, s)
   259  	sLen := uint32(len(s))
   260  
   261  	stack.SetResultRef(0, sRef)
   262  	stack.SetResultUint32(1, sLen)
   263  }
   264  
   265  // ValueLoadString implements js.valueLoadString, which is used copy a string
   266  // value for `o.String()`.
   267  //
   268  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L533
   269  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L409-L413
   270  var ValueLoadString = goos.NewFunc(custom.NameSyscallValueLoadString, valueLoadString)
   271  
   272  func valueLoadString(ctx context.Context, mod api.Module, stack goos.Stack) {
   273  	v := stack.ParamVal(ctx, 0, LoadValue)
   274  	b := stack.ParamBytes(mod.Memory(), 1 /*, 2 */)
   275  
   276  	s := valueString(v)
   277  	copy(b, s)
   278  }
   279  
   280  // ValueInstanceOf is stubbed as it isn't used in Go's main source tree.
   281  //
   282  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L543
   283  var ValueInstanceOf = goarch.StubFunction(custom.NameSyscallValueInstanceOf)
   284  
   285  // CopyBytesToGo copies a JavaScript managed byte array to linear memory.
   286  // For example, this is used to read an HTTP response body.
   287  //
   288  // # Results
   289  //
   290  //   - n is the count of bytes written.
   291  //   - ok is false if the src was not a uint8Array.
   292  //
   293  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L569
   294  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L437-L449
   295  var CopyBytesToGo = goos.NewFunc(custom.NameSyscallCopyBytesToGo, copyBytesToGo)
   296  
   297  func copyBytesToGo(ctx context.Context, mod api.Module, stack goos.Stack) {
   298  	dst := stack.ParamBytes(mod.Memory(), 0 /*, 1 */)
   299  	// padding = 2
   300  	src := stack.ParamVal(ctx, 3, LoadValue)
   301  
   302  	var n uint32
   303  	var ok bool
   304  	if src, isBuf := src.(*goos.ByteArray); isBuf {
   305  		n = uint32(copy(dst, src.Unwrap()))
   306  		ok = true
   307  	}
   308  
   309  	stack.SetResultUint32(0, n)
   310  	stack.SetResultBool(1, ok)
   311  }
   312  
   313  // CopyBytesToJS copies linear memory to a JavaScript managed byte array.
   314  // For example, this is used to read an HTTP request body.
   315  //
   316  // # Results
   317  //
   318  //   - n is the count of bytes written.
   319  //   - ok is false if the dst was not a uint8Array.
   320  //
   321  // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L583
   322  // and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L438-L448
   323  var CopyBytesToJS = goos.NewFunc(custom.NameSyscallCopyBytesToJS, copyBytesToJS)
   324  
   325  func copyBytesToJS(ctx context.Context, mod api.Module, stack goos.Stack) {
   326  	dst := stack.ParamVal(ctx, 0, LoadValue)
   327  	src := stack.ParamBytes(mod.Memory(), 1 /*, 2 */)
   328  	// padding = 3
   329  
   330  	var n uint32
   331  	var ok bool
   332  	if dst, isBuf := dst.(*goos.ByteArray); isBuf {
   333  		if dst != nil { // empty is possible on EOF
   334  			n = uint32(copy(dst.Unwrap(), src))
   335  		}
   336  		ok = true
   337  	}
   338  
   339  	stack.SetResultUint32(0, n)
   340  	stack.SetResultBool(1, ok)
   341  }
   342  
   343  // funcWrapper is the result of go's js.FuncOf ("_makeFuncWrapper" here).
   344  //
   345  // This ID is managed on the Go side an increments (possibly rolling over).
   346  type funcWrapper uint32
   347  
   348  // invoke implements jsFn
   349  func (f funcWrapper) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   350  	e := &event{id: uint32(f), this: args[0].(goos.Ref)}
   351  
   352  	if len(args) > 1 { // Ensure arguments are hashable.
   353  		e.args = &objectArray{args[1:]}
   354  		for i, v := range e.args.slice {
   355  			if s, ok := v.([]byte); ok {
   356  				args[i] = goos.WrapByteArray(s)
   357  			} else if s, ok := v.([]interface{}); ok {
   358  				args[i] = &objectArray{s}
   359  			} else if e, ok := v.(error); ok {
   360  				args[i] = e
   361  			}
   362  		}
   363  	}
   364  
   365  	getState(ctx)._pendingEvent = e // Note: _pendingEvent reference is cleared during resume!
   366  
   367  	if _, err := mod.ExportedFunction("resume").Call(ctx); err != nil {
   368  		if _, ok := err.(*sys.ExitError); ok {
   369  			return nil, nil // allow error-handling to unwind when wasm calls exit due to a panic
   370  		} else {
   371  			return nil, err
   372  		}
   373  	}
   374  
   375  	return e.result, nil
   376  }