bug 665354 - page-independent free span. r=wmccloskey

This commit is contained in:
Igor Bukanov 2011-08-05 18:43:59 +02:00
Родитель 2b2ff5f8b1
Коммит 602f05f2d4
3 изменённых файлов: 230 добавлений и 138 удалений

Просмотреть файл

@ -176,7 +176,7 @@ ArenaHeader::checkSynchronizedWithFreeList() const
if (!compartment->rt->gcRunning)
return;
FreeSpan firstSpan(address() + firstFreeSpanStart, address() + firstFreeSpanEnd);
FreeSpan firstSpan = FreeSpan::decodeOffsets(arenaAddress(), firstFreeSpanOffsets);
if (firstSpan.isEmpty())
return;
FreeSpan *list = &compartment->freeLists.lists[getThingKind()];
@ -187,8 +187,7 @@ ArenaHeader::checkSynchronizedWithFreeList() const
* Here this arena has free things, FreeList::lists[thingKind] is not
* empty and also points to this arena. Thus they must the same.
*/
JS_ASSERT(firstSpan.start == list->start);
JS_ASSERT(firstSpan.end == list->end);
JS_ASSERT(firstSpan.isSameNonEmptySpan(list));
}
#endif
@ -200,7 +199,7 @@ Arena::finalize(JSContext *cx)
JS_ASSERT(!aheader.getMarkingDelay()->link);
uintptr_t thing = thingsStart(sizeof(T));
uintptr_t end = thingsEnd();
uintptr_t lastByte = thingsEnd() - 1;
FreeSpan nextFree(aheader.getFirstFreeSpan());
nextFree.checkSpan();
@ -213,15 +212,15 @@ Arena::finalize(JSContext *cx)
size_t nmarked = 0;
#endif
for (;; thing += sizeof(T)) {
JS_ASSERT(thing <= end);
if (thing == nextFree.start) {
JS_ASSERT(nextFree.end <= end);
if (nextFree.end == end)
JS_ASSERT(thing <= lastByte + 1);
if (thing == nextFree.first) {
JS_ASSERT(nextFree.last <= lastByte);
if (nextFree.last == lastByte)
break;
JS_ASSERT(Arena::isAligned(nextFree.end, sizeof(T)));
JS_ASSERT(Arena::isAligned(nextFree.last, sizeof(T)));
if (!newFreeSpanStart)
newFreeSpanStart = thing;
thing = nextFree.end;
thing = nextFree.last;
nextFree = *nextFree.nextSpan();
nextFree.checkSpan();
} else {
@ -233,10 +232,10 @@ Arena::finalize(JSContext *cx)
#endif
if (newFreeSpanStart) {
JS_ASSERT(thing >= thingsStart(sizeof(T)) + sizeof(T));
newListTail->start = newFreeSpanStart;
newListTail->end = thing - sizeof(T);
newListTail = newListTail->nextSpanUnchecked();
newFreeSpanStart = 0;
newListTail->first = newFreeSpanStart;
newListTail->last = thing - sizeof(T);
newListTail = newListTail->nextSpanUnchecked(sizeof(T));
newFreeSpanStart = NULL;
}
} else {
if (!newFreeSpanStart)
@ -253,20 +252,20 @@ Arena::finalize(JSContext *cx)
return true;
}
newListTail->start = newFreeSpanStart ? newFreeSpanStart : nextFree.start;
JS_ASSERT(Arena::isAligned(newListTail->start, sizeof(T)));
newListTail->end = end;
newListTail->first = newFreeSpanStart ? newFreeSpanStart : nextFree.first;
JS_ASSERT(Arena::isAligned(newListTail->first, sizeof(T)));
newListTail->last = lastByte;
#ifdef DEBUG
size_t nfree = 0;
for (FreeSpan *span = &newListHead; span != newListTail; span = span->nextSpan()) {
for (const FreeSpan *span = &newListHead; span != newListTail; span = span->nextSpan()) {
span->checkSpan();
JS_ASSERT(Arena::isAligned(span->start, sizeof(T)));
JS_ASSERT(Arena::isAligned(span->end, sizeof(T)));
nfree += (span->end - span->start) / sizeof(T) + 1;
JS_ASSERT(Arena::isAligned(span->first, sizeof(T)));
JS_ASSERT(Arena::isAligned(span->last, sizeof(T)));
nfree += (span->last - span->first) / sizeof(T) + 1;
JS_ASSERT(nfree + nmarked <= thingsPerArena(sizeof(T)));
}
nfree += (newListTail->end - newListTail->start) / sizeof(T);
nfree += (newListTail->last + 1 - newListTail->first) / sizeof(T);
JS_ASSERT(nfree + nmarked == thingsPerArena(sizeof(T)));
#endif
aheader.setFirstFreeSpan(&newListHead);
@ -629,9 +628,9 @@ InFreeList(ArenaHeader *aheader, uintptr_t addr)
FreeSpan firstSpan(aheader->getFirstFreeSpan());
for (FreeSpan *span = &firstSpan;;) {
for (const FreeSpan *span = &firstSpan;;) {
/* If the thing comes fore the current span, it's not free. */
if (addr < span->start)
if (addr < span->first)
return false;
/*
@ -639,7 +638,7 @@ InFreeList(ArenaHeader *aheader, uintptr_t addr)
* "<" even for the last span as we know that thing is inside the
* arena. Thus for the last span thing < span->end.
*/
if (addr <= span->end)
if (addr <= span->last)
return true;
/*
@ -1364,7 +1363,7 @@ IsGCAllowed(JSContext *cx)
}
template <typename T>
inline Cell *
inline void *
RefillTypedFreeList(JSContext *cx, unsigned thingKind)
{
JS_ASSERT(!cx->runtime->gcRunning);
@ -1391,7 +1390,7 @@ RefillTypedFreeList(JSContext *cx, unsigned thingKind)
* things and populate the free list. If that happens, just
* return that list head.
*/
if (Cell *thing = compartment->freeLists.getNext(thingKind, sizeof(T)))
if (void *thing = compartment->freeLists.getNext(thingKind, sizeof(T)))
return thing;
}
ArenaHeader *aheader =
@ -1414,7 +1413,7 @@ RefillTypedFreeList(JSContext *cx, unsigned thingKind)
return NULL;
}
Cell *
void *
RefillFinalizableFreeList(JSContext *cx, unsigned thingKind)
{
switch (thingKind) {
@ -1452,7 +1451,7 @@ RefillFinalizableFreeList(JSContext *cx, unsigned thingKind)
#endif
default:
JS_NOT_REACHED("bad finalize kind");
return NULL;
return 0;
}
}
@ -2320,7 +2319,7 @@ MarkAndSweep(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind GCTIM
WatchpointMap::sweepAll(rt);
/*
* We finalize objects before other GC things to ensure that object's finalizer
* We finalize objects before other GC things to ensure that object's finalizer
* can access them even if they will be freed. Sweep the runtime's property trees
* after finalizing objects, in case any had watchpoints referencing tree nodes.
* Do this before sweeping compartments, so that we sweep all shapes in
@ -2832,18 +2831,18 @@ IterateCompartmentsArenasCells(JSContext *cx, void *data,
Arena *arena = aheader->getArena();
(*arenaCallback)(cx, data, arena, traceKind, thingSize);
FreeSpan firstSpan(aheader->getFirstFreeSpan());
FreeSpan *span = &firstSpan;
const FreeSpan *span = &firstSpan;
for (uintptr_t thing = arena->thingsStart(thingSize); ; thing += thingSize) {
JS_ASSERT(thing <= arena->thingsEnd());
if (thing == span->start) {
if (thing == span->first) {
if (!span->hasNext())
break;
thing = span->end;
thing = span->last;
span = span->nextSpan();
} else {
(*cellCallback)(cx, data, reinterpret_cast<void *>(thing), traceKind,
thingSize);
void *t = reinterpret_cast<void *>(thing);
(*cellCallback)(cx, data, t, traceKind, thingSize);
}
}
}

Просмотреть файл

@ -126,100 +126,221 @@ const size_t ArenaBitmapWords = ArenaBitmapBits / JS_BITS_PER_WORD;
/*
* A FreeSpan represents a contiguous sequence of free cells in an Arena.
* |start| is the address of the first free cell in the span. |end| is the
* address of the last free cell in the span. The last cell (starting at
* |end|) holds a FreeSpan data structure for the next span. However, the last
* FreeSpan in an Arena is special: |end| points to the end of the Arena (an
* unusable address), and no next FreeSpan is stored there.
* |first| is the address of the first free cell in the span. |last| is the
* address of the last free cell in the span. This last cell holds a FreeSpan
* data structure for the next span unless this is the last span on the list
* of spans in the arena. For this last span |last| points to the last byte of
* the last thing in the arena and no linkage is stored there, so
* |last| == arenaStart + ArenaSize - 1. If the space at the arena end is
* fully used this last span is empty and |first| == |last + 1|.
*
* As things in the arena ends on its boundary that is aligned on ArenaSize,
* end & ArenaMask is zero if and only if the span is last. Also, since the
* first thing in the arena comes after the header, start & ArenaSize is zero
* if and only if the span is the empty span at the end of the arena.
* Thus |first| < |last| implies that we have either the last span with at least
* one element or that the span is not the last and contains at least 2
* elements. In both cases to allocate a thing from this span we need simply
* to increment |first| by the allocation size.
*
* The type of the start and end fields is uintptr_t, not a pointer type, to
* minimize the amount of casting when doing mask operations.
* |first| == |last| implies that we have a one element span that records the
* next span. So to allocate from it we need to update the span list head
* with a copy of the span stored at |last| address so the following
* allocations will use that span.
*
* |first| > |last| implies that we have an empty last span and the arena is
* fully used.
*
* Also only for the last span (|last| & 1)! = 0 as all allocation sizes are
* multiples of Cell::CellSize.
*/
struct FreeSpan {
uintptr_t start;
uintptr_t end;
uintptr_t first;
uintptr_t last;
public:
FreeSpan() { }
FreeSpan() {}
FreeSpan(uintptr_t start, uintptr_t end)
: start(start), end(end) {
FreeSpan(uintptr_t first, uintptr_t last)
: first(first), last(last) {
checkSpan();
}
/*
* To minimize the size of the arena header the first span is encoded
* there as offsets from the arena start.
*/
static size_t encodeOffsets(size_t firstOffset, size_t lastOffset = ArenaSize - 1) {
/* Check that we can pack the offsets into uint16. */
JS_STATIC_ASSERT(ArenaShift < 16);
JS_ASSERT(firstOffset <= ArenaSize);
JS_ASSERT(lastOffset < ArenaSize);
JS_ASSERT(firstOffset <= ((lastOffset + 1) & ~size_t(1)));
return firstOffset | (lastOffset << 16);
}
static const size_t EmptyOffsets = ArenaSize | ((ArenaSize - 1) << 16);
static FreeSpan decodeOffsets(uintptr_t arenaAddr, size_t offsets) {
JS_ASSERT(!(arenaAddr & ArenaMask));
size_t firstOffset = offsets & 0xFFFF;
size_t lastOffset = offsets >> 16;
JS_ASSERT(firstOffset <= ArenaSize);
JS_ASSERT(lastOffset < ArenaSize);
/*
* We must not use | when calculating first as firstOffset is
* ArenaMask + 1 for the empty span.
*/
return FreeSpan(arenaAddr + firstOffset, arenaAddr | lastOffset);
}
void initAsEmpty(uintptr_t arenaAddr = 0) {
JS_ASSERT(!(arenaAddr & ArenaMask));
first = arenaAddr + ArenaSize;
last = arenaAddr | (ArenaSize - 1);
JS_ASSERT(isEmpty());
}
bool isEmpty() const {
checkSpan();
return !(start & ArenaMask);
return first > last;
}
bool hasNext() const {
checkSpan();
return !!(end & ArenaMask);
return !(last & uintptr_t(1));
}
FreeSpan *nextSpan() const {
const FreeSpan *nextSpan() const {
JS_ASSERT(hasNext());
return reinterpret_cast<FreeSpan *>(end);
return reinterpret_cast<FreeSpan *>(last);
}
FreeSpan *nextSpanUnchecked() const {
JS_ASSERT(end & ArenaMask);
return reinterpret_cast<FreeSpan *>(end);
FreeSpan *nextSpanUnchecked(size_t thingSize) const {
#ifdef DEBUG
uintptr_t lastOffset = last & ArenaMask;
JS_ASSERT(!(lastOffset & 1));
JS_ASSERT((ArenaSize - lastOffset) % thingSize == 0);
#endif
return reinterpret_cast<FreeSpan *>(last);
}
uintptr_t arenaAddressUnchecked() const {
return last & ~ArenaMask;
}
uintptr_t arenaAddress() const {
checkSpan();
return arenaAddressUnchecked();
}
ArenaHeader *arenaHeader() const {
return reinterpret_cast<ArenaHeader *>(arenaAddress());
}
bool isSameNonEmptySpan(const FreeSpan *another) const {
JS_ASSERT(!isEmpty());
return start & ~ArenaMask;
JS_ASSERT(!another->isEmpty());
return first == another->first && last == another->last;
}
bool isWithinArena(uintptr_t arenaAddr) const {
JS_ASSERT(!(arenaAddr & ArenaMask));
/* Return true for the last empty span as well. */
return arenaAddress() == arenaAddr;
}
size_t encodeAsOffsets() const {
/*
* We must use first - arenaAddress(), not first & ArenaMask as
* first == ArenaMask + 1 for an empty span.
*/
uintptr_t arenaAddr = arenaAddress();
return encodeOffsets(first - arenaAddr, last & ArenaMask);
}
/* See comments before FreeSpan for details. */
JS_ALWAYS_INLINE void *allocate(size_t thingSize) {
JS_ASSERT(thingSize % Cell::CellSize == 0);
checkSpan();
uintptr_t thing = first;
if (thing < last) {
/* Bump-allocate from the current span. */
first = thing + thingSize;
} else if (JS_LIKELY(thing == last)) {
/*
* Move to the next span. We use JS_LIKELY as without PGO
* compilers mis-predict == here as unlikely to succeed.
*/
*this = *reinterpret_cast<FreeSpan *>(thing);
} else {
return NULL;
}
checkSpan();
return reinterpret_cast<void *>(thing);
}
void checkSpan() const {
#ifdef DEBUG
JS_ASSERT(start <= end);
JS_ASSERT(end - start <= ArenaSize);
if (!(start & ArenaMask)) {
/* The span is last and empty. */
JS_ASSERT(start == end);
/* We do not allow spans at the end of the address space. */
JS_ASSERT(last != uintptr_t(-1));
JS_ASSERT(first);
JS_ASSERT(last);
JS_ASSERT(first - 1 <= last);
uintptr_t arenaAddr = arenaAddressUnchecked();
if (last & 1) {
/* The span is the last. */
JS_ASSERT((last & ArenaMask) == ArenaMask);
if (first - 1 == last) {
/* The span is last and empty. The above start != 0 check
* implies that we are not at the end of the address space.
*/
return;
}
size_t spanLength = last - first + 1;
JS_ASSERT(spanLength % Cell::CellSize == 0);
/* Start and end must belong to the same arena. */
JS_ASSERT((first & ~ArenaMask) == arenaAddr);
return;
}
JS_ASSERT(start);
JS_ASSERT(end);
uintptr_t arena = start & ~ArenaMask;
if (!(end & ArenaMask)) {
/* The last span with few free things at the end of the arena. */
JS_ASSERT(arena + ArenaSize == end);
return;
}
/* The span is not the last and we have more spans to follow. */
JS_ASSERT(first <= last);
size_t spanLengthWithoutOneThing = last - first;
JS_ASSERT(spanLengthWithoutOneThing % Cell::CellSize == 0);
/* The span is not last and we have at least one span that follows it.*/
JS_ASSERT(arena == (end & ~ArenaMask));
FreeSpan *next = reinterpret_cast<FreeSpan *>(end);
JS_ASSERT((first & ~ArenaMask) == arenaAddr);
/*
* If there is not enough space before the arena end to allocate one
* more thing, then the span must be marked as the last one to avoid
* storing useless empty span reference.
*/
size_t beforeTail = ArenaSize - (last & ArenaMask);
JS_ASSERT(beforeTail >= sizeof(FreeSpan) + Cell::CellSize);
FreeSpan *next = reinterpret_cast<FreeSpan *>(last);
/*
* The GC things on the list of free spans come from one arena
* and the spans are linked in ascending address order with
* at least one non-free thing between spans.
*/
JS_ASSERT(end < next->start);
JS_ASSERT(last < next->first);
JS_ASSERT(arenaAddr == next->arenaAddressUnchecked());
if (!(next->start & ArenaMask)) {
if (next->first > next->last) {
/*
* The next span is the empty span that terminates the list for
* arenas that do not have any free things at the end.
*/
JS_ASSERT(next->start == next->end);
JS_ASSERT(arena + ArenaSize == next->start);
} else {
/* The next spans is not empty and must starts inside the arena. */
JS_ASSERT(arena == (next->start & ~ArenaMask));
JS_ASSERT(next->first - 1 == next->last);
JS_ASSERT(arenaAddr + ArenaSize == next->first);
}
#endif
}
};
/* Every arena has a header. */
@ -231,12 +352,9 @@ struct ArenaHeader {
/*
* The first span of free things in the arena. We encode it as the start
* and end offsets within the arena, not as FreeSpan structure, to
* minimize the header size. When the arena has no free things, the span
* must be the empty one pointing to the arena's end. For such a span the
* start and end offsets must be ArenaSize.
* minimize the header size.
*/
uint16_t firstFreeSpanStart;
uint16_t firstFreeSpanEnd;
size_t firstFreeSpanOffsets;
/*
* One of FinalizeKind constants or FINALIZE_LIMIT when the arena does not
@ -263,8 +381,12 @@ struct ArenaHeader {
inline void init(JSCompartment *comp, unsigned thingKind, size_t thingSize);
uintptr_t arenaAddress() const {
return address();
}
Arena *getArena() {
return reinterpret_cast<Arena *>(address());
return reinterpret_cast<Arena *>(arenaAddress());
}
unsigned getThingKind() const {
@ -273,26 +395,23 @@ struct ArenaHeader {
}
bool hasFreeThings() const {
return firstFreeSpanStart != ArenaSize;
return firstFreeSpanOffsets != FreeSpan::EmptyOffsets;
}
void setAsFullyUsed() {
firstFreeSpanStart = firstFreeSpanEnd = uint16_t(ArenaSize);
firstFreeSpanOffsets = FreeSpan::EmptyOffsets;
}
FreeSpan getFirstFreeSpan() const {
#ifdef DEBUG
checkSynchronizedWithFreeList();
#endif
return FreeSpan(address() + firstFreeSpanStart, address() + firstFreeSpanEnd);
return FreeSpan::decodeOffsets(arenaAddress(), firstFreeSpanOffsets);
}
void setFirstFreeSpan(const FreeSpan *span) {
span->checkSpan();
JS_ASSERT(span->start - address() <= ArenaSize);
JS_ASSERT(span->end - address() <= ArenaSize);
firstFreeSpanStart = uint16_t(span->start - address());
firstFreeSpanEnd = uint16_t(span->end - address());
JS_ASSERT(span->isWithinArena(arenaAddress()));
firstFreeSpanOffsets = span->encodeAsOffsets();
}
inline MarkingDelay *getMarkingDelay() const;
@ -549,8 +668,7 @@ ArenaHeader::init(JSCompartment *comp, unsigned kind, size_t thingSize)
JS_ASSERT(!getMarkingDelay()->link);
compartment = comp;
thingKind = kind;
firstFreeSpanStart = uint16_t(Arena::thingsStartOffset(thingSize));
firstFreeSpanEnd = uint16_t(ArenaSize);
firstFreeSpanOffsets = FreeSpan::encodeOffsets(Arena::thingsStartOffset(thingSize));
}
inline uintptr_t
@ -806,7 +924,7 @@ struct FreeLists {
void init() {
for (size_t i = 0; i != JS_ARRAY_LENGTH(lists); ++i)
lists[i].start = lists[i].end = 0;
lists[i].initAsEmpty();
}
/*
@ -817,10 +935,10 @@ struct FreeLists {
for (size_t i = 0; i != size_t(FINALIZE_LIMIT); ++i) {
FreeSpan *list = &lists[i];
if (!list->isEmpty()) {
ArenaHeader *aheader = reinterpret_cast<Cell *>(list->start)->arenaHeader();
ArenaHeader *aheader = list->arenaHeader();
JS_ASSERT(!aheader->hasFreeThings());
aheader->setFirstFreeSpan(list);
list->start = list->end = 0;
list->initAsEmpty();
}
}
}
@ -834,7 +952,7 @@ struct FreeLists {
for (size_t i = 0; i != size_t(FINALIZE_LIMIT); ++i) {
FreeSpan *list = &lists[i];
if (!list->isEmpty()) {
ArenaHeader *aheader = reinterpret_cast<Cell *>(list->start)->arenaHeader();
ArenaHeader *aheader = list->arenaHeader();
JS_ASSERT(!aheader->hasFreeThings());
aheader->setFirstFreeSpan(list);
}
@ -849,47 +967,22 @@ struct FreeLists {
for (size_t i = 0; i != size_t(FINALIZE_LIMIT); ++i) {
FreeSpan *list = &lists[i];
if (!list->isEmpty()) {
ArenaHeader *aheader = reinterpret_cast<Cell *>(list->start)->arenaHeader();
#ifdef DEBUG
FreeSpan span(aheader->getFirstFreeSpan());
JS_ASSERT(span.start == list->start);
JS_ASSERT(span.end == list->end);
#endif
ArenaHeader *aheader = list->arenaHeader();
JS_ASSERT(aheader->getFirstFreeSpan().isSameNonEmptySpan(list));
aheader->setAsFullyUsed();
}
}
}
JS_ALWAYS_INLINE Cell *getNext(unsigned thingKind, size_t thingSize) {
FreeSpan *list = &lists[thingKind];
list->checkSpan();
uintptr_t thing = list->start;
if (thing != list->end) {
/*
* We either have at least one thing in the span that ends the
* arena list or we have at least two things in the non-last span.
* In both cases we just need to bump the start pointer to account
* for the allocation.
*/
list->start += thingSize;
JS_ASSERT(list->start <= list->end);
} else if (thing & ArenaMask) {
/*
* The thing points to the last thing in the span that has at
* least one more span to follow. Return the thing and update
* the list with that next span.
*/
*list = *list->nextSpan();
} else {
return NULL;
}
return reinterpret_cast<Cell *>(thing);
JS_ALWAYS_INLINE void *getNext(unsigned thingKind, size_t thingSize) {
return lists[thingKind].allocate(thingSize);
}
Cell *populate(ArenaHeader *aheader, unsigned thingKind, size_t thingSize) {
lists[thingKind] = aheader->getFirstFreeSpan();
void *populate(ArenaHeader *aheader, unsigned thingKind, size_t thingSize) {
FreeSpan *list = &lists[thingKind];
*list = aheader->getFirstFreeSpan();
aheader->setAsFullyUsed();
Cell *t = getNext(thingKind, thingSize);
void *t = list->allocate(thingSize);
JS_ASSERT(t);
return t;
}
@ -902,7 +995,7 @@ struct FreeLists {
}
};
extern Cell *
extern void *
RefillFinalizableFreeList(JSContext *cx, unsigned thingKind);
} /* namespace gc */

Просмотреть файл

@ -203,8 +203,8 @@ NewGCThing(JSContext *cx, unsigned thingKind, size_t thingSize)
js::gc::RunDebugGC(cx);
#endif
js::gc::Cell *cell = cx->compartment->freeLists.getNext(thingKind, thingSize);
return static_cast<T *>(cell ? cell : js::gc::RefillFinalizableFreeList(cx, thingKind));
void *t = cx->compartment->freeLists.getNext(thingKind, thingSize);
return static_cast<T *>(t ? t : js::gc::RefillFinalizableFreeList(cx, thingKind));
}
inline JSObject *