kdelibs/kjs/collector.cpp
Ivailo Monev 39f1e04295 generic: add back khtml and kjs with some changes
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2015-11-09 23:23:53 +02:00

938 lines
28 KiB
C++

// -*- mode: c++; c-basic-offset: 4 -*-
/*
* This file is part of the KDE libraries
* Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Computer, Inc.
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
* Copyright (C) 2007 Maksim Orlovich <maksim@kde.org>
*
* 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
*
*/
#include "collector.h"
#include <config-kjs.h>
#include <wtf/FastMalloc.h>
#include <wtf/HashCountedSet.h>
#include "internal.h"
#include "list.h"
#include "value.h"
#include <setjmp.h>
#include <limits.h>
#include <algorithm>
#if PLATFORM(DARWIN)
#include <pthread.h>
#include <mach/mach_port.h>
#include <mach/mach_init.h>
#include <mach/task.h>
#include <mach/thread_act.h>
#include <mach/vm_map.h>
#elif PLATFORM(WIN_OS) || COMPILER(CYGWIN)
#include <windows.h>
#include <winnt.h>
#elif PLATFORM(UNIX)
#include <stdlib.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <pthread.h> //krazy:exclude=includes (yes, it's duplicate, but in a different #if branch)
#include <unistd.h>
#if PLATFORM(SOLARIS_OS)
#include <thread.h>
#include <signal.h>
using std::memset;
#endif
#if HAVE(PTHREAD_NP_H)
#include <pthread_np.h>
#endif
#endif
#define DEBUG_COLLECTOR 0
#if HAVE(VALGRIND_MEMCHECK_H) && !defined(NDEBUG)
#include <valgrind/memcheck.h>
#if defined(VALGRIND_MAKE_MEM_DEFINED)
#define VG_DEFINED(p) VALGRIND_MAKE_MEM_DEFINED(&p, sizeof(void*))
#else
#define VG_DEFINED(p)
#endif
#else
#define VG_DEFINED(p)
#endif
using std::max;
namespace KJS {
// tunable parameters
const size_t SPARE_EMPTY_BLOCKS = 2;
const size_t MIN_ARRAY_SIZE = 14;
const size_t GROWTH_FACTOR = 2;
const size_t LOW_WATER_FACTOR = 4;
const size_t ALLOCATIONS_PER_COLLECTION = 4000;
// A whee bit like a WTF::Vector, but perfectly POD, so can be used in global context
// w/o worries.
struct BlockList {
CollectorBlock** m_data;
size_t m_used;
size_t m_capacity;
CollectorBlock* operator[](size_t pos) {
return m_data[pos];
}
size_t used() const {
return m_used;
}
void append(CollectorBlock* block) {
if (m_used == m_capacity) {
static const size_t maxNumBlocks = ULONG_MAX / sizeof(CollectorBlock*) / GROWTH_FACTOR;
if (m_capacity > maxNumBlocks)
CRASH();
m_capacity = max(MIN_ARRAY_SIZE, m_capacity * GROWTH_FACTOR);
m_data = static_cast<CollectorBlock **>(fastRealloc(m_data, m_capacity * sizeof(CollectorBlock *)));
}
m_data[m_used] = block;
++m_used;
}
void remove(CollectorBlock* block) {
size_t c;
for (c = 0; c < m_used; ++c)
if (m_data[c] == block)
break;
if (c == m_used)
return;
// Move things up, and shrink..
--m_used;
for (; c < m_used; ++c)
m_data[c] = m_data[c+1];
}
};
struct CollectorHeap {
// This contains the list of both normal and oversize blocks
BlockList allBlocks;
// Just the normal blocks
BlockList blocks;
size_t firstBlockWithPossibleSpace;
// The oversize block handling is a bit tricky, since we do not wish to slow down
// the usual paths. Hence, we do the following:
// 1) We set the marked bits for any extension portions of the block.
// this way the stack GC doesn't have to do anything special if a pointer
// happens to go that way.
// 2) We keep track of an allBlocks list to help the stack GC tests things.
//
// The oversize heap itself represents blocks as follows:
// 1) free: marked = false, allocd = false, trailer = false, zeroIfFree = 0
// 2) alloc'd, head: marked = depends, allocd = true, trailer = false, zeroIfFree is uncertain
// 3) alloc'd, trailer: marked = true (see above), allocd = true, trailer = true, zeroIfFree is uncertain
//
// For now, we don't use a freelist, so performance may be quite bad if
// this is used heavily; this is just because the cell does not have
// back and forward links; which we need since we can pick a non-first cell
// ### actually, it may be possible to shuffle the list. Not now, though
BlockList oversizeBlocks;
size_t numLiveObjects;
size_t numLiveObjectsAtLastCollect;
size_t extraCost;
};
static CollectorHeap heap;
bool Collector::memoryFull = false;
static CollectorBlock* allocateBlock()
{
#if PLATFORM(DARWIN)
vm_address_t address = 0;
vm_map(current_task(), &address, BLOCK_SIZE, BLOCK_OFFSET_MASK, VM_FLAGS_ANYWHERE, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_DEFAULT, VM_INHERIT_DEFAULT);
#elif PLATFORM(WIN_OS) || COMPILER(CYGWIN)
// windows virtual address granularity is naturally 64k
LPVOID address = VirtualAlloc(NULL, BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif HAVE(POSIX_MEMALIGN)
void* address;
posix_memalign(address, BLOCK_SIZE, BLOCK_SIZE);
memset(reinterpret_cast<void*>(address), 0, BLOCK_SIZE);
#else
static size_t pagesize = getpagesize();
size_t extra = 0;
if (BLOCK_SIZE > pagesize)
extra = BLOCK_SIZE - pagesize;
void* mmapResult = mmap(NULL, BLOCK_SIZE + extra, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
uintptr_t address = reinterpret_cast<uintptr_t>(mmapResult);
size_t adjust = 0;
if ((address & BLOCK_OFFSET_MASK) != 0)
adjust = BLOCK_SIZE - (address & BLOCK_OFFSET_MASK);
if (adjust > 0)
munmap(reinterpret_cast<void*>(address), adjust);
if (adjust < extra)
munmap(reinterpret_cast<void*>(address + adjust + BLOCK_SIZE), extra - adjust);
address += adjust;
memset(reinterpret_cast<void*>(address), 0, BLOCK_SIZE);
#endif
CollectorBlock* ptr = reinterpret_cast<CollectorBlock*>(address);
heap.allBlocks.append(ptr); // Register with the heap
return ptr;
}
static void freeBlock(CollectorBlock* block)
{
// Unregister the block first
heap.allBlocks.remove(block);
#if PLATFORM(DARWIN)
vm_deallocate(current_task(), reinterpret_cast<vm_address_t>(block), BLOCK_SIZE);
#elif PLATFORM(WIN_OS) || COMPILER(CYGWIN)
VirtualFree(block, BLOCK_SIZE, MEM_RELEASE);
#elif HAVE(POSIX_MEMALIGN)
free(block);
#else
munmap(block, BLOCK_SIZE);
#endif
}
void Collector::recordExtraCost(size_t cost)
{
// Our frequency of garbage collection tries to balance memory use against speed
// by collecting based on the number of newly created values. However, for values
// that hold on to a great deal of memory that's not in the form of other JS values,
// that is not good enough - in some cases a lot of those objects can pile up and
// use crazy amounts of memory without a GC happening. So we track these extra
// memory costs. Only unusually large objects are noted, and we only keep track
// of this extra cost until the next GC. In garbage collected languages, most values
// are either very short lived temporaries, or have extremely long lifetimes. So
// if a large value survives one garbage collection, there is not much point to
// collecting more frequently as long as it stays alive.
heap.extraCost += cost;
}
static void* allocOversize(size_t s)
{
size_t cellsNeeded = (s + (CELL_SIZE - 1)) / CELL_SIZE;
// printf("allocOversize, size:%d, cellsNeeded:%d\n", s, cellsNeeded);
// We are not oversize enough to deal with things close to 64K in size
assert(cellsNeeded <= CELLS_PER_BLOCK);
// Look through the blocks, see if one has enough, and where.
CollectorBlock* sufficientBlock = 0;
size_t startOffset = -1;
for (size_t b = 0; b < heap.oversizeBlocks.used() && !sufficientBlock; ++b) {
CollectorBlock* candidate = heap.oversizeBlocks[b];
if (cellsNeeded <= CELLS_PER_BLOCK - candidate->usedCells) {
// Well, there is a chance we will fit.. Let's see if we can find a batch long enough..
for (size_t i = 0; i < CELLS_PER_BLOCK; i++) {
if (i % 32 == 0 && candidate->allocd.bits[i/32] == 0xFFFFFFFF) {
// Can skip this and 31 other cells
i += 31;
continue;
}
if (candidate->allocd.get(i))
continue;
// This cell is free -- are there enough free cells after it?
startOffset = i;
size_t last = i + cellsNeeded - 1;
if (last >= CELLS_PER_BLOCK) // No way it will fit
break;
++i;
while (i <= last && !candidate->allocd.get(i))
++i;
// Did we get through enough?
if (i == last + 1) {
sufficientBlock = candidate;
break;
}
// Not enough room -- and the current entry has a non-zero zeroIfFree,
// so we should go on at i + 1 on next iteration
} // for each cell per block.
} // if enough room in block
} // for each block
if (!sufficientBlock) {
sufficientBlock = allocateBlock();
startOffset = 0;
heap.oversizeBlocks.append(sufficientBlock);
}
sufficientBlock->usedCells += cellsNeeded;
// Set proper bits for trailers and the head
sufficientBlock->allocd.set(startOffset);
for (size_t t = startOffset + 1; t < startOffset + cellsNeeded; ++t) {
sufficientBlock->trailer.set(t);
sufficientBlock->marked.set(t);
sufficientBlock->allocd.set(t);
}
void* result = sufficientBlock->cells + startOffset;
memset(result, 0, s);
heap.numLiveObjects = heap.numLiveObjects + 1;
return result;
}
void* Collector::allocate(size_t s)
{
assert(JSLock::lockCount() > 0);
// collect if needed
size_t numLiveObjects = heap.numLiveObjects;
size_t numLiveObjectsAtLastCollect = heap.numLiveObjectsAtLastCollect;
size_t numNewObjects = numLiveObjects - numLiveObjectsAtLastCollect;
size_t newCost = numNewObjects + heap.extraCost;
if (newCost >= ALLOCATIONS_PER_COLLECTION && newCost >= numLiveObjectsAtLastCollect) {
collect();
numLiveObjects = heap.numLiveObjects;
}
if (s > CELL_SIZE) {
return allocOversize(s);
}
// slab allocator
size_t usedBlocks = heap.blocks.used();
size_t i = heap.firstBlockWithPossibleSpace;
CollectorBlock *targetBlock;
size_t targetBlockUsedCells;
if (i != usedBlocks) {
targetBlock = heap.blocks[i];
targetBlockUsedCells = targetBlock->usedCells;
assert(targetBlockUsedCells <= CELLS_PER_BLOCK);
while (targetBlockUsedCells == CELLS_PER_BLOCK) {
if (++i == usedBlocks)
goto allocateNewBlock;
targetBlock = heap.blocks[i];
targetBlockUsedCells = targetBlock->usedCells;
assert(targetBlockUsedCells <= CELLS_PER_BLOCK);
}
heap.firstBlockWithPossibleSpace = i;
} else {
allocateNewBlock:
// didn't find one, need to allocate a new block
targetBlock = allocateBlock();
targetBlock->freeList = targetBlock->cells;
targetBlockUsedCells = 0;
heap.blocks.append(targetBlock);
heap.firstBlockWithPossibleSpace = usedBlocks; // previous usedBlocks -> new one's index
}
// find a free spot in the block and detach it from the free list
CollectorCell *newCell = targetBlock->freeList;
// "next" field is a byte offset -- 0 means next cell, so a zeroed block is already initialized
// could avoid the casts by using a cell offset, but this avoids a relatively-slow multiply
targetBlock->freeList = reinterpret_cast<CollectorCell *>(reinterpret_cast<char *>(newCell + 1) + newCell->u.freeCell.next);
targetBlock->usedCells = targetBlockUsedCells + 1;
heap.numLiveObjects = numLiveObjects + 1;
return newCell;
}
#if USE(MULTIPLE_THREADS)
struct Collector::Thread {
Thread(pthread_t pthread, mach_port_t mthread) : posixThread(pthread), machThread(mthread) {}
Thread *next;
pthread_t posixThread;
mach_port_t machThread;
};
pthread_key_t registeredThreadKey;
pthread_once_t registeredThreadKeyOnce = PTHREAD_ONCE_INIT;
Collector::Thread *registeredThreads;
static void destroyRegisteredThread(void *data)
{
Collector::Thread *thread = (Collector::Thread *)data;
if (registeredThreads == thread) {
registeredThreads = registeredThreads->next;
} else {
Collector::Thread *last = registeredThreads;
for (Collector::Thread *t = registeredThreads->next; t != NULL; t = t->next) {
if (t == thread) {
last->next = t->next;
break;
}
last = t;
}
}
delete thread;
}
static void initializeRegisteredThreadKey()
{
pthread_key_create(&registeredThreadKey, destroyRegisteredThread);
}
void Collector::registerThread()
{
pthread_once(&registeredThreadKeyOnce, initializeRegisteredThreadKey);
if (!pthread_getspecific(registeredThreadKey)) {
pthread_t pthread = pthread_self();
WTF::fastMallocRegisterThread(pthread);
Collector::Thread *thread = new Collector::Thread(pthread, pthread_mach_thread_np(pthread));
thread->next = registeredThreads;
registeredThreads = thread;
pthread_setspecific(registeredThreadKey, thread);
}
}
#endif
#define IS_POINTER_ALIGNED(p) (((intptr_t)(p) & (sizeof(char *) - 1)) == 0)
// cell size needs to be a power of two for this to be valid
#define IS_CELL_ALIGNED(p) (((intptr_t)(p) & CELL_MASK) == 0)
void Collector::markStackObjectsConservatively(void *start, void *end)
{
if (start > end) {
void *tmp = start;
start = end;
end = tmp;
}
assert(((char *)end - (char *)start) < 0x1000000);
assert(IS_POINTER_ALIGNED(start));
assert(IS_POINTER_ALIGNED(end));
char **p = (char **)start;
char **e = (char **)end;
// We use allBlocks here since we want to mark oversize cells as well.
// Their trailers will have the mark bit set already, to avoid trouble.
size_t usedBlocks = heap.allBlocks.used();
CollectorBlock **blocks = heap.allBlocks.m_data;
const size_t lastCellOffset = sizeof(CollectorCell) * (CELLS_PER_BLOCK - 1);
while (p != e) {
char *x = *p++;
VG_DEFINED(x);
if (IS_CELL_ALIGNED(x) && x) {
uintptr_t offset = reinterpret_cast<uintptr_t>(x) & BLOCK_OFFSET_MASK;
CollectorBlock* blockAddr = reinterpret_cast<CollectorBlock*>(x - offset);
for (size_t block = 0; block < usedBlocks; block++) {
if ((blocks[block] == blockAddr) && (offset <= lastCellOffset)) {
if (((CollectorCell *)x)->u.freeCell.zeroIfFree != 0) {
JSCell *imp = reinterpret_cast<JSCell *>(x);
if (!imp->marked())
imp->mark();
}
} // if valid block
} // for each block
} // if cell-aligned
} // for each pointer
}
static inline void* currentThreadStackBase()
{
#if PLATFORM(DARWIN)
pthread_t thread = pthread_self();
void *stackBase = pthread_get_stackaddr_np(thread);
#elif (PLATFORM(WIN_OS) || COMPILER(CYGWIN))
// tested with mingw32, mingw64, msvc2008, cygwin
NT_TIB *pTib = (NT_TIB*)NtCurrentTeb();
void *stackBase = (void *)pTib->StackBase;
#elif PLATFORM(SOLARIS_OS)
stack_t s;
thr_stksegment(&s);
return s.ss_sp;
// NOTREACHED
void *stackBase = 0;
#elif PLATFORM(UNIX)
static void *stackBase = 0;
static pthread_t stackThread;
pthread_t thread = pthread_self();
if (stackBase == 0 || thread != stackThread) {
pthread_attr_t sattr;
#if HAVE(PTHREAD_NP_H) || defined(__NetBSD__)
// e.g. on FreeBSD 5.4, neundorf@kde.org
// also on NetBSD 3 and 4, raphael.langerhorst@kdemail.net
// HIGHLY RECCOMENDED by manpage to allocate storage, avoids
// crashing in JS immediately in FreeBSD.
pthread_attr_init(&sattr);
pthread_attr_get_np(thread, &sattr);
#else
// FIXME: this function is non-portable; other POSIX systems may have different np alternatives
pthread_getattr_np(thread, &sattr);
#endif // picking the _np function to use --- Linux or BSD
size_t stackSize;
pthread_attr_getstack(&sattr, &stackBase, &stackSize);
stackBase = (char *)stackBase + stackSize; // a matter of interpretation, apparently...
pthread_attr_destroy(&sattr);
assert(stackBase);
stackThread = thread;
}
#else
#error Need a way to get the stack base on this platform
#endif
return stackBase;
}
void Collector::markCurrentThreadConservatively()
{
// setjmp forces volatile registers onto the stack
jmp_buf registers;
#if COMPILER(MSVC)
#pragma warning(push)
#pragma warning(disable: 4611)
#endif
setjmp(registers);
#if COMPILER(MSVC)
#pragma warning(pop)
#endif
void* dummy;
void* stackPointer = &dummy;
void* stackBase = currentThreadStackBase();
markStackObjectsConservatively(stackPointer, stackBase);
}
#if USE(MULTIPLE_THREADS)
typedef unsigned long usword_t; // word size, assumed to be either 32 or 64 bit
void Collector::markOtherThreadConservatively(Thread *thread)
{
thread_suspend(thread->machThread);
#if PLATFORM(X86)
i386_thread_state_t regs;
unsigned user_count = sizeof(regs)/sizeof(int);
thread_state_flavor_t flavor = i386_THREAD_STATE;
#elif PLATFORM(X86_64)
x86_thread_state64_t regs;
unsigned user_count = x86_THREAD_STATE64_COUNT;
thread_state_flavor_t flavor = x86_THREAD_STATE64;
#elif PLATFORM(PPC)
ppc_thread_state_t regs;
unsigned user_count = PPC_THREAD_STATE_COUNT;
thread_state_flavor_t flavor = PPC_THREAD_STATE;
#elif PLATFORM(PPC64)
ppc_thread_state64_t regs;
unsigned user_count = PPC_THREAD_STATE64_COUNT;
thread_state_flavor_t flavor = PPC_THREAD_STATE64;
#else
#error Unknown Architecture
#endif
// get the thread register state
thread_get_state(thread->machThread, flavor, (thread_state_t)&regs, &user_count);
// scan the registers
markStackObjectsConservatively((void *)&regs, (void *)((char *)&regs + (user_count * sizeof(usword_t))));
// scan the stack
#if PLATFORM(X86) && __DARWIN_UNIX03
markStackObjectsConservatively((void *)regs.__esp, pthread_get_stackaddr_np(thread->posixThread));
#elif PLATFORM(X86)
markStackObjectsConservatively((void *)regs.esp, pthread_get_stackaddr_np(thread->posixThread));
#elif PLATFORM(X86_64) && __DARWIN_UNIX03
markStackObjectsConservatively((void *)regs.__rsp, pthread_get_stackaddr_np(thread->posixThread));
#elif PLATFORM(X86_64)
markStackObjectsConservatively((void *)regs.rsp, pthread_get_stackaddr_np(thread->posixThread));
#elif (PLATFORM(PPC) || PLATFORM(PPC64)) && __DARWIN_UNIX03
markStackObjectsConservatively((void *)regs.__r1, pthread_get_stackaddr_np(thread->posixThread));
#elif PLATFORM(PPC) || PLATFORM(PPC64)
markStackObjectsConservatively((void *)regs.r1, pthread_get_stackaddr_np(thread->posixThread));
#else
#error Unknown Architecture
#endif
thread_resume(thread->machThread);
}
#endif
void Collector::markStackObjectsConservatively()
{
markCurrentThreadConservatively();
#if USE(MULTIPLE_THREADS)
for (Thread *thread = registeredThreads; thread != NULL; thread = thread->next) {
if (thread->posixThread != pthread_self()) {
markOtherThreadConservatively(thread);
}
}
#endif
}
typedef HashCountedSet<JSCell *> ProtectCounts;
static ProtectCounts& protectedValues()
{
static ProtectCounts pv;
return pv;
}
void Collector::protect(JSValue *k)
{
assert(k);
assert(JSLock::lockCount() > 0);
if (JSImmediate::isImmediate(k))
return;
protectedValues().add(k->asCell());
}
void Collector::unprotect(JSValue *k)
{
assert(k);
assert(JSLock::lockCount() > 0);
if (JSImmediate::isImmediate(k))
return;
protectedValues().remove(k->asCell());
}
void Collector::markProtectedObjects()
{
ProtectCounts& pv = protectedValues();
ProtectCounts::iterator end = pv.end();
for (ProtectCounts::iterator it = pv.begin(); it != end; ++it) {
JSCell *val = it->first;
if (!val->marked())
val->mark();
}
}
bool Collector::collect()
{
assert(JSLock::lockCount() > 0);
#if USE(MULTIPLE_THREADS)
bool currentThreadIsMainThread = pthread_main_np();
#else
bool currentThreadIsMainThread = true;
#endif
Interpreter::markSourceCachedObjects();
if (Interpreter::s_hook) {
Interpreter* scr = Interpreter::s_hook;
do {
scr->mark(currentThreadIsMainThread);
scr = scr->next;
} while (scr != Interpreter::s_hook);
}
// MARK: first mark all referenced objects recursively starting out from the set of root objects
markStackObjectsConservatively();
markProtectedObjects();
List::markProtectedLists();
#if USE(MULTIPLE_THREADS)
if (!currentThreadIsMainThread)
markMainThreadOnlyObjects();
#endif
// SWEEP: delete everything with a zero refcount (garbage) and unmark everything else
// Note: if a cell has zeroIfFree == 0, it is either free,
// or in the middle of being constructed and has not yet
// had its vtable filled. Hence, such cells should not be cleaned up
size_t emptyBlocks = 0;
size_t numLiveObjects = heap.numLiveObjects;
for (size_t block = 0; block < heap.blocks.used(); block++) {
CollectorBlock *curBlock = heap.blocks[block];
size_t usedCells = curBlock->usedCells;
CollectorCell *freeList = curBlock->freeList;
if (usedCells == CELLS_PER_BLOCK) {
// special case with a block where all cells are used -- testing indicates this happens often
for (size_t i = 0; i < CELLS_PER_BLOCK; i++) {
CollectorCell *cell = curBlock->cells + i;
JSCell *imp = reinterpret_cast<JSCell *>(cell);
if (!curBlock->marked.get(i) && currentThreadIsMainThread) {
// special case for allocated but uninitialized object
// (We don't need this check earlier because nothing prior this point assumes the object has a valid vptr.)
if (cell->u.freeCell.zeroIfFree == 0)
continue;
imp->~JSCell();
--usedCells;
--numLiveObjects;
// put cell on the free list
cell->u.freeCell.zeroIfFree = 0;
cell->u.freeCell.next = reinterpret_cast<char *>(freeList) - reinterpret_cast<char *>(cell + 1);
freeList = cell;
}
}
} else {
size_t minimumCellsToProcess = usedCells;
for (size_t i = 0; (i < minimumCellsToProcess) & (i < CELLS_PER_BLOCK); i++) {
CollectorCell *cell = curBlock->cells + i;
if (cell->u.freeCell.zeroIfFree == 0) {
++minimumCellsToProcess;
} else {
JSCell *imp = reinterpret_cast<JSCell *>(cell);
if (!curBlock->marked.get(i) && currentThreadIsMainThread) {
imp->~JSCell();
--usedCells;
--numLiveObjects;
// put cell on the free list
cell->u.freeCell.zeroIfFree = 0;
cell->u.freeCell.next = reinterpret_cast<char *>(freeList) - reinterpret_cast<char *>(cell + 1);
freeList = cell;
}
}
}
}
curBlock->marked.clearAll();
curBlock->usedCells = usedCells;
curBlock->freeList = freeList;
if (usedCells == 0) {
emptyBlocks++;
if (emptyBlocks > SPARE_EMPTY_BLOCKS) {
#if !DEBUG_COLLECTOR
freeBlock(curBlock);
#endif
// swap with the last block so we compact as we go
heap.blocks.m_data[block] = heap.blocks[heap.blocks.used() - 1];
heap.blocks.m_used--;
block--; // Don't move forward a step in this case
}
}
}
if (heap.numLiveObjects != numLiveObjects)
heap.firstBlockWithPossibleSpace = 0;
// Now sweep oversize blocks.
emptyBlocks = 0;
for (size_t ob = 0; ob < heap.oversizeBlocks.used(); ++ob) {
CollectorBlock* curBlock = heap.oversizeBlocks[ob];
CollectorCell *freeList = curBlock->freeList;
size_t usedCells = curBlock->usedCells;
// Go through the cells
for (size_t i = 0; i < CELLS_PER_BLOCK; i++) {
if (i % 32 == 0 && curBlock->allocd.bits[i/32] == 0) {
// Nothing used around here, skip this and 31 next cells
i += 31;
continue;
}
CollectorCell *cell = curBlock->cells + i;
if (cell->u.freeCell.zeroIfFree == 0)
continue;
if (!curBlock->allocd.get(i))
continue;
JSCell *imp = reinterpret_cast<JSCell *>(cell);
// Live and trailer cells will all have the mark set,
// so we only have to collect with it clear --
// and we already took care of those that are
// already free (or being initialized) above
if (!curBlock->marked.get(i)) {
// Free this block...
imp->~JSCell();
--numLiveObjects;
--usedCells;
// Mark the block and the trailers as available
cell->u.freeCell.zeroIfFree = 0;
curBlock->allocd.clear(i);
++i; // Go to the potential trailer..
while (curBlock->trailer.get(i) && i < CELLS_PER_BLOCK) {
--usedCells;
curBlock->allocd.clear(i);
curBlock->trailer.clear(i);
curBlock->marked.clear (i);
curBlock->cells[i].u.freeCell.zeroIfFree = 0;
++i;
}
--i; // Last item we processed.
} else {
// If this is not a trailer cell, clear the mark
if (!curBlock->trailer.get(i))
curBlock->marked.clear(i);
}
} // each cell
curBlock->usedCells = usedCells;
curBlock->freeList = freeList;
if (usedCells == 0) {
emptyBlocks++;
if (emptyBlocks > SPARE_EMPTY_BLOCKS) {
freeBlock(curBlock);
// swap with the last block so we compact as we go
heap.oversizeBlocks.m_data[ob] = heap.oversizeBlocks[heap.oversizeBlocks.used() - 1];
heap.oversizeBlocks.m_used--;
ob--; // Don't move forward a step in this case
}
}
} // each oversize block
bool deleted = heap.numLiveObjects != numLiveObjects;
heap.numLiveObjects = numLiveObjects;
heap.numLiveObjectsAtLastCollect = numLiveObjects;
heap.extraCost = 0;
bool newMemoryFull = (numLiveObjects >= KJS_MEM_LIMIT);
if (newMemoryFull && newMemoryFull != memoryFull)
reportOutOfMemoryToAllInterpreters();
memoryFull = newMemoryFull;
return deleted;
}
size_t Collector::size()
{
return heap.numLiveObjects;
}
#ifdef KJS_DEBUG_MEM
void Collector::finalCheck()
{
}
#endif
size_t Collector::numInterpreters()
{
size_t count = 0;
if (Interpreter::s_hook) {
Interpreter* scr = Interpreter::s_hook;
do {
++count;
scr = scr->next;
} while (scr != Interpreter::s_hook);
}
return count;
}
size_t Collector::numProtectedObjects()
{
return protectedValues().size();
}
static const char *typeName(JSCell *val)
{
const char *name = "???";
switch (val->type()) {
case UnspecifiedType:
break;
case UndefinedType:
name = "undefined";
break;
case NullType:
name = "null";
break;
case BooleanType:
name = "boolean";
break;
case StringType:
name = "string";
break;
case NumberType:
name = "number";
break;
case ObjectType: {
const ClassInfo *info = static_cast<JSObject *>(val)->classInfo();
name = info ? info->className : "Object";
break;
}
case GetterSetterType:
name = "gettersetter";
break;
}
return name;
}
HashCountedSet<const char*>* Collector::rootObjectTypeCounts()
{
HashCountedSet<const char*>* counts = new HashCountedSet<const char*>;
ProtectCounts& pv = protectedValues();
ProtectCounts::iterator end = pv.end();
for (ProtectCounts::iterator it = pv.begin(); it != end; ++it)
counts->add(typeName(it->first));
return counts;
}
void Collector::reportOutOfMemoryToAllInterpreters()
{
if (!Interpreter::s_hook)
return;
Interpreter* interpreter = Interpreter::s_hook;
do {
ExecState* exec = interpreter->execState();
exec->setException(Error::create(exec, GeneralError, "Out of memory"));
interpreter = interpreter->next;
} while(interpreter != Interpreter::s_hook);
}
} // namespace KJS