wa-lang.org/wazero@v1.0.2/internal/gojs/state.go (about) 1 package gojs 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 8 "wa-lang.org/wazero/api" 9 ) 10 11 func WithState(ctx context.Context) context.Context { 12 s := &state{ 13 values: &values{ids: map[interface{}]uint32{}}, 14 valueGlobal: newJsGlobal(getRoundTripper(ctx)), 15 cwd: "/", 16 } 17 return context.WithValue(ctx, stateKey{}, s) 18 } 19 20 // stateKey is a context.Context Value key. The value must be a state pointer. 21 type stateKey struct{} 22 23 func getState(ctx context.Context) *state { 24 return ctx.Value(stateKey{}).(*state) 25 } 26 27 type event struct { 28 // id is the funcWrapper.id 29 id uint32 30 this ref 31 args *objectArray 32 result interface{} 33 } 34 35 // get implements jsGet.get 36 func (e *event) get(_ context.Context, propertyKey string) interface{} { 37 switch propertyKey { 38 case "id": 39 return e.id 40 case "this": // ex fs 41 return e.this 42 case "args": 43 return e.args 44 } 45 panic(fmt.Sprintf("TODO: event.%s", propertyKey)) 46 } 47 48 var ( 49 undefined = struct{ name string }{name: "undefined"} 50 NaN = math.NaN() 51 ) 52 53 // loadValue reads up to 8 bytes at the memory offset `addr` to return the 54 // value written by storeValue. 55 // 56 // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L122-L133 57 func loadValue(ctx context.Context, ref ref) interface{} { // nolint 58 switch ref { 59 case 0: 60 return undefined 61 case refValueNaN: 62 return NaN 63 case refValueZero: 64 return float64(0) 65 case refValueNull: 66 return nil 67 case refValueTrue: 68 return true 69 case refValueFalse: 70 return false 71 case refValueGlobal: 72 return getState(ctx).valueGlobal 73 case refJsGo: 74 return getState(ctx) 75 case refObjectConstructor: 76 return objectConstructor 77 case refArrayConstructor: 78 return arrayConstructor 79 case refJsProcess: 80 return jsProcess 81 case refJsfs: 82 return jsfs 83 case refJsfsConstants: 84 return jsfsConstants 85 case refUint8ArrayConstructor: 86 return uint8ArrayConstructor 87 case refJsCrypto: 88 return jsCrypto 89 case refJsDateConstructor: 90 return jsDateConstructor 91 case refJsDate: 92 return jsDate 93 case refHttpHeadersConstructor: 94 return headersConstructor 95 default: 96 if (ref>>32)&nanHead != nanHead { // numbers are passed through as a ref 97 return api.DecodeF64(uint64(ref)) 98 } 99 return getState(ctx).values.get(uint32(ref)) 100 } 101 } 102 103 // loadArgs returns a slice of `len` values at the memory offset `addr`. The 104 // returned slice is temporary, not stored in state.values. 105 // 106 // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L191-L199 107 func loadArgs(ctx context.Context, mod api.Module, sliceAddr, sliceLen uint32) []interface{} { // nolint 108 result := make([]interface{}, 0, sliceLen) 109 for i := uint32(0); i < sliceLen; i++ { // nolint 110 iRef := mustReadUint64Le(ctx, mod.Memory(), "iRef", sliceAddr+i*8) 111 result = append(result, loadValue(ctx, ref(iRef))) 112 } 113 return result 114 } 115 116 // storeRef stores a value prior to returning to wasm from a host function. 117 // This returns 8 bytes to represent either the value or a reference to it. 118 // Any side effects besides memory must be cleaned up on wasmExit. 119 // 120 // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L135-L183 121 func storeRef(ctx context.Context, v interface{}) uint64 { // nolint 122 // allow-list because we control all implementations 123 if v == undefined { 124 return uint64(refValueUndefined) 125 } else if v == nil { 126 return uint64(refValueNull) 127 } else if r, ok := v.(ref); ok { 128 return uint64(r) 129 } else if b, ok := v.(bool); ok { 130 if b { 131 return uint64(refValueTrue) 132 } else { 133 return uint64(refValueFalse) 134 } 135 } else if c, ok := v.(*jsVal); ok { 136 return uint64(c.ref) // already stored 137 } else if _, ok := v.(*event); ok { 138 id := getState(ctx).values.increment(v) 139 return uint64(valueRef(id, typeFlagFunction)) 140 } else if _, ok := v.(funcWrapper); ok { 141 id := getState(ctx).values.increment(v) 142 return uint64(valueRef(id, typeFlagFunction)) 143 } else if _, ok := v.(jsFn); ok { 144 id := getState(ctx).values.increment(v) 145 return uint64(valueRef(id, typeFlagFunction)) 146 } else if _, ok := v.(string); ok { 147 id := getState(ctx).values.increment(v) 148 return uint64(valueRef(id, typeFlagString)) 149 } else if ui, ok := v.(uint32); ok { 150 if ui == 0 { 151 return uint64(refValueZero) 152 } 153 return api.EncodeF64(float64(ui)) // numbers are encoded as float and passed through as a ref 154 } else if u, ok := v.(uint64); ok { 155 return u // float is already encoded as a uint64, doesn't need to be stored. 156 } else if f64, ok := v.(float64); ok { 157 if f64 == 0 { 158 return uint64(refValueZero) 159 } 160 return api.EncodeF64(f64) 161 } 162 id := getState(ctx).values.increment(v) 163 return uint64(valueRef(id, typeFlagObject)) 164 } 165 166 type values struct { 167 // Below is needed to avoid exhausting the ID namespace finalizeRef reclaims 168 // See https://go-review.googlesource.com/c/go/+/203600 169 170 values []interface{} // values indexed by ID, nil 171 goRefCounts []uint32 // recount pair-indexed with values 172 ids map[interface{}]uint32 // live values 173 idPool []uint32 // reclaimed IDs (values[i] = nil, goRefCounts[i] nil 174 } 175 176 func (j *values) get(id uint32) interface{} { 177 index := id - nextID 178 if index >= uint32(len(j.values)) { 179 panic(fmt.Errorf("id %d is out of range %d", id, len(j.values))) 180 } 181 return j.values[index] 182 } 183 184 func (j *values) increment(v interface{}) uint32 { 185 id, ok := j.ids[v] 186 if !ok { 187 if len(j.idPool) == 0 { 188 id, j.values, j.goRefCounts = uint32(len(j.values)), append(j.values, v), append(j.goRefCounts, 0) 189 } else { 190 id, j.idPool = j.idPool[len(j.idPool)-1], j.idPool[:len(j.idPool)-1] 191 j.values[id], j.goRefCounts[id] = v, 0 192 } 193 j.ids[v] = id 194 } 195 j.goRefCounts[id]++ 196 return id + nextID 197 } 198 199 func (j *values) decrement(id uint32) { 200 // Special IDs are not refcounted. 201 if id < nextID { 202 return 203 } 204 id -= nextID 205 j.goRefCounts[id]-- 206 if j.goRefCounts[id] == 0 { 207 j.values[id] = nil 208 j.idPool = append(j.idPool, id) 209 } 210 } 211 212 // state holds state used by the "go" imports used by gojs. 213 // Note: This is module-scoped. 214 type state struct { 215 values *values 216 _pendingEvent *event 217 218 valueGlobal *jsVal 219 220 // cwd is initially "/" 221 cwd string 222 } 223 224 // get implements jsGet.get 225 func (s *state) get(_ context.Context, propertyKey string) interface{} { 226 switch propertyKey { 227 case "_pendingEvent": 228 return s._pendingEvent 229 } 230 panic(fmt.Sprintf("TODO: state.%s", propertyKey)) 231 } 232 233 // call implements jsCall.call 234 func (s *state) call(_ context.Context, _ api.Module, this ref, method string, args ...interface{}) (interface{}, error) { 235 switch method { 236 case "_makeFuncWrapper": 237 return funcWrapper(args[0].(float64)), nil 238 } 239 panic(fmt.Sprintf("TODO: state.%s", method)) 240 } 241 242 func (s *state) clear() { 243 s.values.values = s.values.values[:0] 244 s.values.goRefCounts = s.values.goRefCounts[:0] 245 for k := range s.values.ids { 246 delete(s.values.ids, k) 247 } 248 s.values.idPool = s.values.idPool[:0] 249 s._pendingEvent = nil 250 } 251 252 func toInt64(arg interface{}) int64 { 253 if arg == refValueZero || arg == undefined { 254 return 0 255 } else if u, ok := arg.(int64); ok { 256 return u 257 } 258 return int64(arg.(float64)) 259 } 260 261 func toUint32(arg interface{}) uint32 { 262 if arg == refValueZero || arg == undefined { 263 return 0 264 } else if u, ok := arg.(uint32); ok { 265 return u 266 } 267 return uint32(arg.(float64)) 268 } 269 270 // valueString returns the string form of JavaScript string, boolean and number types. 271 func valueString(v interface{}) string { // nolint 272 if s, ok := v.(string); ok { 273 return s 274 } else { 275 return fmt.Sprintf("%v", v) 276 } 277 }