/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 *
 * Copyright 2016 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "wasm/WasmTable.h"

#include "mozilla/CheckedInt.h"

#include "jscntxt.h"

#include "wasm/WasmInstance.h"
#include "wasm/WasmJS.h"

using namespace js;
using namespace js::wasm;
using mozilla::CheckedInt;

Table::Table(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject,
             UniqueByteArray array)
  : maybeObject_(maybeObject),
    observers_(cx->zone(), InstanceSet()),
    array_(Move(array)),
    kind_(desc.kind),
    length_(desc.limits.initial),
    maximum_(desc.limits.maximum),
    external_(desc.external)
{}

/* static */ SharedTable
Table::create(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject)
{
    // The raw element type of a Table depends on whether it is external: an
    // external table can contain functions from multiple instances and thus
    // must store an additional instance pointer in each element.
    UniqueByteArray array;
    if (desc.external)
        array.reset((uint8_t*)cx->pod_calloc<ExternalTableElem>(desc.limits.initial));
    else
        array.reset((uint8_t*)cx->pod_calloc<void*>(desc.limits.initial));
    if (!array)
        return nullptr;

    return SharedTable(cx->new_<Table>(cx, desc, maybeObject, Move(array)));
}

void
Table::tracePrivate(JSTracer* trc)
{
    // If this table has a WasmTableObject, then this method is only called by
    // WasmTableObject's trace hook so maybeObject_ must already be marked.
    // TraceEdge is called so that the pointer can be updated during a moving
    // GC. TraceWeakEdge may sound better, but it is less efficient given that
    // we know object_ is already marked.
    if (maybeObject_) {
        MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
        TraceEdge(trc, &maybeObject_, "wasm table object");
    }

    if (external_) {
        ExternalTableElem* array = externalArray();
        for (uint32_t i = 0; i < length_; i++) {
            if (array[i].tls)
                array[i].tls->instance->trace(trc);
            else
                MOZ_ASSERT(!array[i].code);
        }
    }
}

void
Table::trace(JSTracer* trc)
{
    // The trace hook of WasmTableObject will call Table::tracePrivate at
    // which point we can mark the rest of the children. If there is no
    // WasmTableObject, call Table::tracePrivate directly. Redirecting through
    // the WasmTableObject avoids marking the entire Table on each incoming
    // edge (once per dependent Instance).
    if (maybeObject_)
        TraceEdge(trc, &maybeObject_, "wasm table object");
    else
        tracePrivate(trc);
}

void**
Table::internalArray() const
{
    MOZ_ASSERT(!external_);
    return (void**)array_.get();
}

ExternalTableElem*
Table::externalArray() const
{
    MOZ_ASSERT(external_);
    return (ExternalTableElem*)array_.get();
}

void
Table::set(uint32_t index, void* code, Instance& instance)
{
    if (external_) {
        ExternalTableElem& elem = externalArray()[index];
        if (elem.tls)
            JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());

        elem.code = code;
        elem.tls = &instance.tlsData();

        MOZ_ASSERT(elem.tls->instance->objectUnbarriered()->isTenured(), "no writeBarrierPost");
    } else {
        internalArray()[index] = code;
    }
}

void
Table::setNull(uint32_t index)
{
    // Only external tables can set elements to null after initialization.
    ExternalTableElem& elem = externalArray()[index];
    if (elem.tls)
        JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());

    elem.code = nullptr;
    elem.tls = nullptr;
}

uint32_t
Table::grow(uint32_t delta, JSContext* cx)
{
    // This isn't just an optimization: movingGrowable() assumes that
    // onMovingGrowTable does not fire when length == maximum.
    if (!delta)
        return length_;

    uint32_t oldLength = length_;

    CheckedInt<uint32_t> newLength = oldLength;
    newLength += delta;
    if (!newLength.isValid())
        return -1;

    if (maximum_ && newLength.value() > maximum_.value())
        return -1;

    MOZ_ASSERT(movingGrowable());

    JSRuntime* rt = cx;  // Use JSRuntime's MallocProvider to avoid throwing.

    // Note that realloc does not release array_'s pointee (which is returned by
    // externalArray()) on failure which is exactly what we need here.
    ExternalTableElem* newArray = rt->pod_realloc(externalArray(), length_, newLength.value());
    if (!newArray)
        return -1;
    Unused << array_.release();
    array_.reset((uint8_t*)newArray);

    // Realloc does not zero the delta for us.
    PodZero(newArray + length_, delta);
    length_ = newLength.value();

    if (observers_.initialized()) {
        for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront())
            r.front()->instance().onMovingGrowTable();
    }

    return oldLength;
}

bool
Table::movingGrowable() const
{
    return !maximum_ || length_ < maximum_.value();
}

bool
Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
{
    MOZ_ASSERT(movingGrowable());

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

    if (!observers_.putNew(instance)) {
        ReportOutOfMemory(cx);
        return false;
    }

    return true;
}

size_t
Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
    return mallocSizeOf(array_.get());
}