/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "vm/ArrayBufferObject-inl.h"
#include "vm/ArrayBufferObject.h"

#include "mozilla/Alignment.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h"
#include "mozilla/TaggedAnonymousMemory.h"

#include <string.h>
#ifndef XP_WIN
# include <sys/mman.h>
#endif

#ifdef MOZ_VALGRIND
# include <valgrind/memcheck.h>
#endif

#include "jsapi.h"
#include "jsarray.h"
#include "jscntxt.h"
#include "jscpucfg.h"
#include "jsfriendapi.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jstypes.h"
#include "jsutil.h"
#ifdef XP_WIN
# include "jswin.h"
#endif
#include "jswrapper.h"

#include "builtin/DataViewObject.h"
#include "gc/Barrier.h"
#include "gc/Marking.h"
#include "gc/Memory.h"
#include "js/Conversions.h"
#include "js/MemoryMetrics.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/SharedArrayObject.h"
#include "vm/WrapperObject.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmTypes.h"

#include "jsatominlines.h"

#include "vm/NativeObject-inl.h"
#include "vm/Shape-inl.h"

using JS::ToInt32;

using mozilla::DebugOnly;
using mozilla::CheckedInt;
using mozilla::Some;
using mozilla::Maybe;
using mozilla::Nothing;

using namespace js;
using namespace js::gc;

/*
 * Convert |v| to an array index for an array of length |length| per
 * the Typed Array Specification section 7.0, |subarray|. If successful,
 * the output value is in the range [0, length].
 */
bool
js::ToClampedIndex(JSContext* cx, HandleValue v, uint32_t length, uint32_t* out)
{
    int32_t result;
    if (!ToInt32(cx, v, &result))
        return false;
    if (result < 0) {
        result += length;
        if (result < 0)
            result = 0;
    } else if (uint32_t(result) > length) {
        result = length;
    }
    *out = uint32_t(result);
    return true;
}

/*
 * ArrayBufferObject
 *
 * This class holds the underlying raw buffer that the TypedArrayObject classes
 * access.  It can be created explicitly and passed to a TypedArrayObject, or
 * can be created implicitly by constructing a TypedArrayObject with a size.
 */

/*
 * ArrayBufferObject (base)
 */

static JSObject*
CreateArrayBufferPrototype(JSContext* cx, JSProtoKey key)
{
    return GlobalObject::createBlankPrototype(cx, cx->global(), &ArrayBufferObject::protoClass_);
}

static const ClassOps ArrayBufferObjectClassOps = {
    nullptr,        /* addProperty */
    nullptr,        /* delProperty */
    nullptr,        /* enumerate */
    nullptr,        /* newEnumerate */
    nullptr,        /* resolve */
    nullptr,        /* mayResolve */
    ArrayBufferObject::finalize,
    nullptr,        /* call        */
    nullptr,        /* hasInstance */
    nullptr,        /* construct   */
    ArrayBufferObject::trace,
};

static const JSFunctionSpec static_functions[] = {
    JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0),
    JS_FS_END
};

static const JSPropertySpec static_properties[] = {
    JS_SELF_HOSTED_SYM_GET(species, "ArrayBufferSpecies", 0),
    JS_PS_END
};


static const JSFunctionSpec prototype_functions[] = {
    JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
    JS_FS_END
};

static const JSPropertySpec prototype_properties[] = {
    JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
    JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
    JS_PS_END
};

static const ClassSpec ArrayBufferObjectClassSpec = {
    GenericCreateConstructor<ArrayBufferObject::class_constructor, 1, gc::AllocKind::FUNCTION>,
    CreateArrayBufferPrototype,
    static_functions,
    static_properties,
    prototype_functions,
    prototype_properties
};

static const ClassExtension ArrayBufferObjectClassExtension = {
    nullptr,    /* weakmapKeyDelegateOp */
    ArrayBufferObject::objectMoved
};

const Class ArrayBufferObject::class_ = {
    "ArrayBuffer",
    JSCLASS_DELAY_METADATA_BUILDER |
    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
    JSCLASS_BACKGROUND_FINALIZE,
    &ArrayBufferObjectClassOps,
    &ArrayBufferObjectClassSpec,
    &ArrayBufferObjectClassExtension
};

const Class ArrayBufferObject::protoClass_ = {
    "ArrayBufferPrototype",
    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer),
    JS_NULL_CLASS_OPS,
    &ArrayBufferObjectClassSpec
};

bool
js::IsArrayBuffer(HandleValue v)
{
    return v.isObject() && v.toObject().is<ArrayBufferObject>();
}

bool
js::IsArrayBuffer(HandleObject obj)
{
    return obj->is<ArrayBufferObject>();
}

bool
js::IsArrayBuffer(JSObject* obj)
{
    return obj->is<ArrayBufferObject>();
}

ArrayBufferObject&
js::AsArrayBuffer(HandleObject obj)
{
    MOZ_ASSERT(IsArrayBuffer(obj));
    return obj->as<ArrayBufferObject>();
}

ArrayBufferObject&
js::AsArrayBuffer(JSObject* obj)
{
    MOZ_ASSERT(IsArrayBuffer(obj));
    return obj->as<ArrayBufferObject>();
}

bool
js::IsArrayBufferMaybeShared(HandleValue v)
{
    return v.isObject() && v.toObject().is<ArrayBufferObjectMaybeShared>();
}

bool
js::IsArrayBufferMaybeShared(HandleObject obj)
{
    return obj->is<ArrayBufferObjectMaybeShared>();
}

bool
js::IsArrayBufferMaybeShared(JSObject* obj)
{
    return obj->is<ArrayBufferObjectMaybeShared>();
}

ArrayBufferObjectMaybeShared&
js::AsArrayBufferMaybeShared(HandleObject obj)
{
    MOZ_ASSERT(IsArrayBufferMaybeShared(obj));
    return obj->as<ArrayBufferObjectMaybeShared>();
}

ArrayBufferObjectMaybeShared&
js::AsArrayBufferMaybeShared(JSObject* obj)
{
    MOZ_ASSERT(IsArrayBufferMaybeShared(obj));
    return obj->as<ArrayBufferObjectMaybeShared>();
}

MOZ_ALWAYS_INLINE bool
ArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
{
    MOZ_ASSERT(IsArrayBuffer(args.thisv()));
    args.rval().setInt32(args.thisv().toObject().as<ArrayBufferObject>().byteLength());
    return true;
}

bool
ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
}

/*
 * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
 */
bool
ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    args.rval().setBoolean(args.get(0).isObject() &&
                           JS_IsArrayBufferViewObject(&args.get(0).toObject()));
    return true;
}

// ES2017 draft 24.1.2.1
bool
ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1.
    if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer"))
        return false;

    // Step 2.
    uint64_t byteLength;
    if (!ToIndex(cx, args.get(0), &byteLength))
        return false;

    // Step 3 (Inlined 24.1.1.1 AllocateArrayBuffer).
    // 24.1.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
    RootedObject proto(cx);
    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
        return false;

    // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
    // Refuse to allocate too large buffers, currently limited to ~2 GiB.
    if (byteLength > INT32_MAX) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
        return false;
    }

    // 24.1.1.1, steps 1 and 4-6.
    JSObject* bufobj = create(cx, uint32_t(byteLength), proto);
    if (!bufobj)
        return false;
    args.rval().setObject(*bufobj);
    return true;
}

static ArrayBufferObject::BufferContents
AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
{
    uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
    if (!p)
        ReportOutOfMemory(cx);

    return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
}

static void
NoteViewBufferWasDetached(ArrayBufferViewObject* view,
                          ArrayBufferObject::BufferContents newContents,
                          JSContext* cx)
{
    view->notifyBufferDetached(cx, newContents.data());

    // Notify compiled jit code that the base pointer has moved.
    MarkObjectStateChange(cx, view);
}

/* static */ void
ArrayBufferObject::detach(JSContext* cx, Handle<ArrayBufferObject*> buffer,
                          BufferContents newContents)
{
    assertSameCompartment(cx, buffer);
    MOZ_ASSERT(!buffer->isPreparedForAsmJS());

    // When detaching buffers where we don't know all views, the new data must
    // match the old data. All missing views are typed objects, which do not
    // expect their data to ever change.
    MOZ_ASSERT_IF(buffer->forInlineTypedObject(),
                  newContents.data() == buffer->dataPointer());

    // When detaching a buffer with typed object views, any jitcode accessing
    // such views must be deoptimized so that detachment checks are performed.
    // This is done by setting a compartment-wide flag indicating that buffers
    // with typed object views have been detached.
    if (buffer->hasTypedObjectViews()) {
        // Make sure the global object's group has been instantiated, so the
        // flag change will be observed.
        AutoEnterOOMUnsafeRegion oomUnsafe;
        if (!JSObject::getGroup(cx, cx->global()))
            oomUnsafe.crash("ArrayBufferObject::detach");
        MarkObjectGroupFlags(cx, cx->global(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER);
        cx->compartment()->detachedTypedObjects = 1;
    }

    // Update all views of the buffer to account for the buffer having been
    // detached, and clear the buffer's data and list of views.

    auto& innerViews = cx->compartment()->innerViews.get();
    if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(buffer)) {
        for (size_t i = 0; i < views->length(); i++)
            NoteViewBufferWasDetached((*views)[i], newContents, cx);
        innerViews.removeViews(buffer);
    }
    if (buffer->firstView()) {
        if (buffer->forInlineTypedObject()) {
            // The buffer points to inline data in its first view, so to keep
            // this pointer alive we don't clear out the first view.
            MOZ_ASSERT(buffer->firstView()->is<InlineTransparentTypedObject>());
        } else {
            NoteViewBufferWasDetached(buffer->firstView(), newContents, cx);
            buffer->setFirstView(nullptr);
        }
    }

    if (newContents.data() != buffer->dataPointer())
        buffer->setNewData(cx->runtime()->defaultFreeOp(), newContents, OwnsData);

    buffer->setByteLength(0);
    buffer->setIsDetached();
}

void
ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState)
{
    if (ownsData()) {
        MOZ_ASSERT(newContents.data() != dataPointer());
        releaseData(fop);
    }

    setDataPointer(newContents, ownsState);
}

// This is called *only* from changeContents(), below.
// By construction, every view parameter will be mapping unshared memory (an ArrayBuffer).
// Hence no reason to worry about shared memory here.

void
ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
                                      uint8_t* oldDataPointer, BufferContents newContents)
{
    MOZ_ASSERT(!view->isSharedMemory());

    // Watch out for NULL data pointers in views. This means that the view
    // is not fully initialized (in which case it'll be initialized later
    // with the correct pointer).
    JS::AutoCheckCannotGC nogc;
    uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
    if (viewDataPointer) {
        MOZ_ASSERT(newContents);
        ptrdiff_t offset = viewDataPointer - oldDataPointer;
        viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
        view->setDataPointerUnshared(viewDataPointer);
    }

    // Notify compiled jit code that the base pointer has moved.
    MarkObjectStateChange(cx, view);
}

// BufferContents is specific to ArrayBuffer, hence it will not represent shared memory.

void
ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
                                  OwnsState ownsState)
{
    MOZ_RELEASE_ASSERT(!isWasm());
    MOZ_ASSERT(!forInlineTypedObject());

    // Change buffer contents.
    uint8_t* oldDataPointer = dataPointer();
    setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);

    // Update all views.
    auto& innerViews = cx->compartment()->innerViews.get();
    if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
        for (size_t i = 0; i < views->length(); i++)
            changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
    }
    if (firstView())
        changeViewContents(cx, firstView(), oldDataPointer, newContents);
}

/*
 * Wasm Raw Buf Linear Memory Structure
 *
 * The linear heap in Wasm is an mmaped array buffer. Several
 * constants manage its lifetime:
 *
 *  - length - the wasm-visible current length of the buffer. Accesses in the
 *    range [0, length] succeed. May only increase.
 *
 *  - boundsCheckLimit - when !WASM_HUGE_MEMORY, the size against which we
 *    perform bounds checks. It is always a constant offset smaller than
 *    mappedSize. Currently that constant offset is 64k (wasm::GuardSize).
 *
 *  - max - the optional declared limit on how much length can grow.
 *
 *  - mappedSize - the actual mmaped size. Access in the range
 *    [0, mappedSize] will either succeed, or be handled by the wasm signal
 *    handlers.
 *
 * The below diagram shows the layout of the wasm heap. The wasm-visible
 * portion of the heap starts at 0. There is one extra page prior to the
 * start of the wasm heap which contains the WasmArrayRawBuffer struct at
 * its end (i.e. right before the start of the WASM heap).
 *
 *  WasmArrayRawBuffer
 *      \    ArrayBufferObject::dataPointer()
 *       \  /
 *        \ |
 *  ______|_|____________________________________________________________
 * |______|_|______________|___________________|____________|____________|
 *          0          length              maxSize  boundsCheckLimit  mappedSize
 *
 * \_______________________/
 *          COMMITED
 *                          \____________________________________________/
 *                                           SLOP
 * \_____________________________________________________________________/
 *                         MAPPED
 *
 * Invariants:
 *  - length only increases
 *  - 0 <= length <= maxSize (if present) <= boundsCheckLimit <= mappedSize
 *  - on ARM boundsCheckLimit must be a valid ARM immediate.
 *  - if maxSize is not specified, boundsCheckLimit/mappedSize may grow. They are
 *    otherwise constant.
 *
 * NOTE: For asm.js on non-x64 we guarantee that
 *
 * length == maxSize == boundsCheckLimit == mappedSize
 *
 * That is, signal handlers will not be invoked, since they cannot emulate
 * asm.js accesses on non-x64 architectures.
 *
 * The region between length and mappedSize is the SLOP - an area where we use
 * signal handlers to catch things that slip by bounds checks. Logically it has
 * two parts:
 *
 *  - from length to boundsCheckLimit - this part of the SLOP serves to catch
 *  accesses to memory we have reserved but not yet grown into. This allows us
 *  to grow memory up to max (when present) without having to patch/update the
 *  bounds checks.
 *
 *  - from boundsCheckLimit to mappedSize - (Note: In current patch 0) - this
 *  part of the SLOP allows us to bounds check against base pointers and fold
 *  some constant offsets inside loads. This enables better Bounds
 *  Check Elimination.
 *
 */

class js::WasmArrayRawBuffer
{
    Maybe<uint32_t> maxSize_;
    size_t mappedSize_;

  protected:
    WasmArrayRawBuffer(uint8_t* buffer, uint32_t length, const Maybe<uint32_t>& maxSize,
                       size_t mappedSize)
      : maxSize_(maxSize), mappedSize_(mappedSize)
    {
        MOZ_ASSERT(buffer == dataPointer());
    }

  public:
    static WasmArrayRawBuffer* Allocate(uint32_t numBytes, const Maybe<uint32_t>& maxSize);
    static void Release(void* mem);

    uint8_t* dataPointer() {
        uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
        return ptr + sizeof(WasmArrayRawBuffer);
    }

    uint8_t* basePointer() {
        return dataPointer() - gc::SystemPageSize();
    }

    size_t mappedSize() const {
        return mappedSize_;
    }

    Maybe<uint32_t> maxSize() const {
        return maxSize_;
    }

    size_t allocatedBytes() const {
        return mappedSize_ + gc::SystemPageSize();
    }

#ifndef WASM_HUGE_MEMORY
    uint32_t boundsCheckLimit() const {
        MOZ_ASSERT(mappedSize_ <= UINT32_MAX);
        MOZ_ASSERT(mappedSize_ >= wasm::GuardSize);
        MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize_ - wasm::GuardSize));
        return mappedSize_ - wasm::GuardSize;
    }
#endif

    MOZ_MUST_USE bool growToSizeInPlace(uint32_t oldSize, uint32_t newSize) {
        MOZ_ASSERT(newSize >= oldSize);
        MOZ_ASSERT_IF(maxSize(), newSize <= maxSize().value());
        MOZ_ASSERT(newSize <= mappedSize());

        uint32_t delta = newSize - oldSize;
        MOZ_ASSERT(delta % wasm::PageSize == 0);

        uint8_t* dataEnd = dataPointer() + oldSize;
        MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
# ifdef XP_WIN
        if (delta && !VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE))
            return false;
# else  // XP_WIN
        if (delta && mprotect(dataEnd, delta, PROT_READ | PROT_WRITE))
            return false;
# endif  // !XP_WIN

#  if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
        VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
#  endif

        return true;
    }

#ifndef WASM_HUGE_MEMORY
    bool extendMappedSize(uint32_t maxSize) {
        size_t newMappedSize = wasm::ComputeMappedSize(maxSize);
        MOZ_ASSERT(mappedSize_ <= newMappedSize);
        if (mappedSize_ == newMappedSize)
            return true;

# ifdef XP_WIN
        uint8_t* mappedEnd = dataPointer() + mappedSize_;
        uint32_t delta = newMappedSize - mappedSize_;
        if (!VirtualAlloc(mappedEnd, delta, MEM_RESERVE, PAGE_NOACCESS))
            return false;
# elif defined(XP_LINUX)
        // Note this will not move memory (no MREMAP_MAYMOVE specified)
        if (MAP_FAILED == mremap(dataPointer(), mappedSize_, newMappedSize, 0))
            return false;
# else
        // No mechanism for remapping on MacOS and other Unices. Luckily
        // shouldn't need it here as most of these are 64-bit.
        return false;
# endif

        mappedSize_ = newMappedSize;
        return true;
    }

    // Try and grow the mapped region of memory. Does not changes current size.
    // Does not move memory if no space to grow.
    void tryGrowMaxSizeInPlace(uint32_t deltaMaxSize) {
        CheckedInt<uint32_t> newMaxSize = maxSize_.value();
        newMaxSize += deltaMaxSize;
        MOZ_ASSERT(newMaxSize.isValid());
        MOZ_ASSERT(newMaxSize.value() % wasm::PageSize == 0);

        if (!extendMappedSize(newMaxSize.value()))
            return;

        maxSize_ = Some(newMaxSize.value());
    }
#endif // WASM_HUGE_MEMORY
};

/* static */ WasmArrayRawBuffer*
WasmArrayRawBuffer::Allocate(uint32_t numBytes, const Maybe<uint32_t>& maxSize)
{
    size_t mappedSize;
#ifdef WASM_HUGE_MEMORY
    mappedSize = wasm::HugeMappedSize;
#else
    mappedSize = wasm::ComputeMappedSize(maxSize.valueOr(numBytes));
#endif

    MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
    MOZ_RELEASE_ASSERT(numBytes <= maxSize.valueOr(UINT32_MAX));
    MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
    MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);

    uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize();
    uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize();

# ifdef XP_WIN
    void* data = VirtualAlloc(nullptr, (size_t) mappedSizeWithHeader, MEM_RESERVE, PAGE_NOACCESS);
    if (!data)
        return nullptr;

    if (!VirtualAlloc(data, numBytesWithHeader, MEM_COMMIT, PAGE_READWRITE)) {
        VirtualFree(data, 0, MEM_RELEASE);
        return nullptr;
    }
# else  // XP_WIN
    void* data = MozTaggedAnonymousMmap(nullptr, (size_t) mappedSizeWithHeader, PROT_NONE,
                                        MAP_PRIVATE | MAP_ANON, -1, 0, "wasm-reserved");
    if (data == MAP_FAILED)
        return nullptr;

    // Note we will waste a page on zero-sized memories here
    if (mprotect(data, numBytesWithHeader, PROT_READ | PROT_WRITE)) {
        munmap(data, mappedSizeWithHeader);
        return nullptr;
    }
# endif  // !XP_WIN

#  if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
    VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)data + numBytesWithHeader,
                                                   mappedSizeWithHeader - numBytesWithHeader);
#  endif

    uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize();
    uint8_t* header = base - sizeof(WasmArrayRawBuffer);

    auto rawBuf = new (header) WasmArrayRawBuffer(base, numBytes, maxSize, mappedSize);
    return rawBuf;
}

/* static */ void
WasmArrayRawBuffer::Release(void* mem)
{
    WasmArrayRawBuffer* header = (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer));
    uint8_t* base = header->basePointer();
    MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());

# ifdef XP_WIN
    VirtualFree(base, 0, MEM_RELEASE);
# else  // XP_WIN
    size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
    munmap(base, mappedSizeWithHeader);
# endif  // !XP_WIN

#  if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
    VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, mappedSizeWithHeader);
#  endif
}

WasmArrayRawBuffer*
ArrayBufferObject::BufferContents::wasmBuffer() const
{
    MOZ_RELEASE_ASSERT(kind_ == WASM);
    return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
}

#define ROUND_UP(v, a) ((v) % (a) == 0 ? (v) : v + a - ((v) % (a)))

/* static */ ArrayBufferObject*
ArrayBufferObject::createForWasm(JSContext* cx, uint32_t initialSize,
                                 const Maybe<uint32_t>& maybeMaxSize)
{
    MOZ_ASSERT(initialSize % wasm::PageSize == 0);
    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
    MOZ_RELEASE_ASSERT((initialSize / wasm::PageSize) <= wasm::MaxMemoryInitialPages);

    // Prevent applications specifying a large max (like UINT32_MAX) from
    // unintentially OOMing the browser on 32-bit: they just want "a lot of
    // memory". Maintain the invariant that initialSize <= maxSize.
    Maybe<uint32_t> maxSize = maybeMaxSize;
    if (sizeof(void*) == 4 && maybeMaxSize) {
        static const uint32_t OneGiB = 1 << 30;
        uint32_t clamp = Max(OneGiB, initialSize);
        maxSize = Some(Min(clamp, maybeMaxSize.value()));
    }

    RootedArrayBufferObject buffer(cx, ArrayBufferObject::createEmpty(cx));
    if (!buffer)
        return nullptr;

    // Try to reserve the maximum requested memory
    WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, maxSize);
    if (!wasmBuf) {
#ifdef  WASM_HUGE_MEMORY
        ReportOutOfMemory(cx);
        return nullptr;
#else
        // If we fail, and have a maxSize, try to reserve the biggest chunk in
        // the range [initialSize, maxSize) using log backoff.
        if (!maxSize) {
            ReportOutOfMemory(cx);
            return nullptr;
        }

        uint32_t cur = maxSize.value() / 2;

        for (; cur > initialSize; cur /= 2) {
            wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, Some(ROUND_UP(cur, wasm::PageSize)));
            if (wasmBuf)
                break;
        }

        if (!wasmBuf) {
            ReportOutOfMemory(cx);
            return nullptr;
        }

        // Try to grow our chunk as much as possible.
        for (size_t d = cur / 2; d >= wasm::PageSize; d /= 2)
            wasmBuf->tryGrowMaxSizeInPlace(ROUND_UP(d, wasm::PageSize));
#endif
    }

    auto contents = BufferContents::create<WASM>(wasmBuf->dataPointer());
    buffer->initialize(initialSize, contents, OwnsData);
    cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
    return buffer;
}

// Note this function can return false with or without an exception pending. The
// asm.js caller checks cx->isExceptionPending before propagating failure.
// Returning false without throwing means that asm.js linking will fail which
// will recompile as non-asm.js.
/* static */ bool
ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer, bool needGuard)
{
#ifdef WASM_HUGE_MEMORY
    MOZ_ASSERT(needGuard);
#endif
    MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());

    if (buffer->forInlineTypedObject())
        return false;

    if (needGuard) {
        if (buffer->isWasm() && buffer->isPreparedForAsmJS())
            return true;

        // Non-prepared-for-asm.js wasm buffers can be detached at any time.
        // This error can only be triggered for SIMD.js (which isn't shipping)
        // on !WASM_HUGE_MEMORY so this error is only visible in testing.
        if (buffer->isWasm() || buffer->isPreparedForAsmJS())
            return false;

        uint32_t length = buffer->byteLength();
        WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(length, Some(length));
        if (!wasmBuf) {
            ReportOutOfMemory(cx);
            return false;
        }

        void* data = wasmBuf->dataPointer();
        memcpy(data, buffer->dataPointer(), length);

        // Swap the new elements into the ArrayBufferObject. Mark the
        // ArrayBufferObject so we don't do this again.
        buffer->changeContents(cx, BufferContents::create<WASM>(data), OwnsData);
        buffer->setIsPreparedForAsmJS();
        MOZ_ASSERT(data == buffer->dataPointer());
        cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
        return true;
    }

    if (!buffer->isWasm() && buffer->isPreparedForAsmJS())
        return true;

    // Non-prepared-for-asm.js wasm buffers can be detached at any time.
    if (buffer->isWasm())
        return false;

    if (!buffer->ownsData()) {
        BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
        if (!contents)
            return false;
        memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
        buffer->changeContents(cx, contents, OwnsData);
    }

    buffer->setIsPreparedForAsmJS();
    return true;
}

ArrayBufferObject::BufferContents
ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
{
    void* data = AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
    return BufferContents::create<MAPPED>(data);
}

uint8_t*
ArrayBufferObject::inlineDataPointer() const
{
    return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
}

uint8_t*
ArrayBufferObject::dataPointer() const
{
    return static_cast<uint8_t*>(getSlot(DATA_SLOT).toPrivate());
}

SharedMem<uint8_t*>
ArrayBufferObject::dataPointerShared() const
{
    return SharedMem<uint8_t*>::unshared(getSlot(DATA_SLOT).toPrivate());
}

void
ArrayBufferObject::releaseData(FreeOp* fop)
{
    MOZ_ASSERT(ownsData());

    switch (bufferKind()) {
      case PLAIN:
        fop->free_(dataPointer());
        break;
      case MAPPED:
        DeallocateMappedContent(dataPointer(), byteLength());
        break;
      case WASM:
        WasmArrayRawBuffer::Release(dataPointer());
        break;
      case KIND_MASK:
        MOZ_CRASH("bad bufferKind()");
    }
}

void
ArrayBufferObject::setDataPointer(BufferContents contents, OwnsState ownsData)
{
    setSlot(DATA_SLOT, PrivateValue(contents.data()));
    setOwnsData(ownsData);
    setFlags((flags() & ~KIND_MASK) | contents.kind());
}

uint32_t
ArrayBufferObject::byteLength() const
{
    return getSlot(BYTE_LENGTH_SLOT).toInt32();
}

void
ArrayBufferObject::setByteLength(uint32_t length)
{
    MOZ_ASSERT(length <= INT32_MAX);
    setSlot(BYTE_LENGTH_SLOT, Int32Value(length));
}

size_t
ArrayBufferObject::wasmMappedSize() const
{
    if (isWasm())
        return contents().wasmBuffer()->mappedSize();
    return byteLength();
}

size_t
js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf)
{
    if (buf->is<ArrayBufferObject>())
        return buf->as<ArrayBufferObject>().wasmMappedSize();
#ifdef WASM_HUGE_MEMORY
    return wasm::HugeMappedSize;
#else
    return buf->as<SharedArrayBufferObject>().byteLength();
#endif
}

Maybe<uint32_t>
ArrayBufferObject::wasmMaxSize() const
{
    if (isWasm())
        return contents().wasmBuffer()->maxSize();
    else
        return Some<uint32_t>(byteLength());
}

Maybe<uint32_t>
js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
{
    if (buf->is<ArrayBufferObject>())
        return buf->as<ArrayBufferObject>().wasmMaxSize();

    return Some(buf->as<SharedArrayBufferObject>().byteLength());
}

/* static */ bool
ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize,
                                         HandleArrayBufferObject oldBuf,
                                         MutableHandleArrayBufferObject newBuf,
                                         JSContext* cx)
{
    // On failure, do not throw and ensure that the original buffer is
    // unmodified and valid. After WasmArrayRawBuffer::growToSizeInPlace(), the
    // wasm-visible length of the buffer has been increased so it must be the
    // last fallible operation.

    // byteLength can be at most INT32_MAX. Note: if this hard limit changes,
    // update the clamping behavior in wasm::DecodeMemoryLimits and remove this
    // comment as well as the one in wasmMovingGrowToSize.
    if (newSize > INT32_MAX)
        return false;

    newBuf.set(ArrayBufferObject::createEmpty(cx));
    if (!newBuf) {
        cx->clearPendingException();
        return false;
    }

    if (!oldBuf->contents().wasmBuffer()->growToSizeInPlace(oldBuf->byteLength(), newSize))
        return false;

    bool hasStealableContents = true;
    BufferContents contents = ArrayBufferObject::stealContents(cx, oldBuf, hasStealableContents);
    MOZ_ASSERT(contents);
    newBuf->initialize(newSize, contents, OwnsData);
    return true;
}

#ifndef WASM_HUGE_MEMORY
/* static */ bool
ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize,
                                        HandleArrayBufferObject oldBuf,
                                        MutableHandleArrayBufferObject newBuf,
                                        JSContext* cx)
{
    // On failure, do not throw and ensure that the original buffer is
    // unmodified and valid.

    // byteLength can be at most INT32_MAX.
    // See comment in wasmGrowToSizeInPlace about wasm::DecodeMemoryLimits.
    if (newSize > INT32_MAX)
        return false;

    if (newSize <= oldBuf->wasmBoundsCheckLimit() ||
        oldBuf->contents().wasmBuffer()->extendMappedSize(newSize))
    {
        return wasmGrowToSizeInPlace(newSize, oldBuf, newBuf, cx);
    }

    newBuf.set(ArrayBufferObject::createEmpty(cx));
    if (!newBuf) {
        cx->clearPendingException();
        return false;
    }

    WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::Allocate(newSize, Nothing());
    if (!newRawBuf)
        return false;
    BufferContents contents = BufferContents::create<WASM>(newRawBuf->dataPointer());
    newBuf->initialize(newSize, contents, OwnsData);

    memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
    ArrayBufferObject::detach(cx, oldBuf, BufferContents::createPlain(nullptr));
    return true;
}

uint32_t
ArrayBufferObject::wasmBoundsCheckLimit() const
{
    if (isWasm())
        return contents().wasmBuffer()->boundsCheckLimit();
    else
        return byteLength();
}

uint32_t
ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
{
    if (is<ArrayBufferObject>())
        return as<ArrayBufferObject>().wasmBoundsCheckLimit();

    return as<SharedArrayBufferObject>().byteLength();
}
#endif

uint32_t
ArrayBufferObject::flags() const
{
    return uint32_t(getSlot(FLAGS_SLOT).toInt32());
}

void
ArrayBufferObject::setFlags(uint32_t flags)
{
    setSlot(FLAGS_SLOT, Int32Value(flags));
}

ArrayBufferObject*
ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
                          OwnsState ownsState /* = OwnsData */,
                          HandleObject proto /* = nullptr */,
                          NewObjectKind newKind /* = GenericObject */)
{
    MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);

    // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
    // Refuse to allocate too large buffers, currently limited to ~2 GiB.
    if (nbytes > INT32_MAX) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
        return nullptr;
    }

    // If we need to allocate data, try to use a larger object size class so
    // that the array buffer's data can be allocated inline with the object.
    // The extra space will be left unused by the object's fixed slots and
    // available for the buffer's data, see NewObject().
    size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);

    size_t nslots = reservedSlots;
    bool allocated = false;
    if (contents) {
        if (ownsState == OwnsData) {
            // The ABO is taking ownership, so account the bytes against the zone.
            size_t nAllocated = nbytes;
            if (contents.kind() == MAPPED)
                nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
            else if (contents.kind() == WASM)
                nAllocated = contents.wasmBuffer()->allocatedBytes();
            cx->zone()->updateMallocCounter(nAllocated);
        }
    } else {
        MOZ_ASSERT(ownsState == OwnsData);
        size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
        if (nbytes <= usableSlots * sizeof(Value)) {
            int newSlots = JS_HOWMANY(nbytes, sizeof(Value));
            MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
            nslots = reservedSlots + newSlots;
            contents = BufferContents::createPlain(nullptr);
        } else {
            contents = AllocateArrayBufferContents(cx, nbytes);
            if (!contents)
                return nullptr;
            allocated = true;
        }
    }

    MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
    gc::AllocKind allocKind = GetGCObjectKind(nslots);

    AutoSetNewObjectMetadata metadata(cx);
    Rooted<ArrayBufferObject*> obj(cx,
        NewObjectWithClassProto<ArrayBufferObject>(cx, proto, allocKind, newKind));
    if (!obj) {
        if (allocated)
            js_free(contents.data());
        return nullptr;
    }

    MOZ_ASSERT(obj->getClass() == &class_);
    MOZ_ASSERT(!gc::IsInsideNursery(obj));

    if (!contents) {
        void* data = obj->inlineDataPointer();
        memset(data, 0, nbytes);
        obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
    } else {
        obj->initialize(nbytes, contents, ownsState);
    }

    return obj;
}

ArrayBufferObject*
ArrayBufferObject::create(JSContext* cx, uint32_t nbytes,
                          HandleObject proto /* = nullptr */,
                          NewObjectKind newKind /* = GenericObject */)
{
    return create(cx, nbytes, BufferContents::createPlain(nullptr),
                  OwnsState::OwnsData, proto);
}

ArrayBufferObject*
ArrayBufferObject::createEmpty(JSContext* cx)
{
    AutoSetNewObjectMetadata metadata(cx);
    ArrayBufferObject* obj = NewObjectWithClassProto<ArrayBufferObject>(cx, nullptr);
    if (!obj)
        return nullptr;

    obj->initEmpty();
    return obj;
}

/* static */ ArrayBufferObject::BufferContents
ArrayBufferObject::externalizeContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
                                       bool hasStealableContents)
{
    MOZ_ASSERT(buffer->isPlain(), "Only support doing this on plain ABOs");
    MOZ_ASSERT(!buffer->isDetached(), "must have contents to externalize");
    MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());

    BufferContents contents(buffer->dataPointer(), buffer->bufferKind());

    if (hasStealableContents) {
        buffer->setOwnsData(DoesntOwnData);
        return contents;
    }

    // Create a new chunk of memory to return since we cannot steal the
    // existing contents away from the buffer.
    BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength());
    if (!newContents)
        return BufferContents::createPlain(nullptr);
    memcpy(newContents.data(), contents.data(), buffer->byteLength());
    buffer->changeContents(cx, newContents, DoesntOwnData);

    return newContents;
}

/* static */ ArrayBufferObject::BufferContents
ArrayBufferObject::stealContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
                                 bool hasStealableContents)
{
    // While wasm buffers cannot generally be transferred by content, the
    // stealContents() is used internally by the impl of memory growth.
    MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents() ||
                                        (buffer->isWasm() && !buffer->isPreparedForAsmJS()));
    assertSameCompartment(cx, buffer);

    BufferContents oldContents(buffer->dataPointer(), buffer->bufferKind());

    if (hasStealableContents) {
        // Return the old contents and reset the detached buffer's data
        // pointer. This pointer should never be accessed.
        auto newContents = BufferContents::createPlain(nullptr);
        buffer->setOwnsData(DoesntOwnData); // Do not free the stolen data.
        ArrayBufferObject::detach(cx, buffer, newContents);
        buffer->setOwnsData(DoesntOwnData); // Do not free the nullptr.
        return oldContents;
    }

    // Create a new chunk of memory to return since we cannot steal the
    // existing contents away from the buffer.
    BufferContents contentsCopy = AllocateArrayBufferContents(cx, buffer->byteLength());
    if (!contentsCopy)
        return BufferContents::createPlain(nullptr);

    if (buffer->byteLength() > 0)
        memcpy(contentsCopy.data(), oldContents.data(), buffer->byteLength());
    ArrayBufferObject::detach(cx, buffer, oldContents);
    return contentsCopy;
}

/* static */ void
ArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
                                          JS::ClassInfo* info)
{
    ArrayBufferObject& buffer = AsArrayBuffer(obj);

    if (!buffer.ownsData())
        return;

    switch (buffer.bufferKind()) {
      case PLAIN:
        if (buffer.isPreparedForAsmJS())
            info->objectsMallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer());
        else
            info->objectsMallocHeapElementsNormal += mallocSizeOf(buffer.dataPointer());
        break;
      case MAPPED:
        info->objectsNonHeapElementsNormal += buffer.byteLength();
        break;
      case WASM:
        info->objectsNonHeapElementsWasm += buffer.byteLength();
        MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength());
        info->wasmGuardPages += buffer.wasmMappedSize() - buffer.byteLength();
        break;
      case KIND_MASK:
        MOZ_CRASH("bad bufferKind()");
    }
}

/* static */ void
ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
{
    ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();

    if (buffer.ownsData())
        buffer.releaseData(fop);
}

/* static */ void
ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
                            Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
                            uint32_t count)
{
    MOZ_ASSERT(toBuffer->byteLength() >= count);
    MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
    MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
    MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);

    memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
}

/* static */ void
ArrayBufferObject::trace(JSTracer* trc, JSObject* obj)
{
    // If this buffer is associated with an inline typed object,
    // fix up the data pointer if the typed object was moved.
    ArrayBufferObject& buf = obj->as<ArrayBufferObject>();

    if (!buf.forInlineTypedObject())
        return;

    JSObject* view = MaybeForwarded(buf.firstView());
    MOZ_ASSERT(view && view->is<InlineTransparentTypedObject>());

    TraceManuallyBarrieredEdge(trc, &view, "array buffer inline typed object owner");
    buf.setSlot(DATA_SLOT, PrivateValue(view->as<InlineTransparentTypedObject>().inlineTypedMem()));
}

/* static */ size_t
ArrayBufferObject::objectMoved(JSObject* obj, JSObject* old)
{
    ArrayBufferObject& dst = obj->as<ArrayBufferObject>();
    const ArrayBufferObject& src = old->as<ArrayBufferObject>();

    // Fix up possible inline data pointer.
    if (src.hasInlineData())
        dst.setSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));

    return 0;
}

ArrayBufferViewObject*
ArrayBufferObject::firstView()
{
    return getSlot(FIRST_VIEW_SLOT).isObject()
        ? static_cast<ArrayBufferViewObject*>(&getSlot(FIRST_VIEW_SLOT).toObject())
        : nullptr;
}

void
ArrayBufferObject::setFirstView(ArrayBufferViewObject* view)
{
    setSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
}

bool
ArrayBufferObject::addView(JSContext* cx, JSObject* viewArg)
{
    // Note: we don't pass in an ArrayBufferViewObject as the argument due to
    // tricky inheritance in the various view classes. View classes do not
    // inherit from ArrayBufferViewObject so won't be upcast automatically.
    MOZ_ASSERT(viewArg->is<ArrayBufferViewObject>() || viewArg->is<TypedObject>());
    ArrayBufferViewObject* view = static_cast<ArrayBufferViewObject*>(viewArg);

    if (!firstView()) {
        setFirstView(view);
        return true;
    }
    return cx->compartment()->innerViews.get().addView(cx, this, view);
}

/*
 * InnerViewTable
 */

static size_t VIEW_LIST_MAX_LENGTH = 500;

bool
InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, ArrayBufferViewObject* view)
{
    // ArrayBufferObject entries are only added when there are multiple views.
    MOZ_ASSERT(buffer->firstView());

    if (!map.initialized() && !map.init()) {
        ReportOutOfMemory(cx);
        return false;
    }

    Map::AddPtr p = map.lookupForAdd(buffer);

    MOZ_ASSERT(!gc::IsInsideNursery(buffer));
    bool addToNursery = nurseryKeysValid && gc::IsInsideNursery(view);

    if (p) {
        ViewVector& views = p->value();
        MOZ_ASSERT(!views.empty());

        if (addToNursery) {
            // Only add the entry to |nurseryKeys| if it isn't already there.
            if (views.length() >= VIEW_LIST_MAX_LENGTH) {
                // To avoid quadratic blowup, skip the loop below if we end up
                // adding enormous numbers of views for the same object.
                nurseryKeysValid = false;
            } else {
                for (size_t i = 0; i < views.length(); i++) {
                    if (gc::IsInsideNursery(views[i])) {
                        addToNursery = false;
                        break;
                    }
                }
            }
        }

        if (!views.append(view)) {
            ReportOutOfMemory(cx);
            return false;
        }
    } else {
        if (!map.add(p, buffer, ViewVector())) {
            ReportOutOfMemory(cx);
            return false;
        }
        // ViewVector has one inline element, so the first insertion is
        // guaranteed to succeed.
        MOZ_ALWAYS_TRUE(p->value().append(view));
    }

    if (addToNursery && !nurseryKeys.append(buffer))
        nurseryKeysValid = false;

    return true;
}

InnerViewTable::ViewVector*
InnerViewTable::maybeViewsUnbarriered(ArrayBufferObject* buffer)
{
    if (!map.initialized())
        return nullptr;

    Map::Ptr p = map.lookup(buffer);
    if (p)
        return &p->value();
    return nullptr;
}

void
InnerViewTable::removeViews(ArrayBufferObject* buffer)
{
    Map::Ptr p = map.lookup(buffer);
    MOZ_ASSERT(p);

    map.remove(p);
}

/* static */ bool
InnerViewTable::sweepEntry(JSObject** pkey, ViewVector& views)
{
    if (IsAboutToBeFinalizedUnbarriered(pkey))
        return true;

    MOZ_ASSERT(!views.empty());
    for (size_t i = 0; i < views.length(); i++) {
        if (IsAboutToBeFinalizedUnbarriered(&views[i])) {
            views[i--] = views.back();
            views.popBack();
        }
    }

    return views.empty();
}

void
InnerViewTable::sweep()
{
    MOZ_ASSERT(nurseryKeys.empty());
    map.sweep();
}

void
InnerViewTable::sweepAfterMinorGC()
{
    MOZ_ASSERT(needsSweepAfterMinorGC());

    if (nurseryKeysValid) {
        for (size_t i = 0; i < nurseryKeys.length(); i++) {
            JSObject* buffer = MaybeForwarded(nurseryKeys[i]);
            Map::Ptr p = map.lookup(buffer);
            if (!p)
                continue;

            if (sweepEntry(&p->mutableKey(), p->value()))
                map.remove(buffer);
        }
        nurseryKeys.clear();
    } else {
        // Do the required sweeping by looking at every map entry.
        nurseryKeys.clear();
        sweep();

        nurseryKeysValid = true;
    }
}

size_t
InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
{
    if (!map.initialized())
        return 0;

    size_t vectorSize = 0;
    for (Map::Enum e(map); !e.empty(); e.popFront())
        vectorSize += e.front().value().sizeOfExcludingThis(mallocSizeOf);

    return vectorSize
         + map.sizeOfExcludingThis(mallocSizeOf)
         + nurseryKeys.sizeOfExcludingThis(mallocSizeOf);
}

/*
 * ArrayBufferViewObject
 */

/*
 * This method is used to trace TypedArrayObjects and DataViewObjects. We need
 * a custom tracer to move the object's data pointer if its owner was moved and
 * stores its data inline.
 */
/* static */ void
ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
{
    NativeObject* obj = &objArg->as<NativeObject>();
    HeapSlot& bufSlot = obj->getFixedSlotRef(TypedArrayObject::BUFFER_SLOT);
    TraceEdge(trc, &bufSlot, "typedarray.buffer");

    // Update obj's data pointer if it moved.
    if (bufSlot.isObject()) {
        if (IsArrayBuffer(&bufSlot.toObject())) {
            ArrayBufferObject& buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject()));
            uint32_t offset = uint32_t(obj->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT).toInt32());
            MOZ_ASSERT(offset <= INT32_MAX);

            if (buf.forInlineTypedObject()) {
                MOZ_ASSERT(buf.dataPointer() != nullptr);

                // The data is inline with an InlineTypedObject associated with the
                // buffer. Get a new address for the typed object if it moved.
                JSObject* view = buf.firstView();

                // Mark the object to move it into the tenured space.
                TraceManuallyBarrieredEdge(trc, &view, "typed array nursery owner");
                MOZ_ASSERT(view->is<InlineTypedObject>());
                MOZ_ASSERT(view != obj);

                void* srcData = obj->getPrivate();
                void* dstData = view->as<InlineTypedObject>().inlineTypedMemForGC() + offset;
                obj->setPrivateUnbarriered(dstData);

                // We can't use a direct forwarding pointer here, as there might
                // not be enough bytes available, and other views might have data
                // pointers whose forwarding pointers would overlap this one.
                if (trc->isTenuringTracer()) {
                    Nursery& nursery = obj->zoneFromAnyThread()->group()->nursery();
                    nursery.maybeSetForwardingPointer(trc, srcData, dstData, /* direct = */ false);
                }
            } else {
                MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);

                // The data may or may not be inline with the buffer. The buffer
                // can only move during a compacting GC, in which case its
                // objectMoved hook has already updated the buffer's data pointer.
                obj->initPrivate(buf.dataPointer() + offset);
            }
        }
    }
}

template <>
bool
JSObject::is<js::ArrayBufferViewObject>() const
{
    return is<DataViewObject>() || is<TypedArrayObject>();
}

template <>
bool
JSObject::is<js::ArrayBufferObjectMaybeShared>() const
{
    return is<ArrayBufferObject>() || is<SharedArrayBufferObject>();
}

void
ArrayBufferViewObject::notifyBufferDetached(JSContext* cx, void* newData)
{
    if (is<DataViewObject>()) {
        if (as<DataViewObject>().isSharedMemory())
            return;
        as<DataViewObject>().notifyBufferDetached(newData);
    } else if (is<TypedArrayObject>()) {
        if (as<TypedArrayObject>().isSharedMemory())
            return;
        as<TypedArrayObject>().notifyBufferDetached(cx, newData);
    } else {
        as<OutlineTypedObject>().notifyBufferDetached(newData);
    }
}

uint8_t*
ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
{
    if (is<DataViewObject>()) {
        MOZ_ASSERT(!as<DataViewObject>().isSharedMemory());
        return static_cast<uint8_t*>(as<DataViewObject>().dataPointerUnshared());
    }
    if (is<TypedArrayObject>()) {
        MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
        return static_cast<uint8_t*>(as<TypedArrayObject>().viewDataUnshared());
    }
    return as<TypedObject>().typedMem(nogc);
}

#ifdef DEBUG
bool
ArrayBufferViewObject::isSharedMemory()
{
    if (is<TypedArrayObject>())
        return as<TypedArrayObject>().isSharedMemory();
    return false;
}
#endif

void
ArrayBufferViewObject::setDataPointerUnshared(uint8_t* data)
{
    if (is<DataViewObject>()) {
        MOZ_ASSERT(!as<DataViewObject>().isSharedMemory());
        as<DataViewObject>().setPrivate(data);
    } else if (is<TypedArrayObject>()) {
        MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
        as<TypedArrayObject>().setPrivate(data);
    } else if (is<OutlineTypedObject>()) {
        as<OutlineTypedObject>().setData(data);
    } else {
        MOZ_CRASH();
    }
}

/* static */ ArrayBufferObjectMaybeShared*
ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*> thisObject)
{
    if (thisObject->is<TypedArrayObject>()) {
        Rooted<TypedArrayObject*> typedArray(cx, &thisObject->as<TypedArrayObject>());
        if (!TypedArrayObject::ensureHasBuffer(cx, typedArray))
            return nullptr;
        return thisObject->as<TypedArrayObject>().bufferEither();
    }
    MOZ_ASSERT(thisObject->is<DataViewObject>());
    return &thisObject->as<DataViewObject>().arrayBufferEither();
}

/* JS Friend API */

JS_FRIEND_API(bool)
JS_IsArrayBufferViewObject(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    return obj && obj->is<ArrayBufferViewObject>();
}

JS_FRIEND_API(JSObject*)
js::UnwrapArrayBufferView(JSObject* obj)
{
    if (JSObject* unwrapped = CheckedUnwrap(obj))
        return unwrapped->is<ArrayBufferViewObject>() ? unwrapped : nullptr;
    return nullptr;
}

JS_FRIEND_API(uint32_t)
JS_GetArrayBufferByteLength(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    return obj ? AsArrayBuffer(obj).byteLength() : 0;
}

JS_FRIEND_API(uint8_t*)
JS_GetArrayBufferData(JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&)
{
    obj = CheckedUnwrap(obj);
    if (!obj)
        return nullptr;
    if (!IsArrayBuffer(obj))
        return nullptr;
    *isSharedMemory = false;
    return AsArrayBuffer(obj).dataPointer();
}

JS_FRIEND_API(bool)
JS_DetachArrayBuffer(JSContext* cx, HandleObject obj)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    assertSameCompartment(cx, obj);

    if (!obj->is<ArrayBufferObject>()) {
        JS_ReportErrorASCII(cx, "ArrayBuffer object required");
        return false;
    }

    Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());

    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
        return false;
    }

    ArrayBufferObject::BufferContents newContents =
        buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
                                       : buffer->contents();

    ArrayBufferObject::detach(cx, buffer, newContents);

    return true;
}

JS_FRIEND_API(bool)
JS_IsDetachedArrayBufferObject(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    if (!obj)
        return false;

    return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isDetached();
}

JS_FRIEND_API(JSObject*)
JS_NewArrayBuffer(JSContext* cx, uint32_t nbytes)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    MOZ_ASSERT(nbytes <= INT32_MAX);
    return ArrayBufferObject::create(cx, nbytes);
}

JS_PUBLIC_API(JSObject*)
JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    MOZ_ASSERT_IF(!data, nbytes == 0);
    ArrayBufferObject::BufferContents contents =
        ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
                                     /* proto = */ nullptr, TenuredObject);
}

JS_PUBLIC_API(JSObject*)
JS_NewArrayBufferWithExternalContents(JSContext* cx, size_t nbytes, void* data)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    MOZ_ASSERT_IF(!data, nbytes == 0);
    ArrayBufferObject::BufferContents contents =
        ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData,
                                     /* proto = */ nullptr, TenuredObject);
}

JS_FRIEND_API(bool)
JS_IsArrayBufferObject(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    return obj && obj->is<ArrayBufferObject>();
}

JS_FRIEND_API(bool)
JS_ArrayBufferHasData(JSObject* obj)
{
    return CheckedUnwrap(obj)->as<ArrayBufferObject>().hasData();
}

JS_FRIEND_API(JSObject*)
js::UnwrapArrayBuffer(JSObject* obj)
{
    if (JSObject* unwrapped = CheckedUnwrap(obj))
        return unwrapped->is<ArrayBufferObject>() ? unwrapped : nullptr;
    return nullptr;
}

JS_FRIEND_API(JSObject*)
js::UnwrapSharedArrayBuffer(JSObject* obj)
{
    if (JSObject* unwrapped = CheckedUnwrap(obj))
        return unwrapped->is<SharedArrayBufferObject>() ? unwrapped : nullptr;
    return nullptr;
}

JS_PUBLIC_API(void*)
JS_ExternalizeArrayBufferContents(JSContext* cx, HandleObject obj)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    assertSameCompartment(cx, obj);

    if (!obj->is<ArrayBufferObject>()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
        return nullptr;
    }

    Handle<ArrayBufferObject*> buffer = obj.as<ArrayBufferObject>();
    if (!buffer->isPlain()) {
        // This operation isn't supported on mapped or wsm ArrayBufferObjects.
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
        return nullptr;
    }
    if (buffer->isDetached()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
        return nullptr;
    }

    // The caller assumes that a plain malloc'd buffer is returned.
    // hasStealableContents is true for mapped buffers, so we must additionally
    // require that the buffer is plain. In the future, we could consider
    // returning something that handles releasing the memory.
    bool hasStealableContents = buffer->hasStealableContents();

    return ArrayBufferObject::externalizeContents(cx, buffer, hasStealableContents).data();
}

JS_PUBLIC_API(void*)
JS_StealArrayBufferContents(JSContext* cx, HandleObject objArg)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    assertSameCompartment(cx, objArg);

    JSObject* obj = CheckedUnwrap(objArg);
    if (!obj)
        return nullptr;

    if (!obj->is<ArrayBufferObject>()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
        return nullptr;
    }

    Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
    if (buffer->isDetached()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
        return nullptr;
    }

    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
        return nullptr;
    }

    // The caller assumes that a plain malloc'd buffer is returned.
    // hasStealableContents is true for mapped buffers, so we must additionally
    // require that the buffer is plain. In the future, we could consider
    // returning something that handles releasing the memory.
    bool hasStealableContents = buffer->hasStealableContents() && buffer->isPlain();

    AutoCompartment ac(cx, buffer);
    return ArrayBufferObject::stealContents(cx, buffer, hasStealableContents).data();
}

JS_PUBLIC_API(JSObject*)
JS_NewMappedArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);

    MOZ_ASSERT(data);
    ArrayBufferObject::BufferContents contents =
        ArrayBufferObject::BufferContents::create<ArrayBufferObject::MAPPED>(data);
    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
                                     /* proto = */ nullptr, TenuredObject);
}

JS_PUBLIC_API(void*)
JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
{
    return ArrayBufferObject::createMappedContents(fd, offset, length).data();
}

JS_PUBLIC_API(void)
JS_ReleaseMappedArrayBufferContents(void* contents, size_t length)
{
    DeallocateMappedContent(contents, length);
}

JS_FRIEND_API(bool)
JS_IsMappedArrayBufferObject(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    if (!obj)
        return false;

    return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isMapped();
}

JS_FRIEND_API(void*)
JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&)
{
    obj = CheckedUnwrap(obj);
    if (!obj)
        return nullptr;
    if (obj->is<DataViewObject>()) {
        DataViewObject& dv = obj->as<DataViewObject>();
        *isSharedMemory = dv.isSharedMemory();
        return dv.dataPointerEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
    }
    TypedArrayObject& ta = obj->as<TypedArrayObject>();
    *isSharedMemory = ta.isSharedMemory();
    return ta.viewDataEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
}

JS_FRIEND_API(JSObject*)
JS_GetArrayBufferViewBuffer(JSContext* cx, HandleObject objArg, bool* isSharedMemory)
{
    AssertHeapIsIdle();
    CHECK_REQUEST(cx);
    assertSameCompartment(cx, objArg);

    JSObject* obj = CheckedUnwrap(objArg);
    if (!obj)
        return nullptr;
    MOZ_ASSERT(obj->is<ArrayBufferViewObject>());

    Rooted<ArrayBufferViewObject*> viewObject(cx, static_cast<ArrayBufferViewObject*>(obj));
    ArrayBufferObjectMaybeShared* buffer = ArrayBufferViewObject::bufferObject(cx, viewObject);
    *isSharedMemory = buffer->is<SharedArrayBufferObject>();
    return buffer;
}

JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteLength(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    if (!obj)
        return 0;
    return obj->is<DataViewObject>()
           ? obj->as<DataViewObject>().byteLength()
           : obj->as<TypedArrayObject>().byteLength();
}

JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteOffset(JSObject* obj)
{
    obj = CheckedUnwrap(obj);
    if (!obj)
        return 0;
    return obj->is<DataViewObject>()
           ? obj->as<DataViewObject>().byteOffset()
           : obj->as<TypedArrayObject>().byteOffset();
}

JS_FRIEND_API(JSObject*)
JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
{
    if (!(obj = CheckedUnwrap(obj)))
        return nullptr;
    if (!(obj->is<ArrayBufferViewObject>()))
        return nullptr;

    js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data);
    return obj;
}

JS_FRIEND_API(void)
js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory,
                                    uint8_t** data)
{
    MOZ_ASSERT(obj->is<ArrayBufferViewObject>());

    *length = obj->is<DataViewObject>()
              ? obj->as<DataViewObject>().byteLength()
              : obj->as<TypedArrayObject>().byteLength();

    if (obj->is<DataViewObject>()) {
        DataViewObject& dv = obj->as<DataViewObject>();
        *isSharedMemory = dv.isSharedMemory();
        *data = static_cast<uint8_t*>(
            dv.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/));
    }
    else {
        TypedArrayObject& ta = obj->as<TypedArrayObject>();
        *isSharedMemory = ta.isSharedMemory();
        *data = static_cast<uint8_t*>(
            ta.viewDataEither().unwrap(/*safe - caller sees isShared flag*/));
    }
}

JS_FRIEND_API(JSObject*)
JS_GetObjectAsArrayBuffer(JSObject* obj, uint32_t* length, uint8_t** data)
{
    if (!(obj = CheckedUnwrap(obj)))
        return nullptr;
    if (!IsArrayBuffer(obj))
        return nullptr;

    *length = AsArrayBuffer(obj).byteLength();
    *data = AsArrayBuffer(obj).dataPointer();

    return obj;
}

JS_FRIEND_API(void)
js::GetArrayBufferLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
{
    MOZ_ASSERT(IsArrayBuffer(obj));
    *length = AsArrayBuffer(obj).byteLength();
    *data = AsArrayBuffer(obj).dataPointer();
    *isSharedMemory = false;
}
