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