bug 516832 - conservative stack scanning. This is based on the initial work by Anreas Gal and Gregor Wagner. r=brendan,gal
authorIgor Bukanov <igor@mir2.org>
Fri, 04 Jun 2010 16:22:28 +0200
changeset 47439 7ee77bc4bc8a39f4eec847a39ade34bbc70f100f
parent 47438 2e14a43ef3db669efe6d0ddee6417c6bc0a348e3
child 47440 5493dec0989bf3097d260276749836e72162494f
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbrendan, gal
bugs516832
milestone1.9.3a5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 516832 - conservative stack scanning. This is based on the initial work by Anreas Gal and Gregor Wagner. r=brendan,gal
dom/base/nsDOMClassInfo.cpp
js/src/jsapi-tests/Makefile.in
js/src/jsapi-tests/testConservativeGC.cpp
js/src/jsapi-tests/testIsAboutToBeFinalized.cpp
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsstr.cpp
js/src/jstypes.h
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/tests/unit/test_js_weak_references.js
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -6436,44 +6436,41 @@ nsWindowSH::NewResolve(nsIXPConnectWrapp
     my_cx = cx;
   } else {
     my_cx = (JSContext *)my_context->GetNativeContext();
   }
 
   JSBool ok;
   jsval exn;
   {
-    JSAutoSuspendRequest asr(my_cx != cx ? cx : nsnull);
-    {
-      JSAutoRequest ar(my_cx);
-
-      JSObject *realObj;
-      wrapper->GetJSObject(&realObj);
-
-      // Don't resolve standard classes on XPCNativeWrapper etc, only
-      // resolve them if we're resolving on the real global object.
-      ok = obj == realObj ?
-           ::JS_ResolveStandardClass(my_cx, obj, id, &did_resolve) :
-           JS_TRUE;
-
-      if (!ok) {
-        // Trust the JS engine (or the script security manager) to set
-        // the exception in the JS engine.
-
-        if (!JS_GetPendingException(my_cx, &exn)) {
-          return NS_ERROR_UNEXPECTED;
-        }
-
-        // Return NS_OK to avoid stomping over the exception that was passed
-        // down from the ResolveStandardClass call.
-        // Note that the order of the JS_ClearPendingException and
-        // JS_SetPendingException is important in the case that my_cx == cx.
-
-        JS_ClearPendingException(my_cx);
+    JSAutoTransferRequest transfer(cx, my_cx);
+
+    JSObject *realObj;
+    wrapper->GetJSObject(&realObj);
+    
+    // Don't resolve standard classes on XPCNativeWrapper etc, only
+    // resolve them if we're resolving on the real global object.
+    ok = obj == realObj ?
+         ::JS_ResolveStandardClass(my_cx, obj, id, &did_resolve) :
+         JS_TRUE;
+    
+    if (!ok) {
+      // Trust the JS engine (or the script security manager) to set
+      // the exception in the JS engine.
+      
+      if (!JS_GetPendingException(my_cx, &exn)) {
+        return NS_ERROR_UNEXPECTED;
       }
+      
+      // Return NS_OK to avoid stomping over the exception that was passed
+      // down from the ResolveStandardClass call.
+      // Note that the order of the JS_ClearPendingException and
+      // JS_SetPendingException is important in the case that my_cx == cx.
+      
+      JS_ClearPendingException(my_cx);
     }
   }
 
   if (!ok) {
     JS_SetPendingException(cx, exn);
     *_retval = JS_FALSE;
     return NS_OK;
   }
--- a/js/src/jsapi-tests/Makefile.in
+++ b/js/src/jsapi-tests/Makefile.in
@@ -44,16 +44,17 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 PROGRAM         = jsapi-tests$(BIN_SUFFIX)
 
 CPPSRCS = \
   tests.cpp \
   selfTest.cpp \
+  testConservativeGC.cpp \
   testContexts.cpp \
   testDebugger.cpp \
   testDefineGetterSetterNonEnumerable.cpp \
   testDefineProperty.cpp \
   testExtendedEq.cpp \
   testGCChunkAlloc.cpp \
   testIntString.cpp \
   testIsAboutToBeFinalized.cpp \
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testConservativeGC.cpp
@@ -0,0 +1,59 @@
+#include "tests.h"
+#include "jsobj.h"
+#include "jsstr.h"
+
+BEGIN_TEST(testConservativeGC)
+{
+    jsval v1;
+    EVAL("Math.sqrt(42);", &v1);
+    CHECK(JSVAL_IS_DOUBLE(v1));
+    double numCopy = *JSVAL_TO_DOUBLE(v1);
+
+    jsval v2;
+    EVAL("({foo: 'bar'});", &v2);
+    CHECK(JSVAL_IS_OBJECT(v2));
+    JSObject objCopy = *JSVAL_TO_OBJECT(v2);
+
+    jsval v3;
+    EVAL("String(Math.PI);", &v3);
+    CHECK(JSVAL_IS_STRING(v3));
+    JSString strCopy = *JSVAL_TO_STRING(v3);
+
+    jsval tmp;
+    EVAL("Math.sqrt(41);", &tmp);
+    CHECK(JSVAL_IS_DOUBLE(tmp));
+    jsdouble *num2 = JSVAL_TO_DOUBLE(tmp);
+    jsdouble num2Copy = *num2;
+
+    EVAL("({foo2: 'bar2'});", &tmp);
+    CHECK(JSVAL_IS_OBJECT(tmp));
+    JSObject *obj2 = JSVAL_TO_OBJECT(tmp);
+    JSObject obj2Copy = *obj2;
+
+    EVAL("String(Math.sqrt(3));", &tmp);
+    CHECK(JSVAL_IS_STRING(tmp));
+    JSString *str2 = JSVAL_TO_STRING(tmp);
+    JSString str2Copy = *str2;
+
+    tmp = JSVAL_NULL;
+
+    JS_GC(cx);
+
+    EVAL("var a = [];\n"
+         "for (var i = 0; i != 10000; ++i) {\n"
+         "a.push(i + 0.1, [1, 2], String(Math.sqrt(i)));\n"
+         "}", &tmp);
+
+    JS_GC(cx);
+
+    CHECK(numCopy == *JSVAL_TO_DOUBLE(v1));
+    CHECK(!memcmp(&objCopy,  JSVAL_TO_OBJECT(v2), sizeof(objCopy)));
+    CHECK(!memcmp(&strCopy,  JSVAL_TO_STRING(v3), sizeof(strCopy)));
+
+    CHECK(num2Copy == *num2);
+    CHECK(!memcmp(&obj2Copy,  obj2, sizeof(obj2Copy)));
+    CHECK(!memcmp(&str2Copy,  str2, sizeof(str2Copy)));
+
+    return true;
+}
+END_TEST(testConservativeGC)
--- a/js/src/jsapi-tests/testIsAboutToBeFinalized.cpp
+++ b/js/src/jsapi-tests/testIsAboutToBeFinalized.cpp
@@ -4,34 +4,83 @@
 
 #include "tests.h"
 #include "jsstr.h"
 
 static JSGCCallback oldGCCallback;
 
 static void **checkPointers;
 static jsuint checkPointersLength;
+static size_t checkPointersStaticStrings;
 
 static JSBool
 TestAboutToBeFinalizedCallback(JSContext *cx, JSGCStatus status)
 {
     if (status == JSGC_MARK_END && checkPointers) {
         for (jsuint i = 0; i != checkPointersLength; ++i) {
             void *p = checkPointers[i];
             JS_ASSERT(p);
             if (JS_IsAboutToBeFinalized(cx, p))
                 checkPointers[i] = NULL;
         }
     }
 
     return !oldGCCallback || oldGCCallback(cx, status);
 }
 
+/*
+ * NativeFrameCleaner write to sink so a compiler nwould not be able to optimze out
+ * the buffer memset there.
+ */
+volatile void *ptrSink;
+
+static JS_NEVER_INLINE void
+NativeFrameCleaner()
+{
+    char buffer[1 << 16];
+    memset(buffer, 0, sizeof buffer);
+    ptrSink = buffer; 
+}
+
 BEGIN_TEST(testIsAboutToBeFinalized_bug528645)
 {
+    /*
+     * Due to the conservative GC we use separated never-inline function to
+     * test rooted elements.
+     */
+    createAndTestRooted();
+    NativeFrameCleaner();
+
+    JS_GC(cx);
+
+    /* Everything is unrooted except unit strings. */
+    for (jsuint i = 0; i != checkPointersLength; ++i) {
+        void *p = checkPointers[i];
+        if (p) {
+            CHECK(JSString::isStatic(p));
+            CHECK(checkPointersStaticStrings != 0);
+            --checkPointersStaticStrings;
+        }
+    }
+    CHECK(checkPointersStaticStrings == 0);
+
+    free(checkPointers);
+    checkPointers = NULL;
+    JS_SetGCCallback(cx, oldGCCallback);
+
+    return true;
+}
+
+JS_NEVER_INLINE bool createAndTestRooted();
+
+END_TEST(testIsAboutToBeFinalized_bug528645)
+
+JS_NEVER_INLINE bool
+cls_testIsAboutToBeFinalized_bug528645::createAndTestRooted()
+{
     jsvalRoot root(cx);
 
     /*
      * Check various types of GC things against JS_IsAboutToBeFinalized.
      * Make sure to include unit and numeric strings to the set.
      */
     EVAL("var x = 1.1; "
          "[x + 0.1, ''+x, 'a', '42', 'something'.substring(1), "
@@ -39,55 +88,34 @@ BEGIN_TEST(testIsAboutToBeFinalized_bug5
          root.addr());
 
     JSObject *array = JSVAL_TO_OBJECT(root.value());
     JS_ASSERT(JS_IsArrayObject(cx, array));
 
     JSBool ok = JS_GetArrayLength(cx, array, &checkPointersLength);
     CHECK(ok);
 
-    void **elems = (void **) malloc(sizeof(void *) * checkPointersLength);
-    CHECK(elems);
+    checkPointers = (void **) malloc(sizeof(void *) * checkPointersLength);
+    CHECK(checkPointers);
 
-    size_t staticStrings = 0;
+    checkPointersStaticStrings = 0;
     for (jsuint i = 0; i != checkPointersLength; ++i) {
         jsval v;
         ok = JS_GetElement(cx, array, i, &v);
         CHECK(ok);
         JS_ASSERT(JSVAL_IS_GCTHING(v));
         JS_ASSERT(!JSVAL_IS_NULL(v));
-        elems[i] = JSVAL_TO_GCTHING(v);
-        if (JSString::isStatic(elems[i]))
-            ++staticStrings;
+        checkPointers[i] = JSVAL_TO_GCTHING(v);
+        if (JSString::isStatic(checkPointers[i]))
+            ++checkPointersStaticStrings;
     }
 
     oldGCCallback = JS_SetGCCallback(cx, TestAboutToBeFinalizedCallback);
-    checkPointers = elems;
     JS_GC(cx);
 
     /*
      * All GC things are rooted via the root holding the array containing them
      * and TestAboutToBeFinalizedCallback must keep them as is.
      */
     for (jsuint i = 0; i != checkPointersLength; ++i)
         CHECK(checkPointers[i]);
-
-    root = JSVAL_NULL;
-    JS_GC(cx);
+}
 
-    /* Everything is unrooted except unit strings. */
-    for (jsuint i = 0; i != checkPointersLength; ++i) {
-        void *p = checkPointers[i];
-        if (p) {
-            CHECK(JSString::isStatic(p));
-            CHECK(staticStrings != 0);
-            --staticStrings;
-        }
-    }
-    CHECK(staticStrings == 0);
-
-    checkPointers = NULL;
-    JS_SetGCCallback(cx, oldGCCallback);
-    free(elems);
-
-    return true;
-}
-END_TEST(testIsAboutToBeFinalized_bug528645)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -778,23 +778,22 @@ JS_BeginRequest(JSContext *cx)
         cx->outstandingRequests++;
         return;
     }
     cx->requestDepth++;
     cx->outstandingRequests++;
 #endif
 }
 
-JS_PUBLIC_API(void)
-JS_EndRequest(JSContext *cx)
-{
 #ifdef JS_THREADSAFE
+static void
+StopRequest(JSContext *cx)
+{
     JSRuntime *rt;
 
-    CHECK_REQUEST(cx);
     JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread));
     JS_ASSERT(cx->requestDepth > 0);
     JS_ASSERT(cx->outstandingRequests > 0);
     JS_ASSERT(cx->thread->contextsInRequests > 0);
     if (cx->requestDepth == 1) {
         LeaveTrace(cx);  /* for GC safety */
 
         /* Lock before clearing to interlock with ClaimScope, in jslock.c. */
@@ -811,16 +810,31 @@ JS_EndRequest(JSContext *cx)
         rt->requestCount--;
         cx->thread->contextsInRequests--;
         if (rt->requestCount == 0)
             JS_NOTIFY_REQUEST_DONE(rt);
         return;
     }
     cx->requestDepth--;
     cx->outstandingRequests--;
+}
+#endif
+
+JS_PUBLIC_API(void)
+JS_EndRequest(JSContext *cx)
+{
+#ifdef JS_THREADSAFE
+    /*
+     * We do not allow to use JS_EndRequest to exit the request when there are
+     * native frames on the stack that insist that the request must be on. But
+     * we do allow to call the API if the request was suspended.
+     */
+    JS_ASSERT_IF(cx->requestDepth == 1 && cx->outstandingRequests == 1,
+                 cx->checkRequestDepth == 0);
+    StopRequest(cx);
 #endif
 }
 
 /* Yield to pending GC operations, regardless of request depth */
 JS_PUBLIC_API(void)
 JS_YieldRequest(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
@@ -830,36 +844,46 @@ JS_YieldRequest(JSContext *cx)
 #endif
 }
 
 JS_PUBLIC_API(jsrefcount)
 JS_SuspendRequest(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
     jsrefcount saveDepth = cx->requestDepth;
-
-    while (cx->requestDepth) {
-        cx->outstandingRequests++;  /* compensate for JS_EndRequest */
-        JS_EndRequest(cx);
-    }
+    if (saveDepth == 0)
+        return 0;
+
+    do {
+        cx->outstandingRequests++;  /* compensate for StopRequest */
+        StopRequest(cx);
+    } while (cx->requestDepth);
+
+    JS_THREAD_DATA(cx)->conservativeGC.enable();
+
     return saveDepth;
 #else
     return 0;
 #endif
 }
 
 JS_PUBLIC_API(void)
 JS_ResumeRequest(JSContext *cx, jsrefcount saveDepth)
 {
 #ifdef JS_THREADSAFE
-    JS_ASSERT(!cx->requestDepth);
-    while (--saveDepth >= 0) {
+    if (saveDepth == 0)
+        return;
+
+    JS_THREAD_DATA(cx)->conservativeGC.disable();
+
+    JS_ASSERT(cx->outstandingRequests != 0);
+    do {
         JS_BeginRequest(cx);
         cx->outstandingRequests--;  /* compensate for JS_BeginRequest */
-    }
+    } while (--saveDepth != 0);
 #endif
 }
 
 JS_PUBLIC_API(void)
 JS_TransferRequest(JSContext *cx, JSContext *another)
 {
     JS_ASSERT(cx != another);
     JS_ASSERT(cx->runtime == another->runtime);
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -59,27 +59,27 @@
 #include "jsversion.h"
 #include "jsdbgapi.h"
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsmath.h"
+#include "jsnativestack.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jspubtd.h"
 #include "jsscan.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstaticcheck.h"
 #include "jsstr.h"
 #include "jstracer.h"
-#include "jsnativestack.h"
 
 #include "jscntxtinlines.h"
 
 #ifdef XP_WIN
 # include <windows.h>
 #elif defined(XP_OS2)
 # define INCL_DOSMEMMGR
 # include <os2.h>
@@ -533,16 +533,17 @@ void
 JSThreadData::finish()
 {
 #ifdef DEBUG
     /* All GC-related things must be already removed at this point. */
     JS_ASSERT(gcFreeLists.isEmpty());
     for (size_t i = 0; i != JS_ARRAY_LENGTH(scriptsToGC); ++i)
         JS_ASSERT(!scriptsToGC[i]);
     JS_ASSERT(!localRootStack);
+    JS_ASSERT(!conservativeGC.isEnabled());
 #endif
 
     if (dtoaState)
         js_DestroyDtoaState(dtoaState);
 
     js_FinishGSNCache(&gsnCache);
     propertyCache.~PropertyCache();
 #if defined JS_TRACER
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1044,16 +1044,18 @@ struct JSThreadData {
     JSObject            *cachedNativeIterators[NATIVE_ITER_CACHE_SIZE];
 
     /* Base address of the native stack for the current thread. */
     jsuword             *nativeStackBase;
 
     /* List of currently pending operations on proxies. */
     JSPendingProxyOperation *pendingProxyOperation;
 
+    js::ConservativeGCThreadData conservativeGC;
+
     bool init();
     void finish();
     void mark(JSTracer *trc);
     void purge(JSContext *cx);
     void purgeGCFreeLists();
 };
 
 #ifdef JS_THREADSAFE
@@ -1817,16 +1819,20 @@ struct JSContext
      */
     js::CallStack *containingCallStack(const JSStackFrame *target);
 
 #ifdef JS_THREADSAFE
     JSThread            *thread;
     jsrefcount          requestDepth;
     /* Same as requestDepth but ignoring JS_SuspendRequest/JS_ResumeRequest */
     jsrefcount          outstandingRequests;
+# ifdef DEBUG
+    unsigned            checkRequestDepth;
+# endif    
+
     JSTitle             *lockedSealedTitle; /* weak ref, for low-cost sealed
                                                title locking */
     JSCList             threadLinks;        /* JSThread contextList linkage */
 
 #define CX_FROM_THREAD_LINKS(tl) \
     ((JSContext *)((char *)(tl) - offsetof(JSContext, threadLinks)))
 #endif
 
@@ -2120,18 +2126,39 @@ InvokeArgsGuard::~InvokeArgsGuard()
     JS_ASSERT(cs == cx->stack().getCurrentCallStack());
     cx->stack().popInvokeArgs(cx, vp);
 }
 
 } /* namespace js */
 
 #ifdef JS_THREADSAFE
 # define JS_THREAD_ID(cx)       ((cx)->thread ? (cx)->thread->id : 0)
+#endif
+
+#if defined JS_THREADSAFE && defined DEBUG
+
+namespace js {
+
+class AutoCheckRequestDepth {
+    JSContext *cx;
+  public:
+    AutoCheckRequestDepth(JSContext *cx) : cx(cx) { cx->checkRequestDepth++; }
+
+    ~AutoCheckRequestDepth() {
+        JS_ASSERT(cx->checkRequestDepth != 0);
+        cx->checkRequestDepth--;
+    }
+};
+
+}
+
 # define CHECK_REQUEST(cx)                                                  \
-    JS_ASSERT((cx)->requestDepth || (cx)->thread == (cx)->runtime->gcThread)
+    JS_ASSERT((cx)->requestDepth || (cx)->thread == (cx)->runtime->gcThread);\
+    AutoCheckRequestDepth _autoCheckRequestDepth(cx);
+
 #else
 # define CHECK_REQUEST(cx)       ((void)0)
 #endif
 
 static inline uintN
 FramePCOffset(JSContext *cx, JSStackFrame* fp)
 {
     return uintN((fp->imacpc ? fp->imacpc : fp->pc(cx)) - fp->script->code);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -805,16 +805,24 @@ GetFinalizableTraceKind(size_t thingKind
 static inline size_t
 GetFinalizableArenaTraceKind(JSGCArenaInfo *ainfo)
 {
     JS_ASSERT(ainfo->list);
     return GetFinalizableTraceKind(ainfo->list->thingKind);
 }
 
 static inline size_t
+GetArenaTraceKind(JSGCArenaInfo *ainfo)
+{
+    if (!ainfo->list)
+        return JSTRACE_DOUBLE;
+    return GetFinalizableArenaTraceKind(ainfo);
+}
+
+static inline size_t
 GetFinalizableThingTraceKind(void *thing)
 {
     JSGCArenaInfo *ainfo = JSGCArenaInfo::fromGCThing(thing);
     return GetFinalizableArenaTraceKind(ainfo);
 }
 
 static void
 InitGCArenaLists(JSRuntime *rt)
@@ -861,19 +869,17 @@ js_GetExternalStringGCType(JSString *str
 
 JS_FRIEND_API(uint32)
 js_GetGCThingTraceKind(void *thing)
 {
     if (JSString::isStatic(thing))
         return JSTRACE_STRING;
 
     JSGCArenaInfo *ainfo = JSGCArenaInfo::fromGCThing(thing);
-    if (!ainfo->list)
-        return JSTRACE_DOUBLE;
-    return GetFinalizableArenaTraceKind(ainfo);
+    return GetArenaTraceKind(ainfo);
 }
 
 JSRuntime *
 js_GetGCThingRuntime(void *thing)
 {
     jsuword chunk = JSGCArena::fromGCThing(thing)->getChunk();
     return JSGCChunkInfo::fromChunk(chunk)->runtime;
 }
@@ -923,16 +929,422 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes
      * (during JS engine start).
      */
     rt->setGCLastBytes(8192);
 
     METER(PodZero(&rt->gcStats));
     return true;
 }
 
+namespace js {
+
+struct GCChunkHasher
+{
+    typedef jsuword Lookup;
+    static HashNumber hash(jsuword chunk) {
+        /*
+         * Strip zeros for better distribution after multiplying by the golden
+         * ratio.
+         */
+        JS_ASSERT(!(chunk & GC_CHUNK_MASK));
+        return HashNumber(chunk >> GC_CHUNK_SHIFT);
+    }
+    static bool match(jsuword k, jsuword l) {
+        JS_ASSERT(!(k & GC_CHUNK_MASK));
+        JS_ASSERT(!(l & GC_CHUNK_MASK));
+        return k == l;
+    }
+};
+
+class ConservativeGCStackMarker {
+  public:
+    ConservativeGCStackMarker(JSTracer *trc);
+
+    ~ConservativeGCStackMarker() {
+#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS
+        dumpConservativeRoots();
+#endif
+#ifdef JS_GCMETER
+        JSConservativeGCStats *total = &trc->context->runtime->gcStats.conservative;
+        total->words        += stats.words;
+        total->unique       += stats.unique;
+        total->oddaddress   += stats.oddaddress;
+        total->outside      += stats.outside;
+        total->notarena     += stats.notarena;
+        total->notchunk     += stats.notchunk;
+        total->freearena    += stats.freearena;
+        total->wrongtag     += stats.wrongtag;
+        total->notlive      += stats.notlive;
+        total->gcthings     += stats.gcthings;
+        total->raw          += stats.raw;
+        total->unmarked     += stats.unmarked;
+#endif
+    }
+
+    void markRoots();
+
+  private:
+    void markRange(jsuword *begin, jsuword *end);
+    void markWord(jsuword w);
+
+    JSTracer *trc;
+    jsuword gcthingAddressStart;
+    jsuword gcthingAddressSpan;
+    HashSet<jsuword, GCChunkHasher, SystemAllocPolicy> chunkSet;
+    HashSet<jsuword, DefaultHasher<jsuword>, SystemAllocPolicy> history;
+
+#if defined(JS_DUMP_CONSERVATIVE_GC_ROOTS) || defined(JS_GCMETER)
+    JSConservativeGCStats stats;
+
+  public:
+    static void dumpStats(FILE *fp, JSConservativeGCStats *stats);
+
+# define CONSERVATIVE_METER(x)  ((void) (x))
+# define CONSERVATIVE_METER_IF(condition, x) ((void) ((condition) && (x)))
+
+#else
+
+# define CONSERVATIVE_METER(x)                  ((void) 0)
+# define CONSERVATIVE_METER_IF(condition, x)    ((void) 0)
+
+#endif
+
+#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS
+  private:
+    struct ConservativeRoot { void *thing; uint32 traceKind; };
+    Vector<ConservativeRoot, 0, SystemAllocPolicy> conservativeRoots;
+    const char *dumpFileName;
+
+    void dumpConservativeRoots();
+#endif
+};
+
+ConservativeGCStackMarker::ConservativeGCStackMarker(JSTracer *trc)
+  : trc(trc)
+{
+    /*
+     * If initializing fails because we are out of memory, stack scanning
+     * slows down but is otherwise unaffected.
+     */
+    history.init(3000);
+
+    JSRuntime *rt = trc->context->runtime;
+    jsuword minchunk = 0, maxchunk = 0;
+    bool chunkSetOk = chunkSet.init(rt->gcChunks.length());
+    for (JSGCChunkInfo **i = rt->gcChunks.begin(); i != rt->gcChunks.end(); ++i) {
+        jsuword chunk =(*i)->getChunk();
+        if (chunkSetOk) {
+            JS_ASSERT(!chunkSet.has(chunk));
+            JS_ALWAYS_TRUE(chunkSet.put(chunk));
+        }
+
+        if (minchunk == 0)
+            minchunk = maxchunk = chunk;
+        else if (chunk < minchunk)
+            minchunk = chunk;
+        else if (chunk > maxchunk)
+            maxchunk = chunk;
+    }
+
+    gcthingAddressStart = minchunk;
+    gcthingAddressSpan = (minchunk != 0)
+                         ? maxchunk + GC_MARK_BITMAP_ARRAY_OFFSET
+                         : 0;
+
+#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS
+    dumpFileName = getenv("JS_DUMP_CONSERVATIVE_GC_ROOTS");
+    memset(&stats, 0, sizeof(stats));
+#endif
+}
+
+#if defined(JS_DUMP_CONSERVATIVE_GC_ROOTS) || defined(JS_GCMETER)
+/* static */
+void
+ConservativeGCStackMarker::dumpStats(FILE *fp, JSConservativeGCStats *stats)
+{
+#define ULSTAT(x)       ((unsigned long)(stats->x))
+    fprintf(fp, "CONSERVATIVE STACK SCANNING:\n");
+    fprintf(fp, "      number of stack words: %lu\n", ULSTAT(words));
+    fprintf(fp, "     number of unique words: %lu\n", ULSTAT(unique));
+    fprintf(fp, "      excluded, low bit set: %lu\n", ULSTAT(oddaddress));
+    fprintf(fp, "    not withing chunk range: %lu\n", ULSTAT(outside));
+    fprintf(fp, "     not within arena range: %lu\n", ULSTAT(notarena));
+    fprintf(fp, "        not withing a chunk: %lu\n", ULSTAT(notchunk));
+    fprintf(fp, "       points to free arena: %lu\n", ULSTAT(freearena));
+    fprintf(fp, "        excluded, wrong tag: %lu\n", ULSTAT(wrongtag));
+    fprintf(fp, "         excluded, not live: %lu\n", ULSTAT(notlive));
+    fprintf(fp, "              things marked: %lu\n", ULSTAT(gcthings));
+    fprintf(fp, "        raw pointers marked: %lu\n", ULSTAT(raw));
+    fprintf(fp, "         conservative roots: %lu\n", ULSTAT(unmarked));
+#undef ULSTAT
+}
+#endif
+
+#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS
+void
+ConservativeGCStackMarker::dumpConservativeRoots()
+{
+    if (!dumpFileName)
+        return;
+
+    JS_ASSERT(stats.unmarked == conservativeRoots.length());
+
+    FILE *fp;
+    if (!strcmp(dumpFileName, "stdout")) {
+        fp = stdout;
+    } else if (!strcmp(dumpFileName, "stderr")) {
+        fp = stderr;
+    } else if (!(fp = fopen(dumpFileName, "aw"))) {
+        fprintf(stderr,
+                "Warning: cannot open %s to dump the conservative roots\n",
+                dumpFileName);
+        return;
+    }
+
+    dumpStats(fp, &stats);
+    for (ConservativeRoot *i = conservativeRoots.begin();
+         i != conservativeRoots.end();
+         ++i) {
+        fprintf(fp, "  %p: ", i->thing);
+        switch (i->traceKind) {
+          default:
+            JS_NOT_REACHED("Unknown trace kind");
+
+          case JSTRACE_OBJECT: {
+            JSObject *obj = (JSObject *) i->thing;
+            fprintf(fp, "object %s", obj->getClass()->name);
+            break;
+          }
+          case JSTRACE_STRING: {
+            JSString *str = (JSString *) i->thing;
+            char buf[50];
+            js_PutEscapedString(buf, sizeof buf, str, '"');
+            fprintf(fp, "string %s", buf);
+            break;
+          }
+          case JSTRACE_DOUBLE: {
+            jsdouble *dp = (jsdouble *) i->thing;
+            fprintf(fp, "double %e", *dp);
+            break;
+          }
+# if JS_HAS_XML_SUPPORT
+          case JSTRACE_XML: {
+            JSXML *xml = (JSXML *) i->thing;
+            fprintf(fp, "xml %u", xml->xml_class);
+            break;
+          }
+# endif
+        }
+        fputc('\n', fp);
+    }
+    fputc('\n', fp);
+
+    if (fp != stdout && fp != stderr)
+        fclose(fp);
+}
+#endif /* JS_DUMP_CONSERVATIVE_GC_ROOTS */
+
+void
+ConservativeGCStackMarker::markWord(jsuword w)
+{
+#define RETURN(x) do { CONSERVATIVE_METER(stats.x++); return; } while (0)
+
+    JSRuntime *rt = trc->context->runtime;
+
+    CONSERVATIVE_METER(stats.unique++);
+
+    /* If this is an odd addresses or a tagged integer, skip it. */
+    if (w & 1)
+        RETURN(oddaddress);
+
+    /* Strip off the tag bits. */
+    jsuword tag = w & JSVAL_TAGMASK;
+    jsuword p = w & ~(JSVAL_TAGMASK);
+
+    /* Also ignore tagged special values (they never contain pointers). */
+    if (tag == JSVAL_SPECIAL)
+        RETURN(wrongtag);
+
+    /* The remaining pointer must be within the heap boundaries. */
+    if ((p - gcthingAddressStart) >= gcthingAddressSpan)
+        RETURN(outside);
+
+    if ((p & GC_CHUNK_MASK) >= GC_MARK_BITMAP_ARRAY_OFFSET)
+        RETURN(notarena);
+
+    jsuword chunk = p & ~GC_CHUNK_MASK;
+    JSGCChunkInfo *ci;
+    if (JS_LIKELY(chunkSet.initialized())) {
+        if (!chunkSet.has(chunk))
+            RETURN(notchunk);
+        ci = JSGCChunkInfo::fromChunk(chunk);
+    } else {
+        ci = JSGCChunkInfo::fromChunk(chunk);
+        for (JSGCChunkInfo **i = rt->gcChunks.begin(); ; ++i) {
+            if (i == rt->gcChunks.end())
+                RETURN(notchunk);
+            if (*i == ci)
+                break;
+        }
+    }
+
+    size_t arenaIndex = (p & GC_CHUNK_MASK) >> GC_ARENA_SHIFT;
+    if (JS_TEST_BIT(ci->getFreeArenaBitmap(), arenaIndex))
+        RETURN(freearena);
+
+    JSGCArena *a = JSGCArena::fromChunkAndIndex(chunk, arenaIndex);
+    JSGCArenaInfo *ainfo = a->getInfo();
+
+    JSGCThing *thing;
+    if (!ainfo->list) { /* doubles */
+        if (tag && tag != JSVAL_DOUBLE)
+            RETURN(wrongtag);
+        JS_STATIC_ASSERT(JSVAL_TAGMASK == 7 && (sizeof(double) - 1) == 7);
+        thing = (JSGCThing *) p;
+    } else {
+        if (tag == JSVAL_DOUBLE)
+            RETURN(wrongtag);
+        jsuword start = a->toPageStart();
+        jsuword offset = p - start;
+        size_t thingSize = ainfo->list->thingSize;
+        p = (start + offset - (offset % thingSize));
+        thing = (JSGCThing *) p;
+
+        /* Make sure the thing is not on the freelist of the arena. */
+        JSGCThing *cursor = ainfo->freeList;
+        while (cursor) {
+            /* If the cursor moves past the thing, it's not in the freelist. */
+            if (thing < cursor)
+                break;
+
+            /* If we find it on the freelist, it's dead. */
+            if (thing == cursor)
+                RETURN(notlive);
+            cursor = cursor->link;
+        }
+    }
+
+    CONSERVATIVE_METER(stats.gcthings++);
+    CONSERVATIVE_METER_IF(!tag, stats.raw++);
+
+    /*
+     * We have now a valid pointer, that is either raw or tagged properly.
+     * Since we do not rely on the conservative scanning yet and assume that
+     * all the roots are precisely reported, any unmarked GC things here mean
+     * those things leaked.
+     */
+    if (IS_GC_MARKING_TRACER(trc)) {
+        if (!js_IsAboutToBeFinalized(thing))
+            return;
+        CONSERVATIVE_METER(stats.unmarked++);
+    }
+
+    uint32 traceKind = GetArenaTraceKind(ainfo);
+#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS
+    if (IS_GC_MARKING_TRACER(trc) && dumpFileName) {
+        ConservativeRoot root = {thing, traceKind};
+        conservativeRoots.append(root);
+    }
+#endif
+    JS_SET_TRACING_NAME(trc, "machine stack");
+    js_CallGCMarker(trc, thing, traceKind);
+
+#undef RETURN
+}
+
+void
+ConservativeGCStackMarker::markRange(jsuword *begin, jsuword *end)
+{
+    JS_ASSERT(begin <= end);
+    if (history.initialized()) {
+        for (jsuword *i = begin; i != end; ++i) {
+            CONSERVATIVE_METER(stats.words++);
+            jsuword p = *i;
+            if (history.has(p))
+                continue;
+            markWord(p);
+
+            /*
+             * If adding the address to the hash table fails because we are
+             * out of memory, stack scanning slows down but is otherwise
+             * unaffected.
+             */
+            history.put(p);
+        }
+    } else {
+        for (jsuword *i = begin; i != end; ++i)
+            markWord(*i);
+    }
+}
+
+void
+ConservativeGCStackMarker::markRoots()
+{
+    /* Do conservative scanning of the stack. */
+    for (ThreadDataIter i(trc->context->runtime); !i.empty(); i.popFront()) {
+        JSThreadData *td = i.threadData();
+        ConservativeGCThreadData *ctd = &td->conservativeGC;
+        if (ctd->isEnabled()) {
+#if JS_STACK_GROWTH_DIRECTION > 0
+            JS_ASSERT(td->nativeStackBase <= ctd->nativeStackTop);
+            markRange(td->nativeStackBase, ctd->nativeStackTop);
+#else
+            JS_ASSERT(td->nativeStackBase >= ctd->nativeStackTop + 1);
+            markRange(ctd->nativeStackTop + 1, td->nativeStackBase);
+#endif
+            markRange(ctd->registerSnapshot.words,
+                      JS_ARRAY_END(ctd->registerSnapshot.words));
+        }
+    }
+}
+
+/* static */
+JS_NEVER_INLINE JS_FRIEND_API(void)
+ConservativeGCThreadData::enable(bool knownStackBoundary)
+{
+    ++enableCount;
+    if (enableCount <= 0)
+        return;
+
+    /* Update the native stack pointer if it points to a bigger stack. */
+#if JS_STACK_GROWTH_DIRECTION > 0
+# define CMP >
+#else
+# define CMP <
+#endif
+    jsuword dummy;
+    if (knownStackBoundary || enableCount == 1 || &dummy CMP nativeStackTop)
+        nativeStackTop = &dummy;
+#undef CMP
+
+    /* Update the register snapshot with the latest values. */
+#if defined(_MSC_VER)
+# pragma warning(push)
+# pragma warning(disable: 4611)
+#endif
+    setjmp(registerSnapshot.jmpbuf);
+#if defined(_MSC_VER)
+# pragma warning(pop)
+#endif
+
+}
+
+JS_NEVER_INLINE JS_FRIEND_API(void)
+ConservativeGCThreadData::disable()
+{
+    --enableCount;
+#ifdef DEBUG
+    if (enableCount == 0)
+        nativeStackTop = NULL;
+#endif
+}
+
+} /* namespace js */
+
+
 #ifdef JS_GCMETER
 
 static void
 UpdateArenaStats(JSGCArenaStats *st, uint32 nlivearenas, uint32 nkilledArenas,
                  uint32 nthings)
 {
     size_t narenas;
 
@@ -1088,16 +1500,18 @@ js_DumpGCStats(JSRuntime *rt, FILE *fp)
     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));
 
+    ConservativeGCStackMarker::dumpStats(fp, &rt->gcStats.conservative);
+
 #undef UL
 #undef ULSTAT
 #undef PERCENT
 }
 #endif
 
 #ifdef DEBUG
 static void
@@ -2228,37 +2642,44 @@ js_TraceContext(JSTracer *trc, JSContext
     }
 #endif
 }
 
 JS_REQUIRES_STACK void
 js_TraceRuntime(JSTracer *trc)
 {
     JSRuntime *rt = trc->context->runtime;
-    JSContext *iter, *acx;
 
     for (GCRoots::Range r = rt->gcRootsHash.all(); !r.empty(); r.popFront())
         gc_root_traversal(r.front(), trc);
 
     for (GCLocks::Range r = rt->gcLocksHash.all(); !r.empty(); r.popFront())
         gc_lock_traversal(r.front(), trc);
 
     js_TraceAtomState(trc);
     js_TraceRuntimeNumberState(trc);
     js_MarkTraps(trc);
 
-    iter = NULL;
-    while ((acx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL)
+    JSContext *iter = NULL;
+    while (JSContext *acx = js_ContextIterator(rt, JS_TRUE, &iter))
         js_TraceContext(trc, acx);
 
     for (ThreadDataIter i(rt); !i.empty(); i.popFront())
         i.threadData()->mark(trc);
 
     if (rt->gcExtraRootsTraceOp)
         rt->gcExtraRootsTraceOp(trc, rt->gcExtraRootsData);
+
+    /*
+     * For now we use the conservative stack scanner only as a sanity check,
+     * so we mark conservatively after marking all other roots to detect
+     * conservative leaks.
+     */
+    if (rt->state != JSRTS_LANDING)
+        ConservativeGCStackMarker(trc).markRoots();
 }
 
 void
 js_TriggerGC(JSContext *cx, JSBool gcLocked)
 {
     JSRuntime *rt = cx->runtime;
 
 #ifdef JS_THREADSAFE
@@ -3022,27 +3443,29 @@ LetOtherGCFinish(JSContext *cx)
     cx->thread->gcWaiting = true;
     js_ShareWaitingTitles(cx);
 
     /*
      * Check that we did not release the GC lock above and let the GC to
      * finish before we wait.
      */
     JS_ASSERT(rt->gcThread);
+    JS_THREAD_DATA(cx)->conservativeGC.enable(true);
 
     /*
      * Wait for GC to finish on the other thread, even if requestDebit is 0
      * and even if GC has not started yet because the gcThread is waiting in
      * BeginGCSession. This ensures that js_GC never returns without a full GC
      * cycle happening.
      */
     do {
         JS_AWAIT_GC_DONE(rt);
     } while (rt->gcThread);
 
+    JS_THREAD_DATA(cx)->conservativeGC.disable();
     cx->thread->gcWaiting = false;
     rt->requestCount += requestDebit;
 }
 
 #endif
 
 /*
  * Start a new GC session assuming no GC is running on this or other threads.
@@ -3159,16 +3582,27 @@ GCUntilDone(JSContext *cx, JSGCInvocatio
             return;
     }
 #endif /* JS_THREADSAFE */
 
     BeginGCSession(cx);
 
     METER(rt->gcStats.poke++);
 
+    /*
+     * Do not scan the current thread on the shutdown or when the GC is called
+     * outside a request.
+     */
+    bool scanGCThreadStack =
+#ifdef JS_THREADSAFE
+                             (cx->thread->contextsInRequests != 0) &&
+#endif
+                             (rt->state != JSRTS_LANDING);
+    if (scanGCThreadStack)
+        JS_THREAD_DATA(cx)->conservativeGC.enable(true);
     bool firstRun = true;
     do {
         rt->gcPoke = false;
 
         AutoUnlockGC unlock(rt);
         if (firstRun) {
             PreGCCleanup(cx, gckind);
             TIMESTAMP(startMark);
@@ -3177,16 +3611,19 @@ GCUntilDone(JSContext *cx, JSGCInvocatio
         GC(cx  GCTIMER_ARG);
 
         // GC again if:
         //   - another thread, not in a request, called js_GC
         //   - js_GC was called recursively
         //   - a finalizer called js_RemoveRoot or js_UnlockGCThingRT.
     } while (rt->gcPoke);
 
+    if (scanGCThreadStack)
+        JS_THREAD_DATA(cx)->conservativeGC.disable();
+
     rt->gcRegenShapes = false;
     rt->setGCLastBytes(rt->gcBytes);
 
     EndGCSession(cx);
 }
 
 /*
  * The gckind flag bit GC_LOCK_HELD indicates a call from js_NewGCThing with
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -37,16 +37,19 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsgc_h___
 #define jsgc_h___
 /*
  * JS Garbage Collector.
  */
+#include <setjmp.h>
+
+#include "jstypes.h"
 #include "jsprvtd.h"
 #include "jspubtd.h"
 #include "jsdhash.h"
 #include "jsbit.h"
 #include "jsutil.h"
 #include "jstask.h"
 #include "jsvector.h"
 #include "jsversion.h"
@@ -366,19 +369,19 @@ struct JSWeakRoots {
     /* Root for the result of the most recent js_InternalInvoke call. */
     jsval           lastInternalResult;
 
     void mark(JSTracer *trc);
 };
 
 #define JS_CLEAR_WEAK_ROOTS(wr) (memset((wr), 0, sizeof(JSWeakRoots)))
 
-#ifdef JS_THREADSAFE
+namespace js {
 
-namespace js {
+#ifdef JS_THREADSAFE
 
 /*
  * During the finalization we do not free immediately. Rather we add the
  * corresponding pointers to a buffer which we later release on the
  * background thread.
  *
  * The buffer is implemented as a vector of 64K arrays of pointers, not as a
  * simple vector, to avoid realloc calls during the vector growth and to not
@@ -412,29 +415,72 @@ class BackgroundSweepTask : public JSBac
             *freeCursor++ = ptr;
         else
             replenishAndFreeLater(ptr);
     }
 
     virtual void run();
 };
 
-}
-#endif
+#endif /* JS_THREADSAFE */
+
+struct ConservativeGCThreadData {
+
+    /*
+     * The GC scans conservatively between JSThreadData::nativeStackBase and
+     * nativeStackTop unless the latter is NULL.
+     */
+    jsuword             *nativeStackTop;
+
+    union {
+        jmp_buf         jmpbuf;
+        jsuword         words[JS_HOWMANY(sizeof(jmp_buf), sizeof(jsuword))];
+    } registerSnapshot;
+
+    int                 enableCount;
+
+    JS_NEVER_INLINE JS_FRIEND_API(void) enable(bool knownStackBoundary = false);
+    JS_FRIEND_API(void) disable();
+    bool isEnabled() const { return enableCount > 0; }
+};
+
+} /* namespace js */
+
+#define JS_DUMP_CONSERVATIVE_GC_ROOTS 1
 
 extern void
 js_FinalizeStringRT(JSRuntime *rt, JSString *str);
 
 #if defined JS_GCMETER
 const bool JS_WANT_GC_METER_PRINT = true;
 #elif defined DEBUG
 # define JS_GCMETER 1
 const bool JS_WANT_GC_METER_PRINT = false;
 #endif
 
+#if defined JS_GCMETER || defined JS_DUMP_CONSERVATIVE_GC_ROOTS
+
+struct JSConservativeGCStats {
+    uint32  words;      /* number of words on native stacks */
+    uint32  unique;     /* number of unique words */
+    uint32  oddaddress; /* excluded because low bit was set */
+    uint32  outside;    /* not within chunk min/max address range */
+    uint32  notarena;   /* not within arena range in a chunk */
+    uint32  notchunk;   /* not within a valid chunk */
+    uint32  freearena;  /* not within non-free arena */
+    uint32  wrongtag;   /* tagged pointer but wrong type */
+    uint32  notlive;    /* gcthing is not allocated */
+    uint32  gcthings;   /* number of live gcthings */
+    uint32  raw;        /* number of raw pointers marked */
+    uint32  unmarked;   /* number of unmarked gc things discovered on the
+                           stack */
+};
+
+#endif
+
 #ifdef JS_GCMETER
 
 struct JSGCArenaStats {
     uint32  alloc;          /* allocation attempts */
     uint32  localalloc;     /* allocations from local lists */
     uint32  retry;          /* allocation retries after running the GC */
     uint32  fail;           /* allocation failures */
     uint32  nthings;        /* live GC things */
@@ -473,16 +519,18 @@ struct JSGCStats {
     uint32  maxcloselater;  /* max number of close hooks scheduled to run */
     uint32  nallarenas;     /* number of all allocated arenas */
     uint32  maxnallarenas;  /* maximum number of all allocated arenas */
     uint32  nchunks;        /* number of allocated chunks */
     uint32  maxnchunks;     /* maximum number of allocated chunks */
 
     JSGCArenaStats  arenaStats[FINALIZE_LIMIT];
     JSGCArenaStats  doubleArenaStats;
+
+    JSConservativeGCStats conservative;
 };
 
 extern JS_FRIEND_API(void)
 js_DumpGCStats(JSRuntime *rt, FILE *fp);
 
 #endif /* JS_GCMETER */
 
 /*
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -5524,17 +5524,17 @@ Utf8ToOneUcs4Char(const uint8 *utf8Buffe
             ucs4Char = OVERLONG_UTF8;
         } else if (ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) {
             ucs4Char = 0xFFFD;
         }
     }
     return ucs4Char;
 }
 
-#ifdef DEBUG
+#if defined DEBUG || defined JS_DUMP_CONSERVATIVE_GC_ROOTS
 
 JS_FRIEND_API(size_t)
 js_PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp,
                         JSString *str, uint32 quote)
 {
     const jschar *chars, *charsEnd;
     size_t n;
     const char *escape;
--- a/js/src/jstypes.h
+++ b/js/src/jstypes.h
@@ -199,16 +199,26 @@
 #  define JS_ALWAYS_INLINE   __forceinline
 # elif defined __GNUC__
 #  define JS_ALWAYS_INLINE   __attribute__((always_inline)) JS_INLINE
 # else
 #  define JS_ALWAYS_INLINE   JS_INLINE
 # endif
 #endif
 
+#ifndef JS_NEVER_INLINE
+# if defined _MSC_VER
+#  define JS_NEVER_INLINE __declspec(noinline)
+# elif defined __GNUC__
+#  define JS_NEVER_INLINE __attribute__((noinline))
+# else
+#  define JS_NEVER_INLINE
+# endif
+#endif
+
 #ifdef NS_STATIC_CHECKING
 /*
  * Attributes for static analysis. Functions declared with JS_REQUIRES_STACK
  * always have a valid cx->fp and can access it freely.  Other functions can
  * access cx->fp only after calling a function that "forces" the stack
  * (i.e. lazily instantiates it as needed).
  */
 # define JS_REQUIRES_STACK   __attribute__((user("JS_REQUIRES_STACK")))
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -440,17 +440,34 @@ nsXPConnect::Collect()
     mCycleCollecting = PR_TRUE;
     mCycleCollectionContext = &cycleCollectionContext;
     gDidCollection = PR_FALSE;
     gInCollection = PR_FALSE;
     gCollected = PR_FALSE;
 
     JSContext *cx = mCycleCollectionContext->GetJSContext();
     gOldJSGCCallback = JS_SetGCCallback(cx, XPCCycleCollectGCCallback);
-    JS_GC(cx);
+
+    // We want to scan the current thread for GC roots only if it was in a
+    // request prior to the Collect call to avoid false positives during the
+    // cycle collection. So to compensate for JS_BeginRequest in
+    // XPCCallContext::Init we disable the conservative scanner if that call
+    // has started the only request on this thread.
+    JS_ASSERT(cx->requestDepth >= 1);
+    JS_ASSERT(cx->thread->contextsInRequests >= 1);
+    if(cx->requestDepth >= 2 || cx->thread->contextsInRequests >= 2)
+    {
+        JS_GC(cx);
+    }
+    else
+    {
+        JS_THREAD_DATA(cx)->conservativeGC.disable();
+        JS_GC(cx);
+        JS_THREAD_DATA(cx)->conservativeGC.enable();
+    }
     JS_SetGCCallback(cx, gOldJSGCCallback);
     gOldJSGCCallback = nsnull;
 
     mCycleCollectionContext = nsnull;
     mCycleCollecting = PR_FALSE;
 
     return gCollected;
 }
@@ -577,17 +594,18 @@ nsXPConnect::ToParticipant(void *p)
 
 void
 nsXPConnect::CommenceShutdown()
 {
 #ifdef DEBUG
     fprintf(stderr, "nsXPConnect::CommenceShutdown()\n");
 #endif
     // Tell the JS engine that we are about to destroy the runtime.
-    JS_CommenceRuntimeShutDown(mRuntime->GetJSRuntime());
+    JSRuntime* rt = mRuntime->GetJSRuntime();
+    JS_CommenceRuntimeShutDown(rt);
 }
 
 NS_IMETHODIMP
 nsXPConnect::RootAndUnlinkJSObjects(void *p)
 {
     return NS_OK;
 }
 
--- a/js/src/xpconnect/tests/unit/test_js_weak_references.js
+++ b/js/src/xpconnect/tests/unit/test_js_weak_references.js
@@ -48,16 +48,22 @@ function run_test()
   // Force garbage collection
   Components.utils.forceGC();
 
   // obj still references the object, so it should still be accessible via weak
   do_check_true(weak.get() === obj);
   do_check_true(weak.get().num == 5);
   do_check_true(weak.get().str == 'foo');
 
-  // Clear obj's reference to the object and force garbage collection
-  obj = null;
+  // Clear obj's reference to the object and force garbage collection. To make
+  // sure that there are no instances of obj stored in the registers or on the
+  // native stack and the conservative GC would not find it we force the same
+  // code paths that we used for the initial allocation.
+  obj = { num: 6, str: 'foo2' };
+  var weak2 = Components.utils.getWeakReference(obj);
+  do_check_true(weak2.get() === obj);
+
   Components.utils.forceGC();
 
   // The object should have been garbage collected and so should no longer be
   // accessible via weak
   do_check_true(weak.get() === null);
 }