Bug 1842773 - Part 29: Add {FixedLength,Growable}SharedArrayBufferObject classes...
[gecko.git] / js / src / vm / SharedArrayObject.h
blob95c190516b5a0f546ad84846c440e37ded179a84
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 mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
60 mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> length_;
62 // A list of structures representing tasks waiting on some
63 // location within this buffer.
64 FutexWaiter* waiters_ = nullptr;
66 protected:
67 SharedArrayRawBuffer(bool isWasm, uint8_t* buffer, size_t length)
68 : isWasm_(isWasm), refcount_(1), length_(length) {
69 MOZ_ASSERT(buffer == dataPointerShared());
72 public:
73 static SharedArrayRawBuffer* Allocate(size_t length);
75 inline WasmSharedArrayRawBuffer* toWasmBuffer();
77 // This may be called from multiple threads. The caller must take
78 // care of mutual exclusion.
79 FutexWaiter* waiters() const { return waiters_; }
81 // This may be called from multiple threads. The caller must take
82 // care of mutual exclusion.
83 void setWaiters(FutexWaiter* waiters) { waiters_ = waiters; }
85 inline SharedMem<uint8_t*> dataPointerShared() const;
87 size_t volatileByteLength() const { return length_; }
89 bool isWasm() const { return isWasm_; }
91 uint32_t refcount() const { return refcount_; }
93 [[nodiscard]] bool addReference();
94 void dropReference();
96 static int32_t liveBuffers();
99 class WasmSharedArrayRawBuffer : public SharedArrayRawBuffer {
100 private:
101 Mutex growLock_ MOZ_UNANNOTATED;
102 // The index type of this buffer.
103 wasm::IndexType indexType_;
104 // The maximum size of this buffer in wasm pages.
105 wasm::Pages clampedMaxPages_;
106 wasm::Pages sourceMaxPages_;
107 size_t mappedSize_; // Does not include the page for the header.
109 uint8_t* basePointer() {
110 SharedMem<uint8_t*> p = dataPointerShared() - gc::SystemPageSize();
111 MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
112 return p.unwrap(/* we trust you won't abuse it */);
115 protected:
116 WasmSharedArrayRawBuffer(uint8_t* buffer, size_t length,
117 wasm::IndexType indexType,
118 wasm::Pages clampedMaxPages,
119 wasm::Pages sourceMaxPages, size_t mappedSize)
120 : SharedArrayRawBuffer(/* isWasm = */ true, buffer, length),
121 growLock_(mutexid::SharedArrayGrow),
122 indexType_(indexType),
123 clampedMaxPages_(clampedMaxPages),
124 sourceMaxPages_(sourceMaxPages),
125 mappedSize_(mappedSize) {}
127 public:
128 friend class SharedArrayRawBuffer;
130 class Lock;
131 friend class Lock;
133 class MOZ_RAII Lock {
134 WasmSharedArrayRawBuffer* buf;
136 public:
137 explicit Lock(WasmSharedArrayRawBuffer* buf) : buf(buf) {
138 buf->growLock_.lock();
140 ~Lock() { buf->growLock_.unlock(); }
143 static WasmSharedArrayRawBuffer* AllocateWasm(
144 wasm::IndexType indexType, wasm::Pages initialPages,
145 wasm::Pages clampedMaxPages,
146 const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
147 const mozilla::Maybe<size_t>& mappedSize);
149 static const WasmSharedArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) {
150 return reinterpret_cast<const WasmSharedArrayRawBuffer*>(
151 dataPtr - sizeof(WasmSharedArrayRawBuffer));
154 static WasmSharedArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) {
155 return reinterpret_cast<WasmSharedArrayRawBuffer*>(
156 dataPtr - sizeof(WasmSharedArrayRawBuffer));
159 wasm::IndexType wasmIndexType() const { return indexType_; }
161 wasm::Pages volatileWasmPages() const {
162 return wasm::Pages::fromByteLengthExact(length_);
165 wasm::Pages wasmClampedMaxPages() const { return clampedMaxPages_; }
166 wasm::Pages wasmSourceMaxPages() const { return sourceMaxPages_; }
168 size_t mappedSize() const { return mappedSize_; }
170 void tryGrowMaxPagesInPlace(wasm::Pages deltaMaxPages);
172 bool wasmGrowToPagesInPlace(const Lock&, wasm::IndexType t,
173 wasm::Pages newPages);
175 // Discard a region of memory, zeroing the pages and releasing physical memory
176 // back to the operating system. byteOffset and byteLen must be wasm page
177 // aligned and in bounds. A discard of zero bytes will have no effect.
178 void discard(size_t byteOffset, size_t byteLen);
181 inline WasmSharedArrayRawBuffer* SharedArrayRawBuffer::toWasmBuffer() {
182 MOZ_ASSERT(isWasm());
183 return static_cast<WasmSharedArrayRawBuffer*>(this);
186 inline SharedMem<uint8_t*> SharedArrayRawBuffer::dataPointerShared() const {
187 uint8_t* ptr =
188 reinterpret_cast<uint8_t*>(const_cast<SharedArrayRawBuffer*>(this));
189 ptr += isWasm() ? sizeof(WasmSharedArrayRawBuffer)
190 : sizeof(SharedArrayRawBuffer);
191 return SharedMem<uint8_t*>::shared(ptr);
195 * SharedArrayBufferObject
197 * When transferred to a WebWorker, the buffer is not detached on the
198 * parent side, and both child and parent reference the same buffer.
200 * The underlying memory is memory-mapped and reference counted
201 * (across workers and/or processes). The SharedArrayBuffer object
202 * has a finalizer that decrements the refcount, the last one to leave
203 * (globally) unmaps the memory. The sender ups the refcount before
204 * transmitting the memory to another worker.
206 * SharedArrayBufferObject (or really the underlying memory) /is
207 * racy/: more than one worker can access the memory at the same time.
209 * A TypedArrayObject (a view) references a SharedArrayBuffer
210 * and keeps it alive. The SharedArrayBuffer does /not/ reference its
211 * views.
213 * SharedArrayBufferObject is an abstract base class and has exactly two
214 * concrete subclasses, FixedLengthSharedArrayBufferObject and
215 * GrowableSharedArrayBufferObject.
217 class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared {
218 static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
220 public:
221 // RAWBUF_SLOT holds a pointer (as "private" data) to the
222 // SharedArrayRawBuffer object, which is manually managed storage.
223 static const uint8_t RAWBUF_SLOT = 0;
225 // LENGTH_SLOT holds the length of the underlying buffer as it was when this
226 // object was created. For JS use cases this is the same length as the
227 // buffer, but for Wasm the buffer can grow, and the buffer's length may be
228 // greater than the object's length.
229 static const uint8_t LENGTH_SLOT = 1;
231 static_assert(LENGTH_SLOT == ArrayBufferObject::BYTE_LENGTH_SLOT,
232 "JIT code assumes the same slot is used for the length");
234 static const uint8_t RESERVED_SLOTS = 2;
236 static const JSClass protoClass_;
238 static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
240 static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
242 static bool isOriginalByteLengthGetter(Native native) {
243 return native == byteLengthGetter;
246 // Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
247 static SharedArrayBufferObject* New(JSContext* cx, size_t length,
248 HandleObject proto = nullptr);
250 // Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer,
251 // recording the given length in the SharedArrayBufferObject.
252 static SharedArrayBufferObject* New(JSContext* cx,
253 SharedArrayRawBuffer* buffer,
254 size_t length,
255 HandleObject proto = nullptr);
257 static void Finalize(JS::GCContext* gcx, JSObject* obj);
259 static void addSizeOfExcludingThis(JSObject* obj,
260 mozilla::MallocSizeOf mallocSizeOf,
261 JS::ClassInfo* info,
262 JS::RuntimeSizes* runtimeSizes);
264 static void copyData(Handle<ArrayBufferObjectMaybeShared*> toBuffer,
265 size_t toIndex,
266 Handle<ArrayBufferObjectMaybeShared*> fromBuffer,
267 size_t fromIndex, size_t count);
269 SharedArrayRawBuffer* rawBufferObject() const;
271 WasmSharedArrayRawBuffer* rawWasmBufferObject() const {
272 return rawBufferObject()->toWasmBuffer();
275 // Invariant: This method does not cause GC and can be called
276 // without anchoring the object it is called on.
277 uintptr_t globalID() const {
278 // The buffer address is good enough as an ID provided the memory is not
279 // shared between processes or, if it is, it is mapped to the same address
280 // in every process. (At the moment, shared memory cannot be shared between
281 // processes.)
282 return dataPointerShared().asValue();
285 protected:
286 size_t growableByteLength() const {
287 MOZ_ASSERT(isGrowable());
288 return rawBufferObject()->volatileByteLength();
291 public:
292 // Returns either the byte length for fixed-length shared arrays. Or the
293 // maximum byte length for growable shared arrays.
294 size_t byteLengthOrMaxByteLength() const {
295 return size_t(getFixedSlot(LENGTH_SLOT).toPrivate());
298 size_t byteLength() const {
299 if (isGrowable()) {
300 return growableByteLength();
302 return byteLengthOrMaxByteLength();
305 bool isWasm() const { return rawBufferObject()->isWasm(); }
307 bool isGrowable() const {
308 // NOTE: Growable SharedArrayBuffer aren't yet implemented, so this always
309 // returns false;
310 return false;
313 SharedMem<uint8_t*> dataPointerShared() const {
314 return rawBufferObject()->dataPointerShared();
317 // WebAssembly support:
319 // Create a SharedArrayBufferObject using the provided buffer and size.
320 // Assumes ownership of a reference to |buffer| even in case of failure,
321 // i.e. on failure |buffer->dropReference()| is performed.
322 static SharedArrayBufferObject* createFromNewRawBuffer(
323 JSContext* cx, WasmSharedArrayRawBuffer* buffer, size_t initialSize);
325 wasm::Pages volatileWasmPages() const {
326 return rawWasmBufferObject()->volatileWasmPages();
328 wasm::Pages wasmClampedMaxPages() const {
329 return rawWasmBufferObject()->wasmClampedMaxPages();
331 wasm::Pages wasmSourceMaxPages() const {
332 return rawWasmBufferObject()->wasmSourceMaxPages();
335 size_t wasmMappedSize() const { return rawWasmBufferObject()->mappedSize(); }
337 static void wasmDiscard(Handle<SharedArrayBufferObject*> buf,
338 uint64_t byteOffset, uint64_t byteLength);
340 private:
341 [[nodiscard]] bool acceptRawBuffer(SharedArrayRawBuffer* buffer,
342 size_t length);
343 void dropRawBuffer();
347 * FixedLengthSharedArrayBufferObject
349 * SharedArrayBuffer object with a fixed length. The JS exposed length is
350 * unmodifiable, but the underlying memory can still grow for WebAssembly.
352 * Fixed-length SharedArrayBuffers can be used for asm.js and WebAssembly.
354 class FixedLengthSharedArrayBufferObject : public SharedArrayBufferObject {
355 public:
356 static const JSClass class_;
358 size_t byteLength() const { return byteLengthOrMaxByteLength(); }
362 * GrowableSharedArrayBufferObject
364 * SharedArrayBuffer object which can grow in size. The maximum byte length it
365 * can grow to is set when creating the object.
367 * Growable SharedArrayBuffers can neither be used for asm.js nor WebAssembly.
369 class GrowableSharedArrayBufferObject : public SharedArrayBufferObject {
370 public:
371 static const JSClass class_;
373 size_t byteLength() const { return growableByteLength(); }
375 size_t maxByteLength() const { return byteLengthOrMaxByteLength(); }
378 } // namespace js
380 template <>
381 inline bool JSObject::is<js::SharedArrayBufferObject>() const {
382 return is<js::FixedLengthSharedArrayBufferObject>() ||
383 is<js::GrowableSharedArrayBufferObject>();
386 #endif // vm_SharedArrayObject_h