/*
 * Copyright (C) 2008 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 INC. OR
 * 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 "CodeBlock.h"
#include "ExecutableAllocator.h"
#include "JSGlobalData.h"
#include "JSValueInlines.h"

#if ENABLE(EXECUTABLE_ALLOCATOR_DEMAND)
#include "CodeProfiling.h"
#include <wtf/DoublyLinkedList.h>
#include <wtf/HashSet.h>
#include <wtf/MetaAllocator.h>
#if ENABLE(ASSEMBLER_WX_EXCLUSIVE)
#include <wtf/PassOwnPtr.h>
#endif
#include <wtf/ThreadingPrimitives.h>
#include <wtf/VMTags.h>
#endif
#include <wtf/MemoryMonitor.h>

#if ENABLE(ASSEMBLER)

using namespace WTF;

namespace JSC {

#if ENABLE(EXECUTABLE_ALLOCATOR_DEMAND)

static const unsigned jitAllocationGranule = 8;

static const unsigned int DefaultJITCodeCap = (32 * MB);

static unsigned int sJITCodeCap = DefaultJITCodeCap;

static int sTotalReservedBytes;
static int sPeakReservedBytes;

static const size_t JIT_ALLOCATOR_LARGE_ALLOC_PAGES = 16;

static int targetRequestedNumberOfPages(int numPages)
{
	if(numPages < JIT_ALLOCATOR_LARGE_ALLOC_PAGES) {
		size_t newNumPages = (numPages + JIT_ALLOCATOR_LARGE_ALLOC_PAGES - 1) / JIT_ALLOCATOR_LARGE_ALLOC_PAGES * JIT_ALLOCATOR_LARGE_ALLOC_PAGES;
		if(newNumPages > numPages)
			numPages = newNumPages;
	}

	return numPages;
}

static int requestedNumberOfPages(size_t sizeInBytes)
{
	sizeInBytes = (sizeInBytes + jitAllocationGranule - 1) & ~(jitAllocationGranule - 1);
	size_t pageSize = WTF::pageSize();
    return targetRequestedNumberOfPages((sizeInBytes + pageSize - 1) / pageSize);
}

class DemandExecutableAllocator : public MetaAllocator {
	WTF_MAKE_FAST_ALLOCATED;

	friend class DoublyLinkedList<DemandExecutableAllocator>;

	struct Reservation
	{
		Reservation()
		: base(0)
		, length(0)
		{
		}

		Reservation(void* p, size_t len)
		: base(p)
		, length(len)
		{
		}

		void* base;
		size_t length;
	};

    void setPrev(DemandExecutableAllocator* prev) { m_prev = prev; }
    void setNext(DemandExecutableAllocator* next) { m_next = next; }

public:
    DemandExecutableAllocator* prev() const { return m_prev; }
    DemandExecutableAllocator* next() const { return m_next; }

    DemandExecutableAllocator(bool isBaselineJIT)
    : MetaAllocator(jitAllocationGranule)
	, m_fixedStart(0)
	, m_fixedLen(0)
	, m_next(0)
	, m_prev(0)
    , m_isBaselineJIT(isBaselineJIT)
    {
    }

    DemandExecutableAllocator(void *start, int len, bool isBaselineJIT)
    : MetaAllocator(jitAllocationGranule)
    , m_fixedStart(start)
    , m_fixedLen(len)
	, m_next(0)
	, m_prev(0)
    , m_isBaselineJIT(isBaselineJIT)
    {
    }
    
    virtual ~DemandExecutableAllocator()
    {
    	sTotalReservedBytes -= bytesReserved();

    	if(m_fixedStart)
    		return;

		fastAlignedFree(m_reservation.base, m_reservation.length, true);
    }

    bool isBaselineJIT() const { return m_isBaselineJIT; }

    size_t bytesReservedByAllAllocators()
    {
        return bytesReserved();
    }

    size_t bytesCommittedByAllocactors()
    {
        return bytesCommitted();
    }

    int fixedLen() const { return m_fixedLen; }

    ALWAYS_INLINE PassRefPtr<MetaAllocatorHandle> allocate(size_t sizeInBytes, void* ownerUID)
	{
    	return MetaAllocator::allocate(sizeInBytes, ownerUID);
	}

#if ENABLE(META_ALLOCATOR_PROFILE)
    static void dumpProfileFromAllAllocators()
    {
        MutexLocker lock(allocatorsMutex());
        for (HashSet<DemandExecutableAllocator*>::const_iterator allocator = allocators().begin(); allocator != allocators().end(); ++allocator)
            (*allocator)->dumpProfile();
    }
#endif

protected:
    virtual void* allocateNewSpace(size_t& numPages)
    {
    	if(m_reservation.base)
    		return 0;

        void* start;
        size_t len;
        size_t pageSize = WTF::pageSize();

    	if(m_fixedStart) {
    		start = m_fixedStart;
    		len = m_fixedLen;
    		numPages = m_fixedLen / pageSize;
    	}
    	else {
			numPages  = targetRequestedNumberOfPages(numPages);
			len = numPages * pageSize;
			start = fastPageAlignedMalloc(pageSize, len, true);
    	}

		sTotalReservedBytes += len;

		if(sTotalReservedBytes > sPeakReservedBytes)
			sPeakReservedBytes = sTotalReservedBytes;

		m_reservation = Reservation(start, len);

        return start;
    }
    
    virtual void notifyNeedPage(void* page)
    {
    }
    
    virtual void notifyPageIsFree(void* page)
    {
    }

private:
    void* m_fixedStart;
    int m_fixedLen;
    Reservation m_reservation;
    DemandExecutableAllocator* m_next;
    DemandExecutableAllocator* m_prev;
    bool m_isBaselineJIT;
};

#if ENABLE(ASSEMBLER_WX_EXCLUSIVE)
void ExecutableAllocator::initializeAllocator()
{
}
#else
static DoublyLinkedList<DemandExecutableAllocator> gFixedAllocators;
static DoublyLinkedList<DemandExecutableAllocator> gSmallAllocators;
static DoublyLinkedList<DemandExecutableAllocator> gLargeAllocators;

void ExecutableAllocator::initializeAllocator()
{
}
#endif

ExecutableAllocator::ExecutableAllocator(JSGlobalData&)
#if ENABLE(ASSEMBLER_WX_EXCLUSIVE)
    : m_allocator(adoptPtr(new  DemandExecutableAllocator()))
#endif
{
    ASSERT(allocator());
}

ExecutableAllocator::~ExecutableAllocator()
{
}

bool ExecutableAllocator::isValid() const
{
    return true;
}

bool ExecutableAllocator::underMemoryPressure()
{
    return sTotalReservedBytes > sJITCodeCap;
}

double ExecutableAllocator::memoryPressureMultiplier(size_t addedMemoryUsage)
{
    UNUSED_PARAM(addedMemoryUsage);
    return 1.0f;
}

void gcExecutableAllocators(bool freeUnusedMemory)
{
	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);

	int targetReservedBytes = 0;
	double jitCodeAllocatorGCPercentage = Options::jitCodeAllocatorGCPercentage();

	if(jitCodeAllocatorGCPercentage < 1.0) {
		if(sTotalReservedBytes > sJITCodeCap)
			targetReservedBytes = sJITCodeCap * (1.0 - jitCodeAllocatorGCPercentage);
		else
			targetReservedBytes = sTotalReservedBytes * (1.0 - jitCodeAllocatorGCPercentage);
	}
	else
		freeUnusedMemory = true;

	DemandExecutableAllocator* cur = gSmallAllocators.head();

	while(cur && (freeUnusedMemory || sTotalReservedBytes > targetReservedBytes)){
		DemandExecutableAllocator* next = cur->next();

		if(cur->bytesAllocated() == 0){
			gSmallAllocators.remove(cur);
			delete cur;
		}

		cur = next;
	}
}

PassRefPtr<ExecutableMemoryHandle> ExecutableAllocator::allocate(JSGlobalData& globalData, size_t sizeInBytes, void* ownerUID, JITCompilationEffort effort, bool isBaselineJIT)
{
	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);

    for(DemandExecutableAllocator* cur = gFixedAllocators.head(); cur; cur = cur->next()) {
		if(RefPtr<ExecutableMemoryHandle> result = cur->DemandExecutableAllocator::allocate(sizeInBytes, ownerUID))
			return result.release();
	}

	size_t pageSize = WTF::pageSize();

	unsigned jitCodeAllocatorGranularityBytes = JIT_ALLOCATOR_LARGE_ALLOC_PAGES * pageSize;
	size_t requiredSizeInBytes = requestedNumberOfPages(sizeInBytes) * pageSize;

	DemandExecutableAllocator* cur = gSmallAllocators.head();
	while(cur) {
		DemandExecutableAllocator* next = cur->next();

		// do not share large block
		if( cur->isBaselineJIT() == isBaselineJIT &&
			(cur->bytesReserved() <= jitCodeAllocatorGranularityBytes) &&
			(cur->bytesReserved() - cur->bytesAllocated()) >= sizeInBytes) {
			if(RefPtr<ExecutableMemoryHandle> result = cur->DemandExecutableAllocator::allocate(sizeInBytes, ownerUID))
				return result.release();
		}

		if(cur->bytesAllocated() == 0 && (sTotalReservedBytes + requiredSizeInBytes) > sJITCodeCap) {
			gSmallAllocators.remove(cur);
			delete cur;
		}

		cur = next;
	}

	DemandExecutableAllocator* newAllocator = new DemandExecutableAllocator(isBaselineJIT);
	RefPtr<ExecutableMemoryHandle> result = newAllocator->DemandExecutableAllocator::allocate(sizeInBytes, ownerUID);

	if(newAllocator->bytesReserved() > jitCodeAllocatorGranularityBytes)
		gLargeAllocators.append(newAllocator);
	else
		gSmallAllocators.append(newAllocator);

	return result;
}

size_t ExecutableAllocator::committedByteCount()
{
	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);

	size_t committed = 0;

    for(DemandExecutableAllocator* cur = gFixedAllocators.head(); cur; cur = cur->next())
		committed += cur->bytesCommitted();

    for(DemandExecutableAllocator* cur = gSmallAllocators.head(); cur; cur = cur->next())
		committed += cur->bytesCommitted();

    for(DemandExecutableAllocator* cur = gLargeAllocators.head(); cur; cur = cur->next())
		committed += cur->bytesCommitted();

    return committed;
}

DLL_PUBLIC void setJITCodeMemoryLimit(unsigned int cap)
{
	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);

	if(cap > 0)
		sJITCodeCap = cap;
	else
		sJITCodeCap = DefaultJITCodeCap;

	sPeakReservedBytes = 0;

	int fixedLen = 0;

	for(DemandExecutableAllocator* cur = gFixedAllocators.head(); cur; cur = cur->next()) {
		sPeakReservedBytes += cur->bytesReserved();
		fixedLen += cur->fixedLen();
	}

	if(sJITCodeCap < fixedLen)
		sJITCodeCap = fixedLen;

	for(DemandExecutableAllocator* cur = gSmallAllocators.head(); cur; cur = cur->next())
		sPeakReservedBytes += cur->bytesReserved();

	for(DemandExecutableAllocator* cur = gLargeAllocators.head(); cur; cur = cur->next())
		sPeakReservedBytes += cur->bytesReserved();
}

DLL_PUBLIC void addJITCodeMem(void *ptr, int size)
{
	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);

	gFixedAllocators.append(new DemandExecutableAllocator(ptr, size, true));

	int fixedLen = 0;

	for(DemandExecutableAllocator* cur = gFixedAllocators.head(); cur; cur = cur->next())
		fixedLen += cur->fixedLen();

	if(sJITCodeCap < fixedLen)
		sJITCodeCap = fixedLen;
}

DLL_PUBLIC int jitCodeCapBytes()
{
	return sJITCodeCap;
}

DLL_PUBLIC int jitCodeReservedBytes()
{
	return sTotalReservedBytes;
}

DLL_PUBLIC int jitCodePeakReservedBytes()
{
	return sPeakReservedBytes;
}

DLL_PUBLIC void jitCodeBytes(unsigned int& allocated, unsigned int& reserved, unsigned int& committed, unsigned int& totalBlocks)
{
	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);

	allocated = 0;
	reserved  = 0;
	committed = 0;
	totalBlocks = 0;

    for(DemandExecutableAllocator* cur = gFixedAllocators.head(); cur; cur = cur->next()) {
		allocated += cur->bytesAllocated();
		reserved  += cur->bytesReserved();
		committed += cur->bytesCommitted();
		++totalBlocks;
	}

	for(DemandExecutableAllocator* cur = gSmallAllocators.head(); cur; cur = cur->next()) {
		allocated += cur->bytesAllocated();
		reserved  += cur->bytesReserved();
		committed += cur->bytesCommitted();
		++totalBlocks;
	}

	for(DemandExecutableAllocator* cur = gLargeAllocators.head(); cur; cur = cur->next()) {
		allocated += cur->bytesAllocated();
		reserved  += cur->bytesReserved();
		committed += cur->bytesCommitted();
		++totalBlocks;
	}
}

#if ENABLE(META_ALLOCATOR_PROFILE)
void ExecutableAllocator::dumpProfile()
{
    DemandExecutableAllocator::dumpProfileFromAllAllocators();
}
#endif

#endif // ENABLE(EXECUTABLE_ALLOCATOR_DEMAND)

#if ENABLE(ASSEMBLER_WX_EXCLUSIVE)

#if OS(WINDOWS)
#error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform."
#endif

void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
{
    size_t pageSize = WTF::pageSize();

    // Calculate the start of the page containing this region,
    // and account for this extra memory within size.
    intptr_t startPtr = reinterpret_cast<intptr_t>(start);
    intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
    void* pageStart = reinterpret_cast<void*>(pageStartPtr);
    size += (startPtr - pageStartPtr);

    // Round size up
    size += (pageSize - 1);
    size &= ~(pageSize - 1);

    mprotect(pageStart, size, (setting == Writable) ? PROTECTION_FLAGS_RW : PROTECTION_FLAGS_RX);
}

#endif

}

namespace WTF
{

MetaAllocatorHandle::~MetaAllocatorHandle()
{
    ASSERT(m_allocator);
    m_allocator->release(this); // holds s_accessLock

	SpinLockHolder holder(&WTF::MetaAllocator::s_accessLock);
    JSC::DemandExecutableAllocator* demandExecutableAllocator = static_cast<JSC::DemandExecutableAllocator*>(m_allocator);

    if(demandExecutableAllocator->bytesAllocated() > 0)
    	return;

	size_t pageSize = WTF::pageSize();
	unsigned jitCodeAllocatorGranularityBytes = JSC::JIT_ALLOCATOR_LARGE_ALLOC_PAGES * pageSize;

	if(demandExecutableAllocator->bytesReserved() > jitCodeAllocatorGranularityBytes) {
		JSC::gLargeAllocators.remove(demandExecutableAllocator);
		delete demandExecutableAllocator;
	}
}

}
#endif // HAVE(ASSEMBLER)
