|
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 "jsprf.h" |
|
10 #include "jsobjinlines.h" |
|
11 |
|
12 #ifdef XP_WIN |
|
13 # include "jswin.h" |
|
14 #else |
|
15 # include <sys/mman.h> |
|
16 #endif |
|
17 |
|
18 #ifdef MOZ_VALGRIND |
|
19 # include <valgrind/memcheck.h> |
|
20 #endif |
|
21 |
|
22 #include "mozilla/Atomics.h" |
|
23 #include "jit/AsmJS.h" |
|
24 |
|
25 using namespace js; |
|
26 |
|
27 using mozilla::IsNaN; |
|
28 using mozilla::PodCopy; |
|
29 |
|
30 /* |
|
31 * SharedArrayRawBuffer |
|
32 */ |
|
33 |
|
34 static inline void * |
|
35 MapMemory(size_t length, bool commit) |
|
36 { |
|
37 #ifdef XP_WIN |
|
38 int prot = (commit ? MEM_COMMIT : MEM_RESERVE); |
|
39 int flags = (commit ? PAGE_READWRITE : PAGE_NOACCESS); |
|
40 return VirtualAlloc(nullptr, length, prot, flags); |
|
41 #else |
|
42 int prot = (commit ? (PROT_READ | PROT_WRITE) : PROT_NONE); |
|
43 void *p = mmap(nullptr, length, prot, MAP_PRIVATE | MAP_ANON, -1, 0); |
|
44 if (p == MAP_FAILED) |
|
45 return nullptr; |
|
46 return p; |
|
47 #endif |
|
48 } |
|
49 |
|
50 static inline void |
|
51 UnmapMemory(void *addr, size_t len) |
|
52 { |
|
53 #ifdef XP_WIN |
|
54 VirtualFree(addr, 0, MEM_RELEASE); |
|
55 #else |
|
56 munmap(addr, len); |
|
57 #endif |
|
58 } |
|
59 |
|
60 static inline bool |
|
61 MarkValidRegion(void *addr, size_t len) |
|
62 { |
|
63 #ifdef XP_WIN |
|
64 if (!VirtualAlloc(addr, len, MEM_COMMIT, PAGE_READWRITE)) |
|
65 return false; |
|
66 return true; |
|
67 #else |
|
68 if (mprotect(addr, len, PROT_READ | PROT_WRITE)) |
|
69 return false; |
|
70 return true; |
|
71 #endif |
|
72 } |
|
73 |
|
74 SharedArrayRawBuffer * |
|
75 SharedArrayRawBuffer::New(uint32_t length) |
|
76 { |
|
77 // Enforced by SharedArrayBufferObject constructor. |
|
78 JS_ASSERT(IsValidAsmJSHeapLength(length)); |
|
79 |
|
80 #ifdef JS_CPU_X64 |
|
81 // Get the entire reserved region (with all pages inaccessible) |
|
82 void *p = MapMemory(AsmJSMappedSize, false); |
|
83 if (!p) |
|
84 return nullptr; |
|
85 |
|
86 size_t validLength = AsmJSPageSize + length; |
|
87 if (!MarkValidRegion(p, validLength)) { |
|
88 UnmapMemory(p, AsmJSMappedSize); |
|
89 return nullptr; |
|
90 } |
|
91 # if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE) |
|
92 // Tell Valgrind/Memcheck to not report accesses in the inaccessible region. |
|
93 VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)p + validLength, |
|
94 AsmJSMappedSize-validLength); |
|
95 # endif |
|
96 #else |
|
97 uint32_t allocSize = length + AsmJSPageSize; |
|
98 if (allocSize <= length) |
|
99 return nullptr; |
|
100 |
|
101 void *p = MapMemory(allocSize, true); |
|
102 if (!p) |
|
103 return nullptr; |
|
104 #endif |
|
105 uint8_t *buffer = reinterpret_cast<uint8_t*>(p) + AsmJSPageSize; |
|
106 uint8_t *base = buffer - sizeof(SharedArrayRawBuffer); |
|
107 return new (base) SharedArrayRawBuffer(buffer, length); |
|
108 } |
|
109 |
|
110 void |
|
111 SharedArrayRawBuffer::addReference() |
|
112 { |
|
113 JS_ASSERT(this->refcount > 0); |
|
114 ++this->refcount; // Atomic. |
|
115 } |
|
116 |
|
117 void |
|
118 SharedArrayRawBuffer::dropReference() |
|
119 { |
|
120 // Drop the reference to the buffer. |
|
121 uint32_t refcount = --this->refcount; // Atomic. |
|
122 |
|
123 // If this was the final reference, release the buffer. |
|
124 if (refcount == 0) { |
|
125 uint8_t *p = this->dataPointer() - AsmJSPageSize; |
|
126 JS_ASSERT(uintptr_t(p) % AsmJSPageSize == 0); |
|
127 #ifdef JS_CPU_X64 |
|
128 UnmapMemory(p, AsmJSMappedSize); |
|
129 # if defined(MOZ_VALGRIND) \ |
|
130 && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE) |
|
131 // Tell Valgrind/Memcheck to recommence reporting accesses in the |
|
132 // previously-inaccessible region. |
|
133 if (AsmJSMappedSize > 0) { |
|
134 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(p, AsmJSMappedSize); |
|
135 } |
|
136 # endif |
|
137 #else |
|
138 UnmapMemory(p, this->length + AsmJSPageSize); |
|
139 #endif |
|
140 } |
|
141 } |
|
142 |
|
143 /* |
|
144 * SharedArrayBufferObject |
|
145 */ |
|
146 bool |
|
147 js::IsSharedArrayBuffer(HandleValue v) |
|
148 { |
|
149 return v.isObject() && v.toObject().is<SharedArrayBufferObject>(); |
|
150 } |
|
151 |
|
152 MOZ_ALWAYS_INLINE bool |
|
153 SharedArrayBufferObject::byteLengthGetterImpl(JSContext *cx, CallArgs args) |
|
154 { |
|
155 JS_ASSERT(IsSharedArrayBuffer(args.thisv())); |
|
156 args.rval().setInt32(args.thisv().toObject().as<SharedArrayBufferObject>().byteLength()); |
|
157 return true; |
|
158 } |
|
159 |
|
160 bool |
|
161 SharedArrayBufferObject::byteLengthGetter(JSContext *cx, unsigned argc, Value *vp) |
|
162 { |
|
163 CallArgs args = CallArgsFromVp(argc, vp); |
|
164 return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, args); |
|
165 } |
|
166 |
|
167 bool |
|
168 SharedArrayBufferObject::class_constructor(JSContext *cx, unsigned argc, Value *vp) |
|
169 { |
|
170 int32_t length = 0; |
|
171 CallArgs args = CallArgsFromVp(argc, vp); |
|
172 if (args.length() > 0 && !ToInt32(cx, args[0], &length)) |
|
173 return false; |
|
174 |
|
175 if (length < 0) { |
|
176 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); |
|
177 return false; |
|
178 } |
|
179 |
|
180 JSObject *bufobj = New(cx, uint32_t(length)); |
|
181 if (!bufobj) |
|
182 return false; |
|
183 args.rval().setObject(*bufobj); |
|
184 return true; |
|
185 } |
|
186 |
|
187 JSObject * |
|
188 SharedArrayBufferObject::New(JSContext *cx, uint32_t length) |
|
189 { |
|
190 if (!IsValidAsmJSHeapLength(length)) { |
|
191 ScopedJSFreePtr<char> msg( |
|
192 JS_smprintf("SharedArrayBuffer byteLength 0x%x is not a valid length. The next valid " |
|
193 "length is 0x%x", length, RoundUpToNextValidAsmJSHeapLength(length))); |
|
194 JS_ReportError(cx, msg.get()); |
|
195 return nullptr; |
|
196 } |
|
197 |
|
198 SharedArrayRawBuffer *buffer = SharedArrayRawBuffer::New(length); |
|
199 if (!buffer) |
|
200 return nullptr; |
|
201 |
|
202 return New(cx, buffer); |
|
203 } |
|
204 |
|
205 JSObject * |
|
206 SharedArrayBufferObject::New(JSContext *cx, SharedArrayRawBuffer *buffer) |
|
207 { |
|
208 Rooted<SharedArrayBufferObject*> obj(cx, NewBuiltinClassInstance<SharedArrayBufferObject>(cx)); |
|
209 if (!obj) |
|
210 return nullptr; |
|
211 |
|
212 JS_ASSERT(obj->getClass() == &class_); |
|
213 |
|
214 obj->initialize(buffer->byteLength(), nullptr, DoesntOwnData); |
|
215 |
|
216 obj->acceptRawBuffer(buffer); |
|
217 obj->setIsSharedArrayBuffer(); |
|
218 |
|
219 return obj; |
|
220 } |
|
221 |
|
222 void |
|
223 SharedArrayBufferObject::acceptRawBuffer(SharedArrayRawBuffer *buffer) |
|
224 { |
|
225 setReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT, PrivateValue(buffer)); |
|
226 } |
|
227 |
|
228 void |
|
229 SharedArrayBufferObject::dropRawBuffer() |
|
230 { |
|
231 setReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT, UndefinedValue()); |
|
232 } |
|
233 |
|
234 SharedArrayRawBuffer * |
|
235 SharedArrayBufferObject::rawBufferObject() const |
|
236 { |
|
237 // RAWBUF_SLOT must be populated via acceptRawBuffer(), |
|
238 // and the raw buffer must not have been dropped. |
|
239 Value v = getReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT); |
|
240 return (SharedArrayRawBuffer *)v.toPrivate(); |
|
241 } |
|
242 |
|
243 uint8_t * |
|
244 SharedArrayBufferObject::dataPointer() const |
|
245 { |
|
246 return rawBufferObject()->dataPointer(); |
|
247 } |
|
248 |
|
249 uint32_t |
|
250 SharedArrayBufferObject::byteLength() const |
|
251 { |
|
252 return rawBufferObject()->byteLength(); |
|
253 } |
|
254 |
|
255 void |
|
256 SharedArrayBufferObject::Finalize(FreeOp *fop, JSObject *obj) |
|
257 { |
|
258 SharedArrayBufferObject &buf = obj->as<SharedArrayBufferObject>(); |
|
259 |
|
260 // Detect the case of failure during SharedArrayBufferObject creation, |
|
261 // which causes a SharedArrayRawBuffer to never be attached. |
|
262 Value v = buf.getReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT); |
|
263 if (!v.isUndefined()) { |
|
264 buf.rawBufferObject()->dropReference(); |
|
265 buf.dropRawBuffer(); |
|
266 } |
|
267 } |
|
268 |
|
269 /* |
|
270 * SharedArrayBufferObject |
|
271 */ |
|
272 |
|
273 const Class SharedArrayBufferObject::protoClass = { |
|
274 "SharedArrayBufferPrototype", |
|
275 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer), |
|
276 JS_PropertyStub, /* addProperty */ |
|
277 JS_DeletePropertyStub, /* delProperty */ |
|
278 JS_PropertyStub, /* getProperty */ |
|
279 JS_StrictPropertyStub, /* setProperty */ |
|
280 JS_EnumerateStub, |
|
281 JS_ResolveStub, |
|
282 JS_ConvertStub |
|
283 }; |
|
284 |
|
285 const Class SharedArrayBufferObject::class_ = { |
|
286 "SharedArrayBuffer", |
|
287 JSCLASS_IMPLEMENTS_BARRIERS | |
|
288 JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) | |
|
289 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer), |
|
290 JS_PropertyStub, /* addProperty */ |
|
291 JS_DeletePropertyStub, /* delProperty */ |
|
292 JS_PropertyStub, /* getProperty */ |
|
293 JS_StrictPropertyStub, /* setProperty */ |
|
294 JS_EnumerateStub, |
|
295 JS_ResolveStub, |
|
296 JS_ConvertStub, |
|
297 SharedArrayBufferObject::Finalize, |
|
298 nullptr, /* call */ |
|
299 nullptr, /* hasInstance */ |
|
300 nullptr, /* construct */ |
|
301 ArrayBufferObject::obj_trace, |
|
302 JS_NULL_CLASS_SPEC, |
|
303 JS_NULL_CLASS_EXT |
|
304 }; |
|
305 |
|
306 JSObject * |
|
307 js_InitSharedArrayBufferClass(JSContext *cx, HandleObject obj) |
|
308 { |
|
309 JS_ASSERT(obj->isNative()); |
|
310 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
|
311 RootedObject proto(cx, global->createBlankPrototype(cx, &SharedArrayBufferObject::protoClass)); |
|
312 if (!proto) |
|
313 return nullptr; |
|
314 |
|
315 RootedFunction ctor(cx, global->createConstructor(cx, SharedArrayBufferObject::class_constructor, |
|
316 cx->names().SharedArrayBuffer, 1)); |
|
317 if (!ctor) |
|
318 return nullptr; |
|
319 |
|
320 if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
|
321 return nullptr; |
|
322 |
|
323 RootedId byteLengthId(cx, NameToId(cx->names().byteLength)); |
|
324 unsigned attrs = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT; |
|
325 JSObject *getter = NewFunction(cx, NullPtr(), SharedArrayBufferObject::byteLengthGetter, 0, |
|
326 JSFunction::NATIVE_FUN, global, NullPtr()); |
|
327 if (!getter) |
|
328 return nullptr; |
|
329 |
|
330 RootedValue value(cx, UndefinedValue()); |
|
331 if (!DefineNativeProperty(cx, proto, byteLengthId, value, |
|
332 JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr, attrs)) |
|
333 { |
|
334 return nullptr; |
|
335 } |
|
336 |
|
337 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_SharedArrayBuffer, ctor, proto)) |
|
338 return nullptr; |
|
339 return proto; |
|
340 } |