/* -*- 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/. */

#ifdef JSGC_GENERATIONAL

#include "jsgc.h"

#include "gc/Barrier-inl.h"
#include "gc/StoreBuffer.h"
#include "vm/ForkJoin.h"
#include "vm/ObjectImpl-inl.h"

using namespace js;
using namespace js::gc;

/*** SlotEdge ***/

JS_ALWAYS_INLINE HeapSlot *
StoreBuffer::SlotEdge::slotLocation() const
{
    if (kind == HeapSlot::Element) {
        if (offset >= object->getDenseInitializedLength())
            return NULL;
        return (HeapSlot *)&object->getDenseElement(offset);
    }
    if (offset >= object->slotSpan())
        return NULL;
    return &object->getSlotRef(offset);
}

JS_ALWAYS_INLINE void *
StoreBuffer::SlotEdge::deref() const
{
    HeapSlot *loc = slotLocation();
    return (loc && loc->isGCThing()) ? loc->toGCThing() : NULL;
}

JS_ALWAYS_INLINE void *
StoreBuffer::SlotEdge::location() const
{
    return (void *)slotLocation();
}

JS_ALWAYS_INLINE bool
StoreBuffer::SlotEdge::inRememberedSet(const Nursery &nursery) const
{
    return !nursery.isInside(object) && nursery.isInside(deref());
}

JS_ALWAYS_INLINE bool
StoreBuffer::SlotEdge::isNullEdge() const
{
    return !deref();
}

void
StoreBuffer::WholeCellEdges::mark(JSTracer *trc)
{
    JSGCTraceKind kind = GetGCThingTraceKind(tenured);
    if (kind <= JSTRACE_OBJECT) {
        MarkChildren(trc, static_cast<JSObject *>(tenured));
        return;
    }
    JS_ASSERT(kind == JSTRACE_IONCODE);
    static_cast<jit::IonCode *>(tenured)->trace(trc);
}

/*** MonoTypeBuffer ***/

/* How full we allow a store buffer to become before we request a MinorGC. */
const static double HighwaterRatio = 7.0 / 8.0;

template <typename T>
bool
StoreBuffer::MonoTypeBuffer<T>::enable(uint8_t *region, size_t len)
{
    JS_ASSERT(len % sizeof(T) == 0);
    base = pos = reinterpret_cast<T *>(region);
    top = reinterpret_cast<T *>(region + len);
    highwater = reinterpret_cast<T *>(region + size_t(double(len) * HighwaterRatio));
    JS_ASSERT(highwater > base);
    JS_ASSERT(highwater < top);
    return true;
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::disable()
{
    base = pos = top = highwater = NULL;
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::clear()
{
    pos = base;
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::compactNotInSet(const Nursery &nursery)
{
    T *insert = base;
    for (T *v = base; v != pos; ++v) {
        if (v->inRememberedSet(nursery))
            *insert++ = *v;
    }
    pos = insert;
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::compactRemoveDuplicates()
{
    JS_ASSERT(duplicates.empty());

    T *insert = base;
    for (T *v = base; v != pos; ++v) {
        if (!duplicates.has(v->location())) {
            *insert++ = *v;
            /* Failure to insert will leave the set with duplicates. Oh well. */
            duplicates.put(v->location());
        }
    }
    pos = insert;
    duplicates.clear();
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::compact()
{
    compactNotInSet(owner->runtime->gcNursery);
    compactRemoveDuplicates();
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::mark(JSTracer *trc)
{
    compact();
    T *cursor = base;
    while (cursor != pos) {
        T edge = *cursor++;

        if (edge.isNullEdge())
            continue;

        edge.mark(trc);
    }
}

namespace js {
namespace gc {
class AccumulateEdgesTracer : public JSTracer
{
    EdgeSet *edges;

    static void tracer(JSTracer *jstrc, void **thingp, JSGCTraceKind kind) {
        AccumulateEdgesTracer *trc = static_cast<AccumulateEdgesTracer *>(jstrc);
        trc->edges->put(thingp);
    }

  public:
    AccumulateEdgesTracer(JSRuntime *rt, EdgeSet *edgesArg) : edges(edgesArg) {
        JS_TracerInit(this, rt, AccumulateEdgesTracer::tracer);
    }
};
} /* namespace gc */
} /* namespace js */

/*** RelocatableMonoTypeBuffer ***/

template <typename T>
void
StoreBuffer::RelocatableMonoTypeBuffer<T>::compactMoved()
{
    for (T *v = this->base; v != this->pos; ++v) {
        if (v->isTagged()) {
            T match = v->untagged();
            for (T *r = this->base; r != v; ++r) {
                T check = r->untagged();
                if (check == match)
                    *r = NULL;
            }
            *v = NULL;
        }
    }
    T *insert = this->base;
    for (T *cursor = this->base; cursor != this->pos; ++cursor) {
        if (*cursor != NULL)
            *insert++ = *cursor;
    }
    this->pos = insert;
#ifdef DEBUG
    for (T *cursor = this->base; cursor != this->pos; ++cursor)
        JS_ASSERT(!cursor->isTagged());
#endif
}

template <typename T>
void
StoreBuffer::RelocatableMonoTypeBuffer<T>::compact()
{
    compactMoved();
    StoreBuffer::MonoTypeBuffer<T>::compact();
}

/*** GenericBuffer ***/

bool
StoreBuffer::GenericBuffer::enable(uint8_t *region, size_t len)
{
    base = pos = region;
    top = region + len;
    return true;
}

void
StoreBuffer::GenericBuffer::disable()
{
    base = pos = top = NULL;
}

void
StoreBuffer::GenericBuffer::clear()
{
    pos = base;
}

void
StoreBuffer::GenericBuffer::mark(JSTracer *trc)
{
    uint8_t *p = base;
    while (p < pos) {
        unsigned size = *((unsigned *)p);
        p += sizeof(unsigned);

        BufferableRef *edge = reinterpret_cast<BufferableRef *>(p);
        edge->mark(trc);

        p += size;
    }
}

/*** Edges ***/

void
StoreBuffer::CellPtrEdge::mark(JSTracer *trc)
{
    MarkObjectRoot(trc, reinterpret_cast<JSObject**>(edge), "store buffer edge");
}

void
StoreBuffer::ValueEdge::mark(JSTracer *trc)
{
    MarkValueRoot(trc, edge, "store buffer edge");
}

void
StoreBuffer::SlotEdge::mark(JSTracer *trc)
{
    if (kind == HeapSlot::Element)
        MarkSlot(trc, (HeapSlot*)&object->getDenseElement(offset), "store buffer edge");
    else
        MarkSlot(trc, &object->getSlotRef(offset), "store buffer edge");
}

/*** StoreBuffer ***/

bool
StoreBuffer::enable()
{
    if (enabled)
        return true;

    buffer = js_malloc(TotalSize);
    if (!buffer)
        return false;

    /* Initialize the individual edge buffers in sub-regions. */
    uint8_t *asBytes = static_cast<uint8_t *>(buffer);
    size_t offset = 0;

    if (!bufferVal.enable(&asBytes[offset], ValueBufferSize))
        return false;
    offset += ValueBufferSize;

    if (!bufferCell.enable(&asBytes[offset], CellBufferSize))
        return false;
    offset += CellBufferSize;

    if (!bufferSlot.enable(&asBytes[offset], SlotBufferSize))
        return false;
    offset += SlotBufferSize;

    if (!bufferWholeCell.enable(&asBytes[offset], WholeCellBufferSize))
        return false;
    offset += WholeCellBufferSize;

    if (!bufferRelocVal.enable(&asBytes[offset], RelocValueBufferSize))
        return false;
    offset += RelocValueBufferSize;

    if (!bufferRelocCell.enable(&asBytes[offset], RelocCellBufferSize))
        return false;
    offset += RelocCellBufferSize;

    if (!bufferGeneric.enable(&asBytes[offset], GenericBufferSize))
        return false;
    offset += GenericBufferSize;

    JS_ASSERT(offset == TotalSize);

    enabled = true;
    return true;
}

void
StoreBuffer::disable()
{
    if (!enabled)
        return;

    aboutToOverflow = false;

    bufferVal.disable();
    bufferCell.disable();
    bufferSlot.disable();
    bufferWholeCell.disable();
    bufferRelocVal.disable();
    bufferRelocCell.disable();
    bufferGeneric.disable();

    js_free(buffer);
    enabled = false;
    overflowed = false;
}

bool
StoreBuffer::clear()
{
    if (!enabled)
        return true;

    aboutToOverflow = false;

    bufferVal.clear();
    bufferCell.clear();
    bufferSlot.clear();
    bufferWholeCell.clear();
    bufferRelocVal.clear();
    bufferRelocCell.clear();
    bufferGeneric.clear();

    return true;
}

void
StoreBuffer::mark(JSTracer *trc)
{
    JS_ASSERT(isEnabled());
    JS_ASSERT(!overflowed);

    bufferVal.mark(trc);
    bufferCell.mark(trc);
    bufferSlot.mark(trc);
    bufferWholeCell.mark(trc);
    bufferRelocVal.mark(trc);
    bufferRelocCell.mark(trc);
    bufferGeneric.mark(trc);
}

void
StoreBuffer::setAboutToOverflow()
{
    aboutToOverflow = true;
    runtime->triggerOperationCallback();
}

void
StoreBuffer::setOverflowed()
{
    JS_ASSERT(enabled);
    overflowed = true;
}

bool
StoreBuffer::inParallelSection() const
{
    return InParallelSection();
}

JS_PUBLIC_API(void)
JS::HeapCellPostBarrier(js::gc::Cell **cellp)
{
    JS_ASSERT(*cellp);
    JSRuntime *runtime = (*cellp)->runtime();
    runtime->gcStoreBuffer.putRelocatableCell(cellp);
}

JS_PUBLIC_API(void)
JS::HeapCellRelocate(js::gc::Cell **cellp)
{
    /* Called with old contents of *pp before overwriting. */
    JS_ASSERT(*cellp);
    JSRuntime *runtime = (*cellp)->runtime();
    runtime->gcStoreBuffer.removeRelocatableCell(cellp);
}

JS_PUBLIC_API(void)
JS::HeapValuePostBarrier(JS::Value *valuep)
{
    JS_ASSERT(JSVAL_IS_TRACEABLE(*valuep));
    JSRuntime *runtime = static_cast<js::gc::Cell *>(valuep->toGCThing())->runtime();
    runtime->gcStoreBuffer.putRelocatableValue(valuep);
}

JS_PUBLIC_API(void)
JS::HeapValueRelocate(JS::Value *valuep)
{
    /* Called with old contents of *valuep before overwriting. */
    JS_ASSERT(JSVAL_IS_TRACEABLE(*valuep));
    JSRuntime *runtime = static_cast<js::gc::Cell *>(valuep->toGCThing())->runtime();
    runtime->gcStoreBuffer.removeRelocatableValue(valuep);
}

template class StoreBuffer::MonoTypeBuffer<StoreBuffer::ValueEdge>;
template class StoreBuffer::MonoTypeBuffer<StoreBuffer::CellPtrEdge>;
template class StoreBuffer::MonoTypeBuffer<StoreBuffer::SlotEdge>;
template class StoreBuffer::MonoTypeBuffer<StoreBuffer::WholeCellEdges>;
template class StoreBuffer::RelocatableMonoTypeBuffer<StoreBuffer::ValueEdge>;
template class StoreBuffer::RelocatableMonoTypeBuffer<StoreBuffer::CellPtrEdge>;

#endif /* JSGC_GENERATIONAL */
