github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/gojs/goos/goos.go (about) 1 // Package goos isolates code from runtime.GOOS=js in a way that avoids cyclic 2 // dependencies when re-used from other packages. 3 package goos 4 5 import ( 6 "context" 7 "encoding/binary" 8 "fmt" 9 10 "github.com/bananabytelabs/wazero/api" 11 "github.com/bananabytelabs/wazero/internal/gojs/goarch" 12 "github.com/bananabytelabs/wazero/internal/gojs/util" 13 "github.com/bananabytelabs/wazero/internal/wasm" 14 ) 15 16 // Ref is used to identify a JavaScript value, since the value itself cannot 17 // be passed to WebAssembly. 18 // 19 // The JavaScript value "undefined" is represented by the value 0. 20 // 21 // A JavaScript number (64-bit float, except 0 and NaN) is represented by its 22 // IEEE 754 binary representation. 23 // 24 // All other values are represented as an IEEE 754 binary representation of NaN 25 // with bits 0-31 used as an ID and bits 32-34 used to differentiate between 26 // string, symbol, function and object. 27 type Ref uint64 28 29 const ( 30 // predefined 31 32 IdValueNaN uint32 = iota 33 IdValueZero 34 IdValueNull 35 IdValueTrue 36 IdValueFalse 37 IdValueGlobal 38 IdJsGo 39 40 // The below are derived from analyzing `*_js.go` source. 41 42 IdObjectConstructor 43 IdArrayConstructor 44 IdJsProcess 45 IdJsfs 46 IdJsfsConstants 47 IdUint8ArrayConstructor 48 IdJsCrypto 49 IdJsDateConstructor 50 IdJsDate 51 NextID 52 ) 53 54 const ( 55 RefValueUndefined = Ref(0) 56 RefValueNaN = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNaN) 57 RefValueZero = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueZero) 58 RefValueNull = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNull) 59 RefValueTrue = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueTrue) 60 RefValueFalse = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueFalse) 61 RefValueGlobal = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdValueGlobal) 62 RefJsGo = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsGo) 63 64 RefObjectConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdObjectConstructor) 65 RefArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdArrayConstructor) 66 RefJsProcess = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsProcess) 67 RefJsfs = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfs) 68 RefJsfsConstants = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfsConstants) 69 RefUint8ArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdUint8ArrayConstructor) 70 RefJsCrypto = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsCrypto) 71 RefJsDateConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsDateConstructor) 72 RefJsDate = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsDate) 73 ) 74 75 type TypeFlag byte 76 77 // the type flags need to be in sync with gojs.js 78 const ( 79 TypeFlagNone TypeFlag = iota 80 TypeFlagObject 81 TypeFlagString 82 TypeFlagSymbol //nolint 83 TypeFlagFunction 84 ) 85 86 func ValueRef(id uint32, typeFlag TypeFlag) Ref { 87 return (NanHead|Ref(typeFlag))<<32 | Ref(id) 88 } 89 90 var le = binary.LittleEndian 91 92 // NanHead are the upper 32 bits of a Ref which are set if the value is not encoded as an IEEE 754 number (see above). 93 const NanHead = 0x7FF80000 94 95 func (ref Ref) ParseFloat() (v float64, ok bool) { 96 if (ref>>32)&NanHead != NanHead { 97 v = api.DecodeF64(uint64(ref)) 98 ok = true 99 } 100 return 101 } 102 103 // GetLastEventArgs returns the arguments to the last event created by 104 // custom.NameSyscallValueCall. 105 type GetLastEventArgs func(context.Context) []interface{} 106 107 type ValLoader func(context.Context, Ref) interface{} 108 109 type Stack interface { 110 goarch.Stack 111 112 ParamRef(i int) Ref 113 114 ParamRefs(mem api.Memory, i int) []Ref 115 116 ParamVal(ctx context.Context, i int, loader ValLoader) interface{} 117 118 // ParamVals is used by functions whose final parameter is an arg array. 119 ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} 120 121 SetResultRef(i int, v Ref) 122 } 123 124 type stack struct { 125 s goarch.Stack 126 } 127 128 // Name implements the same method as documented on goarch.Stack 129 func (s *stack) Name() string { 130 return s.s.Name() 131 } 132 133 // Param implements the same method as documented on goarch.Stack 134 func (s *stack) Param(i int) uint64 { 135 return s.s.Param(i) 136 } 137 138 // ParamBytes implements the same method as documented on goarch.Stack 139 func (s *stack) ParamBytes(mem api.Memory, i int) []byte { 140 return s.s.ParamBytes(mem, i) 141 } 142 143 // ParamRef implements Stack.ParamRef 144 func (s *stack) ParamRef(i int) Ref { 145 return Ref(s.s.Param(i)) 146 } 147 148 // ParamRefs implements Stack.ParamRefs 149 func (s *stack) ParamRefs(mem api.Memory, i int) []Ref { 150 offset := s.s.ParamUint32(i) 151 size := s.s.ParamUint32(i + 1) 152 byteCount := size << 3 // size * 8 153 154 result := make([]Ref, 0, size) 155 156 buf := util.MustRead(mem, s.Name(), i, offset, byteCount) 157 for pos := uint32(0); pos < byteCount; pos += 8 { 158 ref := Ref(le.Uint64(buf[pos:])) 159 result = append(result, ref) 160 } 161 return result 162 } 163 164 // ParamString implements the same method as documented on goarch.Stack 165 func (s *stack) ParamString(mem api.Memory, i int) string { 166 return s.s.ParamString(mem, i) 167 } 168 169 // ParamInt32 implements the same method as documented on goarch.Stack 170 func (s *stack) ParamInt32(i int) int32 { 171 return s.s.ParamInt32(i) 172 } 173 174 // ParamUint32 implements the same method as documented on goarch.Stack 175 func (s *stack) ParamUint32(i int) uint32 { 176 return s.s.ParamUint32(i) 177 } 178 179 // ParamVal implements Stack.ParamVal 180 func (s *stack) ParamVal(ctx context.Context, i int, loader ValLoader) interface{} { 181 ref := s.ParamRef(i) 182 return loader(ctx, ref) 183 } 184 185 // ParamVals implements Stack.ParamVals 186 func (s *stack) ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} { 187 offset := s.s.ParamUint32(i) 188 size := s.s.ParamUint32(i + 1) 189 byteCount := size << 3 // size * 8 190 191 result := make([]interface{}, 0, size) 192 193 buf := util.MustRead(mem, s.Name(), i, offset, byteCount) 194 for pos := uint32(0); pos < byteCount; pos += 8 { 195 ref := Ref(le.Uint64(buf[pos:])) 196 result = append(result, loader(ctx, ref)) 197 } 198 return result 199 } 200 201 // Refresh implements the same method as documented on goarch.Stack 202 func (s *stack) Refresh(mod api.Module) { 203 s.s.Refresh(mod) 204 } 205 206 // SetResult implements the same method as documented on goarch.Stack 207 func (s *stack) SetResult(i int, v uint64) { 208 s.s.SetResult(i, v) 209 } 210 211 // SetResultBool implements the same method as documented on goarch.Stack 212 func (s *stack) SetResultBool(i int, v bool) { 213 s.s.SetResultBool(i, v) 214 } 215 216 // SetResultI32 implements the same method as documented on goarch.Stack 217 func (s *stack) SetResultI32(i int, v int32) { 218 s.s.SetResultI32(i, v) 219 } 220 221 // SetResultI64 implements the same method as documented on goarch.Stack 222 func (s *stack) SetResultI64(i int, v int64) { 223 s.s.SetResultI64(i, v) 224 } 225 226 // SetResultRef implements Stack.SetResultRef 227 func (s *stack) SetResultRef(i int, v Ref) { 228 s.s.SetResult(i, uint64(v)) 229 } 230 231 // SetResultUint32 implements the same method as documented on goarch.Stack 232 func (s *stack) SetResultUint32(i int, v uint32) { 233 s.s.SetResultUint32(i, v) 234 } 235 236 func NewFunc(name string, goFunc Func) *wasm.HostFunc { 237 sf := &stackFunc{name: name, f: goFunc} 238 return util.NewFunc(name, sf.Call) 239 } 240 241 type Func func(context.Context, api.Module, Stack) 242 243 type stackFunc struct { 244 name string 245 f Func 246 } 247 248 // Call implements the same method as defined on api.GoModuleFunction. 249 func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) { 250 s := NewStack(f.name, mod.Memory(), uint32(wasmStack[0])) 251 f.f(ctx, mod, s) 252 } 253 254 func NewStack(name string, mem api.Memory, sp uint32) *stack { 255 return &stack{goarch.NewStack(name, mem, sp)} 256 } 257 258 var Undefined = struct{ name string }{name: "undefined"} 259 260 func ValueToUint32(arg interface{}) uint32 { 261 if arg == RefValueZero || arg == Undefined { 262 return 0 263 } else if u, ok := arg.(uint32); ok { 264 return u 265 } 266 return uint32(arg.(float64)) 267 } 268 269 func ValueToInt32(arg interface{}) int32 { 270 if arg == RefValueZero || arg == Undefined { 271 return 0 272 } else if u, ok := arg.(int); ok { 273 return int32(u) 274 } 275 return int32(uint32(arg.(float64))) 276 } 277 278 // GetFunction allows getting a JavaScript property by name. 279 type GetFunction interface { 280 Get(propertyKey string) interface{} 281 } 282 283 // ByteArray is a result of uint8ArrayConstructor which temporarily stores 284 // binary data outside linear memory. 285 // 286 // Note: This is a wrapper because a slice is not hashable. 287 type ByteArray struct { 288 slice []byte 289 } 290 291 func WrapByteArray(buf []byte) *ByteArray { 292 return &ByteArray{buf} 293 } 294 295 // Unwrap returns the underlying byte slice 296 func (a *ByteArray) Unwrap() []byte { 297 return a.slice 298 } 299 300 // Get implements GetFunction 301 func (a *ByteArray) Get(propertyKey string) interface{} { 302 switch propertyKey { 303 case "byteLength": 304 return uint32(len(a.slice)) 305 } 306 panic(fmt.Sprintf("TODO: get byteArray.%s", propertyKey)) 307 }