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