From 6f9b37fd43f71f67644bf30bf86df203098e6d00 Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Tue, 24 Jan 2012 13:50:45 -0500 Subject: [PATCH] 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 --- js/src/jsgcchunk.cpp | 221 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 180 insertions(+), 41 deletions(-) diff --git a/js/src/jsgcchunk.cpp b/js/src/jsgcchunk.cpp index c6c6ba55e6ad..488542d52de9 100644 --- a/js/src/jsgcchunk.cpp +++ b/js/src/jsgcchunk.cpp @@ -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 . @@ -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(addr) & ChunkMask; +} + static inline void * FindChunkStart(void *p) { @@ -282,58 +300,179 @@ FindChunkStart(void *p) return reinterpret_cast(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(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(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(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(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(p) & ChunkMask)); + JS_ASSERT(ChunkAddrToOffset(p) == 0); UnmapPages(p, ChunkSize); }