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  }