Bug 1842773 - Part 7: Support constructing resizable ArrayBuffers. r=sfink
[gecko.git] / js / src / vm / ArrayBufferObject.cpp
blobd5847dfa12d37c47d7a1348a956ab5d15dadc065
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/ArrayBufferObject-inl.h"
8 #include "vm/ArrayBufferObject.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/ScopeExit.h"
16 #include "mozilla/TaggedAnonymousMemory.h"
18 #include <algorithm> // std::max, std::min
19 #include <memory> // std::uninitialized_copy_n
20 #include <string.h>
21 #if !defined(XP_WIN) && !defined(__wasi__)
22 # include <sys/mman.h>
23 #endif
24 #include <tuple> // std::tuple
25 #include <type_traits>
26 #ifdef MOZ_VALGRIND
27 # include <valgrind/memcheck.h>
28 #endif
30 #include "jsnum.h"
31 #include "jstypes.h"
33 #include "gc/Barrier.h"
34 #include "gc/Memory.h"
35 #include "js/ArrayBuffer.h"
36 #include "js/Conversions.h"
37 #include "js/experimental/TypedData.h" // JS_IsArrayBufferViewObject
38 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
39 #include "js/MemoryMetrics.h"
40 #include "js/PropertySpec.h"
41 #include "js/SharedArrayBuffer.h"
42 #include "js/Wrapper.h"
43 #include "util/WindowsWrapper.h"
44 #include "vm/GlobalObject.h"
45 #include "vm/JSContext.h"
46 #include "vm/JSObject.h"
47 #include "vm/SharedArrayObject.h"
48 #include "vm/Warnings.h" // js::WarnNumberASCII
49 #include "wasm/WasmConstants.h"
50 #include "wasm/WasmLog.h"
51 #include "wasm/WasmMemory.h"
52 #include "wasm/WasmModuleTypes.h"
53 #include "wasm/WasmProcess.h"
55 #include "gc/GCContext-inl.h"
56 #include "gc/Marking-inl.h"
57 #include "vm/NativeObject-inl.h"
58 #include "vm/Realm-inl.h" // js::AutoRealm
60 using JS::ToInt32;
62 using js::wasm::IndexType;
63 using js::wasm::Pages;
64 using mozilla::Atomic;
65 using mozilla::CheckedInt;
66 using mozilla::DebugOnly;
67 using mozilla::Maybe;
68 using mozilla::Nothing;
69 using mozilla::Some;
71 using namespace js;
73 // Wasm allows large amounts of memory to be reserved at a time. On 64-bit
74 // platforms (with "huge memories") we reserve around 4GB of virtual address
75 // space for every wasm memory; on 32-bit platforms we usually do not, but users
76 // often initialize memories in the hundreds of megabytes.
78 // If too many wasm memories remain live, we run up against system resource
79 // exhaustion (address space or number of memory map descriptors) - see bug
80 // 1068684, bug 1073934, bug 1517412, bug 1502733 for details. The limiting case
81 // seems to be Android on ARM64, where the per-process address space is limited
82 // to 4TB (39 bits) by the organization of the page tables. An earlier problem
83 // was Windows Vista Home 64-bit, where the per-process address space is limited
84 // to 8TB (40 bits). And 32-bit platforms only have 4GB of address space anyway.
86 // Thus we track the amount of memory reserved for wasm, and set a limit per
87 // process. We trigger GC work when we approach the limit and we throw an OOM
88 // error if the per-process limit is exceeded. The limit (WasmReservedBytesMax)
89 // is specific to architecture, OS, and OS configuration.
91 // Since the WasmReservedBytesMax limit is not generally accounted for by
92 // any existing GC-trigger heuristics, we need an extra heuristic for triggering
93 // GCs when the caller is allocating memories rapidly without other garbage
94 // (e.g. bug 1773225). Thus, once the reserved memory crosses the threshold
95 // WasmReservedBytesStartTriggering, we start triggering GCs every
96 // WasmReservedBytesPerTrigger bytes. Once we reach
97 // WasmReservedBytesStartSyncFullGC bytes reserved, we perform expensive
98 // non-incremental full GCs as a last-ditch effort to avoid unnecessary failure.
99 // Once we reach WasmReservedBytesMax, we perform further full GCs before giving
100 // up.
102 // (History: The original implementation only tracked the number of "huge
103 // memories" allocated by WASM, but this was found to be insufficient because
104 // 32-bit platforms have similar resource exhaustion issues. We now track
105 // reserved bytes directly.)
107 // (We also used to reserve significantly more than 4GB for huge memories, but
108 // this was reduced in bug 1442544.)
110 // ASAN and TSAN use a ton of vmem for bookkeeping leaving a lot less for the
111 // program so use a lower limit.
112 #if defined(MOZ_TSAN) || defined(MOZ_ASAN)
113 static const uint64_t WasmMemAsanOverhead = 2;
114 #else
115 static const uint64_t WasmMemAsanOverhead = 1;
116 #endif
118 // WasmReservedStartTriggering + WasmReservedPerTrigger must be well below
119 // WasmReservedStartSyncFullGC in order to provide enough time for incremental
120 // GC to do its job.
122 #if defined(JS_CODEGEN_ARM64) && defined(ANDROID)
124 static const uint64_t WasmReservedBytesMax =
125 75 * wasm::HugeMappedSize / WasmMemAsanOverhead;
126 static const uint64_t WasmReservedBytesStartTriggering =
127 15 * wasm::HugeMappedSize;
128 static const uint64_t WasmReservedBytesStartSyncFullGC =
129 WasmReservedBytesMax - 15 * wasm::HugeMappedSize;
130 static const uint64_t WasmReservedBytesPerTrigger = 15 * wasm::HugeMappedSize;
132 #elif defined(WASM_SUPPORTS_HUGE_MEMORY)
134 static const uint64_t WasmReservedBytesMax =
135 1000 * wasm::HugeMappedSize / WasmMemAsanOverhead;
136 static const uint64_t WasmReservedBytesStartTriggering =
137 100 * wasm::HugeMappedSize;
138 static const uint64_t WasmReservedBytesStartSyncFullGC =
139 WasmReservedBytesMax - 100 * wasm::HugeMappedSize;
140 static const uint64_t WasmReservedBytesPerTrigger = 100 * wasm::HugeMappedSize;
142 #else // 32-bit (and weird 64-bit platforms without huge memory)
144 static const uint64_t GiB = 1024 * 1024 * 1024;
146 static const uint64_t WasmReservedBytesMax =
147 (4 * GiB) / 2 / WasmMemAsanOverhead;
148 static const uint64_t WasmReservedBytesStartTriggering = (4 * GiB) / 8;
149 static const uint64_t WasmReservedBytesStartSyncFullGC =
150 WasmReservedBytesMax - (4 * GiB) / 8;
151 static const uint64_t WasmReservedBytesPerTrigger = (4 * GiB) / 8;
153 #endif
155 // The total number of bytes reserved for wasm memories.
156 static Atomic<uint64_t, mozilla::ReleaseAcquire> wasmReservedBytes(0);
157 // The number of bytes of wasm memory reserved since the last GC trigger.
158 static Atomic<uint64_t, mozilla::ReleaseAcquire> wasmReservedBytesSinceLast(0);
160 uint64_t js::WasmReservedBytes() { return wasmReservedBytes; }
162 [[nodiscard]] static bool CheckArrayBufferTooLarge(JSContext* cx,
163 uint64_t nbytes) {
164 // Refuse to allocate too large buffers.
165 if (MOZ_UNLIKELY(nbytes > ArrayBufferObject::MaxByteLength)) {
166 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
167 JSMSG_BAD_ARRAY_LENGTH);
168 return false;
171 return true;
174 void* js::MapBufferMemory(wasm::IndexType t, size_t mappedSize,
175 size_t initialCommittedSize) {
176 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
177 MOZ_ASSERT(initialCommittedSize % gc::SystemPageSize() == 0);
178 MOZ_ASSERT(initialCommittedSize <= mappedSize);
180 auto failed = mozilla::MakeScopeExit(
181 [&] { wasmReservedBytes -= uint64_t(mappedSize); });
182 wasmReservedBytes += uint64_t(mappedSize);
184 // Test >= to guard against the case where multiple extant runtimes
185 // race to allocate.
186 if (wasmReservedBytes >= WasmReservedBytesMax) {
187 if (OnLargeAllocationFailure) {
188 OnLargeAllocationFailure();
190 if (wasmReservedBytes >= WasmReservedBytesMax) {
191 return nullptr;
195 #ifdef XP_WIN
196 void* data = VirtualAlloc(nullptr, mappedSize, MEM_RESERVE, PAGE_NOACCESS);
197 if (!data) {
198 return nullptr;
201 if (!VirtualAlloc(data, initialCommittedSize, MEM_COMMIT, PAGE_READWRITE)) {
202 VirtualFree(data, 0, MEM_RELEASE);
203 return nullptr;
205 #elif defined(__wasi__)
206 void* data = nullptr;
207 if (int err = posix_memalign(&data, gc::SystemPageSize(), mappedSize)) {
208 MOZ_ASSERT(err == ENOMEM);
209 return nullptr;
211 MOZ_ASSERT(data);
212 memset(data, 0, mappedSize);
213 #else // !XP_WIN && !__wasi__
214 void* data =
215 MozTaggedAnonymousMmap(nullptr, mappedSize, PROT_NONE,
216 MAP_PRIVATE | MAP_ANON, -1, 0, "wasm-reserved");
217 if (data == MAP_FAILED) {
218 return nullptr;
221 // Note we will waste a page on zero-sized memories here
222 if (mprotect(data, initialCommittedSize, PROT_READ | PROT_WRITE)) {
223 munmap(data, mappedSize);
224 return nullptr;
226 #endif // !XP_WIN && !__wasi__
228 #if defined(MOZ_VALGRIND) && \
229 defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
230 VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(
231 (unsigned char*)data + initialCommittedSize,
232 mappedSize - initialCommittedSize);
233 #endif
235 failed.release();
236 return data;
239 bool js::CommitBufferMemory(void* dataEnd, size_t delta) {
240 MOZ_ASSERT(delta);
241 MOZ_ASSERT(delta % gc::SystemPageSize() == 0);
243 #ifdef XP_WIN
244 if (!VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE)) {
245 return false;
247 #elif defined(__wasi__)
248 // posix_memalign'd memory is already committed
249 return true;
250 #else
251 if (mprotect(dataEnd, delta, PROT_READ | PROT_WRITE)) {
252 return false;
254 #endif // XP_WIN
256 #if defined(MOZ_VALGRIND) && \
257 defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
258 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
259 #endif
261 return true;
264 bool js::ExtendBufferMapping(void* dataPointer, size_t mappedSize,
265 size_t newMappedSize) {
266 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
267 MOZ_ASSERT(newMappedSize % gc::SystemPageSize() == 0);
268 MOZ_ASSERT(newMappedSize >= mappedSize);
270 #ifdef XP_WIN
271 void* mappedEnd = (char*)dataPointer + mappedSize;
272 uint32_t delta = newMappedSize - mappedSize;
273 if (!VirtualAlloc(mappedEnd, delta, MEM_RESERVE, PAGE_NOACCESS)) {
274 return false;
276 return true;
277 #elif defined(__wasi__)
278 return false;
279 #elif defined(XP_LINUX)
280 // Note this will not move memory (no MREMAP_MAYMOVE specified)
281 if (MAP_FAILED == mremap(dataPointer, mappedSize, newMappedSize, 0)) {
282 return false;
284 return true;
285 #else
286 // No mechanism for remapping on MacOS and other Unices. Luckily
287 // shouldn't need it here as most of these are 64-bit.
288 return false;
289 #endif
292 void js::UnmapBufferMemory(wasm::IndexType t, void* base, size_t mappedSize) {
293 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
295 #ifdef XP_WIN
296 VirtualFree(base, 0, MEM_RELEASE);
297 #elif defined(__wasi__)
298 free(base);
299 #else
300 munmap(base, mappedSize);
301 #endif // XP_WIN
303 #if defined(MOZ_VALGRIND) && \
304 defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
305 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)base,
306 mappedSize);
307 #endif
309 // Untrack reserved memory *after* releasing memory -- otherwise, a race
310 // condition could enable the creation of unlimited buffers.
311 wasmReservedBytes -= uint64_t(mappedSize);
315 * ArrayBufferObject
317 * This class holds the underlying raw buffer that the TypedArrayObject classes
318 * access. It can be created explicitly and passed to a TypedArrayObject, or
319 * can be created implicitly by constructing a TypedArrayObject with a size.
323 * ArrayBufferObject (base)
326 static const JSClassOps ArrayBufferObjectClassOps = {
327 nullptr, // addProperty
328 nullptr, // delProperty
329 nullptr, // enumerate
330 nullptr, // newEnumerate
331 nullptr, // resolve
332 nullptr, // mayResolve
333 ArrayBufferObject::finalize, // finalize
334 nullptr, // call
335 nullptr, // construct
336 nullptr, // trace
339 static const JSFunctionSpec arraybuffer_functions[] = {
340 JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0),
341 JS_FS_END,
344 static const JSPropertySpec arraybuffer_properties[] = {
345 JS_SELF_HOSTED_SYM_GET(species, "$ArrayBufferSpecies", 0),
346 JS_PS_END,
349 static const JSFunctionSpec arraybuffer_proto_functions[] = {
350 JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
351 JS_FN("transfer", ArrayBufferObject::transfer, 0, 0),
352 JS_FN("transferToFixedLength", ArrayBufferObject::transferToFixedLength, 0,
354 JS_FS_END,
357 static const JSPropertySpec arraybuffer_proto_properties[] = {
358 JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
359 #ifdef NIGHTLY_BUILD
360 JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0),
361 JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0),
362 #endif
363 JS_PSG("detached", ArrayBufferObject::detachedGetter, 0),
364 JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
365 JS_PS_END,
368 static JSObject* CreateArrayBufferPrototype(JSContext* cx, JSProtoKey key) {
369 return GlobalObject::createBlankPrototype(cx, cx->global(),
370 &ArrayBufferObject::protoClass_);
373 static const ClassSpec ArrayBufferObjectClassSpec = {
374 GenericCreateConstructor<ArrayBufferObject::class_constructor, 1,
375 gc::AllocKind::FUNCTION>,
376 CreateArrayBufferPrototype,
377 arraybuffer_functions,
378 arraybuffer_properties,
379 arraybuffer_proto_functions,
380 arraybuffer_proto_properties,
383 static const ClassExtension FixedLengthArrayBufferObjectClassExtension = {
384 ArrayBufferObject::objectMoved<
385 FixedLengthArrayBufferObject>, // objectMovedOp
388 static const ClassExtension ResizableArrayBufferObjectClassExtension = {
389 ArrayBufferObject::objectMoved<
390 ResizableArrayBufferObject>, // objectMovedOp
393 const JSClass ArrayBufferObject::protoClass_ = {
394 "ArrayBuffer.prototype",
395 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer),
396 JS_NULL_CLASS_OPS,
397 &ArrayBufferObjectClassSpec,
400 const JSClass FixedLengthArrayBufferObject::class_ = {
401 "ArrayBuffer",
402 JSCLASS_DELAY_METADATA_BUILDER |
403 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
404 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
405 JSCLASS_BACKGROUND_FINALIZE,
406 &ArrayBufferObjectClassOps,
407 &ArrayBufferObjectClassSpec,
408 &FixedLengthArrayBufferObjectClassExtension,
411 const JSClass ResizableArrayBufferObject::class_ = {
412 "ArrayBuffer",
413 JSCLASS_DELAY_METADATA_BUILDER |
414 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
415 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
416 JSCLASS_BACKGROUND_FINALIZE,
417 &ArrayBufferObjectClassOps,
418 &ArrayBufferObjectClassSpec,
419 &ResizableArrayBufferObjectClassExtension,
422 static bool IsArrayBuffer(HandleValue v) {
423 return v.isObject() && v.toObject().is<ArrayBufferObject>();
426 MOZ_ALWAYS_INLINE bool ArrayBufferObject::byteLengthGetterImpl(
427 JSContext* cx, const CallArgs& args) {
428 MOZ_ASSERT(IsArrayBuffer(args.thisv()));
429 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
430 args.rval().setNumber(buffer->byteLength());
431 return true;
434 bool ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc,
435 Value* vp) {
436 CallArgs args = CallArgsFromVp(argc, vp);
437 return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
441 * ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability )
443 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffercopyanddetach
445 static ArrayBufferObject* ArrayBufferCopyAndDetach(
446 JSContext* cx, Handle<ArrayBufferObject*> arrayBuffer,
447 Handle<Value> newLength) {
448 // Steps 1-2. (Not applicable in our implementation.)
450 // Steps 3-4.
451 uint64_t newByteLength;
452 if (newLength.isUndefined()) {
453 // Step 3.a.
454 newByteLength = arrayBuffer->byteLength();
455 } else {
456 // Step 4.a.
457 if (!ToIndex(cx, newLength, &newByteLength)) {
458 return nullptr;
462 // Step 5.
463 if (arrayBuffer->isDetached()) {
464 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
465 JSMSG_TYPED_ARRAY_DETACHED);
466 return nullptr;
469 // Steps 6-7. (Not applicable in our implementation.)
470 // We don't yet support resizable ArrayBuffers (bug 1670026).
472 // Step 8.
473 if (arrayBuffer->hasDefinedDetachKey()) {
474 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
475 JSMSG_WASM_NO_TRANSFER);
476 return nullptr;
479 // Steps 9-16.
481 // 25.1.2.1 AllocateArrayBuffer, step 2.
482 // 6.2.9.1 CreateByteDataBlock, step 2.
483 if (!CheckArrayBufferTooLarge(cx, newByteLength)) {
484 return nullptr;
486 return ArrayBufferObject::copyAndDetach(cx, size_t(newByteLength),
487 arrayBuffer);
490 #ifdef NIGHTLY_BUILD
492 * get ArrayBuffer.prototype.maxByteLength
494 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
496 bool ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx,
497 const CallArgs& args) {
498 MOZ_ASSERT(IsArrayBuffer(args.thisv()));
500 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
502 // Steps 4-6.
503 size_t maxByteLength;
504 if (buffer->isResizable()) {
505 maxByteLength = buffer->as<ResizableArrayBufferObject>().maxByteLength();
506 } else {
507 maxByteLength = buffer->byteLength();
509 MOZ_ASSERT_IF(buffer->isDetached(), maxByteLength == 0);
511 // Step 7.
512 args.rval().setNumber(maxByteLength);
513 return true;
517 * get ArrayBuffer.prototype.maxByteLength
519 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
521 bool ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc,
522 Value* vp) {
523 // Steps 1-3.
524 CallArgs args = CallArgsFromVp(argc, vp);
525 return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args);
529 * get ArrayBuffer.prototype.resizable
531 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
533 bool ArrayBufferObject::resizableGetterImpl(JSContext* cx,
534 const CallArgs& args) {
535 MOZ_ASSERT(IsArrayBuffer(args.thisv()));
537 // Step 4.
538 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
539 args.rval().setBoolean(buffer->isResizable());
540 return true;
544 * get ArrayBuffer.prototype.resizable
546 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
548 bool ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc,
549 Value* vp) {
550 // Steps 1-3.
551 CallArgs args = CallArgsFromVp(argc, vp);
552 return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args);
554 #endif
557 * get ArrayBuffer.prototype.detached
559 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
561 bool ArrayBufferObject::detachedGetterImpl(JSContext* cx,
562 const CallArgs& args) {
563 MOZ_ASSERT(IsArrayBuffer(args.thisv()));
565 // Step 4.
566 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
567 args.rval().setBoolean(buffer->isDetached());
568 return true;
572 * get ArrayBuffer.prototype.detached
574 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
576 bool ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc,
577 Value* vp) {
578 // Steps 1-3.
579 CallArgs args = CallArgsFromVp(argc, vp);
580 return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args);
584 * ArrayBuffer.prototype.transfer ( [ newLength ] )
586 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
588 bool ArrayBufferObject::transferImpl(JSContext* cx, const CallArgs& args) {
589 MOZ_ASSERT(IsArrayBuffer(args.thisv()));
591 // Steps 1-2.
592 Rooted<ArrayBufferObject*> buffer(
593 cx, &args.thisv().toObject().as<ArrayBufferObject>());
594 auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0));
595 if (!newBuffer) {
596 return false;
599 args.rval().setObject(*newBuffer);
600 return true;
604 * ArrayBuffer.prototype.transfer ( [ newLength ] )
606 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
608 bool ArrayBufferObject::transfer(JSContext* cx, unsigned argc, Value* vp) {
609 CallArgs args = CallArgsFromVp(argc, vp);
610 return CallNonGenericMethod<IsArrayBuffer, transferImpl>(cx, args);
614 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )
616 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
618 bool ArrayBufferObject::transferToFixedLengthImpl(JSContext* cx,
619 const CallArgs& args) {
620 MOZ_ASSERT(IsArrayBuffer(args.thisv()));
622 // Steps 1-2.
623 Rooted<ArrayBufferObject*> buffer(
624 cx, &args.thisv().toObject().as<ArrayBufferObject>());
625 auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0));
626 if (!newBuffer) {
627 return false;
630 args.rval().setObject(*newBuffer);
631 return true;
635 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )
637 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
639 bool ArrayBufferObject::transferToFixedLength(JSContext* cx, unsigned argc,
640 Value* vp) {
641 CallArgs args = CallArgsFromVp(argc, vp);
642 return CallNonGenericMethod<IsArrayBuffer, transferToFixedLengthImpl>(cx,
643 args);
647 * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
649 bool ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp) {
650 CallArgs args = CallArgsFromVp(argc, vp);
651 args.rval().setBoolean(args.get(0).isObject() &&
652 JS_IsArrayBufferViewObject(&args.get(0).toObject()));
653 return true;
656 // ES2017 draft 24.1.2.1
657 bool ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc,
658 Value* vp) {
659 CallArgs args = CallArgsFromVp(argc, vp);
661 // Step 1.
662 if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer")) {
663 return false;
666 // Step 2.
667 uint64_t byteLength;
668 if (!ToIndex(cx, args.get(0), &byteLength)) {
669 return false;
672 mozilla::Maybe<uint64_t> maxByteLength;
673 #ifdef NIGHTLY_BUILD
674 // Step 3.
675 if (cx->realm()->creationOptions().getArrayBufferResizableEnabled()) {
676 // Inline call to GetArrayBufferMaxByteLengthOption.
677 if (args.get(1).isObject()) {
678 Rooted<JSObject*> options(cx, &args[1].toObject());
680 Rooted<Value> val(cx);
681 if (!GetProperty(cx, options, options, cx->names().maxByteLength, &val)) {
682 return false;
684 if (!val.isUndefined()) {
685 uint64_t maxByteLengthInt;
686 if (!ToIndex(cx, val, &maxByteLengthInt)) {
687 return false;
690 // AllocateArrayBuffer, step 3.a.
691 if (byteLength > maxByteLengthInt) {
692 JS_ReportErrorNumberASCII(
693 cx, GetErrorMessage, nullptr,
694 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
695 return false;
697 maxByteLength = mozilla::Some(maxByteLengthInt);
701 #endif
703 // Step 4 (Inlined 25.1.2.1 AllocateArrayBuffer).
704 // 25.1.2.1, step 1 (Inlined 10.1.13 OrdinaryCreateFromConstructor).
705 RootedObject proto(cx);
706 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ArrayBuffer,
707 &proto)) {
708 return false;
711 // 25.1.2.1, step 5 (Inlined 6.2.9.1 CreateByteDataBlock, step 2).
712 if (!CheckArrayBufferTooLarge(cx, byteLength)) {
713 return false;
716 if (maxByteLength) {
717 // 25.1.2.1, step 8.a.
718 if (!CheckArrayBufferTooLarge(cx, *maxByteLength)) {
719 return false;
722 auto* bufobj = ResizableArrayBufferObject::createZeroed(
723 cx, byteLength, *maxByteLength, proto);
724 if (!bufobj) {
725 return false;
727 args.rval().setObject(*bufobj);
728 return true;
731 // 25.1.2.1, steps 1 and 4-9.
732 JSObject* bufobj = createZeroed(cx, byteLength, proto);
733 if (!bufobj) {
734 return false;
736 args.rval().setObject(*bufobj);
737 return true;
740 using ArrayBufferContents = UniquePtr<uint8_t[], JS::FreePolicy>;
742 static ArrayBufferContents AllocateUninitializedArrayBufferContents(
743 JSContext* cx, size_t nbytes) {
744 // First attempt a normal allocation.
745 uint8_t* p =
746 cx->maybe_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
747 if (MOZ_UNLIKELY(!p)) {
748 // Otherwise attempt a large allocation, calling the
749 // large-allocation-failure callback if necessary.
750 p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
751 js::AllocFunction::Malloc, js::ArrayBufferContentsArena, nbytes));
752 if (!p) {
753 ReportOutOfMemory(cx);
757 return ArrayBufferContents(p);
760 static ArrayBufferContents AllocateArrayBufferContents(JSContext* cx,
761 size_t nbytes) {
762 // First attempt a normal allocation.
763 uint8_t* p =
764 cx->maybe_pod_arena_calloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
765 if (MOZ_UNLIKELY(!p)) {
766 // Otherwise attempt a large allocation, calling the
767 // large-allocation-failure callback if necessary.
768 p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
769 js::AllocFunction::Calloc, js::ArrayBufferContentsArena, nbytes));
770 if (!p) {
771 ReportOutOfMemory(cx);
775 return ArrayBufferContents(p);
778 static ArrayBufferContents ReallocateArrayBufferContents(JSContext* cx,
779 uint8_t* old,
780 size_t oldSize,
781 size_t newSize) {
782 // First attempt a normal reallocation.
783 uint8_t* p = cx->maybe_pod_arena_realloc<uint8_t>(
784 js::ArrayBufferContentsArena, old, oldSize, newSize);
785 if (MOZ_UNLIKELY(!p)) {
786 // Otherwise attempt a large allocation, calling the
787 // large-allocation-failure callback if necessary.
788 p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
789 js::AllocFunction::Realloc, js::ArrayBufferContentsArena, newSize,
790 old));
791 if (!p) {
792 ReportOutOfMemory(cx);
796 return ArrayBufferContents(p);
799 static ArrayBufferContents NewCopiedBufferContents(
800 JSContext* cx, Handle<ArrayBufferObject*> buffer) {
801 ArrayBufferContents dataCopy =
802 AllocateUninitializedArrayBufferContents(cx, buffer->byteLength());
803 if (dataCopy) {
804 if (auto count = buffer->byteLength()) {
805 memcpy(dataCopy.get(), buffer->dataPointer(), count);
808 return dataCopy;
811 /* static */
812 void ArrayBufferObject::detach(JSContext* cx,
813 Handle<ArrayBufferObject*> buffer) {
814 cx->check(buffer);
815 MOZ_ASSERT(!buffer->isPreparedForAsmJS());
816 MOZ_ASSERT(!buffer->isLengthPinned());
818 // Update all views of the buffer to account for the buffer having been
819 // detached, and clear the buffer's data and list of views.
821 // Typed object buffers are not exposed and cannot be detached.
823 auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
824 if (InnerViewTable::ViewVector* views =
825 innerViews.maybeViewsUnbarriered(buffer)) {
826 for (size_t i = 0; i < views->length(); i++) {
827 JSObject* view = (*views)[i];
828 view->as<ArrayBufferViewObject>().notifyBufferDetached();
830 innerViews.removeViews(buffer);
832 if (JSObject* view = buffer->firstView()) {
833 view->as<ArrayBufferViewObject>().notifyBufferDetached();
834 buffer->setFirstView(nullptr);
837 if (buffer->dataPointer()) {
838 buffer->releaseData(cx->gcContext());
839 buffer->setDataPointer(BufferContents::createNoData());
842 buffer->setByteLength(0);
843 buffer->setIsDetached();
844 if (buffer->isResizable()) {
845 buffer->as<ResizableArrayBufferObject>().setMaxByteLength(0);
849 /* clang-format off */
851 * [SMDOC] WASM Linear Memory structure
853 * Wasm Raw Buf Linear Memory Structure
855 * The linear heap in Wasm is an mmaped array buffer. Several constants manage
856 * its lifetime:
858 * - byteLength - the wasm-visible current length of the buffer in
859 * bytes. Accesses in the range [0, byteLength] succeed. May only increase.
861 * - boundsCheckLimit - the size against which we perform bounds checks. The
862 * value of this depends on the bounds checking strategy chosen for the array
863 * buffer and the specific bounds checking semantics. For asm.js code and
864 * for wasm code running with explicit bounds checking, it is the always the
865 * same as the byteLength. For wasm code using the huge-memory trick, it is
866 * always wasm::GuardSize smaller than mappedSize.
868 * See also "Linear memory addresses and bounds checking" in
869 * wasm/WasmMemory.cpp.
871 * See also WasmMemoryObject::boundsCheckLimit().
873 * - sourceMaxSize - the optional declared limit on how far byteLength can grow
874 * in pages. This is the unmodified maximum size from the source module or
875 * JS-API invocation. This may not be representable in byte lengths, nor
876 * feasible for a module to actually grow to due to implementation limits.
877 * It is used for correct linking checks and js-types reflection.
879 * - clampedMaxSize - the maximum size on how far the byteLength can grow in
880 * pages. This value respects implementation limits and is always
881 * representable as a byte length. Every memory has a clampedMaxSize, even if
882 * no maximum was specified in source. When a memory has no sourceMaxSize,
883 * the clampedMaxSize will be the maximum amount of memory that can be grown
884 * to while still respecting implementation limits.
886 * - mappedSize - the actual mmapped size. Access in the range [0, mappedSize]
887 * will either succeed, or be handled by the wasm signal handlers. If
888 * sourceMaxSize is present at initialization, then we attempt to map the
889 * whole clampedMaxSize. Otherwise we only map the region needed for the
890 * initial size.
892 * The below diagram shows the layout of the wasm heap. The wasm-visible portion
893 * of the heap starts at 0. There is one extra page prior to the start of the
894 * wasm heap which contains the WasmArrayRawBuffer struct at its end (i.e. right
895 * before the start of the WASM heap).
897 * WasmArrayRawBuffer
898 * \ ArrayBufferObject::dataPointer()
899 * \ /
900 * \ |
901 * ______|_|______________________________________________________
902 * |______|_|______________|___________________|___________________|
903 * 0 byteLength clampedMaxSize mappedSize
905 * \_______________________/
906 * COMMITED
907 * \_____________________________________/
908 * SLOP
909 * \______________________________________________________________/
910 * MAPPED
912 * Invariants on byteLength, clampedMaxSize, and mappedSize:
913 * - byteLength only increases
914 * - 0 <= byteLength <= clampedMaxSize <= mappedSize
915 * - if sourceMaxSize is not specified, mappedSize may grow.
916 * It is otherwise constant.
917 * - initialLength <= clampedMaxSize <= sourceMaxSize (if present)
918 * - clampedMaxSize <= wasm::MaxMemoryPages()
920 * Invariants on boundsCheckLimit:
921 * - for wasm code with the huge-memory trick,
922 * clampedMaxSize <= boundsCheckLimit <= mappedSize
923 * - for asm.js code or wasm with explicit bounds checking,
924 * byteLength == boundsCheckLimit <= clampedMaxSize
925 * - on ARM, boundsCheckLimit must be a valid ARM immediate.
926 * - if sourceMaxSize is not specified, boundsCheckLimit may grow as
927 * mappedSize grows. They are otherwise constant.
929 * NOTE: For asm.js on 32-bit platforms and on all platforms when running with
930 * explicit bounds checking, we guarantee that
932 * byteLength == maxSize == boundsCheckLimit == mappedSize
934 * That is, signal handlers will not be invoked.
936 * The region between byteLength and mappedSize is the SLOP - an area where we use
937 * signal handlers to catch things that slip by bounds checks. Logically it has
938 * two parts:
940 * - from byteLength to boundsCheckLimit - this part of the SLOP serves to catch
941 * accesses to memory we have reserved but not yet grown into. This allows us
942 * to grow memory up to max (when present) without having to patch/update the
943 * bounds checks.
945 * - from boundsCheckLimit to mappedSize - this part of the SLOP allows us to
946 * bounds check against base pointers and fold some constant offsets inside
947 * loads. This enables better Bounds Check Elimination. See "Linear memory
948 * addresses and bounds checking" in wasm/WasmMemory.cpp.
951 /* clang-format on */
953 [[nodiscard]] bool WasmArrayRawBuffer::growToPagesInPlace(Pages newPages) {
954 size_t newSize = newPages.byteLength();
955 size_t oldSize = byteLength();
957 MOZ_ASSERT(newSize >= oldSize);
958 MOZ_ASSERT(newPages <= clampedMaxPages());
959 MOZ_ASSERT(newSize <= mappedSize());
961 size_t delta = newSize - oldSize;
962 MOZ_ASSERT(delta % wasm::PageSize == 0);
964 uint8_t* dataEnd = dataPointer() + oldSize;
965 MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
967 if (delta && !CommitBufferMemory(dataEnd, delta)) {
968 return false;
971 length_ = newSize;
973 return true;
976 bool WasmArrayRawBuffer::extendMappedSize(Pages maxPages) {
977 size_t newMappedSize = wasm::ComputeMappedSize(maxPages);
978 MOZ_ASSERT(mappedSize_ <= newMappedSize);
979 if (mappedSize_ == newMappedSize) {
980 return true;
983 if (!ExtendBufferMapping(dataPointer(), mappedSize_, newMappedSize)) {
984 return false;
987 mappedSize_ = newMappedSize;
988 return true;
991 void WasmArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages) {
992 Pages newMaxPages = clampedMaxPages_;
994 DebugOnly<bool> valid = newMaxPages.checkedIncrement(deltaMaxPages);
995 // Caller must ensure increment does not overflow or increase over the
996 // specified maximum pages.
997 MOZ_ASSERT(valid);
998 MOZ_ASSERT_IF(sourceMaxPages_.isSome(), newMaxPages <= *sourceMaxPages_);
1000 if (!extendMappedSize(newMaxPages)) {
1001 return;
1003 clampedMaxPages_ = newMaxPages;
1006 void WasmArrayRawBuffer::discard(size_t byteOffset, size_t byteLen) {
1007 uint8_t* memBase = dataPointer();
1009 // The caller is responsible for ensuring these conditions are met; see this
1010 // function's comment in ArrayBufferObject.h.
1011 MOZ_ASSERT(byteOffset % wasm::PageSize == 0);
1012 MOZ_ASSERT(byteLen % wasm::PageSize == 0);
1013 MOZ_ASSERT(wasm::MemoryBoundsCheck(uint64_t(byteOffset), uint64_t(byteLen),
1014 byteLength()));
1016 // Discarding zero bytes "succeeds" with no effect.
1017 if (byteLen == 0) {
1018 return;
1021 void* addr = memBase + uintptr_t(byteOffset);
1023 // On POSIX-ish platforms, we discard memory by overwriting previously-mapped
1024 // pages with freshly-mapped pages (which are all zeroed). The operating
1025 // system recognizes this and decreases the process RSS, and eventually
1026 // collects the abandoned physical pages.
1028 // On Windows, committing over previously-committed pages has no effect, and
1029 // the memory must be explicitly decommitted first. This is not the same as an
1030 // munmap; the address space is still reserved.
1032 #ifdef XP_WIN
1033 if (!VirtualFree(addr, byteLen, MEM_DECOMMIT)) {
1034 MOZ_CRASH("wasm discard: failed to decommit memory");
1036 if (!VirtualAlloc(addr, byteLen, MEM_COMMIT, PAGE_READWRITE)) {
1037 MOZ_CRASH("wasm discard: decommitted memory but failed to recommit");
1039 #elif defined(__wasi__)
1040 memset(addr, 0, byteLen);
1041 #else // !XP_WIN
1042 void* data = MozTaggedAnonymousMmap(addr, byteLen, PROT_READ | PROT_WRITE,
1043 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0,
1044 "wasm-reserved");
1045 if (data == MAP_FAILED) {
1046 MOZ_CRASH("failed to discard wasm memory; memory mappings may be broken");
1048 #endif
1051 /* static */
1052 WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm(
1053 IndexType indexType, Pages initialPages, Pages clampedMaxPages,
1054 const Maybe<Pages>& sourceMaxPages, const Maybe<size_t>& mapped) {
1055 // Prior code has asserted that initial pages is within our implementation
1056 // limits (wasm::MaxMemoryPages) and we can assume it is a valid size_t.
1057 MOZ_ASSERT(initialPages.hasByteLength());
1058 size_t numBytes = initialPages.byteLength();
1060 // If there is a specified maximum, attempt to map the whole range for
1061 // clampedMaxPages. Or else map only what's required for initialPages.
1062 Pages initialMappedPages =
1063 sourceMaxPages.isSome() ? clampedMaxPages : initialPages;
1065 // Use an override mapped size, or else compute the mapped size from
1066 // initialMappedPages.
1067 size_t mappedSize =
1068 mapped.isSome() ? *mapped : wasm::ComputeMappedSize(initialMappedPages);
1070 MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
1071 MOZ_RELEASE_ASSERT(numBytes <= SIZE_MAX - gc::SystemPageSize());
1072 MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages);
1073 MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
1074 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
1076 uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize();
1077 uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize();
1079 void* data = MapBufferMemory(indexType, (size_t)mappedSizeWithHeader,
1080 (size_t)numBytesWithHeader);
1081 if (!data) {
1082 return nullptr;
1085 uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize();
1086 uint8_t* header = base - sizeof(WasmArrayRawBuffer);
1088 auto rawBuf = new (header) WasmArrayRawBuffer(
1089 indexType, base, clampedMaxPages, sourceMaxPages, mappedSize, numBytes);
1090 return rawBuf;
1093 /* static */
1094 void WasmArrayRawBuffer::Release(void* mem) {
1095 WasmArrayRawBuffer* header =
1096 (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer));
1098 MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());
1099 size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
1101 static_assert(std::is_trivially_destructible_v<WasmArrayRawBuffer>,
1102 "no need to call the destructor");
1104 UnmapBufferMemory(header->indexType(), header->basePointer(),
1105 mappedSizeWithHeader);
1108 WasmArrayRawBuffer* ArrayBufferObject::BufferContents::wasmBuffer() const {
1109 MOZ_RELEASE_ASSERT(kind_ == WASM);
1110 return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
1113 template <typename ObjT, typename RawbufT>
1114 static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer(
1115 JSContext* cx, const wasm::MemoryDesc& memory) {
1116 bool useHugeMemory = wasm::IsHugeMemoryEnabled(memory.indexType());
1117 Pages initialPages = memory.initialPages();
1118 Maybe<Pages> sourceMaxPages = memory.maximumPages();
1119 Pages clampedMaxPages = wasm::ClampedMaxPages(
1120 memory.indexType(), initialPages, sourceMaxPages, useHugeMemory);
1122 Maybe<size_t> mappedSize;
1123 #ifdef WASM_SUPPORTS_HUGE_MEMORY
1124 // Override the mapped size if we are using huge memory. If we are not, then
1125 // it will be calculated by the raw buffer we are using.
1126 if (useHugeMemory) {
1127 mappedSize = Some(wasm::HugeMappedSize);
1129 #endif
1131 RawbufT* buffer =
1132 RawbufT::AllocateWasm(memory.limits.indexType, initialPages,
1133 clampedMaxPages, sourceMaxPages, mappedSize);
1134 if (!buffer) {
1135 if (useHugeMemory) {
1136 WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED);
1137 if (cx->isExceptionPending()) {
1138 cx->clearPendingException();
1141 ReportOutOfMemory(cx);
1142 return nullptr;
1145 // If we fail, and have a sourceMaxPages, try to reserve the biggest
1146 // chunk in the range [initialPages, clampedMaxPages) using log backoff.
1147 if (!sourceMaxPages) {
1148 wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed",
1149 initialPages.value());
1150 ReportOutOfMemory(cx);
1151 return nullptr;
1154 uint64_t cur = clampedMaxPages.value() / 2;
1155 for (; Pages(cur) > initialPages; cur /= 2) {
1156 buffer = RawbufT::AllocateWasm(memory.limits.indexType, initialPages,
1157 Pages(cur), sourceMaxPages, mappedSize);
1158 if (buffer) {
1159 break;
1163 if (!buffer) {
1164 wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed",
1165 initialPages.value());
1166 ReportOutOfMemory(cx);
1167 return nullptr;
1170 // Try to grow our chunk as much as possible.
1171 for (size_t d = cur / 2; d >= 1; d /= 2) {
1172 buffer->tryGrowMaxPagesInPlace(Pages(d));
1176 // ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case
1177 // of failure.
1178 Rooted<ArrayBufferObjectMaybeShared*> object(
1179 cx, ObjT::createFromNewRawBuffer(cx, buffer, initialPages.byteLength()));
1180 if (!object) {
1181 return nullptr;
1184 // See MaximumLiveMappedBuffers comment above.
1185 if (wasmReservedBytes > WasmReservedBytesStartSyncFullGC) {
1186 JS::PrepareForFullGC(cx);
1187 JS::NonIncrementalGC(cx, JS::GCOptions::Normal,
1188 JS::GCReason::TOO_MUCH_WASM_MEMORY);
1189 wasmReservedBytesSinceLast = 0;
1190 } else if (wasmReservedBytes > WasmReservedBytesStartTriggering) {
1191 wasmReservedBytesSinceLast += uint64_t(buffer->mappedSize());
1192 if (wasmReservedBytesSinceLast > WasmReservedBytesPerTrigger) {
1193 (void)cx->runtime()->gc.triggerGC(JS::GCReason::TOO_MUCH_WASM_MEMORY);
1194 wasmReservedBytesSinceLast = 0;
1196 } else {
1197 wasmReservedBytesSinceLast = 0;
1200 // Log the result with details on the memory allocation
1201 if (sourceMaxPages) {
1202 if (useHugeMemory) {
1203 wasm::Log(cx,
1204 "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64
1205 " pages}) succeeded",
1206 initialPages.value(), sourceMaxPages->value());
1207 } else {
1208 wasm::Log(cx,
1209 "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64
1210 " pages}) succeeded "
1211 "with internal maximum of %" PRIu64 " pages",
1212 initialPages.value(), sourceMaxPages->value(),
1213 object->wasmClampedMaxPages().value());
1215 } else {
1216 wasm::Log(cx, "new Memory({initial:%" PRIu64 " pages}) succeeded",
1217 initialPages.value());
1220 return object;
1223 ArrayBufferObjectMaybeShared* js::CreateWasmBuffer(
1224 JSContext* cx, const wasm::MemoryDesc& memory) {
1225 MOZ_RELEASE_ASSERT(memory.initialPages() <=
1226 wasm::MaxMemoryPages(memory.indexType()));
1227 MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);
1229 if (memory.isShared()) {
1230 if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
1231 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1232 JSMSG_WASM_NO_SHMEM_LINK);
1233 return nullptr;
1235 return CreateSpecificWasmBuffer<SharedArrayBufferObject,
1236 WasmSharedArrayRawBuffer>(cx, memory);
1238 return CreateSpecificWasmBuffer<ArrayBufferObject, WasmArrayRawBuffer>(
1239 cx, memory);
1242 bool ArrayBufferObject::prepareForAsmJS() {
1243 MOZ_ASSERT(byteLength() % wasm::PageSize == 0,
1244 "prior size checking should have guaranteed page-size multiple");
1245 MOZ_ASSERT(byteLength() > 0,
1246 "prior size checking should have excluded empty buffers");
1247 MOZ_ASSERT(!isResizable(),
1248 "prior checks should have excluded resizable buffers");
1250 switch (bufferKind()) {
1251 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
1252 case MALLOCED_UNKNOWN_ARENA:
1253 case MAPPED:
1254 case EXTERNAL:
1255 // It's okay if this uselessly sets the flag a second time.
1256 setIsPreparedForAsmJS();
1257 return true;
1259 case INLINE_DATA:
1260 static_assert(
1261 wasm::PageSize > FixedLengthArrayBufferObject::MaxInlineBytes,
1262 "inline data must be too small to be a page size multiple");
1263 MOZ_ASSERT_UNREACHABLE(
1264 "inline-data buffers should be implicitly excluded by size checks");
1265 return false;
1267 case NO_DATA:
1268 MOZ_ASSERT_UNREACHABLE(
1269 "size checking should have excluded detached or empty buffers");
1270 return false;
1272 // asm.js code and associated buffers are potentially long-lived. Yet a
1273 // buffer of user-owned data *must* be detached by the user before the
1274 // user-owned data is disposed. No caller wants to use a user-owned
1275 // ArrayBuffer with asm.js, so just don't support this and avoid a mess of
1276 // complexity.
1277 case USER_OWNED:
1278 // wasm buffers can be detached at any time.
1279 case WASM:
1280 MOZ_ASSERT(!isPreparedForAsmJS());
1281 return false;
1284 MOZ_ASSERT_UNREACHABLE("non-exhaustive kind-handling switch?");
1285 return false;
1288 ArrayBufferObject::BufferContents ArrayBufferObject::createMappedContents(
1289 int fd, size_t offset, size_t length) {
1290 void* data =
1291 gc::AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
1292 return BufferContents::createMapped(data);
1295 uint8_t* FixedLengthArrayBufferObject::inlineDataPointer() const {
1296 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
1299 uint8_t* ResizableArrayBufferObject::inlineDataPointer() const {
1300 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
1303 uint8_t* ArrayBufferObject::dataPointer() const {
1304 return static_cast<uint8_t*>(getFixedSlot(DATA_SLOT).toPrivate());
1307 SharedMem<uint8_t*> ArrayBufferObject::dataPointerShared() const {
1308 return SharedMem<uint8_t*>::unshared(getFixedSlot(DATA_SLOT).toPrivate());
1311 ArrayBufferObject::FreeInfo* ArrayBufferObject::freeInfo() const {
1312 MOZ_ASSERT(isExternal());
1313 MOZ_ASSERT(!isResizable());
1314 auto* data = as<FixedLengthArrayBufferObject>().inlineDataPointer();
1315 return reinterpret_cast<FreeInfo*>(data);
1318 void ArrayBufferObject::releaseData(JS::GCContext* gcx) {
1319 switch (bufferKind()) {
1320 case INLINE_DATA:
1321 // Inline data doesn't require releasing.
1322 break;
1323 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
1324 case MALLOCED_UNKNOWN_ARENA:
1325 gcx->free_(this, dataPointer(), associatedBytes(),
1326 MemoryUse::ArrayBufferContents);
1327 break;
1328 case NO_DATA:
1329 // There's nothing to release if there's no data.
1330 MOZ_ASSERT(dataPointer() == nullptr);
1331 break;
1332 case USER_OWNED:
1333 // User-owned data is released by, well, the user.
1334 break;
1335 case MAPPED:
1336 gc::DeallocateMappedContent(dataPointer(), byteLength());
1337 gcx->removeCellMemory(this, associatedBytes(),
1338 MemoryUse::ArrayBufferContents);
1339 break;
1340 case WASM:
1341 WasmArrayRawBuffer::Release(dataPointer());
1342 gcx->removeCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents);
1343 break;
1344 case EXTERNAL:
1345 MOZ_ASSERT(freeInfo()->freeFunc);
1347 // The analyzer can't know for sure whether the embedder-supplied
1348 // free function will GC. We give the analyzer a hint here.
1349 // (Doing a GC in the free function is considered a programmer
1350 // error.)
1351 JS::AutoSuppressGCAnalysis nogc;
1352 freeInfo()->freeFunc(dataPointer(), freeInfo()->freeUserData);
1354 break;
1358 void ArrayBufferObject::setDataPointer(BufferContents contents) {
1359 setFixedSlot(DATA_SLOT, PrivateValue(contents.data()));
1360 setFlags((flags() & ~KIND_MASK) | contents.kind());
1362 if (isExternal()) {
1363 auto info = freeInfo();
1364 info->freeFunc = contents.freeFunc();
1365 info->freeUserData = contents.freeUserData();
1369 size_t ArrayBufferObject::byteLength() const {
1370 return size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate());
1373 inline size_t ArrayBufferObject::associatedBytes() const {
1374 if (isMalloced()) {
1375 if (isResizable()) {
1376 return as<ResizableArrayBufferObject>().maxByteLength();
1378 return byteLength();
1380 if (isMapped()) {
1381 return RoundUp(byteLength(), js::gc::SystemPageSize());
1383 MOZ_CRASH("Unexpected buffer kind");
1386 void ArrayBufferObject::setByteLength(size_t length) {
1387 MOZ_ASSERT(length <= ArrayBufferObject::MaxByteLength);
1388 setFixedSlot(BYTE_LENGTH_SLOT, PrivateValue(length));
1391 size_t ArrayBufferObject::wasmMappedSize() const {
1392 if (isWasm()) {
1393 return contents().wasmBuffer()->mappedSize();
1395 return byteLength();
1398 IndexType ArrayBufferObject::wasmIndexType() const {
1399 if (isWasm()) {
1400 return contents().wasmBuffer()->indexType();
1402 MOZ_ASSERT(isPreparedForAsmJS());
1403 return wasm::IndexType::I32;
1406 Pages ArrayBufferObject::wasmPages() const {
1407 if (isWasm()) {
1408 return contents().wasmBuffer()->pages();
1410 MOZ_ASSERT(isPreparedForAsmJS());
1411 return Pages::fromByteLengthExact(byteLength());
1414 Pages ArrayBufferObject::wasmClampedMaxPages() const {
1415 if (isWasm()) {
1416 return contents().wasmBuffer()->clampedMaxPages();
1418 MOZ_ASSERT(isPreparedForAsmJS());
1419 return Pages::fromByteLengthExact(byteLength());
1422 Maybe<Pages> ArrayBufferObject::wasmSourceMaxPages() const {
1423 if (isWasm()) {
1424 return contents().wasmBuffer()->sourceMaxPages();
1426 MOZ_ASSERT(isPreparedForAsmJS());
1427 return Some<Pages>(Pages::fromByteLengthExact(byteLength()));
1430 size_t js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf) {
1431 if (buf->is<ArrayBufferObject>()) {
1432 return buf->as<ArrayBufferObject>().wasmMappedSize();
1434 return buf->as<SharedArrayBufferObject>().wasmMappedSize();
1437 IndexType js::WasmArrayBufferIndexType(
1438 const ArrayBufferObjectMaybeShared* buf) {
1439 if (buf->is<ArrayBufferObject>()) {
1440 return buf->as<ArrayBufferObject>().wasmIndexType();
1442 return buf->as<SharedArrayBufferObject>().wasmIndexType();
1444 Pages js::WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf) {
1445 if (buf->is<ArrayBufferObject>()) {
1446 return buf->as<ArrayBufferObject>().wasmPages();
1448 return buf->as<SharedArrayBufferObject>().volatileWasmPages();
1450 Pages js::WasmArrayBufferClampedMaxPages(
1451 const ArrayBufferObjectMaybeShared* buf) {
1452 if (buf->is<ArrayBufferObject>()) {
1453 return buf->as<ArrayBufferObject>().wasmClampedMaxPages();
1455 return buf->as<SharedArrayBufferObject>().wasmClampedMaxPages();
1457 Maybe<Pages> js::WasmArrayBufferSourceMaxPages(
1458 const ArrayBufferObjectMaybeShared* buf) {
1459 if (buf->is<ArrayBufferObject>()) {
1460 return buf->as<ArrayBufferObject>().wasmSourceMaxPages();
1462 return Some(buf->as<SharedArrayBufferObject>().wasmSourceMaxPages());
1465 static void CheckStealPreconditions(Handle<ArrayBufferObject*> buffer,
1466 JSContext* cx) {
1467 cx->check(buffer);
1469 MOZ_ASSERT(!buffer->isDetached(), "can't steal from a detached buffer");
1470 MOZ_ASSERT(!buffer->isPreparedForAsmJS(),
1471 "asm.js-prepared buffers don't have detachable/stealable data");
1474 /* static */
1475 ArrayBufferObject* ArrayBufferObject::wasmGrowToPagesInPlace(
1476 wasm::IndexType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf,
1477 JSContext* cx) {
1478 if (oldBuf->isLengthPinned()) {
1479 return nullptr;
1482 CheckStealPreconditions(oldBuf, cx);
1484 MOZ_ASSERT(oldBuf->isWasm());
1486 // Check that the new pages is within our allowable range. This will
1487 // simultaneously check against the maximum specified in source and our
1488 // implementation limits.
1489 if (newPages > oldBuf->wasmClampedMaxPages()) {
1490 return nullptr;
1492 MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) &&
1493 newPages.byteLength() <= ArrayBufferObject::MaxByteLength);
1495 // We have checked against the clamped maximum and so we know we can convert
1496 // to byte lengths now.
1497 size_t newSize = newPages.byteLength();
1499 // On failure, do not throw and ensure that the original buffer is
1500 // unmodified and valid. After WasmArrayRawBuffer::growToPagesInPlace(), the
1501 // wasm-visible length of the buffer has been increased so it must be the
1502 // last fallible operation.
1504 auto* newBuf = ArrayBufferObject::createEmpty(cx);
1505 if (!newBuf) {
1506 cx->clearPendingException();
1507 return nullptr;
1510 MOZ_ASSERT(newBuf->isNoData());
1512 if (!oldBuf->contents().wasmBuffer()->growToPagesInPlace(newPages)) {
1513 return nullptr;
1516 // Extract the grown contents from |oldBuf|.
1517 BufferContents oldContents = oldBuf->contents();
1519 // Overwrite |oldBuf|'s data pointer *without* releasing old data.
1520 oldBuf->setDataPointer(BufferContents::createNoData());
1522 // Detach |oldBuf| now that doing so won't release |oldContents|.
1523 RemoveCellMemory(oldBuf, oldBuf->byteLength(),
1524 MemoryUse::ArrayBufferContents);
1525 ArrayBufferObject::detach(cx, oldBuf);
1527 // Set |newBuf|'s contents to |oldBuf|'s original contents.
1528 newBuf->initialize(newSize, oldContents);
1529 AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents);
1531 return newBuf;
1534 /* static */
1535 ArrayBufferObject* ArrayBufferObject::wasmMovingGrowToPages(
1536 IndexType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf,
1537 JSContext* cx) {
1538 // On failure, do not throw and ensure that the original buffer is
1539 // unmodified and valid.
1540 if (oldBuf->isLengthPinned()) {
1541 return nullptr;
1544 // Check that the new pages is within our allowable range. This will
1545 // simultaneously check against the maximum specified in source and our
1546 // implementation limits.
1547 if (newPages > oldBuf->wasmClampedMaxPages()) {
1548 return nullptr;
1550 MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) &&
1551 newPages.byteLength() < ArrayBufferObject::MaxByteLength);
1553 // We have checked against the clamped maximum and so we know we can convert
1554 // to byte lengths now.
1555 size_t newSize = newPages.byteLength();
1557 if (wasm::ComputeMappedSize(newPages) <= oldBuf->wasmMappedSize() ||
1558 oldBuf->contents().wasmBuffer()->extendMappedSize(newPages)) {
1559 return wasmGrowToPagesInPlace(t, newPages, oldBuf, cx);
1562 Rooted<ArrayBufferObject*> newBuf(cx, ArrayBufferObject::createEmpty(cx));
1563 if (!newBuf) {
1564 cx->clearPendingException();
1565 return nullptr;
1568 Pages clampedMaxPages =
1569 wasm::ClampedMaxPages(t, newPages, Nothing(), /* hugeMemory */ false);
1570 WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::AllocateWasm(
1571 oldBuf->wasmIndexType(), newPages, clampedMaxPages, Nothing(), Nothing());
1572 if (!newRawBuf) {
1573 return nullptr;
1576 AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents);
1578 BufferContents contents =
1579 BufferContents::createWasm(newRawBuf->dataPointer());
1580 newBuf->initialize(newSize, contents);
1582 memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
1583 ArrayBufferObject::detach(cx, oldBuf);
1585 return newBuf;
1588 /* static */
1589 void ArrayBufferObject::wasmDiscard(Handle<ArrayBufferObject*> buf,
1590 uint64_t byteOffset, uint64_t byteLen) {
1591 MOZ_ASSERT(buf->isWasm());
1592 buf->contents().wasmBuffer()->discard(byteOffset, byteLen);
1595 uint32_t ArrayBufferObject::flags() const {
1596 return uint32_t(getFixedSlot(FLAGS_SLOT).toInt32());
1599 void ArrayBufferObject::setFlags(uint32_t flags) {
1600 setFixedSlot(FLAGS_SLOT, Int32Value(flags));
1603 static constexpr js::gc::AllocKind GetArrayBufferGCObjectKind(size_t numSlots) {
1604 if (numSlots <= 4) {
1605 return js::gc::AllocKind::ARRAYBUFFER4;
1607 if (numSlots <= 8) {
1608 return js::gc::AllocKind::ARRAYBUFFER8;
1610 if (numSlots <= 12) {
1611 return js::gc::AllocKind::ARRAYBUFFER12;
1613 return js::gc::AllocKind::ARRAYBUFFER16;
1616 template <class ArrayBufferType>
1617 static ArrayBufferType* NewArrayBufferObject(JSContext* cx, HandleObject proto_,
1618 gc::AllocKind allocKind) {
1619 MOZ_ASSERT(allocKind == gc::AllocKind::ARRAYBUFFER4 ||
1620 allocKind == gc::AllocKind::ARRAYBUFFER8 ||
1621 allocKind == gc::AllocKind::ARRAYBUFFER12 ||
1622 allocKind == gc::AllocKind::ARRAYBUFFER16);
1624 static_assert(std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject> ||
1625 std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>);
1627 RootedObject proto(cx, proto_);
1628 if (!proto) {
1629 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_ArrayBuffer);
1630 if (!proto) {
1631 return nullptr;
1635 const JSClass* clasp = &ArrayBufferType::class_;
1637 // Array buffers can store data inline so we only use fixed slots to cover the
1638 // reserved slots, ignoring the AllocKind.
1639 MOZ_ASSERT(ClassCanHaveFixedData(clasp));
1640 constexpr size_t nfixed = ArrayBufferType::RESERVED_SLOTS;
1641 static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS);
1643 Rooted<SharedShape*> shape(
1645 SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
1646 nfixed, ObjectFlags()));
1647 if (!shape) {
1648 return nullptr;
1651 // Array buffers can't be nursery allocated but can be background-finalized.
1652 MOZ_ASSERT(IsBackgroundFinalized(allocKind));
1653 MOZ_ASSERT(!CanNurseryAllocateFinalizedClass(clasp));
1654 constexpr gc::Heap heap = gc::Heap::Tenured;
1656 return NativeObject::create<ArrayBufferType>(cx, allocKind, heap, shape);
1659 // Creates a new ArrayBufferObject with %ArrayBuffer.prototype% as proto and no
1660 // space for inline data.
1661 static ArrayBufferObject* NewArrayBufferObject(JSContext* cx) {
1662 constexpr auto allocKind =
1663 GetArrayBufferGCObjectKind(FixedLengthArrayBufferObject::RESERVED_SLOTS);
1664 return NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr,
1665 allocKind);
1667 static ResizableArrayBufferObject* NewResizableArrayBufferObject(
1668 JSContext* cx) {
1669 constexpr auto allocKind =
1670 GetArrayBufferGCObjectKind(ResizableArrayBufferObject::RESERVED_SLOTS);
1671 return NewArrayBufferObject<ResizableArrayBufferObject>(cx, nullptr,
1672 allocKind);
1675 ArrayBufferObject* ArrayBufferObject::createForContents(
1676 JSContext* cx, size_t nbytes, BufferContents contents) {
1677 MOZ_ASSERT(contents);
1678 MOZ_ASSERT(contents.kind() != INLINE_DATA);
1679 MOZ_ASSERT(contents.kind() != NO_DATA);
1680 MOZ_ASSERT(contents.kind() != WASM);
1682 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
1683 if (!CheckArrayBufferTooLarge(cx, nbytes)) {
1684 return nullptr;
1687 // Some |contents| kinds need to store extra data in the ArrayBuffer beyond a
1688 // data pointer. If needed for the particular kind, add extra fixed slots to
1689 // the ArrayBuffer for use as raw storage to store such information.
1690 constexpr size_t reservedSlots = FixedLengthArrayBufferObject::RESERVED_SLOTS;
1692 size_t nAllocated = 0;
1693 size_t nslots = reservedSlots;
1694 if (contents.kind() == USER_OWNED) {
1695 // No accounting to do in this case.
1696 } else if (contents.kind() == EXTERNAL) {
1697 // Store the FreeInfo in the inline data slots so that we
1698 // don't use up slots for it in non-refcounted array buffers.
1699 constexpr size_t freeInfoSlots = HowMany(sizeof(FreeInfo), sizeof(Value));
1700 static_assert(
1701 reservedSlots + freeInfoSlots <= NativeObject::MAX_FIXED_SLOTS,
1702 "FreeInfo must fit in inline slots");
1703 nslots += freeInfoSlots;
1704 } else {
1705 // The ABO is taking ownership, so account the bytes against the zone.
1706 nAllocated = nbytes;
1707 if (contents.kind() == MAPPED) {
1708 nAllocated = RoundUp(nbytes, js::gc::SystemPageSize());
1709 } else {
1710 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
1711 contents.kind() == MALLOCED_UNKNOWN_ARENA,
1712 "should have handled all possible callers' kinds");
1716 gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);
1718 AutoSetNewObjectMetadata metadata(cx);
1719 Rooted<ArrayBufferObject*> buffer(
1720 cx, NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr,
1721 allocKind));
1722 if (!buffer) {
1723 return nullptr;
1726 MOZ_ASSERT(!gc::IsInsideNursery(buffer),
1727 "ArrayBufferObject has a finalizer that must be called to not "
1728 "leak in some cases, so it can't be nursery-allocated");
1730 buffer->initialize(nbytes, contents);
1732 if (contents.kind() == MAPPED ||
1733 contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
1734 contents.kind() == MALLOCED_UNKNOWN_ARENA) {
1735 AddCellMemory(buffer, nAllocated, MemoryUse::ArrayBufferContents);
1738 return buffer;
1741 template <class ArrayBufferType, ArrayBufferObject::FillContents FillType>
1742 /* static */ std::tuple<ArrayBufferType*, uint8_t*>
1743 ArrayBufferObject::createUninitializedBufferAndData(
1744 JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata&,
1745 JS::Handle<JSObject*> proto) {
1746 MOZ_ASSERT(nbytes <= ArrayBufferObject::MaxByteLength,
1747 "caller must validate the byte count it passes");
1749 static_assert(std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject> ||
1750 std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>);
1752 // Try fitting the data inline with the object by repurposing fixed-slot
1753 // storage. Add extra fixed slots if necessary to accomplish this, but don't
1754 // exceed the maximum number of fixed slots!
1755 size_t nslots = ArrayBufferType::RESERVED_SLOTS;
1756 ArrayBufferContents data;
1757 if (nbytes <= ArrayBufferType::MaxInlineBytes) {
1758 int newSlots = HowMany(nbytes, sizeof(Value));
1759 MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
1761 nslots += newSlots;
1762 } else {
1763 data = FillType == FillContents::Uninitialized
1764 ? AllocateUninitializedArrayBufferContents(cx, nbytes)
1765 : AllocateArrayBufferContents(cx, nbytes);
1766 if (!data) {
1767 return {nullptr, nullptr};
1771 gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);
1773 auto* buffer = NewArrayBufferObject<ArrayBufferType>(cx, proto, allocKind);
1774 if (!buffer) {
1775 return {nullptr, nullptr};
1778 MOZ_ASSERT(!gc::IsInsideNursery(buffer),
1779 "ArrayBufferObject has a finalizer that must be called to not "
1780 "leak in some cases, so it can't be nursery-allocated");
1782 if (data) {
1783 return {buffer, data.release()};
1786 if constexpr (FillType == FillContents::Zero) {
1787 memset(buffer->inlineDataPointer(), 0, nbytes);
1789 return {buffer, nullptr};
1792 template <ArrayBufferObject::FillContents FillType>
1793 /* static */ std::tuple<ArrayBufferObject*, uint8_t*>
1794 ArrayBufferObject::createBufferAndData(
1795 JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata,
1796 JS::Handle<JSObject*> proto /* = nullptr */) {
1797 MOZ_ASSERT(nbytes <= ArrayBufferObject::MaxByteLength,
1798 "caller must validate the byte count it passes");
1800 auto [buffer, data] =
1801 createUninitializedBufferAndData<FixedLengthArrayBufferObject, FillType>(
1802 cx, nbytes, metadata, proto);
1803 if (!buffer) {
1804 return {nullptr, nullptr};
1807 if (data) {
1808 buffer->initialize(nbytes, BufferContents::createMallocedArrayBufferContentsArena(data));
1809 AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
1810 } else {
1811 data = buffer->inlineDataPointer();
1812 buffer->initialize(nbytes, BufferContents::createInlineData(data));
1814 return {buffer, data};
1817 template <ArrayBufferObject::FillContents FillType>
1818 /* static */ std::tuple<ResizableArrayBufferObject*, uint8_t*>
1819 ResizableArrayBufferObject::createBufferAndData(
1820 JSContext* cx, size_t byteLength, size_t maxByteLength,
1821 AutoSetNewObjectMetadata& metadata, Handle<JSObject*> proto) {
1822 MOZ_ASSERT(byteLength <= maxByteLength);
1823 MOZ_ASSERT(maxByteLength <= ArrayBufferObject::MaxByteLength,
1824 "caller must validate the byte count it passes");
1826 // NOTE: The spec proposal for resizable ArrayBuffers suggests to use a
1827 // virtual memory based approach to avoid eagerly allocating the maximum byte
1828 // length. We don't yet support this and instead are allocating the maximum
1829 // byte length direct from the start.
1830 size_t nbytes = maxByteLength;
1832 auto [buffer, data] =
1833 createUninitializedBufferAndData<ResizableArrayBufferObject, FillType>(
1834 cx, nbytes, metadata, proto);
1835 if (!buffer) {
1836 return {nullptr, nullptr};
1839 if (data) {
1840 buffer->initialize(byteLength, maxByteLength,
1841 BufferContents::createMallocedArrayBufferContentsArena(data));
1842 AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
1843 } else {
1844 data = buffer->inlineDataPointer();
1845 buffer->initialize(byteLength, maxByteLength,
1846 BufferContents::createInlineData(data));
1848 return {buffer, data};
1851 /* static */ ArrayBufferObject* ArrayBufferObject::copy(
1852 JSContext* cx, size_t newByteLength,
1853 JS::Handle<ArrayBufferObject*> source) {
1854 MOZ_ASSERT(!source->isDetached());
1855 MOZ_ASSERT(newByteLength <= ArrayBufferObject::MaxByteLength,
1856 "caller must validate the byte count it passes");
1858 size_t sourceByteLength = source->byteLength();
1860 if (newByteLength > sourceByteLength) {
1861 // Copy into a larger buffer.
1862 AutoSetNewObjectMetadata metadata(cx);
1863 auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
1864 cx, newByteLength, metadata, nullptr);
1865 if (!buffer) {
1866 return nullptr;
1869 std::copy_n(source->dataPointer(), sourceByteLength, toFill);
1871 return buffer;
1874 // Copy into a smaller or same size buffer.
1875 AutoSetNewObjectMetadata metadata(cx);
1876 auto [buffer, toFill] = createBufferAndData<FillContents::Uninitialized>(
1877 cx, newByteLength, metadata, nullptr);
1878 if (!buffer) {
1879 return nullptr;
1882 std::uninitialized_copy_n(source->dataPointer(), newByteLength, toFill);
1884 return buffer;
1887 /* static */ ArrayBufferObject* ArrayBufferObject::copyAndDetach(
1888 JSContext* cx, size_t newByteLength,
1889 JS::Handle<ArrayBufferObject*> source) {
1890 MOZ_ASSERT(!source->isDetached());
1891 MOZ_ASSERT(newByteLength <= ArrayBufferObject::MaxByteLength,
1892 "caller must validate the byte count it passes");
1894 if (newByteLength > FixedLengthArrayBufferObject::MaxInlineBytes &&
1895 source->isMalloced()) {
1896 if (newByteLength == source->associatedBytes()) {
1897 return copyAndDetachSteal(cx, source);
1899 if (source->bufferKind() ==
1900 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA) {
1901 return copyAndDetachRealloc(cx, newByteLength, source);
1905 auto* newBuffer = ArrayBufferObject::copy(cx, newByteLength, source);
1906 if (!newBuffer) {
1907 return nullptr;
1909 ArrayBufferObject::detach(cx, source);
1911 return newBuffer;
1914 /* static */ ArrayBufferObject* ArrayBufferObject::copyAndDetachSteal(
1915 JSContext* cx, JS::Handle<ArrayBufferObject*> source) {
1916 MOZ_ASSERT(!source->isDetached());
1917 MOZ_ASSERT(source->isMalloced());
1919 size_t newByteLength = source->associatedBytes();
1920 MOZ_ASSERT(newByteLength > FixedLengthArrayBufferObject::MaxInlineBytes,
1921 "prefer copying small buffers");
1922 MOZ_ASSERT(source->byteLength() <= newByteLength,
1923 "source length is less-or-equal to |newByteLength|");
1925 auto* newBuffer = ArrayBufferObject::createEmpty(cx);
1926 if (!newBuffer) {
1927 return nullptr;
1930 // Extract the contents from |source|.
1931 BufferContents contents = source->contents();
1932 MOZ_ASSERT(contents);
1933 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
1934 contents.kind() == MALLOCED_UNKNOWN_ARENA);
1936 // Overwrite |source|'s data pointer *without* releasing the data.
1937 source->setDataPointer(BufferContents::createNoData());
1939 // Detach |source| now that doing so won't release |contents|.
1940 RemoveCellMemory(source, newByteLength, MemoryUse::ArrayBufferContents);
1941 ArrayBufferObject::detach(cx, source);
1943 // Set |newBuffer|'s contents to |source|'s original contents.
1944 newBuffer->initialize(newByteLength, contents);
1945 AddCellMemory(newBuffer, newByteLength, MemoryUse::ArrayBufferContents);
1947 return newBuffer;
1950 /* static */ ArrayBufferObject* ArrayBufferObject::copyAndDetachRealloc(
1951 JSContext* cx, size_t newByteLength,
1952 JS::Handle<ArrayBufferObject*> source) {
1953 MOZ_ASSERT(!source->isDetached());
1954 MOZ_ASSERT(source->bufferKind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);
1955 MOZ_ASSERT(newByteLength > FixedLengthArrayBufferObject::MaxInlineBytes,
1956 "prefer copying small buffers");
1957 MOZ_ASSERT(newByteLength <= ArrayBufferObject::MaxByteLength,
1958 "caller must validate the byte count it passes");
1960 size_t oldByteLength = source->associatedBytes();
1961 MOZ_ASSERT(oldByteLength != newByteLength,
1962 "steal instead of realloc same size buffers");
1963 MOZ_ASSERT(source->byteLength() <= oldByteLength,
1964 "source length is less-or-equal to |oldByteLength|");
1966 Rooted<ArrayBufferObject*> newBuffer(cx, ArrayBufferObject::createEmpty(cx));
1967 if (!newBuffer) {
1968 return nullptr;
1971 // Extract the contents from |source|.
1972 BufferContents contents = source->contents();
1973 MOZ_ASSERT(contents);
1974 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);
1976 // Reallocate the data pointer.
1977 auto newData = ReallocateArrayBufferContents(cx, contents.data(),
1978 oldByteLength, newByteLength);
1979 if (!newData) {
1980 // If reallocation failed, the old pointer is still valid, so just return.
1981 return nullptr;
1983 auto newContents =
1984 BufferContents::createMallocedArrayBufferContentsArena(newData.release());
1986 // Overwrite |source|'s data pointer *without* releasing the data.
1987 source->setDataPointer(BufferContents::createNoData());
1989 // Detach |source| now that doing so won't release |contents|.
1990 RemoveCellMemory(source, oldByteLength, MemoryUse::ArrayBufferContents);
1991 ArrayBufferObject::detach(cx, source);
1993 // Set |newBuffer|'s contents to |newContents|.
1994 newBuffer->initialize(newByteLength, newContents);
1995 AddCellMemory(newBuffer, newByteLength, MemoryUse::ArrayBufferContents);
1997 // Zero initialize the newly allocated memory, if necessary.
1998 if (newByteLength > oldByteLength) {
1999 size_t count = newByteLength - oldByteLength;
2000 std::uninitialized_fill_n(newContents.data() + oldByteLength, count, 0);
2003 return newBuffer;
2006 ArrayBufferObject* ArrayBufferObject::createZeroed(
2007 JSContext* cx, size_t nbytes, HandleObject proto /* = nullptr */) {
2008 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
2009 if (!CheckArrayBufferTooLarge(cx, nbytes)) {
2010 return nullptr;
2013 AutoSetNewObjectMetadata metadata(cx);
2014 auto [buffer, toFill] =
2015 createBufferAndData<FillContents::Zero>(cx, nbytes, metadata, proto);
2016 (void)toFill;
2017 return buffer;
2020 ResizableArrayBufferObject* ResizableArrayBufferObject::createZeroed(
2021 JSContext* cx, size_t byteLength, size_t maxByteLength,
2022 HandleObject proto /* = nullptr */) {
2023 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
2024 if (!CheckArrayBufferTooLarge(cx, byteLength) ||
2025 !CheckArrayBufferTooLarge(cx, maxByteLength)) {
2026 return nullptr;
2028 if (byteLength > maxByteLength) {
2029 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2030 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
2031 return nullptr;
2034 AutoSetNewObjectMetadata metadata(cx);
2035 auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
2036 cx, byteLength, maxByteLength, metadata, proto);
2037 (void)toFill;
2038 return buffer;
2041 ArrayBufferObject* ArrayBufferObject::createEmpty(JSContext* cx) {
2042 AutoSetNewObjectMetadata metadata(cx);
2043 ArrayBufferObject* obj = NewArrayBufferObject(cx);
2044 if (!obj) {
2045 return nullptr;
2048 obj->initialize(0, BufferContents::createNoData());
2049 return obj;
2052 ResizableArrayBufferObject* ResizableArrayBufferObject::createEmpty(
2053 JSContext* cx) {
2054 AutoSetNewObjectMetadata metadata(cx);
2055 auto* obj = NewResizableArrayBufferObject(cx);
2056 if (!obj) {
2057 return nullptr;
2060 obj->initialize(0, 0, BufferContents::createNoData());
2061 return obj;
2064 ArrayBufferObject* ArrayBufferObject::createFromNewRawBuffer(
2065 JSContext* cx, WasmArrayRawBuffer* rawBuffer, size_t initialSize) {
2066 AutoSetNewObjectMetadata metadata(cx);
2067 ArrayBufferObject* buffer = NewArrayBufferObject(cx);
2068 if (!buffer) {
2069 WasmArrayRawBuffer::Release(rawBuffer->dataPointer());
2070 return nullptr;
2073 MOZ_ASSERT(initialSize == rawBuffer->byteLength());
2075 auto contents = BufferContents::createWasm(rawBuffer->dataPointer());
2076 buffer->initialize(initialSize, contents);
2078 AddCellMemory(buffer, initialSize, MemoryUse::ArrayBufferContents);
2080 return buffer;
2083 /* static */ uint8_t* ArrayBufferObject::stealMallocedContents(
2084 JSContext* cx, Handle<ArrayBufferObject*> buffer) {
2085 if (buffer->isLengthPinned()) {
2086 return nullptr;
2088 CheckStealPreconditions(buffer, cx);
2090 switch (buffer->bufferKind()) {
2091 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
2092 case MALLOCED_UNKNOWN_ARENA: {
2093 uint8_t* stolenData = buffer->dataPointer();
2094 MOZ_ASSERT(stolenData);
2096 // Resizable buffers are initially allocated with their maximum
2097 // byte-length. When stealing the buffer contents shrink the allocated
2098 // memory to the actually used byte-length.
2099 if (buffer->isResizable()) {
2100 auto* resizableBuffer = &buffer->as<ResizableArrayBufferObject>();
2101 size_t byteLength = resizableBuffer->byteLength();
2102 size_t maxByteLength = resizableBuffer->maxByteLength();
2103 MOZ_ASSERT(byteLength <= maxByteLength);
2105 if (byteLength < maxByteLength) {
2106 auto newData = ReallocateArrayBufferContents(
2107 cx, stolenData, maxByteLength, byteLength);
2108 if (!newData) {
2109 // If reallocation failed, the old pointer is still valid. The
2110 // ArrayBuffer isn't detached and still owns the malloc'ed memory.
2111 return nullptr;
2114 // The following code must be infallible, because the data pointer of
2115 // |buffer| is possibly no longer valid after the above realloc.
2117 stolenData = newData.release();
2121 RemoveCellMemory(buffer, buffer->associatedBytes(),
2122 MemoryUse::ArrayBufferContents);
2124 // Overwrite the old data pointer *without* releasing the contents
2125 // being stolen.
2126 buffer->setDataPointer(BufferContents::createNoData());
2128 // Detach |buffer| now that doing so won't free |stolenData|.
2129 ArrayBufferObject::detach(cx, buffer);
2130 return stolenData;
2133 case INLINE_DATA:
2134 case NO_DATA:
2135 case USER_OWNED:
2136 case MAPPED:
2137 case EXTERNAL: {
2138 // We can't use these data types directly. Make a copy to return.
2139 ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer);
2140 if (!copiedData) {
2141 return nullptr;
2144 // Detach |buffer|. This immediately releases the currently owned
2145 // contents, freeing or unmapping data in the MAPPED and EXTERNAL cases.
2146 ArrayBufferObject::detach(cx, buffer);
2147 return copiedData.release();
2150 case WASM:
2151 MOZ_ASSERT_UNREACHABLE(
2152 "wasm buffers aren't stealable except by a "
2153 "memory.grow operation that shouldn't call this "
2154 "function");
2155 return nullptr;
2158 MOZ_ASSERT_UNREACHABLE("garbage kind computed");
2159 return nullptr;
2162 /* static */ ArrayBufferObject::BufferContents
2163 ArrayBufferObject::extractStructuredCloneContents(
2164 JSContext* cx, Handle<ArrayBufferObject*> buffer) {
2165 if (buffer->isLengthPinned()) {
2166 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2167 JSMSG_ARRAYBUFFER_LENGTH_PINNED);
2168 return BufferContents::createFailed();
2171 CheckStealPreconditions(buffer, cx);
2173 BufferContents contents = buffer->contents();
2175 switch (contents.kind()) {
2176 case INLINE_DATA:
2177 case NO_DATA:
2178 case USER_OWNED: {
2179 ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer);
2180 if (!copiedData) {
2181 return BufferContents::createFailed();
2184 ArrayBufferObject::detach(cx, buffer);
2185 return BufferContents::createMallocedArrayBufferContentsArena(
2186 copiedData.release());
2189 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
2190 case MALLOCED_UNKNOWN_ARENA:
2191 case MAPPED: {
2192 MOZ_ASSERT(contents);
2194 RemoveCellMemory(buffer, buffer->associatedBytes(),
2195 MemoryUse::ArrayBufferContents);
2197 // Overwrite the old data pointer *without* releasing old data.
2198 buffer->setDataPointer(BufferContents::createNoData());
2200 // Detach |buffer| now that doing so won't release |oldContents|.
2201 ArrayBufferObject::detach(cx, buffer);
2202 return contents;
2205 case WASM:
2206 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2207 JSMSG_WASM_NO_TRANSFER);
2208 return BufferContents::createFailed();
2210 case EXTERNAL:
2211 MOZ_ASSERT_UNREACHABLE(
2212 "external ArrayBuffer shouldn't have passed the "
2213 "structured-clone preflighting");
2214 break;
2217 MOZ_ASSERT_UNREACHABLE("garbage kind computed");
2218 return BufferContents::createFailed();
2221 /* static */
2222 bool ArrayBufferObject::ensureNonInline(JSContext* cx,
2223 Handle<ArrayBufferObject*> buffer) {
2224 if (buffer->isDetached() || buffer->isPreparedForAsmJS()) {
2225 return true;
2228 if (buffer->isLengthPinned()) {
2229 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2230 JSMSG_ARRAYBUFFER_LENGTH_PINNED);
2231 return false;
2234 BufferContents inlineContents = buffer->contents();
2235 if (inlineContents.kind() != INLINE_DATA) {
2236 return true;
2239 size_t nbytes = buffer->byteLength();
2240 ArrayBufferContents copy = NewCopiedBufferContents(cx, buffer);
2241 if (!copy) {
2242 return false;
2244 BufferContents outOfLineContents =
2245 BufferContents::createMallocedArrayBufferContentsArena(copy.release());
2246 buffer->setDataPointer(outOfLineContents);
2247 AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
2249 if (!buffer->firstView()) {
2250 return true; // No views! Easy!
2253 buffer->firstView()->as<ArrayBufferViewObject>().notifyBufferMoved(
2254 inlineContents.data(), outOfLineContents.data());
2256 auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
2257 if (InnerViewTable::ViewVector* views =
2258 innerViews.maybeViewsUnbarriered(buffer)) {
2259 for (JSObject* view : *views) {
2260 view->as<ArrayBufferViewObject>().notifyBufferMoved(
2261 inlineContents.data(), outOfLineContents.data());
2265 return true;
2268 /* static */
2269 void ArrayBufferObject::addSizeOfExcludingThis(
2270 JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info,
2271 JS::RuntimeSizes* runtimeSizes) {
2272 auto& buffer = obj->as<ArrayBufferObject>();
2273 switch (buffer.bufferKind()) {
2274 case INLINE_DATA:
2275 // Inline data's size should be reported by this object's size-class
2276 // reporting.
2277 break;
2278 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
2279 case MALLOCED_UNKNOWN_ARENA:
2280 if (buffer.isPreparedForAsmJS()) {
2281 info->objectsMallocHeapElementsAsmJS +=
2282 mallocSizeOf(buffer.dataPointer());
2283 } else {
2284 info->objectsMallocHeapElementsNormal +=
2285 mallocSizeOf(buffer.dataPointer());
2287 break;
2288 case NO_DATA:
2289 // No data is no memory.
2290 MOZ_ASSERT(buffer.dataPointer() == nullptr);
2291 break;
2292 case USER_OWNED:
2293 // User-owned data should be accounted for by the user.
2294 break;
2295 case EXTERNAL:
2296 // External data will be accounted for by the owner of the buffer,
2297 // not this view.
2298 break;
2299 case MAPPED:
2300 info->objectsNonHeapElementsNormal += buffer.byteLength();
2301 break;
2302 case WASM:
2303 if (!buffer.isDetached()) {
2304 info->objectsNonHeapElementsWasm += buffer.byteLength();
2305 if (runtimeSizes) {
2306 MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength());
2307 runtimeSizes->wasmGuardPages +=
2308 buffer.wasmMappedSize() - buffer.byteLength();
2311 break;
2315 /* static */
2316 void ArrayBufferObject::finalize(JS::GCContext* gcx, JSObject* obj) {
2317 obj->as<ArrayBufferObject>().releaseData(gcx);
2320 /* static */
2321 void ArrayBufferObject::copyData(ArrayBufferObject* toBuffer, size_t toIndex,
2322 ArrayBufferObject* fromBuffer,
2323 size_t fromIndex, size_t count) {
2324 MOZ_ASSERT(!toBuffer->isDetached());
2325 MOZ_ASSERT(toBuffer->byteLength() >= count);
2326 MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
2327 MOZ_ASSERT(!fromBuffer->isDetached());
2328 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
2329 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
2331 memcpy(toBuffer->dataPointer() + toIndex,
2332 fromBuffer->dataPointer() + fromIndex, count);
2335 template <class ArrayBufferType>
2336 /* static */
2337 size_t ArrayBufferObject::objectMoved(JSObject* obj, JSObject* old) {
2338 auto& dst = obj->as<ArrayBufferType>();
2339 const auto& src = old->as<ArrayBufferType>();
2341 MOZ_ASSERT(
2342 !obj->runtimeFromMainThread()->gc.nursery().isInside(src.dataPointer()));
2344 // Fix up possible inline data pointer.
2345 if (src.hasInlineData()) {
2346 dst.setFixedSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
2349 return 0;
2352 JSObject* ArrayBufferObject::firstView() {
2353 return getFixedSlot(FIRST_VIEW_SLOT).isObject()
2354 ? &getFixedSlot(FIRST_VIEW_SLOT).toObject()
2355 : nullptr;
2358 void ArrayBufferObject::setFirstView(ArrayBufferViewObject* view) {
2359 setFixedSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
2362 bool ArrayBufferObject::addView(JSContext* cx, ArrayBufferViewObject* view) {
2363 if (!firstView()) {
2364 setFirstView(view);
2365 return true;
2368 return ObjectRealm::get(this).innerViews.get().addView(cx, this, view);
2372 * InnerViewTable
2375 inline bool InnerViewTable::Views::empty() { return views.empty(); }
2377 inline bool InnerViewTable::Views::hasNurseryViews() {
2378 return firstNurseryView < views.length();
2381 bool InnerViewTable::Views::addView(ArrayBufferViewObject* view) {
2382 // Add the view to the list, ensuring that all nursery views are at end.
2384 if (!views.append(view)) {
2385 return false;
2388 if (!gc::IsInsideNursery(view)) {
2389 // Move tenured views before |firstNurseryView|.
2390 if (firstNurseryView != views.length() - 1) {
2391 std::swap(views[firstNurseryView], views.back());
2393 firstNurseryView++;
2396 check();
2398 return true;
2401 bool InnerViewTable::Views::sweepAfterMinorGC(JSTracer* trc) {
2402 return traceWeak(trc, firstNurseryView);
2405 bool InnerViewTable::Views::traceWeak(JSTracer* trc, size_t startIndex) {
2406 // Use |trc| to trace the view vector from |startIndex| to the end, removing
2407 // dead views and updating |firstNurseryView|.
2409 size_t index = startIndex;
2410 bool sawNurseryView = false;
2411 views.mutableEraseIf(
2412 [&](auto& view) {
2413 if (!JS::GCPolicy<ViewVector::ElementType>::traceWeak(trc, &view)) {
2414 return true;
2417 if (!sawNurseryView && gc::IsInsideNursery(view)) {
2418 sawNurseryView = true;
2419 firstNurseryView = index;
2422 index++;
2423 return false;
2425 startIndex);
2427 if (!sawNurseryView) {
2428 firstNurseryView = views.length();
2431 check();
2433 return !views.empty();
2436 inline void InnerViewTable::Views::check() {
2437 #ifdef DEBUG
2438 MOZ_ASSERT(firstNurseryView <= views.length());
2439 if (views.length() < 100) {
2440 for (size_t i = 0; i < views.length(); i++) {
2441 MOZ_ASSERT(gc::IsInsideNursery(views[i]) == (i >= firstNurseryView));
2444 #endif
2447 bool InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer,
2448 ArrayBufferViewObject* view) {
2449 // ArrayBufferObject entries are only added when there are multiple views.
2450 MOZ_ASSERT(buffer->firstView());
2451 MOZ_ASSERT(!gc::IsInsideNursery(buffer));
2453 // Ensure the buffer is present in the map, getting the list of views.
2454 auto ptr = map.lookupForAdd(buffer);
2455 if (!ptr && !map.add(ptr, buffer, Views(cx->zone()))) {
2456 ReportOutOfMemory(cx);
2457 return false;
2459 Views& views = ptr->value();
2461 bool isNurseryView = gc::IsInsideNursery(view);
2462 bool hadNurseryViews = views.hasNurseryViews();
2463 if (!views.addView(view)) {
2464 ReportOutOfMemory(cx);
2465 return false;
2468 // If we added the first nursery view, add the buffer to the list of buffers
2469 // which have nursery views.
2470 if (isNurseryView && !hadNurseryViews && nurseryKeysValid) {
2471 #ifdef DEBUG
2472 if (nurseryKeys.length() < 100) {
2473 for (auto* key : nurseryKeys) {
2474 MOZ_ASSERT(key != buffer);
2477 #endif
2478 if (!nurseryKeys.append(buffer)) {
2479 nurseryKeysValid = false;
2483 return true;
2486 InnerViewTable::ViewVector* InnerViewTable::maybeViewsUnbarriered(
2487 ArrayBufferObject* buffer) {
2488 auto ptr = map.lookup(buffer);
2489 if (ptr) {
2490 return &ptr->value().views;
2492 return nullptr;
2495 void InnerViewTable::removeViews(ArrayBufferObject* buffer) {
2496 auto ptr = map.lookup(buffer);
2497 MOZ_ASSERT(ptr);
2499 map.remove(ptr);
2502 bool InnerViewTable::traceWeak(JSTracer* trc) { return map.traceWeak(trc); }
2504 void InnerViewTable::sweepAfterMinorGC(JSTracer* trc) {
2505 MOZ_ASSERT(needsSweepAfterMinorGC());
2507 if (nurseryKeysValid) {
2508 for (size_t i = 0; i < nurseryKeys.length(); i++) {
2509 ArrayBufferObject* buffer = nurseryKeys[i];
2510 MOZ_ASSERT(!gc::IsInsideNursery(buffer));
2511 auto ptr = map.lookup(buffer);
2512 if (ptr && !ptr->value().sweepAfterMinorGC(trc)) {
2513 map.remove(ptr);
2516 } else {
2517 for (ArrayBufferViewMap::Enum e(map); !e.empty(); e.popFront()) {
2518 MOZ_ASSERT(!gc::IsInsideNursery(e.front().key()));
2519 if (!e.front().value().sweepAfterMinorGC(trc)) {
2520 e.removeFront();
2525 nurseryKeys.clear();
2526 nurseryKeysValid = true;
2529 size_t InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
2530 size_t vectorSize = 0;
2531 for (auto r = map.all(); !r.empty(); r.popFront()) {
2532 vectorSize += r.front().value().views.sizeOfExcludingThis(mallocSizeOf);
2535 return vectorSize + map.shallowSizeOfExcludingThis(mallocSizeOf) +
2536 nurseryKeys.sizeOfExcludingThis(mallocSizeOf);
2539 template <>
2540 bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const {
2541 return is<ArrayBufferObject>() || is<SharedArrayBufferObject>();
2544 JS_PUBLIC_API size_t JS::GetArrayBufferByteLength(JSObject* obj) {
2545 ArrayBufferObject* aobj = obj->maybeUnwrapAs<ArrayBufferObject>();
2546 return aobj ? aobj->byteLength() : 0;
2549 JS_PUBLIC_API uint8_t* JS::GetArrayBufferData(JSObject* obj,
2550 bool* isSharedMemory,
2551 const JS::AutoRequireNoGC&) {
2552 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
2553 if (!aobj) {
2554 return nullptr;
2556 *isSharedMemory = false;
2557 return aobj->dataPointer();
2560 static ArrayBufferObject* UnwrapOrReportArrayBuffer(
2561 JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer) {
2562 JSObject* obj = CheckedUnwrapStatic(maybeArrayBuffer);
2563 if (!obj) {
2564 ReportAccessDenied(cx);
2565 return nullptr;
2568 if (!obj->is<ArrayBufferObject>()) {
2569 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2570 JSMSG_ARRAYBUFFER_REQUIRED);
2571 return nullptr;
2574 return &obj->as<ArrayBufferObject>();
2577 JS_PUBLIC_API bool JS::DetachArrayBuffer(JSContext* cx, HandleObject obj) {
2578 AssertHeapIsIdle();
2579 CHECK_THREAD(cx);
2580 cx->check(obj);
2582 Rooted<ArrayBufferObject*> unwrappedBuffer(
2583 cx, UnwrapOrReportArrayBuffer(cx, obj));
2584 if (!unwrappedBuffer) {
2585 return false;
2588 if (unwrappedBuffer->hasDefinedDetachKey()) {
2589 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2590 JSMSG_WASM_NO_TRANSFER);
2591 return false;
2593 if (unwrappedBuffer->isLengthPinned()) {
2594 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2595 JSMSG_ARRAYBUFFER_LENGTH_PINNED);
2596 return false;
2599 AutoRealm ar(cx, unwrappedBuffer);
2600 ArrayBufferObject::detach(cx, unwrappedBuffer);
2601 return true;
2604 JS_PUBLIC_API bool JS::HasDefinedArrayBufferDetachKey(JSContext* cx,
2605 HandleObject obj,
2606 bool* isDefined) {
2607 Rooted<ArrayBufferObject*> unwrappedBuffer(
2608 cx, UnwrapOrReportArrayBuffer(cx, obj));
2609 if (!unwrappedBuffer) {
2610 return false;
2613 *isDefined = unwrappedBuffer->hasDefinedDetachKey();
2614 return true;
2617 JS_PUBLIC_API bool JS::IsDetachedArrayBufferObject(JSObject* obj) {
2618 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
2619 if (!aobj) {
2620 return false;
2623 return aobj->isDetached();
2626 JS_PUBLIC_API JSObject* JS::NewArrayBuffer(JSContext* cx, size_t nbytes) {
2627 AssertHeapIsIdle();
2628 CHECK_THREAD(cx);
2630 return ArrayBufferObject::createZeroed(cx, nbytes);
2633 JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents(
2634 JSContext* cx, size_t nbytes,
2635 mozilla::UniquePtr<void, JS::FreePolicy> contents) {
2636 auto* result = NewArrayBufferWithContents(
2637 cx, nbytes, contents.get(),
2638 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
2639 if (result) {
2640 // If and only if an ArrayBuffer is successfully created, ownership of
2641 // |contents| is transferred to the new ArrayBuffer.
2642 (void)contents.release();
2644 return result;
2647 JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents(
2648 JSContext* cx, size_t nbytes, void* data, NewArrayBufferOutOfMemory) {
2649 AssertHeapIsIdle();
2650 CHECK_THREAD(cx);
2651 MOZ_ASSERT_IF(!data, nbytes == 0);
2653 if (!data) {
2654 // Don't pass nulled contents to |createForContents|.
2655 return ArrayBufferObject::createZeroed(cx, 0);
2658 using BufferContents = ArrayBufferObject::BufferContents;
2660 BufferContents contents = BufferContents::createMallocedUnknownArena(data);
2661 return ArrayBufferObject::createForContents(cx, nbytes, contents);
2664 JS_PUBLIC_API JSObject* JS::CopyArrayBuffer(JSContext* cx,
2665 Handle<JSObject*> arrayBuffer) {
2666 AssertHeapIsIdle();
2667 CHECK_THREAD(cx);
2669 MOZ_ASSERT(arrayBuffer != nullptr);
2671 Rooted<ArrayBufferObject*> unwrappedSource(
2672 cx, UnwrapOrReportArrayBuffer(cx, arrayBuffer));
2673 if (!unwrappedSource) {
2674 return nullptr;
2677 if (unwrappedSource->isDetached()) {
2678 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2679 JSMSG_TYPED_ARRAY_DETACHED);
2680 return nullptr;
2683 return ArrayBufferObject::copy(cx, unwrappedSource->byteLength(),
2684 unwrappedSource);
2687 JS_PUBLIC_API JSObject* JS::NewExternalArrayBuffer(
2688 JSContext* cx, size_t nbytes,
2689 mozilla::UniquePtr<void, JS::BufferContentsDeleter> contents) {
2690 AssertHeapIsIdle();
2691 CHECK_THREAD(cx);
2693 MOZ_ASSERT(contents);
2695 using BufferContents = ArrayBufferObject::BufferContents;
2697 BufferContents bufferContents = BufferContents::createExternal(
2698 contents.get(), contents.get_deleter().freeFunc(),
2699 contents.get_deleter().userData());
2700 auto* result =
2701 ArrayBufferObject::createForContents(cx, nbytes, bufferContents);
2702 if (result) {
2703 // If and only if an ArrayBuffer is successfully created, ownership of
2704 // |contents| is transferred to the new ArrayBuffer.
2705 (void)contents.release();
2707 return result;
2710 JS_PUBLIC_API JSObject* JS::NewArrayBufferWithUserOwnedContents(JSContext* cx,
2711 size_t nbytes,
2712 void* data) {
2713 AssertHeapIsIdle();
2714 CHECK_THREAD(cx);
2716 MOZ_ASSERT(data);
2718 using BufferContents = ArrayBufferObject::BufferContents;
2720 BufferContents contents = BufferContents::createUserOwned(data);
2721 return ArrayBufferObject::createForContents(cx, nbytes, contents);
2724 JS_PUBLIC_API bool JS::IsArrayBufferObject(JSObject* obj) {
2725 return obj->canUnwrapAs<ArrayBufferObject>();
2728 JS_PUBLIC_API bool JS::ArrayBufferHasData(JSObject* obj) {
2729 return !obj->unwrapAs<ArrayBufferObject>().isDetached();
2732 JS_PUBLIC_API JSObject* JS::UnwrapArrayBuffer(JSObject* obj) {
2733 return obj->maybeUnwrapIf<ArrayBufferObject>();
2736 JS_PUBLIC_API JSObject* JS::UnwrapSharedArrayBuffer(JSObject* obj) {
2737 return obj->maybeUnwrapIf<SharedArrayBufferObject>();
2740 JS_PUBLIC_API void* JS::StealArrayBufferContents(JSContext* cx,
2741 HandleObject obj) {
2742 AssertHeapIsIdle();
2743 CHECK_THREAD(cx);
2744 cx->check(obj);
2746 Rooted<ArrayBufferObject*> unwrappedBuffer(
2747 cx, UnwrapOrReportArrayBuffer(cx, obj));
2748 if (!unwrappedBuffer) {
2749 return nullptr;
2752 if (unwrappedBuffer->isDetached()) {
2753 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2754 JSMSG_TYPED_ARRAY_DETACHED);
2755 return nullptr;
2758 if (unwrappedBuffer->hasDefinedDetachKey()) {
2759 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2760 JSMSG_WASM_NO_TRANSFER);
2761 return nullptr;
2764 AutoRealm ar(cx, unwrappedBuffer);
2765 return ArrayBufferObject::stealMallocedContents(cx, unwrappedBuffer);
2768 JS_PUBLIC_API JSObject* JS::NewMappedArrayBufferWithContents(JSContext* cx,
2769 size_t nbytes,
2770 void* data) {
2771 AssertHeapIsIdle();
2772 CHECK_THREAD(cx);
2774 MOZ_ASSERT(data);
2776 using BufferContents = ArrayBufferObject::BufferContents;
2778 BufferContents contents = BufferContents::createMapped(data);
2779 return ArrayBufferObject::createForContents(cx, nbytes, contents);
2782 JS_PUBLIC_API void* JS::CreateMappedArrayBufferContents(int fd, size_t offset,
2783 size_t length) {
2784 return ArrayBufferObject::createMappedContents(fd, offset, length).data();
2787 JS_PUBLIC_API void JS::ReleaseMappedArrayBufferContents(void* contents,
2788 size_t length) {
2789 gc::DeallocateMappedContent(contents, length);
2792 JS_PUBLIC_API bool JS::IsMappedArrayBufferObject(JSObject* obj) {
2793 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
2794 if (!aobj) {
2795 return false;
2798 return aobj->isMapped();
2801 JS_PUBLIC_API JSObject* JS::GetObjectAsArrayBuffer(JSObject* obj,
2802 size_t* length,
2803 uint8_t** data) {
2804 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
2805 if (!aobj) {
2806 return nullptr;
2809 *length = aobj->byteLength();
2810 *data = aobj->dataPointer();
2812 return aobj;
2815 JS_PUBLIC_API void JS::GetArrayBufferLengthAndData(JSObject* obj,
2816 size_t* length,
2817 bool* isSharedMemory,
2818 uint8_t** data) {
2819 auto& aobj = obj->as<ArrayBufferObject>();
2820 *length = aobj.byteLength();
2821 *data = aobj.dataPointer();
2822 *isSharedMemory = false;
2825 const JSClass* const JS::ArrayBuffer::FixedLengthUnsharedClass =
2826 &FixedLengthArrayBufferObject::class_;
2827 const JSClass* const JS::ArrayBuffer::ResizableUnsharedClass =
2828 &ResizableArrayBufferObject::class_;
2829 const JSClass* const JS::ArrayBuffer::SharedClass =
2830 &SharedArrayBufferObject::class_;
2832 /* static */ JS::ArrayBuffer JS::ArrayBuffer::create(JSContext* cx,
2833 size_t nbytes) {
2834 AssertHeapIsIdle();
2835 CHECK_THREAD(cx);
2836 return JS::ArrayBuffer(ArrayBufferObject::createZeroed(cx, nbytes));
2839 mozilla::Span<uint8_t> JS::ArrayBuffer::getData(
2840 bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) {
2841 auto* buffer = obj->maybeUnwrapAs<ArrayBufferObjectMaybeShared>();
2842 if (!buffer) {
2843 return nullptr;
2845 size_t length = buffer->byteLength();
2846 if (buffer->is<SharedArrayBufferObject>()) {
2847 *isSharedMemory = true;
2848 return {buffer->dataPointerEither().unwrap(), length};
2850 *isSharedMemory = false;
2851 return {buffer->as<ArrayBufferObject>().dataPointer(), length};
2854 JS::ArrayBuffer JS::ArrayBuffer::unwrap(JSObject* maybeWrapped) {
2855 if (!maybeWrapped) {
2856 return JS::ArrayBuffer(nullptr);
2858 auto* ab = maybeWrapped->maybeUnwrapIf<ArrayBufferObjectMaybeShared>();
2859 return fromObject(ab);
2862 bool JS::ArrayBufferCopyData(JSContext* cx, Handle<JSObject*> toBlock,
2863 size_t toIndex, Handle<JSObject*> fromBlock,
2864 size_t fromIndex, size_t count) {
2865 Rooted<ArrayBufferObjectMaybeShared*> unwrappedToBlock(
2866 cx, toBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>());
2867 if (!unwrappedToBlock) {
2868 ReportAccessDenied(cx);
2869 return false;
2872 Rooted<ArrayBufferObjectMaybeShared*> unwrappedFromBlock(
2873 cx, fromBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>());
2874 if (!unwrappedFromBlock) {
2875 ReportAccessDenied(cx);
2876 return false;
2879 // Verify that lengths still make sense and throw otherwise.
2880 if (toIndex + count < toIndex || // size_t overflow
2881 fromIndex + count < fromIndex || // size_t overflow
2882 toIndex + count > unwrappedToBlock->byteLength() ||
2883 fromIndex + count > unwrappedFromBlock->byteLength()) {
2884 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2885 JSMSG_ARRAYBUFFER_COPY_RANGE);
2886 return false;
2889 // If both are array buffers, can use ArrayBufferCopyData
2890 if (unwrappedToBlock->is<ArrayBufferObject>() &&
2891 unwrappedFromBlock->is<ArrayBufferObject>()) {
2892 Rooted<ArrayBufferObject*> toArray(
2893 cx, &unwrappedToBlock->as<ArrayBufferObject>());
2894 Rooted<ArrayBufferObject*> fromArray(
2895 cx, &unwrappedFromBlock->as<ArrayBufferObject>());
2896 ArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex, count);
2897 return true;
2900 Rooted<ArrayBufferObjectMaybeShared*> toArray(
2901 cx, &unwrappedToBlock->as<ArrayBufferObjectMaybeShared>());
2902 Rooted<ArrayBufferObjectMaybeShared*> fromArray(
2903 cx, &unwrappedFromBlock->as<ArrayBufferObjectMaybeShared>());
2904 SharedArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex,
2905 count);
2907 return true;
2910 // https://tc39.es/ecma262/#sec-clonearraybuffer
2911 // We only support the case where cloneConstructor is %ArrayBuffer%. Note,
2912 // this means that cloning a SharedArrayBuffer will produce an ArrayBuffer
2913 JSObject* JS::ArrayBufferClone(JSContext* cx, Handle<JSObject*> srcBuffer,
2914 size_t srcByteOffset, size_t srcLength) {
2915 MOZ_ASSERT(srcBuffer->is<ArrayBufferObjectMaybeShared>());
2917 // 2. (reordered) If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
2918 // exception.
2919 if (IsDetachedArrayBufferObject(srcBuffer)) {
2920 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2921 JSMSG_TYPED_ARRAY_DETACHED);
2922 return nullptr;
2925 // 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
2926 JS::RootedObject targetBuffer(cx, JS::NewArrayBuffer(cx, srcLength));
2927 if (!targetBuffer) {
2928 return nullptr;
2931 // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
2932 // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].
2933 // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset,
2934 // srcLength).
2935 if (!ArrayBufferCopyData(cx, targetBuffer, 0, srcBuffer, srcByteOffset,
2936 srcLength)) {
2937 return nullptr;
2940 // 6. Return targetBuffer.
2941 return targetBuffer;