Bug 519949 - TM: remove LocalRootScopes r=igor
authorGregor Wagner <anygregor@gmail.com>
Wed, 23 Jun 2010 11:37:35 -0500
changeset 47494 fbf9ceffee0609bae569b8fc4c9d576492861510
parent 47493 03876a6eba5a5046c5cf8c60aa1e0e47c05ddf27
child 47495 dd2e6dc2699ec59e90b528185429c68813f6eecf
push id14372
push userrsayre@mozilla.com
push dateWed, 14 Jul 2010 13:02:13 +0000
treeherdermozilla-central@c26c255bade9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersigor
bugs519949
milestone1.9.3a6pre
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 519949 - TM: remove LocalRootScopes r=igor
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsinterp.cpp
js/src/jsxml.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1920,44 +1920,16 @@ JS_RemoveGCThingRoot(JSContext *cx, void
 }
 
 JS_PUBLIC_API(void)
 JS_ClearNewbornRoots(JSContext *cx)
 {
     JS_CLEAR_WEAK_ROOTS(&cx->weakRoots);
 }
 
-JS_PUBLIC_API(JSBool)
-JS_EnterLocalRootScope(JSContext *cx)
-{
-    CHECK_REQUEST(cx);
-    return js_EnterLocalRootScope(cx);
-}
-
-JS_PUBLIC_API(void)
-JS_LeaveLocalRootScope(JSContext *cx)
-{
-    CHECK_REQUEST(cx);
-    js_LeaveLocalRootScope(cx);
-}
-
-JS_PUBLIC_API(void)
-JS_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval)
-{
-    CHECK_REQUEST(cx);
-    js_LeaveLocalRootScopeWithResult(cx, rval);
-}
-
-JS_PUBLIC_API(void)
-JS_ForgetLocalRoot(JSContext *cx, void *thing)
-{
-    CHECK_REQUEST(cx);
-    js_ForgetLocalRoot(cx, (jsval) thing);
-}
-
 #ifdef DEBUG
 
 JS_PUBLIC_API(void)
 JS_DumpNamedRoots(JSRuntime *rt,
                   void (*dump)(const char *name, void *rp, void *data),
                   void *data)
 {
     js_DumpNamedRoots(rt, dump, data);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -989,126 +989,24 @@ extern JS_FRIEND_API(JSBool)
 js_RemoveRoot(JSRuntime *rt, void *rp);
 
 /*
  * This symbol may be used by embedders to detect the change from the old
  * JS_AddRoot(JSContext *, void *) APIs to the new ones above.
  */
 #define JS_TYPED_ROOTING_API
 
-/*
- * The last GC thing of each type (object, string, double, external string
- * types) created on a given context is kept alive until another thing of the
- * same type is created, using a newborn root in the context.  These newborn
- * roots help native code protect newly-created GC-things from GC invocations
- * activated before those things can be rooted using local or global roots.
- *
- * However, the newborn roots can also entrain great gobs of garbage, so the
- * JS_GC entry point clears them for the context on which GC is being forced.
- * Embeddings may need to do likewise for all contexts.
- *
- * See the scoped local root API immediately below for a better way to manage
- * newborns in cases where native hooks (functions, getters, setters, etc.)
- * create many GC-things, potentially without connecting them to predefined
- * local roots such as *rval or argv[i] in an active native function.  Using
- * JS_EnterLocalRootScope disables updating of the context's per-gc-thing-type
- * newborn roots, until control flow unwinds and leaves the outermost nesting
- * local root scope.
- */
 extern JS_PUBLIC_API(void)
 JS_ClearNewbornRoots(JSContext *cx);
 
-/*
- * Scoped local root management allows native functions, getter/setters, etc.
- * to avoid worrying about the newborn root pigeon-holes, overloading local
- * roots allocated in argv and *rval, or ending up having to call JS_Add*Root
- * and JS_Remove*Root to manage global roots temporarily.
- *
- * Instead, calling JS_EnterLocalRootScope and JS_LeaveLocalRootScope around
- * the body of the native hook causes the engine to allocate a local root for
- * each newborn created in between the two API calls, using a local root stack
- * associated with cx.  For example:
- *
- *    JSBool
- *    my_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
- *    {
- *        JSBool ok;
- *
- *        if (!JS_EnterLocalRootScope(cx))
- *            return JS_FALSE;
- *        ok = my_GetPropertyBody(cx, obj, id, vp);
- *        JS_LeaveLocalRootScope(cx);
- *        return ok;
- *    }
- *
- * NB: JS_LeaveLocalRootScope must be called once for every prior successful
- * call to JS_EnterLocalRootScope.  If JS_EnterLocalRootScope fails, you must
- * not make the matching JS_LeaveLocalRootScope call.
- *
- * JS_LeaveLocalRootScopeWithResult(cx, rval) is an alternative way to leave
- * a local root scope that protects a result or return value, by effectively
- * pushing it in the caller's local root scope.
- *
- * In case a native hook allocates many objects or other GC-things, but the
- * native protects some of those GC-things by storing them as property values
- * in an object that is itself protected, the hook can call JS_ForgetLocalRoot
- * to free the local root automatically pushed for the now-protected GC-thing.
- *
- * JS_ForgetLocalRoot works on any GC-thing allocated in the current local
- * root scope, but it's more time-efficient when called on references to more
- * recently created GC-things.  Calling it successively on other than the most
- * recently allocated GC-thing will tend to average the time inefficiency, and
- * may risk O(n^2) growth rate, but in any event, you shouldn't allocate too
- * many local roots if you can root as you go (build a tree of objects from
- * the top down, forgetting each latest-allocated GC-thing immediately upon
- * linking it to its parent).
- */
-extern JS_PUBLIC_API(JSBool)
-JS_EnterLocalRootScope(JSContext *cx);
-
-extern JS_PUBLIC_API(void)
-JS_LeaveLocalRootScope(JSContext *cx);
-
-extern JS_PUBLIC_API(void)
-JS_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval);
-
-extern JS_PUBLIC_API(void)
-JS_ForgetLocalRoot(JSContext *cx, void *thing);
-
-#ifdef __cplusplus
-JS_END_EXTERN_C
-
-class JSAutoLocalRootScope {
-  public:
-    JSAutoLocalRootScope(JSContext *cx JS_GUARD_OBJECT_NOTIFIER_PARAM)
-        : mContext(cx) {
-        JS_GUARD_OBJECT_NOTIFIER_INIT;
-        JS_EnterLocalRootScope(mContext);
-    }
-    ~JSAutoLocalRootScope() {
-        JS_LeaveLocalRootScope(mContext);
-    }
-
-    void forget(void *thing) {
-        JS_ForgetLocalRoot(mContext, thing);
-    }
-
-  protected:
-    JSContext *mContext;
-    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
-
-#if 0
-  private:
-    static void *operator new(size_t) CPP_THROW_NEW { return 0; };
-    static void operator delete(void *, size_t) { };
-#endif
-};
-
-JS_BEGIN_EXTERN_C
-#endif
+/* Obsolete rooting APIs. */
+#define JS_EnterLocalRootScope(cx) (true)
+#define JS_LeaveLocalRootScope(cx) ((void) 0)
+#define JS_LeaveLocalRootScopeWithResult(cx, rval) ((void) 0)
+#define JS_ForgetLocalRoot(cx, thing) ((void) 0)
 
 #ifdef DEBUG
 extern JS_PUBLIC_API(void)
 JS_DumpNamedRoots(JSRuntime *rt,
                   void (*dump)(const char *name, void *rp, void *data),
                   void *data);
 #endif
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -99,19 +99,16 @@
 using namespace js;
 
 static const size_t ARENA_HEADER_SIZE_HACK = 40;
 static const size_t TEMP_POOL_CHUNK_SIZE = 4096 - ARENA_HEADER_SIZE_HACK;
 
 static void
 FreeContext(JSContext *cx);
 
-static void
-MarkLocalRoots(JSTracer *trc, JSLocalRootStack *lrs);
-
 #ifdef DEBUG
 JS_REQUIRES_STACK bool
 CallStack::contains(const JSStackFrame *fp) const
 {
     JS_ASSERT(inContext());
     JSStackFrame *start;
     JSStackFrame *stop;
     if (isSuspended()) {
@@ -533,17 +530,16 @@ JSThreadData::init()
 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();
@@ -555,24 +551,22 @@ JSThreadData::finish()
 
 void
 JSThreadData::mark(JSTracer *trc)
 {
     stackSpace.mark(trc);
 #ifdef JS_TRACER
     traceMonitor.mark(trc);
 #endif
-    if (localRootStack)
-        MarkLocalRoots(trc, localRootStack);
 }
 
 void
 JSThreadData::purge(JSContext *cx)
 {
-    purgeGCFreeLists();
+    gcFreeLists.purge();
 
     js_PurgeGSNCache(&gsnCache);
 
     /* FIXME: bug 506341. */
     propertyCache.purge(cx);
 
 #ifdef JS_TRACER
     /*
@@ -587,27 +581,16 @@ JSThreadData::purge(JSContext *cx)
     js_DestroyScriptsToGC(cx, this);
 
     /* Purge cached native iterators. */
     memset(cachedNativeIterators, 0, sizeof(cachedNativeIterators));
 
     dtoaCache.s = NULL;
 }
 
-void
-JSThreadData::purgeGCFreeLists()
-{
-    if (!localRootStack) {
-        gcFreeLists.purge();
-    } else {
-        JS_ASSERT(gcFreeLists.isEmpty());
-        localRootStack->gcFreeLists.purge();
-    }
-}
-
 #ifdef JS_THREADSAFE
 
 static JSThread *
 NewThread(void *id)
 {
     JS_ASSERT(js_CurrentThreadId() == id);
     JSThread *thread = (JSThread *) js_calloc(sizeof(JSThread));
     if (!thread)
@@ -746,17 +729,17 @@ js_PurgeThreads(JSContext *cx)
         if (JS_CLIST_IS_EMPTY(&thread->contextList)) {
             JS_ASSERT(cx->thread != thread);
             js_DestroyScriptsToGC(cx, &thread->data);
 
             /*
              * The following is potentially suboptimal as it also zeros the
              * caches in data, but the code simplicity wins here.
              */
-            thread->data.purgeGCFreeLists();
+            thread->data.gcFreeLists.purge();
             DestroyThread(thread);
             e.removeFront();
         } else {
             thread->data.purge(cx);
             thread->gcThreadMallocBytes = JS_GC_THREAD_MALLOC_LIMIT;
         }
     }
 #else
@@ -1357,246 +1340,16 @@ js_StopResolving(JSContext *cx, JSResolv
      * compressing or shrinking the table as needed.
      */
     if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2)
         JS_DHashTableRawRemove(table, &entry->hdr);
     else
         JS_DHashTableOperate(table, key, JS_DHASH_REMOVE);
 }
 
-JSBool
-js_EnterLocalRootScope(JSContext *cx)
-{
-    JSThreadData *td = JS_THREAD_DATA(cx);
-    JSLocalRootStack *lrs = td->localRootStack;
-    if (!lrs) {
-        lrs = (JSLocalRootStack *) js_malloc(sizeof *lrs);
-        if (!lrs) {
-            js_ReportOutOfMemory(cx);
-            return false;
-        }
-        lrs->scopeMark = JSLRS_NULL_MARK;
-        lrs->rootCount = 0;
-        lrs->topChunk = &lrs->firstChunk;
-        lrs->firstChunk.down = NULL;
-        td->gcFreeLists.moveTo(&lrs->gcFreeLists);
-        td->localRootStack = lrs;
-    }
-
-    /* Push lrs->scopeMark to save it for restore when leaving. */
-    int mark = js_PushLocalRoot(cx, lrs, INT_TO_JSVAL(lrs->scopeMark));
-    if (mark < 0)
-        return JS_FALSE;
-    lrs->scopeMark = (uint32) mark;
-    return true;
-}
-
-void
-js_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval)
-{
-    JSLocalRootStack *lrs;
-    uint32 mark, m, n;
-    JSLocalRootChunk *lrc;
-
-    /* Defend against buggy native callers. */
-    lrs = JS_THREAD_DATA(cx)->localRootStack;
-    JS_ASSERT(lrs && lrs->rootCount != 0);
-    if (!lrs || lrs->rootCount == 0)
-        return;
-
-    mark = lrs->scopeMark;
-    JS_ASSERT(mark != JSLRS_NULL_MARK);
-    if (mark == JSLRS_NULL_MARK)
-        return;
-
-    /* Free any chunks being popped by this leave operation. */
-    m = mark >> JSLRS_CHUNK_SHIFT;
-    n = (lrs->rootCount - 1) >> JSLRS_CHUNK_SHIFT;
-    while (n > m) {
-        lrc = lrs->topChunk;
-        JS_ASSERT(lrc != &lrs->firstChunk);
-        lrs->topChunk = lrc->down;
-        js_free(lrc);
-        --n;
-    }
-
-    /*
-     * Pop the scope, restoring lrs->scopeMark.  If rval is a GC-thing, push
-     * it on the caller's scope, or store it in lastInternalResult if we are
-     * leaving the outermost scope.  We don't need to allocate a new lrc
-     * because we can overwrite the old mark's slot with rval.
-     */
-    lrc = lrs->topChunk;
-    m = mark & JSLRS_CHUNK_MASK;
-    lrs->scopeMark = (uint32) JSVAL_TO_INT(lrc->roots[m]);
-    if (JSVAL_IS_GCTHING(rval) && !JSVAL_IS_NULL(rval)) {
-        if (mark == 0) {
-            cx->weakRoots.lastInternalResult = rval;
-        } else {
-            /*
-             * Increment m to avoid the "else if (m == 0)" case below.  If
-             * rval is not a GC-thing, that case would take care of freeing
-             * any chunk that contained only the old mark.  Since rval *is*
-             * a GC-thing here, we want to reuse that old mark's slot.
-             */
-            lrc->roots[m++] = rval;
-            ++mark;
-        }
-    }
-    lrs->rootCount = (uint32) mark;
-
-    /*
-     * Free the stack eagerly, risking malloc churn.  The alternative would
-     * require an lrs->entryCount member, maintained by Enter and Leave, and
-     * tested by the GC in addition to the cx->localRootStack non-null test.
-     *
-     * That approach would risk hoarding 264 bytes (net) per context.  Right
-     * now it seems better to give fresh (dirty in CPU write-back cache, and
-     * the data is no longer needed) memory back to the malloc heap.
-     */
-    if (mark == 0) {
-        JSThreadData *td = JS_THREAD_DATA(cx);
-        JS_ASSERT(td->gcFreeLists.isEmpty());
-        lrs->gcFreeLists.moveTo(&td->gcFreeLists);
-        td->localRootStack = NULL;
-        js_free(lrs);
-    } else if (m == 0) {
-        lrs->topChunk = lrc->down;
-        js_free(lrc);
-    }
-}
-
-void
-js_ForgetLocalRoot(JSContext *cx, jsval v)
-{
-    JSLocalRootStack *lrs;
-    uint32 i, j, m, n, mark;
-    JSLocalRootChunk *lrc, *lrc2;
-    jsval top;
-
-    lrs = JS_THREAD_DATA(cx)->localRootStack;
-    JS_ASSERT(lrs && lrs->rootCount);
-    if (!lrs || lrs->rootCount == 0)
-        return;
-
-    /* Prepare to pop the top-most value from the stack. */
-    n = lrs->rootCount - 1;
-    m = n & JSLRS_CHUNK_MASK;
-    lrc = lrs->topChunk;
-    top = lrc->roots[m];
-
-    /* Be paranoid about calls on an empty scope. */
-    mark = lrs->scopeMark;
-    JS_ASSERT(mark < n);
-    if (mark >= n)
-        return;
-
-    /* If v was not the last root pushed in the top scope, find it. */
-    if (top != v) {
-        /* Search downward in case v was recently pushed. */
-        i = n;
-        j = m;
-        lrc2 = lrc;
-        while (--i > mark) {
-            if (j == 0)
-                lrc2 = lrc2->down;
-            j = i & JSLRS_CHUNK_MASK;
-            if (lrc2->roots[j] == v)
-                break;
-        }
-
-        /* If we didn't find v in this scope, assert and bail out. */
-        JS_ASSERT(i != mark);
-        if (i == mark)
-            return;
-
-        /* Swap top and v so common tail code can pop v. */
-        lrc2->roots[j] = top;
-    }
-
-    /* Pop the last value from the stack. */
-    lrc->roots[m] = JSVAL_NULL;
-    lrs->rootCount = n;
-    if (m == 0) {
-        JS_ASSERT(n != 0);
-        JS_ASSERT(lrc != &lrs->firstChunk);
-        lrs->topChunk = lrc->down;
-        cx->free(lrc);
-    }
-}
-
-int
-js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v)
-{
-    uint32 n, m;
-    JSLocalRootChunk *lrc;
-
-    n = lrs->rootCount;
-    m = n & JSLRS_CHUNK_MASK;
-    if (n == 0 || m != 0) {
-        /*
-         * At start of first chunk, or not at start of a non-first top chunk.
-         * Check for lrs->rootCount overflow.
-         */
-        if ((uint32)(n + 1) == 0) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                                 JSMSG_TOO_MANY_LOCAL_ROOTS);
-            return -1;
-        }
-        lrc = lrs->topChunk;
-        JS_ASSERT(n != 0 || lrc == &lrs->firstChunk);
-    } else {
-        /*
-         * After lrs->firstChunk, trying to index at a power-of-two chunk
-         * boundary: need a new chunk.
-         */
-        lrc = (JSLocalRootChunk *) js_malloc(sizeof *lrc);
-        if (!lrc) {
-            js_ReportOutOfMemory(cx);
-            return -1;
-        }
-        lrc->down = lrs->topChunk;
-        lrs->topChunk = lrc;
-    }
-    lrs->rootCount = n + 1;
-    lrc->roots[m] = v;
-    return (int) n;
-}
-
-static void
-MarkLocalRoots(JSTracer *trc, JSLocalRootStack *lrs)
-{
-    uint32 n, m, mark;
-    JSLocalRootChunk *lrc;
-    jsval v;
-
-    n = lrs->rootCount;
-    if (n == 0)
-        return;
-
-    mark = lrs->scopeMark;
-    lrc = lrs->topChunk;
-    do {
-        while (--n > mark) {
-            m = n & JSLRS_CHUNK_MASK;
-            v = lrc->roots[m];
-            JS_ASSERT(JSVAL_IS_GCTHING(v) && v != JSVAL_NULL);
-            JS_SET_TRACING_INDEX(trc, "local_root", n);
-            js_CallValueTracerIfGCThing(trc, v);
-            if (m == 0)
-                lrc = lrc->down;
-        }
-        m = n & JSLRS_CHUNK_MASK;
-        mark = JSVAL_TO_INT(lrc->roots[m]);
-        if (m == 0)
-            lrc = lrc->down;
-    } while (n != 0);
-    JS_ASSERT(!lrc);
-}
-
 static void
 ReportError(JSContext *cx, const char *message, JSErrorReport *reportp,
             JSErrorCallback callback, void *userRef)
 {
     /*
      * Check the error report, and set a JavaScript-catchable exception
      * if the error is defined to have an associated exception.  If an
      * exception is thrown, then the JSREPORT_EXCEPTION flag will be set
@@ -2433,17 +2186,17 @@ JSContext::checkMallocGCPressure(void *p
 
             /*
              * Empty the GC free lists to trigger a last-ditch GC when any GC
              * thing is allocated later on this thread. This makes unnecessary
              * to check for the memory pressure on the fast path of the GC
              * allocator. We cannot touch the free lists on other threads as
              * their manipulation is not thread-safe.
              */
-            JS_THREAD_DATA(this)->purgeGCFreeLists();
+            JS_THREAD_DATA(this)->gcFreeLists.purge();
             js_TriggerGC(this, true);
         }
     }
 }
 
 bool
 JSContext::isConstructing()
 {
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -952,39 +952,16 @@ struct JSEvalCacheMeter {
 
 struct JSFunctionMeter {
     int32 FUNCTION_KIND_METER_LIST(identity);
 };
 
 # undef identity
 #endif
 
-struct JSLocalRootChunk;
-
-#define JSLRS_CHUNK_SHIFT       8
-#define JSLRS_CHUNK_SIZE        JS_BIT(JSLRS_CHUNK_SHIFT)
-#define JSLRS_CHUNK_MASK        JS_BITMASK(JSLRS_CHUNK_SHIFT)
-
-struct JSLocalRootChunk {
-    jsval               roots[JSLRS_CHUNK_SIZE];
-    JSLocalRootChunk    *down;
-};
-
-struct JSLocalRootStack {
-    uint32              scopeMark;
-    uint32              rootCount;
-    JSLocalRootChunk    *topChunk;
-    JSLocalRootChunk    firstChunk;
-
-    /* See comments in js_NewFinalizableGCThing. */
-    JSGCFreeLists       gcFreeLists;
-};
-
-const uint32 JSLRS_NULL_MARK = uint32(-1);
-
 #define NATIVE_ITER_CACHE_LOG2  8
 #define NATIVE_ITER_CACHE_MASK  JS_BITMASK(NATIVE_ITER_CACHE_LOG2)
 #define NATIVE_ITER_CACHE_SIZE  JS_BIT(NATIVE_ITER_CACHE_LOG2)
 
 struct JSPendingProxyOperation {
     JSPendingProxyOperation *next;
     JSObject *object;
 };
@@ -1006,19 +983,16 @@ struct JSThreadData {
      * The GSN cache is per thread since even multi-cx-per-thread embeddings
      * do not interleave js_GetSrcNote calls.
      */
     JSGSNCache          gsnCache;
 
     /* Property cache for faster call/get/set invocation. */
     js::PropertyCache   propertyCache;
 
-    /* Optional stack of heap-allocated scoped local GC roots. */
-    JSLocalRootStack    *localRootStack;
-
 #ifdef JS_TRACER
     /* Trace-tree JIT recorder/interpreter state. */
     js::TraceMonitor    traceMonitor;
 #endif
 
     /* Lock-free hashed lists of scripts created by eval to garbage-collect. */
     JSScript            *scriptsToGC[JS_EVAL_CACHE_SIZE];
 
@@ -1050,17 +1024,16 @@ struct JSThreadData {
     JSPendingProxyOperation *pendingProxyOperation;
 
     js::ConservativeGCThreadData conservativeGC;
 
     bool init();
     void finish();
     void mark(JSTracer *trc);
     void purge(JSContext *cx);
-    void purgeGCFreeLists();
 };
 
 #ifdef JS_THREADSAFE
 
 /*
  * Structure uniquely representing a thread.  It holds thread-private data
  * that can be accessed without a global lock.
  */
@@ -2763,39 +2736,16 @@ extern JSBool
 js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
                   JSResolvingEntry **entryp);
 
 extern void
 js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
                  JSResolvingEntry *entry, uint32 generation);
 
 /*
- * Local root set management.
- *
- * NB: the jsval parameters below may be properly tagged jsvals, or GC-thing
- * pointers cast to (jsval).  This relies on JSObject's tag being zero, but
- * on the up side it lets us push int-jsval-encoded scopeMark values on the
- * local root stack.
- */
-extern JSBool
-js_EnterLocalRootScope(JSContext *cx);
-
-#define js_LeaveLocalRootScope(cx) \
-    js_LeaveLocalRootScopeWithResult(cx, JSVAL_NULL)
-
-extern void
-js_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval);
-
-extern void
-js_ForgetLocalRoot(JSContext *cx, jsval v);
-
-extern int
-js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v);
-
-/*
  * Report an exception, which is currently realized as a printf-style format
  * string and its arguments.
  */
 typedef enum JSErrNum {
 #define MSG_DEF(name, number, count, exception, format) \
     name = number,
 #include "js.msg"
 #undef MSG_DEF
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1742,26 +1742,16 @@ IsGCThresholdReached(JSRuntime *rt)
     /*
      * Since the initial value of the gcLastBytes parameter is not equal to
      * zero (see the js_InitGC function) the return value is false when
      * the gcBytes value is close to zero at the JS engine start.
      */
     return rt->isGCMallocLimitReached() || rt->gcBytes >= rt->gcTriggerBytes;
 }
 
-static inline JSGCFreeLists *
-GetGCFreeLists(JSContext *cx)
-{
-    JSThreadData *td = JS_THREAD_DATA(cx);
-    if (!td->localRootStack)
-        return &td->gcFreeLists;
-    JS_ASSERT(td->gcFreeLists.isEmpty());
-    return &td->localRootStack->gcFreeLists;
-}
-
 static void
 LastDitchGC(JSContext *cx)
 {
     JS_ASSERT(!JS_ON_TRACE(cx));
 
     /* The last ditch GC preserves weak roots and all atoms. */
     AutoPreserveWeakRoots save(cx);
     AutoKeepAtoms keep(cx->runtime);
@@ -1774,17 +1764,17 @@ LastDitchGC(JSContext *cx)
      * operating systems. For the gory details, see bug 162779.
      */
     js_GC(cx, GC_LOCK_HELD);
 }
 
 static JSGCThing *
 RefillFinalizableFreeList(JSContext *cx, unsigned thingKind)
 {
-    JS_ASSERT(!GetGCFreeLists(cx)->finalizables[thingKind]);
+    JS_ASSERT(!JS_THREAD_DATA(cx)->gcFreeLists.finalizables[thingKind]);
     JSRuntime *rt = cx->runtime;
     JSGCArenaList *arenaList;
     JSGCArena *a;
 
     {
         AutoLockGC lock(rt);
         JS_ASSERT(!rt->gcRunning);
         if (rt->gcRunning) {
@@ -1801,17 +1791,17 @@ RefillFinalizableFreeList(JSContext *cx,
                 METER(cx->runtime->gcStats.arenaStats[thingKind].retry++);
                 canGC = false;
 
                 /*
                  * The JSGC_END callback can legitimately allocate new GC
                  * things and populate the free list. If that happens, just
                  * return that list head.
                  */
-                JSGCThing *freeList = GetGCFreeLists(cx)->finalizables[thingKind];
+                JSGCThing *freeList = JS_THREAD_DATA(cx)->gcFreeLists.finalizables[thingKind];
                 if (freeList)
                     return freeList;
             }
 
             while ((a = arenaList->cursor) != NULL) {
                 JSGCArenaInfo *ainfo = a->getInfo();
                 arenaList->cursor = ainfo->prev;
                 JSGCThing *freeList = ainfo->freeList;
@@ -1873,78 +1863,39 @@ js_NewFinalizableGCThing(JSContext *cx, 
 
     /* Updates of metering counters here may not be thread-safe. */
     METER(cx->runtime->gcStats.arenaStats[thingKind].alloc++);
 
     JSGCThing **freeListp =
         JS_THREAD_DATA(cx)->gcFreeLists.finalizables + thingKind;
     JSGCThing *thing = *freeListp;
     if (thing) {
-        JS_ASSERT(!JS_THREAD_DATA(cx)->localRootStack);
         *freeListp = thing->link;
         cx->weakRoots.finalizableNewborns[thingKind] = thing;
         CheckGCFreeListLink(thing);
         METER(cx->runtime->gcStats.arenaStats[thingKind].localalloc++);
         return thing;
     }
 
-    /*
-     * To avoid for the local roots on each GC allocation when the local roots
-     * are not active we move the GC free lists from JSThreadData to lrs in
-     * JS_EnterLocalRootScope(). This way with inactive local roots we only
-     * check for non-null lrs only when we exhaust the free list.
-     */
-    JSLocalRootStack *lrs = JS_THREAD_DATA(cx)->localRootStack;
-    for (;;) {
-        if (lrs) {
-            freeListp = lrs->gcFreeLists.finalizables + thingKind;
-            thing = *freeListp;
-            if (thing) {
-                *freeListp = thing->link;
-                METER(cx->runtime->gcStats.arenaStats[thingKind].localalloc++);
-                break;
-            }
-        }
-
-        thing = RefillFinalizableFreeList(cx, thingKind);
-        if (thing) {
-            /*
-             * See comments in RefillFinalizableFreeList about a possibility
-             * of *freeListp == thing.
-             */
-            JS_ASSERT(!*freeListp || *freeListp == thing);
-            *freeListp = thing->link;
-            break;
-        }
-
+    thing = RefillFinalizableFreeList(cx, thingKind);
+    if (!thing) {
         js_ReportOutOfMemory(cx);
         return NULL;
     }
 
+    /*
+     * See comments in RefillFinalizableFreeList about a possibility
+     * of *freeListp == thing.
+     */
+    JS_ASSERT(!*freeListp || *freeListp == thing);
+    *freeListp = thing->link;
+
     CheckGCFreeListLink(thing);
-    if (lrs) {
-        /*
-         * If we're in a local root scope, don't set newborn[type] at all, to
-         * avoid entraining garbage from it for an unbounded amount of time
-         * on this context.  A caller will leave the local root scope and pop
-         * this reference, allowing thing to be GC'd if it has no other refs.
-         * See JS_EnterLocalRootScope and related APIs.
-         */
-        if (js_PushLocalRoot(cx, lrs, (jsval) thing) < 0) {
-            JS_ASSERT(thing->link == *freeListp);
-            *freeListp = thing;
-            return NULL;
-        }
-    } else {
-        /*
-         * No local root scope, so we're stuck with the old, fragile model of
-         * depending on a pigeon-hole newborn per type per context.
-         */
-        cx->weakRoots.finalizableNewborns[thingKind] = thing;
-    }
+
+    cx->weakRoots.finalizableNewborns[thingKind] = thing;
 
     return thing;
 }
 
 static JSGCThing *
 TurnUsedArenaIntoDoubleList(JSGCArena *a)
 {
     JSGCThing *head;
@@ -1996,34 +1947,34 @@ TurnUsedArenaIntoDoubleList(JSGCArena *a
     }
     *tailp = NULL;
     return head;
 }
 
 static JSGCThing *
 RefillDoubleFreeList(JSContext *cx)
 {
-    JS_ASSERT(!GetGCFreeLists(cx)->doubles);
+    JS_ASSERT(!JS_THREAD_DATA(cx)->gcFreeLists.doubles);
 
     JSRuntime *rt = cx->runtime;
     JS_ASSERT(!rt->gcRunning);
 
     JS_LOCK_GC(rt);
 
     bool canGC = !JS_ON_TRACE(cx) && !JS_THREAD_DATA(cx)->waiveGCQuota;
     bool doGC = canGC && IsGCThresholdReached(rt);
     JSGCArena *a;
     for (;;) {
         if (doGC) {
             LastDitchGC(cx);
             METER(rt->gcStats.doubleArenaStats.retry++);
             canGC = false;
 
             /* See comments in RefillFinalizableFreeList. */
-            JSGCThing *freeList = GetGCFreeLists(cx)->doubles;
+            JSGCThing *freeList = JS_THREAD_DATA(cx)->gcFreeLists.doubles;
             if (freeList) {
                 JS_UNLOCK_GC(rt);
                 return freeList;
             }
         }
 
         /*
          * Loop until we find arena with some free doubles. We turn arenas
@@ -2064,57 +2015,44 @@ js_NewDoubleInRootedValue(JSContext *cx,
 {
     /* Updates of metering counters here are not thread-safe. */
     METER(cx->runtime->gcStats.doubleArenaStats.alloc++);
 
     JSGCThing **freeListp = &JS_THREAD_DATA(cx)->gcFreeLists.doubles;
     JSGCThing *thing = *freeListp;
     if (thing) {
         METER(cx->runtime->gcStats.doubleArenaStats.localalloc++);
-        JS_ASSERT(!JS_THREAD_DATA(cx)->localRootStack);
         CheckGCFreeListLink(thing);
         *freeListp = thing->link;
 
         jsdouble *dp = &thing->asDouble;
         *dp = d;
         *vp = DOUBLE_TO_JSVAL(dp);
         return true;
     }
 
-    JSLocalRootStack *lrs = JS_THREAD_DATA(cx)->localRootStack;
-    for (;;) {
-        if (lrs) {
-            freeListp = &lrs->gcFreeLists.doubles;
-            thing = *freeListp;
-            if (thing) {
-                METER(cx->runtime->gcStats.doubleArenaStats.localalloc++);
-                break;
-            }
-        }
-        thing = RefillDoubleFreeList(cx);
-        if (thing) {
-            JS_ASSERT(!*freeListp || *freeListp == thing);
-            break;
-        }
-
+    thing = RefillDoubleFreeList(cx);
+    if (!thing) {
         if (!JS_ON_TRACE(cx)) {
             /* Trace code handle this on its own. */
             js_ReportOutOfMemory(cx);
             METER(cx->runtime->gcStats.doubleArenaStats.fail++);
         }
         return false;
     }
 
+    JS_ASSERT(!*freeListp || *freeListp == thing);
+
     CheckGCFreeListLink(thing);
     *freeListp = thing->link;
 
     jsdouble *dp = reinterpret_cast<jsdouble *>(thing);
     *dp = d;
     *vp = DOUBLE_TO_JSVAL(dp);
-    return !lrs || js_PushLocalRoot(cx, lrs, *vp) >= 0;
+    return true;
 }
 
 jsdouble *
 js_NewWeaklyRootedDouble(JSContext *cx, jsdouble d)
 {
     jsval v;
     if (!js_NewDoubleInRootedValue(cx, d, &v))
         return NULL;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -691,32 +691,24 @@ js_InternalInvoke(JSContext *cx, JSObjec
     args.getvp()[0] = fval;
     args.getvp()[1] = OBJECT_TO_JSVAL(obj);
     memcpy(args.getvp() + 2, argv, argc * sizeof(jsval));
 
     if (!js_Invoke(cx, args, flags))
         return JS_FALSE;
 
     /*
-     * Store *rval in the a scoped local root if a scope is open, else in
-     * the lastInternalResult pigeon-hole GC root, solely so users of
-     * js_InternalInvoke and its direct and indirect (js_ValueToString for
-     * example) callers do not need to manage roots for local, temporary
-     * references to such results.
+     * Store *rval in the lastInternalResult pigeon-hole GC root, solely
+     * so users of js_InternalInvoke and its direct and indirect
+     * (js_ValueToString for example) callers do not need to manage roots
+     * for local, temporary references to such results.
      */
     *rval = *args.getvp();
-    if (JSVAL_IS_GCTHING(*rval) && *rval != JSVAL_NULL) {
-        JSLocalRootStack *lrs = JS_THREAD_DATA(cx)->localRootStack;
-        if (lrs) {
-            if (js_PushLocalRoot(cx, lrs, *rval) < 0)
-                return JS_FALSE;
-        } else {
-            cx->weakRoots.lastInternalResult = *rval;
-        }
-    }
+    if (JSVAL_IS_GCTHING(*rval) && *rval != JSVAL_NULL)
+        cx->weakRoots.lastInternalResult = *rval;
 
     return JS_TRUE;
 }
 
 JSBool
 js_InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, jsval fval,
                     JSAccessMode mode, uintN argc, jsval *argv, jsval *rval)
 {
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -86,16 +86,32 @@ using namespace js;
  *
  * TODO
  * - XXXbe patrol
  * - Fuse objects and their JSXML* private data into single GC-things
  * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
  * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
  */
 
+static inline bool
+js_EnterLocalRootScope(JSContext *cx)
+{
+    return true;
+}
+
+static inline void
+js_LeaveLocalRootScope(JSContext *cx)
+{
+}
+
+static inline void
+js_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval)
+{
+}
+
 #ifdef XML_METERING
 static struct {
     jsrefcount  qname;
     jsrefcount  xmlnamespace;
     jsrefcount  xml;
     jsrefcount  xmlobj;
 } xml_stats;
 
@@ -2939,18 +2955,16 @@ DeepCopy(JSContext *cx, JSXML *xml, JSOb
 static JSBool
 DeepCopySetInLRS(JSContext *cx, JSXMLArray *from, JSXMLArray *to, JSXML *parent,
                  uintN flags)
 {
     uint32 j, n;
     JSXML *kid2;
     JSString *str;
 
-    JS_ASSERT(JS_THREAD_DATA(cx)->localRootStack);
-
     n = from->length;
     if (!to->setCapacity(cx, n))
         return JS_FALSE;
 
     JSXMLArrayCursor cursor(from);
     j = 0;
     while (JSXML *kid = (JSXML *) cursor.getNext()) {
         if ((flags & XSF_IGNORE_COMMENTS) &&
@@ -2996,19 +3010,16 @@ static JSXML *
 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
 {
     JSXML *copy;
     JSObject *qn;
     JSBool ok;
     uint32 i, n;
     JSObject *ns, *ns2;
 
-    /* Our caller must be protecting newborn objects. */
-    JS_ASSERT(JS_THREAD_DATA(cx)->localRootStack);
-
     JS_CHECK_RECURSION(cx, return NULL);
 
     copy = js_NewXML(cx, JSXMLClass(xml->xml_class));
     if (!copy)
         return NULL;
     qn = xml->name;
     if (qn) {
         qn = NewXMLQName(cx, GetURI(qn), GetPrefix(qn), GetLocalName(qn));
@@ -4375,19 +4386,16 @@ bad:
 /* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
 static JSBool
 ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
 {
     JSXML *target, *base;
     JSObject *targetprop;
     jsval id, tv;
 
-    /* Our caller must be protecting newborn objects. */
-    JS_ASSERT(JS_THREAD_DATA(cx)->localRootStack);
-
     if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
         if (!js_GetXMLObject(cx, list))
             return JS_FALSE;
         *result = list;
         return JS_TRUE;
     }
 
     target = list->xml_target;