wa-lang.org/wazero@v1.0.2/internal/gojs/syscall.go (about)

     1  package gojs
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/fs"
     9  	"net/http"
    10  	"syscall"
    11  
    12  	"wa-lang.org/wazero/api"
    13  	"wa-lang.org/wazero/internal/gojs/spfunc"
    14  	"wa-lang.org/wazero/internal/wasm"
    15  	"wa-lang.org/wazero/sys"
    16  )
    17  
    18  const (
    19  	functionFinalizeRef        = "syscall/js.finalizeRef"
    20  	functionStringVal          = "syscall/js.stringVal"
    21  	functionValueGet           = "syscall/js.valueGet"
    22  	functionValueSet           = "syscall/js.valueSet"
    23  	functionValueDelete        = "syscall/js.valueDelete" // stubbed
    24  	functionValueIndex         = "syscall/js.valueIndex"
    25  	functionValueSetIndex      = "syscall/js.valueSetIndex" // stubbed
    26  	functionValueCall          = "syscall/js.valueCall"
    27  	functionValueInvoke        = "syscall/js.valueInvoke" // stubbed
    28  	functionValueNew           = "syscall/js.valueNew"
    29  	functionValueLength        = "syscall/js.valueLength"
    30  	functionValuePrepareString = "syscall/js.valuePrepareString"
    31  	functionValueLoadString    = "syscall/js.valueLoadString"
    32  	functionValueInstanceOf    = "syscall/js.valueInstanceOf" // stubbed
    33  	functionCopyBytesToGo      = "syscall/js.copyBytesToGo"
    34  	functionCopyBytesToJS      = "syscall/js.copyBytesToJS"
    35  )
    36  
    37  // FinalizeRef implements js.finalizeRef, which is used as a
    38  // runtime.SetFinalizer on the given reference.
    39  //
    40  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L61
    41  var FinalizeRef = spfunc.MustCallFromSP(false, &wasm.HostFunc{
    42  	ExportNames: []string{functionFinalizeRef},
    43  	Name:        functionFinalizeRef,
    44  	ParamTypes:  []api.ValueType{i32},
    45  	ParamNames:  []string{"r"},
    46  	Code: &wasm.Code{
    47  		IsHostFunction: true,
    48  		GoFunc:         api.GoFunc(finalizeRef),
    49  	},
    50  })
    51  
    52  func finalizeRef(ctx context.Context, stack []uint64) {
    53  	id := uint32(stack[0]) // 32-bits of the ref are the ID
    54  
    55  	getState(ctx).values.decrement(id)
    56  }
    57  
    58  // StringVal implements js.stringVal, which is used to load the string for
    59  // `js.ValueOf(x)`. For example, this is used when setting HTTP headers.
    60  //
    61  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L212
    62  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L305-L308
    63  var StringVal = spfunc.MustCallFromSP(false, &wasm.HostFunc{
    64  	ExportNames: []string{functionStringVal},
    65  	Name:        functionStringVal,
    66  	ParamTypes:  []api.ValueType{i32, i32},
    67  	ParamNames:  []string{"xAddr", "xLen"},
    68  	ResultTypes: []api.ValueType{i64},
    69  	Code: &wasm.Code{
    70  		IsHostFunction: true,
    71  		GoFunc:         api.GoModuleFunc(stringVal),
    72  	},
    73  })
    74  
    75  func stringVal(ctx context.Context, mod api.Module, stack []uint64) {
    76  	xAddr, xLen := uint32(stack[0]), uint32(stack[1])
    77  
    78  	x := string(mustRead(ctx, mod.Memory(), "x", xAddr, xLen))
    79  
    80  	stack[0] = storeRef(ctx, x)
    81  }
    82  
    83  // ValueGet implements js.valueGet, which is used to load a js.Value property
    84  // by name, e.g. `v.Get("address")`. Notably, this is used by js.handleEvent to
    85  // get the pending event.
    86  //
    87  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L295
    88  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L311-L316
    89  var ValueGet = spfunc.MustCallFromSP(false, &wasm.HostFunc{
    90  	ExportNames: []string{functionValueGet},
    91  	Name:        functionValueGet,
    92  	ParamTypes:  []api.ValueType{i64, i32, i32},
    93  	ParamNames:  []string{"v", "pAddr", "pLen"},
    94  	ResultTypes: []api.ValueType{i64},
    95  	Code: &wasm.Code{
    96  		IsHostFunction: true,
    97  		GoFunc:         api.GoModuleFunc(valueGet),
    98  	},
    99  })
   100  
   101  func valueGet(ctx context.Context, mod api.Module, stack []uint64) {
   102  	vRef := stack[0]
   103  	pAddr := uint32(stack[1])
   104  	pLen := uint32(stack[2])
   105  
   106  	p := string(mustRead(ctx, mod.Memory(), "p", pAddr, pLen))
   107  	v := loadValue(ctx, ref(vRef))
   108  
   109  	var result interface{}
   110  	if g, ok := v.(jsGet); ok {
   111  		result = g.get(ctx, p)
   112  	} else if e, ok := v.(error); ok {
   113  		switch p {
   114  		case "message": // js (GOOS=js) error, can be anything.
   115  			result = e.Error()
   116  		case "code": // syscall (GOARCH=wasm) error, must match key in mapJSError in fs_js.go
   117  			result = mapJSError(e).Error()
   118  		default:
   119  			panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p))
   120  		}
   121  	} else {
   122  		panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p))
   123  	}
   124  
   125  	stack[0] = storeRef(ctx, result)
   126  }
   127  
   128  // ValueSet implements js.valueSet, which is used to store a js.Value property
   129  // by name, e.g. `v.Set("address", a)`. Notably, this is used by js.handleEvent
   130  // set the event result.
   131  //
   132  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L309
   133  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L318-L322
   134  var ValueSet = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   135  	ExportNames: []string{functionValueSet},
   136  	Name:        functionValueSet,
   137  	ParamTypes:  []api.ValueType{i64, i32, i32, i64},
   138  	ParamNames:  []string{"v", "pAddr", "pLen", "x"},
   139  	Code: &wasm.Code{
   140  		IsHostFunction: true,
   141  		GoFunc:         api.GoModuleFunc(valueSet),
   142  	},
   143  })
   144  
   145  func valueSet(ctx context.Context, mod api.Module, stack []uint64) {
   146  	vRef := stack[0]
   147  	pAddr := uint32(stack[1])
   148  	pLen := uint32(stack[2])
   149  	xRef := stack[3]
   150  
   151  	v := loadValue(ctx, ref(vRef))
   152  	p := string(mustRead(ctx, mod.Memory(), "p", pAddr, pLen))
   153  	x := loadValue(ctx, ref(xRef))
   154  	if v == getState(ctx) {
   155  		switch p {
   156  		case "_pendingEvent":
   157  			if x == nil { // syscall_js.handleEvent
   158  				v.(*state)._pendingEvent = nil
   159  				return
   160  			}
   161  		}
   162  	} else if e, ok := v.(*event); ok { // syscall_js.handleEvent
   163  		switch p {
   164  		case "result":
   165  			e.result = x
   166  			return
   167  		}
   168  	} else if m, ok := v.(*object); ok {
   169  		m.properties[p] = x // e.g. opt.Set("method", req.Method)
   170  		return
   171  	}
   172  	panic(fmt.Errorf("TODO: valueSet(v=%v, p=%s, x=%v)", v, p, x))
   173  }
   174  
   175  // ValueDelete is stubbed as it isn't used in Go's main source tree.
   176  //
   177  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L321
   178  var ValueDelete = stubFunction(functionValueDelete)
   179  
   180  // ValueIndex implements js.valueIndex, which is used to load a js.Value property
   181  // by index, e.g. `v.Index(0)`. Notably, this is used by js.handleEvent to read
   182  // event arguments
   183  //
   184  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L334
   185  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L331-L334
   186  var ValueIndex = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   187  	ExportNames: []string{functionValueIndex},
   188  	Name:        functionValueIndex,
   189  	ParamTypes:  []api.ValueType{i64, i32},
   190  	ParamNames:  []string{"v", "i"},
   191  	ResultTypes: []api.ValueType{i64},
   192  	Code: &wasm.Code{
   193  		IsHostFunction: true,
   194  		GoFunc:         api.GoFunc(valueIndex),
   195  	},
   196  })
   197  
   198  func valueIndex(ctx context.Context, stack []uint64) {
   199  	vRef := stack[0]
   200  	i := uint32(stack[1])
   201  
   202  	v := loadValue(ctx, ref(vRef))
   203  	result := v.(*objectArray).slice[i]
   204  
   205  	stack[0] = storeRef(ctx, result)
   206  }
   207  
   208  // ValueSetIndex is stubbed as it is only used for js.ValueOf when the input is
   209  // []interface{}, which doesn't appear to occur in Go's source tree.
   210  //
   211  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L348
   212  var ValueSetIndex = stubFunction(functionValueSetIndex)
   213  
   214  // ValueCall implements js.valueCall, which is used to call a js.Value function
   215  // by name, e.g. `document.Call("createElement", "div")`.
   216  //
   217  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L394
   218  //
   219  //	https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L343-L358
   220  var ValueCall = spfunc.MustCallFromSP(true, &wasm.HostFunc{
   221  	ExportNames: []string{functionValueCall},
   222  	Name:        functionValueCall,
   223  	ParamTypes:  []api.ValueType{i64, i32, i32, i32, i32},
   224  	ParamNames:  []string{"v", "mAddr", "mLen", "argsArray", "argsLen"},
   225  	ResultTypes: []api.ValueType{i64, i32, i32},
   226  	Code: &wasm.Code{
   227  		IsHostFunction: true,
   228  		GoFunc:         api.GoModuleFunc(valueCall),
   229  	},
   230  })
   231  
   232  func valueCall(ctx context.Context, mod api.Module, stack []uint64) {
   233  	vRef := stack[0]
   234  	mAddr := uint32(stack[1])
   235  	mLen := uint32(stack[2])
   236  	argsArray := uint32(stack[3])
   237  	argsLen := uint32(stack[4])
   238  
   239  	this := ref(vRef)
   240  	v := loadValue(ctx, this)
   241  	m := string(mustRead(ctx, mod.Memory(), "m", mAddr, mLen))
   242  	args := loadArgs(ctx, mod, argsArray, argsLen)
   243  
   244  	var xRef uint64
   245  	var ok, sp uint32
   246  	if c, isCall := v.(jsCall); !isCall {
   247  		panic(fmt.Errorf("TODO: valueCall(v=%v, m=%v, args=%v)", v, m, args))
   248  	} else if result, err := c.call(ctx, mod, this, m, args...); err != nil {
   249  		xRef = storeRef(ctx, err)
   250  		ok = 0
   251  	} else {
   252  		xRef = storeRef(ctx, result)
   253  		ok = 1
   254  	}
   255  
   256  	sp = refreshSP(mod)
   257  	stack[0], stack[1], stack[2] = xRef, uint64(ok), uint64(sp)
   258  }
   259  
   260  // ValueInvoke is stubbed as it isn't used in Go's main source tree.
   261  //
   262  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L413
   263  var ValueInvoke = stubFunction(functionValueInvoke)
   264  
   265  // ValueNew implements js.valueNew, which is used to call a js.Value, e.g.
   266  // `array.New(2)`.
   267  //
   268  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L432
   269  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L380-L391
   270  var ValueNew = spfunc.MustCallFromSP(true, &wasm.HostFunc{
   271  	ExportNames: []string{functionValueNew},
   272  	Name:        functionValueNew,
   273  	ParamTypes:  []api.ValueType{i64, i32, i32},
   274  	ParamNames:  []string{"v", "argsArray", "argsLen"},
   275  	ResultTypes: []api.ValueType{i64, i32, i32},
   276  	Code: &wasm.Code{
   277  		IsHostFunction: true,
   278  		GoFunc:         api.GoModuleFunc(valueNew),
   279  	},
   280  })
   281  
   282  func valueNew(ctx context.Context, mod api.Module, stack []uint64) {
   283  	vRef := stack[0]
   284  	argsArray := uint32(stack[1])
   285  	argsLen := uint32(stack[2])
   286  
   287  	args := loadArgs(ctx, mod, argsArray, argsLen)
   288  	ref := ref(vRef)
   289  	v := loadValue(ctx, ref)
   290  
   291  	var xRef uint64
   292  	var ok, sp uint32
   293  	switch ref {
   294  	case refArrayConstructor:
   295  		result := &objectArray{}
   296  		xRef = storeRef(ctx, result)
   297  		ok = 1
   298  	case refUint8ArrayConstructor:
   299  		var result *byteArray
   300  		if n, ok := args[0].(float64); ok {
   301  			result = &byteArray{make([]byte, uint32(n))}
   302  		} else if n, ok := args[0].(uint32); ok {
   303  			result = &byteArray{make([]byte, n)}
   304  		} else if b, ok := args[0].(*byteArray); ok {
   305  			// In case of below, in HTTP, return the same ref
   306  			//	uint8arrayWrapper := uint8Array.New(args[0])
   307  			result = b
   308  		} else {
   309  			panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", v, args))
   310  		}
   311  		xRef = storeRef(ctx, result)
   312  		ok = 1
   313  	case refObjectConstructor:
   314  		result := &object{properties: map[string]interface{}{}}
   315  		xRef = storeRef(ctx, result)
   316  		ok = 1
   317  	case refHttpHeadersConstructor:
   318  		result := &headers{headers: http.Header{}}
   319  		xRef = storeRef(ctx, result)
   320  		ok = 1
   321  	case refJsDateConstructor:
   322  		xRef = uint64(refJsDate)
   323  		ok = 1
   324  	default:
   325  		panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", v, args))
   326  	}
   327  
   328  	sp = refreshSP(mod)
   329  	stack[0], stack[1], stack[2] = xRef, uint64(ok), uint64(sp)
   330  }
   331  
   332  // ValueLength implements js.valueLength, which is used to load the length
   333  // property of a value, e.g. `array.length`.
   334  //
   335  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L372
   336  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L396-L397
   337  var ValueLength = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   338  	ExportNames: []string{functionValueLength},
   339  	Name:        functionValueLength,
   340  	ParamTypes:  []api.ValueType{i64},
   341  	ParamNames:  []string{"v"},
   342  	ResultTypes: []api.ValueType{i32},
   343  	Code: &wasm.Code{
   344  		IsHostFunction: true,
   345  		GoFunc:         api.GoFunc(valueLength),
   346  	},
   347  })
   348  
   349  func valueLength(ctx context.Context, stack []uint64) {
   350  	vRef := stack[0]
   351  
   352  	v := loadValue(ctx, ref(vRef))
   353  	l := uint32(len(v.(*objectArray).slice))
   354  
   355  	stack[0] = uint64(l)
   356  }
   357  
   358  // ValuePrepareString implements js.valuePrepareString, which is used to load
   359  // the string for `o.String()` (via js.jsString) for string, boolean and
   360  // number types. Notably, http.Transport uses this in RoundTrip to coerce the
   361  // URL to a string.
   362  //
   363  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L531
   364  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L402-L405
   365  var ValuePrepareString = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   366  	ExportNames: []string{functionValuePrepareString},
   367  	Name:        functionValuePrepareString,
   368  	ParamTypes:  []api.ValueType{i64},
   369  	ParamNames:  []string{"v"},
   370  	ResultTypes: []api.ValueType{i64, i32},
   371  	Code: &wasm.Code{
   372  		IsHostFunction: true,
   373  		GoFunc:         api.GoFunc(valuePrepareString),
   374  	},
   375  })
   376  
   377  func valuePrepareString(ctx context.Context, stack []uint64) {
   378  	vRef := stack[0]
   379  
   380  	v := loadValue(ctx, ref(vRef))
   381  	s := valueString(v)
   382  
   383  	sRef := storeRef(ctx, s)
   384  	sLen := uint32(len(s))
   385  
   386  	stack[0], stack[1] = sRef, uint64(sLen)
   387  }
   388  
   389  // ValueLoadString implements js.valueLoadString, which is used copy a string
   390  // value for `o.String()`.
   391  //
   392  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L533
   393  //
   394  //	https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L410-L412
   395  var ValueLoadString = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   396  	ExportNames: []string{functionValueLoadString},
   397  	Name:        functionValueLoadString,
   398  	ParamTypes:  []api.ValueType{i64, i32, i32},
   399  	ParamNames:  []string{"v", "bAddr", "bLen"},
   400  	Code: &wasm.Code{
   401  		IsHostFunction: true,
   402  		GoFunc:         api.GoModuleFunc(valueLoadString),
   403  	},
   404  })
   405  
   406  func valueLoadString(ctx context.Context, mod api.Module, stack []uint64) {
   407  	vRef := stack[0]
   408  	bAddr := uint32(stack[1])
   409  	bLen := uint32(stack[2])
   410  
   411  	v := loadValue(ctx, ref(vRef))
   412  	s := valueString(v)
   413  	b := mustRead(ctx, mod.Memory(), "b", bAddr, bLen)
   414  	copy(b, s)
   415  }
   416  
   417  // ValueInstanceOf is stubbed as it isn't used in Go's main source tree.
   418  //
   419  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L543
   420  var ValueInstanceOf = stubFunction(functionValueInstanceOf)
   421  
   422  // CopyBytesToGo copies a JavaScript managed byte array to linear memory.
   423  // For example, this is used to read an HTTP response body.
   424  //
   425  // # Results
   426  //
   427  //   - n is the count of bytes written.
   428  //   - ok is false if the src was not a uint8Array.
   429  //
   430  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L569
   431  // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L424-L433
   432  var CopyBytesToGo = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   433  	ExportNames: []string{functionCopyBytesToGo},
   434  	Name:        functionCopyBytesToGo,
   435  	ParamTypes:  []api.ValueType{i32, i32, i32, i64},
   436  	ParamNames:  []string{"dstAddr", "dstLen", "_", "src"},
   437  	ResultTypes: []api.ValueType{i32, i32},
   438  	Code: &wasm.Code{
   439  		IsHostFunction: true,
   440  		GoFunc:         api.GoModuleFunc(copyBytesToGo),
   441  	},
   442  })
   443  
   444  func copyBytesToGo(ctx context.Context, mod api.Module, stack []uint64) {
   445  	dstAddr := uint32(stack[0])
   446  	dstLen := uint32(stack[1])
   447  	_ /* unknown */ = uint32(stack[2])
   448  	srcRef := stack[3]
   449  
   450  	dst := mustRead(ctx, mod.Memory(), "dst", dstAddr, dstLen) // nolint
   451  	v := loadValue(ctx, ref(srcRef))
   452  
   453  	var n, ok uint32
   454  	if src, isBuf := v.(*byteArray); isBuf {
   455  		n = uint32(copy(dst, src.slice))
   456  		ok = 1
   457  	}
   458  
   459  	stack[0], stack[1] = uint64(n), uint64(ok)
   460  }
   461  
   462  // CopyBytesToJS copies linear memory to a JavaScript managed byte array.
   463  // For example, this is used to read an HTTP request body.
   464  //
   465  // # Results
   466  //
   467  //   - n is the count of bytes written.
   468  //   - ok is false if the dst was not a uint8Array.
   469  //
   470  // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L583
   471  //
   472  //	https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L438-L448
   473  var CopyBytesToJS = spfunc.MustCallFromSP(false, &wasm.HostFunc{
   474  	ExportNames: []string{functionCopyBytesToJS},
   475  	Name:        functionCopyBytesToJS,
   476  	ParamTypes:  []api.ValueType{i64, i32, i32, i32},
   477  	ParamNames:  []string{"dst", "srcAddr", "srcLen", "_"},
   478  	ResultTypes: []api.ValueType{i32, i32},
   479  	Code: &wasm.Code{
   480  		IsHostFunction: true,
   481  		GoFunc:         api.GoModuleFunc(copyBytesToJS),
   482  	},
   483  })
   484  
   485  func copyBytesToJS(ctx context.Context, mod api.Module, stack []uint64) {
   486  	dstRef := stack[0]
   487  	srcAddr := uint32(stack[1])
   488  	srcLen := uint32(stack[2])
   489  	_ /* unknown */ = uint32(stack[3])
   490  
   491  	src := mustRead(ctx, mod.Memory(), "src", srcAddr, srcLen) // nolint
   492  	v := loadValue(ctx, ref(dstRef))
   493  
   494  	var n, ok uint32
   495  	if dst, isBuf := v.(*byteArray); isBuf {
   496  		if dst != nil { // empty is possible on EOF
   497  			n = uint32(copy(dst.slice, src))
   498  		}
   499  		ok = 1
   500  	}
   501  
   502  	stack[0], stack[1] = uint64(n), uint64(ok)
   503  }
   504  
   505  // refreshSP refreshes the stack pointer, which is needed prior to storeValue
   506  // when in an operation that can trigger a Go event handler.
   507  //
   508  // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L210-L213
   509  func refreshSP(mod api.Module) uint32 {
   510  	// Cheat by reading global[0] directly instead of through a function proxy.
   511  	// https://github.com/golang/go/blob/go1.19/src/runtime/rt0_js_wasm.s#L87-L90
   512  	return uint32(mod.(*wasm.CallContext).GlobalVal(0))
   513  }
   514  
   515  // syscallErr is a (GOARCH=wasm) error, which must match a key in mapJSError.
   516  //
   517  // See https://github.com/golang/go/blob/go1.19/src/syscall/tables_js.go#L371-L494
   518  type syscallErr struct {
   519  	s string
   520  }
   521  
   522  // Error implements error.
   523  func (e *syscallErr) Error() string {
   524  	return e.s
   525  }
   526  
   527  // While usually I/O returns the correct errors, being explicit helps reduce
   528  // chance of problems.
   529  var (
   530  	ebadf   = &syscallErr{"EBADF"}
   531  	einval  = &syscallErr{"EBADF"}
   532  	eexist  = &syscallErr{"EEXIST"}
   533  	enoent  = &syscallErr{"ENOENT"}
   534  	enotdir = &syscallErr{"ENOTDIR"}
   535  )
   536  
   537  // mapJSError maps I/O errors as the message must be the code, ex. "EINVAL",
   538  // not the message, ex. "invalid argument".
   539  func mapJSError(err error) *syscallErr {
   540  	if e, ok := err.(*syscallErr); ok {
   541  		return e
   542  	}
   543  	switch {
   544  	case errors.Is(err, syscall.EBADF), errors.Is(err, fs.ErrClosed):
   545  		return ebadf
   546  	case errors.Is(err, syscall.EINVAL), errors.Is(err, fs.ErrInvalid):
   547  		return einval
   548  	case errors.Is(err, syscall.EEXIST), errors.Is(err, fs.ErrExist):
   549  		return eexist
   550  	case errors.Is(err, syscall.ENOENT), errors.Is(err, fs.ErrNotExist):
   551  		return enoent
   552  	case errors.Is(err, syscall.ENOTDIR):
   553  		return enotdir
   554  	default:
   555  		// panic so we can map the error before reaching JavaScript, which
   556  		// can't see the error message as it just prints "object".
   557  		panic(fmt.Errorf("unmapped error: %v", err))
   558  	}
   559  }
   560  
   561  // syscallOpen is like syscall.Open
   562  func syscallOpen(ctx context.Context, mod api.Module, name string, flags, perm uint32) (uint32, error) {
   563  	fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
   564  	return fsc.OpenFile(ctx, name)
   565  }
   566  
   567  const (
   568  	fdStdin = iota
   569  	fdStdout
   570  	fdStderr
   571  )
   572  
   573  // fdReader returns a valid reader for the given file descriptor or nil if ErrnoBadf.
   574  func fdReader(ctx context.Context, mod api.Module, fd uint32) io.Reader {
   575  	sysCtx := mod.(*wasm.CallContext).Sys
   576  	if fd == fdStdin {
   577  		return sysCtx.Stdin()
   578  	} else if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
   579  		return nil
   580  	} else {
   581  		return f.File
   582  	}
   583  }
   584  
   585  // fdWriter returns a valid writer for the given file descriptor or nil if ErrnoBadf.
   586  func fdWriter(ctx context.Context, mod api.Module, fd uint32) io.Writer {
   587  	sysCtx := mod.(*wasm.CallContext).Sys
   588  	switch fd {
   589  	case fdStdout:
   590  		return sysCtx.Stdout()
   591  	case fdStderr:
   592  		return sysCtx.Stderr()
   593  	default:
   594  		// Check to see if the file descriptor is available
   595  		if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok || f.File == nil {
   596  			return nil
   597  			// fs.FS doesn't declare io.Writer, but implementations such as
   598  			// os.File implement it.
   599  		} else if writer, ok := f.File.(io.Writer); !ok {
   600  			return nil
   601  		} else {
   602  			return writer
   603  		}
   604  	}
   605  }
   606  
   607  // funcWrapper is the result of go's js.FuncOf ("_makeFuncWrapper" here).
   608  //
   609  // This ID is managed on the Go side an increments (possibly rolling over).
   610  type funcWrapper uint32
   611  
   612  // jsFn implements jsFn.invoke
   613  func (f funcWrapper) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   614  	e := &event{id: uint32(f), this: args[0].(ref)}
   615  
   616  	if len(args) > 1 { // Ensure arguments are hashable.
   617  		e.args = &objectArray{args[1:]}
   618  		for i, v := range e.args.slice {
   619  			if s, ok := v.([]byte); ok {
   620  				args[i] = &byteArray{s}
   621  			} else if s, ok := v.([]interface{}); ok {
   622  				args[i] = &objectArray{s}
   623  			} else if e, ok := v.(error); ok {
   624  				args[i] = e
   625  			}
   626  		}
   627  	}
   628  
   629  	getState(ctx)._pendingEvent = e // Note: _pendingEvent reference is cleared during resume!
   630  
   631  	if _, err := mod.ExportedFunction("resume").Call(ctx); err != nil {
   632  		if _, ok := err.(*sys.ExitError); ok {
   633  			return nil, nil // allow error-handling to unwind when wasm calls exit due to a panic
   634  		} else {
   635  			return nil, err
   636  		}
   637  	}
   638  
   639  	return e.result, nil
   640  }