summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ArrayBufferObject.h
blob: 5aa96bf887220e98c93332bb2f7ff89e5ff42527 (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
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
/* -*- 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 JS_PUBLIC_API GenericPrinter;
class JSONPrinter;

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
//   - NativeObject
//     - ArrayBufferObjectMaybeShared
//       - ArrayBufferObject
//         - FixedLengthArrayBufferObject
//         - ResizableArrayBufferObject
//       - SharedArrayBufferObject
//         - FixedLengthSharedArrayBufferObject
//         - GrowableSharedArrayBufferObject
//     - ArrayBufferViewObject
//       - DataViewObject
//         - FixedLengthDataViewObject
//         - ResizableDataViewObject
//       - TypedArrayObject (declared in vm/TypedArrayObject.h)
//         - FixedLengthTypedArrayObject
//           - FixedLengthTypedArrayObjectTemplate<NativeType>, also inheriting
//             from TypedArrayObjectTemplate<NativeType>
//             - FixedLengthTypedArrayObjectTemplate<int8_t>
//             - FixedLengthTypedArrayObjectTemplate<uint8_t>
//             - ...
//         - ResizableTypedArrayObject
//           - ResizableTypedArrayObjectTemplate<NativeType>, also inheriting
//             from TypedArrayObjectTemplate<NativeType>
//             - ResizableTypedArrayObjectTemplate<int8_t>
//             - ResizableTypedArrayObjectTemplate<uint8_t>
//             - ...
//
// Note that |{FixedLength,Resizable}TypedArrayObjectTemplate| is just an
// implementation detail that makes implementing its various subclasses easier.
//
// FixedLengthArrayBufferObject and ResizableArrayBufferObject are also
// implementation specific types to differentiate between fixed-length and
// resizable ArrayBuffers.
//
// 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.
//
// 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) may move. During a compacting GC, (2) and (3) 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 bool isResizable() const;
  inline SharedMem<uint8_t*> dataPointerEither();

  inline bool pinLength(bool pin);

  // 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;
};

class FixedLengthArrayBufferObject;
class ResizableArrayBufferObject;

/*
 * ArrayBufferObject
 *
 * This class holds the underlying raw buffer that the various ArrayBufferViews
 * (DataViewObject and the TypedArrays) 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 that doesn't have an explicit buffer.
 *
 * ArrayBufferObject is an abstract base class and has exactly two concrete
 * subclasses, FixedLengthArrayBufferObject and ResizableArrayBufferObject.
 *
 * 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);
  static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
  static bool resizableGetterImpl(JSContext* cx, const CallArgs& args);
  static bool detachedGetterImpl(JSContext* cx, const CallArgs& args);
  static bool resizeImpl(JSContext* cx, const CallArgs& args);
  static bool transferImpl(JSContext* cx, const CallArgs& args);
  static bool transferToFixedLengthImpl(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 ByteLengthLimitForSmallBuffer = INT32_MAX;
#ifdef JS_64BIT
  static constexpr size_t ByteLengthLimit =
      size_t(8) * 1024 * 1024 * 1024;  // 8 GB.
#else
  static constexpr size_t ByteLengthLimit = ByteLengthLimitForSmallBuffer;
#endif

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

    /*
     * Data allocated using the SpiderMonkey allocator, created within
     * js::ArrayBufferContentsArena.
     */
    MALLOCED_ARRAYBUFFER_CONTENTS_ARENA = 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,

    /**
     * Data allocated using the SpiderMonkey allocator, created within an
     * unknown memory arena.
     */
    MALLOCED_UNKNOWN_ARENA = 0b111,

    KIND_MASK = 0b111
  };

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

    DETACHED = 0b1000,

    // Resizable ArrayBuffer.
    RESIZABLE = 0b1'0000,

    // 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,

    // The length is temporarily pinned, so it should not be detached. In the
    // future, this will also prevent GrowableArrayBuffer/ResizeableArrayBuffer
    // from modifying the length while this is set.
    PINNED_LENGTH = 0b100'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 <class ArrayBufferType, FillContents FillType>
  static std::tuple<ArrayBufferType*, uint8_t*>
  createUninitializedBufferAndData(JSContext* cx, size_t nbytes,
                                   AutoSetNewObjectMetadata&,
                                   JS::Handle<JSObject*> proto);

  template <FillContents FillType>
  static std::tuple<ArrayBufferObject*, uint8_t*> createBufferAndData(
      JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata,
      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 createMallocedArrayBufferContentsArena(void* data) {
      return BufferContents(static_cast<uint8_t*>(data),
                            MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);
    }

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

    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) {
      MOZ_ASSERT(freeFunc);
      return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, freeFunc,
                            freeUserData);
    }

    static BufferContents createFailed() {
      // There's no harm in tagging this as MALLOCED_ARRAYBUFFER_CONTENTS_ARENA,
      // 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_ARRAYBUFFER_CONTENTS_ARENA);
    }

    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 protoClass_;

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

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

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

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

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

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

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

  static bool transferToFixedLength(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, size_t newByteLength,
                                 JS::Handle<ArrayBufferObject*> source);

  static ArrayBufferObject* copyAndDetach(
      JSContext* cx, size_t newByteLength,
      JS::Handle<ArrayBufferObject*> source);

 private:
  static ArrayBufferObject* copyAndDetachSteal(
      JSContext* cx, JS::Handle<ArrayBufferObject*> source);

  static ArrayBufferObject* copyAndDetachRealloc(
      JSContext* cx, size_t newByteLength,
      JS::Handle<ArrayBufferObject*> source);

 public:
  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(ArrayBufferObject* toBuffer, size_t toIndex,
                       ArrayBufferObject* fromBuffer, size_t fromIndex,
                       size_t count);

  template <class ArrayBufferType>
  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);

  // Pin or unpin the length. Returns whether pinned status was changed.
  bool pinLength(bool pin) {
    if (bool(flags() & PINNED_LENGTH) == pin) {
      return false;
    }
    setFlags(flags() ^ PINNED_LENGTH);
    return true;
  }

  static bool ensureNonInline(JSContext* cx, Handle<ArrayBufferObject*> buffer);

  // 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);
  }

 protected:
  void setFirstView(ArrayBufferViewObject* view);

 private:
  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());
  }

  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_ARRAYBUFFER_CONTENTS_ARENA ||
           bufferKind() == MALLOCED_UNKNOWN_ARENA;
  }
  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 isResizable() const { return flags() & RESIZABLE; }
  bool isLengthPinned() const { return flags() & PINNED_LENGTH; }
  bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }

  // Only WASM and asm.js buffers have a non-undefined [[ArrayBufferDetachKey]].
  //
  // https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances
  bool hasDefinedDetachKey() const { return isWasm() || isPreparedForAsmJS(); }

  // 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 ArrayBufferObject* wasmGrowToPagesInPlace(
      wasm::IndexType t, wasm::Pages newPages,
      Handle<ArrayBufferObject*> oldBuf, JSContext* cx);
  [[nodiscard]] static ArrayBufferObject* wasmMovingGrowToPages(
      wasm::IndexType t, wasm::Pages newPages,
      Handle<ArrayBufferObject*> oldBuf, 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);

  /**
   * Return the byte length for fixed-length buffers or the maximum byte length
   * for resizable buffers.
   */
  inline size_t maxByteLength() const;

  size_t associatedBytes() const;

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

  void setIsDetached() {
    MOZ_ASSERT(!(flags() & PINNED_LENGTH));
    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);
  }

 public:
#if defined(DEBUG) || defined(JS_JITSPEW)
  void dumpOwnFields(js::JSONPrinter& json) const;
  void dumpOwnStringContent(js::GenericPrinter& out) const;
#endif
};

/**
 * FixedLengthArrayBufferObject
 *
 * ArrayBuffer object with a fixed length. Its length is unmodifiable, except
 * when zeroing it for detached buffers. Supports all possible memory stores
 * for ArrayBuffer objects, including inline data, malloc'ed memory, mapped
 * memory, and user-owner memory.
 *
 * Fixed-length ArrayBuffers can be used for asm.js and WebAssembly.
 */
class FixedLengthArrayBufferObject : public ArrayBufferObject {
  friend class ArrayBufferObject;

  uint8_t* inlineDataPointer() const;

  bool hasInlineData() const { return dataPointer() == inlineDataPointer(); }

 public:
  // Fixed-length ArrayBuffer objects don't have any additional reserved slots.
  static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS;

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

  static const JSClass class_;
};

/**
 * ResizableArrayBufferObject
 *
 * ArrayBuffer object which can both grow and shrink. The maximum byte length it
 * can grow to is set when creating the object. The data of resizable
 * ArrayBuffer object is either stored inline or malloc'ed memory.
 *
 * When a resizable ArrayBuffer object is detached, its maximum byte length
 * slot is set to zero in addition to the byte length slot.
 *
 * Resizable ArrayBuffers can neither be used for asm.js nor WebAssembly.
 */
class ResizableArrayBufferObject : public ArrayBufferObject {
  friend class ArrayBufferObject;

  template <FillContents FillType>
  static std::tuple<ResizableArrayBufferObject*, uint8_t*> createBufferAndData(
      JSContext* cx, size_t byteLength, size_t maxByteLength,
      AutoSetNewObjectMetadata& metadata, Handle<JSObject*> proto);

  static ResizableArrayBufferObject* createEmpty(JSContext* cx);

 public:
  static ResizableArrayBufferObject* createZeroed(
      JSContext* cx, size_t byteLength, size_t maxByteLength,
      Handle<JSObject*> proto = nullptr);

 private:
  uint8_t* inlineDataPointer() const;

  bool hasInlineData() const { return dataPointer() == inlineDataPointer(); }

  void setMaxByteLength(size_t length) {
    MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit);
    setFixedSlot(MAX_BYTE_LENGTH_SLOT, PrivateValue(length));
  }

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

  // Resize this buffer.
  void resize(size_t newByteLength);

  static ResizableArrayBufferObject* copy(
      JSContext* cx, size_t newByteLength,
      JS::Handle<ResizableArrayBufferObject*> source);

 public:
  static const uint8_t MAX_BYTE_LENGTH_SLOT = ArrayBufferObject::RESERVED_SLOTS;

  static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS + 1;

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

  static const JSClass class_;

  size_t maxByteLength() const {
    return size_t(getFixedSlot(MAX_BYTE_LENGTH_SLOT).toPrivate());
  }

  static ResizableArrayBufferObject* copyAndDetach(
      JSContext* cx, size_t newByteLength,
      JS::Handle<ResizableArrayBufferObject*> source);

 private:
  static ResizableArrayBufferObject* copyAndDetachSteal(
      JSContext* cx, size_t newByteLength,
      JS::Handle<ResizableArrayBufferObject*> source);
};

size_t ArrayBufferObject::maxByteLength() const {
  if (isResizable()) {
    return as<ResizableArrayBufferObject>().maxByteLength();
  }
  return byteLength();
}

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

// Per-compartment table that manages the relationship between array buffers
// and the views that use their storage.
class InnerViewTable {
  // Store views in a vector such that all the tenured views come before any
  // nursery views. Maintain the index of the first nursery view so there is an
  // efficient way to access only the nursery views.
  using ViewVector =
      GCVector<UnsafeBarePtr<ArrayBufferViewObject*>, 1, ZoneAllocPolicy>;
  struct Views {
    ViewVector views;  // List of views with tenured views at the front.
    size_t firstNurseryView = 0;

    explicit Views(JS::Zone* zone) : views(zone) {}
    bool empty();
    bool hasNurseryViews();
    bool addView(ArrayBufferViewObject* view);

    bool traceWeak(JSTracer* trc, size_t startIndex = 0);
    bool sweepAfterMinorGC(JSTracer* trc);

    void check();
  };

  // 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.
  //
  // 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 ArrayBufferViewMap =
      GCHashMap<UnsafeBarePtr<ArrayBufferObject*>, Views,
                StableCellHasher<JSObject*>, ZoneAllocPolicy>;
  ArrayBufferViewMap 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<ArrayBufferObject*, 0, SystemAllocPolicy> nurseryKeys;

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

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

  // 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);

 private:
  friend class ArrayBufferObject;
  friend class ResizableArrayBufferObject;
  bool addView(JSContext* cx, ArrayBufferObject* buffer,
               ArrayBufferViewObject* view);
  ViewVector* maybeViewsUnbarriered(ArrayBufferObject* buffer);
  void removeViews(ArrayBufferObject* buffer);
};

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 <>
inline bool JSObject::is<js::ArrayBufferObject>() const {
  return is<js::FixedLengthArrayBufferObject>() ||
         is<js::ResizableArrayBufferObject>();
}

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

#endif  // vm_ArrayBufferObject_h