Use the runtime page size to control arena decommit (RHBZ#911314) Return success when decommit is disabled Author: Gustavo Luiz Duarte Based on Terrence Cole's patch v0 on MOZ#840242 Index: mozilla-release/js/src/gc/Heap.h =================================================================== --- mozilla-release.orig/js/src/gc/Heap.h +++ mozilla-release/js/src/gc/Heap.h @@ -800,7 +800,7 @@ struct Chunk /* Search for a decommitted arena to allocate. */ unsigned findDecommittedArenaOffset(); - ArenaHeader* fetchNextDecommittedArena(); + ArenaHeader* fetchNextDecommittedArena(JSRuntime *rt); public: /* Unlink and return the freeArenasHead. */ Index: mozilla-release/js/src/gc/Memory.cpp =================================================================== --- mozilla-release.orig/js/src/gc/Memory.cpp +++ mozilla-release/js/src/gc/Memory.cpp @@ -8,6 +8,7 @@ #include "mozilla/Assertions.h" #include "jsapi.h" +#include "jscntxt.h" #include "js/HeapAPI.h" #include "js/Utility.h" @@ -18,39 +19,34 @@ using namespace js::gc; /* Unused memory decommiting requires the arena size match the page size. */ static bool -DecommitEnabled() +DecommitEnabled(JSRuntime *rt) { - return PageSize == ArenaSize; + return rt->gcSystemPageSize == ArenaSize; } #if defined(XP_WIN) #include "jswin.h" #include -static size_t AllocationGranularity = 0; - void -gc::InitMemorySubsystem() +gc::InitMemorySubsystem(JSRuntime *rt) { SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); - if (sysinfo.dwPageSize != PageSize) { - fprintf(stderr,"SpiderMonkey compiled with incorrect page size; please update js/public/HeapAPI.h.\n"); - MOZ_CRASH(); - } - AllocationGranularity = sysinfo.dwAllocationGranularity; + rt->gcSystemPageSize = sysinfo.dwPageSize; + rt->gcSystemAllocGranularity = sysinfo.dwAllocationGranularity; } void * -gc::MapAlignedPages(size_t size, size_t alignment) +gc::MapAlignedPages(JSRuntime *rt, size_t size, size_t alignment) { JS_ASSERT(size >= alignment); JS_ASSERT(size % alignment == 0); - JS_ASSERT(size % PageSize == 0); - JS_ASSERT(alignment % AllocationGranularity == 0); + JS_ASSERT(size % rt->gcSystemPageSize == 0); + JS_ASSERT(alignment % rt->gcSystemAllocGranularity == 0); /* Special case: If we want allocation alignment, no further work is needed. */ - if (alignment == AllocationGranularity) { + if (alignment == rt->gcSystemAllocGranularity) { return VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); } @@ -74,7 +70,7 @@ gc::MapAlignedPages(size_t size, size_t if (!p) return NULL; void *chunkStart = (void *)(uintptr_t(p) + (alignment - (uintptr_t(p) % alignment))); - UnmapPages(p, size * 2); + UnmapPages(rt, p, size * 2); p = VirtualAlloc(chunkStart, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); /* Failure here indicates a race with another thread, so try again. */ @@ -85,26 +81,26 @@ gc::MapAlignedPages(size_t size, size_t } void -gc::UnmapPages(void *p, size_t size) +gc::UnmapPages(JSRuntime *rt, void *p, size_t size) { JS_ALWAYS_TRUE(VirtualFree(p, 0, MEM_RELEASE)); } bool -gc::MarkPagesUnused(void *p, size_t size) +gc::MarkPagesUnused(JSRuntime *rt, void *p, size_t size) { - if (!DecommitEnabled()) - return false; + if (!DecommitEnabled(rt)) + return true; - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); LPVOID p2 = VirtualAlloc(p, size, MEM_RESET, PAGE_READWRITE); return p2 == p; } bool -gc::MarkPagesInUse(void *p, size_t size) +gc::MarkPagesInUse(JSRuntime *rt, void *p, size_t size) { - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); return true; } @@ -126,12 +122,13 @@ gc::GetPageFaultCount() #define OS2_MAX_RECURSIONS 16 void -gc::InitMemorySubsystem() +gc::InitMemorySubsystem(JSRuntime *rt) { + rt->gcSystemPageSize = rt->gcSystemAllocGranularity = ArenaSize; } void -gc::UnmapPages(void *addr, size_t size) +gc::UnmapPages(JSRuntime *rt, void *addr, size_t size) { if (!DosFreeMem(addr)) return; @@ -152,7 +149,7 @@ gc::UnmapPages(void *addr, size_t size) } static void * -gc::MapAlignedPagesRecursively(size_t size, size_t alignment, int& recursions) +gc::MapAlignedPagesRecursively(JSRuntime *rt, size_t size, size_t alignment, int& recursions) { if (++recursions >= OS2_MAX_RECURSIONS) return NULL; @@ -178,7 +175,7 @@ gc::MapAlignedPagesRecursively(size_t si unsigned long rc = DosQueryMem(&(static_cast(tmp))[size], &cb, &flags); if (!rc && (flags & PAG_FREE) && cb >= filler) { - UnmapPages(tmp, 0); + UnmapPages(rt, tmp, 0); if (DosAllocMem(&tmp, filler, OBJ_ANY | PAG_COMMIT | PAG_READ | PAG_WRITE)) { JS_ALWAYS_TRUE(DosAllocMem(&tmp, filler, @@ -186,19 +183,19 @@ gc::MapAlignedPagesRecursively(size_t si } } - void *p = MapAlignedPagesRecursively(size, alignment, recursions); - UnmapPages(tmp, 0); + void *p = MapAlignedPagesRecursively(rt, size, alignment, recursions); + UnmapPages(rt, tmp, 0); return p; } void * -gc::MapAlignedPages(size_t size, size_t alignment) +gc::MapAlignedPages(JSRuntime *rt, size_t size, size_t alignment) { JS_ASSERT(size >= alignment); JS_ASSERT(size % alignment == 0); - JS_ASSERT(size % PageSize == 0); - JS_ASSERT(alignment % PageSize == 0); + JS_ASSERT(size % rt->gcSystemPageSize == 0); + JS_ASSERT(alignment % rt->gcSystemAllocGranularity == 0); int recursions = -1; @@ -207,7 +204,7 @@ gc::MapAlignedPages(size_t size, size_t * of the right size by recursively allocating blocks of unaligned * free memory until only an aligned allocation is possible. */ - void *p = MapAlignedPagesRecursively(size, alignment, recursions); + void *p = MapAlignedPagesRecursively(rt, size, alignment, recursions); if (p) return p; @@ -229,16 +226,15 @@ gc::MapAlignedPages(size_t size, size_t } bool -gc::MarkPagesUnused(void *p, size_t size) -{ - JS_ASSERT(uintptr_t(p) % PageSize == 0); +MarkPagesUnused(JSRuntime *rt, void *p, size_t size) { + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); return true; } bool -gc::MarkPagesInUse(void *p, size_t size) +gc::MarkPagesInUse(JSRuntime *rt, void *p, size_t size) { - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); return true; } @@ -258,17 +254,18 @@ gc::GetPageFaultCount() #endif void -gc::InitMemorySubsystem() +gc::InitMemorySubsystem(JSRuntime *rt) { + rt->gcSystemPageSize = rt->gcSystemAllocGranularity = size_t(sysconf(_SC_PAGESIZE)); } void * -gc::MapAlignedPages(size_t size, size_t alignment) +gc::MapAlignedPages(JSRuntime *rt, size_t size, size_t alignment) { JS_ASSERT(size >= alignment); JS_ASSERT(size % alignment == 0); - JS_ASSERT(size % PageSize == 0); - JS_ASSERT(alignment % PageSize == 0); + JS_ASSERT(size % rt->gcSystemPageSize == 0); + JS_ASSERT(alignment % rt->gcSystemAllocGranularity == 0); int prot = PROT_READ | PROT_WRITE; int flags = MAP_PRIVATE | MAP_ANON | MAP_ALIGN | MAP_NOSYNC; @@ -280,22 +277,22 @@ gc::MapAlignedPages(size_t size, size_t } void -gc::UnmapPages(void *p, size_t size) +gc::UnmapPages(JSRuntime *rt, void *p, size_t size) { JS_ALWAYS_TRUE(0 == munmap((caddr_t)p, size)); } bool -gc::MarkPagesUnused(void *p, size_t size) +gc::MarkPagesUnused(JSRuntime *rt, void *p, size_t size) { - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); return true; } bool -gc::MarkPagesInUse(void *p, size_t size) +gc::MarkPagesInUse(JSRuntime *rt, void *p, size_t size) { - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); return true; } @@ -313,27 +310,24 @@ gc::GetPageFaultCount() #include void -gc::InitMemorySubsystem() +gc::InitMemorySubsystem(JSRuntime *rt) { - if (size_t(sysconf(_SC_PAGESIZE)) != PageSize) { - fprintf(stderr,"SpiderMonkey compiled with incorrect page size; please update js/public/HeapAPI.h.\n"); - MOZ_CRASH(); - } + rt->gcSystemPageSize = rt->gcSystemAllocGranularity = size_t(sysconf(_SC_PAGESIZE)); } void * -gc::MapAlignedPages(size_t size, size_t alignment) +gc::MapAlignedPages(JSRuntime *rt, size_t size, size_t alignment) { JS_ASSERT(size >= alignment); JS_ASSERT(size % alignment == 0); - JS_ASSERT(size % PageSize == 0); - JS_ASSERT(alignment % PageSize == 0); + JS_ASSERT(size % rt->gcSystemPageSize == 0); + JS_ASSERT(alignment % rt->gcSystemAllocGranularity == 0); int prot = PROT_READ | PROT_WRITE; int flags = MAP_PRIVATE | MAP_ANON; /* Special case: If we want page alignment, no further work is needed. */ - if (alignment == PageSize) { + if (alignment == rt->gcSystemAllocGranularity) { return mmap(NULL, size, prot, flags, -1, 0); } @@ -359,26 +353,26 @@ gc::MapAlignedPages(size_t size, size_t } void -gc::UnmapPages(void *p, size_t size) +gc::UnmapPages(JSRuntime *rt, void *p, size_t size) { JS_ALWAYS_TRUE(0 == munmap(p, size)); } bool -gc::MarkPagesUnused(void *p, size_t size) +gc::MarkPagesUnused(JSRuntime *rt, void *p, size_t size) { - if (!DecommitEnabled()) - return false; + if (!DecommitEnabled(rt)) + return true; - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); int result = madvise(p, size, MADV_DONTNEED); return result != -1; } bool -gc::MarkPagesInUse(void *p, size_t size) +gc::MarkPagesInUse(JSRuntime *rt, void *p, size_t size) { - JS_ASSERT(uintptr_t(p) % PageSize == 0); + JS_ASSERT(uintptr_t(p) % rt->gcSystemPageSize == 0); return true; } Index: mozilla-release/js/src/gc/Memory.h =================================================================== --- mozilla-release.orig/js/src/gc/Memory.h +++ mozilla-release/js/src/gc/Memory.h @@ -16,20 +16,20 @@ namespace gc { // Sanity check that our compiled configuration matches the currently running // instance and initialize any runtime data needed for allocation. -void InitMemorySubsystem(); +void InitMemorySubsystem(JSRuntime *rt); // Allocate or deallocate pages from the system with the given alignment. -void *MapAlignedPages(size_t size, size_t alignment); -void UnmapPages(void *p, size_t size); +void *MapAlignedPages(JSRuntime *rt, size_t size, size_t alignment); +void UnmapPages(JSRuntime *rt, void *p, size_t size); // Tell the OS that the given pages are not in use, so they should not // be written to a paging file. This may be a no-op on some platforms. -bool MarkPagesUnused(void *p, size_t size); +bool MarkPagesUnused(JSRuntime *rt, void *p, size_t size); // Undo |MarkPagesUnused|: tell the OS that the given pages are of interest // and should be paged in and out normally. This may be a no-op on some // platforms. -bool MarkPagesInUse(void *p, size_t size); +bool MarkPagesInUse(JSRuntime *rt, void *p, size_t size); // Returns #(hard faults) + #(soft faults) size_t GetPageFaultCount(); Index: mozilla-release/js/src/jsapi.cpp =================================================================== --- mozilla-release.orig/js/src/jsapi.cpp +++ mozilla-release/js/src/jsapi.cpp @@ -1113,8 +1113,6 @@ JS_NewRuntime(uint32_t maxbytes, JSUseHe #undef MSG_DEF #endif /* DEBUG */ - InitMemorySubsystem(); - if (!js::TlsPerThreadData.init()) return NULL; Index: mozilla-release/js/src/jscntxt.h =================================================================== --- mozilla-release.orig/js/src/jscntxt.h +++ mozilla-release/js/src/jscntxt.h @@ -873,6 +873,15 @@ struct JSRuntime : js::RuntimeFriendFiel /* Stack of thread-stack-allocated GC roots. */ js::AutoGCRooter *autoGCRooters; + /* + * The GC can only safely decommit memory when the page size of the + * running process matches the compiled arena size. + */ + size_t gcSystemPageSize; + + /* The OS allocation granularity may not match the page size. */ + size_t gcSystemAllocGranularity; + /* Strong references on scripts held for PCCount profiling API. */ js::ScriptAndCountsVector *scriptAndCountsVector; Index: mozilla-release/js/src/jsgc.cpp =================================================================== --- mozilla-release.orig/js/src/jsgc.cpp +++ mozilla-release/js/src/jsgc.cpp @@ -477,13 +477,13 @@ FinalizeArenas(FreeOp *fop, } static inline Chunk * -AllocChunk() { - return static_cast(MapAlignedPages(ChunkSize, ChunkSize)); +AllocChunk(JSRuntime *rt) { + return static_cast(MapAlignedPages(rt, ChunkSize, ChunkSize)); } static inline void -FreeChunk(Chunk *p) { - UnmapPages(static_cast(p), ChunkSize); +FreeChunk(JSRuntime *rt, Chunk *p) { + UnmapPages(rt, static_cast(p), ChunkSize); } inline bool @@ -573,25 +573,25 @@ ChunkPool::expire(JSRuntime *rt, bool re } static void -FreeChunkList(Chunk *chunkListHead) +FreeChunkList(JSRuntime *rt, Chunk *chunkListHead) { while (Chunk *chunk = chunkListHead) { JS_ASSERT(!chunk->info.numArenasFreeCommitted); chunkListHead = chunk->info.next; - FreeChunk(chunk); + FreeChunk(rt, chunk); } } void ChunkPool::expireAndFree(JSRuntime *rt, bool releaseAll) { - FreeChunkList(expire(rt, releaseAll)); + FreeChunkList(rt, expire(rt, releaseAll)); } /* static */ Chunk * Chunk::allocate(JSRuntime *rt) { - Chunk *chunk = static_cast(AllocChunk()); + Chunk *chunk = static_cast(AllocChunk(rt)); #ifdef JSGC_ROOT_ANALYSIS // Our poison pointers are not guaranteed to be invalid on 64-bit @@ -604,7 +604,7 @@ Chunk::allocate(JSRuntime *rt) // were marked as uncommitted, but it's a little complicated to avoid // clobbering pre-existing unrelated mappings. while (IsPoisonedPtr(chunk)) - chunk = static_cast(AllocChunk()); + chunk = static_cast(AllocChunk(rt)); #endif if (!chunk) @@ -620,7 +620,7 @@ Chunk::release(JSRuntime *rt, Chunk *chu { JS_ASSERT(chunk); chunk->prepareToBeFreed(rt); - FreeChunk(chunk); + FreeChunk(rt, chunk); } inline void @@ -736,7 +736,7 @@ Chunk::findDecommittedArenaOffset() } ArenaHeader * -Chunk::fetchNextDecommittedArena() +Chunk::fetchNextDecommittedArena(JSRuntime *rt) { JS_ASSERT(info.numArenasFreeCommitted == 0); JS_ASSERT(info.numArenasFree > 0); @@ -747,7 +747,7 @@ Chunk::fetchNextDecommittedArena() decommittedArenas.unset(offset); Arena *arena = &arenas[offset]; - MarkPagesInUse(arena, ArenaSize); + MarkPagesInUse(rt, arena, ArenaSize); arena->aheader.setAsNotAllocated(); return &arena->aheader; @@ -781,7 +781,7 @@ Chunk::allocateArena(JSCompartment *comp ArenaHeader *aheader = JS_LIKELY(info.numArenasFreeCommitted > 0) ? fetchNextFreeArena(rt) - : fetchNextDecommittedArena(); + : fetchNextDecommittedArena(rt); aheader->init(comp, thingKind); if (JS_UNLIKELY(!hasAvailableArenas())) removeFromAvailableList(); @@ -881,6 +881,8 @@ static const int64_t JIT_SCRIPT_RELEASE_ JSBool js_InitGC(JSRuntime *rt, uint32_t maxbytes) { + InitMemorySubsystem(rt); + if (!rt->gcChunkSet.init(INITIAL_CHUNK_CAPACITY)) return false; @@ -1969,7 +1971,7 @@ DecommitArenasFromAvailableList(JSRuntim Maybe maybeUnlock; if (!rt->isHeapBusy()) maybeUnlock.construct(rt); - ok = MarkPagesUnused(aheader->getArena(), ArenaSize); + ok = MarkPagesUnused(rt, aheader->getArena(), ArenaSize); } if (ok) { @@ -2037,7 +2039,7 @@ ExpireChunksAndArenas(JSRuntime *rt, boo { if (Chunk *toFree = rt->gcChunkPool.expire(rt, shouldShrink)) { AutoUnlockGC unlock(rt); - FreeChunkList(toFree); + FreeChunkList(rt, toFree); } if (shouldShrink) Index: mozilla-release/js/public/HeapAPI.h =================================================================== --- mozilla-release.orig/js/public/HeapAPI.h +++ mozilla-release/js/public/HeapAPI.h @@ -13,24 +13,7 @@ namespace js { namespace gc { -/* - * Page size must be static to support our arena pointer optimizations, so we - * are forced to support each platform with non-4096 pages as a special case. - * Note: The freelist supports a maximum arena shift of 15. - * Note: Do not use JS_CPU_SPARC here, this header is used outside JS. - */ -#if (defined(SOLARIS) || defined(__FreeBSD__)) && \ - (defined(__sparc) || defined(__sparcv9) || defined(__ia64)) -const size_t PageShift = 13; -const size_t ArenaShift = PageShift; -#elif defined(__powerpc64__) -const size_t PageShift = 16; const size_t ArenaShift = 12; -#else -const size_t PageShift = 12; -const size_t ArenaShift = PageShift; -#endif -const size_t PageSize = size_t(1) << PageShift; const size_t ArenaSize = size_t(1) << ArenaShift; const size_t ArenaMask = ArenaSize - 1;