/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
 *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifndef Heap_h
#define Heap_h

#include "CodeBlockSet.h"
#include "CopyVisitor.h"
#include "GCIncomingRefCountedSet.h"
#include "GCThreadSharedData.h"
#include "HandleSet.h"
#include "HeapOperation.h"
#include "MarkedAllocator.h"
#include "MarkedBlock.h"
#include "MarkedBlockSet.h"
#include "MarkedSpace.h"
#include "SlotVisitor.h"
#include "WeakHandleOwner.h"
#include "WriteBarrierSupport.h"
#include <wtf/DoublyLinkedList.h>
#include <wtf/HashCountedSet.h>
#include <wtf/HashSet.h>

#define COLLECT_ON_EVERY_ALLOCATION 0

namespace JSC {

class ArrayBuffer;
class CopiedSpace;
class CodeBlock;
class ExecutableBase;
class GCActivityCallback;
class GCAwareJITStubRoutine;
class GlobalCodeBlock;
class Heap;
class HeapRootVisitor;
class IncrementalSweeper;
class JITStubRoutine;
class JITStubRoutineSet;
class JSCell;
class JSGlobalData;
class JSValue;
class LiveObjectIterator;
class LLIntOffsetsExtractor;
class MarkedArgumentBuffer;
class JSStack;
class StructureRareData;
class WeakGCHandlePool;
class WeakGCMapBase;
class SlotVisitor;

static void* const zombifiedBits = reinterpret_cast<void*>(0xdeadbeef);

typedef HashCountedSet<JSCell*> ProtectCountSet;
typedef HashCountedSet<const char*> TypeCountSet;

enum HeapType { SmallHeap, LargeHeap };

class Heap {
	WTF_MAKE_NONCOPYABLE(Heap);
public:
	friend class JIT;
	friend class GCThreadSharedData;
	static Heap* heap(const JSValue); // 0 for immediate values
	static Heap* heap(const JSCell*);

	// This constant determines how many blocks we iterate between checks of our
	// deadline when calling Heap::isPagedOut. Decreasing it will cause us to detect
	// overstepping our deadline more quickly, while increasing it will cause
	// our scan to run faster.
	static const unsigned s_timeCheckResolution = 16;

    static bool isLive(const void*);
	static bool isMarked(const void*);
	static bool testAndSetMarked(const void*);
	static void setMarked(const void*);

	static bool isWriteBarrierEnabled();
    static void writeBarrier(const JSCell*);
	static void writeBarrier(const JSCell*, JSValue);
	static void writeBarrier(const JSCell*, JSCell*);
	static uint8_t* addressOfCardFor(JSCell*);

	Heap(JSGlobalData*, HeapType);
	~Heap();
	JS_EXPORT_PRIVATE void lastChanceToFinalize();

	JSGlobalData* globalData() const { return m_vm; }
	JSGlobalData* vm() const { return m_vm; }
	MarkedSpace& objectSpace() { return m_objectSpace; }
	MachineThreads& machineThreads() { return m_machineThreads; }

#if ENABLE(GC_ACTIVITY_CALLBACK)
	JS_EXPORT_PRIVATE GCActivityCallback* activityCallback();
	JS_EXPORT_PRIVATE void setActivityCallback(GCActivityCallback*);
#endif
	JS_EXPORT_PRIVATE void setGarbageCollectionTimerEnabled(bool);

#if ENABLE(GC_INCREMENTALSWEEPER)
	JS_EXPORT_PRIVATE IncrementalSweeper* sweeper();
#endif

	// true if collection is in progress
	inline bool isCollecting();
    inline HeapOperation operationInProgress() { return m_operationInProgress; }
	// true if an allocation or collection is in progress
	inline bool isBusy();

	MarkedSpace::Subspace& subspaceForObjectWithoutDestructor() { return m_objectSpace.subspaceForObjectsWithoutDestructor(); }
	MarkedSpace::Subspace& subspaceForObjectNormalDestructor() { return m_objectSpace.subspaceForObjectsWithNormalDestructor(); }
	MarkedSpace::Subspace& subspaceForObjectsWithImmortalStructure() { return m_objectSpace.subspaceForObjectsWithImmortalStructure(); }
	MarkedAllocator& allocatorForObjectWithoutDestructor(size_t bytes) { return m_objectSpace.allocatorFor(bytes); }
	MarkedAllocator& allocatorForObjectWithNormalDestructor(size_t bytes) { return m_objectSpace.normalDestructorAllocatorFor(bytes); }
	MarkedAllocator& allocatorForObjectWithImmortalStructureDestructor(size_t bytes) { return m_objectSpace.immortalStructureDestructorAllocatorFor(bytes); }

	CopiedAllocator& storageAllocator() { return m_storageSpace.allocator(); }
	void allocateStorage(size_t, void**);
	CheckedBoolean tryAllocateStorage(size_t, void**);
	CheckedBoolean tryReallocateStorage(void**, size_t, size_t);

	typedef void (*Finalizer)(JSCell*);
	JS_EXPORT_PRIVATE void addFinalizer(JSCell*, Finalizer);
	void addExecutable(ExecutableBase*);

	void notifyIsSafeToCollect() { m_isSafeToCollect = true; }
	bool isSafeToCollect() const { return m_isSafeToCollect; }

	JS_EXPORT_PRIVATE bool collectAllGarbage(bool noStackObjects = false, bool force = false);
	bool shouldCollect();
    void gcTimerDidFire() { m_shouldDoFullCollection = true; }
    void setShouldDoFullCollection(bool shouldDoFullCollection) { m_shouldDoFullCollection = shouldDoFullCollection; }
	bool collect(bool noStackObjects = false, bool force = false);
	bool collectIfNecessaryOrDefer(bool noStackObjects = false, bool force = false); // Returns true if it did collect.

    // Use this API to report non-GC memory referenced by GC objects. Be sure to
    // call both of these functions: Calling only one may trigger catastropic
    // memory growth.
    void reportExtraMemoryAllocated(size_t);
    void reportExtraMemoryVisited(JSCell*, size_t);

    // Use this API to report non-GC memory if you can't use the better API above.
    void deprecatedReportExtraMemory(size_t);

	JS_EXPORT_PRIVATE void reportAbandonedObjectGraph();

	JS_EXPORT_PRIVATE void protect(JSValue);
	JS_EXPORT_PRIVATE bool unprotect(JSValue); // True when the protect count drops to 0.

    size_t extraMemorySize(); // Non-GC memory referenced by GC objects.
	JS_EXPORT_PRIVATE size_t size();
	JS_EXPORT_PRIVATE size_t capacity();
	JS_EXPORT_PRIVATE size_t objectCount();
	JS_EXPORT_PRIVATE size_t globalObjectCount();
	JS_EXPORT_PRIVATE size_t protectedObjectCount();
	JS_EXPORT_PRIVATE size_t protectedGlobalObjectCount();
	JS_EXPORT_PRIVATE PassOwnPtr<TypeCountSet> protectedObjectTypeCounts();
	JS_EXPORT_PRIVATE PassOwnPtr<TypeCountSet> objectTypeCounts();
	void showStatistics();

	HashSet<MarkedArgumentBuffer*>& markListSet() { if (!m_markListSet) m_markListSet = adoptPtr(new HashSet<MarkedArgumentBuffer*>); return *m_markListSet; }

	template<typename Functor> typename Functor::ReturnType forEachProtectedCell(Functor&);
	template<typename Functor> typename Functor::ReturnType forEachProtectedCell();

	HandleSet* handleSet() { return &m_handleSet; }

    void willStartIterating();
    void didFinishIterating();
	void getConservativeRegisterRoots(HashSet<JSCell*>& roots);

	double lastGCLength() { return m_lastGCLength; }
	void increaseLastGCLength(double amount) { m_lastGCLength += amount; }

	bool shouldDeleteCodeBlocks() const;
    JS_EXPORT_PRIVATE void deleteAllCodeBlocks(bool all = true);

	void didAllocate(size_t);
	void didAbandon(size_t);

	void didInstallCode(ExecutableBase*);

	bool isPagedOut(double deadline);
	void didStartVMShutdown();

	void addReference(JSCell*, ArrayBuffer*);

	bool isDeferred() const { return m_deferralDepth; }

    void addLogicallyEmptyWeakBlock(WeakBlock*);

    static bool isZombified(JSCell* cell) { return *(void**)cell == zombifiedBits; }

    void registerWeakGCMap(WeakGCMapBase* weakGCMap)
    {
    	m_weakGCMaps.add(weakGCMap);
    }
    void unregisterWeakGCMap(WeakGCMapBase* weakGCMap)
    {
    	m_weakGCMaps.remove(weakGCMap);
    }

    void registerStructureRareDataWithObjectToStringAdaptiveInferredValueWatchpoint(StructureRareData* structureRareData)
    {
    	m_structureRareDataWithObjectToStringAdaptiveInferredValueWatchpointMap.add(structureRareData);
    }
    void unregisterStructureRareDataWithObjectToStringAdaptiveInferredValueWatchpoint(StructureRareData* structureRareData)
    {
    	m_structureRareDataWithObjectToStringAdaptiveInferredValueWatchpointMap.remove(structureRareData);
    }
    bool containsStructureRareDataWithObjectToStringAdaptiveInferredValueWatchpoint(StructureRareData* structureRareData)
    {
    	return m_structureRareDataWithObjectToStringAdaptiveInferredValueWatchpointMap.contains(structureRareData);
    }

private:
	friend class CodeBlock;
	friend class CopiedBlock;
	friend class DeferGC;
	friend class DeferGCForAWhile;
	friend class GCAwareJITStubRoutine;
	friend class HandleSet;
	friend class JITStubRoutine;
	friend class LLIntOffsetsExtractor;
	friend class MarkedSpace;
	friend class MarkedAllocator;
	friend class MarkedBlock;
	friend class CopiedSpace;
	friend class CopyVisitor;
	friend class SlotVisitor;
	friend class IncrementalSweeper;
	friend class WeakSet;
	template<typename T> friend void* allocateCell(Heap&);
	template<typename T> friend void* allocateCell(Heap&, size_t);

	void* allocateWithImmortalStructureDestructor(size_t); // For use with special objects whose Structures never die.
	void* allocateWithNormalDestructor(size_t); // For use with objects that inherit directly or indirectly from JSDestructibleObject.
	void* allocateWithoutDestructor(size_t); // For use with objects without destructors.

    static const size_t minExtraMemory = 256;

	class FinalizerOwner : public WeakHandleOwner {
		virtual void finalize(Handle<Unknown>, void* context);
	};

	JS_EXPORT_PRIVATE bool isValidAllocation(size_t);
    JS_EXPORT_PRIVATE void reportExtraMemoryAllocatedSlowCase(size_t);
    JS_EXPORT_PRIVATE void deprecatedReportExtraMemorySlowCase(size_t);

    void visitException(HeapRootVisitor&);

    void collectImpl(void* stackOrigin, void* stackTop, MachineThreads::RegisterState&, bool noStackObjects);

	void markRoots(bool fullGC, void* stackOrigin, void* stackTop, MachineThreads::RegisterState&, bool noStackObjects = false);

    void pruneStaleEntriesFromWeakGCMaps();

	void markProtectedObjects(HeapRootVisitor&);
	void copyBackingStores();
	void harvestWeakReferences();
	void finalizeUnconditionalFinalizers();
	void deleteUnmarkedCompiledCode();
	void zombifyDeadObjects();
	void markDeadObjects();

    void sweepAllLogicallyEmptyWeakBlocks();
    bool sweepNextLogicallyEmptyWeakBlock();

	size_t sizeAfterCollect();

	JSStack& registerFile();

	void incrementDeferralDepth();
	void decrementDeferralDepth();
	void decrementDeferralDepthAndGCIfNeeded();

	const HeapType m_heapType;
	const size_t m_ramSize;
	const size_t m_minBytesPerCycle;
	size_t m_sizeAfterLastCollect;

	size_t m_bytesAllocatedLimit;
	size_t m_bytesAllocated;
	size_t m_extraBytesAllocated;
	size_t m_deprecatedExtraBytesAllocated;
	size_t m_bytesAbandoned;

    bool m_shouldDoFullCollection;
	size_t m_totalBytesVisited;
	size_t m_totalBytesCopied;

    size_t m_extraMemorySize;
    size_t m_deprecatedExtraMemorySize;

    HeapOperation m_operationInProgress;
	MarkedSpace m_objectSpace;
	CopiedSpace m_storageSpace;
	GCIncomingRefCountedSet<ArrayBuffer> m_arrayBuffers;

#if ENABLE(SIMPLE_HEAP_PROFILING)
	VTableSpectrum m_destroyedTypeCounts;
#endif

	ProtectCountSet m_protectedValues;
	OwnPtr<HashSet<MarkedArgumentBuffer*> > m_markListSet;

	MachineThreads m_machineThreads;

	GCThreadSharedData m_sharedData;
	SlotVisitor m_slotVisitor;
	CopyVisitor m_copyVisitor;

	HandleSet m_handleSet;
    CodeBlockSet m_codeBlocks;
	OwnPtr<JITStubRoutineSet> m_jitStubRoutines;
	FinalizerOwner m_finalizerOwner;

	bool m_isSafeToCollect;

	JSGlobalData* m_vm;
	double m_lastGCLength;
	int m_lastOpaqueRootCount;

	double m_lastCodeDiscardTime;

	DoublyLinkedList<ExecutableBase> m_executables;

    Vector<WeakBlock*> m_logicallyEmptyWeakBlocks;
    size_t m_indexOfNextLogicallyEmptyWeakBlockToSweep;

#if ENABLE(GC_ACTIVITY_CALLBACK)
	GCActivityCallback* m_activityCallback;
#endif
#if ENABLE(GC_INCREMENTALSWEEPER)
	IncrementalSweeper* m_sweeper;
#endif

	Vector<MarkedBlock*> m_blockSnapshot;

	unsigned m_deferralDepth;

    HashSet<StructureRareData*> m_structureRareDataWithObjectToStringAdaptiveInferredValueWatchpointMap;
    HashSet<WeakGCMapBase*> m_weakGCMaps;
};

struct MarkedBlockSnapshotFunctor : public MarkedBlock::VoidFunctor {
	MarkedBlockSnapshotFunctor(Vector<MarkedBlock*>& blocks)
		: m_index(0)
		, m_blocks(blocks)
	{
	}

	void operator()(MarkedBlock* block) { m_blocks[m_index++] = block; }

	size_t m_index;
	Vector<MarkedBlock*>& m_blocks;
};

ALWAYS_INLINE bool Heap::shouldCollect()
{
	if (isDeferred())
		return false;
#if ENABLE(GGC)
	return m_objectSpace.nurseryWaterMark() >= m_minBytesPerCycle && m_isSafeToCollect && m_operationInProgress == NoOperation;
#else
	return (m_isSafeToCollect && m_operationInProgress == NoOperation && (m_bytesAllocated > m_bytesAllocatedLimit));
#endif
}

ALWAYS_INLINE bool Heap::isCollecting()
{
    return m_operationInProgress == FullCollection || m_operationInProgress == EdenCollection;
}

ALWAYS_INLINE bool Heap::isBusy()
{
	return m_operationInProgress != NoOperation;
}

ALWAYS_INLINE Heap* Heap::heap(const JSCell* cell)
{
	return MarkedBlock::blockFor(cell)->heap();
}

ALWAYS_INLINE Heap* Heap::heap(const JSValue v)
{
	if (LIKELY(v.isCell()))
		return heap(v.asCell());

	return 0;
}

inline bool Heap::isLive(const void* cell)
{
    return MarkedBlock::blockFor(cell)->isLiveCell(cell);
}

ALWAYS_INLINE bool Heap::isMarked(const void* cell)
{
	return MarkedBlock::blockFor(cell)->isMarked(cell);
}

ALWAYS_INLINE bool Heap::testAndSetMarked(const void* cell)
{
	return MarkedBlock::blockFor(cell)->testAndSetMarked(cell);
}

ALWAYS_INLINE void Heap::setMarked(const void* cell)
{
	MarkedBlock::blockFor(cell)->setMarked(cell);
}

ALWAYS_INLINE bool Heap::isWriteBarrierEnabled()
{
#if ENABLE(GGC) || ENABLE(WRITE_BARRIER_PROFILING)
	return true;
#else
	return false;
#endif
}

#if ENABLE(GGC)
inline uint8_t* Heap::addressOfCardFor(JSCell* cell)
{
	return MarkedBlock::blockFor(cell)->addressOfCardFor(cell);
}

inline void Heap::writeBarrier(const JSCell* owner, JSCell*)
{
	WriteBarrierCounters::countWriteBarrier();
	MarkedBlock* block = MarkedBlock::blockFor(owner);
	if (block->isMarked(owner))
		block->setDirtyObject(owner);
}

inline void Heap::writeBarrier(const JSCell* owner, JSValue value)
{
	if (!value)
		return;
	if (!value.isCell())
		return;
	writeBarrier(owner, value.asCell());
}
#else

ALWAYS_INLINE void Heap::writeBarrier(const JSCell*)
{
	WriteBarrierCounters::countWriteBarrier();
}

ALWAYS_INLINE void Heap::writeBarrier(const JSCell*, JSCell*)
{
	WriteBarrierCounters::countWriteBarrier();
}

ALWAYS_INLINE void Heap::writeBarrier(const JSCell*, JSValue)
{
	WriteBarrierCounters::countWriteBarrier();
}
#endif

ALWAYS_INLINE void Heap::reportExtraMemoryAllocated(size_t size)
{
	m_extraBytesAllocated += size;
    if(LIKELY(m_extraBytesAllocated < (32 * KB)))
    	return;
    size_t extraBytesAllocated = m_extraBytesAllocated;
	m_extraBytesAllocated = 0;
	reportExtraMemoryAllocatedSlowCase(extraBytesAllocated);
}

ALWAYS_INLINE void Heap::deprecatedReportExtraMemory(size_t size)
{
	m_deprecatedExtraBytesAllocated += size;
    if(LIKELY(m_deprecatedExtraBytesAllocated < (32 * KB)))
    	return;
    size_t deprecatedExtraBytesAllocated = m_deprecatedExtraBytesAllocated;
    m_deprecatedExtraBytesAllocated = 0;
	deprecatedReportExtraMemorySlowCase(deprecatedExtraBytesAllocated);
}

ALWAYS_INLINE void Heap::reportExtraMemoryVisited(JSCell* owner, size_t size)
{
#if ENABLE(GGC)
    // We don't want to double-count the extra memory that was reported in previous collections.
    if (operationInProgress() == EdenCollection && Heap::isRemembered(owner))
        return;
#else
    UNUSED_PARAM(owner);
#endif

    size_t* counter = &m_extraMemorySize;

    for (;;) {
        size_t oldSize = *counter;
        // FIXME: Change this to use SaturatedArithmetic when available.
		// https://bugs.webkit.org/show_bug.cgi?id=170411
		Checked<size_t, RecordOverflow> checkedNewSize = oldSize;
		checkedNewSize += size;
		size_t newSize = UNLIKELY(checkedNewSize.hasOverflowed()) ? std::numeric_limits<size_t>::max() : checkedNewSize.unsafeGet();
        if (WTF::weakCompareAndSwapSize(counter, oldSize, newSize))
            return;
    }
}

template<typename Functor> inline typename Functor::ReturnType Heap::forEachProtectedCell(Functor& functor)
{
	ProtectCountSet::iterator end = m_protectedValues.end();
	for (ProtectCountSet::iterator it = m_protectedValues.begin(); it != end; ++it)
		functor(it->first);
	m_handleSet.forEachStrongHandle(functor, m_protectedValues);

	return functor.returnValue();
}

template<typename Functor> inline typename Functor::ReturnType Heap::forEachProtectedCell()
{
	Functor functor;
	return forEachProtectedCell(functor);
}

ALWAYS_INLINE void* Heap::allocateWithNormalDestructor(size_t bytes)
{
	ASSERT(isValidAllocation(bytes));
	return m_objectSpace.allocateWithNormalDestructor(bytes);
}

ALWAYS_INLINE void* Heap::allocateWithImmortalStructureDestructor(size_t bytes)
{
	ASSERT(isValidAllocation(bytes));
	return m_objectSpace.allocateWithImmortalStructureDestructor(bytes);
}

ALWAYS_INLINE void* Heap::allocateWithoutDestructor(size_t bytes)
{
	ASSERT(isValidAllocation(bytes));
	return m_objectSpace.allocateWithoutDestructor(bytes);
}

ALWAYS_INLINE void Heap::allocateStorage(size_t bytes, void** outPtr)
{
	m_storageSpace.allocate(bytes, outPtr);
}

ALWAYS_INLINE CheckedBoolean Heap::tryAllocateStorage(size_t bytes, void** outPtr)
{
	return m_storageSpace.tryAllocate(bytes, outPtr);
}

ALWAYS_INLINE CheckedBoolean Heap::tryReallocateStorage(void** ptr, size_t oldSize, size_t newSize)
{
	return m_storageSpace.tryReallocate(ptr, oldSize, newSize);
}

ALWAYS_INLINE void Heap::didAbandon(size_t bytes)
{
#if ENABLE(GC_ACTIVITY_CALLBACK)
	m_activityCallback->didAllocate(m_bytesAllocated + m_bytesAbandoned);
#endif
	m_bytesAbandoned += bytes;
}

ALWAYS_INLINE void Heap::didAllocate(size_t bytes)
{
#if ENABLE(GC_ACTIVITY_CALLBACK)
	m_activityCallback->didAllocate(m_bytesAllocated + m_bytesAbandoned);
#endif
	m_bytesAllocated += bytes;
}

ALWAYS_INLINE bool Heap::collectIfNecessaryOrDefer(bool noStackObjects, bool force)
{
    if (!shouldCollect())
        return false;

    return collect(noStackObjects, force);
}

ALWAYS_INLINE void Heap::incrementDeferralDepth()
{
	RELEASE_ASSERT(m_deferralDepth < 100); // Sanity check to make sure this doesn't get ridiculous.

	++m_deferralDepth;
}

ALWAYS_INLINE void Heap::decrementDeferralDepth()
{
	RELEASE_ASSERT(m_deferralDepth >= 1);

	--m_deferralDepth;
}

ALWAYS_INLINE void Heap::decrementDeferralDepthAndGCIfNeeded()
{
	decrementDeferralDepth();
	collectIfNecessaryOrDefer();
}

} // namespace JSC

#endif // Heap_h
