// Package goos isolates code from runtime.GOOS=js in a way that avoids cyclic // dependencies when re-used from other packages. package goos import ( "context" "encoding/binary" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/gojs/goarch" "github.com/tetratelabs/wazero/internal/gojs/util" "github.com/tetratelabs/wazero/internal/wasm" ) // Ref is used to identify a JavaScript value, since the value itself cannot // be passed to WebAssembly. // // The JavaScript value "undefined" is represented by the value 0. // // A JavaScript number (64-bit float, except 0 and NaN) is represented by its // IEEE 754 binary representation. // // All other values are represented as an IEEE 754 binary representation of NaN // with bits 0-31 used as an ID and bits 32-34 used to differentiate between // string, symbol, function and object. type Ref uint64 const ( // predefined IdValueNaN uint32 = iota IdValueZero IdValueNull IdValueTrue IdValueFalse IdValueGlobal IdJsGo // The below are derived from analyzing `*_js.go` source. IdObjectConstructor IdArrayConstructor IdJsProcess IdJsfs IdJsfsConstants IdUint8ArrayConstructor IdJsCrypto IdJsDateConstructor IdJsDate IdHttpFetch IdHttpHeaders NextID ) const ( RefValueUndefined = Ref(0) RefValueNaN = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNaN) RefValueZero = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueZero) RefValueNull = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNull) RefValueTrue = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueTrue) RefValueFalse = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueFalse) RefValueGlobal = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdValueGlobal) RefJsGo = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsGo) RefObjectConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdObjectConstructor) RefArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdArrayConstructor) RefJsProcess = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsProcess) RefJsfs = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfs) RefJsfsConstants = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfsConstants) RefUint8ArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdUint8ArrayConstructor) RefJsCrypto = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsCrypto) RefJsDateConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsDateConstructor) RefJsDate = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsDate) RefHttpFetch = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdHttpFetch) RefHttpHeadersConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdHttpHeaders) ) type TypeFlag byte // the type flags need to be in sync with gojs.js const ( TypeFlagNone TypeFlag = iota TypeFlagObject TypeFlagString TypeFlagSymbol //nolint TypeFlagFunction ) func ValueRef(id uint32, typeFlag TypeFlag) Ref { return (NanHead|Ref(typeFlag))<<32 | Ref(id) } var le = binary.LittleEndian // 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). const NanHead = 0x7FF80000 func (ref Ref) ParseFloat() (v float64, ok bool) { if (ref>>32)&NanHead != NanHead { v = api.DecodeF64(uint64(ref)) ok = true } return } // GetLastEventArgs returns the arguments to the last event created by // custom.NameSyscallValueCall. type GetLastEventArgs func(context.Context) []interface{} type ValLoader func(context.Context, Ref) interface{} type Stack interface { goarch.Stack ParamRef(i int) Ref ParamRefs(mem api.Memory, i int) []Ref ParamVal(ctx context.Context, i int, loader ValLoader) interface{} // ParamVals is used by functions whose final parameter is an arg array. ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} SetResultRef(i int, v Ref) } type stack struct { s goarch.Stack } // Name implements the same method as documented on goarch.Stack func (s *stack) Name() string { return s.s.Name() } // Param implements the same method as documented on goarch.Stack func (s *stack) Param(i int) uint64 { return s.s.Param(i) } // ParamBytes implements the same method as documented on goarch.Stack func (s *stack) ParamBytes(mem api.Memory, i int) []byte { return s.s.ParamBytes(mem, i) } // ParamRef implements Stack.ParamRef func (s *stack) ParamRef(i int) Ref { return Ref(s.s.Param(i)) } // ParamRefs implements Stack.ParamRefs func (s *stack) ParamRefs(mem api.Memory, i int) []Ref { offset := s.s.ParamUint32(i) size := s.s.ParamUint32(i + 1) byteCount := size << 3 // size * 8 result := make([]Ref, 0, size) buf := util.MustRead(mem, s.Name(), i, offset, byteCount) for pos := uint32(0); pos < byteCount; pos += 8 { ref := Ref(le.Uint64(buf[pos:])) result = append(result, ref) } return result } // ParamString implements the same method as documented on goarch.Stack func (s *stack) ParamString(mem api.Memory, i int) string { return s.s.ParamString(mem, i) } // ParamUint32 implements the same method as documented on goarch.Stack func (s *stack) ParamUint32(i int) uint32 { return s.s.ParamUint32(i) } // ParamVal implements Stack.ParamVal func (s *stack) ParamVal(ctx context.Context, i int, loader ValLoader) interface{} { ref := s.ParamRef(i) return loader(ctx, ref) } // ParamVals implements Stack.ParamVals func (s *stack) ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} { offset := s.s.ParamUint32(i) size := s.s.ParamUint32(i + 1) byteCount := size << 3 // size * 8 result := make([]interface{}, 0, size) buf := util.MustRead(mem, s.Name(), i, offset, byteCount) for pos := uint32(0); pos < byteCount; pos += 8 { ref := Ref(le.Uint64(buf[pos:])) result = append(result, loader(ctx, ref)) } return result } // Refresh implements the same method as documented on goarch.Stack func (s *stack) Refresh(mod api.Module) { s.s.Refresh(mod) } // SetResult implements the same method as documented on goarch.Stack func (s *stack) SetResult(i int, v uint64) { s.s.SetResult(i, v) } // SetResultBool implements the same method as documented on goarch.Stack func (s *stack) SetResultBool(i int, v bool) { s.s.SetResultBool(i, v) } // SetResultI32 implements the same method as documented on goarch.Stack func (s *stack) SetResultI32(i int, v int32) { s.s.SetResultI32(i, v) } // SetResultI64 implements the same method as documented on goarch.Stack func (s *stack) SetResultI64(i int, v int64) { s.s.SetResultI64(i, v) } // SetResultRef implements Stack.SetResultRef func (s *stack) SetResultRef(i int, v Ref) { s.s.SetResult(i, uint64(v)) } // SetResultUint32 implements the same method as documented on goarch.Stack func (s *stack) SetResultUint32(i int, v uint32) { s.s.SetResultUint32(i, v) } func NewFunc(name string, goFunc Func) *wasm.HostFunc { return util.NewFunc(name, (&stackFunc{name: name, f: goFunc}).Call) } type Func func(context.Context, api.Module, Stack) type stackFunc struct { name string f Func } // Call implements the same method as defined on api.GoModuleFunction. func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) { s := NewStack(f.name, mod.Memory(), uint32(wasmStack[0])) f.f(ctx, mod, s) } func NewStack(name string, mem api.Memory, sp uint32) *stack { return &stack{goarch.NewStack(name, mem, sp)} }