ArrayBufferObject.h (41132B)
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_ArrayBufferObject_h 8 #define vm_ArrayBufferObject_h 9 10 #include "mozilla/Maybe.h" 11 12 #include <tuple> // std::tuple 13 14 #include "builtin/TypedArrayConstants.h" 15 #include "gc/Memory.h" 16 #include "gc/ZoneAllocator.h" 17 #include "js/ArrayBuffer.h" 18 #include "js/GCHashTable.h" 19 #include "vm/JSFunction.h" 20 #include "vm/JSObject.h" 21 #include "vm/SharedMem.h" 22 #include "wasm/WasmMemory.h" 23 24 namespace js { 25 26 class JS_PUBLIC_API GenericPrinter; 27 class JSONPrinter; 28 29 class ArrayBufferViewObject; 30 class AutoSetNewObjectMetadata; 31 class FixedLengthTypedArrayObject; 32 class WasmArrayRawBuffer; 33 34 namespace wasm { 35 struct MemoryDesc; 36 } // namespace wasm 37 38 // Create a new mapping of size `mappedSize` with an initially committed prefix 39 // of size `initialCommittedSize`. Both arguments denote bytes and must be 40 // multiples of the page size, with `initialCommittedSize` <= `mappedSize`. 41 // Returns nullptr on failure. 42 void* MapBufferMemory(wasm::AddressType, wasm::PageSize, size_t mappedSize, 43 size_t initialCommittedSize); 44 45 // Commit additional memory in an existing mapping. `dataEnd` must be the 46 // correct value for the end of the existing committed area, and `delta` must be 47 // a byte amount to grow the mapping by, and must be a multiple of the page 48 // size. Returns false on failure. 49 bool CommitBufferMemory(void* dataEnd, size_t delta); 50 51 // Remove an existing mapping. `dataStart` must be the pointer to the start of 52 // the mapping, and `mappedSize` the size of that mapping. 53 void UnmapBufferMemory(wasm::AddressType t, void* dataStart, size_t mappedSize, 54 size_t committedSize); 55 56 // Return the number of bytes currently reserved for WebAssembly memory 57 uint64_t WasmReservedBytes(); 58 59 // The inheritance hierarchy for the various classes relating to typed arrays 60 // is as follows. 61 // 62 // 63 // - JSObject 64 // - NativeObject 65 // - ArrayBufferObjectMaybeShared 66 // - ArrayBufferObject 67 // - FixedLengthArrayBufferObject 68 // - ResizableArrayBufferObject 69 // - ImmutableArrayBufferObject 70 // - SharedArrayBufferObject 71 // - FixedLengthSharedArrayBufferObject 72 // - GrowableSharedArrayBufferObject 73 // - ArrayBufferViewObject 74 // - DataViewObject 75 // - FixedLengthDataViewObject 76 // - ResizableDataViewObject 77 // - ImmutableDataViewObject 78 // - TypedArrayObject (declared in vm/TypedArrayObject.h) 79 // - FixedLengthTypedArrayObject 80 // - FixedLengthTypedArrayObjectTemplate<NativeType>, also inheriting 81 // from TypedArrayObjectTemplate<NativeType> 82 // - FixedLengthTypedArrayObjectTemplate<int8_t> 83 // - FixedLengthTypedArrayObjectTemplate<uint8_t> 84 // - ... 85 // - ResizableTypedArrayObject 86 // - ResizableTypedArrayObjectTemplate<NativeType>, also inheriting 87 // from TypedArrayObjectTemplate<NativeType> 88 // - ResizableTypedArrayObjectTemplate<int8_t> 89 // - ResizableTypedArrayObjectTemplate<uint8_t> 90 // - ... 91 // - ImmutableTypedArrayObject 92 // - ImmutableTypedArrayObjectTemplate<NativeType>, also inheriting 93 // from TypedArrayObjectTemplate<NativeType> 94 // - ImmutableTypedArrayObjectTemplate<int8_t> 95 // - ImmutableTypedArrayObjectTemplate<uint8_t> 96 // - ... 97 // 98 // Note that |{FixedLength,Resizable,Immutable}TypedArrayObjectTemplate| is just 99 // an implementation detail that makes implementing its various subclasses 100 // easier. 101 // 102 // FixedLengthArrayBufferObject, ResizableArrayBufferObject, and 103 // ImmutableArrayBufferObject are also implementation specific types to 104 // differentiate between fixed-length, resizable, and immutable ArrayBuffers. 105 // 106 // ArrayBufferObject and SharedArrayBufferObject are unrelated data types: 107 // the racy memory of the latter cannot substitute for the non-racy memory of 108 // the former; the non-racy memory of the former cannot be used with the 109 // atomics; the former can be detached and the latter not. Hence they have been 110 // separated completely. 111 // 112 // Most APIs will only accept ArrayBufferObject. ArrayBufferObjectMaybeShared 113 // exists as a join point to allow APIs that can take or use either, notably 114 // AsmJS. 115 // 116 // In contrast with the separation of ArrayBufferObject and 117 // SharedArrayBufferObject, the TypedArray types can map either. 118 // 119 // The possible data ownership and reference relationships with ArrayBuffers 120 // and related classes are enumerated below. These are the possible locations 121 // for typed data: 122 // 123 // (1) malloc'ed or mmap'ed data owned by an ArrayBufferObject. 124 // (2) Data allocated inline with an ArrayBufferObject. 125 // (3) Data allocated inline with a TypedArrayObject. 126 // 127 // An ArrayBufferObject may point to any of these sources of data, except (3). 128 // All array buffer views may point to any of these sources of data, except 129 // that (3) may only be pointed to by the typed array the data is inline with. 130 // 131 // During a minor GC, (3) may move. During a compacting GC, (2) and (3) may 132 // move. 133 134 class ArrayBufferObjectMaybeShared; 135 136 wasm::AddressType WasmArrayBufferAddressType( 137 const ArrayBufferObjectMaybeShared* buf); 138 wasm::PageSize WasmArrayBufferPageSize(const ArrayBufferObjectMaybeShared* buf); 139 wasm::Pages WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf); 140 wasm::Pages WasmArrayBufferClampedMaxPages( 141 const ArrayBufferObjectMaybeShared* buf); 142 mozilla::Maybe<wasm::Pages> WasmArrayBufferSourceMaxPages( 143 const ArrayBufferObjectMaybeShared* buf); 144 size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf); 145 146 class ArrayBufferObjectMaybeShared : public NativeObject { 147 public: 148 inline size_t byteLength() const; 149 inline bool isDetached() const; 150 inline bool isResizable() const; 151 inline bool isImmutable() const; 152 inline SharedMem<uint8_t*> dataPointerEither(); 153 154 inline bool pinLength(bool pin); 155 156 // WebAssembly support: 157 // Note: the eventual goal is to remove this from ArrayBuffer and have 158 // (Shared)ArrayBuffers alias memory owned by some wasm::Memory object. 159 160 wasm::AddressType wasmAddressType() const { 161 return WasmArrayBufferAddressType(this); 162 } 163 wasm::PageSize wasmPageSize() const { return WasmArrayBufferPageSize(this); } 164 wasm::Pages wasmPages() const { return WasmArrayBufferPages(this); } 165 wasm::Pages wasmClampedMaxPages() const { 166 return WasmArrayBufferClampedMaxPages(this); 167 } 168 mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const { 169 return WasmArrayBufferSourceMaxPages(this); 170 } 171 size_t wasmMappedSize() const { return WasmArrayBufferMappedSize(this); } 172 173 inline bool isPreparedForAsmJS() const; 174 inline bool isWasm() const; 175 }; 176 177 class FixedLengthArrayBufferObject; 178 class ResizableArrayBufferObject; 179 class ImmutableArrayBufferObject; 180 181 /* 182 * ArrayBufferObject 183 * 184 * This class holds the underlying raw buffer that the various ArrayBufferViews 185 * (DataViewObject and the TypedArrays) access. It can be created explicitly and 186 * used to construct an ArrayBufferView, or can be created lazily when it is 187 * first accessed for a TypedArrayObject that doesn't have an explicit buffer. 188 * 189 * ArrayBufferObject is an abstract base class and has exactly three concrete 190 * subclasses: FixedLengthArrayBufferObject, ResizableArrayBufferObject, and 191 * ImmutableArrayBufferObject. 192 * 193 * ArrayBufferObject (or really the underlying memory) /is not racy/: the 194 * memory is private to a single worker. 195 */ 196 class ArrayBufferObject : public ArrayBufferObjectMaybeShared { 197 static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args); 198 static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args); 199 static bool resizableGetterImpl(JSContext* cx, const CallArgs& args); 200 static bool detachedGetterImpl(JSContext* cx, const CallArgs& args); 201 #ifdef NIGHTLY_BUILD 202 static bool immutableGetterImpl(JSContext* cx, const CallArgs& args); 203 #endif 204 static bool sliceImpl(JSContext* cx, const CallArgs& args); 205 #ifdef NIGHTLY_BUILD 206 static bool sliceToImmutableImpl(JSContext* cx, const CallArgs& args); 207 #endif 208 static bool resizeImpl(JSContext* cx, const CallArgs& args); 209 static bool transferImpl(JSContext* cx, const CallArgs& args); 210 static bool transferToFixedLengthImpl(JSContext* cx, const CallArgs& args); 211 #ifdef NIGHTLY_BUILD 212 static bool transferToImmutableImpl(JSContext* cx, const CallArgs& args); 213 #endif 214 215 public: 216 static const uint8_t DATA_SLOT = 0; 217 static const uint8_t BYTE_LENGTH_SLOT = 1; 218 static const uint8_t FIRST_VIEW_SLOT = 2; 219 static const uint8_t FLAGS_SLOT = 3; 220 221 static const uint8_t RESERVED_SLOTS = 4; 222 223 // Alignment for ArrayBuffer objects. Must match the largest possible 224 // TypedArray scalar to ensure TypedArray and Atomics accesses are always 225 // aligned. 226 static constexpr size_t ARRAY_BUFFER_ALIGNMENT = 8; 227 228 static_assert(FLAGS_SLOT == JS_ARRAYBUFFER_FLAGS_SLOT, 229 "self-hosted code with burned-in constants must get the " 230 "right flags slot"); 231 232 // The length of an ArrayBuffer or SharedArrayBuffer can be at most INT32_MAX 233 // on 32-bit platforms. Allow a larger limit on 64-bit platforms. 234 static constexpr size_t ByteLengthLimitForSmallBuffer = INT32_MAX; 235 #ifdef JS_64BIT 236 static constexpr size_t ByteLengthLimit = 237 size_t(16) * 1024 * 1024 * 1024; // 16 GB. 238 #else 239 static constexpr size_t ByteLengthLimit = ByteLengthLimitForSmallBuffer; 240 #endif 241 242 public: 243 enum BufferKind { 244 /** Inline data kept in the repurposed slots of this ArrayBufferObject. */ 245 INLINE_DATA = 0b000, 246 247 /* 248 * Data allocated using the SpiderMonkey allocator, created within 249 * js::ArrayBufferContentsArena. 250 */ 251 MALLOCED_ARRAYBUFFER_CONTENTS_ARENA = 0b001, 252 253 /** 254 * No bytes are associated with this buffer. (This could be because the 255 * buffer is detached, because it's an internal, newborn buffer not yet 256 * overwritten with user-exposable semantics, or some other reason. The 257 * point is, don't read precise language semantics into this kind.) 258 */ 259 NO_DATA = 0b010, 260 261 /** 262 * User-owned memory. The associated buffer must be manually detached 263 * before the user invalidates (deallocates, reuses the storage of, &c.) 264 * the user-owned memory. 265 */ 266 USER_OWNED = 0b011, 267 268 WASM = 0b100, 269 MAPPED = 0b101, 270 EXTERNAL = 0b110, 271 272 /** 273 * Data allocated using the SpiderMonkey allocator, created within an 274 * unknown memory arena. 275 */ 276 MALLOCED_UNKNOWN_ARENA = 0b111, 277 278 KIND_MASK = 0b111 279 }; 280 281 public: 282 enum ArrayBufferFlags { 283 // The flags also store the BufferKind 284 BUFFER_KIND_MASK = BufferKind::KIND_MASK, 285 286 DETACHED = 0b1000, 287 288 // Resizable ArrayBuffer. 289 RESIZABLE = 0b1'0000, 290 291 // This MALLOCED, MAPPED, or EXTERNAL buffer has been prepared for asm.js 292 // and cannot henceforth be transferred/detached. (WASM, USER_OWNED, and 293 // INLINE_DATA buffers can't be prepared for asm.js -- although if an 294 // INLINE_DATA buffer is used with asm.js, it's silently rewritten into a 295 // MALLOCED buffer which *can* be prepared.) 296 FOR_ASMJS = 0b10'0000, 297 298 // The length is temporarily pinned, so it should not be detached. In the 299 // future, this will also prevent GrowableArrayBuffer/ResizeableArrayBuffer 300 // from modifying the length while this is set. 301 PINNED_LENGTH = 0b100'0000, 302 303 // Immutable ArrayBuffer. 304 IMMUTABLE = 0b1000'0000, 305 }; 306 307 static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED, 308 "self-hosted code with burned-in constants must use the " 309 "correct DETACHED bit value"); 310 311 static_assert(JS_ARRAYBUFFER_IMMUTABLE_FLAG == IMMUTABLE, 312 "self-hosted code with burned-in constants must use the " 313 "correct IMMUTABLE bit value"); 314 315 protected: 316 enum class FillContents { Zero, Uninitialized }; 317 318 template <class ArrayBufferType, FillContents FillType> 319 static std::tuple<ArrayBufferType*, uint8_t*> 320 createUninitializedBufferAndData(JSContext* cx, size_t nbytes, 321 AutoSetNewObjectMetadata&, 322 JS::Handle<JSObject*> proto); 323 324 template <class ArrayBufferType, FillContents FillType> 325 static std::tuple<ArrayBufferType*, uint8_t*> createBufferAndData( 326 JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata, 327 JS::Handle<JSObject*> proto = nullptr); 328 329 public: 330 class BufferContents { 331 uint8_t* data_; 332 BufferKind kind_; 333 JS::BufferContentsFreeFunc free_; 334 void* freeUserData_; 335 336 friend class ArrayBufferObject; 337 friend class ResizableArrayBufferObject; 338 friend class ImmutableArrayBufferObject; 339 340 BufferContents(uint8_t* data, BufferKind kind, 341 JS::BufferContentsFreeFunc freeFunc = nullptr, 342 void* freeUserData = nullptr) 343 : data_(data), 344 kind_(kind), 345 free_(freeFunc), 346 freeUserData_(freeUserData) { 347 MOZ_ASSERT((kind_ & ~KIND_MASK) == 0); 348 MOZ_ASSERT_IF(free_ || freeUserData_, kind_ == EXTERNAL); 349 350 // It is the caller's responsibility to ensure that the 351 // BufferContents does not outlive the data. 352 } 353 354 #ifdef DEBUG 355 // Checks if the buffer contents are properly aligned. 356 // 357 // `malloc(0)` is implementation defined and may return a pointer which 358 // isn't aligned to `max_align_t`, so we only require proper alignment when 359 // `byteLength` is non-zero. 360 // 361 // jemalloc doesn't implement restriction, but instead uses `sizeof(void*)` 362 // for its smallest allocation class. Larger allocations are guaranteed to 363 // be eight byte aligned. 364 bool isAligned(size_t byteLength) const { 365 // `malloc(0)` has implementation defined behavior. 366 if (byteLength == 0) { 367 return true; 368 } 369 370 // Allow jemalloc tiny allocations to have smaller alignment requirements 371 // than `std::malloc`. 372 if (sizeof(void*) < ArrayBufferObject::ARRAY_BUFFER_ALIGNMENT) { 373 if (byteLength <= sizeof(void*)) { 374 return true; 375 } 376 } 377 378 // `std::malloc` returns memory at least as strictly aligned as for 379 // max_align_t and the alignment of max_align_t is a multiple of the array 380 // buffer alignment. 381 static_assert(alignof(std::max_align_t) % 382 ArrayBufferObject::ARRAY_BUFFER_ALIGNMENT == 383 0); 384 385 // Otherwise the memory must be correctly alignment. 386 auto ptr = reinterpret_cast<uintptr_t>(data()); 387 return ptr % ArrayBufferObject::ARRAY_BUFFER_ALIGNMENT == 0; 388 } 389 #endif 390 391 public: 392 static BufferContents createInlineData(void* data) { 393 return BufferContents(static_cast<uint8_t*>(data), INLINE_DATA); 394 } 395 396 static BufferContents createMallocedArrayBufferContentsArena(void* data) { 397 return BufferContents(static_cast<uint8_t*>(data), 398 MALLOCED_ARRAYBUFFER_CONTENTS_ARENA); 399 } 400 401 static BufferContents createMallocedUnknownArena(void* data) { 402 return BufferContents(static_cast<uint8_t*>(data), 403 MALLOCED_UNKNOWN_ARENA); 404 } 405 406 static BufferContents createNoData() { 407 return BufferContents(nullptr, NO_DATA); 408 } 409 410 static BufferContents createUserOwned(void* data) { 411 return BufferContents(static_cast<uint8_t*>(data), USER_OWNED); 412 } 413 414 static BufferContents createWasm(void* data) { 415 return BufferContents(static_cast<uint8_t*>(data), WASM); 416 } 417 418 static BufferContents createMapped(void* data) { 419 return BufferContents(static_cast<uint8_t*>(data), MAPPED); 420 } 421 422 static BufferContents createExternal(void* data, 423 JS::BufferContentsFreeFunc freeFunc, 424 void* freeUserData = nullptr) { 425 MOZ_ASSERT(freeFunc); 426 return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, freeFunc, 427 freeUserData); 428 } 429 430 static BufferContents createFailed() { 431 // There's no harm in tagging this as MALLOCED_ARRAYBUFFER_CONTENTS_ARENA, 432 // even tho obviously it isn't. And adding an extra tag purely for this 433 // case is a complication that presently appears avoidable. 434 return BufferContents(nullptr, MALLOCED_ARRAYBUFFER_CONTENTS_ARENA); 435 } 436 437 uint8_t* data() const { return data_; } 438 BufferKind kind() const { return kind_; } 439 JS::BufferContentsFreeFunc freeFunc() const { return free_; } 440 void* freeUserData() const { return freeUserData_; } 441 442 explicit operator bool() const { return data_ != nullptr; } 443 WasmArrayRawBuffer* wasmBuffer() const; 444 }; 445 446 static const JSClass protoClass_; 447 448 static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp); 449 450 static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp); 451 452 static bool resizableGetter(JSContext* cx, unsigned argc, Value* vp); 453 454 static bool detachedGetter(JSContext* cx, unsigned argc, Value* vp); 455 456 #ifdef NIGHTLY_BUILD 457 static bool immutableGetter(JSContext* cx, unsigned argc, Value* vp); 458 #endif 459 460 static bool fun_isView(JSContext* cx, unsigned argc, Value* vp); 461 462 static bool slice(JSContext* cx, unsigned argc, Value* vp); 463 464 #ifdef NIGHTLY_BUILD 465 static bool sliceToImmutable(JSContext* cx, unsigned argc, Value* vp); 466 #endif 467 468 static bool resize(JSContext* cx, unsigned argc, Value* vp); 469 470 static bool transfer(JSContext* cx, unsigned argc, Value* vp); 471 472 static bool transferToFixedLength(JSContext* cx, unsigned argc, Value* vp); 473 474 #ifdef NIGHTLY_BUILD 475 static bool transferToImmutable(JSContext* cx, unsigned argc, Value* vp); 476 #endif 477 478 static bool class_constructor(JSContext* cx, unsigned argc, Value* vp); 479 480 static ArrayBufferObject* createForContents(JSContext* cx, size_t nbytes, 481 BufferContents contents); 482 483 static ArrayBufferObject* createFromTypedArrayMallocedElements( 484 JSContext* cx, Handle<FixedLengthTypedArrayObject*> tarray); 485 486 protected: 487 template <class ArrayBufferType> 488 static ArrayBufferType* copy(JSContext* cx, size_t newByteLength, 489 JS::Handle<ArrayBufferObject*> source); 490 491 template <class ArrayBufferType> 492 static ArrayBufferType* copyAndDetach(JSContext* cx, size_t newByteLength, 493 JS::Handle<ArrayBufferObject*> source); 494 495 private: 496 template <class ArrayBufferType> 497 static ArrayBufferType* copyAndDetachSteal( 498 JSContext* cx, JS::Handle<ArrayBufferObject*> source); 499 500 template <class ArrayBufferType> 501 static ArrayBufferType* copyAndDetachRealloc( 502 JSContext* cx, size_t newByteLength, 503 JS::Handle<ArrayBufferObject*> source); 504 505 public: 506 static FixedLengthArrayBufferObject* createZeroed( 507 JSContext* cx, size_t nbytes, HandleObject proto = nullptr); 508 509 // Create an ArrayBufferObject that is safely finalizable and can later be 510 // initialize()d to become a real, content-visible ArrayBufferObject. 511 static FixedLengthArrayBufferObject* createEmpty(JSContext* cx); 512 513 // Create an ArrayBufferObject using the provided buffer and size. Assumes 514 // ownership of |buffer| even in case of failure, i.e. on failure |buffer| 515 // is deallocated. 516 static ArrayBufferObject* createFromNewRawBuffer(JSContext* cx, 517 WasmArrayRawBuffer* buffer, 518 size_t initialSize); 519 520 // Create an ArrayBufferObject object (resizable or fixed-length) based 521 // on the existing donor object. The |buffer| will be removed from the 522 // latter, and its ownership will be assumed by the new object. 523 template <typename ArrayBufferType> 524 static ArrayBufferType* createFromWasmObject( 525 JSContext* cx, Handle<ArrayBufferObject*> donor); 526 527 static void copyData(ArrayBufferObject* toBuffer, size_t toIndex, 528 ArrayBufferObject* fromBuffer, size_t fromIndex, 529 size_t count); 530 531 template <class ArrayBufferType> 532 static size_t objectMoved(JSObject* obj, JSObject* old); 533 534 static uint8_t* stealMallocedContents(JSContext* cx, 535 Handle<ArrayBufferObject*> buffer); 536 537 static BufferContents extractStructuredCloneContents( 538 JSContext* cx, Handle<ArrayBufferObject*> buffer); 539 540 static void addSizeOfExcludingThis(JSObject* obj, 541 mozilla::MallocSizeOf mallocSizeOf, 542 JS::ClassInfo* info, 543 JS::RuntimeSizes* runtimeSizes); 544 545 // ArrayBufferObjects (strongly) store the first view added to them, while 546 // later views are (weakly) stored in the compartment's InnerViewTable 547 // below. Buffers usually only have one view, so this slot optimizes for 548 // the common case. Avoiding entries in the InnerViewTable saves memory and 549 // non-incrementalized sweep time. 550 JSObject* firstView(); 551 552 bool addView(JSContext* cx, ArrayBufferViewObject* view); 553 554 // Pin or unpin the length. Returns whether pinned status was changed. 555 bool pinLength(bool pin) { 556 if (bool(flags() & PINNED_LENGTH) == pin) { 557 return false; 558 } 559 setFlags(flags() ^ PINNED_LENGTH); 560 return true; 561 } 562 563 static bool ensureNonInline(JSContext* cx, Handle<ArrayBufferObject*> buffer); 564 565 // Detach this buffer from its original memory. (This necessarily makes 566 // views of this buffer unusable for modifying that original memory.) 567 static void detach(JSContext* cx, Handle<ArrayBufferObject*> buffer); 568 569 static constexpr size_t offsetOfByteLengthSlot() { 570 return getFixedSlotOffset(BYTE_LENGTH_SLOT); 571 } 572 static constexpr size_t offsetOfFlagsSlot() { 573 return getFixedSlotOffset(FLAGS_SLOT); 574 } 575 576 protected: 577 void setFirstView(ArrayBufferViewObject* view); 578 579 private: 580 struct FreeInfo { 581 JS::BufferContentsFreeFunc freeFunc; 582 void* freeUserData; 583 }; 584 FreeInfo* freeInfo() const; 585 586 public: 587 uint8_t* dataPointer() const; 588 SharedMem<uint8_t*> dataPointerShared() const; 589 size_t byteLength() const; 590 591 BufferContents contents() const { 592 if (isExternal()) { 593 return BufferContents(dataPointer(), EXTERNAL, freeInfo()->freeFunc, 594 freeInfo()->freeUserData); 595 } 596 return BufferContents(dataPointer(), bufferKind()); 597 } 598 599 void releaseData(JS::GCContext* gcx); 600 601 BufferKind bufferKind() const { 602 return BufferKind(flags() & BUFFER_KIND_MASK); 603 } 604 605 bool isInlineData() const { return bufferKind() == INLINE_DATA; } 606 bool isMalloced() const { 607 return bufferKind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA || 608 bufferKind() == MALLOCED_UNKNOWN_ARENA; 609 } 610 bool isNoData() const { return bufferKind() == NO_DATA; } 611 bool hasUserOwnedData() const { return bufferKind() == USER_OWNED; } 612 613 bool isWasm() const { return bufferKind() == WASM; } 614 bool isMapped() const { return bufferKind() == MAPPED; } 615 bool isExternal() const { return bufferKind() == EXTERNAL; } 616 617 bool isDetached() const { return flags() & DETACHED; } 618 bool isResizable() const { return flags() & RESIZABLE; } 619 bool isLengthPinned() const { return flags() & PINNED_LENGTH; } 620 bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; } 621 bool isImmutable() const { return flags() & IMMUTABLE; } 622 623 // Only WASM and asm.js buffers have a non-undefined [[ArrayBufferDetachKey]]. 624 // 625 // https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances 626 bool hasDefinedDetachKey() const { return isWasm() || isPreparedForAsmJS(); } 627 628 // WebAssembly support: 629 630 /** 631 * Prepare this ArrayBuffer for use with asm.js. Returns true on success, 632 * false on failure. This function reports no errors. 633 */ 634 [[nodiscard]] bool prepareForAsmJS(); 635 636 size_t wasmMappedSize() const; 637 638 wasm::AddressType wasmAddressType() const; 639 wasm::PageSize wasmPageSize() const; 640 wasm::Pages wasmPages() const; 641 wasm::Pages wasmClampedMaxPages() const; 642 mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const; 643 644 [[nodiscard]] static ArrayBufferObject* wasmGrowToPagesInPlace( 645 wasm::AddressType t, wasm::Pages newPages, 646 Handle<ArrayBufferObject*> oldBuf, JSContext* cx); 647 [[nodiscard]] static ArrayBufferObject* wasmMovingGrowToPages( 648 wasm::AddressType t, wasm::Pages newPages, 649 Handle<ArrayBufferObject*> oldBuf, JSContext* cx); 650 static void wasmDiscard(Handle<ArrayBufferObject*> buf, uint64_t byteOffset, 651 uint64_t byteLength); 652 653 static void finalize(JS::GCContext* gcx, JSObject* obj); 654 655 static BufferContents createMappedContents(int fd, size_t offset, 656 size_t length); 657 658 protected: 659 void setDataPointer(BufferContents contents); 660 void setByteLength(size_t length); 661 662 /** 663 * Return the byte length for fixed-length buffers or the maximum byte length 664 * for resizable buffers. 665 */ 666 inline size_t maxByteLength() const; 667 668 size_t wasmClampedMaxByteLength() const { 669 MOZ_ASSERT(isWasm()); 670 return wasmClampedMaxPages().byteLength(); 671 } 672 673 size_t associatedBytes() const; 674 675 uint32_t flags() const; 676 void setFlags(uint32_t flags); 677 678 void setIsDetached() { 679 MOZ_ASSERT(!isLengthPinned()); 680 MOZ_ASSERT(!isImmutable()); 681 setFlags(flags() | DETACHED); 682 } 683 void setIsPreparedForAsmJS() { 684 MOZ_ASSERT(!isWasm()); 685 MOZ_ASSERT(!hasUserOwnedData()); 686 MOZ_ASSERT(!isInlineData()); 687 MOZ_ASSERT(isMalloced() || isMapped() || isExternal()); 688 setFlags(flags() | FOR_ASMJS); 689 } 690 691 void initialize(size_t byteLength, BufferContents contents) { 692 MOZ_ASSERT(contents.isAligned(byteLength)); 693 setByteLength(byteLength); 694 setFlags(0); 695 setFirstView(nullptr); 696 setDataPointer(contents); 697 } 698 699 public: 700 #if defined(DEBUG) || defined(JS_JITSPEW) 701 void dumpOwnFields(js::JSONPrinter& json) const; 702 void dumpOwnStringContent(js::GenericPrinter& out) const; 703 #endif 704 }; 705 706 /** 707 * FixedLengthArrayBufferObject 708 * 709 * ArrayBuffer object with a fixed length. Its length is unmodifiable, except 710 * when zeroing it for detached buffers. Supports all possible memory stores 711 * for ArrayBuffer objects, including inline data, malloc'ed memory, mapped 712 * memory, and user-owner memory. 713 * 714 * Fixed-length ArrayBuffers can be used for asm.js and WebAssembly. 715 */ 716 class FixedLengthArrayBufferObject : public ArrayBufferObject { 717 friend class ArrayBufferObject; 718 719 uint8_t* inlineDataPointer() const; 720 721 bool hasInlineData() const { return dataPointer() == inlineDataPointer(); } 722 723 public: 724 // Fixed-length ArrayBuffer objects don't have any additional reserved slots. 725 static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS; 726 727 /** The largest number of bytes that can be stored inline. */ 728 static constexpr size_t MaxInlineBytes = 729 (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value); 730 731 static const JSClass class_; 732 733 static FixedLengthArrayBufferObject* copy( 734 JSContext* cx, size_t newByteLength, 735 JS::Handle<ArrayBufferObject*> source) { 736 return ArrayBufferObject::copy<FixedLengthArrayBufferObject>( 737 cx, newByteLength, source); 738 } 739 740 static FixedLengthArrayBufferObject* copyAndDetach( 741 JSContext* cx, size_t newByteLength, 742 JS::Handle<ArrayBufferObject*> source) { 743 return ArrayBufferObject::copyAndDetach<FixedLengthArrayBufferObject>( 744 cx, newByteLength, source); 745 } 746 }; 747 748 /** 749 * ResizableArrayBufferObject 750 * 751 * ArrayBuffer object which can both grow and shrink. The maximum byte length it 752 * can grow to is set when creating the object. The data of resizable 753 * ArrayBuffer object is either stored inline or malloc'ed memory. 754 * 755 * When a resizable ArrayBuffer object is detached, its maximum byte length 756 * slot is set to zero in addition to the byte length slot. 757 * 758 * Resizable ArrayBuffers can neither be used for asm.js nor WebAssembly. 759 */ 760 class ResizableArrayBufferObject : public ArrayBufferObject { 761 friend class ArrayBufferObject; 762 763 template <FillContents FillType> 764 static std::tuple<ResizableArrayBufferObject*, uint8_t*> createBufferAndData( 765 JSContext* cx, size_t byteLength, size_t maxByteLength, 766 AutoSetNewObjectMetadata& metadata, Handle<JSObject*> proto); 767 768 static ResizableArrayBufferObject* createEmpty(JSContext* cx); 769 770 public: 771 static ResizableArrayBufferObject* createZeroed( 772 JSContext* cx, size_t byteLength, size_t maxByteLength, 773 Handle<JSObject*> proto = nullptr); 774 775 private: 776 uint8_t* inlineDataPointer() const; 777 778 bool hasInlineData() const { return dataPointer() == inlineDataPointer(); } 779 780 void setMaxByteLength(size_t length) { 781 MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit); 782 setFixedSlot(MAX_BYTE_LENGTH_SLOT, PrivateValue(length)); 783 } 784 785 void initialize(size_t byteLength, size_t maxByteLength, 786 BufferContents contents) { 787 MOZ_ASSERT(contents.isAligned(byteLength)); 788 setByteLength(byteLength); 789 setMaxByteLength(maxByteLength); 790 setFlags(RESIZABLE); 791 setFirstView(nullptr); 792 setDataPointer(contents); 793 } 794 795 // Resize this buffer. 796 void resize(size_t newByteLength); 797 798 static ResizableArrayBufferObject* copy( 799 JSContext* cx, size_t newByteLength, 800 JS::Handle<ResizableArrayBufferObject*> source); 801 802 public: 803 static const uint8_t MAX_BYTE_LENGTH_SLOT = ArrayBufferObject::RESERVED_SLOTS; 804 805 static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS + 1; 806 807 /** The largest number of bytes that can be stored inline. */ 808 static constexpr size_t MaxInlineBytes = 809 (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value); 810 811 static const JSClass class_; 812 813 size_t maxByteLength() const { 814 return size_t(getFixedSlot(MAX_BYTE_LENGTH_SLOT).toPrivate()); 815 } 816 817 static ResizableArrayBufferObject* copyAndDetach( 818 JSContext* cx, size_t newByteLength, 819 JS::Handle<ResizableArrayBufferObject*> source); 820 821 private: 822 static ResizableArrayBufferObject* copyAndDetachSteal( 823 JSContext* cx, size_t newByteLength, 824 JS::Handle<ResizableArrayBufferObject*> source); 825 }; 826 827 /** 828 * ImmutableArrayBufferObject 829 * 830 * ArrayBuffer object with immutable length and contents. Supports all possible 831 * memory stores for ArrayBuffer objects, including inline data, malloc'ed 832 * memory, mapped memory, and user-owner memory. 833 * 834 * Immutable ArrayBuffers can neither be used for asm.js nor WebAssembly. 835 */ 836 class ImmutableArrayBufferObject : public ArrayBufferObject { 837 friend class ArrayBufferObject; 838 839 static ImmutableArrayBufferObject* createEmpty(JSContext* cx); 840 841 public: 842 static ImmutableArrayBufferObject* createZeroed( 843 JSContext* cx, size_t byteLength, Handle<JSObject*> proto = nullptr); 844 845 private: 846 uint8_t* inlineDataPointer() const; 847 848 bool hasInlineData() const { return dataPointer() == inlineDataPointer(); } 849 850 void initialize(size_t byteLength, BufferContents contents) { 851 MOZ_ASSERT(contents.isAligned(byteLength)); 852 setByteLength(byteLength); 853 setFlags(IMMUTABLE); 854 setFirstView(nullptr); 855 setDataPointer(contents); 856 } 857 858 public: 859 // Immutable ArrayBuffer objects don't have any additional reserved slots. 860 static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS; 861 862 /** The largest number of bytes that can be stored inline. */ 863 static constexpr size_t MaxInlineBytes = 864 (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value); 865 866 static const JSClass class_; 867 868 static ImmutableArrayBufferObject* copy( 869 JSContext* cx, size_t newByteLength, 870 JS::Handle<ArrayBufferObject*> source) { 871 return ArrayBufferObject::copy<ImmutableArrayBufferObject>( 872 cx, newByteLength, source); 873 } 874 875 static ImmutableArrayBufferObject* copyAndDetach( 876 JSContext* cx, size_t newByteLength, 877 JS::Handle<ArrayBufferObject*> source) { 878 return ArrayBufferObject::copyAndDetach<ImmutableArrayBufferObject>( 879 cx, newByteLength, source); 880 } 881 882 static ImmutableArrayBufferObject* slice( 883 JSContext* cx, size_t newByteLength, 884 JS::Handle<ArrayBufferObject*> source, size_t sourceByteOffset); 885 }; 886 887 size_t ArrayBufferObject::maxByteLength() const { 888 if (isResizable()) { 889 return as<ResizableArrayBufferObject>().maxByteLength(); 890 } 891 return byteLength(); 892 } 893 894 // Create a buffer for a wasm memory, whose type is determined by 895 // memory.addressType(). 896 ArrayBufferObjectMaybeShared* CreateWasmBuffer(JSContext* cx, 897 const wasm::MemoryDesc& memory); 898 899 // Per-compartment table that manages the relationship between array buffers 900 // and the views that use their storage. 901 class InnerViewTable { 902 // Store views in a vector such that all the tenured views come before any 903 // nursery views. Maintain the index of the first nursery view so there is an 904 // efficient way to access only the nursery views. 905 using ViewVector = 906 GCVector<UnsafeBarePtr<ArrayBufferViewObject*>, 1, ZoneAllocPolicy>; 907 struct Views { 908 ViewVector views; // List of views with tenured views at the front. 909 size_t firstNurseryView = 0; 910 911 explicit Views(JS::Zone* zone) : views(zone) {} 912 bool empty(); 913 bool hasNurseryViews(); 914 bool addView(ArrayBufferViewObject* view); 915 916 bool traceWeak(JSTracer* trc, size_t startIndex = 0); 917 bool sweepAfterMinorGC(JSTracer* trc); 918 919 void check(); 920 }; 921 922 // For all objects sharing their storage with some other view, this maps 923 // the object to the list of such views. All entries in this map are weak. 924 // 925 // This key is a raw pointer and not a WeakHeapPtr because the post-barrier 926 // would hold nursery-allocated entries live unconditionally. It is a very 927 // common pattern in low-level and performance-oriented JavaScript to create 928 // hundreds or thousands of very short lived temporary views on a larger 929 // buffer; having to tenure all of these would be a catastrophic performance 930 // regression. Thus, it is vital that nursery pointers in this map not be held 931 // live. Special support is required in the minor GC, implemented in 932 // sweepAfterMinorGC. 933 using ArrayBufferViewMap = 934 GCHashMap<UnsafeBarePtr<ArrayBufferObject*>, Views, 935 StableCellHasher<JSObject*>, ZoneAllocPolicy>; 936 ArrayBufferViewMap map; 937 938 // List of keys from map where either the source or at least one target is in 939 // the nursery. The raw pointer to a JSObject is allowed here because this 940 // vector is cleared after every minor collection. Users in sweepAfterMinorGC 941 // must be careful to use MaybeForwarded before touching these pointers. 942 using NurseryKeysVector = 943 GCVector<UnsafeBarePtr<ArrayBufferObject*>, 0, SystemAllocPolicy>; 944 NurseryKeysVector nurseryKeys; 945 946 // Whether nurseryKeys is a complete list. 947 bool nurseryKeysValid = true; 948 949 bool sweepMapEntryAfterMinorGC(UnsafeBarePtr<JSObject*>& buffer, 950 ViewVector& views); 951 952 public: 953 explicit InnerViewTable(Zone* zone) : map(zone) {} 954 955 // Remove references to dead objects in the table and update table entries 956 // to reflect moved objects. 957 bool traceWeak(JSTracer* trc); 958 void sweepAfterMinorGC(JSTracer* trc); 959 960 bool empty() const { return map.empty(); } 961 962 bool needsSweepAfterMinorGC() const { 963 return !nurseryKeys.empty() || !nurseryKeysValid; 964 } 965 966 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); 967 968 private: 969 friend class ArrayBufferObject; 970 friend class ResizableArrayBufferObject; 971 bool addView(JSContext* cx, ArrayBufferObject* buffer, 972 ArrayBufferViewObject* view); 973 ViewVector* maybeViewsUnbarriered(ArrayBufferObject* buffer); 974 void removeViews(ArrayBufferObject* buffer); 975 976 bool sweepViewsAfterMinorGC(JSTracer* trc, ArrayBufferObject* buffer, 977 Views& views); 978 }; 979 980 template <typename Wrapper> 981 class MutableWrappedPtrOperations<InnerViewTable, Wrapper> 982 : public WrappedPtrOperations<InnerViewTable, Wrapper> { 983 InnerViewTable& table() { return static_cast<Wrapper*>(this)->get(); } 984 985 public: 986 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 987 return table().sizeOfExcludingThis(mallocSizeOf); 988 } 989 }; 990 991 class WasmArrayRawBuffer { 992 wasm::AddressType addressType_; 993 wasm::PageSize pageSize_; 994 wasm::Pages clampedMaxPages_; 995 mozilla::Maybe<wasm::Pages> sourceMaxPages_; 996 size_t mappedSize_; // See comment on mappedSize(). 997 size_t length_; 998 999 protected: 1000 WasmArrayRawBuffer(wasm::AddressType addressType, wasm::PageSize pageSize, 1001 uint8_t* buffer, wasm::Pages clampedMaxPages, 1002 const mozilla::Maybe<wasm::Pages>& sourceMaxPages, 1003 size_t mappedSize, size_t length) 1004 : addressType_(addressType), 1005 pageSize_(pageSize), 1006 clampedMaxPages_(clampedMaxPages), 1007 sourceMaxPages_(sourceMaxPages), 1008 mappedSize_(mappedSize), 1009 length_(length) { 1010 // Assert that this WasmArrayRawBuffer was allocated in the correct place 1011 // relative to its data. 1012 MOZ_ASSERT(buffer == dataPointer()); 1013 MOZ_ASSERT(pageSize == clampedMaxPages.pageSize()); 1014 MOZ_ASSERT_IF(sourceMaxPages.isSome(), 1015 (pageSize == sourceMaxPages->pageSize())); 1016 } 1017 1018 public: 1019 static WasmArrayRawBuffer* AllocateWasm( 1020 wasm::AddressType addressType, wasm::PageSize pageSize, 1021 wasm::Pages initialPages, wasm::Pages clampedMaxPages, 1022 const mozilla::Maybe<wasm::Pages>& sourceMaxPages, 1023 const mozilla::Maybe<size_t>& mappedSize); 1024 static void Release(void* mem); 1025 1026 uint8_t* dataPointer() { 1027 uint8_t* ptr = reinterpret_cast<uint8_t*>(this); 1028 return ptr + sizeof(WasmArrayRawBuffer); 1029 } 1030 1031 static const WasmArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) { 1032 return reinterpret_cast<const WasmArrayRawBuffer*>( 1033 dataPtr - sizeof(WasmArrayRawBuffer)); 1034 } 1035 1036 static WasmArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) { 1037 return reinterpret_cast<WasmArrayRawBuffer*>(dataPtr - 1038 sizeof(WasmArrayRawBuffer)); 1039 } 1040 1041 wasm::AddressType addressType() const { return addressType_; } 1042 wasm::PageSize pageSize() const { return pageSize_; } 1043 1044 uint8_t* basePointer() { return dataPointer() - gc::SystemPageSize(); } 1045 1046 /* 1047 * The actual mmapped size. Access in the range [0, mappedSize) will either 1048 * succeed, or be handled by the wasm signal handlers. The mapped size will be 1049 * aligned to the system allocation granularity such that we can 1050 * optimistically map other regions following it, in order to reduce copies 1051 * when growing memory. 1052 * 1053 * Note that this does NOT include the header page in which this buffer itself 1054 * is allocated. 1055 */ 1056 size_t mappedSize() const { return mappedSize_; } 1057 1058 /* 1059 * The wasm-visible current length of the buffer in bytes. Accesses in the 1060 * range [0, byteLength) will succeed. May only increase. 1061 * 1062 * For more info see "WASM Linear Memory structure" in ArrayBufferObject.cpp. 1063 */ 1064 size_t byteLength() const { return length_; } 1065 1066 wasm::Pages pages() const { 1067 return wasm::Pages::fromByteLengthExact(length_, pageSize()); 1068 } 1069 1070 /* 1071 * The maximum size on how far the byteLength can grow in pages. This value 1072 * respects implementation limits and is always representable as a byte 1073 * length. Every memory has a clampedMaxSize, even if no maximum was specified 1074 * in source. When a memory has no sourceMaxSize, the clampedMaxSize will be 1075 * the maximum amount of memory that can be grown to while still respecting 1076 * implementation limits. 1077 * 1078 * For more info see "WASM Linear Memory structure" in ArrayBufferObject.cpp. 1079 */ 1080 wasm::Pages clampedMaxPages() const { return clampedMaxPages_; } 1081 1082 /* 1083 * The optional declared limit on how far byteLength can grow in pages. This 1084 * is the unmodified maximum size from the source module or JS-API invocation. 1085 * This may not be representable in byte lengths, nor feasible for a module to 1086 * actually grow to due to implementation limits. It is used for correct 1087 * linking checks and js-types reflection. 1088 * 1089 * For more info see "WASM Linear Memory structure" in ArrayBufferObject.cpp. 1090 */ 1091 mozilla::Maybe<wasm::Pages> sourceMaxPages() const { return sourceMaxPages_; } 1092 1093 [[nodiscard]] bool growToPagesInPlace(wasm::Pages newPages); 1094 1095 // Discard a region of memory, zeroing the pages and releasing physical memory 1096 // back to the operating system. byteOffset and byteLen must be wasm page 1097 // aligned and in bounds. A discard of zero bytes will have no effect. 1098 void discard(size_t byteOffset, size_t byteLen); 1099 }; 1100 1101 } // namespace js 1102 1103 template <> 1104 inline bool JSObject::is<js::ArrayBufferObject>() const { 1105 return is<js::FixedLengthArrayBufferObject>() || 1106 is<js::ResizableArrayBufferObject>() || 1107 is<js::ImmutableArrayBufferObject>(); 1108 } 1109 1110 template <> 1111 bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const; 1112 1113 #endif // vm_ArrayBufferObject_h