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 }