Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / SharedArrayObject.cpp
blob6cf11ae543f60d32a727401a29df81744be652ee
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/. */
7 #include "vm/SharedArrayObject.h"
9 #include "mozilla/Atomics.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/TaggedAnonymousMemory.h"
13 #include "gc/GCContext.h"
14 #include "gc/Memory.h"
15 #include "jit/AtomicOperations.h"
16 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
17 #include "js/PropertySpec.h"
18 #include "js/SharedArrayBuffer.h"
19 #include "util/Memory.h"
20 #include "util/WindowsWrapper.h"
21 #include "vm/SharedMem.h"
22 #include "wasm/WasmConstants.h"
23 #include "wasm/WasmMemory.h"
25 #include "vm/ArrayBufferObject-inl.h"
26 #include "vm/JSObject-inl.h"
27 #include "vm/NativeObject-inl.h"
29 using js::wasm::Pages;
30 using mozilla::DebugOnly;
31 using mozilla::Maybe;
32 using mozilla::Nothing;
33 using mozilla::Some;
35 using namespace js;
36 using namespace js::jit;
38 static size_t WasmSharedArrayAccessibleSize(size_t length) {
39 return AlignBytes(length, gc::SystemPageSize());
42 static size_t NonWasmSharedArrayAllocSize(size_t length) {
43 MOZ_ASSERT(length <= ArrayBufferObject::MaxByteLength);
44 return sizeof(SharedArrayRawBuffer) + length;
47 // The mapped size for a plain shared array buffer, used only for tracking
48 // memory usage. This is incorrect for some WASM cases, and for hypothetical
49 // callers of js::SharedArrayBufferObject::createFromNewRawBuffer that do not
50 // currently exist, but it's fine as a signal of GC pressure.
51 static size_t SharedArrayMappedSize(bool isWasm, size_t length) {
52 // Wasm buffers use MapBufferMemory and allocate a full page for the header.
53 // Non-Wasm buffers use malloc.
54 if (isWasm) {
55 return WasmSharedArrayAccessibleSize(length) + gc::SystemPageSize();
57 return NonWasmSharedArrayAllocSize(length);
60 SharedArrayRawBuffer* SharedArrayRawBuffer::Allocate(bool isGrowable,
61 size_t length,
62 size_t maxLength) {
63 MOZ_RELEASE_ASSERT(length <= ArrayBufferObject::MaxByteLength);
64 MOZ_RELEASE_ASSERT(maxLength <= ArrayBufferObject::MaxByteLength);
65 MOZ_ASSERT_IF(!isGrowable, length == maxLength);
66 MOZ_ASSERT_IF(isGrowable, length <= maxLength);
68 size_t allocSize = NonWasmSharedArrayAllocSize(maxLength);
69 uint8_t* p = js_pod_calloc<uint8_t>(allocSize);
70 if (!p) {
71 return nullptr;
74 uint8_t* buffer = p + sizeof(SharedArrayRawBuffer);
75 return new (p) SharedArrayRawBuffer(isGrowable, buffer, length);
78 WasmSharedArrayRawBuffer* WasmSharedArrayRawBuffer::AllocateWasm(
79 wasm::IndexType indexType, Pages initialPages, wasm::Pages clampedMaxPages,
80 const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
81 const mozilla::Maybe<size_t>& mappedSize) {
82 // Prior code has asserted that initial pages is within our implementation
83 // limits (wasm::MaxMemoryPages()) and we can assume it is a valid size_t.
84 MOZ_ASSERT(initialPages.hasByteLength());
85 size_t length = initialPages.byteLength();
87 MOZ_RELEASE_ASSERT(length <= ArrayBufferObject::MaxByteLength);
89 size_t accessibleSize = WasmSharedArrayAccessibleSize(length);
90 if (accessibleSize < length) {
91 return nullptr;
94 size_t computedMappedSize = mappedSize.isSome()
95 ? *mappedSize
96 : wasm::ComputeMappedSize(clampedMaxPages);
97 MOZ_ASSERT(accessibleSize <= computedMappedSize);
99 uint64_t mappedSizeWithHeader = computedMappedSize + gc::SystemPageSize();
100 uint64_t accessibleSizeWithHeader = accessibleSize + gc::SystemPageSize();
102 void* p = MapBufferMemory(indexType, mappedSizeWithHeader,
103 accessibleSizeWithHeader);
104 if (!p) {
105 return nullptr;
108 uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
109 uint8_t* base = buffer - sizeof(WasmSharedArrayRawBuffer);
110 return new (base) WasmSharedArrayRawBuffer(
111 buffer, length, indexType, clampedMaxPages,
112 sourceMaxPages.valueOr(Pages(0)), computedMappedSize);
115 void WasmSharedArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages) {
116 Pages newMaxPages = clampedMaxPages_;
117 DebugOnly<bool> valid = newMaxPages.checkedIncrement(deltaMaxPages);
118 // Caller must ensure increment does not overflow or increase over the
119 // specified maximum pages.
120 MOZ_ASSERT(valid);
121 MOZ_ASSERT(newMaxPages <= sourceMaxPages_);
123 size_t newMappedSize = wasm::ComputeMappedSize(newMaxPages);
124 MOZ_ASSERT(mappedSize_ <= newMappedSize);
125 if (mappedSize_ == newMappedSize) {
126 return;
129 if (!ExtendBufferMapping(basePointer(), mappedSize_, newMappedSize)) {
130 return;
133 mappedSize_ = newMappedSize;
134 clampedMaxPages_ = newMaxPages;
137 bool WasmSharedArrayRawBuffer::wasmGrowToPagesInPlace(const Lock&,
138 wasm::IndexType t,
139 wasm::Pages newPages) {
140 // Check that the new pages is within our allowable range. This will
141 // simultaneously check against the maximum specified in source and our
142 // implementation limits.
143 if (newPages > clampedMaxPages_) {
144 return false;
146 MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) &&
147 newPages.byteLength() <= ArrayBufferObject::MaxByteLength);
149 // We have checked against the clamped maximum and so we know we can convert
150 // to byte lengths now.
151 size_t newLength = newPages.byteLength();
153 MOZ_ASSERT(newLength >= length_);
155 if (newLength == length_) {
156 return true;
159 size_t delta = newLength - length_;
160 MOZ_ASSERT(delta % wasm::PageSize == 0);
162 uint8_t* dataEnd = dataPointerShared().unwrap(/* for resize */) + length_;
163 MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
165 if (!CommitBufferMemory(dataEnd, delta)) {
166 return false;
169 // We rely on CommitBufferMemory (and therefore memmap/VirtualAlloc) to only
170 // return once it has committed memory for all threads. We only update with a
171 // new length once this has occurred.
172 length_ = newLength;
174 return true;
177 void WasmSharedArrayRawBuffer::discard(size_t byteOffset, size_t byteLen) {
178 SharedMem<uint8_t*> memBase = dataPointerShared();
180 // The caller is responsible for ensuring these conditions are met; see this
181 // function's comment in SharedArrayObject.h.
182 MOZ_ASSERT(byteOffset % wasm::PageSize == 0);
183 MOZ_ASSERT(byteLen % wasm::PageSize == 0);
184 MOZ_ASSERT(wasm::MemoryBoundsCheck(uint64_t(byteOffset), uint64_t(byteLen),
185 volatileByteLength()));
187 // Discarding zero bytes "succeeds" with no effect.
188 if (byteLen == 0) {
189 return;
192 SharedMem<uint8_t*> addr = memBase + uintptr_t(byteOffset);
194 // On POSIX-ish platforms, we discard memory by overwriting previously-mapped
195 // pages with freshly-mapped pages (which are all zeroed). The operating
196 // system recognizes this and decreases the process RSS, and eventually
197 // collects the abandoned physical pages.
199 // On Windows, committing over previously-committed pages has no effect. We
200 // could decommit and recommit, but this doesn't work for shared memories
201 // since other threads could access decommitted memory - causing a trap.
202 // Instead, we simply zero memory (memset 0), and then VirtualUnlock(), which
203 // for Historical Reasons immediately removes the pages from the working set.
204 // And then, because the pages were zeroed, Windows will actually reclaim the
205 // memory entirely instead of paging it out to disk. Naturally this behavior
206 // is not officially documented, but a Raymond Chen blog post is basically as
207 // good as MSDN, right?
209 // https://devblogs.microsoft.com/oldnewthing/20170113-00/?p=95185
211 #ifdef XP_WIN
212 // Discarding the entire region at once causes us to page the entire region
213 // into the working set, only to throw it out again. This can be actually
214 // disastrous when discarding already-discarded memory. To mitigate this, we
215 // discard a chunk of memory at a time - this comes at a small performance
216 // cost from syscalls and potentially less-optimal memsets.
217 size_t numPages = byteLen / wasm::PageSize;
218 for (size_t i = 0; i < numPages; i++) {
219 AtomicOperations::memsetSafeWhenRacy(addr + (i * wasm::PageSize), 0,
220 wasm::PageSize);
221 DebugOnly<bool> result =
222 VirtualUnlock(addr.unwrap() + (i * wasm::PageSize), wasm::PageSize);
223 MOZ_ASSERT(!result); // this always "fails" when unlocking unlocked
224 // memory...which is the only case we care about
226 #elif defined(__wasi__)
227 AtomicOperations::memsetSafeWhenRacy(addr, 0, byteLen);
228 #else // !XP_WIN
229 void* data = MozTaggedAnonymousMmap(
230 addr.unwrap(), byteLen, PROT_READ | PROT_WRITE,
231 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0, "wasm-reserved");
232 if (data == MAP_FAILED) {
233 MOZ_CRASH("failed to discard wasm memory; memory mappings may be broken");
235 #endif
238 bool SharedArrayRawBuffer::addReference() {
239 MOZ_RELEASE_ASSERT(refcount_ > 0);
241 // Be careful never to overflow the refcount field.
242 for (;;) {
243 uint32_t old_refcount = refcount_;
244 uint32_t new_refcount = old_refcount + 1;
245 if (new_refcount == 0) {
246 return false;
248 if (refcount_.compareExchange(old_refcount, new_refcount)) {
249 return true;
254 void SharedArrayRawBuffer::dropReference() {
255 // Normally if the refcount is zero then the memory will have been unmapped
256 // and this test may just crash, but if the memory has been retained for any
257 // reason we will catch the underflow here.
258 MOZ_RELEASE_ASSERT(refcount_ > 0);
260 // Drop the reference to the buffer.
261 uint32_t new_refcount = --refcount_; // Atomic.
262 if (new_refcount) {
263 return;
266 // This was the final reference, so release the buffer.
267 if (isWasm()) {
268 WasmSharedArrayRawBuffer* wasmBuf = toWasmBuffer();
269 wasm::IndexType indexType = wasmBuf->wasmIndexType();
270 uint8_t* basePointer = wasmBuf->basePointer();
271 size_t mappedSizeWithHeader = wasmBuf->mappedSize() + gc::SystemPageSize();
272 // Call the destructor to destroy the growLock_ Mutex.
273 wasmBuf->~WasmSharedArrayRawBuffer();
274 UnmapBufferMemory(indexType, basePointer, mappedSizeWithHeader);
275 } else {
276 js_delete(this);
280 bool SharedArrayRawBuffer::grow(size_t newByteLength) {
281 MOZ_RELEASE_ASSERT(isGrowable());
283 // The caller is responsible to ensure |newByteLength| doesn't exceed the
284 // maximum allowed byte length.
286 while (true) {
287 // `mozilla::Atomic::compareExchange` doesn't return the current value, so
288 // we need to perform a normal load here. (bug 1005335)
289 size_t oldByteLength = length_;
290 if (newByteLength == oldByteLength) {
291 return true;
293 if (newByteLength < oldByteLength) {
294 return false;
296 if (length_.compareExchange(oldByteLength, newByteLength)) {
297 return true;
302 static bool IsSharedArrayBuffer(HandleValue v) {
303 return v.isObject() && v.toObject().is<SharedArrayBufferObject>();
306 #ifdef NIGHTLY_BUILD
307 static bool IsGrowableSharedArrayBuffer(HandleValue v) {
308 return v.isObject() && v.toObject().is<GrowableSharedArrayBufferObject>();
310 #endif
312 MOZ_ALWAYS_INLINE bool SharedArrayBufferObject::byteLengthGetterImpl(
313 JSContext* cx, const CallArgs& args) {
314 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
315 auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>();
316 args.rval().setNumber(buffer->byteLength());
317 return true;
320 bool SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc,
321 Value* vp) {
322 CallArgs args = CallArgsFromVp(argc, vp);
323 return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx,
324 args);
327 #ifdef NIGHTLY_BUILD
329 * get SharedArrayBuffer.prototype.maxByteLength
331 bool SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx,
332 const CallArgs& args) {
333 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
334 auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>();
336 // Steps 4-6.
337 args.rval().setNumber(buffer->byteLengthOrMaxByteLength());
338 return true;
342 * get SharedArrayBuffer.prototype.maxByteLength
344 bool SharedArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc,
345 Value* vp) {
346 // Steps 1-3.
347 CallArgs args = CallArgsFromVp(argc, vp);
348 return CallNonGenericMethod<IsSharedArrayBuffer, maxByteLengthGetterImpl>(
349 cx, args);
353 * get SharedArrayBuffer.prototype.growable
355 bool SharedArrayBufferObject::growableGetterImpl(JSContext* cx,
356 const CallArgs& args) {
357 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
358 auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>();
360 // Step 4.
361 args.rval().setBoolean(buffer->isGrowable());
362 return true;
366 * get SharedArrayBuffer.prototype.growable
368 bool SharedArrayBufferObject::growableGetter(JSContext* cx, unsigned argc,
369 Value* vp) {
370 // Steps 1-3.
371 CallArgs args = CallArgsFromVp(argc, vp);
372 return CallNonGenericMethod<IsSharedArrayBuffer, growableGetterImpl>(cx,
373 args);
377 * SharedArrayBuffer.prototype.grow ( newLength )
379 bool SharedArrayBufferObject::growImpl(JSContext* cx, const CallArgs& args) {
380 MOZ_ASSERT(IsGrowableSharedArrayBuffer(args.thisv()));
381 Rooted<GrowableSharedArrayBufferObject*> buffer(
382 cx, &args.thisv().toObject().as<GrowableSharedArrayBufferObject>());
384 // Step 4.
385 uint64_t newByteLength;
386 if (!ToIndex(cx, args.get(0), &newByteLength)) {
387 return false;
390 // Steps 5-11.
391 if (newByteLength > buffer->maxByteLength()) {
392 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
393 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
394 return false;
396 if (!buffer->rawBufferObject()->grow(newByteLength)) {
397 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
398 JSMSG_SHARED_ARRAY_LENGTH_SMALLER_THAN_CURRENT);
399 return false;
402 args.rval().setUndefined();
403 return true;
407 * SharedArrayBuffer.prototype.grow ( newLength )
409 bool SharedArrayBufferObject::grow(JSContext* cx, unsigned argc, Value* vp) {
410 // Steps 1-3.
411 CallArgs args = CallArgsFromVp(argc, vp);
412 return CallNonGenericMethod<IsGrowableSharedArrayBuffer, growImpl>(cx, args);
414 #endif
416 // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
417 // 24.2.2.1 SharedArrayBuffer( length )
418 bool SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc,
419 Value* vp) {
420 CallArgs args = CallArgsFromVp(argc, vp);
422 // Step 1.
423 if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer")) {
424 return false;
427 // Step 2.
428 uint64_t byteLength;
429 if (!ToIndex(cx, args.get(0), &byteLength)) {
430 return false;
433 mozilla::Maybe<uint64_t> maxByteLength;
434 #ifdef NIGHTLY_BUILD
435 // SharedArrayBuffer ( length [ , options ] ), step 3.
436 if (cx->realm()->creationOptions().getSharedArrayBufferGrowableEnabled()) {
437 // Inline call to GetArrayBufferMaxByteLengthOption.
438 if (args.get(1).isObject()) {
439 Rooted<JSObject*> options(cx, &args[1].toObject());
441 Rooted<Value> val(cx);
442 if (!GetProperty(cx, options, options, cx->names().maxByteLength, &val)) {
443 return false;
445 if (!val.isUndefined()) {
446 uint64_t maxByteLengthInt;
447 if (!ToIndex(cx, val, &maxByteLengthInt)) {
448 return false;
451 // AllocateSharedArrayBuffer, step 2.
452 if (byteLength > maxByteLengthInt) {
453 JS_ReportErrorNumberASCII(
454 cx, GetErrorMessage, nullptr,
455 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
456 return false;
458 maxByteLength = mozilla::Some(maxByteLengthInt);
462 #endif
464 // Step 3 (Inlined 24.2.1.1 AllocateSharedArrayBuffer).
465 // 24.2.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
466 RootedObject proto(cx);
467 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_SharedArrayBuffer,
468 &proto)) {
469 return false;
472 // 24.2.1.1, step 3 (Inlined 6.2.7.2 CreateSharedByteDataBlock, step 2).
473 // Refuse to allocate too large buffers.
474 if (byteLength > ArrayBufferObject::MaxByteLength) {
475 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
476 JSMSG_SHARED_ARRAY_BAD_LENGTH);
477 return false;
480 if (maxByteLength) {
481 auto* bufobj = NewGrowable(cx, byteLength, *maxByteLength, proto);
482 if (!bufobj) {
483 return false;
485 args.rval().setObject(*bufobj);
486 return true;
489 // 24.2.1.1, steps 1 and 4-6.
490 JSObject* bufobj = New(cx, byteLength, proto);
491 if (!bufobj) {
492 return false;
494 args.rval().setObject(*bufobj);
495 return true;
498 FixedLengthSharedArrayBufferObject* SharedArrayBufferObject::New(
499 JSContext* cx, size_t length, HandleObject proto) {
500 bool isGrowable = false;
501 size_t maxLength = length;
502 auto* buffer = SharedArrayRawBuffer::Allocate(isGrowable, length, maxLength);
503 if (!buffer) {
504 js::ReportOutOfMemory(cx);
505 return nullptr;
508 auto* obj = New(cx, buffer, length, proto);
509 if (!obj) {
510 buffer->dropReference();
511 return nullptr;
514 return obj;
517 FixedLengthSharedArrayBufferObject* SharedArrayBufferObject::New(
518 JSContext* cx, SharedArrayRawBuffer* buffer, size_t length,
519 HandleObject proto) {
520 return NewWith<FixedLengthSharedArrayBufferObject>(cx, buffer, length, proto);
523 GrowableSharedArrayBufferObject* SharedArrayBufferObject::NewGrowable(
524 JSContext* cx, size_t length, size_t maxLength, HandleObject proto) {
525 bool isGrowable = true;
526 auto* buffer = SharedArrayRawBuffer::Allocate(isGrowable, length, maxLength);
527 if (!buffer) {
528 js::ReportOutOfMemory(cx);
529 return nullptr;
532 auto* obj = NewGrowable(cx, buffer, maxLength, proto);
533 if (!obj) {
534 buffer->dropReference();
535 return nullptr;
538 return obj;
541 GrowableSharedArrayBufferObject* SharedArrayBufferObject::NewGrowable(
542 JSContext* cx, SharedArrayRawBuffer* buffer, size_t maxLength,
543 HandleObject proto) {
544 return NewWith<GrowableSharedArrayBufferObject>(cx, buffer, maxLength, proto);
547 template <class SharedArrayBufferType>
548 SharedArrayBufferType* SharedArrayBufferObject::NewWith(
549 JSContext* cx, SharedArrayRawBuffer* buffer, size_t length,
550 HandleObject proto) {
551 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
553 static_assert(
554 std::is_same_v<SharedArrayBufferType,
555 FixedLengthSharedArrayBufferObject> ||
556 std::is_same_v<SharedArrayBufferType, GrowableSharedArrayBufferObject>);
558 if constexpr (std::is_same_v<SharedArrayBufferType,
559 FixedLengthSharedArrayBufferObject>) {
560 MOZ_ASSERT(!buffer->isGrowable());
561 } else {
562 MOZ_ASSERT(buffer->isGrowable());
565 AutoSetNewObjectMetadata metadata(cx);
566 auto* obj = NewObjectWithClassProto<SharedArrayBufferType>(cx, proto);
567 if (!obj) {
568 return nullptr;
571 MOZ_ASSERT(obj->getClass() == &SharedArrayBufferType::class_);
573 cx->runtime()->incSABCount();
575 if (!obj->acceptRawBuffer(buffer, length)) {
576 js::ReportOutOfMemory(cx);
577 return nullptr;
580 return obj;
583 bool SharedArrayBufferObject::acceptRawBuffer(SharedArrayRawBuffer* buffer,
584 size_t length) {
585 if (!zone()->addSharedMemory(buffer,
586 SharedArrayMappedSize(buffer->isWasm(), length),
587 MemoryUse::SharedArrayRawBuffer)) {
588 return false;
591 setFixedSlot(RAWBUF_SLOT, PrivateValue(buffer));
592 setFixedSlot(LENGTH_SLOT, PrivateValue(length));
593 return true;
596 void SharedArrayBufferObject::dropRawBuffer() {
597 size_t length = byteLengthOrMaxByteLength();
598 size_t size = SharedArrayMappedSize(isWasm(), length);
599 zoneFromAnyThread()->removeSharedMemory(rawBufferObject(), size,
600 MemoryUse::SharedArrayRawBuffer);
601 rawBufferObject()->dropReference();
602 setFixedSlot(RAWBUF_SLOT, UndefinedValue());
605 SharedArrayRawBuffer* SharedArrayBufferObject::rawBufferObject() const {
606 Value v = getFixedSlot(RAWBUF_SLOT);
607 MOZ_ASSERT(!v.isUndefined());
608 return reinterpret_cast<SharedArrayRawBuffer*>(v.toPrivate());
611 void SharedArrayBufferObject::Finalize(JS::GCContext* gcx, JSObject* obj) {
612 // Must be foreground finalizable so that we can account for the object.
613 MOZ_ASSERT(gcx->onMainThread());
614 gcx->runtime()->decSABCount();
616 SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
618 // Detect the case of failure during SharedArrayBufferObject creation,
619 // which causes a SharedArrayRawBuffer to never be attached.
620 Value v = buf.getFixedSlot(RAWBUF_SLOT);
621 if (!v.isUndefined()) {
622 buf.dropRawBuffer();
626 /* static */
627 void SharedArrayBufferObject::addSizeOfExcludingThis(
628 JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info,
629 JS::RuntimeSizes* runtimeSizes) {
630 // Divide the buffer size by the refcount to get the fraction of the buffer
631 // owned by this thread. It's conceivable that the refcount might change in
632 // the middle of memory reporting, in which case the amount reported for
633 // some threads might be to high (if the refcount goes up) or too low (if
634 // the refcount goes down). But that's unlikely and hard to avoid, so we
635 // just live with the risk.
636 const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
637 size_t nbytes = buf.byteLengthOrMaxByteLength();
638 size_t owned = nbytes / buf.rawBufferObject()->refcount();
639 if (buf.isWasm()) {
640 info->objectsNonHeapElementsWasmShared += owned;
641 if (runtimeSizes) {
642 size_t ownedGuardPages =
643 (buf.wasmMappedSize() - nbytes) / buf.rawBufferObject()->refcount();
644 runtimeSizes->wasmGuardPages += ownedGuardPages;
646 } else {
647 info->objectsNonHeapElementsShared += owned;
651 /* static */
652 void SharedArrayBufferObject::copyData(
653 Handle<ArrayBufferObjectMaybeShared*> toBuffer, size_t toIndex,
654 Handle<ArrayBufferObjectMaybeShared*> fromBuffer, size_t fromIndex,
655 size_t count) {
656 MOZ_ASSERT(toBuffer->byteLength() >= count);
657 MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
658 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
659 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
661 jit::AtomicOperations::memcpySafeWhenRacy(
662 toBuffer->dataPointerEither() + toIndex,
663 fromBuffer->dataPointerEither() + fromIndex, count);
666 SharedArrayBufferObject* SharedArrayBufferObject::createFromNewRawBuffer(
667 JSContext* cx, WasmSharedArrayRawBuffer* buffer, size_t initialSize) {
668 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
670 AutoSetNewObjectMetadata metadata(cx);
671 auto* obj = NewBuiltinClassInstance<FixedLengthSharedArrayBufferObject>(cx);
672 if (!obj) {
673 buffer->dropReference();
674 return nullptr;
677 cx->runtime()->incSABCount();
679 if (!obj->acceptRawBuffer(buffer, initialSize)) {
680 buffer->dropReference();
681 return nullptr;
684 return obj;
687 /* static */
688 void SharedArrayBufferObject::wasmDiscard(Handle<SharedArrayBufferObject*> buf,
689 uint64_t byteOffset,
690 uint64_t byteLen) {
691 MOZ_ASSERT(buf->isWasm());
692 buf->rawWasmBufferObject()->discard(byteOffset, byteLen);
695 static const JSClassOps SharedArrayBufferObjectClassOps = {
696 nullptr, // addProperty
697 nullptr, // delProperty
698 nullptr, // enumerate
699 nullptr, // newEnumerate
700 nullptr, // resolve
701 nullptr, // mayResolve
702 SharedArrayBufferObject::Finalize, // finalize
703 nullptr, // call
704 nullptr, // construct
705 nullptr, // trace
708 static const JSFunctionSpec sharedarray_functions[] = {
709 JS_FS_END,
712 static const JSPropertySpec sharedarray_properties[] = {
713 JS_SELF_HOSTED_SYM_GET(species, "$SharedArrayBufferSpecies", 0),
714 JS_PS_END,
717 static const JSFunctionSpec sharedarray_proto_functions[] = {
718 JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0),
719 #ifdef NIGHTLY_BUILD
720 JS_FN("grow", SharedArrayBufferObject::grow, 1, 0),
721 #endif
722 JS_FS_END,
725 static const JSPropertySpec sharedarray_proto_properties[] = {
726 JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
727 #ifdef NIGHTLY_BUILD
728 JS_PSG("maxByteLength", SharedArrayBufferObject::maxByteLengthGetter, 0),
729 JS_PSG("growable", SharedArrayBufferObject::growableGetter, 0),
730 #endif
731 JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
732 JS_PS_END,
735 static JSObject* CreateSharedArrayBufferPrototype(JSContext* cx,
736 JSProtoKey key) {
737 return GlobalObject::createBlankPrototype(
738 cx, cx->global(), &SharedArrayBufferObject::protoClass_);
741 static const ClassSpec SharedArrayBufferObjectClassSpec = {
742 GenericCreateConstructor<SharedArrayBufferObject::class_constructor, 1,
743 gc::AllocKind::FUNCTION>,
744 CreateSharedArrayBufferPrototype,
745 sharedarray_functions,
746 sharedarray_properties,
747 sharedarray_proto_functions,
748 sharedarray_proto_properties,
751 const JSClass SharedArrayBufferObject::protoClass_ = {
752 "SharedArrayBuffer.prototype",
753 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer),
754 JS_NULL_CLASS_OPS,
755 &SharedArrayBufferObjectClassSpec,
758 const JSClass FixedLengthSharedArrayBufferObject::class_ = {
759 "SharedArrayBuffer",
760 JSCLASS_DELAY_METADATA_BUILDER |
761 JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) |
762 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer) |
763 JSCLASS_FOREGROUND_FINALIZE,
764 &SharedArrayBufferObjectClassOps,
765 &SharedArrayBufferObjectClassSpec,
766 JS_NULL_CLASS_EXT,
769 const JSClass GrowableSharedArrayBufferObject::class_ = {
770 "SharedArrayBuffer",
771 JSCLASS_DELAY_METADATA_BUILDER |
772 JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) |
773 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer) |
774 JSCLASS_FOREGROUND_FINALIZE,
775 &SharedArrayBufferObjectClassOps,
776 &SharedArrayBufferObjectClassSpec,
777 JS_NULL_CLASS_EXT,
780 JS_PUBLIC_API size_t JS::GetSharedArrayBufferByteLength(JSObject* obj) {
781 auto* aobj = obj->maybeUnwrapAs<SharedArrayBufferObject>();
782 return aobj ? aobj->byteLength() : 0;
785 JS_PUBLIC_API void JS::GetSharedArrayBufferLengthAndData(JSObject* obj,
786 size_t* length,
787 bool* isSharedMemory,
788 uint8_t** data) {
789 MOZ_ASSERT(obj->is<SharedArrayBufferObject>());
790 *length = obj->as<SharedArrayBufferObject>().byteLength();
791 *data = obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(
792 /*safe - caller knows*/);
793 *isSharedMemory = true;
796 JS_PUBLIC_API JSObject* JS::NewSharedArrayBuffer(JSContext* cx, size_t nbytes) {
797 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
799 if (nbytes > ArrayBufferObject::MaxByteLength) {
800 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
801 JSMSG_SHARED_ARRAY_BAD_LENGTH);
802 return nullptr;
805 return SharedArrayBufferObject::New(cx, nbytes,
806 /* proto = */ nullptr);
809 JS_PUBLIC_API bool JS::IsSharedArrayBufferObject(JSObject* obj) {
810 return obj->canUnwrapAs<SharedArrayBufferObject>();
813 JS_PUBLIC_API uint8_t* JS::GetSharedArrayBufferData(
814 JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&) {
815 auto* aobj = obj->maybeUnwrapAs<SharedArrayBufferObject>();
816 if (!aobj) {
817 return nullptr;
819 *isSharedMemory = true;
820 return aobj->dataPointerShared().unwrap(/*safe - caller knows*/);
823 JS_PUBLIC_API bool JS::ContainsSharedArrayBuffer(JSContext* cx) {
824 return cx->runtime()->hasLiveSABs();