michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/SharedArrayObject.h" michael@0: michael@0: #include "jsprf.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: #ifdef XP_WIN michael@0: # include "jswin.h" michael@0: #else michael@0: # include michael@0: #endif michael@0: michael@0: #ifdef MOZ_VALGRIND michael@0: # include michael@0: #endif michael@0: michael@0: #include "mozilla/Atomics.h" michael@0: #include "jit/AsmJS.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::IsNaN; michael@0: using mozilla::PodCopy; michael@0: michael@0: /* michael@0: * SharedArrayRawBuffer michael@0: */ michael@0: michael@0: static inline void * michael@0: MapMemory(size_t length, bool commit) michael@0: { michael@0: #ifdef XP_WIN michael@0: int prot = (commit ? MEM_COMMIT : MEM_RESERVE); michael@0: int flags = (commit ? PAGE_READWRITE : PAGE_NOACCESS); michael@0: return VirtualAlloc(nullptr, length, prot, flags); michael@0: #else michael@0: int prot = (commit ? (PROT_READ | PROT_WRITE) : PROT_NONE); michael@0: void *p = mmap(nullptr, length, prot, MAP_PRIVATE | MAP_ANON, -1, 0); michael@0: if (p == MAP_FAILED) michael@0: return nullptr; michael@0: return p; michael@0: #endif michael@0: } michael@0: michael@0: static inline void michael@0: UnmapMemory(void *addr, size_t len) michael@0: { michael@0: #ifdef XP_WIN michael@0: VirtualFree(addr, 0, MEM_RELEASE); michael@0: #else michael@0: munmap(addr, len); michael@0: #endif michael@0: } michael@0: michael@0: static inline bool michael@0: MarkValidRegion(void *addr, size_t len) michael@0: { michael@0: #ifdef XP_WIN michael@0: if (!VirtualAlloc(addr, len, MEM_COMMIT, PAGE_READWRITE)) michael@0: return false; michael@0: return true; michael@0: #else michael@0: if (mprotect(addr, len, PROT_READ | PROT_WRITE)) michael@0: return false; michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: SharedArrayRawBuffer * michael@0: SharedArrayRawBuffer::New(uint32_t length) michael@0: { michael@0: // Enforced by SharedArrayBufferObject constructor. michael@0: JS_ASSERT(IsValidAsmJSHeapLength(length)); michael@0: michael@0: #ifdef JS_CPU_X64 michael@0: // Get the entire reserved region (with all pages inaccessible) michael@0: void *p = MapMemory(AsmJSMappedSize, false); michael@0: if (!p) michael@0: return nullptr; michael@0: michael@0: size_t validLength = AsmJSPageSize + length; michael@0: if (!MarkValidRegion(p, validLength)) { michael@0: UnmapMemory(p, AsmJSMappedSize); michael@0: return nullptr; michael@0: } michael@0: # if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE) michael@0: // Tell Valgrind/Memcheck to not report accesses in the inaccessible region. michael@0: VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)p + validLength, michael@0: AsmJSMappedSize-validLength); michael@0: # endif michael@0: #else michael@0: uint32_t allocSize = length + AsmJSPageSize; michael@0: if (allocSize <= length) michael@0: return nullptr; michael@0: michael@0: void *p = MapMemory(allocSize, true); michael@0: if (!p) michael@0: return nullptr; michael@0: #endif michael@0: uint8_t *buffer = reinterpret_cast(p) + AsmJSPageSize; michael@0: uint8_t *base = buffer - sizeof(SharedArrayRawBuffer); michael@0: return new (base) SharedArrayRawBuffer(buffer, length); michael@0: } michael@0: michael@0: void michael@0: SharedArrayRawBuffer::addReference() michael@0: { michael@0: JS_ASSERT(this->refcount > 0); michael@0: ++this->refcount; // Atomic. michael@0: } michael@0: michael@0: void michael@0: SharedArrayRawBuffer::dropReference() michael@0: { michael@0: // Drop the reference to the buffer. michael@0: uint32_t refcount = --this->refcount; // Atomic. michael@0: michael@0: // If this was the final reference, release the buffer. michael@0: if (refcount == 0) { michael@0: uint8_t *p = this->dataPointer() - AsmJSPageSize; michael@0: JS_ASSERT(uintptr_t(p) % AsmJSPageSize == 0); michael@0: #ifdef JS_CPU_X64 michael@0: UnmapMemory(p, AsmJSMappedSize); michael@0: # if defined(MOZ_VALGRIND) \ michael@0: && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE) michael@0: // Tell Valgrind/Memcheck to recommence reporting accesses in the michael@0: // previously-inaccessible region. michael@0: if (AsmJSMappedSize > 0) { michael@0: VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(p, AsmJSMappedSize); michael@0: } michael@0: # endif michael@0: #else michael@0: UnmapMemory(p, this->length + AsmJSPageSize); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * SharedArrayBufferObject michael@0: */ michael@0: bool michael@0: js::IsSharedArrayBuffer(HandleValue v) michael@0: { michael@0: return v.isObject() && v.toObject().is(); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: SharedArrayBufferObject::byteLengthGetterImpl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsSharedArrayBuffer(args.thisv())); michael@0: args.rval().setInt32(args.thisv().toObject().as().byteLength()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SharedArrayBufferObject::byteLengthGetter(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: bool michael@0: SharedArrayBufferObject::class_constructor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: int32_t length = 0; michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() > 0 && !ToInt32(cx, args[0], &length)) michael@0: return false; michael@0: michael@0: if (length < 0) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); michael@0: return false; michael@0: } michael@0: michael@0: JSObject *bufobj = New(cx, uint32_t(length)); michael@0: if (!bufobj) michael@0: return false; michael@0: args.rval().setObject(*bufobj); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: SharedArrayBufferObject::New(JSContext *cx, uint32_t length) michael@0: { michael@0: if (!IsValidAsmJSHeapLength(length)) { michael@0: ScopedJSFreePtr msg( michael@0: JS_smprintf("SharedArrayBuffer byteLength 0x%x is not a valid length. The next valid " michael@0: "length is 0x%x", length, RoundUpToNextValidAsmJSHeapLength(length))); michael@0: JS_ReportError(cx, msg.get()); michael@0: return nullptr; michael@0: } michael@0: michael@0: SharedArrayRawBuffer *buffer = SharedArrayRawBuffer::New(length); michael@0: if (!buffer) michael@0: return nullptr; michael@0: michael@0: return New(cx, buffer); michael@0: } michael@0: michael@0: JSObject * michael@0: SharedArrayBufferObject::New(JSContext *cx, SharedArrayRawBuffer *buffer) michael@0: { michael@0: Rooted obj(cx, NewBuiltinClassInstance(cx)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(obj->getClass() == &class_); michael@0: michael@0: obj->initialize(buffer->byteLength(), nullptr, DoesntOwnData); michael@0: michael@0: obj->acceptRawBuffer(buffer); michael@0: obj->setIsSharedArrayBuffer(); michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: void michael@0: SharedArrayBufferObject::acceptRawBuffer(SharedArrayRawBuffer *buffer) michael@0: { michael@0: setReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT, PrivateValue(buffer)); michael@0: } michael@0: michael@0: void michael@0: SharedArrayBufferObject::dropRawBuffer() michael@0: { michael@0: setReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT, UndefinedValue()); michael@0: } michael@0: michael@0: SharedArrayRawBuffer * michael@0: SharedArrayBufferObject::rawBufferObject() const michael@0: { michael@0: // RAWBUF_SLOT must be populated via acceptRawBuffer(), michael@0: // and the raw buffer must not have been dropped. michael@0: Value v = getReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT); michael@0: return (SharedArrayRawBuffer *)v.toPrivate(); michael@0: } michael@0: michael@0: uint8_t * michael@0: SharedArrayBufferObject::dataPointer() const michael@0: { michael@0: return rawBufferObject()->dataPointer(); michael@0: } michael@0: michael@0: uint32_t michael@0: SharedArrayBufferObject::byteLength() const michael@0: { michael@0: return rawBufferObject()->byteLength(); michael@0: } michael@0: michael@0: void michael@0: SharedArrayBufferObject::Finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: SharedArrayBufferObject &buf = obj->as(); michael@0: michael@0: // Detect the case of failure during SharedArrayBufferObject creation, michael@0: // which causes a SharedArrayRawBuffer to never be attached. michael@0: Value v = buf.getReservedSlot(SharedArrayBufferObject::RAWBUF_SLOT); michael@0: if (!v.isUndefined()) { michael@0: buf.rawBufferObject()->dropReference(); michael@0: buf.dropRawBuffer(); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * SharedArrayBufferObject michael@0: */ michael@0: michael@0: const Class SharedArrayBufferObject::protoClass = { michael@0: "SharedArrayBufferPrototype", michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: const Class SharedArrayBufferObject::class_ = { michael@0: "SharedArrayBuffer", michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: SharedArrayBufferObject::Finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: ArrayBufferObject::obj_trace, michael@0: JS_NULL_CLASS_SPEC, michael@0: JS_NULL_CLASS_EXT michael@0: }; michael@0: michael@0: JSObject * michael@0: js_InitSharedArrayBufferClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: Rooted global(cx, &obj->as()); michael@0: RootedObject proto(cx, global->createBlankPrototype(cx, &SharedArrayBufferObject::protoClass)); michael@0: if (!proto) michael@0: return nullptr; michael@0: michael@0: RootedFunction ctor(cx, global->createConstructor(cx, SharedArrayBufferObject::class_constructor, michael@0: cx->names().SharedArrayBuffer, 1)); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: if (!LinkConstructorAndPrototype(cx, ctor, proto)) michael@0: return nullptr; michael@0: michael@0: RootedId byteLengthId(cx, NameToId(cx->names().byteLength)); michael@0: unsigned attrs = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT; michael@0: JSObject *getter = NewFunction(cx, NullPtr(), SharedArrayBufferObject::byteLengthGetter, 0, michael@0: JSFunction::NATIVE_FUN, global, NullPtr()); michael@0: if (!getter) michael@0: return nullptr; michael@0: michael@0: RootedValue value(cx, UndefinedValue()); michael@0: if (!DefineNativeProperty(cx, proto, byteLengthId, value, michael@0: JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr, attrs)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_SharedArrayBuffer, ctor, proto)) michael@0: return nullptr; michael@0: return proto; michael@0: }