aboutsummaryrefslogtreecommitdiffstats
path: root/tools/node_modules/expresso/deps/jscoverage/js/jsgc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/expresso/deps/jscoverage/js/jsgc.cpp')
-rw-r--r--tools/node_modules/expresso/deps/jscoverage/js/jsgc.cpp3763
1 files changed, 3763 insertions, 0 deletions
diff --git a/tools/node_modules/expresso/deps/jscoverage/js/jsgc.cpp b/tools/node_modules/expresso/deps/jscoverage/js/jsgc.cpp
new file mode 100644
index 0000000..ac29021
--- /dev/null
+++ b/tools/node_modules/expresso/deps/jscoverage/js/jsgc.cpp
@@ -0,0 +1,3763 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * JS Mark-and-Sweep Garbage Collector.
+ *
+ * This GC allocates fixed-sized things with sizes up to GC_NBYTES_MAX (see
+ * jsgc.h). It allocates from a special GC arena pool with each arena allocated
+ * using malloc. It uses an ideally parallel array of flag bytes to hold the
+ * mark bit, finalizer type index, etc.
+ *
+ * XXX swizzle page to freelist for better locality of reference
+ */
+#include "jsstddef.h"
+#include <stdlib.h> /* for free */
+#include <math.h>
+#include <string.h> /* for memset used when DEBUG */
+#include "jstypes.h"
+#include "jsutil.h" /* Added by JSIFY */
+#include "jshash.h" /* Added by JSIFY */
+#include "jsbit.h"
+#include "jsclist.h"
+#include "jsprf.h"
+#include "jsapi.h"
+#include "jsatom.h"
+#include "jscntxt.h"
+#include "jsversion.h"
+#include "jsdbgapi.h"
+#include "jsexn.h"
+#include "jsfun.h"
+#include "jsgc.h"
+#include "jsinterp.h"
+#include "jsiter.h"
+#include "jslock.h"
+#include "jsnum.h"
+#include "jsobj.h"
+#include "jsparse.h"
+#include "jsscope.h"
+#include "jsscript.h"
+#include "jsstr.h"
+#include "jstracer.h"
+
+#if JS_HAS_XML_SUPPORT
+#include "jsxml.h"
+#endif
+
+/*
+ * Check if posix_memalign is available.
+ */
+#if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || MOZ_MEMORY
+# define HAS_POSIX_MEMALIGN 1
+#else
+# define HAS_POSIX_MEMALIGN 0
+#endif
+
+/*
+ * jemalloc provides posix_memalign.
+ */
+#ifdef MOZ_MEMORY
+extern "C" {
+#include "../../memory/jemalloc/jemalloc.h"
+}
+#endif
+
+/*
+ * Include the headers for mmap unless we have posix_memalign and do not
+ * insist on mmap.
+ */
+#if JS_GC_USE_MMAP || (!defined JS_GC_USE_MMAP && !HAS_POSIX_MEMALIGN)
+# if defined(XP_WIN)
+# ifndef JS_GC_USE_MMAP
+# define JS_GC_USE_MMAP 1
+# endif
+# include <windows.h>
+# else
+# if defined(XP_UNIX) || defined(XP_BEOS)
+# include <unistd.h>
+# endif
+# if _POSIX_MAPPED_FILES > 0
+# ifndef JS_GC_USE_MMAP
+# define JS_GC_USE_MMAP 1
+# endif
+# include <sys/mman.h>
+
+/* On Mac OS X MAP_ANONYMOUS is not defined. */
+# if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+# define MAP_ANONYMOUS MAP_ANON
+# endif
+# else
+# if JS_GC_USE_MMAP
+# error "JS_GC_USE_MMAP is set when mmap is not available"
+# endif
+# endif
+# endif
+#endif
+
+/*
+ * A GC arena contains a fixed number of flag bits for each thing in its heap,
+ * and supports O(1) lookup of a flag given its thing's address.
+ *
+ * To implement this, we allocate things of the same size from a GC arena
+ * containing GC_ARENA_SIZE bytes aligned on GC_ARENA_SIZE boundary. The
+ * following picture shows arena's layout:
+ *
+ * +------------------------------+--------------------+---------------+
+ * | allocation area for GC thing | flags of GC things | JSGCArenaInfo |
+ * +------------------------------+--------------------+---------------+
+ *
+ * To find the flag bits for the thing we calculate the thing index counting
+ * from arena's start using:
+ *
+ * thingIndex = (thingAddress & GC_ARENA_MASK) / thingSize
+ *
+ * The details of flag's lookup depend on thing's kind. For all GC things
+ * except doubles we use one byte of flags where the 4 bits determine thing's
+ * type and the rest is used to implement GC marking, finalization and
+ * locking. We calculate the address of flag's byte using:
+ *
+ * flagByteAddress =
+ * (thingAddress | GC_ARENA_MASK) - sizeof(JSGCArenaInfo) - thingIndex
+ *
+ * where
+ *
+ * (thingAddress | GC_ARENA_MASK) - sizeof(JSGCArenaInfo)
+ *
+ * is the last byte of flags' area.
+ *
+ * This implies that the things are allocated from the start of their area and
+ * flags are allocated from the end. This arrangement avoids a relatively
+ * expensive calculation of the location of the boundary separating things and
+ * flags. The boundary's offset from the start of the arena is given by:
+ *
+ * thingsPerArena * thingSize
+ *
+ * where thingsPerArena is the number of things that the arena can hold:
+ *
+ * (GC_ARENA_SIZE - sizeof(JSGCArenaInfo)) / (thingSize + 1).
+ *
+ * To allocate doubles we use a specialized arena. It can contain only numbers
+ * so we do not need the type bits. Moreover, since the doubles do not require
+ * a finalizer and very few of them are locked via js_LockGCThing API, we use
+ * just one bit of flags per double to denote if it was marked during the
+ * marking phase of the GC. The locking is implemented via a hash table. Thus
+ * for doubles the flag area becomes a bitmap.
+ *
+ * JS_GC_USE_MMAP macro governs the choice of the aligned arena allocator.
+ * When it is true, a platform-dependent function like mmap is used to get
+ * memory aligned on CPU page boundaries. If the macro is false or undefined,
+ * posix_memalign is used when available. Otherwise the code uses malloc to
+ * over-allocate a chunk with js_gcArenasPerChunk aligned arenas. The
+ * approximate space overhead of this is 1/js_gcArenasPerChunk. For details,
+ * see NewGCChunk/DestroyGCChunk below.
+ *
+ * The code also allocates arenas in chunks when JS_GC_USE_MMAP is 1 to
+ * minimize the overhead of mmap/munmap. In this case js_gcArenasPerChunk can
+ * not be a compile-time constant as the system page size is not known until
+ * runtime.
+ */
+#if JS_GC_USE_MMAP
+static uint32 js_gcArenasPerChunk = 0;
+static JSBool js_gcUseMmap = JS_FALSE;
+#elif HAS_POSIX_MEMALIGN
+# define js_gcArenasPerChunk 1
+#else
+# define js_gcArenasPerChunk 7
+#endif
+
+#if defined(js_gcArenasPerChunk) && js_gcArenasPerChunk == 1
+# define CHUNKED_ARENA_ALLOCATION 0
+#else
+# define CHUNKED_ARENA_ALLOCATION 1
+#endif
+
+#define GC_ARENA_SHIFT 12
+#define GC_ARENA_MASK ((jsuword) JS_BITMASK(GC_ARENA_SHIFT))
+#define GC_ARENA_SIZE JS_BIT(GC_ARENA_SHIFT)
+
+/*
+ * JS_GC_ARENA_PAD defines the number of bytes to pad JSGCArenaInfo structure.
+ * It is used to improve allocation efficiency when using posix_memalign. If
+ * malloc's implementation uses internal headers, then calling
+ *
+ * posix_memalign(&p, GC_ARENA_SIZE, GC_ARENA_SIZE * js_gcArenasPerChunk)
+ *
+ * in a sequence leaves holes between allocations of the size GC_ARENA_SIZE
+ * due to the need to fit headers. JS_GC_ARENA_PAD mitigates that so the code
+ * calls
+ *
+ * posix_memalign(&p, GC_ARENA_SIZE,
+ * GC_ARENA_SIZE * js_gcArenasPerChunk - JS_GC_ARENA_PAD)
+ *
+ * When JS_GC_ARENA_PAD is equal or greater than the number of words in the
+ * system header, the system can pack all allocations together without holes.
+ *
+ * With JS_GC_USE_MEMALIGN we want at least 2 word pad unless posix_memalign
+ * comes from jemalloc that does not use any headers/trailers.
+ */
+#ifndef JS_GC_ARENA_PAD
+# if HAS_POSIX_MEMALIGN && !MOZ_MEMORY
+# define JS_GC_ARENA_PAD (2 * JS_BYTES_PER_WORD)
+# else
+# define JS_GC_ARENA_PAD 0
+# endif
+#endif
+
+struct JSGCArenaInfo {
+ /*
+ * Allocation list for the arena or NULL if the arena holds double values.
+ */
+ JSGCArenaList *list;
+
+ /*
+ * Pointer to the previous arena in a linked list. The arena can either
+ * belong to one of JSContext.gcArenaList lists or, when it does not have
+ * any allocated GC things, to the list of free arenas in the chunk with
+ * head stored in JSGCChunkInfo.lastFreeArena.
+ */
+ JSGCArenaInfo *prev;
+
+#if !CHUNKED_ARENA_ALLOCATION
+ jsuword prevUntracedPage;
+#else
+ /*
+ * A link field for the list of arenas with marked but not yet traced
+ * things. The field is encoded as arena's page to share the space with
+ * firstArena and arenaIndex fields.
+ */
+ jsuword prevUntracedPage : JS_BITS_PER_WORD - GC_ARENA_SHIFT;
+
+ /*
+ * When firstArena is false, the index of arena in the chunk. When
+ * firstArena is true, the index of a free arena holding JSGCChunkInfo or
+ * NO_FREE_ARENAS if there are no free arenas in the chunk.
+ *
+ * GET_ARENA_INDEX and GET_CHUNK_INFO_INDEX are convenience macros to
+ * access either of indexes.
+ */
+ jsuword arenaIndex : GC_ARENA_SHIFT - 1;
+
+ /* Flag indicating if the arena is the first in the chunk. */
+ jsuword firstArena : 1;
+#endif
+
+ union {
+ jsuword untracedThings; /* bitset for fast search of marked
+ but not yet traced things */
+ JSBool hasMarkedDoubles; /* the arena has marked doubles */
+ } u;
+
+#if JS_GC_ARENA_PAD != 0
+ uint8 pad[JS_GC_ARENA_PAD];
+#endif
+};
+
+/*
+ * Verify that the bit fields are indeed shared and JSGCArenaInfo is as small
+ * as possible. The code does not rely on this check so if on a particular
+ * platform this does not compile, then, as a workaround, comment the assert
+ * out and submit a bug report.
+ */
+JS_STATIC_ASSERT(offsetof(JSGCArenaInfo, u) == 3 * sizeof(jsuword));
+
+/*
+ * Macros to convert between JSGCArenaInfo, the start address of the arena and
+ * arena's page defined as (start address) >> GC_ARENA_SHIFT.
+ */
+#define ARENA_INFO_OFFSET (GC_ARENA_SIZE - (uint32) sizeof(JSGCArenaInfo))
+
+#define IS_ARENA_INFO_ADDRESS(arena) \
+ (((jsuword) (arena) & GC_ARENA_MASK) == ARENA_INFO_OFFSET)
+
+#define ARENA_START_TO_INFO(arenaStart) \
+ (JS_ASSERT(((arenaStart) & (jsuword) GC_ARENA_MASK) == 0), \
+ (JSGCArenaInfo *) ((arenaStart) + (jsuword) ARENA_INFO_OFFSET))
+
+#define ARENA_INFO_TO_START(arena) \
+ (JS_ASSERT(IS_ARENA_INFO_ADDRESS(arena)), \
+ (jsuword) (arena) & ~(jsuword) GC_ARENA_MASK)
+
+#define ARENA_PAGE_TO_INFO(arenaPage) \
+ (JS_ASSERT(arenaPage != 0), \
+ JS_ASSERT(!((jsuword)(arenaPage) >> (JS_BITS_PER_WORD-GC_ARENA_SHIFT))), \
+ ARENA_START_TO_INFO((arenaPage) << GC_ARENA_SHIFT))
+
+#define ARENA_INFO_TO_PAGE(arena) \
+ (JS_ASSERT(IS_ARENA_INFO_ADDRESS(arena)), \
+ ((jsuword) (arena) >> GC_ARENA_SHIFT))
+
+#define GET_ARENA_INFO(chunk, index) \
+ (JS_ASSERT((index) < js_gcArenasPerChunk), \
+ ARENA_START_TO_INFO(chunk + ((index) << GC_ARENA_SHIFT)))
+
+#if CHUNKED_ARENA_ALLOCATION
+/*
+ * Definitions for allocating arenas in chunks.
+ *
+ * All chunks that have at least one free arena are put on the doubly-linked
+ * list with the head stored in JSRuntime.gcChunkList. JSGCChunkInfo contains
+ * the head of the chunk's free arena list together with the link fields for
+ * gcChunkList.
+ *
+ * Structure stored in one of chunk's free arenas. GET_CHUNK_INFO_INDEX gives
+ * the index of this arena. When all arenas in the chunk are used, it is
+ * removed from the list and the index is set to NO_FREE_ARENAS indicating
+ * that the chunk is not on gcChunkList and has no JSGCChunkInfo available.
+ */
+
+struct JSGCChunkInfo {
+ JSGCChunkInfo **prevp;
+ JSGCChunkInfo *next;
+ JSGCArenaInfo *lastFreeArena;
+ uint32 numFreeArenas;
+};
+
+#define NO_FREE_ARENAS JS_BITMASK(GC_ARENA_SHIFT - 1)
+
+#ifdef js_gcArenasPerChunk
+JS_STATIC_ASSERT(1 <= js_gcArenasPerChunk &&
+ js_gcArenasPerChunk <= NO_FREE_ARENAS);
+#endif
+
+#define GET_ARENA_CHUNK(arena, index) \
+ (JS_ASSERT(GET_ARENA_INDEX(arena) == index), \
+ ARENA_INFO_TO_START(arena) - ((index) << GC_ARENA_SHIFT))
+
+#define GET_ARENA_INDEX(arena) \
+ ((arena)->firstArena ? 0 : (uint32) (arena)->arenaIndex)
+
+#define GET_CHUNK_INFO_INDEX(chunk) \
+ ((uint32) ARENA_START_TO_INFO(chunk)->arenaIndex)
+
+#define SET_CHUNK_INFO_INDEX(chunk, index) \
+ (JS_ASSERT((index) < js_gcArenasPerChunk || (index) == NO_FREE_ARENAS), \
+ (void) (ARENA_START_TO_INFO(chunk)->arenaIndex = (jsuword) (index)))
+
+#define GET_CHUNK_INFO(chunk, infoIndex) \
+ (JS_ASSERT(GET_CHUNK_INFO_INDEX(chunk) == (infoIndex)), \
+ JS_ASSERT((uint32) (infoIndex) < js_gcArenasPerChunk), \
+ (JSGCChunkInfo *) ((chunk) + ((infoIndex) << GC_ARENA_SHIFT)))
+
+#define CHUNK_INFO_TO_INDEX(ci) \
+ GET_ARENA_INDEX(ARENA_START_TO_INFO((jsuword)ci))
+
+#endif
+
+/*
+ * Macros for GC-thing operations.
+ */
+#define THINGS_PER_ARENA(thingSize) \
+ ((GC_ARENA_SIZE - (uint32) sizeof(JSGCArenaInfo)) / ((thingSize) + 1U))
+
+#define THING_TO_ARENA(thing) \
+ ((JSGCArenaInfo *)(((jsuword) (thing) | GC_ARENA_MASK) + \
+ 1 - sizeof(JSGCArenaInfo)))
+
+#define THING_TO_INDEX(thing, thingSize) \
+ ((uint32) ((jsuword) (thing) & GC_ARENA_MASK) / (uint32) (thingSize))
+
+#define THING_FLAGS_END(arena) ((uint8 *)(arena))
+
+#define THING_FLAGP(arena, thingIndex) \
+ (JS_ASSERT((jsuword) (thingIndex) \
+ < (jsuword) THINGS_PER_ARENA((arena)->list->thingSize)), \
+ (uint8 *)(arena) - 1 - (thingIndex))
+
+#define THING_TO_FLAGP(thing, thingSize) \
+ THING_FLAGP(THING_TO_ARENA(thing), THING_TO_INDEX(thing, thingSize))
+
+#define FLAGP_TO_ARENA(flagp) THING_TO_ARENA(flagp)
+
+#define FLAGP_TO_INDEX(flagp) \
+ (JS_ASSERT(((jsuword) (flagp) & GC_ARENA_MASK) < ARENA_INFO_OFFSET), \
+ (ARENA_INFO_OFFSET - 1 - (uint32) ((jsuword) (flagp) & GC_ARENA_MASK)))
+
+#define FLAGP_TO_THING(flagp, thingSize) \
+ (JS_ASSERT(((jsuword) (flagp) & GC_ARENA_MASK) >= \
+ (ARENA_INFO_OFFSET - THINGS_PER_ARENA(thingSize))), \
+ (JSGCThing *)(((jsuword) (flagp) & ~GC_ARENA_MASK) + \
+ (thingSize) * FLAGP_TO_INDEX(flagp)))
+
+/*
+ * Macros for the specialized arena for doubles.
+ *
+ * DOUBLES_PER_ARENA defines the maximum number of doubles that the arena can
+ * hold. We find it as the following. Let n be the number of doubles in the
+ * arena. Together with the bitmap of flags and JSGCArenaInfo they should fit
+ * the arena. Hence DOUBLES_PER_ARENA or n_max is the maximum value of n for
+ * which the following holds:
+ *
+ * n*s + ceil(n/B) <= M (1)
+ *
+ * where "/" denotes normal real division,
+ * ceil(r) gives the least integer not smaller than the number r,
+ * s is the number of words in jsdouble,
+ * B is number of bits per word or B == JS_BITS_PER_WORD
+ * M is the number of words in the arena before JSGCArenaInfo or
+ * M == (GC_ARENA_SIZE - sizeof(JSGCArenaInfo)) / sizeof(jsuword).
+ * M == ARENA_INFO_OFFSET / sizeof(jsuword)
+ *
+ * We rewrite the inequality as
+ *
+ * n*B*s/B + ceil(n/B) <= M,
+ * ceil(n*B*s/B + n/B) <= M,
+ * ceil(n*(B*s + 1)/B) <= M (2)
+ *
+ * We define a helper function e(n, s, B),
+ *
+ * e(n, s, B) := ceil(n*(B*s + 1)/B) - n*(B*s + 1)/B, 0 <= e(n, s, B) < 1.
+ *
+ * It gives:
+ *
+ * n*(B*s + 1)/B + e(n, s, B) <= M,
+ * n + e*B/(B*s + 1) <= M*B/(B*s + 1)
+ *
+ * We apply the floor function to both sides of the last equation, where
+ * floor(r) gives the biggest integer not greater than r. As a consequence we
+ * have:
+ *
+ * floor(n + e*B/(B*s + 1)) <= floor(M*B/(B*s + 1)),
+ * n + floor(e*B/(B*s + 1)) <= floor(M*B/(B*s + 1)),
+ * n <= floor(M*B/(B*s + 1)), (3)
+ *
+ * where floor(e*B/(B*s + 1)) is zero as e*B/(B*s + 1) < B/(B*s + 1) < 1.
+ * Thus any n that satisfies the original constraint (1) or its equivalent (2),
+ * must also satisfy (3). That is, we got an upper estimate for the maximum
+ * value of n. Lets show that this upper estimate,
+ *
+ * floor(M*B/(B*s + 1)), (4)
+ *
+ * also satisfies (1) and, as such, gives the required maximum value.
+ * Substituting it into (2) gives:
+ *
+ * ceil(floor(M*B/(B*s + 1))*(B*s + 1)/B) == ceil(floor(M/X)*X)
+ *
+ * where X == (B*s + 1)/B > 1. But then floor(M/X)*X <= M/X*X == M and
+ *
+ * ceil(floor(M/X)*X) <= ceil(M) == M.
+ *
+ * Thus the value of (4) gives the maximum n satisfying (1).
+ *
+ * For the final result we observe that in (4)
+ *
+ * M*B == ARENA_INFO_OFFSET / sizeof(jsuword) * JS_BITS_PER_WORD
+ * == ARENA_INFO_OFFSET * JS_BITS_PER_BYTE
+ *
+ * and
+ *
+ * B*s == JS_BITS_PER_WORD * sizeof(jsdouble) / sizeof(jsuword)
+ * == JS_BITS_PER_DOUBLE.
+ */
+#define DOUBLES_PER_ARENA \
+ ((ARENA_INFO_OFFSET * JS_BITS_PER_BYTE) / (JS_BITS_PER_DOUBLE + 1))
+
+/*
+ * Check that ARENA_INFO_OFFSET and sizeof(jsdouble) divides sizeof(jsuword).
+ */
+JS_STATIC_ASSERT(ARENA_INFO_OFFSET % sizeof(jsuword) == 0);
+JS_STATIC_ASSERT(sizeof(jsdouble) % sizeof(jsuword) == 0);
+JS_STATIC_ASSERT(sizeof(jsbitmap) == sizeof(jsuword));
+
+#define DOUBLES_ARENA_BITMAP_WORDS \
+ (JS_HOWMANY(DOUBLES_PER_ARENA, JS_BITS_PER_WORD))
+
+/* Check that DOUBLES_PER_ARENA indeed maximises (1). */
+JS_STATIC_ASSERT(DOUBLES_PER_ARENA * sizeof(jsdouble) +
+ DOUBLES_ARENA_BITMAP_WORDS * sizeof(jsuword) <=
+ ARENA_INFO_OFFSET);
+
+JS_STATIC_ASSERT((DOUBLES_PER_ARENA + 1) * sizeof(jsdouble) +
+ sizeof(jsuword) *
+ JS_HOWMANY((DOUBLES_PER_ARENA + 1), JS_BITS_PER_WORD) >
+ ARENA_INFO_OFFSET);
+
+/*
+ * When DOUBLES_PER_ARENA % BITS_PER_DOUBLE_FLAG_UNIT != 0, some bits in the
+ * last byte of the occupation bitmap are unused.
+ */
+#define UNUSED_DOUBLE_BITMAP_BITS \
+ (DOUBLES_ARENA_BITMAP_WORDS * JS_BITS_PER_WORD - DOUBLES_PER_ARENA)
+
+JS_STATIC_ASSERT(UNUSED_DOUBLE_BITMAP_BITS < JS_BITS_PER_WORD);
+
+#define DOUBLES_ARENA_BITMAP_OFFSET \
+ (ARENA_INFO_OFFSET - DOUBLES_ARENA_BITMAP_WORDS * sizeof(jsuword))
+
+#define CHECK_DOUBLE_ARENA_INFO(arenaInfo) \
+ (JS_ASSERT(IS_ARENA_INFO_ADDRESS(arenaInfo)), \
+ JS_ASSERT(!(arenaInfo)->list)) \
+
+/*
+ * Get the start of the bitmap area containing double mark flags in the arena.
+ * To access the flag the code uses
+ *
+ * JS_TEST_BIT(bitmapStart, index)
+ *
+ * That is, compared with the case of arenas with non-double things, we count
+ * flags from the start of the bitmap area, not from the end.
+ */
+#define DOUBLE_ARENA_BITMAP(arenaInfo) \
+ (CHECK_DOUBLE_ARENA_INFO(arenaInfo), \
+ (jsbitmap *) arenaInfo - DOUBLES_ARENA_BITMAP_WORDS)
+
+#define DOUBLE_THING_TO_INDEX(thing) \
+ (CHECK_DOUBLE_ARENA_INFO(THING_TO_ARENA(thing)), \
+ JS_ASSERT(((jsuword) (thing) & GC_ARENA_MASK) < \
+ DOUBLES_ARENA_BITMAP_OFFSET), \
+ ((uint32) (((jsuword) (thing) & GC_ARENA_MASK) / sizeof(jsdouble))))
+
+static void
+ClearDoubleArenaFlags(JSGCArenaInfo *a)
+{
+ jsbitmap *bitmap, mask;
+ uintN nused;
+
+ /*
+ * When some high bits in the last byte of the double occupation bitmap
+ * are unused, we must set them. Otherwise RefillDoubleFreeList will
+ * assume that they corresponds to some free cells and tries to allocate
+ * them.
+ *
+ * Note that the code works correctly with UNUSED_DOUBLE_BITMAP_BITS == 0.
+ */
+ bitmap = DOUBLE_ARENA_BITMAP(a);
+ memset(bitmap, 0, (DOUBLES_ARENA_BITMAP_WORDS - 1) * sizeof *bitmap);
+ mask = ((jsbitmap) 1 << UNUSED_DOUBLE_BITMAP_BITS) - 1;
+ nused = JS_BITS_PER_WORD - UNUSED_DOUBLE_BITMAP_BITS;
+ bitmap[DOUBLES_ARENA_BITMAP_WORDS - 1] = mask << nused;
+}
+
+static JS_ALWAYS_INLINE JSBool
+IsMarkedDouble(JSGCArenaInfo *a, uint32 index)
+{
+ jsbitmap *bitmap;
+
+ JS_ASSERT(a->u.hasMarkedDoubles);
+ bitmap = DOUBLE_ARENA_BITMAP(a);
+ return JS_TEST_BIT(bitmap, index);
+}
+
+/*
+ * JSRuntime.gcDoubleArenaList.nextDoubleFlags points either to:
+ *
+ * 1. The next byte in the bitmap area for doubles to check for unmarked
+ * (or free) doubles.
+ * 2. Or to the end of the bitmap area when all GC cells of the arena are
+ * allocated.
+ * 3. Or to a special sentinel value indicating that there are no arenas
+ * to check for unmarked doubles.
+ *
+ * We set the sentinel to ARENA_INFO_OFFSET so the single check
+ *
+ * ((jsuword) nextDoubleFlags & GC_ARENA_MASK) == ARENA_INFO_OFFSET
+ *
+ * will cover both the second and the third cases.
+ */
+#define DOUBLE_BITMAP_SENTINEL ((jsbitmap *) ARENA_INFO_OFFSET)
+
+#ifdef JS_THREADSAFE
+/*
+ * The maximum number of things to put on the local free list by taking
+ * several things from the global free list or from the tail of the last
+ * allocated arena to amortize the cost of rt->gcLock.
+ *
+ * We use number 8 based on benchmarks from bug 312238.
+ */
+#define MAX_THREAD_LOCAL_THINGS 8
+
+#endif
+
+JS_STATIC_ASSERT(sizeof(JSStackHeader) >= 2 * sizeof(jsval));
+
+JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(JSString));
+JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble));
+
+/* We want to use all the available GC thing space for object's slots. */
+JS_STATIC_ASSERT(sizeof(JSObject) % sizeof(JSGCThing) == 0);
+
+/*
+ * Ensure that JSObject is allocated from a different GC-list rather than
+ * jsdouble and JSString so we can easily finalize JSObject before these 2
+ * types of GC things. See comments in js_GC.
+ */
+JS_STATIC_ASSERT(GC_FREELIST_INDEX(sizeof(JSString)) !=
+ GC_FREELIST_INDEX(sizeof(JSObject)));
+JS_STATIC_ASSERT(GC_FREELIST_INDEX(sizeof(jsdouble)) !=
+ GC_FREELIST_INDEX(sizeof(JSObject)));
+
+/*
+ * JSPtrTable capacity growth descriptor. The table grows by powers of two
+ * starting from capacity JSPtrTableInfo.minCapacity, but switching to linear
+ * growth when capacity reaches JSPtrTableInfo.linearGrowthThreshold.
+ */
+typedef struct JSPtrTableInfo {
+ uint16 minCapacity;
+ uint16 linearGrowthThreshold;
+} JSPtrTableInfo;
+
+#define GC_ITERATOR_TABLE_MIN 4
+#define GC_ITERATOR_TABLE_LINEAR 1024
+
+static const JSPtrTableInfo iteratorTableInfo = {
+ GC_ITERATOR_TABLE_MIN,
+ GC_ITERATOR_TABLE_LINEAR
+};
+
+/* Calculate table capacity based on the current value of JSPtrTable.count. */
+static size_t
+PtrTableCapacity(size_t count, const JSPtrTableInfo *info)
+{
+ size_t linear, log, capacity;
+
+ linear = info->linearGrowthThreshold;
+ JS_ASSERT(info->minCapacity <= linear);
+
+ if (count == 0) {
+ capacity = 0;
+ } else if (count < linear) {
+ log = JS_CEILING_LOG2W(count);
+ JS_ASSERT(log != JS_BITS_PER_WORD);
+ capacity = (size_t)1 << log;
+ if (capacity < info->minCapacity)
+ capacity = info->minCapacity;
+ } else {
+ capacity = JS_ROUNDUP(count, linear);
+ }
+
+ JS_ASSERT(capacity >= count);
+ return capacity;
+}
+
+static void
+FreePtrTable(JSPtrTable *table, const JSPtrTableInfo *info)
+{
+ if (table->array) {
+ JS_ASSERT(table->count > 0);
+ free(table->array);
+ table->array = NULL;
+ table->count = 0;
+ }
+ JS_ASSERT(table->count == 0);
+}
+
+static JSBool
+AddToPtrTable(JSContext *cx, JSPtrTable *table, const JSPtrTableInfo *info,
+ void *ptr)
+{
+ size_t count, capacity;
+ void **array;
+
+ count = table->count;
+ capacity = PtrTableCapacity(count, info);
+
+ if (count == capacity) {
+ if (capacity < info->minCapacity) {
+ JS_ASSERT(capacity == 0);
+ JS_ASSERT(!table->array);
+ capacity = info->minCapacity;
+ } else {
+ /*
+ * Simplify the overflow detection assuming pointer is bigger
+ * than byte.
+ */
+ JS_STATIC_ASSERT(2 <= sizeof table->array[0]);
+ capacity = (capacity < info->linearGrowthThreshold)
+ ? 2 * capacity
+ : capacity + info->linearGrowthThreshold;
+ if (capacity > (size_t)-1 / sizeof table->array[0])
+ goto bad;
+ }
+ array = (void **) realloc(table->array,
+ capacity * sizeof table->array[0]);
+ if (!array)
+ goto bad;
+#ifdef DEBUG
+ memset(array + count, JS_FREE_PATTERN,
+ (capacity - count) * sizeof table->array[0]);
+#endif
+ table->array = array;
+ }
+
+ table->array[count] = ptr;
+ table->count = count + 1;
+
+ return JS_TRUE;
+
+ bad:
+ JS_ReportOutOfMemory(cx);
+ return JS_FALSE;
+}
+
+static void
+ShrinkPtrTable(JSPtrTable *table, const JSPtrTableInfo *info,
+ size_t newCount)
+{
+ size_t oldCapacity, capacity;
+ void **array;
+
+ JS_ASSERT(newCount <= table->count);
+ if (newCount == table->count)
+ return;
+
+ oldCapacity = PtrTableCapacity(table->count, info);
+ table->count = newCount;
+ capacity = PtrTableCapacity(newCount, info);
+
+ if (oldCapacity != capacity) {
+ array = table->array;
+ JS_ASSERT(array);
+ if (capacity == 0) {
+ free(array);
+ table->array = NULL;
+ return;
+ }
+ array = (void **) realloc(array, capacity * sizeof array[0]);
+ if (array)
+ table->array = array;
+ }
+#ifdef DEBUG
+ memset(table->array + newCount, JS_FREE_PATTERN,
+ (capacity - newCount) * sizeof table->array[0]);
+#endif
+}
+
+#ifdef JS_GCMETER
+# define METER(x) ((void) (x))
+# define METER_IF(condition, x) ((void) ((condition) && (x)))
+#else
+# define METER(x) ((void) 0)
+# define METER_IF(condition, x) ((void) 0)
+#endif
+
+#define METER_UPDATE_MAX(maxLval, rval) \
+ METER_IF((maxLval) < (rval), (maxLval) = (rval))
+
+#if JS_GC_USE_MMAP || !HAS_POSIX_MEMALIGN
+
+/*
+ * For chunks allocated via over-sized malloc, get a pointer to store the gap
+ * between the malloc's result and the first arena in the chunk.
+ */
+static uint32 *
+GetMallocedChunkGapPtr(jsuword chunk)
+{
+ JS_ASSERT((chunk & GC_ARENA_MASK) == 0);
+
+ /* Use the memory after the chunk, see NewGCChunk for details. */
+ return (uint32 *) (chunk + (js_gcArenasPerChunk << GC_ARENA_SHIFT));
+}
+
+#endif
+
+static jsuword
+NewGCChunk(void)
+{
+ void *p;
+
+#if JS_GC_USE_MMAP
+ if (js_gcUseMmap) {
+# if defined(XP_WIN)
+ p = VirtualAlloc(NULL, js_gcArenasPerChunk << GC_ARENA_SHIFT,
+ MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+ return (jsuword) p;
+# else
+ p = mmap(NULL, js_gcArenasPerChunk << GC_ARENA_SHIFT,
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ return (p == MAP_FAILED) ? 0 : (jsuword) p;
+# endif
+ }
+#endif
+
+#if HAS_POSIX_MEMALIGN
+ if (0 != posix_memalign(&p, GC_ARENA_SIZE,
+ GC_ARENA_SIZE * js_gcArenasPerChunk -
+ JS_GC_ARENA_PAD)) {
+ return 0;
+ }
+ return (jsuword) p;
+#else
+ /*
+ * Implement chunk allocation using oversized malloc if mmap and
+ * posix_memalign are not available.
+ *
+ * Since malloc allocates pointers aligned on the word boundary, to get
+ * js_gcArenasPerChunk aligned arenas, we need to malloc only
+ *
+ * ((js_gcArenasPerChunk + 1) << GC_ARENA_SHIFT) - sizeof(size_t)
+ *
+ * bytes. But since we stores the gap between the malloced pointer and the
+ * first arena in the chunk after the chunk, we need to ask for
+ *
+ * ((js_gcArenasPerChunk + 1) << GC_ARENA_SHIFT)
+ *
+ * bytes to ensure that we always have room to store the gap.
+ */
+ p = malloc((js_gcArenasPerChunk + 1) << GC_ARENA_SHIFT);
+ if (!p)
+ return 0;
+
+ {
+ jsuword chunk;
+
+ chunk = ((jsuword) p + GC_ARENA_MASK) & ~GC_ARENA_MASK;
+ *GetMallocedChunkGapPtr(chunk) = (uint32) (chunk - (jsuword) p);
+ return chunk;
+ }
+#endif
+}
+
+static void
+DestroyGCChunk(jsuword chunk)
+{
+ JS_ASSERT((chunk & GC_ARENA_MASK) == 0);
+#if JS_GC_USE_MMAP
+ if (js_gcUseMmap) {
+# if defined(XP_WIN)
+ VirtualFree((void *) chunk, 0, MEM_RELEASE);
+# elif defined(SOLARIS)
+ munmap((char *) chunk, js_gcArenasPerChunk << GC_ARENA_SHIFT);
+# else
+ munmap((void *) chunk, js_gcArenasPerChunk << GC_ARENA_SHIFT);
+# endif
+ return;
+ }
+#endif
+
+#if HAS_POSIX_MEMALIGN
+ free((void *) chunk);
+#else
+ /* See comments in NewGCChunk. */
+ JS_ASSERT(*GetMallocedChunkGapPtr(chunk) < GC_ARENA_SIZE);
+ free((void *) (chunk - *GetMallocedChunkGapPtr(chunk)));
+#endif
+}
+
+#if CHUNKED_ARENA_ALLOCATION
+
+static void
+AddChunkToList(JSRuntime *rt, JSGCChunkInfo *ci)
+{
+ ci->prevp = &rt->gcChunkList;
+ ci->next = rt->gcChunkList;
+ if (rt->gcChunkList) {
+ JS_ASSERT(rt->gcChunkList->prevp == &rt->gcChunkList);
+ rt->gcChunkList->prevp = &ci->next;
+ }
+ rt->gcChunkList = ci;
+}
+
+static void
+RemoveChunkFromList(JSRuntime *rt, JSGCChunkInfo *ci)
+{
+ *ci->prevp = ci->next;
+ if (ci->next) {
+ JS_ASSERT(ci->next->prevp == &ci->next);
+ ci->next->prevp = ci->prevp;
+ }
+}
+
+#endif
+
+static JSGCArenaInfo *
+NewGCArena(JSRuntime *rt)
+{
+ jsuword chunk;
+ JSGCArenaInfo *a;
+
+ if (rt->gcBytes >= rt->gcMaxBytes)
+ return NULL;
+
+#if CHUNKED_ARENA_ALLOCATION
+ if (js_gcArenasPerChunk == 1) {
+#endif
+ chunk = NewGCChunk();
+ if (chunk == 0)
+ return NULL;
+ a = ARENA_START_TO_INFO(chunk);
+#if CHUNKED_ARENA_ALLOCATION
+ } else {
+ JSGCChunkInfo *ci;
+ uint32 i;
+ JSGCArenaInfo *aprev;
+
+ ci = rt->gcChunkList;
+ if (!ci) {
+ chunk = NewGCChunk();
+ if (chunk == 0)
+ return NULL;
+ JS_ASSERT((chunk & GC_ARENA_MASK) == 0);
+ a = GET_ARENA_INFO(chunk, 0);
+ a->firstArena = JS_TRUE;
+ a->arenaIndex = 0;
+ aprev = NULL;
+ i = 0;
+ do {
+ a->prev = aprev;
+ aprev = a;
+ ++i;
+ a = GET_ARENA_INFO(chunk, i);
+ a->firstArena = JS_FALSE;
+ a->arenaIndex = i;
+ } while (i != js_gcArenasPerChunk - 1);
+ ci = GET_CHUNK_INFO(chunk, 0);
+ ci->lastFreeArena = aprev;
+ ci->numFreeArenas = js_gcArenasPerChunk - 1;
+ AddChunkToList(rt, ci);
+ } else {
+ JS_ASSERT(ci->prevp == &rt->gcChunkList);
+ a = ci->lastFreeArena;
+ aprev = a->prev;
+ if (!aprev) {
+ JS_ASSERT(ci->numFreeArenas == 1);
+ JS_ASSERT(ARENA_INFO_TO_START(a) == (jsuword) ci);
+ RemoveChunkFromList(rt, ci);
+ chunk = GET_ARENA_CHUNK(a, GET_ARENA_INDEX(a));
+ SET_CHUNK_INFO_INDEX(chunk, NO_FREE_ARENAS);
+ } else {
+ JS_ASSERT(ci->numFreeArenas >= 2);
+ JS_ASSERT(ARENA_INFO_TO_START(a) != (jsuword) ci);
+ ci->lastFreeArena = aprev;
+ ci->numFreeArenas--;
+ }
+ }
+ }
+#endif
+
+ rt->gcBytes += GC_ARENA_SIZE;
+ a->prevUntracedPage = 0;
+ memset(&a->u, 0, sizeof(a->u));
+
+ return a;
+}
+
+static void
+DestroyGCArenas(JSRuntime *rt, JSGCArenaInfo *last)
+{
+ JSGCArenaInfo *a;
+
+ while (last) {
+ a = last;
+ last = last->prev;
+
+ METER(rt->gcStats.afree++);
+ JS_ASSERT(rt->gcBytes >= GC_ARENA_SIZE);
+ rt->gcBytes -= GC_ARENA_SIZE;
+
+#if CHUNKED_ARENA_ALLOCATION
+ if (js_gcArenasPerChunk == 1) {
+#endif
+ DestroyGCChunk(ARENA_INFO_TO_START(a));
+#if CHUNKED_ARENA_ALLOCATION
+ } else {
+ uint32 arenaIndex;
+ jsuword chunk;
+ uint32 chunkInfoIndex;
+ JSGCChunkInfo *ci;
+# ifdef DEBUG
+ jsuword firstArena;
+
+ firstArena = a->firstArena;
+ arenaIndex = a->arenaIndex;
+ memset((void *) ARENA_INFO_TO_START(a), JS_FREE_PATTERN,
+ GC_ARENA_SIZE - JS_GC_ARENA_PAD);
+ a->firstArena = firstArena;
+ a->arenaIndex = arenaIndex;
+# endif
+ arenaIndex = GET_ARENA_INDEX(a);
+ chunk = GET_ARENA_CHUNK(a, arenaIndex);
+ chunkInfoIndex = GET_CHUNK_INFO_INDEX(chunk);
+ if (chunkInfoIndex == NO_FREE_ARENAS) {
+ chunkInfoIndex = arenaIndex;
+ SET_CHUNK_INFO_INDEX(chunk, arenaIndex);
+ ci = GET_CHUNK_INFO(chunk, chunkInfoIndex);
+ a->prev = NULL;
+ ci->lastFreeArena = a;
+ ci->numFreeArenas = 1;
+ AddChunkToList(rt, ci);
+ } else {
+ JS_ASSERT(chunkInfoIndex != arenaIndex);
+ ci = GET_CHUNK_INFO(chunk, chunkInfoIndex);
+ JS_ASSERT(ci->numFreeArenas != 0);
+ JS_ASSERT(ci->lastFreeArena);
+ JS_ASSERT(a != ci->lastFreeArena);
+ if (ci->numFreeArenas == js_gcArenasPerChunk - 1) {
+ RemoveChunkFromList(rt, ci);
+ DestroyGCChunk(chunk);
+ } else {
+ ++ci->numFreeArenas;
+ a->prev = ci->lastFreeArena;
+ ci->lastFreeArena = a;
+ }
+ }
+ }
+# endif
+ }
+}
+
+static void
+InitGCArenaLists(JSRuntime *rt)
+{
+ uintN i, thingSize;
+ JSGCArenaList *arenaList;
+
+ for (i = 0; i < GC_NUM_FREELISTS; i++) {
+ arenaList = &rt->gcArenaList[i];
+ thingSize = GC_FREELIST_NBYTES(i);
+ JS_ASSERT((size_t)(uint16)thingSize == thingSize);
+ arenaList->last = NULL;
+ arenaList->lastCount = (uint16) THINGS_PER_ARENA(thingSize);
+ arenaList->thingSize = (uint16) thingSize;
+ arenaList->freeList = NULL;
+ }
+ rt->gcDoubleArenaList.first = NULL;
+ rt->gcDoubleArenaList.nextDoubleFlags = DOUBLE_BITMAP_SENTINEL;
+}
+
+static void
+FinishGCArenaLists(JSRuntime *rt)
+{
+ uintN i;
+ JSGCArenaList *arenaList;
+
+ for (i = 0; i < GC_NUM_FREELISTS; i++) {
+ arenaList = &rt->gcArenaList[i];
+ DestroyGCArenas(rt, arenaList->last);
+ arenaList->last = NULL;
+ arenaList->lastCount = THINGS_PER_ARENA(arenaList->thingSize);
+ arenaList->freeList = NULL;
+ }
+ DestroyGCArenas(rt, rt->gcDoubleArenaList.first);
+ rt->gcDoubleArenaList.first = NULL;
+ rt->gcDoubleArenaList.nextDoubleFlags = DOUBLE_BITMAP_SENTINEL;
+
+ rt->gcBytes = 0;
+ JS_ASSERT(rt->gcChunkList == 0);
+}
+
+/*
+ * This function must not be called when thing is jsdouble.
+ */
+static uint8 *
+GetGCThingFlags(void *thing)
+{
+ JSGCArenaInfo *a;
+ uint32 index;
+
+ a = THING_TO_ARENA(thing);
+ index = THING_TO_INDEX(thing, a->list->thingSize);
+ return THING_FLAGP(a, index);
+}
+
+/*
+ * This function returns null when thing is jsdouble.
+ */
+static uint8 *
+GetGCThingFlagsOrNull(void *thing)
+{
+ JSGCArenaInfo *a;
+ uint32 index;
+
+ a = THING_TO_ARENA(thing);
+ if (!a->list)
+ return NULL;
+ index = THING_TO_INDEX(thing, a->list->thingSize);
+ return THING_FLAGP(a, index);
+}
+
+intN
+js_GetExternalStringGCType(JSString *str)
+{
+ uintN type;
+
+ type = (uintN) *GetGCThingFlags(str) & GCF_TYPEMASK;
+ JS_ASSERT(type == GCX_STRING || type >= GCX_EXTERNAL_STRING);
+ return (type == GCX_STRING) ? -1 : (intN) (type - GCX_EXTERNAL_STRING);
+}
+
+static uint32
+MapGCFlagsToTraceKind(uintN flags)
+{
+ uint32 type;
+
+ type = flags & GCF_TYPEMASK;
+ JS_ASSERT(type != GCX_DOUBLE);
+ JS_ASSERT(type < GCX_NTYPES);
+ return (type < GCX_EXTERNAL_STRING) ? type : JSTRACE_STRING;
+}
+
+JS_FRIEND_API(uint32)
+js_GetGCThingTraceKind(void *thing)
+{
+ JSGCArenaInfo *a;
+ uint32 index;
+
+ a = THING_TO_ARENA(thing);
+ if (!a->list)
+ return JSTRACE_DOUBLE;
+
+ index = THING_TO_INDEX(thing, a->list->thingSize);
+ return MapGCFlagsToTraceKind(*THING_FLAGP(a, index));
+}
+
+JSRuntime*
+js_GetGCStringRuntime(JSString *str)
+{
+ JSGCArenaList *list;
+
+ list = THING_TO_ARENA(str)->list;
+
+ JS_ASSERT(list->thingSize == sizeof(JSGCThing));
+ JS_ASSERT(GC_FREELIST_INDEX(sizeof(JSGCThing)) == 0);
+
+ return (JSRuntime *)((uint8 *)list - offsetof(JSRuntime, gcArenaList));
+}
+
+JSBool
+js_IsAboutToBeFinalized(JSContext *cx, void *thing)
+{
+ JSGCArenaInfo *a;
+ uint32 index, flags;
+
+ a = THING_TO_ARENA(thing);
+ if (!a->list) {
+ /*
+ * Check if arena has no marked doubles. In that case the bitmap with
+ * the mark flags contains all garbage as it is initialized only when
+ * marking the first double in the arena.
+ */
+ if (!a->u.hasMarkedDoubles)
+ return JS_TRUE;
+ index = DOUBLE_THING_TO_INDEX(thing);
+ return !IsMarkedDouble(a, index);
+ }
+ index = THING_TO_INDEX(thing, a->list->thingSize);
+ flags = *THING_FLAGP(a, index);
+ return !(flags & (GCF_MARK | GCF_LOCK | GCF_FINAL));
+}
+
+/* This is compatible with JSDHashEntryStub. */
+typedef struct JSGCRootHashEntry {
+ JSDHashEntryHdr hdr;
+ void *root;
+ const char *name;
+} JSGCRootHashEntry;
+
+/* Initial size of the gcRootsHash table (SWAG, small enough to amortize). */
+#define GC_ROOTS_SIZE 256
+
+#if CHUNKED_ARENA_ALLOCATION
+
+/*
+ * For a CPU with extremely large pages using them for GC things wastes
+ * too much memory.
+ */
+# define GC_ARENAS_PER_CPU_PAGE_LIMIT JS_BIT(18 - GC_ARENA_SHIFT)
+
+JS_STATIC_ASSERT(GC_ARENAS_PER_CPU_PAGE_LIMIT <= NO_FREE_ARENAS);
+
+#endif
+
+JSBool
+js_InitGC(JSRuntime *rt, uint32 maxbytes)
+{
+#if JS_GC_USE_MMAP
+ if (js_gcArenasPerChunk == 0) {
+ size_t cpuPageSize, arenasPerPage;
+# if defined(XP_WIN)
+ SYSTEM_INFO si;
+
+ GetSystemInfo(&si);
+ cpuPageSize = si.dwPageSize;
+
+# elif defined(XP_UNIX) || defined(XP_BEOS)
+ cpuPageSize = (size_t) sysconf(_SC_PAGESIZE);
+# else
+# error "Not implemented"
+# endif
+ /* cpuPageSize is a power of 2. */
+ JS_ASSERT((cpuPageSize & (cpuPageSize - 1)) == 0);
+ arenasPerPage = cpuPageSize >> GC_ARENA_SHIFT;
+#ifdef DEBUG
+ if (arenasPerPage == 0) {
+ fprintf(stderr,
+"JS engine warning: the size of the CPU page, %u bytes, is too low to use\n"
+"paged allocation for the garbage collector. Please report this.\n",
+ (unsigned) cpuPageSize);
+ }
+#endif
+ if (arenasPerPage - 1 <= (size_t) (GC_ARENAS_PER_CPU_PAGE_LIMIT - 1)) {
+ /*
+ * Use at least 4 GC arenas per paged allocation chunk to minimize
+ * the overhead of mmap/VirtualAlloc.
+ */
+ js_gcUseMmap = JS_TRUE;
+ js_gcArenasPerChunk = JS_MAX((uint32) arenasPerPage, 4);
+ } else {
+ js_gcUseMmap = JS_FALSE;
+ js_gcArenasPerChunk = 7;
+ }
+ }
+ JS_ASSERT(1 <= js_gcArenasPerChunk &&
+ js_gcArenasPerChunk <= NO_FREE_ARENAS);
+#endif
+
+ InitGCArenaLists(rt);
+ if (!JS_DHashTableInit(&rt->gcRootsHash, JS_DHashGetStubOps(), NULL,
+ sizeof(JSGCRootHashEntry), GC_ROOTS_SIZE)) {
+ rt->gcRootsHash.ops = NULL;
+ return JS_FALSE;
+ }
+ rt->gcLocksHash = NULL; /* create lazily */
+
+ /*
+ * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes
+ * for default backward API compatibility.
+ */
+ rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes;
+ rt->gcEmptyArenaPoolLifespan = 30000;
+
+ METER(memset(&rt->gcStats, 0, sizeof rt->gcStats));
+ return JS_TRUE;
+}
+
+#ifdef JS_GCMETER
+
+static void
+UpdateArenaStats(JSGCArenaStats *st, uint32 nlivearenas, uint32 nkilledArenas,
+ uint32 nthings)
+{
+ size_t narenas;
+
+ narenas = nlivearenas + nkilledArenas;
+ JS_ASSERT(narenas >= st->livearenas);
+
+ st->newarenas = narenas - st->livearenas;
+ st->narenas = narenas;
+ st->livearenas = nlivearenas;
+ if (st->maxarenas < narenas)
+ st->maxarenas = narenas;
+ st->totalarenas += narenas;
+
+ st->nthings = nthings;
+ if (st->maxthings < nthings)
+ st->maxthings = nthings;
+ st->totalthings += nthings;
+}
+
+JS_FRIEND_API(void)
+js_DumpGCStats(JSRuntime *rt, FILE *fp)
+{
+ int i;
+ size_t sumArenas, sumTotalArenas;
+ size_t sumThings, sumMaxThings;
+ size_t sumThingSize, sumTotalThingSize;
+ size_t sumArenaCapacity, sumTotalArenaCapacity;
+ JSGCArenaStats *st;
+ size_t thingSize, thingsPerArena;
+ size_t sumAlloc, sumLocalAlloc, sumFail, sumRetry;
+
+ fprintf(fp, "\nGC allocation statistics:\n");
+
+#define UL(x) ((unsigned long)(x))
+#define ULSTAT(x) UL(rt->gcStats.x)
+#define PERCENT(x,y) (100.0 * (double) (x) / (double) (y))
+
+ sumArenas = 0;
+ sumTotalArenas = 0;
+ sumThings = 0;
+ sumMaxThings = 0;
+ sumThingSize = 0;
+ sumTotalThingSize = 0;
+ sumArenaCapacity = 0;
+ sumTotalArenaCapacity = 0;
+ sumAlloc = 0;
+ sumLocalAlloc = 0;
+ sumFail = 0;
+ sumRetry = 0;
+ for (i = -1; i < (int) GC_NUM_FREELISTS; i++) {
+ if (i == -1) {
+ thingSize = sizeof(jsdouble);
+ thingsPerArena = DOUBLES_PER_ARENA;
+ st = &rt->gcStats.doubleArenaStats;
+ fprintf(fp,
+ "Arena list for double values (%lu doubles per arena):",
+ UL(thingsPerArena));
+ } else {
+ thingSize = rt->gcArenaList[i].thingSize;
+ thingsPerArena = THINGS_PER_ARENA(thingSize);
+ st = &rt->gcStats.arenaStats[i];
+ fprintf(fp,
+ "Arena list %d (thing size %lu, %lu things per arena):",
+ i, UL(GC_FREELIST_NBYTES(i)), UL(thingsPerArena));
+ }
+ if (st->maxarenas == 0) {
+ fputs(" NEVER USED\n", fp);
+ continue;
+ }
+ putc('\n', fp);
+ fprintf(fp, " arenas before GC: %lu\n", UL(st->narenas));
+ fprintf(fp, " new arenas before GC: %lu (%.1f%%)\n",
+ UL(st->newarenas), PERCENT(st->newarenas, st->narenas));
+ fprintf(fp, " arenas after GC: %lu (%.1f%%)\n",
+ UL(st->livearenas), PERCENT(st->livearenas, st->narenas));
+ fprintf(fp, " max arenas: %lu\n", UL(st->maxarenas));
+ fprintf(fp, " things: %lu\n", UL(st->nthings));
+ fprintf(fp, " GC cell utilization: %.1f%%\n",
+ PERCENT(st->nthings, thingsPerArena * st->narenas));
+ fprintf(fp, " average cell utilization: %.1f%%\n",
+ PERCENT(st->totalthings, thingsPerArena * st->totalarenas));
+ fprintf(fp, " max things: %lu\n", UL(st->maxthings));
+ fprintf(fp, " alloc attempts: %lu\n", UL(st->alloc));
+ fprintf(fp, " alloc without locks: %1u (%.1f%%)\n",
+ UL(st->localalloc), PERCENT(st->localalloc, st->alloc));
+ sumArenas += st->narenas;
+ sumTotalArenas += st->totalarenas;
+ sumThings += st->nthings;
+ sumMaxThings += st->maxthings;
+ sumThingSize += thingSize * st->nthings;
+ sumTotalThingSize += thingSize * st->totalthings;
+ sumArenaCapacity += thingSize * thingsPerArena * st->narenas;
+ sumTotalArenaCapacity += thingSize * thingsPerArena * st->totalarenas;
+ sumAlloc += st->alloc;
+ sumLocalAlloc += st->localalloc;
+ sumFail += st->fail;
+ sumRetry += st->retry;
+ }
+ fprintf(fp, "TOTAL STATS:\n");
+ fprintf(fp, " bytes allocated: %lu\n", UL(rt->gcBytes));
+ fprintf(fp, " total GC arenas: %lu\n", UL(sumArenas));
+ fprintf(fp, " total GC things: %lu\n", UL(sumThings));
+ fprintf(fp, " max total GC things: %lu\n", UL(sumMaxThings));
+ fprintf(fp, " GC cell utilization: %.1f%%\n",
+ PERCENT(sumThingSize, sumArenaCapacity));
+ fprintf(fp, " average cell utilization: %.1f%%\n",
+ PERCENT(sumTotalThingSize, sumTotalArenaCapacity));
+ fprintf(fp, "allocation retries after GC: %lu\n", UL(sumRetry));
+ fprintf(fp, " alloc attempts: %lu\n", UL(sumAlloc));
+ fprintf(fp, " alloc without locks: %1u (%.1f%%)\n",
+ UL(sumLocalAlloc), PERCENT(sumLocalAlloc, sumAlloc));
+ fprintf(fp, " allocation failures: %lu\n", UL(sumFail));
+ fprintf(fp, " things born locked: %lu\n", ULSTAT(lockborn));
+ fprintf(fp, " valid lock calls: %lu\n", ULSTAT(lock));
+ fprintf(fp, " valid unlock calls: %lu\n", ULSTAT(unlock));
+ fprintf(fp, " mark recursion depth: %lu\n", ULSTAT(depth));
+ fprintf(fp, " maximum mark recursion: %lu\n", ULSTAT(maxdepth));
+ fprintf(fp, " mark C recursion depth: %lu\n", ULSTAT(cdepth));
+ fprintf(fp, " maximum mark C recursion: %lu\n", ULSTAT(maxcdepth));
+ fprintf(fp, " delayed tracing calls: %lu\n", ULSTAT(untraced));
+#ifdef DEBUG
+ fprintf(fp, " max trace later count: %lu\n", ULSTAT(maxuntraced));
+#endif
+ fprintf(fp, " maximum GC nesting level: %lu\n", ULSTAT(maxlevel));
+ fprintf(fp, "potentially useful GC calls: %lu\n", ULSTAT(poke));
+ fprintf(fp, " thing arenas freed so far: %lu\n", ULSTAT(afree));
+ fprintf(fp, " stack segments scanned: %lu\n", ULSTAT(stackseg));
+ fprintf(fp, "stack segment slots scanned: %lu\n", ULSTAT(segslots));
+ fprintf(fp, "reachable closeable objects: %lu\n", ULSTAT(nclose));
+ fprintf(fp, " max reachable closeable: %lu\n", ULSTAT(maxnclose));
+ fprintf(fp, " scheduled close hooks: %lu\n", ULSTAT(closelater));
+ fprintf(fp, " max scheduled close hooks: %lu\n", ULSTAT(maxcloselater));
+
+#undef UL
+#undef ULSTAT
+#undef PERCENT
+
+#ifdef JS_ARENAMETER
+ JS_DumpArenaStats(fp);
+#endif
+}
+#endif
+
+#ifdef DEBUG
+static void
+CheckLeakedRoots(JSRuntime *rt);
+#endif
+
+#ifdef JS_THREADSAFE
+static void
+TrimGCFreeListsPool(JSRuntime *rt, uintN keepCount);
+#endif
+
+void
+js_FinishGC(JSRuntime *rt)
+{
+#ifdef JS_ARENAMETER
+ JS_DumpArenaStats(stdout);
+#endif
+#ifdef JS_GCMETER
+ js_DumpGCStats(rt, stdout);
+#endif
+
+ FreePtrTable(&rt->gcIteratorTable, &iteratorTableInfo);
+#ifdef JS_THREADSAFE
+ TrimGCFreeListsPool(rt, 0);
+ JS_ASSERT(!rt->gcFreeListsPool);
+#endif
+ FinishGCArenaLists(rt);
+
+ if (rt->gcRootsHash.ops) {
+#ifdef DEBUG
+ CheckLeakedRoots(rt);
+#endif
+ JS_DHashTableFinish(&rt->gcRootsHash);
+ rt->gcRootsHash.ops = NULL;
+ }
+ if (rt->gcLocksHash) {
+ JS_DHashTableDestroy(rt->gcLocksHash);
+ rt->gcLocksHash = NULL;
+ }
+}
+
+JSBool
+js_AddRoot(JSContext *cx, void *rp, const char *name)
+{
+ JSBool ok = js_AddRootRT(cx->runtime, rp, name);
+ if (!ok)
+ JS_ReportOutOfMemory(cx);
+ return ok;
+}
+
+JSBool
+js_AddRootRT(JSRuntime *rt, void *rp, const char *name)
+{
+ JSBool ok;
+ JSGCRootHashEntry *rhe;
+
+ /*
+ * Due to the long-standing, but now removed, use of rt->gcLock across the
+ * bulk of js_GC, API users have come to depend on JS_AddRoot etc. locking
+ * properly with a racing GC, without calling JS_AddRoot from a request.
+ * We have to preserve API compatibility here, now that we avoid holding
+ * rt->gcLock across the mark phase (including the root hashtable mark).
+ *
+ * If the GC is running and we're called on another thread, wait for this
+ * GC activation to finish. We can safely wait here (in the case where we
+ * are called within a request on another thread's context) without fear
+ * of deadlock because the GC doesn't set rt->gcRunning until after it has
+ * waited for all active requests to end.
+ */
+ JS_LOCK_GC(rt);
+#ifdef JS_THREADSAFE
+ JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0);
+ if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) {
+ do {
+ JS_AWAIT_GC_DONE(rt);
+ } while (rt->gcLevel > 0);
+ }
+#endif
+ rhe = (JSGCRootHashEntry *)
+ JS_DHashTableOperate(&rt->gcRootsHash, rp, JS_DHASH_ADD);
+ if (rhe) {
+ rhe->root = rp;
+ rhe->name = name;
+ ok = JS_TRUE;
+ } else {
+ ok = JS_FALSE;
+ }
+ JS_UNLOCK_GC(rt);
+ return ok;
+}
+
+JSBool
+js_RemoveRoot(JSRuntime *rt, void *rp)
+{
+ /*
+ * Due to the JS_RemoveRootRT API, we may be called outside of a request.
+ * Same synchronization drill as above in js_AddRoot.
+ */
+ JS_LOCK_GC(rt);
+#ifdef JS_THREADSAFE
+ JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0);
+ if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) {
+ do {
+ JS_AWAIT_GC_DONE(rt);
+ } while (rt->gcLevel > 0);
+ }
+#endif
+ (void) JS_DHashTableOperate(&rt->gcRootsHash, rp, JS_DHASH_REMOVE);
+ rt->gcPoke = JS_TRUE;
+ JS_UNLOCK_GC(rt);
+ return JS_TRUE;
+}
+
+#ifdef DEBUG
+
+static JSDHashOperator
+js_root_printer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 i, void *arg)
+{
+ uint32 *leakedroots = (uint32 *)arg;
+ JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
+
+ (*leakedroots)++;
+ fprintf(stderr,
+ "JS engine warning: leaking GC root \'%s\' at %p\n",
+ rhe->name ? (char *)rhe->name : "", rhe->root);
+
+ return JS_DHASH_NEXT;
+}
+
+static void
+CheckLeakedRoots(JSRuntime *rt)
+{
+ uint32 leakedroots = 0;
+
+ /* Warn (but don't assert) debug builds of any remaining roots. */
+ JS_DHashTableEnumerate(&rt->gcRootsHash, js_root_printer,
+ &leakedroots);
+ if (leakedroots > 0) {
+ if (leakedroots == 1) {
+ fprintf(stderr,
+"JS engine warning: 1 GC root remains after destroying the JSRuntime at %p.\n"
+" This root may point to freed memory. Objects reachable\n"
+" through it have not been finalized.\n",
+ (void *) rt);
+ } else {
+ fprintf(stderr,
+"JS engine warning: %lu GC roots remain after destroying the JSRuntime at %p.\n"
+" These roots may point to freed memory. Objects reachable\n"
+" through them have not been finalized.\n",
+ (unsigned long) leakedroots, (void *) rt);
+ }
+ }
+}
+
+typedef struct NamedRootDumpArgs {
+ void (*dump)(const char *name, void *rp, void *data);
+ void *data;
+} NamedRootDumpArgs;
+
+static JSDHashOperator
+js_named_root_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
+ void *arg)
+{
+ NamedRootDumpArgs *args = (NamedRootDumpArgs *) arg;
+ JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
+
+ if (rhe->name)
+ args->dump(rhe->name, rhe->root, args->data);
+ return JS_DHASH_NEXT;
+}
+
+JS_BEGIN_EXTERN_C
+void
+js_DumpNamedRoots(JSRuntime *rt,
+ void (*dump)(const char *name, void *rp, void *data),
+ void *data)
+{
+ NamedRootDumpArgs args;
+
+ args.dump = dump;
+ args.data = data;
+ JS_DHashTableEnumerate(&rt->gcRootsHash, js_named_root_dumper, &args);
+}
+JS_END_EXTERN_C
+
+#endif /* DEBUG */
+
+typedef struct GCRootMapArgs {
+ JSGCRootMapFun map;
+ void *data;
+} GCRootMapArgs;
+
+static JSDHashOperator
+js_gcroot_mapper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
+ void *arg)
+{
+ GCRootMapArgs *args = (GCRootMapArgs *) arg;
+ JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
+ intN mapflags;
+ int op;
+
+ mapflags = args->map(rhe->root, rhe->name, args->data);
+
+#if JS_MAP_GCROOT_NEXT == JS_DHASH_NEXT && \
+ JS_MAP_GCROOT_STOP == JS_DHASH_STOP && \
+ JS_MAP_GCROOT_REMOVE == JS_DHASH_REMOVE
+ op = (JSDHashOperator)mapflags;
+#else
+ op = JS_DHASH_NEXT;
+ if (mapflags & JS_MAP_GCROOT_STOP)
+ op |= JS_DHASH_STOP;
+ if (mapflags & JS_MAP_GCROOT_REMOVE)
+ op |= JS_DHASH_REMOVE;
+#endif
+
+ return (JSDHashOperator) op;
+}
+
+uint32
+js_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data)
+{
+ GCRootMapArgs args;
+ uint32 rv;
+
+ args.map = map;
+ args.data = data;
+ JS_LOCK_GC(rt);
+ rv = JS_DHashTableEnumerate(&rt->gcRootsHash, js_gcroot_mapper, &args);
+ JS_UNLOCK_GC(rt);
+ return rv;
+}
+
+JSBool
+js_RegisterCloseableIterator(JSContext *cx, JSObject *obj)
+{
+ JSRuntime *rt;
+ JSBool ok;
+
+ rt = cx->runtime;
+ JS_ASSERT(!rt->gcRunning);
+
+ JS_LOCK_GC(rt);
+ ok = AddToPtrTable(cx, &rt->gcIteratorTable, &iteratorTableInfo, obj);
+ JS_UNLOCK_GC(rt);
+ return ok;
+}
+
+static void
+CloseNativeIterators(JSContext *cx)
+{
+ JSRuntime *rt;
+ size_t count, newCount, i;
+ void **array;
+ JSObject *obj;
+
+ rt = cx->runtime;
+ count = rt->gcIteratorTable.count;
+ array = rt->gcIteratorTable.array;
+
+ newCount = 0;
+ for (i = 0; i != count; ++i) {
+ obj = (JSObject *)array[i];
+ if (js_IsAboutToBeFinalized(cx, obj))
+ js_CloseNativeIterator(cx, obj);
+ else
+ array[newCount++] = obj;
+ }
+ ShrinkPtrTable(&rt->gcIteratorTable, &iteratorTableInfo, newCount);
+}
+
+#if defined(DEBUG_brendan) || defined(DEBUG_timeless)
+#define DEBUG_gchist
+#endif
+
+#ifdef DEBUG_gchist
+#define NGCHIST 64
+
+static struct GCHist {
+ JSBool lastDitch;
+ JSGCThing *freeList;
+} gchist[NGCHIST];
+
+unsigned gchpos = 0;
+#endif
+
+#ifdef JS_THREADSAFE
+
+const JSGCFreeListSet js_GCEmptyFreeListSet = {
+ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, NULL
+};
+
+static void
+TrimGCFreeListsPool(JSRuntime *rt, uintN keepCount)
+{
+ JSGCFreeListSet **cursor, *freeLists, *link;
+
+ cursor = &rt->gcFreeListsPool;
+ while (keepCount != 0) {
+ --keepCount;
+ freeLists = *cursor;
+ if (!freeLists)
+ return;
+ memset(freeLists->array, 0, sizeof freeLists->array);
+ cursor = &freeLists->link;
+ }
+ freeLists = *cursor;
+ if (freeLists) {
+ *cursor = NULL;
+ do {
+ link = freeLists->link;
+ free(freeLists);
+ } while ((freeLists = link) != NULL);
+ }
+}
+
+void
+js_RevokeGCLocalFreeLists(JSContext *cx)
+{
+ JS_ASSERT(!cx->gcLocalFreeLists->link);
+ if (cx->gcLocalFreeLists != &js_GCEmptyFreeListSet) {
+ cx->gcLocalFreeLists->link = cx->runtime->gcFreeListsPool;
+ cx->runtime->gcFreeListsPool = cx->gcLocalFreeLists;
+ cx->gcLocalFreeLists = (JSGCFreeListSet *) &js_GCEmptyFreeListSet;
+ }
+}
+
+static JSGCFreeListSet *
+EnsureLocalFreeList(JSContext *cx)
+{
+ JSGCFreeListSet *freeLists;
+
+ freeLists = cx->gcLocalFreeLists;
+ if (freeLists != &js_GCEmptyFreeListSet) {
+ JS_ASSERT(freeLists);
+ return freeLists;
+ }
+
+ freeLists = cx->runtime->gcFreeListsPool;
+ if (freeLists) {
+ cx->runtime->gcFreeListsPool = freeLists->link;
+ freeLists->link = NULL;
+ } else {
+ /* JS_malloc is not used as the caller reports out-of-memory itself. */
+ freeLists = (JSGCFreeListSet *) calloc(1, sizeof *freeLists);
+ if (!freeLists)
+ return NULL;
+ }
+ cx->gcLocalFreeLists = freeLists;
+ return freeLists;
+}
+
+#endif
+
+void *
+js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
+{
+ JSRuntime *rt;
+ uintN flindex;
+ JSBool doGC;
+ JSGCThing *thing;
+ uint8 *flagp;
+ JSGCArenaList *arenaList;
+ JSGCArenaInfo *a;
+ uintN thingsLimit;
+ JSLocalRootStack *lrs;
+#ifdef JS_GCMETER
+ JSGCArenaStats *astats;
+#endif
+#ifdef JS_THREADSAFE
+ JSBool gcLocked;
+ uintN localMallocBytes;
+ JSGCFreeListSet *freeLists;
+ JSGCThing **lastptr;
+ JSGCThing *tmpthing;
+ uint8 *tmpflagp;
+ uintN maxFreeThings; /* max to take from the global free list */
+#endif
+
+ JS_ASSERT((flags & GCF_TYPEMASK) != GCX_DOUBLE);
+ rt = cx->runtime;
+ nbytes = JS_ROUNDUP(nbytes, sizeof(JSGCThing));
+ flindex = GC_FREELIST_INDEX(nbytes);
+
+ /* Updates of metering counters here may not be thread-safe. */
+ METER(astats = &cx->runtime->gcStats.arenaStats[flindex]);
+ METER(astats->alloc++);
+
+#ifdef JS_THREADSAFE
+ gcLocked = JS_FALSE;
+ JS_ASSERT(cx->thread);
+ freeLists = cx->gcLocalFreeLists;
+ thing = freeLists->array[flindex];
+ localMallocBytes = cx->thread->gcMallocBytes;
+ if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) {
+ flagp = thing->flagp;
+ freeLists->array[flindex] = thing->next;
+ METER(astats->localalloc++);
+ goto success;
+ }
+
+ JS_LOCK_GC(rt);
+ gcLocked = JS_TRUE;
+
+ /* Transfer thread-local counter to global one. */
+ if (localMallocBytes != 0) {
+ cx->thread->gcMallocBytes = 0;
+ if (rt->gcMaxMallocBytes - rt->gcMallocBytes < localMallocBytes)
+ rt->gcMallocBytes = rt->gcMaxMallocBytes;
+ else
+ rt->gcMallocBytes += localMallocBytes;
+ }
+#endif
+ JS_ASSERT(!rt->gcRunning);
+ if (rt->gcRunning) {
+ METER(rt->gcStats.finalfail++);
+ JS_UNLOCK_GC(rt);
+ return NULL;
+ }
+
+ doGC = (rt->gcMallocBytes >= rt->gcMaxMallocBytes && rt->gcPoke);
+#ifdef JS_GC_ZEAL
+ doGC = doGC || rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke);
+#endif
+
+ arenaList = &rt->gcArenaList[flindex];
+ for (;;) {
+ if (doGC && !JS_ON_TRACE(cx)) {
+ /*
+ * Keep rt->gcLock across the call into js_GC so we don't starve
+ * and lose to racing threads who deplete the heap just after
+ * js_GC has replenished it (or has synchronized with a racing
+ * GC that collected a bunch of garbage). This unfair scheduling
+ * can happen on certain operating systems. For the gory details,
+ * see bug 162779 at https://bugzilla.mozilla.org/.
+ */
+ js_GC(cx, GC_LAST_DITCH);
+ METER(astats->retry++);
+ }
+
+ /* Try to get thing from the free list. */
+ thing = arenaList->freeList;
+ if (thing) {
+ arenaList->freeList = thing->next;
+ flagp = thing->flagp;
+ JS_ASSERT(*flagp & GCF_FINAL);
+
+#ifdef JS_THREADSAFE
+ /*
+ * Refill the local free list by taking several things from the
+ * global free list unless we are still at rt->gcMaxMallocBytes
+ * barrier or the free list is already populated. The former
+ * happens when GC is canceled due to !gcCallback(cx, JSGC_BEGIN)
+ * or no gcPoke. The latter is caused via allocating new things
+ * in gcCallback(cx, JSGC_END).
+ */
+ if (rt->gcMallocBytes >= rt->gcMaxMallocBytes)
+ break;
+
+ freeLists = EnsureLocalFreeList(cx);
+ if (!freeLists)
+ goto fail;
+ if (freeLists->array[flindex])
+ break;
+
+ tmpthing = arenaList->freeList;
+ if (tmpthing) {
+ maxFreeThings = MAX_THREAD_LOCAL_THINGS;
+ do {
+ if (!tmpthing->next)
+ break;
+ tmpthing = tmpthing->next;
+ } while (--maxFreeThings != 0);
+
+ freeLists->array[flindex] = arenaList->freeList;
+ arenaList->freeList = tmpthing->next;
+ tmpthing->next = NULL;
+ }
+#endif
+ break;
+ }
+
+ /*
+ * Try to allocate things from the last arena. If it is fully used,
+ * check if we can allocate a new one and, if we cannot, consider
+ * doing a "last ditch" GC unless already tried.
+ */
+ thingsLimit = THINGS_PER_ARENA(nbytes);
+ if (arenaList->lastCount != thingsLimit) {
+ JS_ASSERT(arenaList->lastCount < thingsLimit);
+ a = arenaList->last;
+ } else {
+ a = NewGCArena(rt);
+ if (!a) {
+ if (doGC || JS_ON_TRACE(cx))
+ goto fail;
+ doGC = JS_TRUE;
+ continue;
+ }
+ a->list = arenaList;
+ a->prev = arenaList->last;
+ a->prevUntracedPage = 0;
+ a->u.untracedThings = 0;
+ arenaList->last = a;
+ arenaList->lastCount = 0;
+ }
+
+ flagp = THING_FLAGP(a, arenaList->lastCount);
+ thing = FLAGP_TO_THING(flagp, nbytes);
+ arenaList->lastCount++;
+
+#ifdef JS_THREADSAFE
+ /*
+ * Refill the local free list by taking free things from the last
+ * arena. Prefer to order free things by ascending address in the
+ * (unscientific) hope of better cache locality.
+ */
+ if (rt->gcMallocBytes >= rt->gcMaxMallocBytes)
+ break;
+
+ freeLists = EnsureLocalFreeList(cx);
+ if (!freeLists)
+ goto fail;
+ if (freeLists->array[flindex])
+ break;
+ lastptr = &freeLists->array[flindex];
+ maxFreeThings = thingsLimit - arenaList->lastCount;
+ if (maxFreeThings > MAX_THREAD_LOCAL_THINGS)
+ maxFreeThings = MAX_THREAD_LOCAL_THINGS;
+ while (maxFreeThings != 0) {
+ --maxFreeThings;
+
+ tmpflagp = THING_FLAGP(a, arenaList->lastCount);
+ tmpthing = FLAGP_TO_THING(tmpflagp, nbytes);
+ arenaList->lastCount++;
+ tmpthing->flagp = tmpflagp;
+ *tmpflagp = GCF_FINAL; /* signifying that thing is free */
+
+ *lastptr = tmpthing;
+ lastptr = &tmpthing->next;
+ }
+ *lastptr = NULL;
+#endif
+ break;
+ }
+
+ /* We successfully allocated the thing. */
+#ifdef JS_THREADSAFE
+ success:
+#endif
+ lrs = cx->localRootStack;
+ if (lrs) {
+ /*
+ * If we're in a local root scope, don't set newborn[type] at all, to
+ * avoid entraining garbage from it for an unbounded amount of time
+ * on this context. A caller will leave the local root scope and pop
+ * this reference, allowing thing to be GC'd if it has no other refs.
+ * See JS_EnterLocalRootScope and related APIs.
+ */
+ if (js_PushLocalRoot(cx, lrs, (jsval) thing) < 0) {
+ /*
+ * When we fail for a thing allocated through the tail of the last
+ * arena, thing's flag byte is not initialized. So to prevent GC
+ * accessing the uninitialized flags during the finalization, we
+ * always mark the thing as final. See bug 337407.
+ */
+ *flagp = GCF_FINAL;
+ goto fail;
+ }
+ } else {
+ /*
+ * No local root scope, so we're stuck with the old, fragile model of
+ * depending on a pigeon-hole newborn per type per context.
+ */
+ cx->weakRoots.newborn[flags & GCF_TYPEMASK] = thing;
+ }
+
+ /* We can't fail now, so update flags. */
+ *flagp = (uint8)flags;
+
+#ifdef DEBUG_gchist
+ gchist[gchpos].lastDitch = doGC;
+ gchist[gchpos].freeList = rt->gcArenaList[flindex].freeList;
+ if (++gchpos == NGCHIST)
+ gchpos = 0;
+#endif
+
+ /* This is not thread-safe for thread-local allocations. */
+ METER_IF(flags & GCF_LOCK, rt->gcStats.lockborn++);
+
+#ifdef JS_THREADSAFE
+ if (gcLocked)
+ JS_UNLOCK_GC(rt);
+#endif
+ JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
+ return thing;
+
+fail:
+#ifdef JS_THREADSAFE
+ if (gcLocked)
+ JS_UNLOCK_GC(rt);
+#endif
+ METER(astats->fail++);
+ if (!JS_ON_TRACE(cx))
+ JS_ReportOutOfMemory(cx);
+ return NULL;
+}
+
+static JSGCDoubleCell *
+RefillDoubleFreeList(JSContext *cx)
+{
+ JSRuntime *rt;
+ jsbitmap *doubleFlags, usedBits;
+ JSBool didGC = JS_FALSE;
+ JSGCArenaInfo *a;
+ uintN bit, index;
+ JSGCDoubleCell *cell, *list, *lastcell;
+
+ JS_ASSERT(!cx->doubleFreeList);
+
+ rt = cx->runtime;
+ JS_LOCK_GC(rt);
+
+ JS_ASSERT(!rt->gcRunning);
+ if (rt->gcRunning) {
+ METER(rt->gcStats.finalfail++);
+ JS_UNLOCK_GC(rt);
+ return NULL;
+ }
+
+ if (rt->gcMallocBytes >= rt->gcMaxMallocBytes && rt->gcPoke
+#ifdef JS_GC_ZEAL
+ && (rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke))
+#endif
+ ) {
+ goto do_gc;
+ }
+
+ /*
+ * Loop until we find a flag bitmap byte with unset bits indicating free
+ * double cells, then set all bits as used and put the cells to the free
+ * list for the current context.
+ */
+ doubleFlags = rt->gcDoubleArenaList.nextDoubleFlags;
+ for (;;) {
+ if (((jsuword) doubleFlags & GC_ARENA_MASK) ==
+ ARENA_INFO_OFFSET) {
+ if (doubleFlags == DOUBLE_BITMAP_SENTINEL ||
+ !((JSGCArenaInfo *) doubleFlags)->prev) {
+ a = NewGCArena(rt);
+ if (!a) {
+ do_gc:
+ if (didGC || JS_ON_TRACE(cx)) {
+ METER(rt->gcStats.doubleArenaStats.fail++);
+ JS_UNLOCK_GC(rt);
+ if (!JS_ON_TRACE(cx))
+ JS_ReportOutOfMemory(cx);
+ return NULL;
+ }
+ js_GC(cx, GC_LAST_DITCH);
+ METER(rt->gcStats.doubleArenaStats.retry++);
+ doubleFlags = rt->gcDoubleArenaList.nextDoubleFlags;
+ didGC = JS_TRUE;
+ continue;
+ }
+ a->list = NULL;
+ a->prev = NULL;
+ if (doubleFlags == DOUBLE_BITMAP_SENTINEL) {
+ JS_ASSERT(!rt->gcDoubleArenaList.first);
+ rt->gcDoubleArenaList.first = a;
+ } else {
+ JS_ASSERT(rt->gcDoubleArenaList.first);
+ ((JSGCArenaInfo *) doubleFlags)->prev = a;
+ }
+ ClearDoubleArenaFlags(a);
+ doubleFlags = DOUBLE_ARENA_BITMAP(a);
+ break;
+ }
+ doubleFlags =
+ DOUBLE_ARENA_BITMAP(((JSGCArenaInfo *) doubleFlags)->prev);
+ }
+
+ /*
+ * When doubleFlags points the last bitmap's word in the arena, its
+ * high bits corresponds to non-existing cells. ClearDoubleArenaFlags
+ * sets such bits to 1. Thus even for this last word its bit is unset
+ * iff the corresponding cell exists and free.
+ */
+ if (*doubleFlags != (jsbitmap) -1)
+ break;
+ ++doubleFlags;
+ }
+
+ rt->gcDoubleArenaList.nextDoubleFlags = doubleFlags + 1;
+ usedBits = *doubleFlags;
+ JS_ASSERT(usedBits != (jsbitmap) -1);
+ *doubleFlags = (jsbitmap) -1;
+ JS_UNLOCK_GC(rt);
+
+ /*
+ * Find the index corresponding to the first bit in *doubleFlags. The last
+ * bit will have "index + JS_BITS_PER_WORD - 1".
+ */
+ index = ((uintN) ((jsuword) doubleFlags & GC_ARENA_MASK) -
+ DOUBLES_ARENA_BITMAP_OFFSET) * JS_BITS_PER_BYTE;
+ cell = (JSGCDoubleCell *) ((jsuword) doubleFlags & ~GC_ARENA_MASK) + index;
+
+ if (usedBits == 0) {
+ /* The common case when all doubles from *doubleFlags are free. */
+ JS_ASSERT(index + JS_BITS_PER_WORD <= DOUBLES_PER_ARENA);
+ list = cell;
+ for (lastcell = cell + JS_BITS_PER_WORD - 1; cell != lastcell; ++cell)
+ cell->link = cell + 1;
+ lastcell->link = NULL;
+ } else {
+ /*
+ * Assemble the free list from free cells from *doubleFlags starting
+ * from the tail. In the loop
+ *
+ * index + bit >= DOUBLES_PER_ARENA
+ *
+ * when bit is one of the unused bits. We do not check for such bits
+ * explicitly as they must be set and the "if" check filters them out.
+ */
+ JS_ASSERT(index + JS_BITS_PER_WORD <=
+ DOUBLES_PER_ARENA + UNUSED_DOUBLE_BITMAP_BITS);
+ bit = JS_BITS_PER_WORD;
+ cell += bit;
+ list = NULL;
+ do {
+ --bit;
+ --cell;
+ if (!(((jsbitmap) 1 << bit) & usedBits)) {
+ JS_ASSERT(index + bit < DOUBLES_PER_ARENA);
+ JS_ASSERT_IF(index + bit == DOUBLES_PER_ARENA - 1, !list);
+ cell->link = list;
+ list = cell;
+ }
+ } while (bit != 0);
+ }
+ JS_ASSERT(list);
+ JS_COUNT_OPERATION(cx, JSOW_ALLOCATION * JS_BITS_PER_WORD);
+
+ /*
+ * We delegate assigning cx->doubleFreeList to js_NewDoubleInRootedValue as
+ * it immediately consumes the head of the list.
+ */
+ return list;
+}
+
+JSBool
+js_NewDoubleInRootedValue(JSContext *cx, jsdouble d, jsval *vp)
+{
+#ifdef JS_GCMETER
+ JSGCArenaStats *astats;
+#endif
+ JSGCDoubleCell *cell;
+
+ /* Updates of metering counters here are not thread-safe. */
+ METER(astats = &cx->runtime->gcStats.doubleArenaStats);
+ METER(astats->alloc++);
+ cell = cx->doubleFreeList;
+ if (!cell) {
+ cell = RefillDoubleFreeList(cx);
+ if (!cell) {
+ METER(astats->fail++);
+ return JS_FALSE;
+ }
+ } else {
+ METER(astats->localalloc++);
+ }
+ cx->doubleFreeList = cell->link;
+ cell->number = d;
+ *vp = DOUBLE_TO_JSVAL(&cell->number);
+ return JS_TRUE;
+}
+
+jsdouble *
+js_NewWeaklyRootedDouble(JSContext *cx, jsdouble d)
+{
+ jsval v;
+ jsdouble *dp;
+
+ if (!js_NewDoubleInRootedValue(cx, d, &v))
+ return NULL;
+
+ JS_ASSERT(JSVAL_IS_DOUBLE(v));
+ dp = JSVAL_TO_DOUBLE(v);
+ if (cx->localRootStack) {
+ if (js_PushLocalRoot(cx, cx->localRootStack, v) < 0)
+ return NULL;
+ } else {
+ cx->weakRoots.newborn[GCX_DOUBLE] = dp;
+ }
+ return dp;
+}
+
+JSBool
+js_AddAsGCBytes(JSContext *cx, size_t sz)
+{
+ JSRuntime *rt;
+
+ rt = cx->runtime;
+ if (rt->gcBytes >= rt->gcMaxBytes ||
+ sz > (size_t) (rt->gcMaxBytes - rt->gcBytes)
+#ifdef JS_GC_ZEAL
+ || rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke)
+#endif
+ ) {
+ if (JS_ON_TRACE(cx)) {
+ JS_UNLOCK_GC(rt);
+ return JS_FALSE;
+ }
+ js_GC(cx, GC_LAST_DITCH);
+ if (rt->gcBytes >= rt->gcMaxBytes ||
+ sz > (size_t) (rt->gcMaxBytes - rt->gcBytes)) {
+ JS_UNLOCK_GC(rt);
+ JS_ReportOutOfMemory(cx);
+ return JS_FALSE;
+ }
+ }
+ rt->gcBytes += (uint32) sz;
+ return JS_TRUE;
+}
+
+void
+js_RemoveAsGCBytes(JSRuntime *rt, size_t sz)
+{
+ JS_ASSERT((size_t) rt->gcBytes >= sz);
+ rt->gcBytes -= (uint32) sz;
+}
+
+/*
+ * Shallow GC-things can be locked just by setting the GCF_LOCK bit, because
+ * they have no descendants to mark during the GC. Currently the optimization
+ * is only used for non-dependant strings.
+ */
+#define GC_THING_IS_SHALLOW(flagp, thing) \
+ ((flagp) && \
+ ((*(flagp) & GCF_TYPEMASK) >= GCX_EXTERNAL_STRING || \
+ ((*(flagp) & GCF_TYPEMASK) == GCX_STRING && \
+ !JSSTRING_IS_DEPENDENT((JSString *) (thing)))))
+
+/* This is compatible with JSDHashEntryStub. */
+typedef struct JSGCLockHashEntry {
+ JSDHashEntryHdr hdr;
+ const void *thing;
+ uint32 count;
+} JSGCLockHashEntry;
+
+JSBool
+js_LockGCThingRT(JSRuntime *rt, void *thing)
+{
+ JSBool shallow, ok;
+ uint8 *flagp;
+ JSGCLockHashEntry *lhe;
+
+ if (!thing)
+ return JS_TRUE;
+
+ flagp = GetGCThingFlagsOrNull(thing);
+ JS_LOCK_GC(rt);
+ shallow = GC_THING_IS_SHALLOW(flagp, thing);
+
+ /*
+ * Avoid adding a rt->gcLocksHash entry for shallow things until someone
+ * nests a lock.
+ */
+ if (shallow && !(*flagp & GCF_LOCK)) {
+ *flagp |= GCF_LOCK;
+ METER(rt->gcStats.lock++);
+ ok = JS_TRUE;
+ goto out;
+ }
+
+ if (!rt->gcLocksHash) {
+ rt->gcLocksHash = JS_NewDHashTable(JS_DHashGetStubOps(), NULL,
+ sizeof(JSGCLockHashEntry),
+ GC_ROOTS_SIZE);
+ if (!rt->gcLocksHash) {
+ ok = JS_FALSE;
+ goto out;
+ }
+ }
+
+ lhe = (JSGCLockHashEntry *)
+ JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_ADD);
+ if (!lhe) {
+ ok = JS_FALSE;
+ goto out;
+ }
+ if (!lhe->thing) {
+ lhe->thing = thing;
+ lhe->count = 1;
+ } else {
+ JS_ASSERT(lhe->count >= 1);
+ lhe->count++;
+ }
+
+ METER(rt->gcStats.lock++);
+ ok = JS_TRUE;
+ out:
+ JS_UNLOCK_GC(rt);
+ return ok;
+}
+
+JSBool
+js_UnlockGCThingRT(JSRuntime *rt, void *thing)
+{
+ uint8 *flagp;
+ JSBool shallow;
+ JSGCLockHashEntry *lhe;
+
+ if (!thing)
+ return JS_TRUE;
+
+ flagp = GetGCThingFlagsOrNull(thing);
+ JS_LOCK_GC(rt);
+ shallow = GC_THING_IS_SHALLOW(flagp, thing);
+
+ if (shallow && !(*flagp & GCF_LOCK))
+ goto out;
+ if (!rt->gcLocksHash ||
+ (lhe = (JSGCLockHashEntry *)
+ JS_DHashTableOperate(rt->gcLocksHash, thing,
+ JS_DHASH_LOOKUP),
+ JS_DHASH_ENTRY_IS_FREE(&lhe->hdr))) {
+ /* Shallow entry is not in the hash -> clear its lock bit. */
+ if (shallow)
+ *flagp &= ~GCF_LOCK;
+ else
+ goto out;
+ } else {
+ if (--lhe->count != 0)
+ goto out;
+ JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_REMOVE);
+ }
+
+ rt->gcPoke = JS_TRUE;
+ METER(rt->gcStats.unlock++);
+ out:
+ JS_UNLOCK_GC(rt);
+ return JS_TRUE;
+}
+
+JS_PUBLIC_API(void)
+JS_TraceChildren(JSTracer *trc, void *thing, uint32 kind)
+{
+ JSObject *obj;
+ size_t nslots, i;
+ jsval v;
+ JSString *str;
+
+ switch (kind) {
+ case JSTRACE_OBJECT:
+ /* If obj has no map, it must be a newborn. */
+ obj = (JSObject *) thing;
+ if (!obj->map)
+ break;
+ if (obj->map->ops->trace) {
+ obj->map->ops->trace(trc, obj);
+ } else {
+ nslots = STOBJ_NSLOTS(obj);
+ for (i = 0; i != nslots; ++i) {
+ v = STOBJ_GET_SLOT(obj, i);
+ if (JSVAL_IS_TRACEABLE(v)) {
+ JS_SET_TRACING_INDEX(trc, "slot", i);
+ JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v),
+ JSVAL_TRACE_KIND(v));
+ }
+ }
+ }
+ break;
+
+ case JSTRACE_STRING:
+ str = (JSString *)thing;
+ if (JSSTRING_IS_DEPENDENT(str))
+ JS_CALL_STRING_TRACER(trc, JSSTRDEP_BASE(str), "base");
+ break;
+
+#if JS_HAS_XML_SUPPORT
+ case JSTRACE_XML:
+ js_TraceXML(trc, (JSXML *)thing);
+ break;
+#endif
+ }
+}
+
+/*
+ * Number of things covered by a single bit of JSGCArenaInfo.u.untracedThings.
+ */
+#define THINGS_PER_UNTRACED_BIT(thingSize) \
+ JS_HOWMANY(THINGS_PER_ARENA(thingSize), JS_BITS_PER_WORD)
+
+static void
+DelayTracingChildren(JSRuntime *rt, uint8 *flagp)
+{
+ JSGCArenaInfo *a;
+ uint32 untracedBitIndex;
+ jsuword bit;
+
+ /*
+ * Things with children to be traced later are marked with
+ * GCF_MARK | GCF_FINAL flags.
+ */
+ JS_ASSERT((*flagp & (GCF_MARK | GCF_FINAL)) == GCF_MARK);
+ *flagp |= GCF_FINAL;
+
+ METER(rt->gcStats.untraced++);
+#ifdef DEBUG
+ ++rt->gcTraceLaterCount;
+ METER_UPDATE_MAX(rt->gcStats.maxuntraced, rt->gcTraceLaterCount);
+#endif
+
+ a = FLAGP_TO_ARENA(flagp);
+ untracedBitIndex = FLAGP_TO_INDEX(flagp) /
+ THINGS_PER_UNTRACED_BIT(a->list->thingSize);
+ JS_ASSERT(untracedBitIndex < JS_BITS_PER_WORD);
+ bit = (jsuword)1 << untracedBitIndex;
+ if (a->u.untracedThings != 0) {
+ JS_ASSERT(rt->gcUntracedArenaStackTop);
+ if (a->u.untracedThings & bit) {
+ /* bit already covers things with children to trace later. */
+ return;
+ }
+ a->u.untracedThings |= bit;
+ } else {
+ /*
+ * The thing is the first thing with not yet traced children in the
+ * whole arena, so push the arena on the stack of arenas with things
+ * to be traced later unless the arena has already been pushed. We
+ * detect that through checking prevUntracedPage as the field is 0
+ * only for not yet pushed arenas. To ensure that
+ * prevUntracedPage != 0
+ * even when the stack contains one element, we make prevUntracedPage
+ * for the arena at the bottom to point to itself.
+ *
+ * See comments in TraceDelayedChildren.
+ */
+ a->u.untracedThings = bit;
+ if (a->prevUntracedPage == 0) {
+ if (!rt->gcUntracedArenaStackTop) {
+ /* Stack was empty, mark the arena as the bottom element. */
+ a->prevUntracedPage = ARENA_INFO_TO_PAGE(a);
+ } else {
+ JS_ASSERT(rt->gcUntracedArenaStackTop->prevUntracedPage != 0);
+ a->prevUntracedPage =
+ ARENA_INFO_TO_PAGE(rt->gcUntracedArenaStackTop);
+ }
+ rt->gcUntracedArenaStackTop = a;
+ }
+ }
+ JS_ASSERT(rt->gcUntracedArenaStackTop);
+}
+
+static void
+TraceDelayedChildren(JSTracer *trc)
+{
+ JSRuntime *rt;
+ JSGCArenaInfo *a, *aprev;
+ uint32 thingSize;
+ uint32 thingsPerUntracedBit;
+ uint32 untracedBitIndex, thingIndex, indexLimit, endIndex;
+ JSGCThing *thing;
+ uint8 *flagp;
+
+ rt = trc->context->runtime;
+ a = rt->gcUntracedArenaStackTop;
+ if (!a) {
+ JS_ASSERT(rt->gcTraceLaterCount == 0);
+ return;
+ }
+
+ for (;;) {
+ /*
+ * The following assert verifies that the current arena belongs to the
+ * untraced stack, since DelayTracingChildren ensures that even for
+ * stack's bottom prevUntracedPage != 0 but rather points to itself.
+ */
+ JS_ASSERT(a->prevUntracedPage != 0);
+ JS_ASSERT(rt->gcUntracedArenaStackTop->prevUntracedPage != 0);
+ thingSize = a->list->thingSize;
+ indexLimit = (a == a->list->last)
+ ? a->list->lastCount
+ : THINGS_PER_ARENA(thingSize);
+ thingsPerUntracedBit = THINGS_PER_UNTRACED_BIT(thingSize);
+
+ /*
+ * We cannot use do-while loop here as a->u.untracedThings can be zero
+ * before the loop as a leftover from the previous iterations. See
+ * comments after the loop.
+ */
+ while (a->u.untracedThings != 0) {
+ untracedBitIndex = JS_FLOOR_LOG2W(a->u.untracedThings);
+ a->u.untracedThings &= ~((jsuword)1 << untracedBitIndex);
+ thingIndex = untracedBitIndex * thingsPerUntracedBit;
+ endIndex = thingIndex + thingsPerUntracedBit;
+
+ /*
+ * endIndex can go beyond the last allocated thing as the real
+ * limit can be "inside" the bit.
+ */
+ if (endIndex > indexLimit)
+ endIndex = indexLimit;
+ JS_ASSERT(thingIndex < indexLimit);
+
+ do {
+ /*
+ * Skip free or already traced things that share the bit
+ * with untraced ones.
+ */
+ flagp = THING_FLAGP(a, thingIndex);
+ if ((*flagp & (GCF_MARK|GCF_FINAL)) != (GCF_MARK|GCF_FINAL))
+ continue;
+ *flagp &= ~GCF_FINAL;
+#ifdef DEBUG
+ JS_ASSERT(rt->gcTraceLaterCount != 0);
+ --rt->gcTraceLaterCount;
+#endif
+ thing = FLAGP_TO_THING(flagp, thingSize);
+ JS_TraceChildren(trc, thing, MapGCFlagsToTraceKind(*flagp));
+ } while (++thingIndex != endIndex);
+ }
+
+ /*
+ * We finished tracing of all things in the the arena but we can only
+ * pop it from the stack if the arena is the stack's top.
+ *
+ * When JS_TraceChildren from the above calls JS_CallTracer that in
+ * turn on low C stack calls DelayTracingChildren and the latter
+ * pushes new arenas to the untraced stack, we have to skip popping
+ * of this arena until it becomes the top of the stack again.
+ */
+ if (a == rt->gcUntracedArenaStackTop) {
+ aprev = ARENA_PAGE_TO_INFO(a->prevUntracedPage);
+ a->prevUntracedPage = 0;
+ if (a == aprev) {
+ /*
+ * prevUntracedPage points to itself and we reached the
+ * bottom of the stack.
+ */
+ break;
+ }
+ rt->gcUntracedArenaStackTop = a = aprev;
+ } else {
+ a = rt->gcUntracedArenaStackTop;
+ }
+ }
+ JS_ASSERT(rt->gcUntracedArenaStackTop);
+ JS_ASSERT(rt->gcUntracedArenaStackTop->prevUntracedPage == 0);
+ rt->gcUntracedArenaStackTop = NULL;
+ JS_ASSERT(rt->gcTraceLaterCount == 0);
+}
+
+JS_PUBLIC_API(void)
+JS_CallTracer(JSTracer *trc, void *thing, uint32 kind)
+{
+ JSContext *cx;
+ JSRuntime *rt;
+ JSGCArenaInfo *a;
+ uintN index;
+ uint8 *flagp;
+
+ JS_ASSERT(thing);
+ JS_ASSERT(JS_IS_VALID_TRACE_KIND(kind));
+ JS_ASSERT(trc->debugPrinter || trc->debugPrintArg);
+
+ if (!IS_GC_MARKING_TRACER(trc)) {
+ trc->callback(trc, thing, kind);
+ goto out;
+ }
+
+ cx = trc->context;
+ rt = cx->runtime;
+ JS_ASSERT(rt->gcMarkingTracer == trc);
+ JS_ASSERT(rt->gcLevel > 0);
+
+ /*
+ * Optimize for string and double as their size is known and their tracing
+ * is not recursive.
+ */
+ switch (kind) {
+ case JSTRACE_DOUBLE:
+ a = THING_TO_ARENA(thing);
+ JS_ASSERT(!a->list);
+ if (!a->u.hasMarkedDoubles) {
+ ClearDoubleArenaFlags(a);
+ a->u.hasMarkedDoubles = JS_TRUE;
+ }
+ index = DOUBLE_THING_TO_INDEX(thing);
+ JS_SET_BIT(DOUBLE_ARENA_BITMAP(a), index);
+ goto out;
+
+ case JSTRACE_STRING:
+ for (;;) {
+ flagp = THING_TO_FLAGP(thing, sizeof(JSGCThing));
+ JS_ASSERT((*flagp & GCF_FINAL) == 0);
+ JS_ASSERT(kind == MapGCFlagsToTraceKind(*flagp));
+ if (!JSSTRING_IS_DEPENDENT((JSString *) thing)) {
+ *flagp |= GCF_MARK;
+ goto out;
+ }
+ if (*flagp & GCF_MARK)
+ goto out;
+ *flagp |= GCF_MARK;
+ thing = JSSTRDEP_BASE((JSString *) thing);
+ }
+ /* NOTREACHED */
+ }
+
+ flagp = GetGCThingFlags(thing);
+ JS_ASSERT(kind == MapGCFlagsToTraceKind(*flagp));
+ if (*flagp & GCF_MARK)
+ goto out;
+
+ /*
+ * We check for non-final flag only if mark is unset as
+ * DelayTracingChildren uses the flag. See comments in the function.
+ */
+ JS_ASSERT(*flagp != GCF_FINAL);
+ *flagp |= GCF_MARK;
+ if (!cx->insideGCMarkCallback) {
+ /*
+ * With JS_GC_ASSUME_LOW_C_STACK defined the mark phase of GC always
+ * uses the non-recursive code that otherwise would be called only on
+ * a low C stack condition.
+ */
+#ifdef JS_GC_ASSUME_LOW_C_STACK
+# define RECURSION_TOO_DEEP() JS_TRUE
+#else
+ int stackDummy;
+# define RECURSION_TOO_DEEP() (!JS_CHECK_STACK_SIZE(cx, stackDummy))
+#endif
+ if (RECURSION_TOO_DEEP())
+ DelayTracingChildren(rt, flagp);
+ else
+ JS_TraceChildren(trc, thing, kind);
+ } else {
+ /*
+ * For API compatibility we allow for the callback to assume that
+ * after it calls JS_MarkGCThing for the last time, the callback can
+ * start to finalize its own objects that are only referenced by
+ * unmarked GC things.
+ *
+ * Since we do not know which call from inside the callback is the
+ * last, we ensure that children of all marked things are traced and
+ * call TraceDelayedChildren(trc) after tracing the thing.
+ *
+ * As TraceDelayedChildren unconditionally invokes JS_TraceChildren
+ * for the things with untraced children, calling DelayTracingChildren
+ * is useless here. Hence we always trace thing's children even with a
+ * low native stack.
+ */
+ cx->insideGCMarkCallback = JS_FALSE;
+ JS_TraceChildren(trc, thing, kind);
+ TraceDelayedChildren(trc);
+ cx->insideGCMarkCallback = JS_TRUE;
+ }
+
+ out:
+#ifdef DEBUG
+ trc->debugPrinter = NULL;
+ trc->debugPrintArg = NULL;
+#endif
+ return; /* to avoid out: right_curl when DEBUG is not defined */
+}
+
+void
+js_CallValueTracerIfGCThing(JSTracer *trc, jsval v)
+{
+ void *thing;
+ uint32 kind;
+
+ if (JSVAL_IS_DOUBLE(v) || JSVAL_IS_STRING(v)) {
+ thing = JSVAL_TO_TRACEABLE(v);
+ kind = JSVAL_TRACE_KIND(v);
+ JS_ASSERT(kind == js_GetGCThingTraceKind(JSVAL_TO_GCTHING(v)));
+ } else if (JSVAL_IS_OBJECT(v) && v != JSVAL_NULL) {
+ /* v can be an arbitrary GC thing reinterpreted as an object. */
+ thing = JSVAL_TO_OBJECT(v);
+ kind = js_GetGCThingTraceKind(thing);
+ } else {
+ return;
+ }
+ JS_CallTracer(trc, thing, kind);
+}
+
+static JSDHashOperator
+gc_root_traversal(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num,
+ void *arg)
+{
+ JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
+ JSTracer *trc = (JSTracer *)arg;
+ jsval *rp = (jsval *)rhe->root;
+ jsval v = *rp;
+
+ /* Ignore null object and scalar values. */
+ if (!JSVAL_IS_NULL(v) && JSVAL_IS_GCTHING(v)) {
+#ifdef DEBUG
+ JSBool root_points_to_gcArenaList = JS_FALSE;
+ jsuword thing = (jsuword) JSVAL_TO_GCTHING(v);
+ JSRuntime *rt;
+ uintN i;
+ JSGCArenaList *arenaList;
+ uint32 thingSize;
+ JSGCArenaInfo *a;
+ size_t limit;
+
+ rt = trc->context->runtime;
+ for (i = 0; i < GC_NUM_FREELISTS; i++) {
+ arenaList = &rt->gcArenaList[i];
+ thingSize = arenaList->thingSize;
+ limit = (size_t) arenaList->lastCount * thingSize;
+ for (a = arenaList->last; a; a = a->prev) {
+ if (thing - ARENA_INFO_TO_START(a) < limit) {
+ root_points_to_gcArenaList = JS_TRUE;
+ break;
+ }
+ limit = (size_t) THINGS_PER_ARENA(thingSize) * thingSize;
+ }
+ }
+ if (!root_points_to_gcArenaList) {
+ for (a = rt->gcDoubleArenaList.first; a; a = a->prev) {
+ if (thing - ARENA_INFO_TO_START(a) <
+ DOUBLES_PER_ARENA * sizeof(jsdouble)) {
+ root_points_to_gcArenaList = JS_TRUE;
+ break;
+ }
+ }
+ }
+ if (!root_points_to_gcArenaList && rhe->name) {
+ fprintf(stderr,
+"JS API usage error: the address passed to JS_AddNamedRoot currently holds an\n"
+"invalid jsval. This is usually caused by a missing call to JS_RemoveRoot.\n"
+"The root's name is \"%s\".\n",
+ rhe->name);
+ }
+ JS_ASSERT(root_points_to_gcArenaList);
+#endif
+ JS_SET_TRACING_NAME(trc, rhe->name ? rhe->name : "root");
+ js_CallValueTracerIfGCThing(trc, v);
+ }
+
+ return JS_DHASH_NEXT;
+}
+
+static JSDHashOperator
+gc_lock_traversal(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num,
+ void *arg)
+{
+ JSGCLockHashEntry *lhe = (JSGCLockHashEntry *)hdr;
+ void *thing = (void *)lhe->thing;
+ JSTracer *trc = (JSTracer *)arg;
+ uint32 traceKind;
+
+ JS_ASSERT(lhe->count >= 1);
+ traceKind = js_GetGCThingTraceKind(thing);
+ JS_CALL_TRACER(trc, thing, traceKind, "locked object");
+ return JS_DHASH_NEXT;
+}
+
+#define TRACE_JSVALS(trc, len, vec, name) \
+ JS_BEGIN_MACRO \
+ jsval _v, *_vp, *_end; \
+ \
+ for (_vp = vec, _end = _vp + len; _vp < _end; _vp++) { \
+ _v = *_vp; \
+ if (JSVAL_IS_TRACEABLE(_v)) { \
+ JS_SET_TRACING_INDEX(trc, name, _vp - (vec)); \
+ JS_CallTracer(trc, JSVAL_TO_TRACEABLE(_v), \
+ JSVAL_TRACE_KIND(_v)); \
+ } \
+ } \
+ JS_END_MACRO
+
+void
+js_TraceStackFrame(JSTracer *trc, JSStackFrame *fp)
+{
+ uintN nslots, minargs, skip;
+
+ if (fp->callobj)
+ JS_CALL_OBJECT_TRACER(trc, fp->callobj, "call");
+ if (fp->argsobj)
+ JS_CALL_OBJECT_TRACER(trc, fp->argsobj, "arguments");
+ if (fp->varobj)
+ JS_CALL_OBJECT_TRACER(trc, fp->varobj, "variables");
+ if (fp->script) {
+ js_TraceScript(trc, fp->script);
+ if (fp->regs) {
+ /*
+ * Don't mark what has not been pushed yet, or what has been
+ * popped already.
+ */
+ nslots = (uintN) (fp->regs->sp - fp->slots);
+ TRACE_JSVALS(trc, nslots, fp->slots, "slot");
+ }
+ } else {
+ JS_ASSERT(!fp->slots);
+ JS_ASSERT(!fp->regs);
+ }
+
+ /* Allow for primitive this parameter due to JSFUN_THISP_* flags. */
+ JS_ASSERT(JSVAL_IS_OBJECT((jsval)fp->thisp) ||
+ (fp->fun && JSFUN_THISP_FLAGS(fp->fun->flags)));
+ JS_CALL_VALUE_TRACER(trc, (jsval)fp->thisp, "this");
+
+ if (fp->callee)
+ JS_CALL_OBJECT_TRACER(trc, fp->callee, "callee");
+
+ if (fp->argv) {
+ nslots = fp->argc;
+ skip = 0;
+ if (fp->fun) {
+ minargs = FUN_MINARGS(fp->fun);
+ if (minargs > nslots)
+ nslots = minargs;
+ if (!FUN_INTERPRETED(fp->fun)) {
+ JS_ASSERT(!(fp->fun->flags & JSFUN_FAST_NATIVE));
+ nslots += fp->fun->u.n.extra;
+ }
+ if (fp->fun->flags & JSFRAME_ROOTED_ARGV)
+ skip = 2 + fp->argc;
+ }
+ TRACE_JSVALS(trc, 2 + nslots - skip, fp->argv - 2 + skip, "operand");
+ }
+
+ JS_CALL_VALUE_TRACER(trc, fp->rval, "rval");
+ if (fp->scopeChain)
+ JS_CALL_OBJECT_TRACER(trc, fp->scopeChain, "scope chain");
+ if (fp->sharpArray)
+ JS_CALL_OBJECT_TRACER(trc, fp->sharpArray, "sharp array");
+
+ if (fp->xmlNamespace)
+ JS_CALL_OBJECT_TRACER(trc, fp->xmlNamespace, "xmlNamespace");
+}
+
+static void
+TraceWeakRoots(JSTracer *trc, JSWeakRoots *wr)
+{
+ uint32 i;
+ void *thing;
+
+#ifdef DEBUG
+ static const char *weakRootNames[JSTRACE_LIMIT] = {
+ "newborn object",
+ "newborn double",
+ "newborn string",
+ "newborn xml"
+ };
+#endif
+
+ for (i = 0; i != JSTRACE_LIMIT; i++) {
+ thing = wr->newborn[i];
+ if (thing)
+ JS_CALL_TRACER(trc, thing, i, weakRootNames[i]);
+ }
+ JS_ASSERT(i == GCX_EXTERNAL_STRING);
+ for (; i != GCX_NTYPES; ++i) {
+ thing = wr->newborn[i];
+ if (thing) {
+ JS_SET_TRACING_INDEX(trc, "newborn external string",
+ i - GCX_EXTERNAL_STRING);
+ JS_CallTracer(trc, thing, JSTRACE_STRING);
+ }
+ }
+
+ JS_CALL_VALUE_TRACER(trc, wr->lastAtom, "lastAtom");
+ JS_SET_TRACING_NAME(trc, "lastInternalResult");
+ js_CallValueTracerIfGCThing(trc, wr->lastInternalResult);
+}
+
+JS_FRIEND_API(void)
+js_TraceContext(JSTracer *trc, JSContext *acx)
+{
+ JSStackFrame *fp, *nextChain;
+ JSStackHeader *sh;
+ JSTempValueRooter *tvr;
+
+ if (IS_GC_MARKING_TRACER(trc)) {
+
+#define FREE_OLD_ARENAS(pool) \
+ JS_BEGIN_MACRO \
+ int64 _age; \
+ JSArena * _a = (pool).current; \
+ if (_a == (pool).first.next && \
+ _a->avail == _a->base + sizeof(int64)) { \
+ _age = JS_Now() - *(int64 *) _a->base; \
+ if (_age > (int64) acx->runtime->gcEmptyArenaPoolLifespan * \
+ 1000) \
+ JS_FreeArenaPool(&(pool)); \
+ } \
+ JS_END_MACRO
+
+#ifdef JS_THREADSAFE
+ js_RevokeGCLocalFreeLists(acx);
+#endif
+
+ /*
+ * Release the stackPool's arenas if the stackPool has existed for
+ * longer than the limit specified by gcEmptyArenaPoolLifespan.
+ */
+ FREE_OLD_ARENAS(acx->stackPool);
+
+ /*
+ * Release the regexpPool's arenas based on the same criterion as for
+ * the stackPool.
+ */
+ FREE_OLD_ARENAS(acx->regexpPool);
+
+ /*
+ * Clear the double free list to release all the pre-allocated doubles.
+ */
+ acx->doubleFreeList = NULL;
+ }
+
+ /*
+ * Iterate frame chain and dormant chains.
+ *
+ * (NB: see comment on this whole "dormant" thing in js_Execute.)
+ */
+ fp = acx->fp;
+ nextChain = acx->dormantFrameChain;
+ if (!fp)
+ goto next_chain;
+
+ /* The top frame must not be dormant. */
+ JS_ASSERT(!fp->dormantNext);
+ for (;;) {
+ do {
+ js_TraceStackFrame(trc, fp);
+ } while ((fp = fp->down) != NULL);
+
+ next_chain:
+ if (!nextChain)
+ break;
+ fp = nextChain;
+ nextChain = nextChain->dormantNext;
+ }
+
+ /* Mark other roots-by-definition in acx. */
+ if (acx->globalObject)
+ JS_CALL_OBJECT_TRACER(trc, acx->globalObject, "global object");
+ TraceWeakRoots(trc, &acx->weakRoots);
+ if (acx->throwing) {
+ JS_CALL_VALUE_TRACER(trc, acx->exception, "exception");
+ } else {
+ /* Avoid keeping GC-ed junk stored in JSContext.exception. */
+ acx->exception = JSVAL_NULL;
+ }
+#if JS_HAS_LVALUE_RETURN
+ if (acx->rval2set)
+ JS_CALL_VALUE_TRACER(trc, acx->rval2, "rval2");
+#endif
+
+ for (sh = acx->stackHeaders; sh; sh = sh->down) {
+ METER(trc->context->runtime->gcStats.stackseg++);
+ METER(trc->context->runtime->gcStats.segslots += sh->nslots);
+ TRACE_JSVALS(trc, sh->nslots, JS_STACK_SEGMENT(sh), "stack");
+ }
+
+ if (acx->localRootStack)
+ js_TraceLocalRoots(trc, acx->localRootStack);
+
+ for (tvr = acx->tempValueRooters; tvr; tvr = tvr->down) {
+ switch (tvr->count) {
+ case JSTVU_SINGLE:
+ JS_SET_TRACING_NAME(trc, "tvr->u.value");
+ js_CallValueTracerIfGCThing(trc, tvr->u.value);
+ break;
+ case JSTVU_TRACE:
+ tvr->u.trace(trc, tvr);
+ break;
+ case JSTVU_SPROP:
+ TRACE_SCOPE_PROPERTY(trc, tvr->u.sprop);
+ break;
+ case JSTVU_WEAK_ROOTS:
+ TraceWeakRoots(trc, tvr->u.weakRoots);
+ break;
+ case JSTVU_PARSE_CONTEXT:
+ js_TraceParseContext(trc, tvr->u.parseContext);
+ break;
+ case JSTVU_SCRIPT:
+ js_TraceScript(trc, tvr->u.script);
+ break;
+ default:
+ JS_ASSERT(tvr->count >= 0);
+ TRACE_JSVALS(trc, tvr->count, tvr->u.array, "tvr->u.array");
+ }
+ }
+
+ if (acx->sharpObjectMap.depth > 0)
+ js_TraceSharpMap(trc, &acx->sharpObjectMap);
+}
+
+void
+js_TraceTraceMonitor(JSTracer *trc, JSTraceMonitor *tm)
+{
+ if (IS_GC_MARKING_TRACER(trc)) {
+ tm->recoveryDoublePoolPtr = tm->recoveryDoublePool;
+ /* Make sure the global shape changes and will force a flush
+ of the code cache. */
+ tm->globalShape = -1;
+ }
+}
+
+void
+js_TraceRuntime(JSTracer *trc, JSBool allAtoms)
+{
+ JSRuntime *rt = trc->context->runtime;
+ JSContext *iter, *acx;
+
+ JS_DHashTableEnumerate(&rt->gcRootsHash, gc_root_traversal, trc);
+ if (rt->gcLocksHash)
+ JS_DHashTableEnumerate(rt->gcLocksHash, gc_lock_traversal, trc);
+ js_TraceAtomState(trc, allAtoms);
+ js_TraceNativeEnumerators(trc);
+ js_TraceRuntimeNumberState(trc);
+
+ iter = NULL;
+ while ((acx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL)
+ js_TraceContext(trc, acx);
+
+ if (rt->gcExtraRootsTraceOp)
+ rt->gcExtraRootsTraceOp(trc, rt->gcExtraRootsData);
+
+#ifdef JS_THREADSAFE
+ /* Trace the loop table(s) which can contain pointers to code objects. */
+ while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) {
+ if (!acx->thread)
+ continue;
+ js_TraceTraceMonitor(trc, &acx->thread->traceMonitor);
+ }
+#else
+ js_TraceTraceMonitor(trc, &rt->traceMonitor);
+#endif
+}
+
+static void
+ProcessSetSlotRequest(JSContext *cx, JSSetSlotRequest *ssr)
+{
+ JSObject *obj, *pobj;
+ uint32 slot;
+
+ obj = ssr->obj;
+ pobj = ssr->pobj;
+ slot = ssr->slot;
+
+ while (pobj) {
+ pobj = js_GetWrappedObject(cx, pobj);
+ if (pobj == obj) {
+ ssr->errnum = JSMSG_CYCLIC_VALUE;
+ return;
+ }
+ pobj = JSVAL_TO_OBJECT(STOBJ_GET_SLOT(pobj, slot));
+ }
+
+ pobj = ssr->pobj;
+
+ if (slot == JSSLOT_PROTO && OBJ_IS_NATIVE(obj)) {
+ JSScope *scope, *newscope;
+ JSObject *oldproto;
+
+ /* Check to see whether obj shares its prototype's scope. */
+ scope = OBJ_SCOPE(obj);
+ oldproto = STOBJ_GET_PROTO(obj);
+ if (oldproto && OBJ_SCOPE(oldproto) == scope) {
+ /* Either obj needs a new empty scope, or it should share pobj's. */
+ if (!pobj ||
+ !OBJ_IS_NATIVE(pobj) ||
+ OBJ_GET_CLASS(cx, pobj) != STOBJ_GET_CLASS(oldproto)) {
+ /*
+ * With no proto and no scope of its own, obj is truly empty.
+ *
+ * If pobj is not native, obj needs its own empty scope -- it
+ * should not continue to share oldproto's scope once oldproto
+ * is not on obj's prototype chain. That would put properties
+ * from oldproto's scope ahead of properties defined by pobj,
+ * in lookup order.
+ *
+ * If pobj's class differs from oldproto's, we may need a new
+ * scope to handle differences in private and reserved slots,
+ * so we suboptimally but safely make one.
+ */
+ if (!js_GetMutableScope(cx, obj)) {
+ ssr->errnum = JSMSG_OUT_OF_MEMORY;
+ return;
+ }
+ } else if (OBJ_SCOPE(pobj) != scope) {
+ newscope = (JSScope *) js_HoldObjectMap(cx, pobj->map);
+ obj->map = &newscope->map;
+ js_DropObjectMap(cx, &scope->map, obj);
+ JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope);
+ }
+ }
+
+ /*
+ * Regenerate property cache shape ids for all of the scopes along the
+ * old prototype chain, in case any property cache entries were filled
+ * by looking up starting from obj.
+ */
+ while (oldproto && OBJ_IS_NATIVE(oldproto)) {
+ scope = OBJ_SCOPE(oldproto);
+ SCOPE_MAKE_UNIQUE_SHAPE(cx, scope);
+ oldproto = STOBJ_GET_PROTO(scope->object);
+ }
+ }
+
+ /* Finally, do the deed. */
+ STOBJ_SET_SLOT(obj, slot, OBJECT_TO_JSVAL(pobj));
+}
+
+static void
+DestroyScriptsToGC(JSContext *cx, JSScript **listp)
+{
+ JSScript *script;
+
+ while ((script = *listp) != NULL) {
+ *listp = script->u.nextToGC;
+ script->u.nextToGC = NULL;
+ js_DestroyScript(cx, script);
+ }
+}
+
+/*
+ * The gckind flag bit GC_LOCK_HELD indicates a call from js_NewGCThing with
+ * rt->gcLock already held, so the lock should be kept on return.
+ */
+void
+js_GC(JSContext *cx, JSGCInvocationKind gckind)
+{
+ JSRuntime *rt;
+ JSBool keepAtoms;
+ JSGCCallback callback;
+ uintN i, type;
+ JSTracer trc;
+ uint32 thingSize, indexLimit;
+ JSGCArenaInfo *a, **ap, *emptyArenas;
+ uint8 flags, *flagp;
+ JSGCThing *thing, *freeList;
+ JSGCArenaList *arenaList;
+ JSBool allClear;
+#ifdef JS_THREADSAFE
+ uint32 requestDebit;
+ JSContext *acx, *iter;
+#endif
+#ifdef JS_GCMETER
+ uint32 nlivearenas, nkilledarenas, nthings;
+#endif
+
+ JS_ASSERT_IF(gckind == GC_LAST_DITCH, !JS_ON_TRACE(cx));
+ rt = cx->runtime;
+#ifdef JS_THREADSAFE
+ /* Avoid deadlock. */
+ JS_ASSERT(!JS_IS_RUNTIME_LOCKED(rt));
+#endif
+
+ if (gckind & GC_KEEP_ATOMS) {
+ /*
+ * The set slot request and last ditch GC kinds preserve all atoms and
+ * weak roots.
+ */
+ keepAtoms = JS_TRUE;
+ } else {
+ /* Keep atoms when a suspended compile is running on another context. */
+ keepAtoms = (rt->gcKeepAtoms != 0);
+ JS_CLEAR_WEAK_ROOTS(&cx->weakRoots);
+ }
+
+ /*
+ * Don't collect garbage if the runtime isn't up, and cx is not the last
+ * context in the runtime. The last context must force a GC, and nothing
+ * should suppress that final collection or there may be shutdown leaks,
+ * or runtime bloat until the next context is created.
+ */
+ if (rt->state != JSRTS_UP && gckind != GC_LAST_CONTEXT)
+ return;
+
+ restart_at_beginning:
+ /*
+ * Let the API user decide to defer a GC if it wants to (unless this
+ * is the last context). Invoke the callback regardless. Sample the
+ * callback in case we are freely racing with a JS_SetGCCallback{,RT} on
+ * another thread.
+ */
+ if (gckind != GC_SET_SLOT_REQUEST && (callback = rt->gcCallback)) {
+ JSBool ok;
+
+ if (gckind & GC_LOCK_HELD)
+ JS_UNLOCK_GC(rt);
+ ok = callback(cx, JSGC_BEGIN);
+ if (gckind & GC_LOCK_HELD)
+ JS_LOCK_GC(rt);
+ if (!ok && gckind != GC_LAST_CONTEXT) {
+ /*
+ * It's possible that we've looped back to this code from the 'goto
+ * restart_at_beginning' below in the GC_SET_SLOT_REQUEST code and
+ * that rt->gcLevel is now 0. Don't return without notifying!
+ */
+ if (rt->gcLevel == 0 && (gckind & GC_LOCK_HELD))
+ JS_NOTIFY_GC_DONE(rt);
+ return;
+ }
+ }
+
+ /* Lock out other GC allocator and collector invocations. */
+ if (!(gckind & GC_LOCK_HELD))
+ JS_LOCK_GC(rt);
+
+ METER(rt->gcStats.poke++);
+ rt->gcPoke = JS_FALSE;
+
+#ifdef JS_THREADSAFE
+ JS_ASSERT(cx->thread->id == js_CurrentThreadId());
+
+ /* Bump gcLevel and return rather than nest on this thread. */
+ if (rt->gcThread == cx->thread) {
+ JS_ASSERT(rt->gcLevel > 0);
+ rt->gcLevel++;
+ METER_UPDATE_MAX(rt->gcStats.maxlevel, rt->gcLevel);
+ if (!(gckind & GC_LOCK_HELD))
+ JS_UNLOCK_GC(rt);
+ return;
+ }
+
+ /*
+ * If we're in one or more requests (possibly on more than one context)
+ * running on the current thread, indicate, temporarily, that all these
+ * requests are inactive. If cx->thread is NULL, then cx is not using
+ * the request model, and does not contribute to rt->requestCount.
+ */
+ requestDebit = 0;
+ if (cx->thread) {
+ JSCList *head, *link;
+
+ /*
+ * Check all contexts on cx->thread->contextList for active requests,
+ * counting each such context against requestDebit.
+ */
+ head = &cx->thread->contextList;
+ for (link = head->next; link != head; link = link->next) {
+ acx = CX_FROM_THREAD_LINKS(link);
+ JS_ASSERT(acx->thread == cx->thread);
+ if (acx->requestDepth)
+ requestDebit++;
+ }
+ } else {
+ /*
+ * We assert, but check anyway, in case someone is misusing the API.
+ * Avoiding the loop over all of rt's contexts is a win in the event
+ * that the GC runs only on request-less contexts with null threads,
+ * in a special thread such as might be used by the UI/DOM/Layout
+ * "mozilla" or "main" thread in Mozilla-the-browser.
+ */
+ JS_ASSERT(cx->requestDepth == 0);
+ if (cx->requestDepth)
+ requestDebit = 1;
+ }
+ if (requestDebit) {
+ JS_ASSERT(requestDebit <= rt->requestCount);
+ rt->requestCount -= requestDebit;
+ if (rt->requestCount == 0)
+ JS_NOTIFY_REQUEST_DONE(rt);
+ }
+
+ /* If another thread is already in GC, don't attempt GC; wait instead. */
+ if (rt->gcLevel > 0) {
+ /* Bump gcLevel to restart the current GC, so it finds new garbage. */
+ rt->gcLevel++;
+ METER_UPDATE_MAX(rt->gcStats.maxlevel, rt->gcLevel);
+
+ /* Wait for the other thread to finish, then resume our request. */
+ while (rt->gcLevel > 0)
+ JS_AWAIT_GC_DONE(rt);
+ if (requestDebit)
+ rt->requestCount += requestDebit;
+ if (!(gckind & GC_LOCK_HELD))
+ JS_UNLOCK_GC(rt);
+ return;
+ }
+
+ /* No other thread is in GC, so indicate that we're now in GC. */
+ rt->gcLevel = 1;
+ rt->gcThread = cx->thread;
+
+ /* Wait for all other requests to finish. */
+ while (rt->requestCount > 0)
+ JS_AWAIT_REQUEST_DONE(rt);
+
+#else /* !JS_THREADSAFE */
+
+ /* Bump gcLevel and return rather than nest; the outer gc will restart. */
+ rt->gcLevel++;
+ METER_UPDATE_MAX(rt->gcStats.maxlevel, rt->gcLevel);
+ if (rt->gcLevel > 1)
+ return;
+
+#endif /* !JS_THREADSAFE */
+
+ /*
+ * Set rt->gcRunning here within the GC lock, and after waiting for any
+ * active requests to end, so that new requests that try to JS_AddRoot,
+ * JS_RemoveRoot, or JS_RemoveRootRT block in JS_BeginRequest waiting for
+ * rt->gcLevel to drop to zero, while request-less calls to the *Root*
+ * APIs block in js_AddRoot or js_RemoveRoot (see above in this file),
+ * waiting for GC to finish.
+ */
+ rt->gcRunning = JS_TRUE;
+
+ if (gckind == GC_SET_SLOT_REQUEST) {
+ JSSetSlotRequest *ssr;
+
+ while ((ssr = rt->setSlotRequests) != NULL) {
+ rt->setSlotRequests = ssr->next;
+ JS_UNLOCK_GC(rt);
+ ssr->next = NULL;
+ ProcessSetSlotRequest(cx, ssr);
+ JS_LOCK_GC(rt);
+ }
+
+ /*
+ * We assume here that killing links to parent and prototype objects
+ * does not create garbage (such objects typically are long-lived and
+ * widely shared, e.g. global objects, Function.prototype, etc.). We
+ * collect garbage only if a racing thread attempted GC and is waiting
+ * for us to finish (gcLevel > 1) or if someone already poked us.
+ */
+ if (rt->gcLevel == 1 && !rt->gcPoke)
+ goto done_running;
+
+ rt->gcLevel = 0;
+ rt->gcPoke = JS_FALSE;
+ rt->gcRunning = JS_FALSE;
+#ifdef JS_THREADSAFE
+ rt->gcThread = NULL;
+ rt->requestCount += requestDebit;
+#endif
+ gckind = GC_LOCK_HELD;
+ goto restart_at_beginning;
+ }
+
+ JS_UNLOCK_GC(rt);
+
+#ifdef JS_TRACER
+ if (JS_ON_TRACE(cx))
+ goto out;
+#endif
+
+ /* Reset malloc counter. */
+ rt->gcMallocBytes = 0;
+
+#ifdef JS_DUMP_SCOPE_METERS
+ { extern void js_DumpScopeMeters(JSRuntime *rt);
+ js_DumpScopeMeters(rt);
+ }
+#endif
+
+ /* Clear property and JIT oracle caches (only for cx->thread if JS_THREADSAFE). */
+ js_FlushPropertyCache(cx);
+#ifdef JS_TRACER
+ js_FlushJITOracle(cx);
+#endif
+
+ /* Destroy eval'ed scripts. */
+ DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx));
+
+#ifdef JS_THREADSAFE
+ /*
+ * Clear thread-based caches. To avoid redundant clearing we unroll the
+ * current thread's step.
+ *
+ * In case a JSScript wrapped within an object was finalized, we null
+ * acx->thread->gsnCache.script and finish the cache's hashtable. Note
+ * that js_DestroyScript, called from script_finalize, will have already
+ * cleared cx->thread->gsnCache above during finalization, so we don't
+ * have to here.
+ */
+ iter = NULL;
+ while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) {
+ if (!acx->thread || acx->thread == cx->thread)
+ continue;
+ GSN_CACHE_CLEAR(&acx->thread->gsnCache);
+ js_FlushPropertyCache(acx);
+#ifdef JS_TRACER
+ js_FlushJITOracle(acx);
+#endif
+ DestroyScriptsToGC(cx, &acx->thread->scriptsToGC);
+ }
+#else
+ /* The thread-unsafe case just has to clear the runtime's GSN cache. */
+ GSN_CACHE_CLEAR(&rt->gsnCache);
+#endif
+
+ restart:
+ rt->gcNumber++;
+ JS_ASSERT(!rt->gcUntracedArenaStackTop);
+ JS_ASSERT(rt->gcTraceLaterCount == 0);
+
+ /* Reset the property cache's type id generator so we can compress ids. */
+ rt->shapeGen = 0;
+
+ /*
+ * Mark phase.
+ */
+ JS_TRACER_INIT(&trc, cx, NULL);
+ rt->gcMarkingTracer = &trc;
+ JS_ASSERT(IS_GC_MARKING_TRACER(&trc));
+
+ for (a = rt->gcDoubleArenaList.first; a; a = a->prev)
+ a->u.hasMarkedDoubles = JS_FALSE;
+
+ js_TraceRuntime(&trc, keepAtoms);
+ js_MarkScriptFilenames(rt, keepAtoms);
+
+ /*
+ * Mark children of things that caused too deep recursion during the above
+ * tracing.
+ */
+ TraceDelayedChildren(&trc);
+
+ JS_ASSERT(!cx->insideGCMarkCallback);
+ if (rt->gcCallback) {
+ cx->insideGCMarkCallback = JS_TRUE;
+ (void) rt->gcCallback(cx, JSGC_MARK_END);
+ JS_ASSERT(cx->insideGCMarkCallback);
+ cx->insideGCMarkCallback = JS_FALSE;
+ }
+ JS_ASSERT(rt->gcTraceLaterCount == 0);
+
+ rt->gcMarkingTracer = NULL;
+
+ /*
+ * Sweep phase.
+ *
+ * Finalize as we sweep, outside of rt->gcLock but with rt->gcRunning set
+ * so that any attempt to allocate a GC-thing from a finalizer will fail,
+ * rather than nest badly and leave the unmarked newborn to be swept.
+ *
+ * We first sweep atom state so we can use js_IsAboutToBeFinalized on
+ * JSString or jsdouble held in a hashtable to check if the hashtable
+ * entry can be freed. Note that even after the entry is freed, JSObject
+ * finalizers can continue to access the corresponding jsdouble* and
+ * JSString* assuming that they are unique. This works since the
+ * atomization API must not be called during GC.
+ */
+ js_SweepAtomState(cx);
+
+ /* Finalize iterator states before the objects they iterate over. */
+ CloseNativeIterators(cx);
+
+ /* Finalize watch points associated with unreachable objects. */
+ js_SweepWatchPoints(cx);
+
+#ifdef DEBUG
+ /* Save the pre-sweep count of scope-mapped properties. */
+ rt->liveScopePropsPreSweep = rt->liveScopeProps;
+#endif
+
+ /*
+ * Here we need to ensure that JSObject instances are finalized before GC-
+ * allocated JSString and jsdouble instances so object's finalizer can
+ * access them even if they will be freed. For that we simply finalize the
+ * list containing JSObject first since the static assert at the beginning
+ * of the file guarantees that JSString and jsdouble instances are
+ * allocated from a different list.
+ */
+ emptyArenas = NULL;
+ for (i = 0; i < GC_NUM_FREELISTS; i++) {
+ arenaList = &rt->gcArenaList[i == 0
+ ? GC_FREELIST_INDEX(sizeof(JSObject))
+ : i == GC_FREELIST_INDEX(sizeof(JSObject))
+ ? 0
+ : i];
+ ap = &arenaList->last;
+ if (!(a = *ap))
+ continue;
+
+ JS_ASSERT(arenaList->lastCount > 0);
+ arenaList->freeList = NULL;
+ freeList = NULL;
+ thingSize = arenaList->thingSize;
+ indexLimit = THINGS_PER_ARENA(thingSize);
+ flagp = THING_FLAGP(a, arenaList->lastCount - 1);
+ METER((nlivearenas = 0, nkilledarenas = 0, nthings = 0));
+ for (;;) {
+ JS_ASSERT(a->prevUntracedPage == 0);
+ JS_ASSERT(a->u.untracedThings == 0);
+ allClear = JS_TRUE;
+ do {
+ flags = *flagp;
+ if (flags & (GCF_MARK | GCF_LOCK)) {
+ *flagp &= ~GCF_MARK;
+ allClear = JS_FALSE;
+ METER(nthings++);
+ } else {
+ thing = FLAGP_TO_THING(flagp, thingSize);
+ if (!(flags & GCF_FINAL)) {
+ /*
+ * Call the finalizer with GCF_FINAL ORed into flags.
+ */
+ *flagp = (uint8)(flags | GCF_FINAL);
+ type = flags & GCF_TYPEMASK;
+ switch (type) {
+ case GCX_OBJECT:
+ js_FinalizeObject(cx, (JSObject *) thing);
+ break;
+ case GCX_DOUBLE:
+ /* Do nothing. */
+ break;
+#if JS_HAS_XML_SUPPORT
+ case GCX_XML:
+ js_FinalizeXML(cx, (JSXML *) thing);
+ break;
+#endif
+ default:
+ JS_ASSERT(type == GCX_STRING ||
+ type - GCX_EXTERNAL_STRING <
+ GCX_NTYPES - GCX_EXTERNAL_STRING);
+ js_FinalizeStringRT(rt, (JSString *) thing,
+ (intN) (type -
+ GCX_EXTERNAL_STRING),
+ cx);
+ break;
+ }
+#ifdef DEBUG
+ memset(thing, JS_FREE_PATTERN, thingSize);
+#endif
+ }
+ thing->flagp = flagp;
+ thing->next = freeList;
+ freeList = thing;
+ }
+ } while (++flagp != THING_FLAGS_END(a));
+
+ if (allClear) {
+ /*
+ * Forget just assembled free list head for the arena and
+ * add the arena itself to the destroy list.
+ */
+ freeList = arenaList->freeList;
+ if (a == arenaList->last)
+ arenaList->lastCount = (uint16) indexLimit;
+ *ap = a->prev;
+ a->prev = emptyArenas;
+ emptyArenas = a;
+ METER(nkilledarenas++);
+ } else {
+ arenaList->freeList = freeList;
+ ap = &a->prev;
+ METER(nlivearenas++);
+ }
+ if (!(a = *ap))
+ break;
+ flagp = THING_FLAGP(a, indexLimit - 1);
+ }
+
+ /*
+ * We use arenaList - &rt->gcArenaList[0], not i, as the stat index
+ * due to the enumeration reorder at the beginning of the loop.
+ */
+ METER(UpdateArenaStats(&rt->gcStats.arenaStats[arenaList -
+ &rt->gcArenaList[0]],
+ nlivearenas, nkilledarenas, nthings));
+ }
+
+#ifdef JS_THREADSAFE
+ /*
+ * Release all but two free list sets to avoid allocating a new set in
+ * js_NewGCThing.
+ */
+ TrimGCFreeListsPool(rt, 2);
+#endif
+
+ ap = &rt->gcDoubleArenaList.first;
+ METER((nlivearenas = 0, nkilledarenas = 0, nthings = 0));
+ while ((a = *ap) != NULL) {
+ if (!a->u.hasMarkedDoubles) {
+ /* No marked double values in the arena. */
+ *ap = a->prev;
+ a->prev = emptyArenas;
+ emptyArenas = a;
+ METER(nkilledarenas++);
+ } else {
+ ap = &a->prev;
+#ifdef JS_GCMETER
+ for (i = 0; i != DOUBLES_PER_ARENA; ++i) {
+ if (IsMarkedDouble(a, index))
+ METER(nthings++);
+ }
+ METER(nlivearenas++);
+#endif
+ }
+ }
+ METER(UpdateArenaStats(&rt->gcStats.doubleArenaStats,
+ nlivearenas, nkilledarenas, nthings));
+ rt->gcDoubleArenaList.nextDoubleFlags =
+ rt->gcDoubleArenaList.first
+ ? DOUBLE_ARENA_BITMAP(rt->gcDoubleArenaList.first)
+ : DOUBLE_BITMAP_SENTINEL;
+
+ /*
+ * Sweep the runtime's property tree after finalizing objects, in case any
+ * had watchpoints referencing tree nodes.
+ */
+ js_SweepScopeProperties(cx);
+
+ /*
+ * Sweep script filenames after sweeping functions in the generic loop
+ * above. In this way when a scripted function's finalizer destroys the
+ * script and calls rt->destroyScriptHook, the hook can still access the
+ * script's filename. See bug 323267.
+ */
+ js_SweepScriptFilenames(rt);
+
+ /*
+ * Destroy arenas after we finished the sweeping sofinalizers can safely
+ * use js_IsAboutToBeFinalized().
+ */
+ DestroyGCArenas(rt, emptyArenas);
+
+ if (rt->gcCallback)
+ (void) rt->gcCallback(cx, JSGC_FINALIZE_END);
+#ifdef DEBUG_srcnotesize
+ { extern void DumpSrcNoteSizeHist();
+ DumpSrcNoteSizeHist();
+ printf("GC HEAP SIZE %lu\n", (unsigned long)rt->gcBytes);
+ }
+#endif
+
+#ifdef JS_SCOPE_DEPTH_METER
+ { static FILE *fp;
+ if (!fp)
+ fp = fopen("/tmp/scopedepth.stats", "w");
+
+ if (fp) {
+ JS_DumpBasicStats(&rt->protoLookupDepthStats, "proto-lookup depth", fp);
+ JS_DumpBasicStats(&rt->scopeSearchDepthStats, "scope-search depth", fp);
+ JS_DumpBasicStats(&rt->hostenvScopeDepthStats, "hostenv scope depth", fp);
+ JS_DumpBasicStats(&rt->lexicalScopeDepthStats, "lexical scope depth", fp);
+
+ putc('\n', fp);
+ fflush(fp);
+ }
+ }
+#endif /* JS_SCOPE_DEPTH_METER */
+
+#ifdef JS_DUMP_LOOP_STATS
+ { static FILE *lsfp;
+ if (!lsfp)
+ lsfp = fopen("/tmp/loopstats", "w");
+ if (lsfp) {
+ JS_DumpBasicStats(&rt->loopStats, "loops", lsfp);
+ fflush(lsfp);
+ }
+ }
+#endif /* JS_DUMP_LOOP_STATS */
+
+#ifdef JS_TRACER
+out:
+#endif
+ JS_LOCK_GC(rt);
+
+ /*
+ * We want to restart GC if js_GC was called recursively or if any of the
+ * finalizers called js_RemoveRoot or js_UnlockGCThingRT.
+ */
+ if (rt->gcLevel > 1 || rt->gcPoke) {
+ rt->gcLevel = 1;
+ rt->gcPoke = JS_FALSE;
+ JS_UNLOCK_GC(rt);
+ goto restart;
+ }
+
+ if (rt->shapeGen >= SHAPE_OVERFLOW_BIT - 1) {
+ /*
+ * FIXME bug 440834: The shape id space has overflowed. Currently we
+ * cope badly with this. Every call to js_GenerateShape does GC, and
+ * we never re-enable the property cache.
+ */
+ js_DisablePropertyCache(cx);
+#ifdef JS_THREADSAFE
+ iter = NULL;
+ while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) {
+ if (!acx->thread || acx->thread == cx->thread)
+ continue;
+ js_DisablePropertyCache(acx);
+ }
+#endif
+ }
+
+ rt->gcLastBytes = rt->gcBytes;
+ done_running:
+ rt->gcLevel = 0;
+ rt->gcRunning = JS_FALSE;
+
+#ifdef JS_THREADSAFE
+ /* If we were invoked during a request, pay back the temporary debit. */
+ if (requestDebit)
+ rt->requestCount += requestDebit;
+ rt->gcThread = NULL;
+ JS_NOTIFY_GC_DONE(rt);
+
+ /*
+ * Unlock unless we have GC_LOCK_HELD which requires locked GC on return.
+ */
+ if (!(gckind & GC_LOCK_HELD))
+ JS_UNLOCK_GC(rt);
+#endif
+
+ /*
+ * Execute JSGC_END callback outside the lock. Again, sample the callback
+ * pointer in case it changes, since we are outside of the GC vs. requests
+ * interlock mechanism here.
+ */
+ if (gckind != GC_SET_SLOT_REQUEST && (callback = rt->gcCallback)) {
+ JSWeakRoots savedWeakRoots;
+ JSTempValueRooter tvr;
+
+ if (gckind & GC_KEEP_ATOMS) {
+ /*
+ * We allow JSGC_END implementation to force a full GC or allocate
+ * new GC things. Thus we must protect the weak roots from garbage
+ * collection and overwrites.
+ */
+ savedWeakRoots = cx->weakRoots;
+ JS_PUSH_TEMP_ROOT_WEAK_COPY(cx, &savedWeakRoots, &tvr);
+ JS_KEEP_ATOMS(rt);
+ JS_UNLOCK_GC(rt);
+ }
+
+ (void) callback(cx, JSGC_END);
+
+ if (gckind & GC_KEEP_ATOMS) {
+ JS_LOCK_GC(rt);
+ JS_UNKEEP_ATOMS(rt);
+ JS_POP_TEMP_ROOT(cx, &tvr);
+ } else if (gckind == GC_LAST_CONTEXT && rt->gcPoke) {
+ /*
+ * On shutdown iterate until JSGC_END callback stops creating
+ * garbage.
+ */
+ goto restart_at_beginning;
+ }
+ }
+}
+
+void
+js_UpdateMallocCounter(JSContext *cx, size_t nbytes)
+{
+ uint32 *pbytes, bytes;
+
+#ifdef JS_THREADSAFE
+ pbytes = &cx->thread->gcMallocBytes;
+#else
+ pbytes = &cx->runtime->gcMallocBytes;
+#endif
+ bytes = *pbytes;
+ *pbytes = ((uint32)-1 - bytes <= nbytes) ? (uint32)-1 : bytes + nbytes;
+}