Bug 696162 - Fix jsgcchunk's AllocGCChunk to be more efficient and to avoid potential problems on Mac 10.7. r=igor
authorJustin Lebar <justin.lebar@gmail.com>
Tue, 24 Jan 2012 13:50:45 -0500
changeset 86482 04b4225269a45d7e5c2e2fbbe50a0cb37b881551
parent 86481 79deba0222272dd103c35d8317eff8c487ef2dbf
child 86483 bd75f26eee254c2b9bf1a22e18c304833060a7a0
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersigor
bugs696162
milestone12.0a1
Bug 696162 - Fix jsgcchunk's AllocGCChunk to be more efficient and to avoid potential problems on Mac 10.7. r=igor
js/src/jsgcchunk.cpp
--- a/js/src/jsgcchunk.cpp
+++ b/js/src/jsgcchunk.cpp
@@ -1,10 +1,10 @@
 /* -*- 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>.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -66,21 +66,33 @@
 #  define MAP_NOSYNC    0
 # endif
 
 #endif
 
 #ifdef XP_WIN
 
 static void *
+MapPagesWithFlags(void *addr, size_t size, uint32_t flags)
+{
+    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)
 {
-    void *p = VirtualAlloc(addr, size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
-    JS_ASSERT_IF(p && addr, p == addr);
-    return p;
+    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)
 {
     JS_ALWAYS_TRUE(VirtualFree(addr, 0, MEM_RELEASE));
 }
 
@@ -269,76 +281,203 @@ UnmapPages(void *addr, size_t size)
 #endif
 }
 
 #endif
 
 namespace js {
 namespace gc {
 
+static inline size_t
+ChunkAddrToOffset(void *addr)
+{
+  return reinterpret_cast<uintptr_t>(addr) & ChunkMask;
+}
+
 static inline void *
 FindChunkStart(void *p)
 {
     uintptr_t addr = reinterpret_cast<uintptr_t>(p);
     addr = (addr + ChunkMask) & ~ChunkMask;
     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;
+    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);
+
+        /* Failure here indicates a race with another thread, so try again. */
+    } while(!p);
+
+    JS_ASSERT(ChunkAddrToOffset(p) == 0);
+    return p;
+}
+
 void *
 AllocChunk()
 {
-    void *p;
+    /*
+     * 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.
+     */
 
-#ifdef JS_GC_HAS_MAP_ALIGN
-    p = MapAlignedPages(ChunkSize, ChunkSize);
-    if (!p)
+    /* Fast path; map just one chunk and hope it's aligned. */
+    void *p = MapPages(NULL, ChunkSize);
+    if (!p) {
         return NULL;
-#else
+    }
+
+    /* 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()
+{
     /*
-     * 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.
+     * Map space for two chunks, then unmap around the result so we're left with
+     * space for one chunk.
      */
-    p = MapPages(NULL, ChunkSize);
+
+    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;
 
-    if (reinterpret_cast<uintptr_t>(p) & ChunkMask) {
-        UnmapPages(p, ChunkSize);
-        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);
+    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.
+     */
 
-            /*
-             * Failure here indicates a race with another thread, so
-             * try again.
-             */
-        }
+    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;
     }
-#endif /* !JS_GC_HAS_MAP_ALIGN */
 
-    JS_ASSERT(!(reinterpret_cast<uintptr_t>(p) & ChunkMask));
-    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);
 }
 
 #ifdef XP_WIN
 bool
 CommitMemory(void *addr, size_t size)
 {
     JS_ASSERT(uintptr_t(addr) % 4096UL == 0);