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 }