summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmValue.h
blob: 26ff65e327af5acc3b51a1c7dd76e6090262c825 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 *
 * Copyright 2021 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef wasm_val_h
#define wasm_val_h

#include "js/Class.h"  // JSClassOps, ClassSpec
#include "vm/JSObject.h"
#include "vm/NativeObject.h"  // NativeObject
#include "wasm/WasmSerialize.h"
#include "wasm/WasmTypeDef.h"

namespace js {
namespace wasm {

// A V128 value.

struct V128 {
  uint8_t bytes[16] = {};  // Little-endian

  WASM_CHECK_CACHEABLE_POD(bytes);

  V128() = default;

  explicit V128(uint8_t splatValue) {
    memset(bytes, int(splatValue), sizeof(bytes));
  }

  template <typename T>
  void extractLane(unsigned lane, T* result) const {
    MOZ_ASSERT(lane < 16 / sizeof(T));
    memcpy(result, bytes + sizeof(T) * lane, sizeof(T));
  }

  template <typename T>
  void insertLane(unsigned lane, T value) {
    MOZ_ASSERT(lane < 16 / sizeof(T));
    memcpy(bytes + sizeof(T) * lane, &value, sizeof(T));
  }

  bool operator==(const V128& rhs) const {
    for (size_t i = 0; i < sizeof(bytes); i++) {
      if (bytes[i] != rhs.bytes[i]) {
        return false;
      }
    }
    return true;
  }

  bool operator!=(const V128& rhs) const { return !(*this == rhs); }
};

WASM_DECLARE_CACHEABLE_POD(V128);

static_assert(sizeof(V128) == 16, "Invariant");

// An AnyRef is a boxed value that can represent any wasm reference type and any
// host type that the host system allows to flow into and out of wasm
// transparently.  It is a pointer-sized datum that has the same representation
// as all its subtypes (funcref, externref, eqref, (ref T), et al) due to the
// non-coercive subtyping of the wasm type system.  Its current representation
// is a plain JSObject*, and the private JSObject subtype WasmValueBox is used
// to box non-object non-null JS values.
//
// The C++/wasm boundary always uses a 'void*' type to express AnyRef values, to
// emphasize the pointer-ness of the value.  The C++ code must transform the
// void* into an AnyRef by calling AnyRef::fromCompiledCode(), and transform an
// AnyRef into a void* by calling AnyRef::toCompiledCode().  Once in C++, we use
// AnyRef everywhere.  A JS Value is transformed into an AnyRef by calling
// AnyRef::box(), and the AnyRef is transformed into a JS Value by calling
// AnyRef::unbox().
//
// NOTE that AnyRef values may point to GC'd storage and as such need to be
// rooted if they are kept live in boxed form across code that may cause GC!
// Use RootedAnyRef / HandleAnyRef / MutableHandleAnyRef where necessary.
//
// The lowest bits of the pointer value are used for tagging, to allow for some
// representation optimizations and to distinguish various types.

// For version 0, we simply equate AnyRef and JSObject* (this means that there
// are technically no tags at all yet).  We use a simple boxing scheme that
// wraps a JS value that is not already JSObject in a distinguishable JSObject
// that holds the value, see WasmTypes.cpp for details.  Knowledge of this
// mapping is embedded in CodeGenerator.cpp (in WasmBoxValue and
// WasmAnyRefFromJSObject) and in WasmStubs.cpp (in functions Box* and Unbox*).

class AnyRef {
  // mutable so that tracing may access a JSObject* from a `const Val` or
  // `const AnyRef`.
  mutable JSObject* value_;

  explicit AnyRef() : value_((JSObject*)-1) {}
  explicit AnyRef(JSObject* p) : value_(p) {
    MOZ_ASSERT(((uintptr_t)p & 0x03) == 0);
  }

 public:
  // An invalid AnyRef cannot arise naturally from wasm and so can be used as
  // a sentinel value to indicate failure from an AnyRef-returning function.
  static AnyRef invalid() { return AnyRef(); }

  // Given a void* that comes from compiled wasm code, turn it into AnyRef.
  static AnyRef fromCompiledCode(void* p) { return AnyRef((JSObject*)p); }

  // Given a JSObject* that comes from JS, turn it into AnyRef.
  static AnyRef fromJSObject(JSObject* p) { return AnyRef(p); }

  // Generate an AnyRef null pointer.
  static AnyRef null() { return AnyRef(nullptr); }

  bool isNull() const { return value_ == nullptr; }

  bool operator==(const AnyRef& rhs) const {
    return this->value_ == rhs.value_;
  }

  bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); }

  void* forCompiledCode() const { return value_; }

  JSObject* asJSObject() const { return value_; }

  JSObject** asJSObjectAddress() const { return &value_; }

  void trace(JSTracer* trc);

  // Tags (to be developed further)
  static constexpr uintptr_t AnyRefTagMask = 1;
  static constexpr uintptr_t AnyRefObjTag = 0;
};

using RootedAnyRef = Rooted<AnyRef>;
using HandleAnyRef = Handle<AnyRef>;
using MutableHandleAnyRef = MutableHandle<AnyRef>;

// TODO/AnyRef-boxing: With boxed immediates and strings, these will be defined
// as MOZ_CRASH or similar so that we can find all locations that need to be
// fixed.

#define ASSERT_ANYREF_IS_JSOBJECT (void)(0)
#define STATIC_ASSERT_ANYREF_IS_JSOBJECT static_assert(1, "AnyRef is JSObject")

// Given any JS value, box it as an AnyRef and store it in *result.  Returns
// false on OOM.

bool BoxAnyRef(JSContext* cx, HandleValue val, MutableHandleAnyRef result);

// Given a JS value that requires an object box, box it as an AnyRef and return
// it, returning nullptr on OOM.
//
// Currently the values requiring a box are those other than JSObject* or
// nullptr, but in the future more values will be represented without an
// allocation.
JSObject* BoxBoxableValue(JSContext* cx, HandleValue val);

// Given any AnyRef, unbox it as a JS Value.  If it is a reference to a wasm
// object it will be reflected as a JSObject* representing some TypedObject
// instance.

Value UnboxAnyRef(AnyRef val);

class WasmValueBox : public NativeObject {
  static const unsigned VALUE_SLOT = 0;

 public:
  static const unsigned RESERVED_SLOTS = 1;
  static const JSClass class_;

  static WasmValueBox* create(JSContext* cx, HandleValue val);
  Value value() const { return getFixedSlot(VALUE_SLOT); }
  static size_t offsetOfValue() {
    return NativeObject::getFixedSlotOffset(VALUE_SLOT);
  }
};

// A FuncRef is a JSFunction* and is hence also an AnyRef, and the remarks above
// about AnyRef apply also to FuncRef.  When 'funcref' is used as a value type
// in wasm code, the value that is held is "the canonical function value", which
// is a function for which IsWasmExportedFunction() is true, and which has the
// correct identity wrt reference equality of functions.  Notably, if a function
// is imported then its ref.func value compares === in JS to the function that
// was passed as an import when the instance was created.
//
// These rules ensure that casts from funcref to anyref are non-converting
// (generate no code), and that no wrapping or unwrapping needs to happen when a
// funcref or anyref flows across the JS/wasm boundary, and that functions have
// the necessary identity when observed from JS, and in the future, from wasm.
//
// Functions stored in tables, whether wasm tables or internal tables, can be
// stored in a form that optimizes for eg call speed, however.
//
// Reading a funcref from a funcref table, writing a funcref to a funcref table,
// and generating the value for a ref.func instruction are therefore nontrivial
// operations that require mapping between the canonical JSFunction and the
// optimized table representation.  Once we get an instruction to call a
// ref.func directly it too will require such a mapping.

// In many cases, a FuncRef is exactly the same as AnyRef and we can use AnyRef
// functionality on funcref values.  The FuncRef class exists mostly to add more
// checks and to make it clear, when we need to, that we're manipulating funcref
// values.  FuncRef does not currently subclass AnyRef because there's been no
// need to, but it probably could.

class FuncRef {
  // mutable so that tracing may access a JSFunction* from a `const FuncRef`
  mutable JSFunction* value_;

  explicit FuncRef() : value_((JSFunction*)-1) {}
  explicit FuncRef(JSFunction* p) : value_(p) {
    MOZ_ASSERT(((uintptr_t)p & 0x03) == 0);
  }

 public:
  // Given a void* that comes from compiled wasm code, turn it into FuncRef.
  static FuncRef fromCompiledCode(void* p) { return FuncRef((JSFunction*)p); }

  // Given a JSFunction* that comes from JS, turn it into FuncRef.
  static FuncRef fromJSFunction(JSFunction* p) { return FuncRef(p); }

  // Given an AnyRef that represents a possibly-null funcref, turn it into a
  // FuncRef.
  static FuncRef fromAnyRefUnchecked(AnyRef p);

  AnyRef asAnyRef() { return AnyRef::fromJSObject((JSObject*)value_); }

  void* forCompiledCode() const { return value_; }

  JSFunction* asJSFunction() { return value_; }

  bool isNull() const { return value_ == nullptr; }

  void trace(JSTracer* trc) const;
};

using RootedFuncRef = Rooted<FuncRef>;
using HandleFuncRef = Handle<FuncRef>;
using MutableHandleFuncRef = MutableHandle<FuncRef>;

// Given any FuncRef, unbox it as a JS Value -- always a JSFunction*.

Value UnboxFuncRef(FuncRef val);

// The LitVal class represents a single WebAssembly value of a given value
// type, mostly for the purpose of numeric literals and initializers. A LitVal
// does not directly map to a JS value since there is not (currently) a precise
// representation of i64 values. A LitVal may contain non-canonical NaNs since,
// within WebAssembly, floats are not canonicalized. Canonicalization must
// happen at the JS boundary.

class LitVal {
 public:
  union Cell {
    uint32_t i32_;
    uint64_t i64_;
    float f32_;
    double f64_;
    wasm::V128 v128_;
    wasm::AnyRef ref_;

    Cell() : v128_() {}
    ~Cell() = default;

    WASM_CHECK_CACHEABLE_POD(i32_, i64_, f32_, f64_, v128_);
    WASM_ALLOW_NON_CACHEABLE_POD_FIELD(
        ref_,
        "The pointer value in ref_ is guaranteed to always be null in a "
        "LitVal.");
  };

 protected:
  ValType type_;
  Cell cell_;

 public:
  LitVal() : type_(ValType()), cell_{} {}

  explicit LitVal(ValType type) : type_(type) {
    switch (type.kind()) {
      case ValType::Kind::I32: {
        cell_.i32_ = 0;
        break;
      }
      case ValType::Kind::I64: {
        cell_.i64_ = 0;
        break;
      }
      case ValType::Kind::F32: {
        cell_.f32_ = 0;
        break;
      }
      case ValType::Kind::F64: {
        cell_.f64_ = 0;
        break;
      }
      case ValType::Kind::V128: {
        new (&cell_.v128_) V128();
        break;
      }
      case ValType::Kind::Ref: {
        cell_.ref_ = AnyRef::null();
        break;
      }
    }
  }

  explicit LitVal(uint32_t i32) : type_(ValType::I32) { cell_.i32_ = i32; }
  explicit LitVal(uint64_t i64) : type_(ValType::I64) { cell_.i64_ = i64; }

  explicit LitVal(float f32) : type_(ValType::F32) { cell_.f32_ = f32; }
  explicit LitVal(double f64) : type_(ValType::F64) { cell_.f64_ = f64; }

  explicit LitVal(V128 v128) : type_(ValType::V128) { cell_.v128_ = v128; }

  explicit LitVal(ValType type, AnyRef any) : type_(type) {
    MOZ_ASSERT(type.isRefRepr());
    MOZ_ASSERT(any.isNull(),
               "use Val for non-nullptr ref types to get tracing");
    cell_.ref_ = any;
  }

  ValType type() const { return type_; }
  static constexpr size_t sizeofLargestValue() { return sizeof(cell_); }

  Cell& cell() { return cell_; }
  const Cell& cell() const { return cell_; }

  uint32_t i32() const {
    MOZ_ASSERT(type_ == ValType::I32);
    return cell_.i32_;
  }
  uint64_t i64() const {
    MOZ_ASSERT(type_ == ValType::I64);
    return cell_.i64_;
  }
  const float& f32() const {
    MOZ_ASSERT(type_ == ValType::F32);
    return cell_.f32_;
  }
  const double& f64() const {
    MOZ_ASSERT(type_ == ValType::F64);
    return cell_.f64_;
  }
  AnyRef ref() const {
    MOZ_ASSERT(type_.isRefRepr());
    return cell_.ref_;
  }
  const V128& v128() const {
    MOZ_ASSERT(type_ == ValType::V128);
    return cell_.v128_;
  }

  WASM_DECLARE_FRIEND_SERIALIZE(LitVal);
};

WASM_DECLARE_CACHEABLE_POD(LitVal::Cell);

// A Val is a LitVal that can contain (non-null) pointers to GC things. All Vals
// must be used with the rooting APIs as they may contain JS objects.

class MOZ_NON_PARAM Val : public LitVal {
 public:
  Val() : LitVal() {}
  explicit Val(ValType type) : LitVal(type) {}
  explicit Val(const LitVal& val);
  explicit Val(uint32_t i32) : LitVal(i32) {}
  explicit Val(uint64_t i64) : LitVal(i64) {}
  explicit Val(float f32) : LitVal(f32) {}
  explicit Val(double f64) : LitVal(f64) {}
  explicit Val(V128 v128) : LitVal(v128) {}
  explicit Val(ValType type, AnyRef val) : LitVal(type, AnyRef::null()) {
    MOZ_ASSERT(type.isRefRepr());
    cell_.ref_ = val;
  }
  explicit Val(ValType type, FuncRef val) : LitVal(type, AnyRef::null()) {
    MOZ_ASSERT(type.refType().isFuncHierarchy());
    cell_.ref_ = val.asAnyRef();
  }

  Val(const Val&) = default;
  Val& operator=(const Val&) = default;

  bool operator==(const Val& rhs) const {
    if (type_ != rhs.type_) {
      return false;
    }
    switch (type_.kind()) {
      case ValType::I32:
        return cell_.i32_ == rhs.cell_.i32_;
      case ValType::I64:
        return cell_.i64_ == rhs.cell_.i64_;
      case ValType::F32:
        return cell_.f32_ == rhs.cell_.f32_;
      case ValType::F64:
        return cell_.f64_ == rhs.cell_.f64_;
      case ValType::V128:
        return cell_.v128_ == rhs.cell_.v128_;
      case ValType::Ref:
        return cell_.ref_ == rhs.cell_.ref_;
    }
    MOZ_ASSERT_UNREACHABLE();
    return false;
  }
  bool operator!=(const Val& rhs) const { return !(*this == rhs); }

  bool isJSObject() const {
    return type_.isValid() && type_.isRefRepr() && !cell_.ref_.isNull();
  }

  JSObject* asJSObject() const {
    MOZ_ASSERT(isJSObject());
    return cell_.ref_.asJSObject();
  }

  JSObject** asJSObjectAddress() const {
    return cell_.ref_.asJSObjectAddress();
  }

  // Read from `loc` which is a rooted location and needs no barriers.
  void readFromRootedLocation(const void* loc);

  // Initialize from `loc` which is a rooted location and needs no barriers.
  void initFromRootedLocation(ValType type, const void* loc);
  void initFromHeapLocation(ValType type, const void* loc);

  // Write to `loc` which is a rooted location and needs no barriers.
  void writeToRootedLocation(void* loc, bool mustWrite64) const;

  // Read from `loc` which is in the heap.
  void readFromHeapLocation(const void* loc);
  // Write to `loc` which is in the heap and must be barriered.
  void writeToHeapLocation(void* loc) const;

  // See the comment for `ToWebAssemblyValue` below.
  static bool fromJSValue(JSContext* cx, ValType targetType, HandleValue val,
                          MutableHandle<Val> rval);
  // See the comment for `ToJSValue` below.
  bool toJSValue(JSContext* cx, MutableHandleValue rval) const;

  void trace(JSTracer* trc) const;
};

using GCPtrVal = GCPtr<Val>;
using RootedVal = Rooted<Val>;
using HandleVal = Handle<Val>;
using MutableHandleVal = MutableHandle<Val>;

using ValVector = GCVector<Val, 0, SystemAllocPolicy>;
using RootedValVector = Rooted<ValVector>;
using HandleValVector = Handle<ValVector>;
using MutableHandleValVector = MutableHandle<ValVector>;

template <int N>
using ValVectorN = GCVector<Val, N, SystemAllocPolicy>;
template <int N>
using RootedValVectorN = Rooted<ValVectorN<N>>;

// Check a value against the given reference type.  If the targetType
// is RefType::Extern then the test always passes, but the value may be boxed.
// If the test passes then the value is stored either in fnval (for
// RefType::Func) or in refval (for other types); this split is not strictly
// necessary but is convenient for the users of this function.
//
// This can return false if the type check fails, or if a boxing into AnyRef
// throws an OOM.
[[nodiscard]] extern bool CheckRefType(JSContext* cx, RefType targetType,
                                       HandleValue v,
                                       MutableHandleFunction fnval,
                                       MutableHandleAnyRef refval);

// The same as above for when the target type is 'funcref'.
[[nodiscard]] extern bool CheckFuncRefValue(JSContext* cx, HandleValue v,
                                            MutableHandleFunction fun);

// The same as above for when the target type is 'anyref'.
[[nodiscard]] extern bool CheckAnyRefValue(JSContext* cx, HandleValue v,
                                           MutableHandleAnyRef vp);

// The same as above for when the target type is 'nullexternref'.
[[nodiscard]] extern bool CheckNullExternRefValue(JSContext* cx, HandleValue v,
                                                  MutableHandleAnyRef vp);

// The same as above for when the target type is 'nullfuncref'.
[[nodiscard]] extern bool CheckNullFuncRefValue(JSContext* cx, HandleValue v,
                                                MutableHandleFunction fun);

// The same as above for when the target type is 'nullref'.
[[nodiscard]] extern bool CheckNullRefValue(JSContext* cx, HandleValue v,
                                            MutableHandleAnyRef vp);

// The same as above for when the target type is 'eqref'.
[[nodiscard]] extern bool CheckEqRefValue(JSContext* cx, HandleValue v,
                                          MutableHandleAnyRef vp);

// The same as above for when the target type is 'structref'.
[[nodiscard]] extern bool CheckStructRefValue(JSContext* cx, HandleValue v,
                                              MutableHandleAnyRef vp);

// The same as above for when the target type is 'arrayref'.
[[nodiscard]] extern bool CheckArrayRefValue(JSContext* cx, HandleValue v,
                                             MutableHandleAnyRef vp);

// The same as above for when the target type is '(ref T)'.
[[nodiscard]] extern bool CheckTypeRefValue(JSContext* cx,
                                            const TypeDef* typeDef,
                                            HandleValue v,
                                            MutableHandleAnyRef vp);
class NoDebug;
class DebugCodegenVal;

// The level of coercion to apply in `ToWebAssemblyValue` and `ToJSValue`.
enum class CoercionLevel {
  // The default coercions given by the JS-API specification.
  Spec,
  // Allow for the coercions given by `Spec` but also use WebAssembly.Global
  // as a container for lossless conversions. This is only available through
  // the wasmLosslessInvoke testing function and is used in tests.
  Lossless,
};

// Coercion function from a JS value to a WebAssembly value [1].
//
// This function may fail for any of the following reasons:
//  * The input value has an incorrect type for the targetType
//  * The targetType is not exposable
//  * An OOM ocurred
// An error will be set upon failure.
//
// [1] https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue
template <typename Debug = NoDebug>
extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, FieldType type,
                               void* loc, bool mustWrite64,
                               CoercionLevel level = CoercionLevel::Spec);
template <typename Debug = NoDebug>
extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, ValType type,
                               void* loc, bool mustWrite64,
                               CoercionLevel level = CoercionLevel::Spec);

// Coercion function from a WebAssembly value to a JS value [1].
//
// This function will only fail if an OOM ocurred. If the type of WebAssembly
// value being coerced is not exposable to JS, then it will be coerced to
// 'undefined'. Callers are responsible for guarding against this if this is
// not desirable.
//
// [1] https://webassembly.github.io/spec/js-api/index.html#tojsvalue
template <typename Debug = NoDebug>
extern bool ToJSValue(JSContext* cx, const void* src, FieldType type,
                      MutableHandleValue dst,
                      CoercionLevel level = CoercionLevel::Spec);
template <typename Debug = NoDebug>
extern bool ToJSValueMayGC(FieldType type);
template <typename Debug = NoDebug>
extern bool ToJSValue(JSContext* cx, const void* src, ValType type,
                      MutableHandleValue dst,
                      CoercionLevel level = CoercionLevel::Spec);
template <typename Debug = NoDebug>
extern bool ToJSValueMayGC(ValType type);
}  // namespace wasm

template <>
struct InternalBarrierMethods<wasm::Val> {
  STATIC_ASSERT_ANYREF_IS_JSOBJECT;

  static bool isMarkable(const wasm::Val& v) { return v.isJSObject(); }

  static void preBarrier(const wasm::Val& v) {
    if (v.isJSObject()) {
      gc::PreWriteBarrier(v.asJSObject());
    }
  }

  static MOZ_ALWAYS_INLINE void postBarrier(wasm::Val* vp,
                                            const wasm::Val& prev,
                                            const wasm::Val& next) {
    MOZ_RELEASE_ASSERT(!prev.type().isValid() || prev.type() == next.type());
    JSObject* prevObj = prev.isJSObject() ? prev.asJSObject() : nullptr;
    JSObject* nextObj = next.isJSObject() ? next.asJSObject() : nullptr;
    if (nextObj) {
      JSObject::postWriteBarrier(vp->asJSObjectAddress(), prevObj, nextObj);
    }
  }

  static void readBarrier(const wasm::Val& v) {
    if (v.isJSObject()) {
      gc::ReadBarrier(v.asJSObject());
    }
  }

#ifdef DEBUG
  static void assertThingIsNotGray(const wasm::Val& v) {
    if (v.isJSObject()) {
      JS::AssertObjectIsNotGray(v.asJSObject());
    }
  }
#endif
};

template <>
struct InternalBarrierMethods<wasm::AnyRef> {
  STATIC_ASSERT_ANYREF_IS_JSOBJECT;

  static bool isMarkable(const wasm::AnyRef v) { return !v.isNull(); }

  static void preBarrier(const wasm::AnyRef v) {
    if (!v.isNull()) {
      gc::PreWriteBarrier(v.asJSObject());
    }
  }

  static MOZ_ALWAYS_INLINE void postBarrier(wasm::AnyRef* vp,
                                            const wasm::AnyRef prev,
                                            const wasm::AnyRef next) {
    JSObject* prevObj = !prev.isNull() ? prev.asJSObject() : nullptr;
    JSObject* nextObj = !next.isNull() ? next.asJSObject() : nullptr;
    if (nextObj) {
      JSObject::postWriteBarrier(vp->asJSObjectAddress(), prevObj, nextObj);
    }
  }

  static void readBarrier(const wasm::AnyRef v) {
    if (!v.isNull()) {
      gc::ReadBarrier(v.asJSObject());
    }
  }

#ifdef DEBUG
  static void assertThingIsNotGray(const wasm::AnyRef v) {
    if (!v.isNull()) {
      JS::AssertObjectIsNotGray(v.asJSObject());
    }
  }
#endif
};

}  // namespace js

#endif  // wasm_val_h