Bug 696162 - Fix jsgcchunk's AllocGCChunk to be more efficient and to avoid potential problems on Mac 10.7. r=igor

--HG--
extra : rebase_source : 13160f0e9d8b09ed31359daf451adff3e68de30d
This commit is contained in:
Justin Lebar 2012-01-24 13:50:45 -05:00
parent 0d7b41f9b5
commit 6d8f20fd40

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=99 ft=cpp:
* vim: set ts=4 sw=4 sts=4 et tw=99 ft=cpp:
*
* ***** BEGIN LICENSE BLOCK *****
* Copyright (C) 2006-2008 Jason Evans <jasone@FreeBSD.org>.
@ -71,13 +71,25 @@
#ifdef XP_WIN
static void *
MapPages(void *addr, size_t size)
MapPagesWithFlags(void *addr, size_t size, uint32_t flags)
{
void *p = VirtualAlloc(addr, size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
void *p = VirtualAlloc(addr, size, flags, PAGE_READWRITE);
JS_ASSERT_IF(p && addr, p == addr);
return p;
}
static void *
MapPages(void *addr, size_t size)
{
return MapPagesWithFlags(addr, size, MEM_COMMIT | MEM_RESERVE);
}
static void *
MapPagesUncommitted(void *addr, size_t size)
{
return MapPagesWithFlags(addr, size, MEM_RESERVE);
}
static void
UnmapPages(void *addr, size_t size)
{
@ -274,6 +286,12 @@ UnmapPages(void *addr, size_t size)
namespace js {
namespace gc {
static inline size_t
ChunkAddrToOffset(void *addr)
{
return reinterpret_cast<uintptr_t>(addr) & ChunkMask;
}
static inline void *
FindChunkStart(void *p)
{
@ -282,58 +300,179 @@ FindChunkStart(void *p)
return reinterpret_cast<void *>(addr);
}
#if defined(JS_GC_HAS_MAP_ALIGN)
void *
AllocChunk()
{
void *p = MapAlignedPages(ChunkSize, ChunkSize);
JS_ASSERT(ChunkAddrToOffset(p) == 0);
return p;
}
#elif defined(XP_WIN)
void *
AllocChunkSlow()
{
void *p;
#ifdef JS_GC_HAS_MAP_ALIGN
p = MapAlignedPages(ChunkSize, ChunkSize);
if (!p)
return NULL;
#else
/*
* Windows requires that there be a 1:1 mapping between VM allocation
* and deallocation operations. Therefore, take care here to acquire the
* final result via one mapping operation. This means unmapping any
* preliminary result that is not correctly aligned.
*/
p = MapPages(NULL, ChunkSize);
if (!p)
return NULL;
if (reinterpret_cast<uintptr_t>(p) & ChunkMask) {
UnmapPages(p, ChunkSize);
do {
/*
* Over-allocate in order to map a memory region that is definitely
* large enough then deallocate and allocate again the correct size,
* within the over-sized mapping.
*
* Since we're going to unmap the whole thing anyway, the first
* mapping doesn't have to commit pages.
*/
p = MapPagesUncommitted(NULL, ChunkSize * 2);
if (!p)
return NULL;
UnmapPages(p, ChunkSize * 2);
p = MapPages(FindChunkStart(p), ChunkSize);
while (!p) {
/*
* Over-allocate in order to map a memory region that is
* definitely large enough then deallocate and allocate again the
* correct size, within the over-sized mapping.
*/
p = MapPages(NULL, ChunkSize * 2);
if (!p)
return 0;
UnmapPages(p, ChunkSize * 2);
p = MapPages(FindChunkStart(p), ChunkSize);
/*
* Failure here indicates a race with another thread, so
* try again.
*/
}
}
#endif /* !JS_GC_HAS_MAP_ALIGN */
/* Failure here indicates a race with another thread, so try again. */
} while(!p);
JS_ASSERT(!(reinterpret_cast<uintptr_t>(p) & ChunkMask));
JS_ASSERT(ChunkAddrToOffset(p) == 0);
return p;
}
void *
AllocChunk()
{
/*
* Like the *nix AllocChunk implementation, this version of AllocChunk has
* a fast and a slow path. We always try the fast path first, then fall
* back to the slow path if the fast one failed.
*
* Our implementation for Windows is complicated by the fact that Windows
* requires there be a 1:1 mapping between VM allocation and deallocation
* operations.
*
* This restriction means we must acquire the final result via exactly one
* mapping operation, so we can't use some of the tricks we play in the
* *nix implementation.
*/
/* Fast path; map just one chunk and hope it's aligned. */
void *p = MapPages(NULL, ChunkSize);
if (!p) {
return NULL;
}
/* If that chunk was properly aligned, we're all done. */
if (ChunkAddrToOffset(p) == 0) {
return p;
}
/*
* Fast path didn't work. See if we can map into the next aligned spot
* past the address we were given. If not, fall back to the slow but
* reliable method.
*
* Notice that we have to unmap before we remap, due to Windows's
* restriction that there be a 1:1 mapping between VM alloc and dealloc
* operations.
*/
UnmapPages(p, ChunkSize);
p = MapPages(FindChunkStart(p), ChunkSize);
if (p) {
JS_ASSERT(ChunkAddrToOffset(p) == 0);
return p;
}
/* When all else fails... */
return AllocChunkSlow();
}
#else /* not JS_GC_HAS_MAP_ALIGN and not Windows */
inline static void *
AllocChunkSlow()
{
/*
* Map space for two chunks, then unmap around the result so we're left with
* space for one chunk.
*/
char *p = reinterpret_cast<char*>(MapPages(NULL, ChunkSize * 2));
if (p == NULL)
return NULL;
size_t offset = ChunkAddrToOffset(p);
if (offset == 0) {
/* Trailing space only. */
UnmapPages(p + ChunkSize, ChunkSize);
return p;
}
/* Leading space. */
UnmapPages(p, ChunkSize - offset);
p += ChunkSize - offset;
/* Trailing space. */
UnmapPages(p + ChunkSize, offset);
JS_ASSERT(ChunkAddrToOffset(p) == 0);
return p;
}
void *
AllocChunk()
{
/*
* We can take either a fast or a slow path here. The fast path sometimes
* fails; when it does, we fall back to the slow path.
*
* jemalloc uses a heuristic in which we bypass the fast path if, last
* time we called AllocChunk() on this thread, the fast path would have
* failed. But here in the js engine, we always try the fast path before
* falling back to the slow path, because it's not clear that jemalloc's
* heuristic is helpful to us.
*/
/* Fast path; just allocate one chunk and hope it's aligned. */
char *p = reinterpret_cast<char*>(MapPages(NULL, ChunkSize));
if (!p)
return NULL;
size_t offset = ChunkAddrToOffset(p);
if (offset == 0) {
/* Fast path worked! */
return p;
}
/*
* We allocated a chunk, but not at the correct alignment. Try to extend
* the tail end of the chunk and then unmap the beginning so that we have
* an aligned chunk. If that fails, do the slow version of AllocChunk.
*/
if (MapPages(p + ChunkSize, ChunkSize - offset) != NULL) {
/* We extended the mapping! Clean up leading space and we're done. */
UnmapPages(p, ChunkSize - offset);
p += ChunkSize - offset;
JS_ASSERT(ChunkAddrToOffset(p) == 0);
return p;
}
/*
* Extension failed. Clean up, then revert to the reliable-but-expensive
* method.
*/
UnmapPages(p, ChunkSize);
return AllocChunkSlow();
}
#endif
void
FreeChunk(void *p)
{
JS_ASSERT(p);
JS_ASSERT(!(reinterpret_cast<uintptr_t>(p) & ChunkMask));
JS_ASSERT(ChunkAddrToOffset(p) == 0);
UnmapPages(p, ChunkSize);
}