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 }