Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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 : #include "vm/SharedArrayObject.h"
8 :
9 : #include "mozilla/Atomics.h"
10 :
11 : #include "jsfriendapi.h"
12 : #include "jsprf.h"
13 :
14 : #ifdef XP_WIN
15 : # include "jswin.h"
16 : #endif
17 : #include "jswrapper.h"
18 : #ifndef XP_WIN
19 : # include <sys/mman.h>
20 : #endif
21 : #ifdef MOZ_VALGRIND
22 : # include <valgrind/memcheck.h>
23 : #endif
24 :
25 : #include "jit/AtomicOperations.h"
26 : #include "vm/SharedMem.h"
27 : #include "wasm/AsmJS.h"
28 : #include "wasm/WasmTypes.h"
29 :
30 : #include "jsobjinlines.h"
31 :
32 : #include "vm/NativeObject-inl.h"
33 :
34 : using namespace js;
35 :
36 : static inline void*
37 0 : MapMemory(size_t length, bool commit)
38 : {
39 : #ifdef XP_WIN
40 : int prot = (commit ? MEM_COMMIT : MEM_RESERVE);
41 : int flags = (commit ? PAGE_READWRITE : PAGE_NOACCESS);
42 : return VirtualAlloc(nullptr, length, prot, flags);
43 : #else
44 0 : int prot = (commit ? (PROT_READ | PROT_WRITE) : PROT_NONE);
45 0 : void* p = mmap(nullptr, length, prot, MAP_PRIVATE | MAP_ANON, -1, 0);
46 0 : if (p == MAP_FAILED)
47 0 : return nullptr;
48 0 : return p;
49 : #endif
50 : }
51 :
52 : static inline void
53 0 : UnmapMemory(void* addr, size_t len)
54 : {
55 : #ifdef XP_WIN
56 : VirtualFree(addr, 0, MEM_RELEASE);
57 : #else
58 0 : munmap(addr, len);
59 : #endif
60 0 : }
61 :
62 : static inline bool
63 0 : MarkValidRegion(void* addr, size_t len)
64 : {
65 : #ifdef XP_WIN
66 : if (!VirtualAlloc(addr, len, MEM_COMMIT, PAGE_READWRITE))
67 : return false;
68 : return true;
69 : #else
70 0 : if (mprotect(addr, len, PROT_READ | PROT_WRITE))
71 0 : return false;
72 0 : return true;
73 : #endif
74 : }
75 :
76 : // Since this SharedArrayBuffer will likely be used for asm.js code, prepare it
77 : // for asm.js by mapping the 4gb protected zone described in WasmTypes.h.
78 : // Since we want to put the SharedArrayBuffer header immediately before the
79 : // heap but keep the heap page-aligned, allocate an extra page before the heap.
80 : static uint64_t
81 0 : SharedArrayMappedSize(uint32_t allocSize)
82 : {
83 0 : MOZ_RELEASE_ASSERT(sizeof(SharedArrayRawBuffer) < gc::SystemPageSize());
84 : #ifdef WASM_HUGE_MEMORY
85 0 : return wasm::HugeMappedSize + gc::SystemPageSize();
86 : #else
87 : return allocSize + wasm::GuardSize;
88 : #endif
89 : }
90 :
91 : // If there are too many 4GB buffers live we run up against system resource
92 : // exhaustion (address space or number of memory map descriptors), see
93 : // bug 1068684, bug 1073934 for details. The limiting case seems to be
94 : // Windows Vista Home 64-bit, where the per-process address space is limited
95 : // to 8TB. Thus we track the number of live objects, and set a limit of
96 : // 1000 live objects per process; we run synchronous GC if necessary; and
97 : // we throw an OOM error if the per-process limit is exceeded.
98 : static mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> numLive;
99 : static const uint32_t maxLive = 1000;
100 :
101 : #ifdef DEBUG
102 : static mozilla::Atomic<int32_t> liveBuffers_;
103 : #endif
104 :
105 : static uint32_t
106 0 : SharedArrayAllocSize(uint32_t length)
107 : {
108 0 : return AlignBytes(length + gc::SystemPageSize(), gc::SystemPageSize());
109 : }
110 :
111 : int32_t
112 0 : SharedArrayRawBuffer::liveBuffers()
113 : {
114 : #ifdef DEBUG
115 0 : return liveBuffers_;
116 : #else
117 : return 0;
118 : #endif
119 : }
120 :
121 : SharedArrayRawBuffer*
122 0 : SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
123 : {
124 : // The value (uint32_t)-1 is used as a signal in various places,
125 : // so guard against it on principle.
126 0 : MOZ_ASSERT(length != (uint32_t)-1);
127 :
128 : // Add a page for the header and round to a page boundary.
129 0 : uint32_t allocSize = SharedArrayAllocSize(length);
130 0 : if (allocSize <= length)
131 0 : return nullptr;
132 :
133 0 : bool preparedForAsmJS = jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
134 :
135 0 : void* p = nullptr;
136 0 : if (preparedForAsmJS) {
137 : // Test >= to guard against the case where multiple extant runtimes
138 : // race to allocate.
139 0 : if (++numLive >= maxLive) {
140 0 : if (OnLargeAllocationFailure)
141 0 : OnLargeAllocationFailure();
142 0 : if (numLive >= maxLive) {
143 0 : numLive--;
144 0 : return nullptr;
145 : }
146 : }
147 :
148 0 : uint32_t mappedSize = SharedArrayMappedSize(allocSize);
149 :
150 : // Get the entire reserved region (with all pages inaccessible)
151 0 : p = MapMemory(mappedSize, false);
152 0 : if (!p) {
153 0 : numLive--;
154 0 : return nullptr;
155 : }
156 :
157 0 : if (!MarkValidRegion(p, allocSize)) {
158 0 : UnmapMemory(p, mappedSize);
159 0 : numLive--;
160 0 : return nullptr;
161 : }
162 :
163 : # if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
164 : // Tell Valgrind/Memcheck to not report accesses in the inaccessible region.
165 : VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)p + allocSize,
166 : mappedSize - allocSize);
167 : # endif
168 : } else {
169 0 : p = MapMemory(allocSize, true);
170 0 : if (!p)
171 0 : return nullptr;
172 : }
173 :
174 0 : uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
175 0 : uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
176 0 : SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(buffer, length, preparedForAsmJS);
177 0 : MOZ_ASSERT(rawbuf->length == length); // Deallocation needs this
178 : #ifdef DEBUG
179 0 : liveBuffers_++;
180 : #endif
181 0 : return rawbuf;
182 : }
183 :
184 : bool
185 0 : SharedArrayRawBuffer::addReference()
186 : {
187 0 : MOZ_RELEASE_ASSERT(this->refcount_ > 0);
188 :
189 : // Be careful never to overflow the refcount field.
190 : for (;;) {
191 0 : uint32_t old_refcount = this->refcount_;
192 0 : uint32_t new_refcount = old_refcount+1;
193 0 : if (new_refcount == 0)
194 0 : return false;
195 0 : if (this->refcount_.compareExchange(old_refcount, new_refcount))
196 0 : return true;
197 0 : }
198 : }
199 :
200 : void
201 0 : SharedArrayRawBuffer::dropReference()
202 : {
203 : // Normally if the refcount is zero then the memory will have been unmapped
204 : // and this test may just crash, but if the memory has been retained for any
205 : // reason we will catch the underflow here.
206 0 : MOZ_RELEASE_ASSERT(this->refcount_ > 0);
207 :
208 : // Drop the reference to the buffer.
209 0 : uint32_t refcount = --this->refcount_; // Atomic.
210 0 : if (refcount)
211 0 : return;
212 :
213 : // If this was the final reference, release the buffer.
214 :
215 : #ifdef DEBUG
216 0 : liveBuffers_--;
217 : #endif
218 :
219 0 : SharedMem<uint8_t*> p = this->dataPointerShared() - gc::SystemPageSize();
220 0 : MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
221 :
222 0 : uint8_t* address = p.unwrap(/*safe - only reference*/);
223 0 : uint32_t allocSize = SharedArrayAllocSize(this->length);
224 :
225 0 : if (this->preparedForAsmJS) {
226 0 : numLive--;
227 :
228 0 : uint32_t mappedSize = SharedArrayMappedSize(allocSize);
229 0 : UnmapMemory(address, mappedSize);
230 :
231 : # if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
232 : // Tell Valgrind/Memcheck to recommence reporting accesses in the
233 : // previously-inaccessible region.
234 : VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(address, mappedSize);
235 : # endif
236 : } else {
237 0 : UnmapMemory(address, allocSize);
238 : }
239 : }
240 :
241 :
242 : MOZ_ALWAYS_INLINE bool
243 0 : SharedArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
244 : {
245 0 : MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
246 0 : args.rval().setInt32(args.thisv().toObject().as<SharedArrayBufferObject>().byteLength());
247 0 : return true;
248 : }
249 :
250 : bool
251 0 : SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
252 : {
253 0 : CallArgs args = CallArgsFromVp(argc, vp);
254 0 : return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, args);
255 : }
256 :
257 : // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
258 : // 24.2.2.1 SharedArrayBuffer( length )
259 : bool
260 0 : SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
261 : {
262 0 : CallArgs args = CallArgsFromVp(argc, vp);
263 :
264 : // Step 1.
265 0 : if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer"))
266 0 : return false;
267 :
268 : // Step 2.
269 : uint64_t byteLength;
270 0 : if (!ToIndex(cx, args.get(0), &byteLength))
271 0 : return false;
272 :
273 : // Step 3 (Inlined 24.2.1.1 AllocateSharedArrayBuffer).
274 : // 24.2.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
275 0 : RootedObject proto(cx);
276 0 : if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
277 0 : return false;
278 :
279 : // 24.2.1.1, step 3 (Inlined 6.2.7.2 CreateSharedByteDataBlock, step 2).
280 : // Refuse to allocate too large buffers, currently limited to ~2 GiB.
281 0 : if (byteLength > INT32_MAX) {
282 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
283 0 : return false;
284 : }
285 :
286 : // 24.2.1.1, steps 1 and 4-6.
287 0 : JSObject* bufobj = New(cx, uint32_t(byteLength), proto);
288 0 : if (!bufobj)
289 0 : return false;
290 0 : args.rval().setObject(*bufobj);
291 0 : return true;
292 : }
293 :
294 : SharedArrayBufferObject*
295 0 : SharedArrayBufferObject::New(JSContext* cx, uint32_t length, HandleObject proto)
296 : {
297 0 : SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length);
298 0 : if (!buffer)
299 0 : return nullptr;
300 :
301 0 : return New(cx, buffer, proto);
302 : }
303 :
304 : SharedArrayBufferObject*
305 0 : SharedArrayBufferObject::New(JSContext* cx, SharedArrayRawBuffer* buffer, HandleObject proto)
306 : {
307 0 : MOZ_ASSERT(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
308 :
309 0 : AutoSetNewObjectMetadata metadata(cx);
310 : Rooted<SharedArrayBufferObject*> obj(cx,
311 0 : NewObjectWithClassProto<SharedArrayBufferObject>(cx, proto));
312 0 : if (!obj)
313 0 : return nullptr;
314 :
315 0 : MOZ_ASSERT(obj->getClass() == &class_);
316 :
317 0 : obj->acceptRawBuffer(buffer);
318 :
319 0 : return obj;
320 : }
321 :
322 : void
323 0 : SharedArrayBufferObject::acceptRawBuffer(SharedArrayRawBuffer* buffer)
324 : {
325 0 : setReservedSlot(RAWBUF_SLOT, PrivateValue(buffer));
326 0 : }
327 :
328 : void
329 0 : SharedArrayBufferObject::dropRawBuffer()
330 : {
331 0 : setReservedSlot(RAWBUF_SLOT, UndefinedValue());
332 0 : }
333 :
334 : SharedArrayRawBuffer*
335 0 : SharedArrayBufferObject::rawBufferObject() const
336 : {
337 0 : Value v = getReservedSlot(RAWBUF_SLOT);
338 0 : MOZ_ASSERT(!v.isUndefined());
339 0 : return reinterpret_cast<SharedArrayRawBuffer*>(v.toPrivate());
340 : }
341 :
342 : void
343 0 : SharedArrayBufferObject::Finalize(FreeOp* fop, JSObject* obj)
344 : {
345 0 : MOZ_ASSERT(fop->maybeOnHelperThread());
346 :
347 0 : SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
348 :
349 : // Detect the case of failure during SharedArrayBufferObject creation,
350 : // which causes a SharedArrayRawBuffer to never be attached.
351 0 : Value v = buf.getReservedSlot(RAWBUF_SLOT);
352 0 : if (!v.isUndefined()) {
353 0 : buf.rawBufferObject()->dropReference();
354 0 : buf.dropRawBuffer();
355 : }
356 0 : }
357 :
358 : /* static */ void
359 0 : SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
360 : JS::ClassInfo* info)
361 : {
362 : // Divide the buffer size by the refcount to get the fraction of the buffer
363 : // owned by this thread. It's conceivable that the refcount might change in
364 : // the middle of memory reporting, in which case the amount reported for
365 : // some threads might be to high (if the refcount goes up) or too low (if
366 : // the refcount goes down). But that's unlikely and hard to avoid, so we
367 : // just live with the risk.
368 0 : const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
369 0 : info->objectsNonHeapElementsShared +=
370 0 : buf.byteLength() / buf.rawBufferObject()->refcount();
371 0 : }
372 :
373 : /* static */ void
374 0 : SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
375 : Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
376 : uint32_t count)
377 : {
378 0 : MOZ_ASSERT(toBuffer->byteLength() >= count);
379 0 : MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
380 0 : MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
381 0 : MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
382 :
383 0 : jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared() + toIndex,
384 0 : fromBuffer->dataPointerShared() + fromIndex,
385 0 : count);
386 0 : }
387 :
388 : static JSObject*
389 6 : CreateSharedArrayBufferPrototype(JSContext* cx, JSProtoKey key)
390 : {
391 6 : return GlobalObject::createBlankPrototype(cx, cx->global(),
392 6 : &SharedArrayBufferObject::protoClass_);
393 : }
394 :
395 : static const ClassOps SharedArrayBufferObjectClassOps = {
396 : nullptr, /* addProperty */
397 : nullptr, /* delProperty */
398 : nullptr, /* getProperty */
399 : nullptr, /* setProperty */
400 : nullptr, /* enumerate */
401 : nullptr, /* newEnumerate */
402 : nullptr, /* resolve */
403 : nullptr, /* mayResolve */
404 : SharedArrayBufferObject::Finalize,
405 : nullptr, /* call */
406 : nullptr, /* hasInstance */
407 : nullptr, /* construct */
408 : nullptr, /* trace */
409 : };
410 :
411 : static const JSFunctionSpec static_functions[] = {
412 : JS_FS_END
413 : };
414 :
415 : static const JSPropertySpec static_properties[] = {
416 : JS_SELF_HOSTED_SYM_GET(species, "SharedArrayBufferSpecies", 0),
417 : JS_PS_END
418 : };
419 :
420 : static const JSFunctionSpec prototype_functions[] = {
421 : JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0),
422 : JS_FS_END
423 : };
424 :
425 : static const JSPropertySpec prototype_properties[] = {
426 : JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
427 : JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
428 : JS_PS_END
429 : };
430 :
431 : static const ClassSpec SharedArrayBufferObjectClassSpec = {
432 : GenericCreateConstructor<SharedArrayBufferObject::class_constructor, 1, gc::AllocKind::FUNCTION>,
433 : CreateSharedArrayBufferPrototype,
434 : static_functions,
435 : static_properties,
436 : prototype_functions,
437 : prototype_properties
438 : };
439 :
440 : const Class SharedArrayBufferObject::class_ = {
441 : "SharedArrayBuffer",
442 : JSCLASS_DELAY_METADATA_BUILDER |
443 : JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) |
444 : JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer) |
445 : JSCLASS_BACKGROUND_FINALIZE,
446 : &SharedArrayBufferObjectClassOps,
447 : &SharedArrayBufferObjectClassSpec,
448 : JS_NULL_CLASS_EXT
449 : };
450 :
451 : const Class SharedArrayBufferObject::protoClass_ = {
452 : "SharedArrayBufferPrototype",
453 : JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer),
454 : JS_NULL_CLASS_OPS,
455 : &SharedArrayBufferObjectClassSpec
456 : };
457 :
458 : bool
459 0 : js::IsSharedArrayBuffer(HandleValue v)
460 : {
461 0 : return v.isObject() && v.toObject().is<SharedArrayBufferObject>();
462 : }
463 :
464 : bool
465 0 : js::IsSharedArrayBuffer(HandleObject o)
466 : {
467 0 : return o->is<SharedArrayBufferObject>();
468 : }
469 :
470 : bool
471 0 : js::IsSharedArrayBuffer(JSObject* o)
472 : {
473 0 : return o->is<SharedArrayBufferObject>();
474 : }
475 :
476 : SharedArrayBufferObject&
477 0 : js::AsSharedArrayBuffer(HandleObject obj)
478 : {
479 0 : MOZ_ASSERT(IsSharedArrayBuffer(obj));
480 0 : return obj->as<SharedArrayBufferObject>();
481 : }
482 :
483 : JS_FRIEND_API(uint32_t)
484 0 : JS_GetSharedArrayBufferByteLength(JSObject* obj)
485 : {
486 0 : obj = CheckedUnwrap(obj);
487 0 : return obj ? obj->as<SharedArrayBufferObject>().byteLength() : 0;
488 : }
489 :
490 : JS_FRIEND_API(void)
491 0 : js::GetSharedArrayBufferLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
492 : {
493 0 : MOZ_ASSERT(obj->is<SharedArrayBufferObject>());
494 0 : *length = obj->as<SharedArrayBufferObject>().byteLength();
495 0 : *data = obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(/*safe - caller knows*/);
496 0 : *isSharedMemory = true;
497 0 : }
498 :
499 : JS_FRIEND_API(JSObject*)
500 0 : JS_NewSharedArrayBuffer(JSContext* cx, uint32_t nbytes)
501 : {
502 0 : MOZ_ASSERT(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
503 :
504 0 : MOZ_ASSERT(nbytes <= INT32_MAX);
505 0 : return SharedArrayBufferObject::New(cx, nbytes, /* proto = */ nullptr);
506 : }
507 :
508 : JS_FRIEND_API(bool)
509 138 : JS_IsSharedArrayBufferObject(JSObject* obj)
510 : {
511 138 : obj = CheckedUnwrap(obj);
512 138 : return obj ? obj->is<SharedArrayBufferObject>() : false;
513 : }
514 :
515 : JS_FRIEND_API(uint8_t*)
516 0 : JS_GetSharedArrayBufferData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&)
517 : {
518 0 : obj = CheckedUnwrap(obj);
519 0 : if (!obj)
520 0 : return nullptr;
521 0 : *isSharedMemory = true;
522 0 : return obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(/*safe - caller knows*/);
523 : }
|