/* -*- 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 "ds/LifoAlloc.h"

#include "mozilla/MathAlgorithms.h"

using namespace js;

using mozilla::RoundUpPow2;
using mozilla::tl::BitSize;

namespace js {
namespace detail {

BumpChunk*
BumpChunk::new_(size_t chunkSize)
{
    MOZ_ASSERT(RoundUpPow2(chunkSize) == chunkSize);
    void* mem = js_malloc(chunkSize);
    if (!mem)
        return nullptr;
    BumpChunk* result = new (mem) BumpChunk(chunkSize - sizeof(BumpChunk));

    // We assume that the alignment of sAlign is less than that of
    // the underlying memory allocator -- creating a new BumpChunk should
    // always satisfy the sAlign alignment constraint.
    MOZ_ASSERT(AlignPtr(result->bump) == result->bump);
    return result;
}

void
BumpChunk::delete_(BumpChunk* chunk)
{
#ifdef DEBUG
    // Part of the chunk may have been marked as poisoned/noaccess.  Undo that
    // before writing the 0xcd bytes.
    size_t size = sizeof(*chunk) + chunk->bumpSpaceSize;
    MOZ_MAKE_MEM_UNDEFINED(chunk, size);
    memset(chunk, 0xcd, size);
#endif
    js_free(chunk);
}

bool
BumpChunk::canAlloc(size_t n)
{
    char* aligned = AlignPtr(bump);
    char* bumped = aligned + n;
    return bumped <= limit && bumped > headerBase();
}

} // namespace detail
} // namespace js

void
LifoAlloc::freeAll()
{
    while (first) {
        BumpChunk* victim = first;
        first = first->next();
        decrementCurSize(victim->computedSizeOfIncludingThis());
        BumpChunk::delete_(victim);
    }
    first = latest = last = nullptr;

    // Nb: maintaining curSize_ correctly isn't easy.  Fortunately, this is an
    // excellent sanity check.
    MOZ_ASSERT(curSize_ == 0);
}

LifoAlloc::BumpChunk*
LifoAlloc::getOrCreateChunk(size_t n)
{
    if (first) {
        // Look for existing, unused BumpChunks to satisfy the request.
        while (latest->next()) {
            latest = latest->next();
            latest->resetBump();    // This was an unused BumpChunk on the chain.
            if (latest->canAlloc(n))
                return latest;
        }
    }

    size_t defaultChunkFreeSpace = defaultChunkSize_ - sizeof(BumpChunk);
    size_t chunkSize;
    if (n > defaultChunkFreeSpace) {
        size_t allocSizeWithHeader = n + sizeof(BumpChunk);

        // Guard for overflow.
        if (allocSizeWithHeader < n ||
            (allocSizeWithHeader & (size_t(1) << (BitSize<size_t>::value - 1)))) {
            return nullptr;
        }

        chunkSize = RoundUpPow2(allocSizeWithHeader);
    } else {
        chunkSize = defaultChunkSize_;
    }

    // If we get here, we couldn't find an existing BumpChunk to fill the request.
    MOZ_ASSERT(fallibleScope_, "[OOM] Cannot allocate a new chunk in an infallible scope.");
    BumpChunk* newChunk = BumpChunk::new_(chunkSize);
    if (!newChunk)
        return nullptr;
    if (!first) {
        latest = first = last = newChunk;
    } else {
        MOZ_ASSERT(latest && !latest->next());
        latest->setNext(newChunk);
        latest = last = newChunk;
    }

    size_t computedChunkSize = newChunk->computedSizeOfIncludingThis();
    MOZ_ASSERT(computedChunkSize == chunkSize);
    incrementCurSize(computedChunkSize);

    return newChunk;
}

void
LifoAlloc::transferFrom(LifoAlloc* other)
{
    MOZ_ASSERT(!markCount);
    MOZ_ASSERT(!other->markCount);

    if (!other->first)
        return;

    incrementCurSize(other->curSize_);
    if (other->isEmpty())
        appendUnused(other->first, other->last);
    else
        appendUsed(other->first, other->latest, other->last);
    other->first = other->last = other->latest = nullptr;
    other->curSize_ = 0;
}

void
LifoAlloc::transferUnusedFrom(LifoAlloc* other)
{
    MOZ_ASSERT(!markCount);
    MOZ_ASSERT(latest == first);

    if (other->markCount || !other->first)
        return;

    // Transfer all chunks *after* |latest|.

    if (other->latest->next()) {
        if (other->latest == other->first) {
            // We're transferring everything except the first chunk.
            size_t delta = other->curSize_ - other->first->computedSizeOfIncludingThis();
            other->decrementCurSize(delta);
            incrementCurSize(delta);
        } else {
            for (BumpChunk* chunk = other->latest->next(); chunk; chunk = chunk->next()) {
                size_t size = chunk->computedSizeOfIncludingThis();
                incrementCurSize(size);
                other->decrementCurSize(size);
            }
        }

        appendUnused(other->latest->next(), other->last);
        other->latest->setNext(nullptr);
        other->last = other->latest;
    }
}