github.com/tetratelabs/wazero@v1.2.1/internal/gojs/state.go (about) 1 package gojs 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 8 "github.com/tetratelabs/wazero/api" 9 "github.com/tetratelabs/wazero/internal/gojs/config" 10 "github.com/tetratelabs/wazero/internal/gojs/goos" 11 "github.com/tetratelabs/wazero/internal/gojs/values" 12 ) 13 14 func NewState(config *config.Config) *State { 15 return &State{ 16 values: values.NewValues(), 17 valueGlobal: newJsGlobal(config), 18 _nextCallbackTimeoutID: 1, 19 _scheduledTimeouts: map[uint32]chan bool{}, 20 } 21 } 22 23 // StateKey is a context.Context Value key. The value must be a state pointer. 24 type StateKey struct{} 25 26 func getState(ctx context.Context) *State { 27 return ctx.Value(StateKey{}).(*State) 28 } 29 30 // GetLastEventArgs implements goos.GetLastEventArgs 31 func GetLastEventArgs(ctx context.Context) []interface{} { 32 if ls := ctx.Value(StateKey{}).(*State)._lastEvent; ls != nil { 33 if args := ls.args; args != nil { 34 return args.slice 35 } 36 } 37 return nil 38 } 39 40 type event struct { 41 // id is the funcWrapper.id 42 id uint32 43 this goos.Ref 44 args *objectArray 45 result interface{} 46 } 47 48 // Get implements the same method as documented on goos.GetFunction 49 func (e *event) Get(propertyKey string) interface{} { 50 switch propertyKey { 51 case "id": 52 return e.id 53 case "this": // ex fs 54 return e.this 55 case "args": 56 return e.args 57 } 58 panic(fmt.Sprintf("TODO: event.%s", propertyKey)) 59 } 60 61 var NaN = math.NaN() 62 63 // LoadValue reads up to 8 bytes at the memory offset `addr` to return the 64 // value written by storeValue. 65 // 66 // See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L122-L133 67 func LoadValue(ctx context.Context, ref goos.Ref) interface{} { //nolint 68 switch ref { 69 case 0: 70 return goos.Undefined 71 case goos.RefValueNaN: 72 return NaN 73 case goos.RefValueZero: 74 return float64(0) 75 case goos.RefValueNull: 76 return nil 77 case goos.RefValueTrue: 78 return true 79 case goos.RefValueFalse: 80 return false 81 case goos.RefValueGlobal: 82 return getState(ctx).valueGlobal 83 case goos.RefJsGo: 84 return getState(ctx) 85 case goos.RefObjectConstructor: 86 return objectConstructor 87 case goos.RefArrayConstructor: 88 return arrayConstructor 89 case goos.RefJsProcess: 90 return getState(ctx).valueGlobal.Get("process") 91 case goos.RefJsfs: 92 return getState(ctx).valueGlobal.Get("fs") 93 case goos.RefJsfsConstants: 94 return jsfsConstants 95 case goos.RefUint8ArrayConstructor: 96 return uint8ArrayConstructor 97 case goos.RefJsCrypto: 98 return jsCrypto 99 case goos.RefJsDateConstructor: 100 return jsDateConstructor 101 case goos.RefJsDate: 102 return jsDate 103 case goos.RefHttpHeadersConstructor: 104 return headersConstructor 105 default: 106 if f, ok := ref.ParseFloat(); ok { // numbers are passed through as a Ref 107 return f 108 } 109 return getState(ctx).values.Get(uint32(ref)) 110 } 111 } 112 113 // storeValue stores a value prior to returning to wasm from a host function. 114 // This returns 8 bytes to represent either the value or a reference to it. 115 // Any side effects besides memory must be cleaned up on wasmExit. 116 // 117 // See https://github.com/golang/go/blob/de4748c47c67392a57f250714509f590f68ad395/misc/wasm/wasm_exec.js#L135-L183 118 func storeValue(ctx context.Context, v interface{}) goos.Ref { //nolint 119 // allow-list because we control all implementations 120 if v == goos.Undefined { 121 return goos.RefValueUndefined 122 } else if v == nil { 123 return goos.RefValueNull 124 } else if r, ok := v.(goos.Ref); ok { 125 return r 126 } else if b, ok := v.(bool); ok { 127 if b { 128 return goos.RefValueTrue 129 } else { 130 return goos.RefValueFalse 131 } 132 } else if c, ok := v.(*jsVal); ok { 133 return c.ref // already stored 134 } else if _, ok := v.(*event); ok { 135 id := getState(ctx).values.Increment(v) 136 return goos.ValueRef(id, goos.TypeFlagFunction) 137 } else if _, ok := v.(funcWrapper); ok { 138 id := getState(ctx).values.Increment(v) 139 return goos.ValueRef(id, goos.TypeFlagFunction) 140 } else if _, ok := v.(jsFn); ok { 141 id := getState(ctx).values.Increment(v) 142 return goos.ValueRef(id, goos.TypeFlagFunction) 143 } else if _, ok := v.(string); ok { 144 id := getState(ctx).values.Increment(v) 145 return goos.ValueRef(id, goos.TypeFlagString) 146 } else if i32, ok := v.(int32); ok { 147 return toFloatRef(float64(i32)) 148 } else if u32, ok := v.(uint32); ok { 149 return toFloatRef(float64(u32)) 150 } else if i64, ok := v.(int64); ok { 151 return toFloatRef(float64(i64)) 152 } else if u64, ok := v.(uint64); ok { 153 return toFloatRef(float64(u64)) 154 } else if f64, ok := v.(float64); ok { 155 return toFloatRef(f64) 156 } 157 id := getState(ctx).values.Increment(v) 158 return goos.ValueRef(id, goos.TypeFlagObject) 159 } 160 161 func toFloatRef(f float64) goos.Ref { 162 if f == 0 { 163 return goos.RefValueZero 164 } 165 // numbers are encoded as float and passed through as a Ref 166 return goos.Ref(api.EncodeF64(f)) 167 } 168 169 // State holds state used by the "go" imports used by gojs. 170 // Note: This is module-scoped. 171 type State struct { 172 values *values.Values 173 _pendingEvent *event 174 // _lastEvent was the last _pendingEvent value 175 _lastEvent *event 176 177 valueGlobal *jsVal 178 179 _nextCallbackTimeoutID uint32 180 _scheduledTimeouts map[uint32]chan bool 181 } 182 183 // Get implements the same method as documented on goos.GetFunction 184 func (s *State) Get(propertyKey string) interface{} { 185 switch propertyKey { 186 case "_pendingEvent": 187 return s._pendingEvent 188 } 189 panic(fmt.Sprintf("TODO: state.%s", propertyKey)) 190 } 191 192 // call implements jsCall.call 193 func (s *State) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) { 194 switch method { 195 case "_makeFuncWrapper": 196 return funcWrapper(args[0].(float64)), nil 197 } 198 panic(fmt.Sprintf("TODO: state.%s", method)) 199 } 200 201 // close releases any state including values and underlying slices for garbage 202 // collection. 203 func (s *State) close() { 204 // _scheduledTimeouts may have in-flight goroutines, so cancel them. 205 for k, cancel := range s._scheduledTimeouts { 206 delete(s._scheduledTimeouts, k) 207 cancel <- true 208 } 209 // Reset all state recursively to their initial values. This allows our 210 // unit tests to check we closed everything. 211 s._scheduledTimeouts = map[uint32]chan bool{} 212 s.values.Reset() 213 s._pendingEvent = nil 214 s._lastEvent = nil 215 s._nextCallbackTimeoutID = 1 216 } 217 218 func toInt64(arg interface{}) int64 { 219 if arg == goos.RefValueZero || arg == goos.Undefined { 220 return 0 221 } else if u, ok := arg.(int64); ok { 222 return u 223 } 224 return int64(arg.(float64)) 225 } 226 227 func toUint64(arg interface{}) uint64 { 228 if arg == goos.RefValueZero || arg == goos.Undefined { 229 return 0 230 } else if u, ok := arg.(uint64); ok { 231 return u 232 } 233 return uint64(arg.(float64)) 234 } 235 236 // valueString returns the string form of JavaScript string, boolean and number types. 237 func valueString(v interface{}) string { //nolint 238 if s, ok := v.(string); ok { 239 return s 240 } else { 241 return fmt.Sprintf("%v", v) 242 } 243 }