/*
 * Copyright (C) 2008-2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "CLoopStack.h"

#if ENABLE(C_LOOP)

#include "CLoopStackInlines.h"
#include "ConservativeRoots.h"
#include "Interpreter.h"
#include "JSCInlines.h"
#include "Options.h"
#include <wtf/Lock.h>
#include <Ultralight/private/util/MemoryTag.h>
#include <Ultralight/private/tracy/Tracy.hpp>

namespace JSC {

static size_t committedBytesCount = 0;

static size_t commitSize()
{
    static size_t size = std::max<size_t>(16 * 1024, pageSize());
    return size;
}

static Lock stackStatisticsMutex;

CLoopStack::CLoopStack(VM& vm)
    : m_vm(vm)
    , m_topCallFrame(vm.topCallFrame)
    , m_softReservedZoneSizeInRegisters(0)
    , m_trackingAlloc(false)
{
    size_t capacity = Options::maxPerThreadStackUsage();
    capacity = WTF::roundUpToMultipleOf(pageSize(), capacity);
    ASSERT(capacity && isPageAligned(capacity));

    m_reservation = PageReservation::reserve(WTF::roundUpToMultipleOf(commitSize(), capacity), OSAllocator::UnknownUsage);

    auto* bottomOfStack = highAddress();
    setCLoopStackLimit(bottomOfStack);
    ASSERT(m_end == bottomOfStack);
    m_commitTop = bottomOfStack;
    m_lastStackPointer = bottomOfStack;
    m_currentStackPointer = bottomOfStack;

    m_topCallFrame = 0;
}

CLoopStack::~CLoopStack()
{
    ptrdiff_t sizeToDecommit = reinterpret_cast<char*>(highAddress()) - reinterpret_cast<char*>(m_commitTop);
    m_reservation.decommit(reinterpret_cast<void*>(m_commitTop), sizeToDecommit);
    addToCommittedByteCount(-sizeToDecommit);

    if (m_TrackingAlloc)
        ProfileFree(reservationTop(), MemoryTagToString(MemoryTag::JavaScript));

    m_reservation.deallocate();
}

bool CLoopStack::grow(Register* newTopOfStack)
{
    Register* newTopOfStackWithReservedZone = newTopOfStack - m_softReservedZoneSizeInRegisters;

    // If we have already committed enough memory to satisfy this request,
    // just update the end pointer and return.
    if (newTopOfStackWithReservedZone >= m_commitTop) {
        setCLoopStackLimit(newTopOfStack);
        return true;
    }

    // Compute the chunk size of additional memory to commit, and see if we
    // have it still within our budget. If not, we'll fail to grow and
    // return false.
    ptrdiff_t delta = reinterpret_cast<char*>(m_commitTop) - reinterpret_cast<char*>(newTopOfStackWithReservedZone);
    delta = WTF::roundUpToMultipleOf(commitSize(), delta);
    Register* newCommitTop = m_commitTop - (delta / sizeof(Register));
    if (newCommitTop < reservationTop())
        return false;

    // Otherwise, the growth is still within our budget. Commit it and return true.
    m_reservation.commit(newCommitTop, delta);
    addToCommittedByteCount(delta);
    m_commitTop = newCommitTop;
    newTopOfStack = m_commitTop + m_softReservedZoneSizeInRegisters;
    setCLoopStackLimit(newTopOfStack);
    return true;
}

void CLoopStack::gatherConservativeRoots(ConservativeRoots& conservativeRoots, JITStubRoutineSet& jitStubRoutines, CodeBlockSet& codeBlocks)
{
    conservativeRoots.add(currentStackPointer(), highAddress(), jitStubRoutines, codeBlocks);
}

void CLoopStack::sanitizeStack()
{
#if !ASAN_ENABLED
    void* stackTop = currentStackPointer();
    ASSERT(stackTop <= highAddress());
    if (m_lastStackPointer < stackTop) {
        char* begin = reinterpret_cast<char*>(m_lastStackPointer);
        char* end = reinterpret_cast<char*>(stackTop);
        memset(begin, 0, end - begin);
    }
    
    m_lastStackPointer = stackTop;
#endif
}

void CLoopStack::releaseExcessCapacity()
{
    Register* highAddressWithReservedZone = highAddress() - m_softReservedZoneSizeInRegisters;
    ptrdiff_t delta = reinterpret_cast<char*>(highAddressWithReservedZone) - reinterpret_cast<char*>(m_commitTop);
    m_reservation.decommit(m_commitTop, delta);
    addToCommittedByteCount(-delta);
    m_commitTop = highAddressWithReservedZone;
}

void CLoopStack::addToCommittedByteCount(long byteCount)
{
    LockHolder locker(stackStatisticsMutex);
    ASSERT(static_cast<long>(committedBytesCount) + byteCount > -1);
    committedBytesCount += byteCount;

    // Memory profiler doesn't support resizing allocations so we'll just spoof it by using a free/alloc.
    if (m_TrackingAlloc) {
        ProfileFree(reservationTop(), MemoryTagToString(MemoryTag::JavaScript));
        m_TrackingAlloc = false;
    }
    
    if (committedBytesCount > 0) {
        ProfileAlloc(reservationTop(), committedBytesCount, MemoryTagToString(MemoryTag::JavaScript))
        m_TrackingAlloc = true;
    }
}

void CLoopStack::setSoftReservedZoneSize(size_t reservedZoneSize)
{
    m_softReservedZoneSizeInRegisters = reservedZoneSize / sizeof(Register);
    if (m_commitTop > m_end - m_softReservedZoneSizeInRegisters)
        grow(m_end);
}

bool CLoopStack::isSafeToRecurse() const
{
    void* reservationLimit = reinterpret_cast<int8_t*>(reservationTop() + m_softReservedZoneSizeInRegisters);
    return !m_topCallFrame || (m_topCallFrame->topOfFrame() > reservationLimit);
}

size_t CLoopStack::committedByteCount()
{
    LockHolder locker(stackStatisticsMutex);
    return committedBytesCount;
}

} // namespace JSC

#endif // ENABLE(C_LOOP)
