summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ArrayBufferObject.h
blob: d2d94d6722ea80b3458d95951c184d6dcc156403 (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
653
654
655
656
657
658
659
660
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_ArrayBufferObject_h
#define vm_ArrayBufferObject_h

#include "mozilla/Maybe.h"

#include <tuple>  // std::tuple

#include "builtin/TypedArrayConstants.h"
#include "gc/Memory.h"
#include "gc/ZoneAllocator.h"
#include "js/ArrayBuffer.h"
#include "js/GCHashTable.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/SharedMem.h"
#include "wasm/WasmMemory.h"

namespace js {

class ArrayBufferViewObject;
class AutoSetNewObjectMetadata;
class WasmArrayRawBuffer;

namespace wasm {
struct MemoryDesc;
}  // namespace wasm

// Create a new mapping of size `mappedSize` with an initially committed prefix
// of size `initialCommittedSize`.  Both arguments denote bytes and must be
// multiples of the page size, with `initialCommittedSize` <= `mappedSize`.
// Returns nullptr on failure.
void* MapBufferMemory(wasm::IndexType, size_t mappedSize,
                      size_t initialCommittedSize);

// Commit additional memory in an existing mapping.  `dataEnd` must be the
// correct value for the end of the existing committed area, and `delta` must be
// a byte amount to grow the mapping by, and must be a multiple of the page
// size.  Returns false on failure.
bool CommitBufferMemory(void* dataEnd, size_t delta);

// Extend an existing mapping by adding uncommited pages to it.  `dataStart`
// must be the pointer to the start of the existing mapping, `mappedSize` the
// size of the existing mapping, and `newMappedSize` the size of the extended
// mapping (sizes in bytes), with `mappedSize` <= `newMappedSize`.  Both sizes
// must be divisible by the page size.  Returns false on failure.
bool ExtendBufferMapping(void* dataStart, size_t mappedSize,
                         size_t newMappedSize);

// Remove an existing mapping.  `dataStart` must be the pointer to the start of
// the mapping, and `mappedSize` the size of that mapping.
void UnmapBufferMemory(wasm::IndexType t, void* dataStart, size_t mappedSize);

// Return the number of bytes currently reserved for WebAssembly memory
uint64_t WasmReservedBytes();

// The inheritance hierarchy for the various classes relating to typed arrays
// is as follows.
//
//
// - JSObject
//   - TypedObject (declared in wasm/TypedObject.h)
//   - NativeObject
//     - ArrayBufferObjectMaybeShared
//       - ArrayBufferObject
//       - SharedArrayBufferObject
//     - ArrayBufferViewObject
//       - DataViewObject
//       - TypedArrayObject (declared in vm/TypedArrayObject.h)
//         - TypedArrayObjectTemplate
//           - Int8ArrayObject
//           - Uint8ArrayObject
//           - ...
//
// Note that |TypedArrayObjectTemplate| is just an implementation
// detail that makes implementing its various subclasses easier.
//
// ArrayBufferObject and SharedArrayBufferObject are unrelated data types:
// the racy memory of the latter cannot substitute for the non-racy memory of
// the former; the non-racy memory of the former cannot be used with the
// atomics; the former can be detached and the latter not.  Hence they have been
// separated completely.
//
// Most APIs will only accept ArrayBufferObject.  ArrayBufferObjectMaybeShared
// exists as a join point to allow APIs that can take or use either, notably
// AsmJS.
//
// In contrast with the separation of ArrayBufferObject and
// SharedArrayBufferObject, the TypedArray types can map either.
//
// The possible data ownership and reference relationships with ArrayBuffers
// and related classes are enumerated below. These are the possible locations
// for typed data:
//
// (1) malloc'ed or mmap'ed data owned by an ArrayBufferObject.
// (2) Data allocated inline with an ArrayBufferObject.
// (3) Data allocated inline with a TypedArrayObject.
// (4) Data allocated inline with an InlineTypedObject.
//
// An ArrayBufferObject may point to any of these sources of data, except (3).
// All array buffer views may point to any of these sources of data, except
// that (3) may only be pointed to by the typed array the data is inline with.
//
// During a minor GC, (3) and (4) may move. During a compacting GC, (2), (3),
// and (4) may move.

class ArrayBufferObjectMaybeShared;

wasm::IndexType WasmArrayBufferIndexType(
    const ArrayBufferObjectMaybeShared* buf);
wasm::Pages WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf);
wasm::Pages WasmArrayBufferClampedMaxPages(
    const ArrayBufferObjectMaybeShared* buf);
mozilla::Maybe<wasm::Pages> WasmArrayBufferSourceMaxPages(
    const ArrayBufferObjectMaybeShared* buf);
size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);

class ArrayBufferObjectMaybeShared : public NativeObject {
 public:
  inline size_t byteLength() const;
  inline bool isDetached() const;
  inline SharedMem<uint8_t*> dataPointerEither();

  // WebAssembly support:
  // Note: the eventual goal is to remove this from ArrayBuffer and have
  // (Shared)ArrayBuffers alias memory owned by some wasm::Memory object.

  wasm::IndexType wasmIndexType() const {
    return WasmArrayBufferIndexType(this);
  }
  wasm::Pages wasmPages() const { return WasmArrayBufferPages(this); }
  wasm::Pages wasmClampedMaxPages() const {
    return WasmArrayBufferClampedMaxPages(this);
  }
  mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const {
    return WasmArrayBufferSourceMaxPages(this);
  }
  size_t wasmMappedSize() const { return WasmArrayBufferMappedSize(this); }

  inline bool isPreparedForAsmJS() const;
  inline bool isWasm() const;
};

using RootedArrayBufferObjectMaybeShared =
    Rooted<ArrayBufferObjectMaybeShared*>;
using HandleArrayBufferObjectMaybeShared =
    Handle<ArrayBufferObjectMaybeShared*>;
using MutableHandleArrayBufferObjectMaybeShared =
    MutableHandle<ArrayBufferObjectMaybeShared*>;

/*
 * ArrayBufferObject
 *
 * This class holds the underlying raw buffer that the various ArrayBufferViews
 * (eg DataViewObject, the TypedArrays, TypedObjects) access. It can be created
 * explicitly and used to construct an ArrayBufferView, or can be created
 * lazily when it is first accessed for a TypedArrayObject or TypedObject that
 * doesn't have an explicit buffer.
 *
 * ArrayBufferObject (or really the underlying memory) /is not racy/: the
 * memory is private to a single worker.
 */
class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
  static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);

 public:
  static const uint8_t DATA_SLOT = 0;
  static const uint8_t BYTE_LENGTH_SLOT = 1;
  static const uint8_t FIRST_VIEW_SLOT = 2;
  static const uint8_t FLAGS_SLOT = 3;

  static const uint8_t RESERVED_SLOTS = 4;

  static const size_t ARRAY_BUFFER_ALIGNMENT = 8;

  static_assert(FLAGS_SLOT == JS_ARRAYBUFFER_FLAGS_SLOT,
                "self-hosted code with burned-in constants must get the "
                "right flags slot");

  // The length of an ArrayBuffer or SharedArrayBuffer can be at most INT32_MAX
  // on 32-bit platforms. Allow a larger limit on 64-bit platforms.
  static constexpr size_t MaxByteLengthForSmallBuffer = INT32_MAX;
#ifdef JS_64BIT
  static constexpr size_t MaxByteLength =
      size_t(8) * 1024 * 1024 * 1024;  // 8 GB.
#else
  static constexpr size_t MaxByteLength = MaxByteLengthForSmallBuffer;
#endif

  /** The largest number of bytes that can be stored inline. */
  static constexpr size_t MaxInlineBytes =
      (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value);

 public:
  enum BufferKind {
    /** Inline data kept in the repurposed slots of this ArrayBufferObject. */
    INLINE_DATA = 0b000,

    /* Data allocated using the SpiderMonkey allocator. */
    MALLOCED = 0b001,

    /**
     * No bytes are associated with this buffer.  (This could be because the
     * buffer is detached, because it's an internal, newborn buffer not yet
     * overwritten with user-exposable semantics, or some other reason.  The
     * point is, don't read precise language semantics into this kind.)
     */
    NO_DATA = 0b010,

    /**
     * User-owned memory.  The associated buffer must be manually detached
     * before the user invalidates (deallocates, reuses the storage of, &c.)
     * the user-owned memory.
     */
    USER_OWNED = 0b011,

    WASM = 0b100,
    MAPPED = 0b101,
    EXTERNAL = 0b110,

    // These kind-values are currently invalid.  We intend to expand valid
    // BufferKinds in the future to either partly or fully use these values.
    BAD1 = 0b111,

    KIND_MASK = 0b111
  };

 public:
  enum ArrayBufferFlags {
    // The flags also store the BufferKind
    BUFFER_KIND_MASK = BufferKind::KIND_MASK,

    DETACHED = 0b1000,

    // This MALLOCED, MAPPED, or EXTERNAL buffer has been prepared for asm.js
    // and cannot henceforth be transferred/detached.  (WASM, USER_OWNED, and
    // INLINE_DATA buffers can't be prepared for asm.js -- although if an
    // INLINE_DATA buffer is used with asm.js, it's silently rewritten into a
    // MALLOCED buffer which *can* be prepared.)
    FOR_ASMJS = 0b10'0000,
  };

  static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
                "self-hosted code with burned-in constants must use the "
                "correct DETACHED bit value");

 protected:
  enum class FillContents { Zero, Uninitialized };

  template <FillContents FillType>
  static std::tuple<ArrayBufferObject*, uint8_t*> createBufferAndData(
      JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata&,
      JS::Handle<JSObject*> proto = nullptr);

 public:
  class BufferContents {
    uint8_t* data_;
    BufferKind kind_;
    JS::BufferContentsFreeFunc free_;
    void* freeUserData_;

    friend class ArrayBufferObject;

    BufferContents(uint8_t* data, BufferKind kind,
                   JS::BufferContentsFreeFunc freeFunc = nullptr,
                   void* freeUserData = nullptr)
        : data_(data),
          kind_(kind),
          free_(freeFunc),
          freeUserData_(freeUserData) {
      MOZ_ASSERT((kind_ & ~KIND_MASK) == 0);
      MOZ_ASSERT_IF(free_ || freeUserData_, kind_ == EXTERNAL);

      // It is the caller's responsibility to ensure that the
      // BufferContents does not outlive the data.
    }

   public:
    static BufferContents createInlineData(void* data) {
      return BufferContents(static_cast<uint8_t*>(data), INLINE_DATA);
    }

    static BufferContents createMalloced(void* data) {
      return BufferContents(static_cast<uint8_t*>(data), MALLOCED);
    }

    static BufferContents createNoData() {
      return BufferContents(nullptr, NO_DATA);
    }

    static BufferContents createUserOwned(void* data) {
      return BufferContents(static_cast<uint8_t*>(data), USER_OWNED);
    }

    static BufferContents createWasm(void* data) {
      return BufferContents(static_cast<uint8_t*>(data), WASM);
    }

    static BufferContents createMapped(void* data) {
      return BufferContents(static_cast<uint8_t*>(data), MAPPED);
    }

    static BufferContents createExternal(void* data,
                                         JS::BufferContentsFreeFunc freeFunc,
                                         void* freeUserData = nullptr) {
      return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, freeFunc,
                            freeUserData);
    }

    static BufferContents createFailed() {
      // There's no harm in tagging this as MALLOCED, even tho obviously it
      // isn't.  And adding an extra tag purely for this case is a complication
      // that presently appears avoidable.
      return BufferContents(nullptr, MALLOCED);
    }

    uint8_t* data() const { return data_; }
    BufferKind kind() const { return kind_; }
    JS::BufferContentsFreeFunc freeFunc() const { return free_; }
    void* freeUserData() const { return freeUserData_; }

    explicit operator bool() const { return data_ != nullptr; }
    WasmArrayRawBuffer* wasmBuffer() const;
  };

  static const JSClass class_;
  static const JSClass protoClass_;

  static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);

  static bool fun_isView(JSContext* cx, unsigned argc, Value* vp);

  static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);

  static bool isOriginalByteLengthGetter(Native native) {
    return native == byteLengthGetter;
  }

  static ArrayBufferObject* createForContents(JSContext* cx, size_t nbytes,
                                              BufferContents contents);

  static ArrayBufferObject* copy(
      JSContext* cx, JS::Handle<ArrayBufferObject*> unwrappedArrayBuffer);

  static ArrayBufferObject* createZeroed(JSContext* cx, size_t nbytes,
                                         HandleObject proto = nullptr);

  // Create an ArrayBufferObject that is safely finalizable and can later be
  // initialize()d to become a real, content-visible ArrayBufferObject.
  static ArrayBufferObject* createEmpty(JSContext* cx);

  // Create an ArrayBufferObject using the provided buffer and size.  Assumes
  // ownership of |buffer| even in case of failure, i.e. on failure |buffer|
  // is deallocated.
  static ArrayBufferObject* createFromNewRawBuffer(JSContext* cx,
                                                   WasmArrayRawBuffer* buffer,
                                                   size_t initialSize);

  static void copyData(Handle<ArrayBufferObject*> toBuffer, size_t toIndex,
                       Handle<ArrayBufferObject*> fromBuffer, size_t fromIndex,
                       size_t count);

  static size_t objectMoved(JSObject* obj, JSObject* old);

  static uint8_t* stealMallocedContents(JSContext* cx,
                                        Handle<ArrayBufferObject*> buffer);

  static BufferContents extractStructuredCloneContents(
      JSContext* cx, Handle<ArrayBufferObject*> buffer);

  static void addSizeOfExcludingThis(JSObject* obj,
                                     mozilla::MallocSizeOf mallocSizeOf,
                                     JS::ClassInfo* info,
                                     JS::RuntimeSizes* runtimeSizes);

  // ArrayBufferObjects (strongly) store the first view added to them, while
  // later views are (weakly) stored in the compartment's InnerViewTable
  // below. Buffers usually only have one view, so this slot optimizes for
  // the common case. Avoiding entries in the InnerViewTable saves memory and
  // non-incrementalized sweep time.
  JSObject* firstView();

  bool addView(JSContext* cx, ArrayBufferViewObject* view);

  // Detach this buffer from its original memory.  (This necessarily makes
  // views of this buffer unusable for modifying that original memory.)
  static void detach(JSContext* cx, Handle<ArrayBufferObject*> buffer);

  static constexpr size_t offsetOfByteLengthSlot() {
    return getFixedSlotOffset(BYTE_LENGTH_SLOT);
  }
  static constexpr size_t offsetOfFlagsSlot() {
    return getFixedSlotOffset(FLAGS_SLOT);
  }

 private:
  void setFirstView(ArrayBufferViewObject* view);

  uint8_t* inlineDataPointer() const;

  struct FreeInfo {
    JS::BufferContentsFreeFunc freeFunc;
    void* freeUserData;
  };
  FreeInfo* freeInfo() const;

 public:
  uint8_t* dataPointer() const;
  SharedMem<uint8_t*> dataPointerShared() const;
  size_t byteLength() const;

  BufferContents contents() const {
    if (isExternal()) {
      return BufferContents(dataPointer(), EXTERNAL, freeInfo()->freeFunc,
                            freeInfo()->freeUserData);
    }
    return BufferContents(dataPointer(), bufferKind());
  }
  bool hasInlineData() const { return dataPointer() == inlineDataPointer(); }

  void releaseData(JS::GCContext* gcx);

  BufferKind bufferKind() const {
    return BufferKind(flags() & BUFFER_KIND_MASK);
  }

  bool isInlineData() const { return bufferKind() == INLINE_DATA; }
  bool isMalloced() const { return bufferKind() == MALLOCED; }
  bool isNoData() const { return bufferKind() == NO_DATA; }
  bool hasUserOwnedData() const { return bufferKind() == USER_OWNED; }

  bool isWasm() const { return bufferKind() == WASM; }
  bool isMapped() const { return bufferKind() == MAPPED; }
  bool isExternal() const { return bufferKind() == EXTERNAL; }

  bool isDetached() const { return flags() & DETACHED; }
  bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }

  // WebAssembly support:

  /**
   * Prepare this ArrayBuffer for use with asm.js.  Returns true on success,
   * false on failure.  This function reports no errors.
   */
  [[nodiscard]] bool prepareForAsmJS();

  size_t wasmMappedSize() const;

  wasm::IndexType wasmIndexType() const;
  wasm::Pages wasmPages() const;
  wasm::Pages wasmClampedMaxPages() const;
  mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const;

  [[nodiscard]] static bool wasmGrowToPagesInPlace(
      wasm::IndexType t, wasm::Pages newPages,
      Handle<ArrayBufferObject*> oldBuf,
      MutableHandle<ArrayBufferObject*> newBuf, JSContext* cx);
  [[nodiscard]] static bool wasmMovingGrowToPages(
      wasm::IndexType t, wasm::Pages newPages,
      Handle<ArrayBufferObject*> oldBuf,
      MutableHandle<ArrayBufferObject*> newBuf, JSContext* cx);
  static void wasmDiscard(Handle<ArrayBufferObject*> buf, uint64_t byteOffset,
                          uint64_t byteLength);

  static void finalize(JS::GCContext* gcx, JSObject* obj);

  static BufferContents createMappedContents(int fd, size_t offset,
                                             size_t length);

 protected:
  void setDataPointer(BufferContents contents);
  void setByteLength(size_t length);

  size_t associatedBytes() const;

  uint32_t flags() const;
  void setFlags(uint32_t flags);

  void setIsDetached() { setFlags(flags() | DETACHED); }
  void setIsPreparedForAsmJS() {
    MOZ_ASSERT(!isWasm());
    MOZ_ASSERT(!hasUserOwnedData());
    MOZ_ASSERT(!isInlineData());
    MOZ_ASSERT(isMalloced() || isMapped() || isExternal());
    setFlags(flags() | FOR_ASMJS);
  }

  void initialize(size_t byteLength, BufferContents contents) {
    setByteLength(byteLength);
    setFlags(0);
    setFirstView(nullptr);
    setDataPointer(contents);
  }

  void* initializeToInlineData(size_t byteLength) {
    void* data = inlineDataPointer();
    initialize(byteLength, BufferContents::createInlineData(data));
    return data;
  }
};

using RootedArrayBufferObject = Rooted<ArrayBufferObject*>;
using HandleArrayBufferObject = Handle<ArrayBufferObject*>;
using MutableHandleArrayBufferObject = MutableHandle<ArrayBufferObject*>;

// Create a buffer for a wasm memory, whose type is determined by
// memory.indexType().
bool CreateWasmBuffer(JSContext* cx, const wasm::MemoryDesc& memory,
                      MutableHandleArrayBufferObjectMaybeShared buffer);

// Per-compartment table that manages the relationship between array buffers
// and the views that use their storage.
class InnerViewTable {
 public:
  using ViewVector = GCVector<UnsafeBarePtr<JSObject*>, 1, ZoneAllocPolicy>;

  friend class ArrayBufferObject;

 private:
  // This key is a raw pointer and not a WeakHeapPtr because the post-barrier
  // would hold nursery-allocated entries live unconditionally. It is a very
  // common pattern in low-level and performance-oriented JavaScript to create
  // hundreds or thousands of very short lived temporary views on a larger
  // buffer; having to tenure all of these would be a catastrophic performance
  // regression. Thus, it is vital that nursery pointers in this map not be held
  // live. Special support is required in the minor GC, implemented in
  // sweepAfterMinorGC.
  using Map = GCHashMap<UnsafeBarePtr<JSObject*>, ViewVector,
                        StableCellHasher<JSObject*>, ZoneAllocPolicy>;

  // For all objects sharing their storage with some other view, this maps
  // the object to the list of such views. All entries in this map are weak.
  Map map;

  // List of keys from innerViews where either the source or at least one
  // target is in the nursery. The raw pointer to a JSObject is allowed here
  // because this vector is cleared after every minor collection. Users in
  // sweepAfterMinorCollection must be careful to use MaybeForwarded before
  // touching these pointers.
  Vector<JSObject*, 0, SystemAllocPolicy> nurseryKeys;

  // Whether nurseryKeys is a complete list.
  bool nurseryKeysValid;

  bool addView(JSContext* cx, ArrayBufferObject* buffer, JSObject* view);
  ViewVector* maybeViewsUnbarriered(ArrayBufferObject* obj);
  void removeViews(ArrayBufferObject* obj);

 public:
  explicit InnerViewTable(Zone* zone) : map(zone), nurseryKeysValid(true) {}

  // Remove references to dead objects in the table and update table entries
  // to reflect moved objects.
  bool traceWeak(JSTracer* trc);
  void sweepAfterMinorGC(JSTracer* trc);

  bool empty() const { return map.empty(); }

  bool needsSweepAfterMinorGC() const {
    return !nurseryKeys.empty() || !nurseryKeysValid;
  }

  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
};

template <typename Wrapper>
class MutableWrappedPtrOperations<InnerViewTable, Wrapper>
    : public WrappedPtrOperations<InnerViewTable, Wrapper> {
  InnerViewTable& table() { return static_cast<Wrapper*>(this)->get(); }

 public:
  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
    return table().sizeOfExcludingThis(mallocSizeOf);
  }
};

class WasmArrayRawBuffer {
  wasm::IndexType indexType_;
  wasm::Pages clampedMaxPages_;
  mozilla::Maybe<wasm::Pages> sourceMaxPages_;
  size_t mappedSize_;  // Not including the header page
  size_t length_;

 protected:
  WasmArrayRawBuffer(wasm::IndexType indexType, uint8_t* buffer,
                     wasm::Pages clampedMaxPages,
                     const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
                     size_t mappedSize, size_t length)
      : indexType_(indexType),
        clampedMaxPages_(clampedMaxPages),
        sourceMaxPages_(sourceMaxPages),
        mappedSize_(mappedSize),
        length_(length) {
    MOZ_ASSERT(buffer == dataPointer());
  }

 public:
  static WasmArrayRawBuffer* AllocateWasm(
      wasm::IndexType indexType, wasm::Pages initialPages,
      wasm::Pages clampedMaxPages,
      const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
      const mozilla::Maybe<size_t>& mappedSize);
  static void Release(void* mem);

  uint8_t* dataPointer() {
    uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
    return ptr + sizeof(WasmArrayRawBuffer);
  }

  static const WasmArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) {
    return reinterpret_cast<const WasmArrayRawBuffer*>(
        dataPtr - sizeof(WasmArrayRawBuffer));
  }

  static WasmArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) {
    return reinterpret_cast<WasmArrayRawBuffer*>(dataPtr -
                                                 sizeof(WasmArrayRawBuffer));
  }

  wasm::IndexType indexType() const { return indexType_; }

  uint8_t* basePointer() { return dataPointer() - gc::SystemPageSize(); }

  size_t mappedSize() const { return mappedSize_; }

  size_t byteLength() const { return length_; }

  wasm::Pages pages() const {
    return wasm::Pages::fromByteLengthExact(length_);
  }

  wasm::Pages clampedMaxPages() const { return clampedMaxPages_; }

  mozilla::Maybe<wasm::Pages> sourceMaxPages() const { return sourceMaxPages_; }

  [[nodiscard]] bool growToPagesInPlace(wasm::Pages newPages);

  [[nodiscard]] bool extendMappedSize(wasm::Pages maxPages);

  // Try and grow the mapped region of memory. Does not change current size.
  // Does not move memory if no space to grow.
  void tryGrowMaxPagesInPlace(wasm::Pages deltaMaxPages);

  // Discard a region of memory, zeroing the pages and releasing physical memory
  // back to the operating system. byteOffset and byteLen must be wasm page
  // aligned and in bounds. A discard of zero bytes will have no effect.
  void discard(size_t byteOffset, size_t byteLen);
};

}  // namespace js

template <>
bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const;

#endif  // vm_ArrayBufferObject_h