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"
14 #include "gc/Memory.h"
15 #include "vm/ArrayBufferObject.h"
16 #include "wasm/WasmMemory.h"
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
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
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
{
56 // Whether this is a WasmSharedArrayRawBuffer.
59 // Whether this is a growable non-Wasm buffer.
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;
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());
83 static SharedArrayRawBuffer
* Allocate(bool isGrowable
, size_t length
,
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
{
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 */);
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
) {}
141 friend class SharedArrayRawBuffer
;
146 class MOZ_RAII Lock
{
147 WasmSharedArrayRawBuffer
* buf
;
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 {
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
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
);
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
;
263 template <class SharedArrayBufferType
>
264 static SharedArrayBufferType
* NewWith(JSContext
* cx
,
265 SharedArrayRawBuffer
* buffer
,
266 size_t length
, HandleObject proto
);
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
,
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
,
297 JS::RuntimeSizes
* runtimeSizes
);
299 static void copyData(Handle
<ArrayBufferObjectMaybeShared
*> toBuffer
,
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
317 return dataPointerShared().asValue();
321 size_t growableByteLength() const {
322 MOZ_ASSERT(isGrowable());
323 return rawBufferObject()->volatileByteLength();
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 {
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
);
372 [[nodiscard
]] bool acceptRawBuffer(SharedArrayRawBuffer
* buffer
,
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
{
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
{
402 static const JSClass class_
;
404 size_t byteLength() const { return growableByteLength(); }
406 size_t maxByteLength() const { return byteLengthOrMaxByteLength(); }
412 inline bool JSObject::is
<js::SharedArrayBufferObject
>() const {
413 return is
<js::FixedLengthSharedArrayBufferObject
>() ||
414 is
<js::GrowableSharedArrayBufferObject
>();
417 #endif // vm_SharedArrayObject_h