github.com/tetratelabs/wazero@v1.2.1/internal/gojs/syscall.go (about)

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