wa-lang.org/wazero@v1.0.2/internal/gojs/state.go (about)

     1  package gojs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  
     8  	"wa-lang.org/wazero/api"
     9  )
    10  
    11  func WithState(ctx context.Context) context.Context {
    12  	s := &state{
    13  		values:      &values{ids: map[interface{}]uint32{}},
    14  		valueGlobal: newJsGlobal(getRoundTripper(ctx)),
    15  		cwd:         "/",
    16  	}
    17  	return context.WithValue(ctx, stateKey{}, s)
    18  }
    19  
    20  // stateKey is a context.Context Value key. The value must be a state pointer.
    21  type stateKey struct{}
    22  
    23  func getState(ctx context.Context) *state {
    24  	return ctx.Value(stateKey{}).(*state)
    25  }
    26  
    27  type event struct {
    28  	// id is the funcWrapper.id
    29  	id     uint32
    30  	this   ref
    31  	args   *objectArray
    32  	result interface{}
    33  }
    34  
    35  // get implements jsGet.get
    36  func (e *event) get(_ context.Context, propertyKey string) interface{} {
    37  	switch propertyKey {
    38  	case "id":
    39  		return e.id
    40  	case "this": // ex fs
    41  		return e.this
    42  	case "args":
    43  		return e.args
    44  	}
    45  	panic(fmt.Sprintf("TODO: event.%s", propertyKey))
    46  }
    47  
    48  var (
    49  	undefined = struct{ name string }{name: "undefined"}
    50  	NaN       = math.NaN()
    51  )
    52  
    53  // loadValue reads up to 8 bytes at the memory offset `addr` to return the
    54  // value written by storeValue.
    55  //
    56  // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L122-L133
    57  func loadValue(ctx context.Context, ref ref) interface{} { // nolint
    58  	switch ref {
    59  	case 0:
    60  		return undefined
    61  	case refValueNaN:
    62  		return NaN
    63  	case refValueZero:
    64  		return float64(0)
    65  	case refValueNull:
    66  		return nil
    67  	case refValueTrue:
    68  		return true
    69  	case refValueFalse:
    70  		return false
    71  	case refValueGlobal:
    72  		return getState(ctx).valueGlobal
    73  	case refJsGo:
    74  		return getState(ctx)
    75  	case refObjectConstructor:
    76  		return objectConstructor
    77  	case refArrayConstructor:
    78  		return arrayConstructor
    79  	case refJsProcess:
    80  		return jsProcess
    81  	case refJsfs:
    82  		return jsfs
    83  	case refJsfsConstants:
    84  		return jsfsConstants
    85  	case refUint8ArrayConstructor:
    86  		return uint8ArrayConstructor
    87  	case refJsCrypto:
    88  		return jsCrypto
    89  	case refJsDateConstructor:
    90  		return jsDateConstructor
    91  	case refJsDate:
    92  		return jsDate
    93  	case refHttpHeadersConstructor:
    94  		return headersConstructor
    95  	default:
    96  		if (ref>>32)&nanHead != nanHead { // numbers are passed through as a ref
    97  			return api.DecodeF64(uint64(ref))
    98  		}
    99  		return getState(ctx).values.get(uint32(ref))
   100  	}
   101  }
   102  
   103  // loadArgs returns a slice of `len` values at the memory offset `addr`. The
   104  // returned slice is temporary, not stored in state.values.
   105  //
   106  // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L191-L199
   107  func loadArgs(ctx context.Context, mod api.Module, sliceAddr, sliceLen uint32) []interface{} { // nolint
   108  	result := make([]interface{}, 0, sliceLen)
   109  	for i := uint32(0); i < sliceLen; i++ { // nolint
   110  		iRef := mustReadUint64Le(ctx, mod.Memory(), "iRef", sliceAddr+i*8)
   111  		result = append(result, loadValue(ctx, ref(iRef)))
   112  	}
   113  	return result
   114  }
   115  
   116  // storeRef stores a value prior to returning to wasm from a host function.
   117  // This returns 8 bytes to represent either the value or a reference to it.
   118  // Any side effects besides memory must be cleaned up on wasmExit.
   119  //
   120  // See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L135-L183
   121  func storeRef(ctx context.Context, v interface{}) uint64 { // nolint
   122  	// allow-list because we control all implementations
   123  	if v == undefined {
   124  		return uint64(refValueUndefined)
   125  	} else if v == nil {
   126  		return uint64(refValueNull)
   127  	} else if r, ok := v.(ref); ok {
   128  		return uint64(r)
   129  	} else if b, ok := v.(bool); ok {
   130  		if b {
   131  			return uint64(refValueTrue)
   132  		} else {
   133  			return uint64(refValueFalse)
   134  		}
   135  	} else if c, ok := v.(*jsVal); ok {
   136  		return uint64(c.ref) // already stored
   137  	} else if _, ok := v.(*event); ok {
   138  		id := getState(ctx).values.increment(v)
   139  		return uint64(valueRef(id, typeFlagFunction))
   140  	} else if _, ok := v.(funcWrapper); ok {
   141  		id := getState(ctx).values.increment(v)
   142  		return uint64(valueRef(id, typeFlagFunction))
   143  	} else if _, ok := v.(jsFn); ok {
   144  		id := getState(ctx).values.increment(v)
   145  		return uint64(valueRef(id, typeFlagFunction))
   146  	} else if _, ok := v.(string); ok {
   147  		id := getState(ctx).values.increment(v)
   148  		return uint64(valueRef(id, typeFlagString))
   149  	} else if ui, ok := v.(uint32); ok {
   150  		if ui == 0 {
   151  			return uint64(refValueZero)
   152  		}
   153  		return api.EncodeF64(float64(ui)) // numbers are encoded as float and passed through as a ref
   154  	} else if u, ok := v.(uint64); ok {
   155  		return u // float is already encoded as a uint64, doesn't need to be stored.
   156  	} else if f64, ok := v.(float64); ok {
   157  		if f64 == 0 {
   158  			return uint64(refValueZero)
   159  		}
   160  		return api.EncodeF64(f64)
   161  	}
   162  	id := getState(ctx).values.increment(v)
   163  	return uint64(valueRef(id, typeFlagObject))
   164  }
   165  
   166  type values struct {
   167  	// Below is needed to avoid exhausting the ID namespace finalizeRef reclaims
   168  	// See https://go-review.googlesource.com/c/go/+/203600
   169  
   170  	values      []interface{}          // values indexed by ID, nil
   171  	goRefCounts []uint32               // recount pair-indexed with values
   172  	ids         map[interface{}]uint32 // live values
   173  	idPool      []uint32               // reclaimed IDs (values[i] = nil, goRefCounts[i] nil
   174  }
   175  
   176  func (j *values) get(id uint32) interface{} {
   177  	index := id - nextID
   178  	if index >= uint32(len(j.values)) {
   179  		panic(fmt.Errorf("id %d is out of range %d", id, len(j.values)))
   180  	}
   181  	return j.values[index]
   182  }
   183  
   184  func (j *values) increment(v interface{}) uint32 {
   185  	id, ok := j.ids[v]
   186  	if !ok {
   187  		if len(j.idPool) == 0 {
   188  			id, j.values, j.goRefCounts = uint32(len(j.values)), append(j.values, v), append(j.goRefCounts, 0)
   189  		} else {
   190  			id, j.idPool = j.idPool[len(j.idPool)-1], j.idPool[:len(j.idPool)-1]
   191  			j.values[id], j.goRefCounts[id] = v, 0
   192  		}
   193  		j.ids[v] = id
   194  	}
   195  	j.goRefCounts[id]++
   196  	return id + nextID
   197  }
   198  
   199  func (j *values) decrement(id uint32) {
   200  	// Special IDs are not refcounted.
   201  	if id < nextID {
   202  		return
   203  	}
   204  	id -= nextID
   205  	j.goRefCounts[id]--
   206  	if j.goRefCounts[id] == 0 {
   207  		j.values[id] = nil
   208  		j.idPool = append(j.idPool, id)
   209  	}
   210  }
   211  
   212  // state holds state used by the "go" imports used by gojs.
   213  // Note: This is module-scoped.
   214  type state struct {
   215  	values        *values
   216  	_pendingEvent *event
   217  
   218  	valueGlobal *jsVal
   219  
   220  	// cwd is initially "/"
   221  	cwd string
   222  }
   223  
   224  // get implements jsGet.get
   225  func (s *state) get(_ context.Context, propertyKey string) interface{} {
   226  	switch propertyKey {
   227  	case "_pendingEvent":
   228  		return s._pendingEvent
   229  	}
   230  	panic(fmt.Sprintf("TODO: state.%s", propertyKey))
   231  }
   232  
   233  // call implements jsCall.call
   234  func (s *state) call(_ context.Context, _ api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
   235  	switch method {
   236  	case "_makeFuncWrapper":
   237  		return funcWrapper(args[0].(float64)), nil
   238  	}
   239  	panic(fmt.Sprintf("TODO: state.%s", method))
   240  }
   241  
   242  func (s *state) clear() {
   243  	s.values.values = s.values.values[:0]
   244  	s.values.goRefCounts = s.values.goRefCounts[:0]
   245  	for k := range s.values.ids {
   246  		delete(s.values.ids, k)
   247  	}
   248  	s.values.idPool = s.values.idPool[:0]
   249  	s._pendingEvent = nil
   250  }
   251  
   252  func toInt64(arg interface{}) int64 {
   253  	if arg == refValueZero || arg == undefined {
   254  		return 0
   255  	} else if u, ok := arg.(int64); ok {
   256  		return u
   257  	}
   258  	return int64(arg.(float64))
   259  }
   260  
   261  func toUint32(arg interface{}) uint32 {
   262  	if arg == refValueZero || arg == undefined {
   263  		return 0
   264  	} else if u, ok := arg.(uint32); ok {
   265  		return u
   266  	}
   267  	return uint32(arg.(float64))
   268  }
   269  
   270  // valueString returns the string form of JavaScript string, boolean and number types.
   271  func valueString(v interface{}) string { // nolint
   272  	if s, ok := v.(string); ok {
   273  		return s
   274  	} else {
   275  		return fmt.Sprintf("%v", v)
   276  	}
   277  }