/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sw=4 et tw=78:
 */
/* 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/ObjectImpl-inl.h"

using namespace js::gc;

/*** MonoTypeBuffer ***/

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);
    return true;
}

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

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::compactNotInSet()
{
    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>::compact()
{
    compactNotInSet();
}

template <typename T>
void
StoreBuffer::MonoTypeBuffer<T>::put(const T &v)
{
    /* Check if we have been enabled. */
    if (!pos)
        return;

    /*
     * Note: it is sometimes valid for a put to happen in the middle of a GC,
     * e.g. a rekey of a Relocatable may end up here. In general, we do not
     * care about these new entries or any overflows they cause.
     */
    *pos++ = v;
    if (isFull()) {
        owner->setOverflowed();
        pos = base;
    }
}

template <typename T>
bool
StoreBuffer::MonoTypeBuffer<T>::accumulateEdges(EdgeSet &edges)
{
    compact();
    T *cursor = base;
    while (cursor != pos) {
        T edge = *cursor++;

        /* Note: the relocatable buffer is allowed to store pointers to NULL. */
        if (edge.isNullEdge())
            continue;
        if (!edges.putNew(edge.location()))
            return false;
    }
    return true;
}

/*** 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();
}

template <typename T>
void
StoreBuffer::RelocatableMonoTypeBuffer<T>::unput(const T &v)
{
    put(v.tagged());
}

/*** 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;
}

bool
StoreBuffer::GenericBuffer::containsEdge(void *location) const
{
    uint8_t *p = base;
    while (p < pos) {
        unsigned size = *((unsigned *)p);
        p += sizeof(unsigned);

        if (((BufferableRef *)p)->match(location))
            return true;

        p += size;
    }
    return false;
}

/*** StoreBuffer ***/

bool
StoreBuffer::enable()
{
    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 (!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()
{
    bufferVal.disable();
    bufferCell.disable();
    bufferSlot.disable();
    bufferRelocVal.disable();
    bufferRelocCell.disable();
    bufferGeneric.disable();

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

bool
StoreBuffer::coalesceForVerification()
{
    if (!edgeSet.initialized()) {
        if (!edgeSet.init())
            return false;
    }
    JS_ASSERT(edgeSet.empty());
    if (!bufferVal.accumulateEdges(edgeSet))
        return false;
    if (!bufferCell.accumulateEdges(edgeSet))
        return false;
    if (!bufferSlot.accumulateEdges(edgeSet))
        return false;
    if (!bufferRelocVal.accumulateEdges(edgeSet))
        return false;
    if (!bufferRelocCell.accumulateEdges(edgeSet))
        return false;
    return true;
}

bool
StoreBuffer::containsEdgeAt(void *loc) const
{
    return edgeSet.has(loc) || bufferGeneric.containsEdge(loc);
}

void
StoreBuffer::releaseVerificationData()
{
    edgeSet.finish();
}

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

#endif /* JSGC_GENERATIONAL */
