github.com/tetratelabs/wazero@v1.2.1/internal/gojs/state.go (about)

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