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/. */
6 
7 #ifndef vm_SharedArrayObject_h
8 #define vm_SharedArrayObject_h
9 
10 #include "mozilla/Atomics.h"
11 
12 #include "jsapi.h"
13 #include "jstypes.h"
14 
15 #include "gc/Barrier.h"
16 #include "gc/Memory.h"
17 #include "vm/ArrayBufferObject.h"
18 #include "vm/JSObject.h"
19 #include "wasm/WasmPages.h"
20 
21 namespace js {
22 
23 class FutexWaiter;
24 
25 /*
26  * SharedArrayRawBuffer
27  *
28  * A bookkeeping object always stored immediately before the raw buffer.
29  * The buffer itself is mmap()'d and refcounted.
30  * SharedArrayBufferObjects and structured clone objects may hold references.
31  *
32  *           |<------ sizeof ------>|<- length ->|
33  *
34  *   | waste | SharedArrayRawBuffer | data array | waste |
35  *
36  * Observe that if we want to map the data array on a specific address, such
37  * as absolute zero (bug 1056027), then the SharedArrayRawBuffer cannot be
38  * prefixed to the data array, it has to be a separate object, also in
39  * shared memory.  (That would get rid of ~4KB of waste, as well.)  Very little
40  * else would have to change throughout the engine, the SARB would point to
41  * the data array using a constant pointer, instead of computing its
42  * address.
43  *
44  * If preparedForWasm_ is true then length_ can change following initialization;
45  * it may grow toward maxSize_.  See extensive comments above WasmArrayRawBuffer
46  * in ArrayBufferObject.cpp.
47  *
48  * length_ only grows when the lock is held.
49  */
50 class SharedArrayRawBuffer {
51  private:
52   mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
53   mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> length_;
54   Mutex growLock_;
55   // The maximum size of this buffer in wasm pages. If this buffer was not
56   // prepared for wasm, then this is zero.
57   wasm::Pages wasmMaxPages_;
58   size_t mappedSize_;  // Does not include the page for the header
59   bool preparedForWasm_;
60 
61   // A list of structures representing tasks waiting on some
62   // location within this buffer.
63   FutexWaiter* waiters_;
64 
basePointer()65   uint8_t* basePointer() {
66     SharedMem<uint8_t*> p = dataPointerShared() - gc::SystemPageSize();
67     MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
68     return p.unwrap(/* we trust you won't abuse it */);
69   }
70 
71  protected:
SharedArrayRawBuffer(uint8_t * buffer,size_t length,wasm::Pages wasmMaxPages,size_t mappedSize,bool preparedForWasm)72   SharedArrayRawBuffer(uint8_t* buffer, size_t length, wasm::Pages wasmMaxPages,
73                        size_t mappedSize, bool preparedForWasm)
74       : refcount_(1),
75         length_(length),
76         growLock_(mutexid::SharedArrayGrow),
77         wasmMaxPages_(wasmMaxPages),
78         mappedSize_(mappedSize),
79         preparedForWasm_(preparedForWasm),
80         waiters_(nullptr) {
81     MOZ_ASSERT(buffer == dataPointerShared());
82   }
83 
84   // Allocate a SharedArrayRawBuffer for either Wasm or other users.
85   // `wasmMaxPages` must always be something for wasm and nothing for other
86   // users.
87   static SharedArrayRawBuffer* AllocateInternal(
88       size_t length, const mozilla::Maybe<wasm::Pages>& wasmMaxPages,
89       const mozilla::Maybe<size_t>& wasmMappedSize);
90 
91  public:
92   class Lock;
93   friend class Lock;
94 
95   class MOZ_STACK_CLASS Lock {
96     SharedArrayRawBuffer* buf;
97 
98    public:
Lock(SharedArrayRawBuffer * buf)99     explicit Lock(SharedArrayRawBuffer* buf) : buf(buf) {
100       buf->growLock_.lock();
101     }
~Lock()102     ~Lock() { buf->growLock_.unlock(); }
103   };
104 
105   static SharedArrayRawBuffer* Allocate(size_t length);
106   static SharedArrayRawBuffer* AllocateWasm(
107       wasm::Pages initialPages, const mozilla::Maybe<wasm::Pages>& maxPages,
108       const mozilla::Maybe<size_t>& mappedSize);
109 
110   // This may be called from multiple threads.  The caller must take
111   // care of mutual exclusion.
waiters()112   FutexWaiter* waiters() const { return waiters_; }
113 
114   // This may be called from multiple threads.  The caller must take
115   // care of mutual exclusion.
setWaiters(FutexWaiter * waiters)116   void setWaiters(FutexWaiter* waiters) { waiters_ = waiters; }
117 
dataPointerShared()118   SharedMem<uint8_t*> dataPointerShared() const {
119     uint8_t* ptr =
120         reinterpret_cast<uint8_t*>(const_cast<SharedArrayRawBuffer*>(this));
121     return SharedMem<uint8_t*>::shared(ptr + sizeof(SharedArrayRawBuffer));
122   }
123 
fromDataPtr(const uint8_t * dataPtr)124   static const SharedArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) {
125     return reinterpret_cast<const SharedArrayRawBuffer*>(
126         dataPtr - sizeof(SharedArrayRawBuffer));
127   }
128 
volatileByteLength()129   size_t volatileByteLength() const { return length_; }
130 
volatileWasmPages()131   wasm::Pages volatileWasmPages() const {
132     return wasm::Pages::fromByteLengthExact(length_);
133   }
134 
wasmMaxPages()135   wasm::Pages wasmMaxPages() const { return wasmMaxPages_; }
136 
mappedSize()137   size_t mappedSize() const { return mappedSize_; }
138 
isWasm()139   bool isWasm() const { return preparedForWasm_; }
140 
141   void tryGrowMaxPagesInPlace(wasm::Pages deltaMaxPages);
142 
143   bool wasmGrowToPagesInPlace(const Lock&, wasm::Pages newPages);
144 
refcount()145   uint32_t refcount() const { return refcount_; }
146 
147   [[nodiscard]] bool addReference();
148   void dropReference();
149 
150   static int32_t liveBuffers();
151 };
152 
153 /*
154  * SharedArrayBufferObject
155  *
156  * When transferred to a WebWorker, the buffer is not detached on the
157  * parent side, and both child and parent reference the same buffer.
158  *
159  * The underlying memory is memory-mapped and reference counted
160  * (across workers and/or processes).  The SharedArrayBuffer object
161  * has a finalizer that decrements the refcount, the last one to leave
162  * (globally) unmaps the memory.  The sender ups the refcount before
163  * transmitting the memory to another worker.
164  *
165  * SharedArrayBufferObject (or really the underlying memory) /is
166  * racy/: more than one worker can access the memory at the same time.
167  *
168  * A TypedArrayObject (a view) references a SharedArrayBuffer
169  * and keeps it alive.  The SharedArrayBuffer does /not/ reference its
170  * views.
171  */
172 class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared {
173   static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
174 
175  public:
176   // RAWBUF_SLOT holds a pointer (as "private" data) to the
177   // SharedArrayRawBuffer object, which is manually managed storage.
178   static const uint8_t RAWBUF_SLOT = 0;
179 
180   // LENGTH_SLOT holds the length of the underlying buffer as it was when this
181   // object was created.  For JS use cases this is the same length as the
182   // buffer, but for Wasm the buffer can grow, and the buffer's length may be
183   // greater than the object's length.
184   static const uint8_t LENGTH_SLOT = 1;
185 
186   static_assert(LENGTH_SLOT == ArrayBufferObject::BYTE_LENGTH_SLOT,
187                 "JIT code assumes the same slot is used for the length");
188 
189   static const uint8_t RESERVED_SLOTS = 2;
190 
191   static const JSClass class_;
192   static const JSClass protoClass_;
193 
194   static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
195 
196   static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
197 
isOriginalByteLengthGetter(Native native)198   static bool isOriginalByteLengthGetter(Native native) {
199     return native == byteLengthGetter;
200   }
201 
202   // Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
203   static SharedArrayBufferObject* New(JSContext* cx, size_t length,
204                                       HandleObject proto = nullptr);
205 
206   // Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer,
207   // recording the given length in the SharedArrayBufferObject.
208   static SharedArrayBufferObject* New(JSContext* cx,
209                                       SharedArrayRawBuffer* buffer,
210                                       size_t length,
211                                       HandleObject proto = nullptr);
212 
213   static void Finalize(JSFreeOp* fop, JSObject* obj);
214 
215   static void addSizeOfExcludingThis(JSObject* obj,
216                                      mozilla::MallocSizeOf mallocSizeOf,
217                                      JS::ClassInfo* info);
218 
219   static void copyData(Handle<SharedArrayBufferObject*> toBuffer,
220                        size_t toIndex,
221                        Handle<SharedArrayBufferObject*> fromBuffer,
222                        size_t fromIndex, size_t count);
223 
224   SharedArrayRawBuffer* rawBufferObject() const;
225 
226   // Invariant: This method does not cause GC and can be called
227   // without anchoring the object it is called on.
globalID()228   uintptr_t globalID() const {
229     // The buffer address is good enough as an ID provided the memory is not
230     // shared between processes or, if it is, it is mapped to the same address
231     // in every process.  (At the moment, shared memory cannot be shared between
232     // processes.)
233     return dataPointerShared().asValue();
234   }
235 
byteLength()236   size_t byteLength() const {
237     return size_t(getFixedSlot(LENGTH_SLOT).toPrivate());
238   }
239 
isWasm()240   bool isWasm() const { return rawBufferObject()->isWasm(); }
dataPointerShared()241   SharedMem<uint8_t*> dataPointerShared() const {
242     return rawBufferObject()->dataPointerShared();
243   }
244 
245   // WebAssembly support:
246 
247   // Create a SharedArrayBufferObject using the provided buffer and size.
248   // Assumes ownership of a reference to |buffer| even in case of failure,
249   // i.e. on failure |buffer->dropReference()| is performed.
250   static SharedArrayBufferObject* createFromNewRawBuffer(
251       JSContext* cx, SharedArrayRawBuffer* buffer, size_t initialSize);
252 
volatileWasmPages()253   wasm::Pages volatileWasmPages() const {
254     return rawBufferObject()->volatileWasmPages();
255   }
wasmMaxPages()256   wasm::Pages wasmMaxPages() const { return rawBufferObject()->wasmMaxPages(); }
257 
wasmMappedSize()258   size_t wasmMappedSize() const { return rawBufferObject()->mappedSize(); }
259 
260  private:
261   [[nodiscard]] bool acceptRawBuffer(SharedArrayRawBuffer* buffer,
262                                      size_t length);
263   void dropRawBuffer();
264 };
265 
266 using RootedSharedArrayBufferObject = Rooted<SharedArrayBufferObject*>;
267 using HandleSharedArrayBufferObject = Handle<SharedArrayBufferObject*>;
268 using MutableHandleSharedArrayBufferObject =
269     MutableHandle<SharedArrayBufferObject*>;
270 
271 }  // namespace js
272 
273 #endif  // vm_SharedArrayObject_h
274