tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SharedArrayObject.h (17074B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef vm_SharedArrayObject_h
      8 #define vm_SharedArrayObject_h
      9 
     10 #include "mozilla/Atomics.h"
     11 
     12 #include "jstypes.h"
     13 
     14 #include "builtin/AtomicsObject.h"
     15 #include "gc/Memory.h"
     16 #include "vm/ArrayBufferObject.h"
     17 #include "wasm/WasmMemory.h"
     18 
     19 namespace js {
     20 
     21 class WasmSharedArrayRawBuffer;
     22 
     23 /*
     24 * SharedArrayRawBuffer
     25 *
     26 * A bookkeeping object always stored before the raw buffer. The buffer itself
     27 * is refcounted. SharedArrayBufferObjects and structured clone objects may hold
     28 * references.
     29 *
     30 * WasmSharedArrayRawBuffer is a derived class that's used for Wasm buffers.
     31 *
     32 * - Non-Wasm buffers are allocated with a single calloc allocation, like this:
     33 *
     34 *    |<------ sizeof ------>|<- length ->|
     35 *    | SharedArrayRawBuffer | data array |
     36 *
     37 * - Wasm buffers are allocated with MapBufferMemory (mmap), like this:
     38 *
     39 *           |<-------- sizeof -------->|<- length ->|
     40 *   | waste | WasmSharedArrayRawBuffer | data array | waste |
     41 *
     42 * Observe that if we want to map the data array on a specific address, such
     43 * as absolute zero (bug 1056027), then the {Wasm}SharedArrayRawBuffer cannot be
     44 * prefixed to the data array, it has to be a separate object, also in
     45 * shared memory.  (That would get rid of ~4KB of waste, as well.)  Very little
     46 * else would have to change throughout the engine, the SARB would point to
     47 * the data array using a constant pointer, instead of computing its
     48 * address.
     49 *
     50 * For Wasm buffers, length_ can change following initialization; it may grow
     51 * toward sourceMaxPages_. See extensive comments above WasmArrayRawBuffer in
     52 * ArrayBufferObject.cpp. length_ only grows when the lock is held.
     53 */
     54 class SharedArrayRawBuffer {
     55 protected:
     56  // Whether this is a WasmSharedArrayRawBuffer.
     57  bool isWasm_;
     58 
     59  // Whether this is a growable non-Wasm buffer. All wasm raw buffers are
     60  // growable, but must be grown through a wasm instruction or by getting
     61  // a GSAB object through wasmMemoryObj.toResizableBuffer().
     62  bool isGrowableJS_;
     63 
     64  mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
     65  mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> length_;
     66 
     67  // The header node of a circular doubly-linked list of structures
     68  // representing tasks waiting on some location within this buffer.
     69  FutexWaiterListHead waiters_;
     70 
     71 protected:
     72  SharedArrayRawBuffer(bool isGrowableJS, uint8_t* buffer, size_t length)
     73      : isWasm_(false),
     74        isGrowableJS_(isGrowableJS),
     75        refcount_(1),
     76        length_(length) {
     77    MOZ_ASSERT(buffer == dataPointerShared());
     78  }
     79 
     80  enum class WasmBuffer {};
     81 
     82  SharedArrayRawBuffer(WasmBuffer, uint8_t* buffer, size_t length)
     83      : isWasm_(true), isGrowableJS_(false), refcount_(1), length_(length) {
     84    MOZ_ASSERT(buffer == dataPointerShared());
     85  }
     86 
     87 public:
     88  static SharedArrayRawBuffer* Allocate(bool isGrowable, size_t length,
     89                                        size_t maxLength);
     90 
     91  inline WasmSharedArrayRawBuffer* toWasmBuffer();
     92 
     93  // This may be called from multiple threads.  The caller must take
     94  // care of mutual exclusion.
     95  FutexWaiterListNode* waiters() { return &waiters_; }
     96 
     97  inline SharedMem<uint8_t*> dataPointerShared() const;
     98 
     99  size_t volatileByteLength() const { return length_; }
    100 
    101  bool isWasm() const { return isWasm_; }
    102 
    103  bool isGrowableJS() const { return isGrowableJS_; }
    104 
    105  uint32_t refcount() const { return refcount_; }
    106 
    107  [[nodiscard]] bool addReference();
    108  void dropReference();
    109 
    110  // Try to grow this buffer to |newByteLength| bytes. Returns false when the
    111  // current byte length is larger than |newByteLength|. Otherwise atomically
    112  // changes the byte length to |newByteLength| and then returns true.
    113  //
    114  // This method DOES NOT perform any memory operations to allocate additional
    115  // space. The caller is responsible to ensure that the buffer has been
    116  // allocated with enough space to hold at least |newByteLength| bytes. IOW
    117  // this method merely sets the number of user accessible bytes of this buffer.
    118  bool growJS(size_t newByteLength);
    119 
    120  static size_t offsetOfByteLength() {
    121    return offsetof(SharedArrayRawBuffer, length_);
    122  }
    123 };
    124 
    125 class WasmSharedArrayRawBuffer : public SharedArrayRawBuffer {
    126 private:
    127  Mutex growLock_ MOZ_UNANNOTATED;
    128  // The address type of this buffer.
    129  wasm::AddressType addressType_;
    130  // The size of each wasm page in this buffer.
    131  wasm::PageSize pageSize_;
    132  // The maximum size of this buffer in wasm pages.
    133  wasm::Pages clampedMaxPages_;
    134  wasm::Pages sourceMaxPages_;
    135  size_t mappedSize_;  // Does not include the page for the header.
    136 
    137  uint8_t* basePointer() {
    138    SharedMem<uint8_t*> p = dataPointerShared() - gc::SystemPageSize();
    139    MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
    140    return p.unwrap(/* we trust you won't abuse it */);
    141  }
    142 
    143 protected:
    144  WasmSharedArrayRawBuffer(uint8_t* buffer, size_t length,
    145                           wasm::AddressType addressType,
    146                           wasm::PageSize pageSize, wasm::Pages clampedMaxPages,
    147                           wasm::Pages sourceMaxPages, size_t mappedSize)
    148      : SharedArrayRawBuffer(WasmBuffer{}, buffer, length),
    149        growLock_(mutexid::SharedArrayGrow),
    150        addressType_(addressType),
    151        pageSize_(pageSize),
    152        clampedMaxPages_(clampedMaxPages),
    153        sourceMaxPages_(sourceMaxPages),
    154        mappedSize_(mappedSize) {}
    155 
    156 public:
    157  friend class SharedArrayRawBuffer;
    158 
    159  class Lock;
    160  friend class Lock;
    161 
    162  class MOZ_RAII Lock {
    163    WasmSharedArrayRawBuffer* buf;
    164 
    165   public:
    166    explicit Lock(WasmSharedArrayRawBuffer* buf) : buf(buf) {
    167      buf->growLock_.lock();
    168    }
    169    ~Lock() { buf->growLock_.unlock(); }
    170  };
    171 
    172  static WasmSharedArrayRawBuffer* AllocateWasm(
    173      wasm::AddressType addressType, wasm::PageSize pageSize,
    174      wasm::Pages initialPages, wasm::Pages clampedMaxPages,
    175      const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
    176      const mozilla::Maybe<size_t>& mappedSize);
    177 
    178  static const WasmSharedArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) {
    179    return reinterpret_cast<const WasmSharedArrayRawBuffer*>(
    180        dataPtr - sizeof(WasmSharedArrayRawBuffer));
    181  }
    182 
    183  static WasmSharedArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) {
    184    return reinterpret_cast<WasmSharedArrayRawBuffer*>(
    185        dataPtr - sizeof(WasmSharedArrayRawBuffer));
    186  }
    187 
    188  wasm::AddressType wasmAddressType() const { return addressType_; }
    189  wasm::PageSize wasmPageSize() const { return pageSize_; }
    190 
    191  wasm::Pages volatileWasmPages() const {
    192    return wasm::Pages::fromByteLengthExact(length_, wasmPageSize());
    193  }
    194 
    195  wasm::Pages wasmClampedMaxPages() const { return clampedMaxPages_; }
    196  wasm::Pages wasmSourceMaxPages() const { return sourceMaxPages_; }
    197 
    198  size_t mappedSize() const { return mappedSize_; }
    199 
    200  size_t wasmClampedMaxByteLength() const {
    201    MOZ_ASSERT(isWasm());
    202    return wasmClampedMaxPages().byteLength();
    203  }
    204 
    205  bool wasmGrowToPagesInPlace(const Lock&, wasm::AddressType t,
    206                              wasm::Pages newPages);
    207 
    208  // Discard a region of memory, zeroing the pages and releasing physical memory
    209  // back to the operating system. byteOffset and byteLen must be wasm page
    210  // aligned and in bounds. A discard of zero bytes will have no effect.
    211  void discard(size_t byteOffset, size_t byteLen);
    212 };
    213 
    214 inline WasmSharedArrayRawBuffer* SharedArrayRawBuffer::toWasmBuffer() {
    215  MOZ_ASSERT(isWasm());
    216  return static_cast<WasmSharedArrayRawBuffer*>(this);
    217 }
    218 
    219 inline SharedMem<uint8_t*> SharedArrayRawBuffer::dataPointerShared() const {
    220  uint8_t* ptr =
    221      reinterpret_cast<uint8_t*>(const_cast<SharedArrayRawBuffer*>(this));
    222  ptr += isWasm() ? sizeof(WasmSharedArrayRawBuffer)
    223                  : sizeof(SharedArrayRawBuffer);
    224  return SharedMem<uint8_t*>::shared(ptr);
    225 }
    226 
    227 class FixedLengthSharedArrayBufferObject;
    228 class GrowableSharedArrayBufferObject;
    229 
    230 /*
    231 * SharedArrayBufferObject
    232 *
    233 * When transferred to a WebWorker, the buffer is not detached on the
    234 * parent side, and both child and parent reference the same buffer.
    235 *
    236 * The underlying memory is memory-mapped and reference counted
    237 * (across workers and/or processes).  The SharedArrayBuffer object
    238 * has a finalizer that decrements the refcount, the last one to leave
    239 * (globally) unmaps the memory.  The sender ups the refcount before
    240 * transmitting the memory to another worker.
    241 *
    242 * SharedArrayBufferObject (or really the underlying memory) /is
    243 * racy/: more than one worker can access the memory at the same time.
    244 *
    245 * A TypedArrayObject (a view) references a SharedArrayBuffer
    246 * and keeps it alive.  The SharedArrayBuffer does /not/ reference its
    247 * views.
    248 *
    249 * SharedArrayBufferObject is an abstract base class and has exactly two
    250 * concrete subclasses, FixedLengthSharedArrayBufferObject and
    251 * GrowableSharedArrayBufferObject.
    252 */
    253 class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared {
    254  static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
    255  static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
    256  static bool growableGetterImpl(JSContext* cx, const CallArgs& args);
    257  static bool growImpl(JSContext* cx, const CallArgs& args);
    258  static bool sliceImpl(JSContext* cx, const CallArgs& args);
    259 
    260 public:
    261  // RAWBUF_SLOT holds a pointer (as "private" data) to the
    262  // SharedArrayRawBuffer object, which is manually managed storage.
    263  static const uint8_t RAWBUF_SLOT = 0;
    264 
    265  // LENGTH_SLOT holds the length of the underlying buffer as it was when this
    266  // object was created.  For JS use cases this is the same length as the
    267  // buffer, but for Wasm the buffer can grow, and the buffer's length may be
    268  // greater than the object's length.
    269  static const uint8_t LENGTH_SLOT = 1;
    270 
    271  static_assert(LENGTH_SLOT == ArrayBufferObject::BYTE_LENGTH_SLOT,
    272                "JIT code assumes the same slot is used for the length");
    273 
    274  static const uint8_t RESERVED_SLOTS = 2;
    275 
    276  static const JSClass protoClass_;
    277 
    278  static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
    279 
    280  static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
    281 
    282  static bool growableGetter(JSContext* cx, unsigned argc, Value* vp);
    283 
    284  static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
    285 
    286  static bool grow(JSContext* cx, unsigned argc, Value* vp);
    287 
    288  static bool slice(JSContext* cx, unsigned argc, Value* vp);
    289 
    290 private:
    291  template <class SharedArrayBufferType>
    292  static SharedArrayBufferType* NewWith(JSContext* cx,
    293                                        SharedArrayRawBuffer* buffer,
    294                                        size_t length, HandleObject proto);
    295 
    296 public:
    297  // Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
    298  static FixedLengthSharedArrayBufferObject* New(JSContext* cx, size_t length,
    299                                                 HandleObject proto = nullptr);
    300 
    301  // Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer,
    302  // recording the given length in the SharedArrayBufferObject.
    303  static FixedLengthSharedArrayBufferObject* New(JSContext* cx,
    304                                                 SharedArrayRawBuffer* buffer,
    305                                                 size_t length,
    306                                                 HandleObject proto = nullptr);
    307 
    308  // Create a growable SharedArrayBufferObject with a new SharedArrayRawBuffer.
    309  static GrowableSharedArrayBufferObject* NewGrowable(
    310      JSContext* cx, size_t length, size_t maxLength,
    311      HandleObject proto = nullptr);
    312 
    313  // Create a growable SharedArrayBufferObject using an existing
    314  // SharedArrayRawBuffer, recording the given length in the
    315  // SharedArrayBufferObject.
    316  static GrowableSharedArrayBufferObject* NewGrowable(
    317      JSContext* cx, SharedArrayRawBuffer* buffer, size_t maxLength,
    318      HandleObject proto = nullptr);
    319 
    320  static void Finalize(JS::GCContext* gcx, JSObject* obj);
    321 
    322  static void addSizeOfExcludingThis(JSObject* obj,
    323                                     mozilla::MallocSizeOf mallocSizeOf,
    324                                     JS::ClassInfo* info,
    325                                     JS::RuntimeSizes* runtimeSizes);
    326 
    327  static void copyData(ArrayBufferObjectMaybeShared* toBuffer, size_t toIndex,
    328                       ArrayBufferObjectMaybeShared* fromBuffer,
    329                       size_t fromIndex, size_t count);
    330 
    331  SharedArrayRawBuffer* rawBufferObject() const;
    332 
    333  WasmSharedArrayRawBuffer* rawWasmBufferObject() const {
    334    return rawBufferObject()->toWasmBuffer();
    335  }
    336 
    337  // Invariant: This method does not cause GC and can be called
    338  // without anchoring the object it is called on.
    339  uintptr_t globalID() const {
    340    // The buffer address is good enough as an ID provided the memory is not
    341    // shared between processes or, if it is, it is mapped to the same address
    342    // in every process.  (At the moment, shared memory cannot be shared between
    343    // processes.)
    344    return dataPointerShared().asValue();
    345  }
    346 
    347 protected:
    348  size_t growableByteLength() const {
    349    MOZ_ASSERT(isGrowable());
    350    return rawBufferObject()->volatileByteLength();
    351  }
    352 
    353 private:
    354  bool isInitialized() const {
    355    bool initialized = getFixedSlot(RAWBUF_SLOT).isDouble();
    356    MOZ_ASSERT_IF(initialized, getFixedSlot(LENGTH_SLOT).isDouble());
    357    return initialized;
    358  }
    359 
    360 public:
    361  // Returns either the byte length for fixed-length shared arrays. Or the
    362  // maximum byte length for growable shared arrays.
    363  size_t byteLengthOrMaxByteLength() const {
    364    return size_t(getFixedSlot(LENGTH_SLOT).toPrivate());
    365  }
    366 
    367  size_t byteLength() const {
    368    if (isGrowable()) {
    369      return growableByteLength();
    370    }
    371    return byteLengthOrMaxByteLength();
    372  }
    373 
    374  wasm::AddressType wasmAddressType() const {
    375    return rawWasmBufferObject()->wasmAddressType();
    376  }
    377 
    378  wasm::PageSize wasmPageSize() const {
    379    return rawWasmBufferObject()->wasmPageSize();
    380  }
    381 
    382  bool isWasm() const { return rawBufferObject()->isWasm(); }
    383 
    384  bool isGrowable() const { return is<GrowableSharedArrayBufferObject>(); }
    385 
    386  SharedMem<uint8_t*> dataPointerShared() const {
    387    return rawBufferObject()->dataPointerShared();
    388  }
    389 
    390  static constexpr int rawBufferOffset() {
    391    return NativeObject::getFixedSlotOffset(RAWBUF_SLOT);
    392  }
    393 
    394  // WebAssembly support:
    395 
    396  // Create a SharedArrayBufferObject using the provided buffer and size.
    397  // Assumes ownership of a reference to |buffer| even in case of failure,
    398  // i.e. on failure |buffer->dropReference()| is performed.
    399  static SharedArrayBufferObject* createFromNewRawBuffer(
    400      JSContext* cx, WasmSharedArrayRawBuffer* buffer, size_t initialSize);
    401 
    402  // Create an SharedArrayBufferObject object (growable or fixed-length),
    403  // using the same buffer as in the wasmBuffer object.
    404  template <typename SharedArrayBufferType>
    405  static SharedArrayBufferType* createFromWasmObject(
    406      JSContext* cx, Handle<SharedArrayBufferObject*> wasmBuffer);
    407 
    408  wasm::Pages volatileWasmPages() const {
    409    return rawWasmBufferObject()->volatileWasmPages();
    410  }
    411  wasm::Pages wasmClampedMaxPages() const {
    412    return rawWasmBufferObject()->wasmClampedMaxPages();
    413  }
    414  wasm::Pages wasmSourceMaxPages() const {
    415    return rawWasmBufferObject()->wasmSourceMaxPages();
    416  }
    417 
    418  size_t wasmMappedSize() const { return rawWasmBufferObject()->mappedSize(); }
    419 
    420  static void wasmDiscard(Handle<SharedArrayBufferObject*> buf,
    421                          uint64_t byteOffset, uint64_t byteLength);
    422 
    423 private:
    424  [[nodiscard]] bool acceptRawBuffer(SharedArrayRawBuffer* buffer,
    425                                     size_t length);
    426  void dropRawBuffer();
    427 };
    428 
    429 /**
    430 * FixedLengthSharedArrayBufferObject
    431 *
    432 * SharedArrayBuffer object with a fixed length. The JS exposed length is
    433 * unmodifiable, but the underlying memory can still grow for WebAssembly.
    434 *
    435 * Fixed-length SharedArrayBuffers can be used for asm.js and WebAssembly.
    436 */
    437 class FixedLengthSharedArrayBufferObject : public SharedArrayBufferObject {
    438 public:
    439  static const JSClass class_;
    440 
    441  size_t byteLength() const { return byteLengthOrMaxByteLength(); }
    442 };
    443 
    444 /**
    445 * GrowableSharedArrayBufferObject
    446 *
    447 * SharedArrayBuffer object which can grow in size. The maximum byte length it
    448 * can grow to is set when creating the object.
    449 *
    450 * Growable SharedArrayBuffers can neither be used for asm.js nor WebAssembly.
    451 */
    452 class GrowableSharedArrayBufferObject : public SharedArrayBufferObject {
    453 public:
    454  static const JSClass class_;
    455 
    456  size_t byteLength() const { return growableByteLength(); }
    457 
    458  size_t maxByteLength() const { return byteLengthOrMaxByteLength(); }
    459 };
    460 
    461 }  // namespace js
    462 
    463 template <>
    464 inline bool JSObject::is<js::SharedArrayBufferObject>() const {
    465  return is<js::FixedLengthSharedArrayBufferObject>() ||
    466         is<js::GrowableSharedArrayBufferObject>();
    467 }
    468 
    469 #endif  // vm_SharedArrayObject_h