| 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- | |
| 2 | * | |
| 3 | * ***** BEGIN LICENSE BLOCK ***** | |
| 4 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |
| 5 | * | |
| 6 | * The contents of this file are subject to the Mozilla Public License Version | |
| 7 | * 1.1 (the "License"); you may not use this file except in compliance with | |
| 8 | * the License. You may obtain a copy of the License at | |
| 9 | * http://www.mozilla.org/MPL/ | |
| 10 | * | |
| 11 | * Software distributed under the License is distributed on an "AS IS" basis, | |
| 12 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
| 13 | * for the specific language governing rights and limitations under the | |
| 14 | * License. | |
| 15 | * | |
| 16 | * The Original Code is Mozilla Communicator client code, released | |
| 17 | * March 31, 1998. | |
| 18 | * | |
| 19 | * The Initial Developer of the Original Code is | |
| 20 | * Netscape Communications Corporation. | |
| 21 | * Portions created by the Initial Developer are Copyright (C) 1998 | |
| 22 | * the Initial Developer. All Rights Reserved. | |
| 23 | * | |
| 24 | * Contributor(s): | |
| 25 | * | |
| 26 | * Alternatively, the contents of this file may be used under the terms of | |
| 27 | * either of the GNU General Public License Version 2 or later (the "GPL"), | |
| 28 | * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | |
| 29 | * in which case the provisions of the GPL or the LGPL are applicable instead | |
| 30 | * of those above. If you wish to allow use of your version of this file only | |
| 31 | * under the terms of either the GPL or the LGPL, and not to allow others to | |
| 32 | * use your version of this file under the terms of the MPL, indicate your | |
| 33 | * decision by deleting the provisions above and replace them with the notice | |
| 34 | * and other provisions required by the GPL or the LGPL. If you do not delete | |
| 35 | * the provisions above, a recipient may use your version of this file under | |
| 36 | * the terms of any one of the MPL, the GPL or the LGPL. | |
| 37 | * | |
| 38 | * ***** END LICENSE BLOCK ***** */ | |
| 39 | ||
| 40 | /* | |
| 41 | * JS Mark-and-Sweep Garbage Collector. | |
| 42 | * | |
| 43 | * This GC allocates only fixed-sized things big enough to contain two words | |
| 44 | * (pointers) on any host architecture. It allocates from an arena pool (see | |
| 45 | * jsarena.h). It uses an ideally parallel array of flag bytes to hold the | |
| 46 | * mark bit, finalizer type index, etc. | |
| 47 | * | |
| 48 | * XXX swizzle page to freelist for better locality of reference | |
| 49 | */ | |
| 50 | #include "jsstddef.h" | |
| 51 | #include <stdlib.h> /* for free, called by JS_ARENA_DESTROY */ | |
| 52 | #include <string.h> /* for memset, called by jsarena.h macros if DEBUG */ | |
| 53 | #include "jstypes.h" | |
| 54 | #include "jsarena.h" /* Added by JSIFY */ | |
| 55 | #include "jsutil.h" /* Added by JSIFY */ | |
| 56 | #include "jshash.h" /* Added by JSIFY */ | |
| 57 | #include "jsapi.h" | |
| 58 | #include "jsatom.h" | |
| 59 | #include "jscntxt.h" | |
| 60 | #include "jsconfig.h" | |
| 61 | #include "jsdbgapi.h" | |
| 62 | #include "jsfun.h" | |
| 63 | #include "jsgc.h" | |
| 64 | #include "jsinterp.h" | |
| 65 | #include "jslock.h" | |
| 66 | #include "jsnum.h" | |
| 67 | #include "jsobj.h" | |
| 68 | #include "jsscope.h" | |
| 69 | #include "jsscript.h" | |
| 70 | #include "jsstr.h" | |
| 71 | ||
| 72 | #if JS_HAS_XML_SUPPORT | |
| 73 | #include "jsxml.h" | |
| 74 | #endif | |
| 75 | ||
| 76 | /* | |
| 77 | * GC arena sizing depends on amortizing arena overhead using a large number | |
| 78 | * of things per arena, and on the thing/flags ratio of 8:1 on most platforms. | |
| 79 | * | |
| 80 | * On 64-bit platforms, we would have half as many things per arena because | |
| 81 | * pointers are twice as big, so we double the bytes for things per arena. | |
| 82 | * This preserves the 1024 byte flags sub-arena size, which relates to the | |
| 83 | * GC_PAGE_SIZE (see below for why). | |
| 84 | */ | |
| 85 | #if JS_BYTES_PER_WORD == 8 | |
| 86 | # define GC_THINGS_SHIFT 14 /* 16KB for things on Alpha, etc. */ | |
| 87 | #else | |
| 88 | # define GC_THINGS_SHIFT 13 /* 8KB for things on most platforms */ | |
| 89 | #endif | |
| 90 | #define GC_THINGS_SIZE JS_BIT(GC_THINGS_SHIFT) | |
| 91 | #define GC_FLAGS_SIZE (GC_THINGS_SIZE / sizeof(JSGCThing)) | |
| 92 | #define GC_ARENA_SIZE (GC_THINGS_SIZE + GC_FLAGS_SIZE) | |
| 93 | ||
| 94 | /* | |
| 95 | * A GC arena contains one flag byte for each thing in its heap, and supports | |
| 96 | * O(1) lookup of a flag given its thing's address. | |
| 97 | * | |
| 98 | * To implement this, we take advantage of the thing/flags numerology: given | |
| 99 | * the 8K bytes worth of GC-things, there are 1K flag bytes. We mask a thing's | |
| 100 | * address with ~1023 to find a JSGCPageInfo record at the front of a mythical | |
| 101 | * "GC page" within the larger 8K thing arena. That JSGCPageInfo contains a | |
| 102 | * pointer to the 128 flag bytes corresponding to the things in the page, so we | |
| 103 | * index into this flags array using the thing's index within its page. | |
| 104 | * | |
| 105 | * To align thing pages on 1024-byte boundaries, we must allocate the 9KB of | |
| 106 | * flags+things arena payload, then find the first 0 mod 1024 boundary after | |
| 107 | * the first payload address. That's where things start, with a JSGCPageInfo | |
| 108 | * taking up the first thing-slot, as usual for 0 mod 1024 byte boundaries. | |
| 109 | * The effect of this alignment trick is to split the flags into at most 2 | |
| 110 | * discontiguous spans, one before the things and one after (if we're really | |
| 111 | * lucky, and the arena payload starts on a 0 mod 1024 byte boundary, no need | |
| 112 | * to split). | |
| 113 | * | |
| 114 | * The overhead of this scheme for most platforms is (16+8*(8+1))/(16+9K) or | |
| 115 | * .95% (assuming 16 byte JSArena header size, and 8 byte JSGCThing size). | |
| 116 | * | |
| 117 | * Here's some ASCII art showing an arena: | |
| 118 | * | |
| 119 | * split | |
| 120 | * | | |
| 121 | * V | |
| 122 | * +--+-------+-------+-------+-------+-------+-------+-------+-------+-----+ | |
| 123 | * |fB| tp0 | tp1 | tp2 | tp3 | tp4 | tp5 | tp6 | tp7 | fA | | |
| 124 | * +--+-------+-------+-------+-------+-------+-------+-------+-------+-----+ | |
| 125 | * ^ ^ | |
| 126 | * tI ---------+ | | |
| 127 | * tJ -------------------------------------------+ | |
| 128 | * | |
| 129 | * - fB are the "before split" flags, fA are the "after split" flags | |
| 130 | * - tp0-tp7 are the 8 thing pages | |
| 131 | * - thing tI points into tp1, whose flags are below the split, in fB | |
| 132 | * - thing tJ points into tp5, clearly above the split | |
| 133 | * | |
| 134 | * In general, one of the thing pages will have some of its things' flags on | |
| 135 | * the low side of the split, and the rest of its things' flags on the high | |
| 136 | * side. All the other pages have flags only below or only above. Therefore | |
| 137 | * we'll have to test something to decide whether the split divides flags in | |
| 138 | * a given thing's page. So we store the split pointer (the pointer to tp0) | |
| 139 | * in each JSGCPageInfo, along with the flags pointer for the 128 flag bytes | |
| 140 | * ideally starting, for tp0 things, at the beginning of the arena's payload | |
| 141 | * (at the start of fB). | |
| 142 | * | |
| 143 | * That is, each JSGCPageInfo's flags pointer is 128 bytes from the previous, | |
| 144 | * or at the start of the arena if there is no previous page in this arena. | |
| 145 | * Thus these ideal 128-byte flag pages run contiguously from the start of the | |
| 146 | * arena (right over the split!), and the JSGCPageInfo flags pointers contain | |
| 147 | * no discontinuities over the split created by the thing pages. So if, for a | |
| 148 | * given JSGCPageInfo *pi, we find that | |
| 149 | * | |
| 150 | * pi->flags + ((jsuword)thing % 1024) / sizeof(JSGCThing) >= pi->split | |
| 151 | * | |
| 152 | * then we must add GC_THINGS_SIZE to the nominal flags pointer to jump over | |
| 153 | * all the thing pages that split the flags into two discontiguous spans. | |
| 154 | * | |
| 155 | * (If we need to implement card-marking for an incremental GC write barrier, | |
| 156 | * we can use the low byte of the pi->split pointer as the card-mark, for an | |
| 157 | * extremely efficient write barrier: when mutating an object obj, just store | |
| 158 | * a 1 byte at (uint8 *) ((jsuword)obj & ~1023) for little-endian platforms. | |
| 159 | * When finding flags, we'll of course have to mask split with ~255, but it is | |
| 160 | * guaranteed to be 1024-byte aligned, so no information is lost by overlaying | |
| 161 | * the card-mark byte on split's low byte.) | |
| 162 | */ | |
| 163 | #define GC_PAGE_SHIFT 10 | |
| 164 | #define GC_PAGE_MASK ((jsuword) JS_BITMASK(GC_PAGE_SHIFT)) | |
| 165 | #define GC_PAGE_SIZE JS_BIT(GC_PAGE_SHIFT) | |
| 166 | ||
| 167 | typedef struct JSGCPageInfo { | |
| 168 | uint8 *split; | |
| 169 | uint8 *flags; | |
| 170 | } JSGCPageInfo; | |
| 171 | ||
| 172 | #define FIRST_THING_PAGE(a) (((a)->base + GC_FLAGS_SIZE) & ~GC_PAGE_MASK) | |
| 173 | ||
| 174 | /* | |
| 175 | * Given a jsuword page pointer p and a thing size n, return the address of | |
| 176 | * the first thing in p. We know that any n not a power of two packs from | |
| 177 | * the end of the page leaving at least enough room for one JSGCPageInfo, but | |
| 178 | * not for another thing, at the front of the page (JS_ASSERTs below insist | |
| 179 | * on this). | |
| 180 | * | |
| 181 | * This works because all allocations are a multiple of sizeof(JSGCThing) == | |
| 182 | * sizeof(JSGCPageInfo) in size. | |
| 183 | */ | |
| 184 | #define FIRST_THING(p,n) (((n) & ((n) - 1)) \ | |
| 185 | ? (p) + (uint32)(GC_PAGE_SIZE % (n)) \ | |
| 186 | : (p) + (n)) | |
| 187 | ||
| 188 | static JSGCThing * | |
| 189 | gc_new_arena(JSArenaPool *pool, size_t nbytes) | |
| 190 | 2494 | { |
| 191 | 2494 | uint8 *flagp, *split, *pagep, *limit; |
| 192 | 2494 | JSArena *a; |
| 193 | 2494 | jsuword p; |
| 194 | 2494 | JSGCThing *thing; |
| 195 | 2494 | JSGCPageInfo *pi; |
| 196 | ||
| 197 | /* Use JS_ArenaAllocate to grab another 9K-net-size hunk of space. */ | |
| 198 | 2494 | flagp = (uint8 *) JS_ArenaAllocate(pool, GC_ARENA_SIZE); |
| 199 | 2494 | if (!flagp) |
| 200 | 0 | return NULL; |
| 201 | 2494 | a = pool->current; |
| 202 | ||
| 203 | /* Reset a->avail to start at the flags split, aka the first thing page. */ | |
| 204 | 2494 | p = FIRST_THING_PAGE(a); |
| 205 | 2494 | split = pagep = (uint8 *) p; |
| 206 | 2494 | a->avail = FIRST_THING(p, nbytes); |
| 207 | 2494 | JS_ASSERT(a->avail >= p + sizeof(JSGCPageInfo)); |
| 208 | 2494 | thing = (JSGCThing *) a->avail; |
| 209 | JS_ArenaCountAllocation(pool, a->avail - p); | |
| 210 | 2494 | a->avail += nbytes; |
| 211 | ||
| 212 | /* Initialize the JSGCPageInfo records at the start of every thing page. */ | |
| 213 | 2494 | limit = pagep + GC_THINGS_SIZE; |
| 214 | 19952 | do { |
| 215 | 19952 | pi = (JSGCPageInfo *) pagep; |
| 216 | 19952 | pi->split = split; |
| 217 | 19952 | pi->flags = flagp; |
| 218 | 19952 | flagp += GC_PAGE_SIZE >> (GC_THINGS_SHIFT - GC_PAGE_SHIFT); |
| 219 | 19952 | pagep += GC_PAGE_SIZE; |
| 220 | 19952 | } while (pagep < limit); |
| 221 | 2494 | return thing; |
| 222 | } | |
| 223 | ||
| 224 | uint8 * | |
| 225 | js_GetGCThingFlags(void *thing) | |
| 226 | 1049446 | { |
| 227 | 1049446 | JSGCPageInfo *pi; |
| 228 | 1049446 | uint8 *flagp; |
| 229 | ||
| 230 | 1049446 | pi = (JSGCPageInfo *) ((jsuword)thing & ~GC_PAGE_MASK); |
| 231 | 1049446 | flagp = pi->flags + ((jsuword)thing & GC_PAGE_MASK) / sizeof(JSGCThing); |
| 232 | 1049446 | if (flagp >= pi->split) |
| 233 | 561095 | flagp += GC_THINGS_SIZE; |
| 234 | 1049446 | return flagp; |
| 235 | } | |
| 236 | ||
| 237 | JSBool | |
| 238 | js_IsAboutToBeFinalized(JSContext *cx, void *thing) | |
| 239 | 0 | { |
| 240 | 0 | uint8 flags = *js_GetGCThingFlags(thing); |
| 241 | ||
| 242 | 0 | return !(flags & (GCF_MARK | GCF_LOCK | GCF_FINAL)); |
| 243 | } | |
| 244 | ||
| 245 | typedef void (*GCFinalizeOp)(JSContext *cx, JSGCThing *thing); | |
| 246 | ||
| 247 | #ifndef DEBUG | |
| 248 | # define js_FinalizeDouble NULL | |
| 249 | #endif | |
| 250 | ||
| 251 | #if !JS_HAS_XML_SUPPORT | |
| 252 | # define js_FinalizeXMLNamespace NULL | |
| 253 | # define js_FinalizeXMLQName NULL | |
| 254 | # define js_FinalizeXML NULL | |
| 255 | #endif | |
| 256 | ||
| 257 | static GCFinalizeOp gc_finalizers[GCX_NTYPES] = { | |
| 258 | (GCFinalizeOp) js_FinalizeObject, /* GCX_OBJECT */ | |
| 259 | (GCFinalizeOp) js_FinalizeString, /* GCX_STRING */ | |
| 260 | (GCFinalizeOp) js_FinalizeDouble, /* GCX_DOUBLE */ | |
| 261 | (GCFinalizeOp) js_FinalizeString, /* GCX_MUTABLE_STRING */ | |
| 262 | NULL, /* GCX_PRIVATE */ | |
| 263 | (GCFinalizeOp) js_FinalizeXMLNamespace, /* GCX_NAMESPACE */ | |
| 264 | (GCFinalizeOp) js_FinalizeXMLQName, /* GCX_QNAME */ | |
| 265 | (GCFinalizeOp) js_FinalizeXML, /* GCX_XML */ | |
| 266 | NULL, /* GCX_EXTERNAL_STRING */ | |
| 267 | NULL, | |
| 268 | NULL, | |
| 269 | NULL, | |
| 270 | NULL, | |
| 271 | NULL, | |
| 272 | NULL, | |
| 273 | NULL | |
| 274 | }; | |
| 275 | ||
| 276 | #ifdef GC_MARK_DEBUG | |
| 277 | static const char newborn_external_string[] = "newborn external string"; | |
| 278 | ||
| 279 | static const char *gc_typenames[GCX_NTYPES] = { | |
| 280 | "newborn object", | |
| 281 | "newborn string", | |
| 282 | "newborn double", | |
| 283 | "newborn mutable string", | |
| 284 | "newborn private", | |
| 285 | "newborn Namespace", | |
| 286 | "newborn QName", | |
| 287 | "newborn XML", | |
| 288 | newborn_external_string, | |
| 289 | newborn_external_string, | |
| 290 | newborn_external_string, | |
| 291 | newborn_external_string, | |
| 292 | newborn_external_string, | |
| 293 | newborn_external_string, | |
| 294 | newborn_external_string, | |
| 295 | newborn_external_string | |
| 296 | }; | |
| 297 | #endif | |
| 298 | ||
| 299 | intN | |
| 300 | js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop, | |
| 301 | JSStringFinalizeOp newop) | |
| 302 | 0 | { |
| 303 | 0 | uintN i; |
| 304 | ||
| 305 | 0 | for (i = GCX_EXTERNAL_STRING; i < GCX_NTYPES; i++) { |
| 306 | 0 | if (gc_finalizers[i] == (GCFinalizeOp) oldop) { |
| 307 | 0 | gc_finalizers[i] = (GCFinalizeOp) newop; |
| 308 | 0 | return (intN) i; |
| 309 | } | |
| 310 | } | |
| 311 | 0 | return -1; |
| 312 | } | |
| 313 | ||
| 314 | #ifdef JS_GCMETER | |
| 315 | #define METER(x) x | |
| 316 | #else | |
| 317 | #define METER(x) /* nothing */ | |
| 318 | #endif | |
| 319 | ||
| 320 | /* Initial size of the gcRootsHash table (SWAG, small enough to amortize). */ | |
| 321 | #define GC_ROOTS_SIZE 256 | |
| 322 | #define GC_FINALIZE_LEN 1024 | |
| 323 | ||
| 324 | JSBool | |
| 325 | js_InitGC(JSRuntime *rt, uint32 maxbytes) | |
| 326 | 16 | { |
| 327 | 16 | uintN i; |
| 328 | ||
| 329 | 16 | JS_ASSERT(sizeof(JSGCThing) == sizeof(JSGCPageInfo)); |
| 330 | 16 | JS_ASSERT(sizeof(JSGCThing) >= sizeof(JSObject)); |
| 331 | 16 | JS_ASSERT(sizeof(JSGCThing) >= sizeof(JSString)); |
| 332 | 16 | JS_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble)); |
| 333 | 16 | JS_ASSERT(GC_FLAGS_SIZE >= GC_PAGE_SIZE); |
| 334 | 16 | JS_ASSERT(sizeof(JSStackHeader) >= 2 * sizeof(jsval)); |
| 335 | ||
| 336 | 176 | for (i = 0; i < GC_NUM_FREELISTS; i++) |
| 337 | 160 | JS_InitArenaPool(&rt->gcArenaPool[i], "gc-arena", GC_ARENA_SIZE, 1); |
| 338 | 16 | if (!JS_DHashTableInit(&rt->gcRootsHash, JS_DHashGetStubOps(), NULL, |
| 339 | sizeof(JSGCRootHashEntry), GC_ROOTS_SIZE)) { | |
| 340 | 0 | rt->gcRootsHash.ops = NULL; |
| 341 | 0 | return JS_FALSE; |
| 342 | } | |
| 343 | 16 | rt->gcLocksHash = NULL; /* create lazily */ |
| 344 | ||
| 345 | /* | |
| 346 | * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes | |
| 347 | * for default backward API compatibility. | |
| 348 | */ | |
| 349 | 16 | rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes; |
| 350 | 16 | return JS_TRUE; |
| 351 | } | |
| 352 | ||
| 353 | #ifdef JS_GCMETER | |
| 354 | JS_FRIEND_API(void) | |
| 355 | js_DumpGCStats(JSRuntime *rt, FILE *fp) | |
| 356 | { | |
| 357 | uintN i; | |
| 358 | ||
| 359 | fprintf(fp, "\nGC allocation statistics:\n"); | |
| 360 | ||
| 361 | #define UL(x) ((unsigned long)(x)) | |
| 362 | #define ULSTAT(x) UL(rt->gcStats.x) | |
| 363 | fprintf(fp, " public bytes allocated: %lu\n", UL(rt->gcBytes)); | |
| 364 | fprintf(fp, " private bytes allocated: %lu\n", UL(rt->gcPrivateBytes)); | |
| 365 | fprintf(fp, " alloc attempts: %lu\n", ULSTAT(alloc)); | |
| 366 | for (i = 0; i < GC_NUM_FREELISTS; i++) { | |
| 367 | fprintf(fp, " GC freelist %u length: %lu\n", | |
| 368 | i, ULSTAT(freelen[i])); | |
| 369 | fprintf(fp, " recycles via GC freelist %u: %lu\n", | |
| 370 | i, ULSTAT(recycle[i])); | |
| 371 | } | |
| 372 | fprintf(fp, "allocation retries after GC: %lu\n", ULSTAT(retry)); | |
| 373 | fprintf(fp, " allocation failures: %lu\n", ULSTAT(fail)); | |
| 374 | fprintf(fp, " things born locked: %lu\n", ULSTAT(lockborn)); | |
| 375 | fprintf(fp, " valid lock calls: %lu\n", ULSTAT(lock)); | |
| 376 | fprintf(fp, " valid unlock calls: %lu\n", ULSTAT(unlock)); | |
| 377 | fprintf(fp, " mark recursion depth: %lu\n", ULSTAT(depth)); | |
| 378 | fprintf(fp, " maximum mark recursion: %lu\n", ULSTAT(maxdepth)); | |
| 379 | fprintf(fp, " mark C recursion depth: %lu\n", ULSTAT(cdepth)); | |
| 380 | fprintf(fp, " maximum mark C recursion: %lu\n", ULSTAT(maxcdepth)); | |
| 381 | fprintf(fp, " mark C stack overflows: %lu\n", ULSTAT(dswmark)); | |
| 382 | fprintf(fp, " mark DSW recursion depth: %lu\n", ULSTAT(dswdepth)); | |
| 383 | fprintf(fp, " maximum mark DSW recursion: %lu\n", ULSTAT(maxdswdepth)); | |
| 384 | fprintf(fp, " mark DSW up-tree movement: %lu\n", ULSTAT(dswup)); | |
| 385 | fprintf(fp, "DSW up-tree obj->slot steps: %lu\n", ULSTAT(dswupstep)); | |
| 386 | fprintf(fp, " maximum GC nesting level: %lu\n", ULSTAT(maxlevel)); | |
| 387 | fprintf(fp, "potentially useful GC calls: %lu\n", ULSTAT(poke)); | |
| 388 | fprintf(fp, " useless GC calls: %lu\n", ULSTAT(nopoke)); | |
| 389 | fprintf(fp, " thing arenas freed so far: %lu\n", ULSTAT(afree)); | |
| 390 | fprintf(fp, " stack segments scanned: %lu\n", ULSTAT(stackseg)); | |
| 391 | fprintf(fp, "stack segment slots scanned: %lu\n", ULSTAT(segslots)); | |
| 392 | #undef UL | |
| 393 | #undef US | |
| 394 | ||
| 395 | #ifdef JS_ARENAMETER | |
| 396 | JS_DumpArenaStats(fp); | |
| 397 | #endif | |
| 398 | } | |
| 399 | #endif | |
| 400 | ||
| 401 | #ifdef DEBUG | |
| 402 | JS_STATIC_DLL_CALLBACK(JSDHashOperator) | |
| 403 | js_root_printer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 i, void *arg) | |
| 404 | { | |
| 405 | uint32 *leakedroots = (uint32 *)arg; | |
| 406 | JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; | |
| 407 | ||
| 408 | (*leakedroots)++; | |
| 409 | fprintf(stderr, | |
| 410 | "JS engine warning: leaking GC root \'%s\' at %p\n", | |
| 411 | rhe->name ? (char *)rhe->name : "", rhe->root); | |
| 412 | ||
| 413 | return JS_DHASH_NEXT; | |
| 414 | } | |
| 415 | #endif | |
| 416 | ||
| 417 | void | |
| 418 | js_FinishGC(JSRuntime *rt) | |
| 419 | 16 | { |
| 420 | 16 | uintN i; |
| 421 | ||
| 422 | #ifdef JS_ARENAMETER | |
| 423 | JS_DumpArenaStats(stdout); | |
| 424 | #endif | |
| 425 | #ifdef JS_GCMETER | |
| 426 | js_DumpGCStats(rt, stdout); | |
| 427 | #endif | |
| 428 | 176 | for (i = 0; i < GC_NUM_FREELISTS; i++) { |
| 429 | 160 | JS_FinishArenaPool(&rt->gcArenaPool[i]); |
| 430 | 160 | rt->gcFreeList[i] = NULL; |
| 431 | } | |
| 432 | 16 | JS_ArenaFinish(); |
| 433 | ||
| 434 | 16 | if (rt->gcRootsHash.ops) { |
| 435 | #ifdef DEBUG | |
| 436 | uint32 leakedroots = 0; | |
| 437 | ||
| 438 | /* Warn (but don't assert) debug builds of any remaining roots. */ | |
| 439 | JS_DHashTableEnumerate(&rt->gcRootsHash, js_root_printer, | |
| 440 | &leakedroots); | |
| 441 | if (leakedroots > 0) { | |
| 442 | if (leakedroots == 1) { | |
| 443 | fprintf(stderr, | |
| 444 | "JS engine warning: 1 GC root remains after destroying the JSRuntime.\n" | |
| 445 | " This root may point to freed memory. Objects reachable\n" | |
| 446 | " through it have not been finalized.\n"); | |
| 447 | } else { | |
| 448 | fprintf(stderr, | |
| 449 | "JS engine warning: %lu GC roots remain after destroying the JSRuntime.\n" | |
| 450 | " These roots may point to freed memory. Objects reachable\n" | |
| 451 | " through them have not been finalized.\n", | |
| 452 | (unsigned long) leakedroots); | |
| 453 | } | |
| 454 | } | |
| 455 | #endif | |
| 456 | ||
| 457 | 16 | JS_DHashTableFinish(&rt->gcRootsHash); |
| 458 | 16 | rt->gcRootsHash.ops = NULL; |
| 459 | } | |
| 460 | 16 | if (rt->gcLocksHash) { |
| 461 | 0 | JS_DHashTableDestroy(rt->gcLocksHash); |
| 462 | 0 | rt->gcLocksHash = NULL; |
| 463 | } | |
| 464 | } | |
| 465 | ||
| 466 | JSBool | |
| 467 | js_AddRoot(JSContext *cx, void *rp, const char *name) | |
| 468 | 22011 | { |
| 469 | 22011 | JSBool ok = js_AddRootRT(cx->runtime, rp, name); |
| 470 | 22011 | if (!ok) |
| 471 | 0 | JS_ReportOutOfMemory(cx); |
| 472 | 22011 | return ok; |
| 473 | } | |
| 474 | ||
| 475 | JSBool | |
| 476 | js_AddRootRT(JSRuntime *rt, void *rp, const char *name) | |
| 477 | 22011 | { |
| 478 | 22011 | JSBool ok; |
| 479 | 22011 | JSGCRootHashEntry *rhe; |
| 480 | ||
| 481 | /* | |
| 482 | * Due to the long-standing, but now removed, use of rt->gcLock across the | |
| 483 | * bulk of js_GC, API users have come to depend on JS_AddRoot etc. locking | |
| 484 | * properly with a racing GC, without calling JS_AddRoot from a request. | |
| 485 | * We have to preserve API compatibility here, now that we avoid holding | |
| 486 | * rt->gcLock across the mark phase (including the root hashtable mark). | |
| 487 | * | |
| 488 | * If the GC is running and we're called on another thread, wait for this | |
| 489 | * GC activation to finish. We can safely wait here (in the case where we | |
| 490 | * are called within a request on another thread's context) without fear | |
| 491 | * of deadlock because the GC doesn't set rt->gcRunning until after it has | |
| 492 | * waited for all active requests to end. | |
| 493 | */ | |
| 494 | 22011 | JS_LOCK_GC(rt); |
| 495 | #ifdef JS_THREADSAFE | |
| 496 | JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0); | |
| 497 | if (rt->gcRunning && rt->gcThread != js_CurrentThreadId()) { | |
| 498 | do { | |
| 499 | JS_AWAIT_GC_DONE(rt); | |
| 500 | } while (rt->gcLevel > 0); | |
| 501 | } | |
| 502 | #endif | |
| 503 | 22011 | rhe = (JSGCRootHashEntry *) JS_DHashTableOperate(&rt->gcRootsHash, rp, |
| 504 | JS_DHASH_ADD); | |
| 505 | 22011 | if (rhe) { |
| 506 | 22011 | rhe->root = rp; |
| 507 | 22011 | rhe->name = name; |
| 508 | 22011 | ok = JS_TRUE; |
| 509 | } else { | |
| 510 | 0 | ok = JS_FALSE; |
| 511 | } | |
| 512 | 22011 | JS_UNLOCK_GC(rt); |
| 513 | 22011 | return ok; |
| 514 | } | |
| 515 | ||
| 516 | JSBool | |
| 517 | js_RemoveRoot(JSRuntime *rt, void *rp) | |
| 518 | 22011 | { |
| 519 | /* | |
| 520 | * Due to the JS_RemoveRootRT API, we may be called outside of a request. | |
| 521 | * Same synchronization drill as above in js_AddRoot. | |
| 522 | */ | |
| 523 | 22011 | JS_LOCK_GC(rt); |
| 524 | #ifdef JS_THREADSAFE | |
| 525 | JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0); | |
| 526 | if (rt->gcRunning && rt->gcThread != js_CurrentThreadId()) { | |
| 527 | do { | |
| 528 | JS_AWAIT_GC_DONE(rt); | |
| 529 | } while (rt->gcLevel > 0); | |
| 530 | } | |
| 531 | #endif | |
| 532 | 22011 | (void) JS_DHashTableOperate(&rt->gcRootsHash, rp, JS_DHASH_REMOVE); |
| 533 | 22011 | rt->gcPoke = JS_TRUE; |
| 534 | 22011 | JS_UNLOCK_GC(rt); |
| 535 | 22011 | return JS_TRUE; |
| 536 | } | |
| 537 | ||
| 538 | #ifdef DEBUG_brendan | |
| 539 | #define NGCHIST 64 | |
| 540 | ||
| 541 | static struct GCHist { | |
| 542 | JSBool lastDitch; | |
| 543 | JSGCThing *freeList; | |
| 544 | } gchist[NGCHIST]; | |
| 545 | ||
| 546 | unsigned gchpos; | |
| 547 | #endif | |
| 548 | ||
| 549 | void * | |
| 550 | js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes) | |
| 551 | 782858 | { |
| 552 | 782858 | JSBool tried_gc; |
| 553 | 782858 | JSRuntime *rt; |
| 554 | 782858 | size_t nflags; |
| 555 | 782858 | uintN i; |
| 556 | 782858 | JSGCThing *thing, **flp; |
| 557 | 782858 | uint8 *flagp; |
| 558 | 782858 | JSLocalRootStack *lrs; |
| 559 | 782858 | uint32 *bytesptr; |
| 560 | ||
| 561 | 782858 | rt = cx->runtime; |
| 562 | 782858 | JS_LOCK_GC(rt); |
| 563 | 782858 | JS_ASSERT(!rt->gcRunning); |
| 564 | 782858 | if (rt->gcRunning) { |
| 565 | METER(rt->gcStats.finalfail++); | |
| 566 | 0 | JS_UNLOCK_GC(rt); |
| 567 | 0 | return NULL; |
| 568 | } | |
| 569 | ||
| 570 | #ifdef TOO_MUCH_GC | |
| 571 | #ifdef WAY_TOO_MUCH_GC | |
| 572 | rt->gcPoke = JS_TRUE; | |
| 573 | #endif | |
| 574 | js_GC(cx, GC_KEEP_ATOMS | GC_ALREADY_LOCKED); | |
| 575 | tried_gc = JS_TRUE; | |
| 576 | #else | |
| 577 | 782858 | tried_gc = JS_FALSE; |
| 578 | #endif | |
| 579 | ||
| 580 | METER(rt->gcStats.alloc++); | |
| 581 | 782858 | nbytes = JS_ROUNDUP(nbytes, sizeof(JSGCThing)); |
| 582 | 782858 | nflags = nbytes / sizeof(JSGCThing); |
| 583 | 782858 | i = GC_FREELIST_INDEX(nbytes); |
| 584 | 782858 | flp = &rt->gcFreeList[i]; |
| 585 | ||
| 586 | retry: | |
| 587 | 782858 | thing = *flp; |
| 588 | 782858 | if (thing) { |
| 589 | 0 | *flp = thing->next; |
| 590 | 0 | flagp = thing->flagp; |
| 591 | METER(rt->gcStats.freelen[i]--); | |
| 592 | METER(rt->gcStats.recycle[i]++); | |
| 593 | } else { | |
| 594 | 782858 | if (rt->gcBytes < rt->gcMaxBytes && |
| 595 | (tried_gc || rt->gcMallocBytes < rt->gcMaxMallocBytes)) | |
| 596 | { | |
| 597 | /* | |
| 598 | * Inline form of JS_ARENA_ALLOCATE adapted to truncate the current | |
| 599 | * arena's limit to a GC_PAGE_SIZE boundary, and to skip over every | |
| 600 | * GC_PAGE_SIZE-byte-aligned thing (which is actually not a thing, | |
| 601 | * it's a JSGCPageInfo record). | |
| 602 | */ | |
| 603 | 782858 | JSArenaPool *pool = &rt->gcArenaPool[i]; |
| 604 | 782858 | JSArena *a = pool->current; |
| 605 | 782858 | jsuword p = a->avail; |
| 606 | 782858 | jsuword q = p + nbytes; |
| 607 | ||
| 608 | 782858 | if (q > (a->limit & ~GC_PAGE_MASK)) { |
| 609 | 2494 | thing = gc_new_arena(pool, nbytes); |
| 610 | } else { | |
| 611 | 780364 | if ((p & GC_PAGE_MASK) == 0) { |
| 612 | /* Beware, p points to a JSGCPageInfo record! */ | |
| 613 | 16848 | p = FIRST_THING(p, nbytes); |
| 614 | 16848 | q = p + nbytes; |
| 615 | JS_ArenaCountAllocation(pool, p & GC_PAGE_MASK); | |
| 616 | } | |
| 617 | 780364 | a->avail = q; |
| 618 | 780364 | thing = (JSGCThing *)p; |
| 619 | } | |
| 620 | JS_ArenaCountAllocation(pool, nbytes); | |
| 621 | } | |
| 622 | ||
| 623 | /* | |
| 624 | * Consider doing a "last ditch" GC if thing couldn't be allocated. | |
| 625 | * | |
| 626 | * Keep rt->gcLock across the call into js_GC so we don't starve and | |
| 627 | * lose to racing threads who deplete the heap just after js_GC has | |
| 628 | * replenished it (or has synchronized with a racing GC that collected | |
| 629 | * a bunch of garbage). This unfair scheduling can happen on certain | |
| 630 | * operating systems. For the gory details, see Mozilla bug 162779 | |
| 631 | * (http://bugzilla.mozilla.org/show_bug.cgi?id=162779). | |
| 632 | */ | |
| 633 | 782858 | if (!thing) { |
| 634 | 0 | if (!tried_gc) { |
| 635 | 0 | rt->gcPoke = JS_TRUE; |
| 636 | 0 | js_GC(cx, GC_KEEP_ATOMS | GC_ALREADY_LOCKED); |
| 637 | 0 | tried_gc = JS_TRUE; |
| 638 | METER(rt->gcStats.retry++); | |
| 639 | 0 | goto retry; |
| 640 | } | |
| 641 | 782858 | goto fail; |
| 642 | } | |
| 643 | ||
| 644 | /* Find the flags pointer given thing's address. */ | |
| 645 | 782858 | flagp = js_GetGCThingFlags(thing); |
| 646 | } | |
| 647 | ||
| 648 | 782858 | lrs = cx->localRootStack; |
| 649 | 782858 | if (lrs) { |
| 650 | /* | |
| 651 | * If we're in a local root scope, don't set cx->newborn[type] at all, | |
| 652 | * to avoid entraining garbage from it for an unbounded amount of time | |
| 653 | * on this context. A caller will leave the local root scope and pop | |
| 654 | * this reference, allowing thing to be GC'd if it has no other refs. | |
| 655 | * See JS_EnterLocalRootScope and related APIs. | |
| 656 | */ | |
| 657 | 1136 | if (js_PushLocalRoot(cx, lrs, (jsval) thing) < 0) { |
| 658 | /* | |
| 659 | * When we fail for a thing allocated through the tail of | |
| 660 | * the last arena, thing's flag byte is not initialized. So | |
| 661 | * to prevent GC accessing the uninitialized flags during | |
| 662 | * the finalization, we always mark the thing as final. See | |
| 663 | * bug 337407. | |
| 664 | */ | |
| 665 | 0 | *flagp = GCF_FINAL; |
| 666 | 0 | goto fail; |
| 667 | } | |
| 668 | } else { | |
| 669 | /* | |
| 670 | * No local root scope, so we're stuck with the old, fragile model of | |
| 671 | * depending on a pigeon-hole newborn per type per context. | |
| 672 | */ | |
| 673 | 781722 | cx->newborn[flags & GCF_TYPEMASK] = thing; |
| 674 | } | |
| 675 | ||
| 676 | /* We can't fail now, so update flags and rt->gc{,Private}Bytes. */ | |
| 677 | 782858 | *flagp = (uint8)flags; |
| 678 | 782858 | bytesptr = ((flags & GCF_TYPEMASK) == GCX_PRIVATE) |
| 679 | ? &rt->gcPrivateBytes | |
| 680 | : &rt->gcBytes; | |
| 681 | 782858 | *bytesptr += nbytes + nflags; |
| 682 | ||
| 683 | /* | |
| 684 | * Clear thing before unlocking in case a GC run is about to scan it, | |
| 685 | * finding it via cx->newborn[]. | |
| 686 | */ | |
| 687 | 782858 | thing->next = NULL; |
| 688 | 782858 | thing->flagp = NULL; |
| 689 | #ifdef DEBUG_brendan | |
| 690 | gchist[gchpos].lastDitch = tried_gc; | |
| 691 | gchist[gchpos].freeList = *flp; | |
| 692 | if (++gchpos == NGCHIST) | |
| 693 | gchpos = 0; | |
| 694 | #endif | |
| 695 | METER(if (flags & GCF_LOCK) rt->gcStats.lockborn++); | |
| 696 | 782858 | JS_UNLOCK_GC(rt); |
| 697 | 782858 | return thing; |
| 698 | ||
| 699 | fail: | |
| 700 | METER(rt->gcStats.fail++); | |
| 701 | 0 | JS_UNLOCK_GC(rt); |
| 702 | 0 | JS_ReportOutOfMemory(cx); |
| 703 | 0 | return NULL; |
| 704 | } | |
| 705 | ||
| 706 | JSBool | |
| 707 | js_LockGCThing(JSContext *cx, void *thing) | |
| 708 | 0 | { |
| 709 | 0 | JSBool ok = js_LockGCThingRT(cx->runtime, thing); |
| 710 | 0 | if (!ok) |
| 711 | 0 | JS_ReportOutOfMemory(cx); |
| 712 | 0 | return ok; |
| 713 | } | |
| 714 | ||
| 715 | /* | |
| 716 | * Deep GC-things can't be locked just by setting the GCF_LOCK bit, because | |
| 717 | * their descendants must be marked by the GC. To find them during the mark | |
| 718 | * phase, they are added to rt->gcLocksHash, which is created lazily. | |
| 719 | * | |
| 720 | * NB: we depend on the order of GC-thing type indexes here! | |
| 721 | */ | |
| 722 | #define GC_TYPE_IS_STRING(t) ((t) == GCX_STRING || \ | |
| 723 | (t) >= GCX_EXTERNAL_STRING) | |
| 724 | #define GC_TYPE_IS_XML(t) ((unsigned)((t) - GCX_NAMESPACE) <= \ | |
| 725 | (unsigned)(GCX_XML - GCX_NAMESPACE)) | |
| 726 | #define GC_TYPE_IS_DEEP(t) ((t) == GCX_OBJECT || GC_TYPE_IS_XML(t)) | |
| 727 | ||
| 728 | #define IS_DEEP_STRING(t,o) (GC_TYPE_IS_STRING(t) && \ | |
| 729 | JSSTRING_IS_DEPENDENT((JSString *)(o))) | |
| 730 | ||
| 731 | #define GC_THING_IS_DEEP(t,o) (GC_TYPE_IS_DEEP(t) || IS_DEEP_STRING(t, o)) | |
| 732 | ||
| 733 | JSBool | |
| 734 | js_LockGCThingRT(JSRuntime *rt, void *thing) | |
| 735 | 0 | { |
| 736 | 0 | JSBool ok, deep; |
| 737 | 0 | uint8 *flagp, flags, lock, type; |
| 738 | 0 | JSGCLockHashEntry *lhe; |
| 739 | ||
| 740 | 0 | ok = JS_TRUE; |
| 741 | 0 | if (!thing) |
| 742 | 0 | return ok; |
| 743 | ||
| 744 | 0 | flagp = js_GetGCThingFlags(thing); |
| 745 | ||
| 746 | 0 | JS_LOCK_GC(rt); |
| 747 | 0 | flags = *flagp; |
| 748 | 0 | lock = (flags & GCF_LOCK); |
| 749 | 0 | type = (flags & GCF_TYPEMASK); |
| 750 | 0 | deep = GC_THING_IS_DEEP(type, thing); |
| 751 | ||
| 752 | /* | |
| 753 | * Avoid adding a rt->gcLocksHash entry for shallow things until someone | |
| 754 | * nests a lock -- then start such an entry with a count of 2, not 1. | |
| 755 | */ | |
| 756 | 0 | if (lock || deep) { |
| 757 | 0 | if (!rt->gcLocksHash) { |
| 758 | 0 | rt->gcLocksHash = |
| 759 | JS_NewDHashTable(JS_DHashGetStubOps(), NULL, | |
| 760 | sizeof(JSGCLockHashEntry), | |
| 761 | GC_ROOTS_SIZE); | |
| 762 | 0 | if (!rt->gcLocksHash) { |
| 763 | 0 | ok = JS_FALSE; |
| 764 | 0 | goto done; |
| 765 | } | |
| 766 | 0 | } else if (lock == 0) { |
| 767 | #ifdef DEBUG | |
| 768 | JSDHashEntryHdr *hdr = | |
| 769 | JS_DHashTableOperate(rt->gcLocksHash, thing, | |
| 770 | JS_DHASH_LOOKUP); | |
| 771 | JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(hdr)); | |
| 772 | #endif | |
| 773 | } | |
| 774 | ||
| 775 | 0 | lhe = (JSGCLockHashEntry *) |
| 776 | JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_ADD); | |
| 777 | 0 | if (!lhe) { |
| 778 | 0 | ok = JS_FALSE; |
| 779 | 0 | goto done; |
| 780 | } | |
| 781 | 0 | if (!lhe->thing) { |
| 782 | 0 | lhe->thing = thing; |
| 783 | 0 | lhe->count = deep ? 1 : 2; |
| 784 | } else { | |
| 785 | 0 | JS_ASSERT(lhe->count >= 1); |
| 786 | 0 | lhe->count++; |
| 787 | } | |
| 788 | } | |
| 789 | ||
| 790 | 0 | *flagp = (uint8)(flags | GCF_LOCK); |
| 791 | METER(rt->gcStats.lock++); | |
| 792 | 0 | ok = JS_TRUE; |
| 793 | done: | |
| 794 | 0 | JS_UNLOCK_GC(rt); |
| 795 | 0 | return ok; |
| 796 | } | |
| 797 | ||
| 798 | JSBool | |
| 799 | js_UnlockGCThingRT(JSRuntime *rt, void *thing) | |
| 800 | 64 | { |
| 801 | 64 | uint8 *flagp, flags; |
| 802 | 64 | JSGCLockHashEntry *lhe; |
| 803 | ||
| 804 | 64 | if (!thing) |
| 805 | 0 | return JS_TRUE; |
| 806 | ||
| 807 | 64 | flagp = js_GetGCThingFlags(thing); |
| 808 | 64 | JS_LOCK_GC(rt); |
| 809 | 64 | flags = *flagp; |
| 810 | ||
| 811 | 64 | if (flags & GCF_LOCK) { |
| 812 | 64 | if (!rt->gcLocksHash || |
| 813 | (lhe = (JSGCLockHashEntry *) | |
| 814 | JS_DHashTableOperate(rt->gcLocksHash, thing, | |
| 815 | JS_DHASH_LOOKUP), | |
| 816 | JS_DHASH_ENTRY_IS_FREE(&lhe->hdr))) { | |
| 817 | /* Shallow GC-thing with an implicit lock count of 1. */ | |
| 818 | 0 | JS_ASSERT(!GC_THING_IS_DEEP(flags & GCF_TYPEMASK, thing)); |
| 819 | } else { | |
| 820 | /* Basis or nested unlock of a deep thing, or nested of shallow. */ | |
| 821 | 0 | if (--lhe->count != 0) |
| 822 | 0 | goto out; |
| 823 | 0 | JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_REMOVE); |
| 824 | } | |
| 825 | 64 | *flagp = (uint8)(flags & ~GCF_LOCK); |
| 826 | } | |
| 827 | ||
| 828 | 64 | rt->gcPoke = JS_TRUE; |
| 829 | out: | |
| 830 | METER(rt->gcStats.unlock++); | |
| 831 | 64 | JS_UNLOCK_GC(rt); |
| 832 | 64 | return JS_TRUE; |
| 833 | } | |
| 834 | ||
| 835 | #ifdef GC_MARK_DEBUG | |
| 836 | ||
| 837 | #include <stdio.h> | |
| 838 | #include "jsprf.h" | |
| 839 | ||
| 840 | JS_FRIEND_DATA(FILE *) js_DumpGCHeap; | |
| 841 | JS_EXPORT_DATA(void *) js_LiveThingToFind; | |
| 842 | ||
| 843 | #ifdef HAVE_XPCONNECT | |
| 844 | #include "dump_xpc.h" | |
| 845 | #endif | |
| 846 | ||
| 847 | static const char * | |
| 848 | gc_object_class_name(void* thing) | |
| 849 | { | |
| 850 | uint8 *flagp = js_GetGCThingFlags(thing); | |
| 851 | const char *className = ""; | |
| 852 | static char depbuf[32]; | |
| 853 | ||
| 854 | switch (*flagp & GCF_TYPEMASK) { | |
| 855 | case GCX_OBJECT: { | |
| 856 | JSObject *obj = (JSObject *)thing; | |
| 857 | JSClass *clasp = JSVAL_TO_PRIVATE(obj->slots[JSSLOT_CLASS]); | |
| 858 | className = clasp->name; | |
| 859 | #ifdef HAVE_XPCONNECT | |
| 860 | if (clasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { | |
| 861 | jsval privateValue = obj->slots[JSSLOT_PRIVATE]; | |
| 862 | ||
| 863 | JS_ASSERT(clasp->flags & JSCLASS_HAS_PRIVATE); | |
| 864 | if (!JSVAL_IS_VOID(privateValue)) { | |
| 865 | void *privateThing = JSVAL_TO_PRIVATE(privateValue); | |
| 866 | const char *xpcClassName = GetXPCObjectClassName(privateThing); | |
| 867 | ||
| 868 | if (xpcClassName) | |
| 869 | className = xpcClassName; | |
| 870 | } | |
| 871 | } | |
| 872 | #endif | |
| 873 | break; | |
| 874 | } | |
| 875 | ||
| 876 | case GCX_STRING: | |
| 877 | case GCX_MUTABLE_STRING: { | |
| 878 | JSString *str = (JSString *)thing; | |
| 879 | if (JSSTRING_IS_DEPENDENT(str)) { | |
| 880 | JS_snprintf(depbuf, sizeof depbuf, "start:%u, length:%u", | |
| 881 | JSSTRDEP_START(str), JSSTRDEP_LENGTH(str)); | |
| 882 | className = depbuf; | |
| 883 | } else { | |
| 884 | className = "string"; | |
| 885 | } | |
| 886 | break; | |
| 887 | } | |
| 888 | ||
| 889 | case GCX_DOUBLE: | |
| 890 | className = "double"; | |
| 891 | break; | |
| 892 | } | |
| 893 | ||
| 894 | return className; | |
| 895 | } | |
| 896 | ||
| 897 | static void | |
| 898 | gc_dump_thing(JSGCThing *thing, uint8 flags, GCMarkNode *prev, FILE *fp) | |
| 899 | { | |
| 900 | GCMarkNode *next = NULL; | |
| 901 | char *path = NULL; | |
| 902 | ||
| 903 | while (prev) { | |
| 904 | next = prev; | |
| 905 | prev = prev->prev; | |
| 906 | } | |
| 907 | while (next) { | |
| 908 | uint8 nextFlags = *js_GetGCThingFlags(next->thing); | |
| 909 | if ((nextFlags & GCF_TYPEMASK) == GCX_OBJECT) { | |
| 910 | path = JS_sprintf_append(path, "%s(%s @ 0x%08p).", | |
| 911 | next->name, | |
| 912 | gc_object_class_name(next->thing), | |
| 913 | (JSObject*)next->thing); | |
| 914 | } else { | |
| 915 | path = JS_sprintf_append(path, "%s(%s).", | |
| 916 | next->name, | |
| 917 | gc_object_class_name(next->thing)); | |
| 918 | } | |
| 919 | next = next->next; | |
| 920 | } | |
| 921 | if (!path) | |
| 922 | return; | |
| 923 | ||
| 924 | fprintf(fp, "%08lx ", (long)thing); | |
| 925 | switch (flags & GCF_TYPEMASK) { | |
| 926 | case GCX_OBJECT: | |
| 927 | { | |
| 928 | JSObject *obj = (JSObject *)thing; | |
| 929 | jsval privateValue = obj->slots[JSSLOT_PRIVATE]; | |
| 930 | void *privateThing = JSVAL_IS_VOID(privateValue) | |
| 931 | ? NULL | |
| 932 | : JSVAL_TO_PRIVATE(privateValue); | |
| 933 | const char *className = gc_object_class_name(thing); | |
| 934 | fprintf(fp, "object %8p %s", privateThing, className); | |
| 935 | break; | |
| 936 | } | |
| 937 | #if JS_HAS_XML_SUPPORT | |
| 938 | case GCX_NAMESPACE: | |
| 939 | { | |
| 940 | JSXMLNamespace *ns = (JSXMLNamespace *)thing; | |
| 941 | fprintf(fp, "namespace %s:%s", | |
| 942 | JS_GetStringBytes(ns->prefix), JS_GetStringBytes(ns->uri)); | |
| 943 | break; | |
| 944 | } | |
| 945 | case GCX_QNAME: | |
| 946 | { | |
| 947 | JSXMLQName *qn = (JSXMLQName *)thing; | |
| 948 | fprintf(fp, "qname %s(%s):%s", | |
| 949 | JS_GetStringBytes(qn->prefix), JS_GetStringBytes(qn->uri), | |
| 950 | JS_GetStringBytes(qn->localName)); | |
| 951 | break; | |
| 952 | } | |
| 953 | case GCX_XML: | |
| 954 | { | |
| 955 | extern const char *js_xml_class_str[]; | |
| 956 | JSXML *xml = (JSXML *)thing; | |
| 957 | fprintf(fp, "xml %8p %s", xml, js_xml_class_str[xml->xml_class]); | |
| 958 | break; | |
| 959 | } | |
| 960 | #endif | |
| 961 | case GCX_DOUBLE: | |
| 962 | fprintf(fp, "double %g", *(jsdouble *)thing); | |
| 963 | break; | |
| 964 | case GCX_PRIVATE: | |
| 965 | fprintf(fp, "private %8p", (void *)thing); | |
| 966 | break; | |
| 967 | default: | |
| 968 | fprintf(fp, "string %s", JS_GetStringBytes((JSString *)thing)); | |
| 969 | break; | |
| 970 | } | |
| 971 | fprintf(fp, " via %s\n", path); | |
| 972 | free(path); | |
| 973 | } | |
| 974 | ||
| 975 | #endif /* !GC_MARK_DEBUG */ | |
| 976 | ||
| 977 | static void | |
| 978 | gc_mark_atom_key_thing(void *thing, void *arg) | |
| 979 | 15664 | { |
| 980 | 15664 | JSContext *cx = (JSContext *) arg; |
| 981 | ||
| 982 | 15664 | GC_MARK(cx, thing, "atom", NULL); |
| 983 | } | |
| 984 | ||
| 985 | void | |
| 986 | js_MarkAtom(JSContext *cx, JSAtom *atom, void *arg) | |
| 987 | 24085 | { |
| 988 | 24085 | jsval key; |
| 989 | ||
| 990 | 24085 | if (atom->flags & ATOM_MARK) |
| 991 | 332 | return; |
| 992 | 23753 | atom->flags |= ATOM_MARK; |
| 993 | 23753 | key = ATOM_KEY(atom); |
| 994 | 23753 | if (JSVAL_IS_GCTHING(key)) { |
| 995 | #ifdef GC_MARK_DEBUG | |
| 996 | char name[32]; | |
| 997 | ||
| 998 | if (JSVAL_IS_STRING(key)) { | |
| 999 | JS_snprintf(name, sizeof name, "'%s'", | |
| 1000 | JS_GetStringBytes(JSVAL_TO_STRING(key))); | |
| 1001 | } else { | |
| 1002 | JS_snprintf(name, sizeof name, "<%x>", key); | |
| 1003 | } | |
| 1004 | #endif | |
| 1005 | 23737 | GC_MARK(cx, JSVAL_TO_GCTHING(key), name, arg); |
| 1006 | } | |
| 1007 | 23753 | if (atom->flags & ATOM_HIDDEN) |
| 1008 | 1344 | js_MarkAtom(cx, atom->entry.value, arg); |
| 1009 | } | |
| 1010 | ||
| 1011 | /* | |
| 1012 | * These macros help avoid passing the GC_MARK_DEBUG-only |arg| parameter | |
| 1013 | * during recursive calls when GC_MARK_DEBUG is not defined. | |
| 1014 | */ | |
| 1015 | #ifdef GC_MARK_DEBUG | |
| 1016 | # define UNMARKED_GC_THING_FLAGS(thing, arg) \ | |
| 1017 | UnmarkedGCThingFlags(thing, arg) | |
| 1018 | # define NEXT_UNMARKED_GC_THING(vp, end, thingp, flagpp, arg) \ | |
| 1019 | NextUnmarkedGCThing(vp, end, thingp, flagpp, arg) | |
| 1020 | # define MARK_GC_THING(cx, thing, flagp, arg) \ | |
| 1021 | MarkGCThing(cx, thing, flagp, arg) | |
| 1022 | # define CALL_GC_THING_MARKER(marker, cx, thing, arg) \ | |
| 1023 | marker(cx, thing, arg) | |
| 1024 | #else | |
| 1025 | # define UNMARKED_GC_THING_FLAGS(thing, arg) \ | |
| 1026 | UnmarkedGCThingFlags(thing) | |
| 1027 | # define NEXT_UNMARKED_GC_THING(vp, end, thingp, flagpp, arg) \ | |
| 1028 | NextUnmarkedGCThing(vp, end, thingp, flagpp) | |
| 1029 | # define MARK_GC_THING(cx, thing, flagp, arg) \ | |
| 1030 | MarkGCThing(cx, thing, flagp) | |
| 1031 | # define CALL_GC_THING_MARKER(marker, cx, thing, arg) \ | |
| 1032 | marker(cx, thing, NULL) | |
| 1033 | #endif | |
| 1034 | ||
| 1035 | static uint8 * | |
| 1036 | UNMARKED_GC_THING_FLAGS(void *thing, void *arg) | |
| 1037 | 151669 | { |
| 1038 | 151669 | uint8 flags, *flagp; |
| 1039 | ||
| 1040 | 151669 | if (!thing) |
| 1041 | 143 | return NULL; |
| 1042 | ||
| 1043 | 151526 | flagp = js_GetGCThingFlags(thing); |
| 1044 | 151526 | flags = *flagp; |
| 1045 | 151526 | JS_ASSERT(flags != GCF_FINAL); |
| 1046 | #ifdef GC_MARK_DEBUG | |
| 1047 | if (js_LiveThingToFind == thing) | |
| 1048 | gc_dump_thing(thing, flags, arg, stderr); | |
| 1049 | #endif | |
| 1050 | ||
| 1051 | 151526 | if (flags & GCF_MARK) |
| 1052 | 81495 | return NULL; |
| 1053 | ||
| 1054 | 70031 | return flagp; |
| 1055 | } | |
| 1056 | ||
| 1057 | static jsval * | |
| 1058 | NEXT_UNMARKED_GC_THING(jsval *vp, jsval *end, void **thingp, uint8 **flagpp, | |
| 1059 | void *arg) | |
| 1060 | 39335 | { |
| 1061 | 39335 | jsval v; |
| 1062 | 39335 | void *thing; |
| 1063 | 39335 | uint8 *flagp; |
| 1064 | ||
| 1065 | 237781 | while (vp < end) { |
| 1066 | 220366 | v = *vp; |
| 1067 | 220366 | if (JSVAL_IS_GCTHING(v)) { |
| 1068 | 71971 | thing = JSVAL_TO_GCTHING(v); |
| 1069 | 71971 | flagp = UNMARKED_GC_THING_FLAGS(thing, arg); |
| 1070 | 71971 | if (flagp) { |
| 1071 | 21920 | *thingp = thing; |
| 1072 | 21920 | *flagpp = flagp; |
| 1073 | 21920 | return vp; |
| 1074 | } | |
| 1075 | } | |
| 1076 | 198446 | vp++; |
| 1077 | } | |
| 1078 | 17415 | return NULL; |
| 1079 | } | |
| 1080 | ||
| 1081 | static void | |
| 1082 | DeutschSchorrWaite(JSContext *cx, void *thing, uint8 *flagp); | |
| 1083 | ||
| 1084 | static JSBool | |
| 1085 | MARK_GC_THING(JSContext *cx, void *thing, uint8 *flagp, void *arg) | |
| 1086 | 61896 | { |
| 1087 | 61896 | JSRuntime *rt; |
| 1088 | 61896 | JSObject *obj; |
| 1089 | 61896 | jsval v, *vp, *end; |
| 1090 | 61896 | JSString *str; |
| 1091 | 61896 | void *next_thing; |
| 1092 | 61896 | uint8 *next_flagp; |
| 1093 | #ifdef JS_GCMETER | |
| 1094 | uint32 tailCallNesting; | |
| 1095 | #endif | |
| 1096 | #ifdef GC_MARK_DEBUG | |
| 1097 | JSScope *scope; | |
| 1098 | JSScopeProperty *sprop; | |
| 1099 | char name[32]; | |
| 1100 | #endif | |
| 1101 | 61896 | int stackDummy; |
| 1102 | ||
| 1103 | 61896 | rt = cx->runtime; |
| 1104 | METER(tailCallNesting = 0); | |
| 1105 | METER(if (++rt->gcStats.cdepth > rt->gcStats.maxcdepth) | |
| 1106 | rt->gcStats.maxcdepth = rt->gcStats.cdepth); | |
| 1107 | ||
| 1108 | #ifndef GC_MARK_DEBUG | |
| 1109 | start: | |
| 1110 | #endif | |
| 1111 | 70013 | JS_ASSERT(flagp); |
| 1112 | METER(if (++rt->gcStats.depth > rt->gcStats.maxdepth) | |
| 1113 | rt->gcStats.maxdepth = rt->gcStats.depth); | |
| 1114 | 70013 | if (*flagp & GCF_MARK) { |
| 1115 | /* | |
| 1116 | * This should happen only if recursive MARK_GC_THING marks flags | |
| 1117 | * already stored in the caller's *next_flagp. | |
| 1118 | */ | |
| 1119 | 142 | goto out; |
| 1120 | } | |
| 1121 | ||
| 1122 | 69871 | *flagp |= GCF_MARK; |
| 1123 | ||
| 1124 | #ifdef GC_MARK_DEBUG | |
| 1125 | if (js_DumpGCHeap) | |
| 1126 | gc_dump_thing(thing, *flagp, arg, js_DumpGCHeap); | |
| 1127 | #endif | |
| 1128 | ||
| 1129 | 69871 | switch (*flagp & GCF_TYPEMASK) { |
| 1130 | case GCX_OBJECT: | |
| 1131 | /* If obj->slots is null, obj must be a newborn. */ | |
| 1132 | 17415 | obj = (JSObject *) thing; |
| 1133 | 17415 | vp = obj->slots; |
| 1134 | 17415 | if (!vp) |
| 1135 | 0 | goto out; |
| 1136 | ||
| 1137 | /* Switch to Deutsch-Schorr-Waite if we exhaust our stack quota. */ | |
| 1138 | 17415 | if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { |
| 1139 | METER(rt->gcStats.dswmark++); | |
| 1140 | 0 | DeutschSchorrWaite(cx, thing, flagp); |
| 1141 | 0 | goto out; |
| 1142 | } | |
| 1143 | ||
| 1144 | /* Mark slots if they are small enough to be GC-allocated. */ | |
| 1145 | 17415 | if ((vp[-1] + 1) * sizeof(jsval) <= GC_NBYTES_MAX) |
| 1146 | 11401 | GC_MARK(cx, vp - 1, "slots", arg); |
| 1147 | ||
| 1148 | /* Set up local variables to loop over unmarked things. */ | |
| 1149 | 17415 | end = vp + ((obj->map->ops->mark) |
| 1150 | ? CALL_GC_THING_MARKER(obj->map->ops->mark, cx, obj, arg) | |
| 1151 | : JS_MIN(obj->map->freeslot, obj->map->nslots)); | |
| 1152 | ||
| 1153 | 17415 | vp = NEXT_UNMARKED_GC_THING(vp, end, &thing, &flagp, arg); |
| 1154 | 17415 | if (!vp) |
| 1155 | 9301 | goto out; |
| 1156 | 8114 | v = *vp; |
| 1157 | ||
| 1158 | /* | |
| 1159 | * Here, thing is the first value in obj->slots referring to an | |
| 1160 | * unmarked GC-thing. | |
| 1161 | */ | |
| 1162 | #ifdef GC_MARK_DEBUG | |
| 1163 | scope = OBJ_IS_NATIVE(obj) ? OBJ_SCOPE(obj) : NULL; | |
| 1164 | #endif | |
| 1165 | 35708 | for (;;) { |
| 1166 | /* Check loop invariants. */ | |
| 1167 | 21920 | JS_ASSERT(v == *vp && JSVAL_IS_GCTHING(v)); |
| 1168 | 21920 | JS_ASSERT(thing == JSVAL_TO_GCTHING(v)); |
| 1169 | 21920 | JS_ASSERT(flagp == js_GetGCThingFlags(thing)); |
| 1170 | ||
| 1171 | #ifdef GC_MARK_DEBUG | |
| 1172 | if (scope) { | |
| 1173 | uint32 slot; | |
| 1174 | jsval nval; | |
| 1175 | ||
| 1176 | slot = vp - obj->slots; | |
| 1177 | for (sprop = SCOPE_LAST_PROP(scope); ; sprop = sprop->parent) { | |
| 1178 | if (!sprop) { | |
| 1179 | switch (slot) { | |
| 1180 | case JSSLOT_PROTO: | |
| 1181 | strcpy(name, js_proto_str); | |
| 1182 | break; | |
| 1183 | case JSSLOT_PARENT: | |
| 1184 | strcpy(name, js_parent_str); | |
| 1185 | break; | |
| 1186 | default: | |
| 1187 | JS_snprintf(name, sizeof name, | |
| 1188 | "**UNKNOWN SLOT %ld**", | |
| 1189 | (long)slot); | |
| 1190 | break; | |
| 1191 | } | |
| 1192 | break; | |
| 1193 | } | |
| 1194 | if (sprop->slot == slot) { | |
| 1195 | nval = ID_TO_VALUE(sprop->id); | |
| 1196 | if (JSVAL_IS_INT(nval)) { | |
| 1197 | JS_snprintf(name, sizeof name, "%ld", | |
| 1198 | (long)JSVAL_TO_INT(nval)); | |
| 1199 | } else if (JSVAL_IS_STRING(nval)) { | |
| 1200 | JS_snprintf(name, sizeof name, "%s", | |
| 1201 | JS_GetStringBytes(JSVAL_TO_STRING(nval))); | |
| 1202 | } else { | |
| 1203 | strcpy(name, "**FINALIZED ATOM KEY**"); | |
| 1204 | } | |
| 1205 | break; | |
| 1206 | } | |
| 1207 | } | |
| 1208 | } else { | |
| 1209 | strcpy(name, "**UNKNOWN OBJECT MAP ENTRY**"); | |
| 1210 | } | |
| 1211 | #endif | |
| 1212 | ||
| 1213 | 21920 | do { |
| 1214 | 21920 | vp = NEXT_UNMARKED_GC_THING(vp+1, end, &next_thing, &next_flagp, |
| 1215 | arg); | |
| 1216 | 21920 | if (!vp) { |
| 1217 | /* | |
| 1218 | * Here thing came from the last unmarked GC-thing slot. | |
| 1219 | * We can eliminate tail recursion unless GC_MARK_DEBUG | |
| 1220 | * is defined. | |
| 1221 | */ | |
| 1222 | #ifdef GC_MARK_DEBUG | |
| 1223 | GC_MARK(cx, thing, name, arg); | |
| 1224 | goto out; | |
| 1225 | #else | |
| 1226 | METER(++tailCallNesting); | |
| 1227 | 8114 | goto start; |
| 1228 | #endif | |
| 1229 | } | |
| 1230 | 13806 | } while (next_thing == thing); |
| 1231 | 13788 | v = *vp; |
| 1232 | ||
| 1233 | #ifdef GC_MARK_DEBUG | |
| 1234 | GC_MARK(cx, thing, name, arg); | |
| 1235 | #else | |
| 1236 | 13788 | MARK_GC_THING(cx, thing, flagp, arg); |
| 1237 | #endif | |
| 1238 | 13788 | thing = next_thing; |
| 1239 | 13788 | flagp = next_flagp; |
| 1240 | } | |
| 1241 | 449 | break; |
| 1242 | ||
| 1243 | #ifdef DEBUG | |
| 1244 | case GCX_STRING: | |
| 1245 | str = (JSString *)thing; | |
| 1246 | JS_ASSERT(!JSSTRING_IS_DEPENDENT(str)); | |
| 1247 | break; | |
| 1248 | #endif | |
| 1249 | ||
| 1250 | case GCX_MUTABLE_STRING: | |
| 1251 | 449 | str = (JSString *)thing; |
| 1252 | 449 | if (JSSTRING_IS_DEPENDENT(str)) { |
| 1253 | 37 | thing = JSSTRDEP_BASE(str); |
| 1254 | 37 | flagp = UNMARKED_GC_THING_FLAGS(thing, arg); |
| 1255 | 37 | if (flagp) { |
| 1256 | #ifdef GC_MARK_DEBUG | |
| 1257 | GC_MARK(cx, thing, "base", arg); | |
| 1258 | goto out; | |
| 1259 | #else | |
| 1260 | METER(++tailCallNesting); | |
| 1261 | 3 | goto start; |
| 1262 | #endif | |
| 1263 | } | |
| 1264 | } | |
| 1265 | 16 | break; |
| 1266 | ||
| 1267 | #if JS_HAS_XML_SUPPORT | |
| 1268 | case GCX_NAMESPACE: | |
| 1269 | 16 | CALL_GC_THING_MARKER(js_MarkXMLNamespace, cx, (JSXMLNamespace *)thing, |
| 1270 | arg); | |
| 1271 | 16 | break; |
| 1272 | ||
| 1273 | case GCX_QNAME: | |
| 1274 | 32 | CALL_GC_THING_MARKER(js_MarkXMLQName, cx, (JSXMLQName *)thing, arg); |
| 1275 | 32 | break; |
| 1276 | ||
| 1277 | case GCX_XML: | |
| 1278 | 16 | CALL_GC_THING_MARKER(js_MarkXML, cx, (JSXML *)thing, arg); |
| 1279 | break; | |
| 1280 | #endif | |
| 1281 | } | |
| 1282 | ||
| 1283 | out: | |
| 1284 | METER(rt->gcStats.depth -= 1 + tailCallNesting); | |
| 1285 | METER(rt->gcStats.cdepth--); | |
| 1286 | 61896 | return JS_TRUE; |
| 1287 | } | |
| 1288 | ||
| 1289 | /* | |
| 1290 | * An invalid object reference that's distinct from JSVAL_TRUE and JSVAL_FALSE | |
| 1291 | * when tagged as a boolean. Used to indicate empty DSW mark stack. | |
| 1292 | * | |
| 1293 | * Reversed pointers that link the DSW mark stack through obj->slots entries | |
| 1294 | * are also tagged as booleans so we can find each pointer and unreverse it. | |
| 1295 | * Because no object pointer is <= 16, these values can be distinguished from | |
| 1296 | * JSVAL_EMPTY, JSVAL_TRUE, and JSVAL_FALSE. | |
| 1297 | */ | |
| 1298 | #define JSVAL_EMPTY (2 << JSVAL_TAGBITS) | |
| 1299 | ||
| 1300 | /* | |
| 1301 | * To optimize native objects to avoid O(n^2) explosion in pathological cases, | |
| 1302 | * we use a dswIndex member of JSScope to tell where in obj->slots to find the | |
| 1303 | * reversed pointer. Scrounging space in JSScope by packing existing members | |
| 1304 | * tighter yielded 16 bits of index, which we use directly if obj->slots has | |
| 1305 | * 64K or fewer slots. Otherwise we make scope->dswIndex a fixed-point 16-bit | |
| 1306 | * fraction of the number of slots. | |
| 1307 | */ | |
| 1308 | static JS_INLINE uint16 | |
| 1309 | EncodeDSWIndex(jsval *vp, jsval *slots) | |
| 1310 | 0 | { |
| 1311 | 0 | uint32 nslots, limit, index; |
| 1312 | 0 | jsdouble d; |
| 1313 | ||
| 1314 | 0 | nslots = slots[-1]; |
| 1315 | 0 | limit = JS_BIT(16); |
| 1316 | 0 | index = PTRDIFF(vp, slots, jsval); |
| 1317 | 0 | JS_ASSERT(index < nslots); |
| 1318 | 0 | if (nslots > limit) { |
| 1319 | 0 | d = ((jsdouble)index / nslots) * limit; |
| 1320 | 0 | JS_ASSERT(0 <= d && d < limit); |
| 1321 | 0 | return (uint16) d; |
| 1322 | } | |
| 1323 | 0 | return (uint16) index; |
| 1324 | } | |
| 1325 | ||
| 1326 | static JS_INLINE uint32 | |
| 1327 | DecodeDSWIndex(uint16 dswIndex, jsval *slots) | |
| 1328 | 0 | { |
| 1329 | 0 | uint32 nslots, limit; |
| 1330 | 0 | jsdouble d; |
| 1331 | ||
| 1332 | 0 | nslots = slots[-1]; |
| 1333 | 0 | limit = JS_BIT(16); |
| 1334 | 0 | JS_ASSERT(dswIndex < nslots); |
| 1335 | 0 | if (nslots > limit) { |
| 1336 | 0 | d = ((jsdouble)dswIndex * nslots) / limit; |
| 1337 | 0 | JS_ASSERT(0 <= d && d < nslots); |
| 1338 | 0 | return (uint32) d; |
| 1339 | } | |
| 1340 | 0 | return dswIndex; |
| 1341 | } | |
| 1342 | ||
| 1343 | static void | |
| 1344 | DeutschSchorrWaite(JSContext *cx, void *thing, uint8 *flagp) | |
| 1345 | 0 | { |
| 1346 | 0 | jsval top, parent, v, *vp, *end; |
| 1347 | 0 | JSObject *obj; |
| 1348 | 0 | JSScope *scope; |
| 1349 | #ifdef JS_GCMETER | |
| 1350 | JSRuntime *rt = cx->runtime; | |
| 1351 | #endif | |
| 1352 | ||
| 1353 | 0 | top = JSVAL_EMPTY; |
| 1354 | ||
| 1355 | down: | |
| 1356 | METER(if (++rt->gcStats.dswdepth > rt->gcStats.maxdswdepth) | |
| 1357 | rt->gcStats.maxdswdepth = rt->gcStats.dswdepth); | |
| 1358 | 0 | obj = (JSObject *) thing; |
| 1359 | 0 | parent = OBJECT_TO_JSVAL(obj); |
| 1360 | ||
| 1361 | /* Precompute for quick testing to set and get scope->dswIndex. */ | |
| 1362 | 0 | scope = (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->object == obj) |
| 1363 | ? OBJ_SCOPE(obj) | |
| 1364 | : NULL; | |
| 1365 | ||
| 1366 | /* Mark slots if they are small enough to be GC-allocated. */ | |
| 1367 | 0 | vp = obj->slots; |
| 1368 | 0 | if ((vp[-1] + 1) * sizeof(jsval) <= GC_NBYTES_MAX) |
| 1369 | 0 | GC_MARK(cx, vp - 1, "slots", NULL); |
| 1370 | ||
| 1371 | 0 | end = vp + ((obj->map->ops->mark) |
| 1372 | ? obj->map->ops->mark(cx, obj, NULL) | |
| 1373 | : JS_MIN(obj->map->freeslot, obj->map->nslots)); | |
| 1374 | ||
| 1375 | 0 | *flagp |= GCF_MARK; |
| 1376 | ||
| 1377 | 0 | for (;;) { |
| 1378 | 0 | while ((vp = NEXT_UNMARKED_GC_THING(vp, end, &thing, &flagp, NULL)) |
| 1379 | != NULL) { | |
| 1380 | 0 | v = *vp; |
| 1381 | 0 | JS_ASSERT(JSVAL_TO_GCTHING(v) == thing); |
| 1382 | ||
| 1383 | 0 | if (JSVAL_IS_OBJECT(v)) { |
| 1384 | 0 | *vp = JSVAL_SETTAG(top, JSVAL_BOOLEAN); |
| 1385 | 0 | top = parent; |
| 1386 | 0 | if (scope) |
| 1387 | 0 | scope->dswIndex = EncodeDSWIndex(vp, obj->slots); |
| 1388 | 0 | goto down; |
| 1389 | } | |
| 1390 | ||
| 1391 | /* Handle string and double GC-things. */ | |
| 1392 | 0 | MARK_GC_THING(cx, thing, flagp, NULL); |
| 1393 | } | |
| 1394 | ||
| 1395 | /* If we are back at the root (or we never left it), we're done. */ | |
| 1396 | METER(rt->gcStats.dswdepth--); | |
| 1397 | 0 | if (scope) |
| 1398 | 0 | scope->dswIndex = 0; |
| 1399 | 0 | if (top == JSVAL_EMPTY) |
| 1400 | 0 | return; |
| 1401 | ||
| 1402 | /* Time to go back up the spanning tree. */ | |
| 1403 | METER(rt->gcStats.dswup++); | |
| 1404 | 0 | obj = JSVAL_TO_OBJECT(top); |
| 1405 | 0 | vp = obj->slots; |
| 1406 | 0 | end = vp + vp[-1]; |
| 1407 | ||
| 1408 | /* | |
| 1409 | * If obj is native and owns its own scope, we can minimize the cost | |
| 1410 | * of searching for the reversed pointer. | |
| 1411 | */ | |
| 1412 | 0 | scope = (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->object == obj) |
| 1413 | ? OBJ_SCOPE(obj) | |
| 1414 | : NULL; | |
| 1415 | 0 | if (scope) |
| 1416 | 0 | vp += DecodeDSWIndex(scope->dswIndex, vp); |
| 1417 | ||
| 1418 | /* | |
| 1419 | * Alas, we must search for the reversed pointer. If we used the | |
| 1420 | * scope->dswIndex hint, we'll step over a few slots for objects with | |
| 1421 | * a few times 64K slots, etc. For more typical (that is, far fewer | |
| 1422 | * than 64K slots) native objects that own their own scopes, this loop | |
| 1423 | * won't iterate at all. The order of complexity for host objects and | |
| 1424 | * unmutated native objects is O(n^2), but n (4 or 5 in most cases) is | |
| 1425 | * low enough that we don't care. | |
| 1426 | * | |
| 1427 | * We cannot use a reversed pointer into obj->slots, because there | |
| 1428 | * is no way to find an object from an address within its slots. | |
| 1429 | */ | |
| 1430 | 0 | v = *vp; |
| 1431 | 0 | while (v <= JSVAL_TRUE || !JSVAL_IS_BOOLEAN(v)) { |
| 1432 | METER(rt->gcStats.dswupstep++); | |
| 1433 | 0 | JS_ASSERT(vp + 1 < end); |
| 1434 | 0 | v = *++vp; |
| 1435 | } | |
| 1436 | ||
| 1437 | 0 | *vp++ = parent; |
| 1438 | 0 | parent = top; |
| 1439 | 0 | top = JSVAL_CLRTAG(v); |
| 1440 | } | |
| 1441 | } | |
| 1442 | ||
| 1443 | void | |
| 1444 | js_MarkGCThing(JSContext *cx, void *thing, void *arg) | |
| 1445 | 79661 | { |
| 1446 | 79661 | uint8 *flagp; |
| 1447 | ||
| 1448 | 79661 | flagp = UNMARKED_GC_THING_FLAGS(thing, arg); |
| 1449 | 79661 | if (!flagp) |
| 1450 | 31553 | return; |
| 1451 | 48108 | MARK_GC_THING(cx, thing, flagp, arg); |
| 1452 | } | |
| 1453 | ||
| 1454 | JS_STATIC_DLL_CALLBACK(JSDHashOperator) | |
| 1455 | gc_root_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg) | |
| 1456 | 21995 | { |
| 1457 | 21995 | JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; |
| 1458 | 21995 | jsval *rp = (jsval *)rhe->root; |
| 1459 | 21995 | jsval v = *rp; |
| 1460 | ||
| 1461 | /* Ignore null object and scalar values. */ | |
| 1462 | 21995 | if (!JSVAL_IS_NULL(v) && JSVAL_IS_GCTHING(v)) { |
| 1463 | 21995 | JSContext *cx = (JSContext *)arg; |
| 1464 | #ifdef DEBUG | |
| 1465 | uintN i; | |
| 1466 | JSArena *a; | |
| 1467 | jsuword firstpage; | |
| 1468 | JSBool root_points_to_gcArenaPool = JS_FALSE; | |
| 1469 | void *thing = JSVAL_TO_GCTHING(v); | |
| 1470 | ||
| 1471 | for (i = 0; i < GC_NUM_FREELISTS; i++) { | |
| 1472 | for (a = cx->runtime->gcArenaPool[i].first.next; a; a = a->next) { | |
| 1473 | firstpage = FIRST_THING_PAGE(a); | |
| 1474 | if (JS_UPTRDIFF(thing, firstpage) < a->avail - firstpage) { | |
| 1475 | root_points_to_gcArenaPool = JS_TRUE; | |
| 1476 | break; | |
| 1477 | } | |
| 1478 | } | |
| 1479 | } | |
| 1480 | if (!root_points_to_gcArenaPool && rhe->name) { | |
| 1481 | fprintf(stderr, | |
| 1482 | "JS API usage error: the address passed to JS_AddNamedRoot currently holds an\n" | |
| 1483 | "invalid jsval. This is usually caused by a missing call to JS_RemoveRoot.\n" | |
| 1484 | "The root's name is \"%s\".\n", | |
| 1485 | rhe->name); | |
| 1486 | } | |
| 1487 | JS_ASSERT(root_points_to_gcArenaPool); | |
| 1488 | #endif | |
| 1489 | ||
| 1490 | 21995 | GC_MARK(cx, JSVAL_TO_GCTHING(v), rhe->name ? rhe->name : "root", NULL); |
| 1491 | } | |
| 1492 | 21995 | return JS_DHASH_NEXT; |
| 1493 | } | |
| 1494 | ||
| 1495 | JS_STATIC_DLL_CALLBACK(JSDHashOperator) | |
| 1496 | gc_lock_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg) | |
| 1497 | 0 | { |
| 1498 | 0 | JSGCLockHashEntry *lhe = (JSGCLockHashEntry *)hdr; |
| 1499 | 0 | void *thing = (void *)lhe->thing; |
| 1500 | 0 | JSContext *cx = (JSContext *)arg; |
| 1501 | ||
| 1502 | 0 | GC_MARK(cx, thing, "locked object", NULL); |
| 1503 | 0 | return JS_DHASH_NEXT; |
| 1504 | } | |
| 1505 | ||
| 1506 | void | |
| 1507 | js_ForceGC(JSContext *cx, uintN gcflags) | |
| 1508 | 16 | { |
| 1509 | 16 | uintN i; |
| 1510 | ||
| 1511 | 272 | for (i = 0; i < GCX_NTYPES; i++) |
| 1512 | 256 | cx->newborn[i] = NULL; |
| 1513 | 16 | cx->lastAtom = NULL; |
| 1514 | 16 | cx->runtime->gcPoke = JS_TRUE; |
| 1515 | 16 | js_GC(cx, gcflags); |
| 1516 | 16 | JS_ArenaFinish(); |
| 1517 | } | |
| 1518 | ||
| 1519 | #define GC_MARK_JSVALS(cx, len, vec, name) \ | |
| 1520 | JS_BEGIN_MACRO \ | |
| 1521 | jsval _v, *_vp, *_end; \ | |
| 1522 | \ | |
| 1523 | for (_vp = vec, _end = _vp + len; _vp < _end; _vp++) { \ | |
| 1524 | _v = *_vp; \ | |
| 1525 | if (JSVAL_IS_GCTHING(_v)) \ | |
| 1526 | GC_MARK(cx, JSVAL_TO_GCTHING(_v), name, NULL); \ | |
| 1527 | } \ | |
| 1528 | JS_END_MACRO | |
| 1529 | ||
| 1530 | void | |
| 1531 | js_GC(JSContext *cx, uintN gcflags) | |
| 1532 | 16 | { |
| 1533 | 16 | JSRuntime *rt; |
| 1534 | 16 | JSContext *iter, *acx; |
| 1535 | 16 | JSStackFrame *fp, *chain; |
| 1536 | 16 | uintN i, depth, nslots, type; |
| 1537 | 16 | JSStackHeader *sh; |
| 1538 | 16 | JSTempValueRooter *tvr; |
| 1539 | 16 | size_t nbytes, nflags; |
| 1540 | 16 | JSArena *a, **ap; |
| 1541 | 16 | uint8 flags, *flagp, *split; |
| 1542 | 16 | JSGCThing *thing, *limit, **flp, **oflp; |
| 1543 | 16 | GCFinalizeOp finalizer; |
| 1544 | 16 | uint32 *bytesptr; |
| 1545 | 16 | JSBool all_clear; |
| 1546 | #ifdef JS_THREADSAFE | |
| 1547 | jsword currentThread; | |
| 1548 | uint32 requestDebit; | |
| 1549 | #endif | |
| 1550 | ||
| 1551 | 16 | rt = cx->runtime; |
| 1552 | #ifdef JS_THREADSAFE | |
| 1553 | /* Avoid deadlock. */ | |
| 1554 | JS_ASSERT(!JS_IS_RUNTIME_LOCKED(rt)); | |
| 1555 | #endif | |
| 1556 | ||
| 1557 | /* | |
| 1558 | * Don't collect garbage if the runtime isn't up, and cx is not the last | |
| 1559 | * context in the runtime. The last context must force a GC, and nothing | |
| 1560 | * should suppress that final collection or there may be shutdown leaks, | |
| 1561 | * or runtime bloat until the next context is created. | |
| 1562 | */ | |
| 1563 | 16 | if (rt->state != JSRTS_UP && !(gcflags & GC_LAST_CONTEXT)) |
| 1564 | 0 | return; |
| 1565 | ||
| 1566 | /* | |
| 1567 | * Let the API user decide to defer a GC if it wants to (unless this | |
| 1568 | * is the last context). Invoke the callback regardless. | |
| 1569 | */ | |
| 1570 | 16 | if (rt->gcCallback) { |
| 1571 | 0 | if (!rt->gcCallback(cx, JSGC_BEGIN) && !(gcflags & GC_LAST_CONTEXT)) |
| 1572 | 0 | return; |
| 1573 | } | |
| 1574 | ||
| 1575 | /* Lock out other GC allocator and collector invocations. */ | |
| 1576 | 16 | if (!(gcflags & GC_ALREADY_LOCKED)) |
| 1577 | 16 | JS_LOCK_GC(rt); |
| 1578 | ||
| 1579 | /* Do nothing if no mutator has executed since the last GC. */ | |
| 1580 | 16 | if (!rt->gcPoke) { |
| 1581 | METER(rt->gcStats.nopoke++); | |
| 1582 | 0 | if (!(gcflags & GC_ALREADY_LOCKED)) |
| 1583 | 0 | JS_UNLOCK_GC(rt); |
| 1584 | 0 | return; |
| 1585 | } | |
| 1586 | METER(rt->gcStats.poke++); | |
| 1587 | 16 | rt->gcPoke = JS_FALSE; |
| 1588 | ||
| 1589 | #ifdef JS_THREADSAFE | |
| 1590 | /* Bump gcLevel and return rather than nest on this thread. */ | |
| 1591 | currentThread = js_CurrentThreadId(); | |
| 1592 | if (rt->gcThread == currentThread) { | |
| 1593 | JS_ASSERT(rt->gcLevel > 0); | |
| 1594 | rt->gcLevel++; | |
| 1595 | METER(if (rt->gcLevel > rt->gcStats.maxlevel) | |
| 1596 | rt->gcStats.maxlevel = rt->gcLevel); | |
| 1597 | if (!(gcflags & GC_ALREADY_LOCKED)) | |
| 1598 | JS_UNLOCK_GC(rt); | |
| 1599 | return; | |
| 1600 | } | |
| 1601 | ||
| 1602 | /* | |
| 1603 | * If we're in one or more requests (possibly on more than one context) | |
| 1604 | * running on the current thread, indicate, temporarily, that all these | |
| 1605 | * requests are inactive. NB: if cx->thread is 0, then cx is not using | |
| 1606 | * the request model, and does not contribute to rt->requestCount. | |
| 1607 | */ | |
| 1608 | requestDebit = 0; | |
| 1609 | if (cx->thread) { | |
| 1610 | /* | |
| 1611 | * Check all contexts for any with the same thread-id. XXX should we | |
| 1612 | * keep a sub-list of contexts having the same id? | |
| 1613 | */ | |
| 1614 | iter = NULL; | |
| 1615 | while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) { | |
| 1616 | if (acx->thread == cx->thread && acx->requestDepth) | |
| 1617 | requestDebit++; | |
| 1618 | } | |
| 1619 | } else { | |
| 1620 | /* | |
| 1621 | * We assert, but check anyway, in case someone is misusing the API. | |
| 1622 | * Avoiding the loop over all of rt's contexts is a win in the event | |
| 1623 | * that the GC runs only on request-less contexts with 0 thread-ids, | |
| 1624 | * in a special thread such as might be used by the UI/DOM/Layout | |
| 1625 | * "mozilla" or "main" thread in Mozilla-the-browser. | |
| 1626 | */ | |
| 1627 | JS_ASSERT(cx->requestDepth == 0); | |
| 1628 | if (cx->requestDepth) | |
| 1629 | requestDebit = 1; | |
| 1630 | } | |
| 1631 | if (requestDebit) { | |
| 1632 | JS_ASSERT(requestDebit <= rt->requestCount); | |
| 1633 | rt->requestCount -= requestDebit; | |
| 1634 | if (rt->requestCount == 0) | |
| 1635 | JS_NOTIFY_REQUEST_DONE(rt); | |
| 1636 | } | |
| 1637 | ||
| 1638 | /* If another thread is already in GC, don't attempt GC; wait instead. */ | |
| 1639 | if (rt->gcLevel > 0) { | |
| 1640 | /* Bump gcLevel to restart the current GC, so it finds new garbage. */ | |
| 1641 | rt->gcLevel++; | |
| 1642 | METER(if (rt->gcLevel > rt->gcStats.maxlevel) | |
| 1643 | rt->gcStats.maxlevel = rt->gcLevel); | |
| 1644 | ||
| 1645 | /* Wait for the other thread to finish, then resume our request. */ | |
| 1646 | while (rt->gcLevel > 0) | |
| 1647 | JS_AWAIT_GC_DONE(rt); | |
| 1648 | if (requestDebit) | |
| 1649 | rt->requestCount += requestDebit; | |
| 1650 | if (!(gcflags & GC_ALREADY_LOCKED)) | |
| 1651 | JS_UNLOCK_GC(rt); | |
| 1652 | return; | |
| 1653 | } | |
| 1654 | ||
| 1655 | /* No other thread is in GC, so indicate that we're now in GC. */ | |
| 1656 | rt->gcLevel = 1; | |
| 1657 | rt->gcThread = currentThread; | |
| 1658 | ||
| 1659 | /* Wait for all other requests to finish. */ | |
| 1660 | while (rt->requestCount > 0) | |
| 1661 | JS_AWAIT_REQUEST_DONE(rt); | |
| 1662 | ||
| 1663 | #else /* !JS_THREADSAFE */ | |
| 1664 | ||
| 1665 | /* Bump gcLevel and return rather than nest; the outer gc will restart. */ | |
| 1666 | 16 | rt->gcLevel++; |
| 1667 | METER(if (rt->gcLevel > rt->gcStats.maxlevel) | |
| 1668 | rt->gcStats.maxlevel = rt->gcLevel); | |
| 1669 | 16 | if (rt->gcLevel > 1) |
| 1670 | 0 | return; |
| 1671 | ||
| 1672 | #endif /* !JS_THREADSAFE */ | |
| 1673 | ||
| 1674 | /* | |
| 1675 | * Set rt->gcRunning here within the GC lock, and after waiting for any | |
| 1676 | * active requests to end, so that new requests that try to JS_AddRoot, | |
| 1677 | * JS_RemoveRoot, or JS_RemoveRootRT block in JS_BeginRequest waiting for | |
| 1678 | * rt->gcLevel to drop to zero, while request-less calls to the *Root* | |
| 1679 | * APIs block in js_AddRoot or js_RemoveRoot (see above in this file), | |
| 1680 | * waiting for GC to finish. | |
| 1681 | */ | |
| 1682 | 16 | rt->gcRunning = JS_TRUE; |
| 1683 | 16 | JS_UNLOCK_GC(rt); |
| 1684 | ||
| 1685 | /* If a suspended compile is running on another context, keep atoms. */ | |
| 1686 | 16 | if (rt->gcKeepAtoms) |
| 1687 | 0 | gcflags |= GC_KEEP_ATOMS; |
| 1688 | ||
| 1689 | /* Reset malloc counter. */ | |
| 1690 | 16 | rt->gcMallocBytes = 0; |
| 1691 | ||
| 1692 | /* Drop atoms held by the property cache, and clear property weak links. */ | |
| 1693 | 16 | js_DisablePropertyCache(cx); |
| 1694 | 16 | js_FlushPropertyCache(cx); |
| 1695 | #ifdef DEBUG_notme | |
| 1696 | { extern void js_DumpScopeMeters(JSRuntime *rt); | |
| 1697 | js_DumpScopeMeters(rt); | |
| 1698 | } | |
| 1699 | #endif | |
| 1700 | ||
| 1701 | restart: | |
| 1702 | 32 | rt->gcNumber++; |
| 1703 | ||
| 1704 | /* | |
| 1705 | * Mark phase. | |
| 1706 | */ | |
| 1707 | 32 | JS_DHashTableEnumerate(&rt->gcRootsHash, gc_root_marker, cx); |
| 1708 | 32 | if (rt->gcLocksHash) |
| 1709 | 0 | JS_DHashTableEnumerate(rt->gcLocksHash, gc_lock_marker, cx); |
| 1710 | 32 | js_MarkAtomState(&rt->atomState, gcflags, gc_mark_atom_key_thing, cx); |
| 1711 | 32 | js_MarkWatchPoints(cx); |
| 1712 | 32 | js_MarkScriptFilenames(rt, gcflags); |
| 1713 | 32 | js_MarkNativeIteratorStates(cx); |
| 1714 | ||
| 1715 | 32 | iter = NULL; |
| 1716 | 32 | while ((acx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL) { |
| 1717 | /* | |
| 1718 | * Iterate frame chain and dormant chains. Temporarily tack current | |
| 1719 | * frame onto the head of the dormant list to ease iteration. | |
| 1720 | * | |
| 1721 | * (NB: see comment on this whole "dormant" thing in js_Execute.) | |
| 1722 | */ | |
| 1723 | 0 | chain = acx->fp; |
| 1724 | 0 | if (chain) { |
| 1725 | 0 | JS_ASSERT(!chain->dormantNext); |
| 1726 | 0 | chain->dormantNext = acx->dormantFrameChain; |
| 1727 | } else { | |
| 1728 | 0 | chain = acx->dormantFrameChain; |
| 1729 | } | |
| 1730 | ||
| 1731 | 0 | for (fp = chain; fp; fp = chain = chain->dormantNext) { |
| 1732 | 0 | do { |
| 1733 | 0 | if (fp->callobj) |
| 1734 | 0 | GC_MARK(cx, fp->callobj, "call object", NULL); |
| 1735 | 0 | if (fp->argsobj) |
| 1736 | 0 | GC_MARK(cx, fp->argsobj, "arguments object", NULL); |
| 1737 | 0 | if (fp->varobj) |
| 1738 | 0 | GC_MARK(cx, fp->varobj, "variables object", NULL); |
| 1739 | 0 | if (fp->script) { |
| 1740 | 0 | js_MarkScript(cx, fp->script, NULL); |
| 1741 | 0 | if (fp->spbase) { |
| 1742 | /* | |
| 1743 | * Don't mark what has not been pushed yet, or what | |
| 1744 | * has been popped already. | |
| 1745 | */ | |
| 1746 | 0 | depth = fp->script->depth; |
| 1747 | 0 | nslots = (JS_UPTRDIFF(fp->sp, fp->spbase) |
| 1748 | < depth * sizeof(jsval)) | |
| 1749 | ? (uintN)(fp->sp - fp->spbase) | |
| 1750 | : depth; | |
| 1751 | 0 | GC_MARK_JSVALS(cx, nslots, fp->spbase, "operand"); |
| 1752 | } | |
| 1753 | } | |
| 1754 | 0 | GC_MARK(cx, fp->thisp, "this", NULL); |
| 1755 | 0 | if (fp->argv) { |
| 1756 | 0 | nslots = fp->argc; |
| 1757 | 0 | if (fp->fun) { |
| 1758 | 0 | if (fp->fun->nargs > nslots) |
| 1759 | 0 | nslots = fp->fun->nargs; |
| 1760 | 0 | nslots += fp->fun->extra; |
| 1761 | } | |
| 1762 | 0 | GC_MARK_JSVALS(cx, nslots, fp->argv, "arg"); |
| 1763 | } | |
| 1764 | 0 | if (JSVAL_IS_GCTHING(fp->rval)) |
| 1765 | 0 | GC_MARK(cx, JSVAL_TO_GCTHING(fp->rval), "rval", NULL); |
| 1766 | 0 | if (fp->vars) |
| 1767 | 0 | GC_MARK_JSVALS(cx, fp->nvars, fp->vars, "var"); |
| 1768 | 0 | GC_MARK(cx, fp->scopeChain, "scope chain", NULL); |
| 1769 | 0 | if (fp->sharpArray) |
| 1770 | 0 | GC_MARK(cx, fp->sharpArray, "sharp array", NULL); |
| 1771 | ||
| 1772 | 0 | if (fp->xmlNamespace) |
| 1773 | 0 | GC_MARK(cx, fp->xmlNamespace, "xmlNamespace", NULL); |
| 1774 | 0 | } while ((fp = fp->down) != NULL); |
| 1775 | } | |
| 1776 | ||
| 1777 | /* Cleanup temporary "dormant" linkage. */ | |
| 1778 | 0 | if (acx->fp) |
| 1779 | 0 | acx->fp->dormantNext = NULL; |
| 1780 | ||
| 1781 | /* Mark other roots-by-definition in acx. */ | |
| 1782 | 0 | GC_MARK(cx, acx->globalObject, "global object", NULL); |
| 1783 | 0 | for (i = 0; i < GCX_NTYPES; i++) |
| 1784 | 0 | GC_MARK(cx, acx->newborn[i], gc_typenames[i], NULL); |
| 1785 | 0 | if (acx->lastAtom) |
| 1786 | 0 | GC_MARK_ATOM(cx, acx->lastAtom, NULL); |
| 1787 | 0 | if (JSVAL_IS_GCTHING(acx->lastInternalResult)) { |
| 1788 | 0 | thing = JSVAL_TO_GCTHING(acx->lastInternalResult); |
| 1789 | 0 | if (thing) |
| 1790 | 0 | GC_MARK(cx, thing, "lastInternalResult", NULL); |
| 1791 | } | |
| 1792 | #if JS_HAS_EXCEPTIONS | |
| 1793 | 0 | if (acx->throwing && JSVAL_IS_GCTHING(acx->exception)) |
| 1794 | 0 | GC_MARK(cx, JSVAL_TO_GCTHING(acx->exception), "exception", NULL); |
| 1795 | #endif | |
| 1796 | #if JS_HAS_LVALUE_RETURN | |
| 1797 | 0 | if (acx->rval2set && JSVAL_IS_GCTHING(acx->rval2)) |
| 1798 | 0 | GC_MARK(cx, JSVAL_TO_GCTHING(acx->rval2), "rval2", NULL); |
| 1799 | #endif | |
| 1800 | ||
| 1801 | 0 | for (sh = acx->stackHeaders; sh; sh = sh->down) { |
| 1802 | METER(rt->gcStats.stackseg++); | |
| 1803 | METER(rt->gcStats.segslots += sh->nslots); | |
| 1804 | 0 | GC_MARK_JSVALS(cx, sh->nslots, JS_STACK_SEGMENT(sh), "stack"); |
| 1805 | } | |
| 1806 | ||
| 1807 | 0 | if (acx->localRootStack) |
| 1808 | 0 | js_MarkLocalRoots(cx, acx->localRootStack); |
| 1809 | 0 | for (tvr = acx->tempValueRooters; tvr; tvr = tvr->down) { |
| 1810 | 0 | if (tvr->count == -1) { |
| 1811 | 0 | if (JSVAL_IS_GCTHING(tvr->u.value)) { |
| 1812 | 0 | GC_MARK(cx, JSVAL_TO_GCTHING(tvr->u.value), |
| 1813 | "tvr->u.value", NULL); | |
| 1814 | } | |
| 1815 | 0 | } else if (tvr->count == -2) { |
| 1816 | 0 | tvr->u.marker(cx, tvr); |
| 1817 | } else { | |
| 1818 | 0 | JS_ASSERT(tvr->count >= 0); |
| 1819 | 0 | GC_MARK_JSVALS(cx, tvr->count, tvr->u.array, "tvr->u.array"); |
| 1820 | } | |
| 1821 | } | |
| 1822 | ||
| 1823 | 0 | if (acx->sharpObjectMap.depth > 0) |
| 1824 | 0 | js_GCMarkSharpMap(cx, &acx->sharpObjectMap); |
| 1825 | } | |
| 1826 | #ifdef DUMP_CALL_TABLE | |
| 1827 | js_DumpCallTable(cx); | |
| 1828 | #endif | |
| 1829 | ||
| 1830 | 32 | if (rt->gcCallback) |
| 1831 | 0 | (void) rt->gcCallback(cx, JSGC_MARK_END); |
| 1832 | ||
| 1833 | /* | |
| 1834 | * Sweep phase. | |
| 1835 | * | |
| 1836 | * Finalize as we sweep, outside of rt->gcLock, but with rt->gcRunning set | |
| 1837 | * so that any attempt to allocate a GC-thing from a finalizer will fail, | |
| 1838 | * rather than nest badly and leave the unmarked newborn to be swept. | |
| 1839 | * | |
| 1840 | * Finalize smaller objects before larger, to guarantee finalization of | |
| 1841 | * GC-allocated obj->slots after obj. See FreeSlots in jsobj.c. | |
| 1842 | */ | |
| 1843 | 32 | js_SweepAtomState(&rt->atomState); |
| 1844 | 32 | js_SweepScopeProperties(rt); |
| 1845 | 352 | for (i = 0; i < GC_NUM_FREELISTS; i++) { |
| 1846 | 320 | nbytes = GC_FREELIST_NBYTES(i); |
| 1847 | 320 | nflags = nbytes / sizeof(JSGCThing); |
| 1848 | ||
| 1849 | 3910 | for (a = rt->gcArenaPool[i].first.next; a; a = a->next) { |
| 1850 | 3590 | flagp = (uint8 *) a->base; |
| 1851 | 3590 | split = (uint8 *) FIRST_THING_PAGE(a); |
| 1852 | 3590 | limit = (JSGCThing *) a->avail; |
| 1853 | 1283727 | for (thing = (JSGCThing *) split; thing < limit; thing += nflags) { |
| 1854 | 1280137 | if (((jsuword)thing & GC_PAGE_MASK) == 0) { |
| 1855 | 27621 | thing = (JSGCThing *) FIRST_THING((jsuword)thing, nbytes); |
| 1856 | 27621 | flagp = js_GetGCThingFlags(thing); |
| 1857 | } | |
| 1858 | 1280137 | flags = *flagp; |
| 1859 | 1280137 | if (flags & GCF_MARK) { |
| 1860 | 69871 | *flagp &= ~GCF_MARK; |
| 1861 | 1210266 | } else if (!(flags & (GCF_LOCK | GCF_FINAL))) { |
| 1862 | /* Call the finalizer with GCF_FINAL ORed into flags. */ | |
| 1863 | 775026 | type = flags & GCF_TYPEMASK; |
| 1864 | 775026 | finalizer = gc_finalizers[type]; |
| 1865 | 775026 | if (finalizer) { |
| 1866 | 343323 | *flagp = (uint8)(flags | GCF_FINAL); |
| 1867 | 343323 | if (type >= GCX_EXTERNAL_STRING) |
| 1868 | 0 | js_PurgeDeflatedStringCache((JSString *)thing); |
| 1869 | 343323 | finalizer(cx, thing); |
| 1870 | } | |
| 1871 | ||
| 1872 | /* Set flags to GCF_FINAL, signifying that thing is free. */ | |
| 1873 | 775026 | *flagp = GCF_FINAL; |
| 1874 | ||
| 1875 | 775026 | bytesptr = (type == GCX_PRIVATE) |
| 1876 | ? &rt->gcPrivateBytes | |
| 1877 | : &rt->gcBytes; | |
| 1878 | 775026 | JS_ASSERT(*bytesptr >= nbytes + nflags); |
| 1879 | 775026 | *bytesptr -= nbytes + nflags; |
| 1880 | } | |
| 1881 | 1280137 | flagp += nflags; |
| 1882 | 1280137 | if (JS_UPTRDIFF(flagp, split) < nflags) |
| 1883 | 3397 | flagp += GC_THINGS_SIZE; |
| 1884 | } | |
| 1885 | } | |
| 1886 | } | |
| 1887 | ||
| 1888 | /* | |
| 1889 | * Sweep script filenames after sweeping functions in the generic loop | |
| 1890 | * above. In this way when scripted function's finalizer destroys script | |
| 1891 | * triggering a call to rt->destroyScriptHook, the hook can still access | |
| 1892 | * script's filename. See bug 323267. | |
| 1893 | */ | |
| 1894 | 32 | js_SweepScriptFilenames(rt); |
| 1895 | ||
| 1896 | /* | |
| 1897 | * Free phase. | |
| 1898 | * Free any unused arenas and rebuild the JSGCThing freelist. | |
| 1899 | */ | |
| 1900 | 352 | for (i = 0; i < GC_NUM_FREELISTS; i++) { |
| 1901 | 320 | ap = &rt->gcArenaPool[i].first.next; |
| 1902 | 320 | a = *ap; |
| 1903 | 320 | if (!a) |
| 1904 | 68 | continue; |
| 1905 | ||
| 1906 | 252 | all_clear = JS_TRUE; |
| 1907 | 252 | flp = oflp = &rt->gcFreeList[i]; |
| 1908 | 252 | *flp = NULL; |
| 1909 | METER(rt->gcStats.freelen[i] = 0); | |
| 1910 | ||
| 1911 | 252 | nbytes = GC_FREELIST_NBYTES(i); |
| 1912 | 252 | nflags = nbytes / sizeof(JSGCThing); |
| 1913 | 3590 | do { |
| 1914 | 3590 | flagp = (uint8 *) a->base; |
| 1915 | 3590 | split = (uint8 *) FIRST_THING_PAGE(a); |
| 1916 | 3590 | limit = (JSGCThing *) a->avail; |
| 1917 | 1283727 | for (thing = (JSGCThing *) split; thing < limit; thing += nflags) { |
| 1918 | 1280137 | if (((jsuword)thing & GC_PAGE_MASK) == 0) { |
| 1919 | 27621 | thing = (JSGCThing *) FIRST_THING((jsuword)thing, nbytes); |
| 1920 | 27621 | flagp = js_GetGCThingFlags(thing); |
| 1921 | } | |
| 1922 | 1280137 | if (*flagp != GCF_FINAL) { |
| 1923 | 69871 | all_clear = JS_FALSE; |
| 1924 | } else { | |
| 1925 | 1210266 | thing->flagp = flagp; |
| 1926 | 1210266 | *flp = thing; |
| 1927 | 1210266 | flp = &thing->next; |
| 1928 | METER(rt->gcStats.freelen[i]++); | |
| 1929 | } | |
| 1930 | 1280137 | flagp += nflags; |
| 1931 | 1280137 | if (JS_UPTRDIFF(flagp, split) < nflags) |
| 1932 | 3397 | flagp += GC_THINGS_SIZE; |
| 1933 | } | |
| 1934 | ||
| 1935 | 3590 | if (all_clear) { |
| 1936 | 2264 | JS_ARENA_DESTROY(&rt->gcArenaPool[i], a, ap); |
| 1937 | 2264 | flp = oflp; |
| 1938 | METER(rt->gcStats.afree++); | |
| 1939 | } else { | |
| 1940 | 1326 | ap = &a->next; |
| 1941 | 1326 | all_clear = JS_TRUE; |
| 1942 | 1326 | oflp = flp; |
| 1943 | } | |
| 1944 | 3590 | } while ((a = *ap) != NULL); |
| 1945 | ||
| 1946 | /* Terminate the new freelist. */ | |
| 1947 | 252 | *flp = NULL; |
| 1948 | } | |
| 1949 | ||
| 1950 | 32 | if (rt->gcCallback) |
| 1951 | 0 | (void) rt->gcCallback(cx, JSGC_FINALIZE_END); |
| 1952 | #ifdef DEBUG_notme | |
| 1953 | { extern void DumpSrcNoteSizeHist(); | |
| 1954 | DumpSrcNoteSizeHist(); | |
| 1955 | printf("GC HEAP SIZE %lu (%lu)\n", | |
| 1956 | (unsigned long)rt->gcBytes, (unsigned long)rt->gcPrivateBytes); | |
| 1957 | } | |
| 1958 | #endif | |
| 1959 | ||
| 1960 | 32 | JS_LOCK_GC(rt); |
| 1961 | 32 | if (rt->gcLevel > 1 || rt->gcPoke) { |
| 1962 | 16 | rt->gcLevel = 1; |
| 1963 | 16 | rt->gcPoke = JS_FALSE; |
| 1964 | 16 | JS_UNLOCK_GC(rt); |
| 1965 | 16 | goto restart; |
| 1966 | } | |
| 1967 | 16 | js_EnablePropertyCache(cx); |
| 1968 | 16 | rt->gcLevel = 0; |
| 1969 | 16 | rt->gcLastBytes = rt->gcBytes; |
| 1970 | 16 | rt->gcRunning = JS_FALSE; |
| 1971 | ||
| 1972 | #ifdef JS_THREADSAFE | |
| 1973 | /* If we were invoked during a request, pay back the temporary debit. */ | |
| 1974 | if (requestDebit) | |
| 1975 | rt->requestCount += requestDebit; | |
| 1976 | rt->gcThread = 0; | |
| 1977 | JS_NOTIFY_GC_DONE(rt); | |
| 1978 | if (!(gcflags & GC_ALREADY_LOCKED)) | |
| 1979 | JS_UNLOCK_GC(rt); | |
| 1980 | #endif | |
| 1981 | ||
| 1982 | 16 | if (rt->gcCallback) { |
| 1983 | 0 | if (gcflags & GC_ALREADY_LOCKED) |
| 1984 | 0 | JS_UNLOCK_GC(rt); |
| 1985 | 0 | (void) rt->gcCallback(cx, JSGC_END); |
| 1986 | 0 | if (gcflags & GC_ALREADY_LOCKED) |
| 1987 | 16 | JS_LOCK_GC(rt); |
| 1988 | } |