Bug 1842773 - Part 30: Add support to create growable ArrayBuffers. r=sfink
[gecko.git] / js / src / vm / SharedArrayObject.h
blob9dd2d005a9bd1b75423419a845493ea973442755
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 #ifndef vm_SharedArrayObject_h
8 #define vm_SharedArrayObject_h
10 #include "mozilla/Atomics.h"
12 #include "jstypes.h"
14 #include "gc/Memory.h"
15 #include "vm/ArrayBufferObject.h"
16 #include "wasm/WasmMemory.h"
18 namespace js {
20 class FutexWaiter;
21 class WasmSharedArrayRawBuffer;
24 * SharedArrayRawBuffer
26 * A bookkeeping object always stored before the raw buffer. The buffer itself
27 * is refcounted. SharedArrayBufferObjects and structured clone objects may hold
28 * references.
30 * WasmSharedArrayRawBuffer is a derived class that's used for Wasm buffers.
32 * - Non-Wasm buffers are allocated with a single calloc allocation, like this:
34 * |<------ sizeof ------>|<- length ->|
35 * | SharedArrayRawBuffer | data array |
37 * - Wasm buffers are allocated with MapBufferMemory (mmap), like this:
39 * |<-------- sizeof -------->|<- length ->|
40 * | waste | WasmSharedArrayRawBuffer | data array | waste |
42 * Observe that if we want to map the data array on a specific address, such
43 * as absolute zero (bug 1056027), then the {Wasm}SharedArrayRawBuffer cannot be
44 * prefixed to the data array, it has to be a separate object, also in
45 * shared memory. (That would get rid of ~4KB of waste, as well.) Very little
46 * else would have to change throughout the engine, the SARB would point to
47 * the data array using a constant pointer, instead of computing its
48 * address.
50 * For Wasm buffers, length_ can change following initialization; it may grow
51 * toward sourceMaxPages_. See extensive comments above WasmArrayRawBuffer in
52 * ArrayBufferObject.cpp. length_ only grows when the lock is held.
54 class SharedArrayRawBuffer {
55 protected:
56 // Whether this is a WasmSharedArrayRawBuffer.
57 bool isWasm_;
59 // Whether this is a growable non-Wasm buffer.
60 bool isGrowable_;
62 mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
63 mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> length_;
65 // A list of structures representing tasks waiting on some
66 // location within this buffer.
67 FutexWaiter* waiters_ = nullptr;
69 protected:
70 SharedArrayRawBuffer(bool isGrowable, uint8_t* buffer, size_t length)
71 : isWasm_(false), isGrowable_(isGrowable), refcount_(1), length_(length) {
72 MOZ_ASSERT(buffer == dataPointerShared());
75 enum class WasmBuffer {};
77 SharedArrayRawBuffer(WasmBuffer, uint8_t* buffer, size_t length)
78 : isWasm_(true), isGrowable_(false), refcount_(1), length_(length) {
79 MOZ_ASSERT(buffer == dataPointerShared());
82 public:
83 static SharedArrayRawBuffer* Allocate(bool isGrowable, size_t length,
84 size_t maxLength);
86 inline WasmSharedArrayRawBuffer* toWasmBuffer();
88 // This may be called from multiple threads. The caller must take
89 // care of mutual exclusion.
90 FutexWaiter* waiters() const { return waiters_; }
92 // This may be called from multiple threads. The caller must take
93 // care of mutual exclusion.
94 void setWaiters(FutexWaiter* waiters) { waiters_ = waiters; }
96 inline SharedMem<uint8_t*> dataPointerShared() const;
98 size_t volatileByteLength() const { return length_; }
100 bool isWasm() const { return isWasm_; }
102 bool isGrowable() const { return isGrowable_; }
104 uint32_t refcount() const { return refcount_; }
106 [[nodiscard]] bool addReference();
107 void dropReference();
109 static int32_t liveBuffers();
112 class WasmSharedArrayRawBuffer : public SharedArrayRawBuffer {
113 private:
114 Mutex growLock_ MOZ_UNANNOTATED;
115 // The index type of this buffer.
116 wasm::IndexType indexType_;
117 // The maximum size of this buffer in wasm pages.
118 wasm::Pages clampedMaxPages_;
119 wasm::Pages sourceMaxPages_;
120 size_t mappedSize_; // Does not include the page for the header.
122 uint8_t* basePointer() {
123 SharedMem<uint8_t*> p = dataPointerShared() - gc::SystemPageSize();
124 MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
125 return p.unwrap(/* we trust you won't abuse it */);
128 protected:
129 WasmSharedArrayRawBuffer(uint8_t* buffer, size_t length,
130 wasm::IndexType indexType,
131 wasm::Pages clampedMaxPages,
132 wasm::Pages sourceMaxPages, size_t mappedSize)
133 : SharedArrayRawBuffer(WasmBuffer{}, buffer, length),
134 growLock_(mutexid::SharedArrayGrow),
135 indexType_(indexType),
136 clampedMaxPages_(clampedMaxPages),
137 sourceMaxPages_(sourceMaxPages),
138 mappedSize_(mappedSize) {}
140 public:
141 friend class SharedArrayRawBuffer;
143 class Lock;
144 friend class Lock;
146 class MOZ_RAII Lock {
147 WasmSharedArrayRawBuffer* buf;
149 public:
150 explicit Lock(WasmSharedArrayRawBuffer* buf) : buf(buf) {
151 buf->growLock_.lock();
153 ~Lock() { buf->growLock_.unlock(); }
156 static WasmSharedArrayRawBuffer* AllocateWasm(
157 wasm::IndexType indexType, wasm::Pages initialPages,
158 wasm::Pages clampedMaxPages,
159 const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
160 const mozilla::Maybe<size_t>& mappedSize);
162 static const WasmSharedArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) {
163 return reinterpret_cast<const WasmSharedArrayRawBuffer*>(
164 dataPtr - sizeof(WasmSharedArrayRawBuffer));
167 static WasmSharedArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) {
168 return reinterpret_cast<WasmSharedArrayRawBuffer*>(
169 dataPtr - sizeof(WasmSharedArrayRawBuffer));
172 wasm::IndexType wasmIndexType() const { return indexType_; }
174 wasm::Pages volatileWasmPages() const {
175 return wasm::Pages::fromByteLengthExact(length_);
178 wasm::Pages wasmClampedMaxPages() const { return clampedMaxPages_; }
179 wasm::Pages wasmSourceMaxPages() const { return sourceMaxPages_; }
181 size_t mappedSize() const { return mappedSize_; }
183 void tryGrowMaxPagesInPlace(wasm::Pages deltaMaxPages);
185 bool wasmGrowToPagesInPlace(const Lock&, wasm::IndexType t,
186 wasm::Pages newPages);
188 // Discard a region of memory, zeroing the pages and releasing physical memory
189 // back to the operating system. byteOffset and byteLen must be wasm page
190 // aligned and in bounds. A discard of zero bytes will have no effect.
191 void discard(size_t byteOffset, size_t byteLen);
194 inline WasmSharedArrayRawBuffer* SharedArrayRawBuffer::toWasmBuffer() {
195 MOZ_ASSERT(isWasm());
196 return static_cast<WasmSharedArrayRawBuffer*>(this);
199 inline SharedMem<uint8_t*> SharedArrayRawBuffer::dataPointerShared() const {
200 uint8_t* ptr =
201 reinterpret_cast<uint8_t*>(const_cast<SharedArrayRawBuffer*>(this));
202 ptr += isWasm() ? sizeof(WasmSharedArrayRawBuffer)
203 : sizeof(SharedArrayRawBuffer);
204 return SharedMem<uint8_t*>::shared(ptr);
207 class FixedLengthSharedArrayBufferObject;
208 class GrowableSharedArrayBufferObject;
211 * SharedArrayBufferObject
213 * When transferred to a WebWorker, the buffer is not detached on the
214 * parent side, and both child and parent reference the same buffer.
216 * The underlying memory is memory-mapped and reference counted
217 * (across workers and/or processes). The SharedArrayBuffer object
218 * has a finalizer that decrements the refcount, the last one to leave
219 * (globally) unmaps the memory. The sender ups the refcount before
220 * transmitting the memory to another worker.
222 * SharedArrayBufferObject (or really the underlying memory) /is
223 * racy/: more than one worker can access the memory at the same time.
225 * A TypedArrayObject (a view) references a SharedArrayBuffer
226 * and keeps it alive. The SharedArrayBuffer does /not/ reference its
227 * views.
229 * SharedArrayBufferObject is an abstract base class and has exactly two
230 * concrete subclasses, FixedLengthSharedArrayBufferObject and
231 * GrowableSharedArrayBufferObject.
233 class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared {
234 static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
236 public:
237 // RAWBUF_SLOT holds a pointer (as "private" data) to the
238 // SharedArrayRawBuffer object, which is manually managed storage.
239 static const uint8_t RAWBUF_SLOT = 0;
241 // LENGTH_SLOT holds the length of the underlying buffer as it was when this
242 // object was created. For JS use cases this is the same length as the
243 // buffer, but for Wasm the buffer can grow, and the buffer's length may be
244 // greater than the object's length.
245 static const uint8_t LENGTH_SLOT = 1;
247 static_assert(LENGTH_SLOT == ArrayBufferObject::BYTE_LENGTH_SLOT,
248 "JIT code assumes the same slot is used for the length");
250 static const uint8_t RESERVED_SLOTS = 2;
252 static const JSClass protoClass_;
254 static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
256 static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
258 static bool isOriginalByteLengthGetter(Native native) {
259 return native == byteLengthGetter;
262 private:
263 template <class SharedArrayBufferType>
264 static SharedArrayBufferType* NewWith(JSContext* cx,
265 SharedArrayRawBuffer* buffer,
266 size_t length, HandleObject proto);
268 public:
269 // Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
270 static FixedLengthSharedArrayBufferObject* New(JSContext* cx, size_t length,
271 HandleObject proto = nullptr);
273 // Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer,
274 // recording the given length in the SharedArrayBufferObject.
275 static FixedLengthSharedArrayBufferObject* New(JSContext* cx,
276 SharedArrayRawBuffer* buffer,
277 size_t length,
278 HandleObject proto = nullptr);
280 // Create a growable SharedArrayBufferObject with a new SharedArrayRawBuffer.
281 static GrowableSharedArrayBufferObject* NewGrowable(
282 JSContext* cx, size_t length, size_t maxLength,
283 HandleObject proto = nullptr);
285 // Create a growable SharedArrayBufferObject using an existing
286 // SharedArrayRawBuffer, recording the given length in the
287 // SharedArrayBufferObject.
288 static GrowableSharedArrayBufferObject* NewGrowable(
289 JSContext* cx, SharedArrayRawBuffer* buffer, size_t maxLength,
290 HandleObject proto = nullptr);
292 static void Finalize(JS::GCContext* gcx, JSObject* obj);
294 static void addSizeOfExcludingThis(JSObject* obj,
295 mozilla::MallocSizeOf mallocSizeOf,
296 JS::ClassInfo* info,
297 JS::RuntimeSizes* runtimeSizes);
299 static void copyData(Handle<ArrayBufferObjectMaybeShared*> toBuffer,
300 size_t toIndex,
301 Handle<ArrayBufferObjectMaybeShared*> fromBuffer,
302 size_t fromIndex, size_t count);
304 SharedArrayRawBuffer* rawBufferObject() const;
306 WasmSharedArrayRawBuffer* rawWasmBufferObject() const {
307 return rawBufferObject()->toWasmBuffer();
310 // Invariant: This method does not cause GC and can be called
311 // without anchoring the object it is called on.
312 uintptr_t globalID() const {
313 // The buffer address is good enough as an ID provided the memory is not
314 // shared between processes or, if it is, it is mapped to the same address
315 // in every process. (At the moment, shared memory cannot be shared between
316 // processes.)
317 return dataPointerShared().asValue();
320 protected:
321 size_t growableByteLength() const {
322 MOZ_ASSERT(isGrowable());
323 return rawBufferObject()->volatileByteLength();
326 public:
327 // Returns either the byte length for fixed-length shared arrays. Or the
328 // maximum byte length for growable shared arrays.
329 size_t byteLengthOrMaxByteLength() const {
330 return size_t(getFixedSlot(LENGTH_SLOT).toPrivate());
333 size_t byteLength() const {
334 if (isGrowable()) {
335 return growableByteLength();
337 return byteLengthOrMaxByteLength();
340 bool isWasm() const { return rawBufferObject()->isWasm(); }
342 bool isGrowable() const { return rawBufferObject()->isGrowable(); }
344 SharedMem<uint8_t*> dataPointerShared() const {
345 return rawBufferObject()->dataPointerShared();
348 // WebAssembly support:
350 // Create a SharedArrayBufferObject using the provided buffer and size.
351 // Assumes ownership of a reference to |buffer| even in case of failure,
352 // i.e. on failure |buffer->dropReference()| is performed.
353 static SharedArrayBufferObject* createFromNewRawBuffer(
354 JSContext* cx, WasmSharedArrayRawBuffer* buffer, size_t initialSize);
356 wasm::Pages volatileWasmPages() const {
357 return rawWasmBufferObject()->volatileWasmPages();
359 wasm::Pages wasmClampedMaxPages() const {
360 return rawWasmBufferObject()->wasmClampedMaxPages();
362 wasm::Pages wasmSourceMaxPages() const {
363 return rawWasmBufferObject()->wasmSourceMaxPages();
366 size_t wasmMappedSize() const { return rawWasmBufferObject()->mappedSize(); }
368 static void wasmDiscard(Handle<SharedArrayBufferObject*> buf,
369 uint64_t byteOffset, uint64_t byteLength);
371 private:
372 [[nodiscard]] bool acceptRawBuffer(SharedArrayRawBuffer* buffer,
373 size_t length);
374 void dropRawBuffer();
378 * FixedLengthSharedArrayBufferObject
380 * SharedArrayBuffer object with a fixed length. The JS exposed length is
381 * unmodifiable, but the underlying memory can still grow for WebAssembly.
383 * Fixed-length SharedArrayBuffers can be used for asm.js and WebAssembly.
385 class FixedLengthSharedArrayBufferObject : public SharedArrayBufferObject {
386 public:
387 static const JSClass class_;
389 size_t byteLength() const { return byteLengthOrMaxByteLength(); }
393 * GrowableSharedArrayBufferObject
395 * SharedArrayBuffer object which can grow in size. The maximum byte length it
396 * can grow to is set when creating the object.
398 * Growable SharedArrayBuffers can neither be used for asm.js nor WebAssembly.
400 class GrowableSharedArrayBufferObject : public SharedArrayBufferObject {
401 public:
402 static const JSClass class_;
404 size_t byteLength() const { return growableByteLength(); }
406 size_t maxByteLength() const { return byteLengthOrMaxByteLength(); }
409 } // namespace js
411 template <>
412 inline bool JSObject::is<js::SharedArrayBufferObject>() const {
413 return is<js::FixedLengthSharedArrayBufferObject>() ||
414 is<js::GrowableSharedArrayBufferObject>();
417 #endif // vm_SharedArrayObject_h