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  }