Bug 888338 - 1 - Add TenuredHeap<T> class r=terrence r=bz
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 23 Jul 2013 10:58:26 +0100
changeset 139576 a80f35bdbaa0e86fb4e758176a683c57cc902993
parent 139554 1ede6adb61d65f8210dbefb0e6247b702c9e70a9
child 139577 03f30c353078636ea84409ba42c3cc041d898427
push id24998
push userryanvm@gmail.com
push dateWed, 24 Jul 2013 00:38:35 +0000
treeherdermozilla-central@2983ca6d4d1a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence, bz
bugs888338
milestone25.0a1
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 888338 - 1 - Add TenuredHeap<T> class r=terrence r=bz
content/base/src/nsContentUtils.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
js/public/RootingAPI.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsgc.cpp
js/src/jsgc.h
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/src/xpcprivate.h
xpcom/base/CycleCollectedJSRuntime.cpp
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -1645,16 +1645,17 @@ nsContentUtils::GetContextFromDocument(n
 void
 nsContentUtils::TraceSafeJSContext(JSTracer* aTrc)
 {
   JSContext* cx = GetSafeJSContext();
   if (!cx) {
     return;
   }
   if (JSObject* global = js::GetDefaultGlobalForContext(cx)) {
+    JS::AssertGCThingMustBeTenured(global);
     JS_CallObjectTracer(aTrc, &global, "safe context");
     MOZ_ASSERT(global == js::GetDefaultGlobalForContext(cx));
   }
 }
 
 nsPIDOMWindow *
 nsContentUtils::GetWindowFromCaller()
 {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1022,17 +1022,16 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
 #ifdef MOZ_GAMEPAD
     mHasSeenGamepadInput(false),
 #endif
     mNotifiedIDDestroyed(false),
     mAllowScriptsToClose(false),
     mTimeoutInsertionPoint(nullptr),
     mTimeoutPublicIdCounter(1),
     mTimeoutFiringDepth(0),
-    mJSObject(nullptr),
     mTimeoutsSuspendDepth(0),
     mFocusMethod(0),
     mSerial(0),
 #ifdef DEBUG
     mSetOpenerWindowCalled(false),
 #endif
     mCleanedUp(false),
     mCallCleanUpAfterModalDialogCloses(false),
@@ -1860,17 +1859,17 @@ nsGlobalWindow::GetGlobalJSObject()
 {
   return FastGetGlobalJSObject();
 }
 
 void
 nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc)
 {
   if (mJSObject) {
-    JS_CallObjectTracer(aTrc, &mJSObject, "active window global");
+    JS_CallTenuredObjectTracer(aTrc, &mJSObject, "active window global");
   }
 }
 
 bool
 nsGlobalWindow::WouldReuseInnerWindow(nsIDocument *aNewDocument)
 {
   // We reuse the inner window when:
   // a. We are currently at our original document.
@@ -2112,24 +2111,23 @@ TreatAsRemoteXUL(nsIPrincipal* aPrincipa
  * Return the native global and an nsISupports 'holder' that can be used
  * to manage the lifetime of it.
  */
 static nsresult
 CreateNativeGlobalForInner(JSContext* aCx,
                            nsGlobalWindow* aNewInner,
                            nsIURI* aURI,
                            nsIPrincipal* aPrincipal,
-                           JSObject** aNativeGlobal,
+                           JS::TenuredHeap<JSObject*>& aNativeGlobal,
                            nsIXPConnectJSObjectHolder** aHolder)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aNewInner);
   MOZ_ASSERT(aNewInner->IsInnerWindow());
   MOZ_ASSERT(aPrincipal);
-  MOZ_ASSERT(aNativeGlobal);
   MOZ_ASSERT(aHolder);
 
   nsGlobalWindow *top = NULL;
   if (aNewInner->GetOuterWindow()) {
     top = aNewInner->GetTop();
   }
   JS::CompartmentOptions options;
   if (top) {
@@ -2149,23 +2147,23 @@ CreateNativeGlobalForInner(JSContext* aC
 
   nsRefPtr<nsIXPConnectJSObjectHolder> jsholder;
   nsresult rv = xpc->InitClassesWithNewWrappedGlobal(
     aCx, ToSupports(aNewInner),
     aPrincipal, flags, options, getter_AddRefs(jsholder));
   NS_ENSURE_SUCCESS(rv, rv);
 
   MOZ_ASSERT(jsholder);
-  *aNativeGlobal = jsholder->GetJSObject();
+  aNativeGlobal = jsholder->GetJSObject();
   jsholder.forget(aHolder);
 
   // Set the location information for the new global, so that tools like
   // about:memory may use that information
-  MOZ_ASSERT(*aNativeGlobal);
-  xpc::SetLocationForGlobal(*aNativeGlobal, aURI);
+  MOZ_ASSERT(aNativeGlobal.getPtr());
+  xpc::SetLocationForGlobal(aNativeGlobal, aURI);
 
   return NS_OK;
 }
 
 nsresult
 nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
                                nsISupports* aState,
                                bool aForceReuseInnerWindow)
@@ -2348,17 +2346,17 @@ nsGlobalWindow::SetNewDocument(nsIDocume
 
       Freeze();
       mCreatingInnerWindow = true;
       // Every script context we are initialized with must create a
       // new global.
       rv = CreateNativeGlobalForInner(cx, newInnerWindow,
                                       aDocument->GetDocumentURI(),
                                       aDocument->NodePrincipal(),
-                                      &newInnerWindow->mJSObject,
+                                      newInnerWindow->mJSObject,
                                       getter_AddRefs(mInnerWindowHolder));
       NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerWindow->mJSObject && mInnerWindowHolder,
                    "Failed to get script global and holder");
 
       mCreatingInnerWindow = false;
       createdInnerWindow = true;
       Thaw();
 
@@ -3148,17 +3146,17 @@ nsGlobalWindow::OnFinalize(JSObject* aOb
   }
 }
 
 void
 nsGlobalWindow::PoisonOuterWindowProxy(JSObject *aObject)
 {
   MOZ_ASSERT(IsOuterWindow());
   if (aObject == mJSObject) {
-    mJSObject = reinterpret_cast<JSObject*>(0x1);
+    mJSObject.setToCrashOnTouch();
   }
 }
 
 void
 nsGlobalWindow::SetScriptsEnabled(bool aEnabled, bool aFireTimeouts)
 {
   FORWARD_TO_INNER_VOID(SetScriptsEnabled, (aEnabled, aFireTimeouts));
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1220,17 +1220,19 @@ protected:
   nsTimeout*                    mTimeoutInsertionPoint;
   uint32_t                      mTimeoutPublicIdCounter;
   uint32_t                      mTimeoutFiringDepth;
   nsRefPtr<nsLocation>          mLocation;
   nsRefPtr<nsHistory>           mHistory;
 
   // These member variables are used on both inner and the outer windows.
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
-  JSObject* mJSObject;
+
+  // The JS global object.  Global objects are always allocated tenured.
+  JS::TenuredHeap<JSObject*> mJSObject;
 
   typedef nsCOMArray<nsIDOMStorageEvent> nsDOMStorageEventArray;
   nsDOMStorageEventArray mPendingStorageEvents;
 
   uint32_t mTimeoutsSuspendDepth;
 
   // the method that was used to focus mFocusedNode
   uint32_t mFocusMethod;
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -164,31 +164,29 @@ CheckStackRoots(JSContext *cx);
  * which avoids creating a Rooted<JSObject*> just to pass NULL.
  */
 struct JS_PUBLIC_API(NullPtr)
 {
     static void * const constNullValue;
 };
 
 /*
- * An encapsulated pointer class for heap based GC thing pointers.
- *
- * This implements post-barriers for GC thing pointers stored on the heap. It is
- * designed to be used for all heap-based GC thing pointers outside the JS
- * engine.
+ * The Heap<T> class is a C/C++ heap-stored reference to a JS GC thing.  All
+ * members of heap classes that refer to GC thing should use Heap<T> (or
+ * possibly TenuredHeap<T>, described below).
  *
- * The template parameter T must be a JS GC thing pointer, masked pointer or
- * possible pointer, such as a JS::Value or jsid.
+ * Heap<T> wraps the complex mechanisms required to ensure GC safety for the
+ * contained reference into a C++ class that behaves similarly to a normal
+ * pointer.
  *
- * The class must be used to declare data members of heap classes only.
- * Stack-based GC thing pointers should used Rooted<T>.
+ * GC references stored on the C/C++ stack must use Rooted/Handle/MutableHandle
+ * instead.
  *
- * Write barriers are implemented by overloading the assingment operator.
- * Assiging to a Heap<T> triggers the appropriate calls into the GC to notify it
- * of the change.
+ * Requirements for type T:
+ *  - Must be one of: Value, jsid, JSObject*, JSString*, JSScript*
  */
 template <typename T>
 class Heap : public js::HeapBase<T>
 {
   public:
     Heap() {
         MOZ_STATIC_ASSERT(sizeof(T) == sizeof(Heap<T>),
                           "Heap<T> must be binary compatible with T.");
@@ -252,16 +250,127 @@ class Heap : public js::HeapBase<T>
 #ifdef JSGC_GENERATIONAL
         js::GCMethods<T>::relocate(&ptr);
 #endif
     }
 
     T ptr;
 };
 
+#ifdef DEBUG
+/*
+ * For generational GC, assert that an object is in the tenured generation as
+ * opposed to being in the nursery.
+ */
+extern JS_FRIEND_API(void)
+AssertGCThingMustBeTenured(JSObject* obj);
+#else
+inline void
+AssertGCThingMustBeTenured(JSObject *obj) {}
+#endif
+
+/*
+ * The TenuredHeap<T> class is similar to the Heap<T> class above in that it
+ * encapsulates the GC concerns of an on-heap reference to a JS object. However,
+ * it has two important differences:
+ *
+ *  1) Pointers which are statically known to only reference "tenured" objects
+ *     can avoid the extra overhead of SpiderMonkey's write barriers.
+ *
+ *  2) Objects in the "tenured" heap have stronger alignment restrictions than
+ *     those in the "nursery", so it is possible to store flags in the lower
+ *     bits of pointers known to be tenured. TenuredHeap wraps a normal tagged
+ *     pointer with a nice API for accessing the flag bits and adds various
+ *     assertions to ensure that it is not mis-used.
+ *
+ * GC things are said to be "tenured" when they are located in the long-lived
+ * heap: e.g. they have gained tenure as an object by surviving past at least
+ * one GC. For performance, SpiderMonkey allocates some things which are known
+ * to normally be long lived directly into the tenured generation; for example,
+ * global objects. Additionally, SpiderMonkey does not visit individual objects
+ * when deleting non-tenured objects, so object with finalizers are also always
+ * tenured; for instance, this includes most DOM objects.
+ *
+ * The considerations to keep in mind when using a TenuredHeap<T> vs a normal
+ * Heap<T> are:
+ *
+ *  - It is invalid for a TenuredHeap<T> to refer to a non-tenured thing.
+ *  - It is however valid for a Heap<T> to refer to a tenured thing.
+ *  - It is not possible to store flag bits in a Heap<T>.
+ */
+template <typename T>
+class TenuredHeap : public js::HeapBase<T>
+{
+  public:
+    TenuredHeap() : bits(0) {
+        MOZ_STATIC_ASSERT(sizeof(T) == sizeof(TenuredHeap<T>),
+                          "TenuredHeap<T> must be binary compatible with T.");
+    }
+    explicit TenuredHeap(T p) : bits(0) { setPtr(p); }
+    explicit TenuredHeap(const TenuredHeap<T> &p) : bits(0) { setPtr(p.ptr); }
+
+    bool operator==(const TenuredHeap<T> &other) { return bits == other.bits; }
+    bool operator!=(const TenuredHeap<T> &other) { return bits != other.bits; }
+
+    void setPtr(T newPtr) {
+        JS_ASSERT((reinterpret_cast<uintptr_t>(newPtr) & flagsMask) == 0);
+        JS_ASSERT(!js::GCMethods<T>::poisoned(newPtr));
+        if (newPtr)
+            AssertGCThingMustBeTenured(newPtr);
+        bits = (bits & flagsMask) | reinterpret_cast<uintptr_t>(newPtr);
+    }
+
+    void setFlags(uintptr_t flagsToSet) {
+        JS_ASSERT((flagsToSet & ~flagsMask) == 0);
+        bits |= flagsToSet;
+    }
+
+    void unsetFlags(uintptr_t flagsToUnset) {
+        JS_ASSERT((flagsToUnset & ~flagsMask) == 0);
+        bits &= ~flagsToUnset;
+    }
+
+    bool hasFlag(uintptr_t flag) const {
+        JS_ASSERT((flag & ~flagsMask) == 0);
+        return (bits & flag) != 0;
+    }
+
+    T getPtr() const { return reinterpret_cast<T>(bits & ~flagsMask); }
+    uintptr_t getFlags() const { return bits & flagsMask; }
+
+    operator T() const { return getPtr(); }
+    T operator->() const { return getPtr(); }
+
+    TenuredHeap<T> &operator=(T p) {
+        setPtr(p);
+        return *this;
+    }
+
+    /*
+     * Set the pointer to a value which will cause a crash if it is
+     * dereferenced.
+     */
+    void setToCrashOnTouch() {
+        bits = (bits & flagsMask) | crashOnTouchPointer;
+    }
+
+    bool isSetToCrashOnTouch() {
+        return (bits & ~flagsMask) == crashOnTouchPointer;
+    }
+
+  private:
+    enum {
+        maskBits = 3,
+        flagsMask = (1 << maskBits) - 1,
+        crashOnTouchPointer = 1 << maskBits
+    };
+
+    uintptr_t bits;
+};
+
 /*
  * Reference to a T that has been rooted elsewhere. This is most useful
  * as a parameter type, which guarantees that the T lvalue is properly
  * rooted. See "Move GC Stack Rooting" above.
  *
  * If you want to add additional methods to Handle for a specific
  * specialization, define a HandleBase<T> specialization containing them.
  */
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2357,27 +2357,26 @@ JS_CallIdTracer(JSTracer *trc, jsid *idp
 
 JS_PUBLIC_API(void)
 JS_CallObjectTracer(JSTracer *trc, JSObject **objp, const char *name)
 {
     MarkObjectUnbarriered(trc, objp, name);
 }
 
 JS_PUBLIC_API(void)
-JS_CallMaskedObjectTracer(JSTracer *trc, uintptr_t *objp, uintptr_t flagMask, const char *name)
-{
-    uintptr_t flags = *objp & flagMask;
-    JSObject *obj = reinterpret_cast<JSObject *>(*objp & ~flagMask);
+JS_CallTenuredObjectTracer(JSTracer *trc, JS::TenuredHeap<JSObject *> *objp, const char *name)
+{
+    JSObject *obj = objp->getPtr();
     if (!obj)
         return;
 
     JS_SET_TRACING_LOCATION(trc, (void*)objp);
     MarkObjectUnbarriered(trc, &obj, name);
 
-    *objp = uintptr_t(obj) | flags;
+    objp->setPtr(obj);
 }
 
 JS_PUBLIC_API(void)
 JS_CallStringTracer(JSTracer *trc, JSString **strp, const char *name)
 {
     MarkStringUnbarriered(trc, strp, name);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2581,23 +2581,21 @@ JS_CallHashSetObjectTracer(JSTracer *trc
     JSObject *updated = key;
     JS_SET_TRACING_LOCATION(trc, reinterpret_cast<void *>(&const_cast<JSObject *&>(key)));
     JS_CallObjectTracer(trc, &updated, name);
     if (updated != key)
         e.rekeyFront(key, updated);
 }
 
 /*
- * The JS_CallMaskedObjectTracer variant traces a JSObject* that is stored
- * with flags embedded in the low bits of the word. The flagMask parameter
- * expects |*objp & flagMask| to yield the flags with the pointer value
- * stripped and |*objp & ~flagMask| to yield a valid GC pointer.
+ * Trace an object that is known to always be tenured.  No post barriers are
+ * required in this case.
  */
 extern JS_PUBLIC_API(void)
-JS_CallMaskedObjectTracer(JSTracer *trc, uintptr_t *objp, uintptr_t flagMask, const char *name);
+JS_CallTenuredObjectTracer(JSTracer *trc, JS::TenuredHeap<JSObject *> *objp, const char *name);
 
 /*
  * API for JSTraceCallback implementations.
  */
 extern JS_PUBLIC_API(void)
 JS_TracerInit(JSTracer *trc, JSRuntime *rt, JSTraceCallback callback);
 
 extern JS_PUBLIC_API(void)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -5102,9 +5102,16 @@ js::UninlinedIsInsideNursery(JSRuntime *
 #ifdef DEBUG
 AutoDisableProxyCheck::AutoDisableProxyCheck(JSRuntime *rt
                                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : count(rt->gcDisableStrictProxyCheckingCount)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     count++;
 }
+
+JS_FRIEND_API(void)
+JS::AssertGCThingMustBeTenured(JSObject *obj)
+{
+    JS_ASSERT((!IsNurseryAllocable(obj->tenuredGetAllocKind()) || obj->getClass()->finalize) &&
+              obj->isTenured());
+}
 #endif
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -146,17 +146,17 @@ template <> struct MapTypeToTraceKind<Un
 template <> struct MapTypeToTraceKind<types::TypeObject>{ const static JSGCTraceKind kind = JSTRACE_TYPE_OBJECT; };
 template <> struct MapTypeToTraceKind<JSAtom>           { const static JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<JSString>         { const static JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<JSFlatString>     { const static JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<JSLinearString>   { const static JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<PropertyName>     { const static JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<ion::IonCode>     { const static JSGCTraceKind kind = JSTRACE_IONCODE; };
 
-#ifdef JSGC_GENERATIONAL
+#if defined(JSGC_GENERATIONAL) || defined(DEBUG)
 static inline bool
 IsNurseryAllocable(AllocKind kind)
 {
     JS_ASSERT(kind >= 0 && unsigned(kind) < FINALIZE_LIMIT);
     static const bool map[] = {
         false,     /* FINALIZE_OBJECT0 */
         true,      /* FINALIZE_OBJECT0_BACKGROUND */
         false,     /* FINALIZE_OBJECT2 */
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -362,16 +362,17 @@ XPCWrappedNative::WrapNewGlobal(xpcObjec
         wrapper->mScriptableInfo = siProto;
         delete si;
     } else {
         wrapper->mScriptableInfo = si;
     }
 
     // Set the JS object to the global we already created.
     wrapper->mFlatJSObject = global;
+    wrapper->mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
 
     // Set the private to the XPCWrappedNative.
     JS_SetPrivate(global, wrapper);
 
     // There are dire comments elsewhere in the code about how a GC can
     // happen somewhere after wrapper initialization but before the wrapper is
     // added to the hashtable in FinishCreate(). It's not clear if that can
     // happen here, but let's just be safe for now.
@@ -752,56 +753,52 @@ XPCWrappedNative::GetUsedOnly(nsISupport
     return NS_OK;
 }
 
 // This ctor is used if this object will have a proto.
 XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports> aIdentity,
                                    XPCWrappedNativeProto* aProto)
     : mMaybeProto(aProto),
       mSet(aProto->GetSet()),
-      mFlatJSObject(INVALID_OBJECT), // non-null to pass IsValid() test
-      mScriptableInfo(nullptr),
-      mWrapperWord(0)
+      mScriptableInfo(nullptr)
 {
     mIdentity = aIdentity.get();
+    mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
 
     NS_ASSERTION(mMaybeProto, "bad ctor param");
     NS_ASSERTION(mSet, "bad ctor param");
 
     DEBUG_TrackNewWrapper(this);
 }
 
 // This ctor is used if this object will NOT have a proto.
 XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports> aIdentity,
                                    XPCWrappedNativeScope* aScope,
                                    XPCNativeSet* aSet)
 
     : mMaybeScope(TagScope(aScope)),
       mSet(aSet),
-      mFlatJSObject(INVALID_OBJECT), // non-null to pass IsValid() test
-      mScriptableInfo(nullptr),
-      mWrapperWord(0)
+      mScriptableInfo(nullptr)
 {
     mIdentity = aIdentity.get();
+    mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
 
     NS_ASSERTION(aScope, "bad ctor param");
     NS_ASSERTION(aSet, "bad ctor param");
 
     DEBUG_TrackNewWrapper(this);
 }
 
 XPCWrappedNative::~XPCWrappedNative()
 {
     DEBUG_TrackDeleteWrapper(this);
 
     Destroy();
 }
 
-static const intptr_t WRAPPER_WORD_POISON = 0xa8a8a8a8;
-
 void
 XPCWrappedNative::Destroy()
 {
     XPCWrappedNativeProto* proto = GetProto();
 
     if (mScriptableInfo &&
         (!HasProto() ||
          (proto && proto->GetScriptableInfo() != mScriptableInfo))) {
@@ -830,24 +827,24 @@ XPCWrappedNative::Destroy()
             NS_RELEASE(mIdentity);
         }
     }
 
     /*
      * The only time GetRuntime() will be NULL is if Destroy is called a second
      * time on a wrapped native. Since we already unregistered the pointer the
      * first time, there's no need to unregister again. Unregistration is safe
-     * the first time because mWrapperWord isn't used afterwards.
+     * the first time because mWrapper isn't used afterwards.
      */
     if (XPCJSRuntime *rt = GetRuntime()) {
         if (IsIncrementalBarrierNeeded(rt->Runtime()))
             IncrementalObjectBarrier(GetWrapperPreserveColor());
-        mWrapperWord = WRAPPER_WORD_POISON;
+        mWrapper.setToCrashOnTouch();
     } else {
-        MOZ_ASSERT(mWrapperWord == WRAPPER_WORD_POISON);
+        MOZ_ASSERT(mWrapper.isSetToCrashOnTouch());
     }
 
     mMaybeScope = nullptr;
 }
 
 void
 XPCWrappedNative::UpdateScriptableInfo(XPCNativeScriptableInfo *si)
 {
@@ -1028,19 +1025,22 @@ XPCWrappedNative::Init(HandleObject pare
     JSObject* protoJSObject = HasProto() ?
                                 GetProto()->GetJSProtoObject() :
                                 JS_GetObjectPrototype(cx, parent);
     if (!protoJSObject) {
         return false;
     }
 
     mFlatJSObject = JS_NewObject(cx, jsclazz, protoJSObject, parent);
-    if (!mFlatJSObject)
+    if (!mFlatJSObject) {
+        mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID);
         return false;
-
+    }
+
+    mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
     JS_SetPrivate(mFlatJSObject, this);
 
     return FinishInit();
 }
 
 JSBool
 XPCWrappedNative::FinishInit()
 {
@@ -1174,18 +1174,18 @@ XPCWrappedNative::FlatJSObjectFinalized(
         }
     }
 
     nsWrapperCache *cache = nullptr;
     CallQueryInterface(mIdentity, &cache);
     if (cache)
         cache->ClearWrapper();
 
-    // This makes IsValid return false from now on...
     mFlatJSObject = nullptr;
+    mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID);
 
     NS_ASSERTION(mIdentity, "bad pointer!");
 #ifdef XP_WIN
     // Try to detect free'd pointer
     NS_ASSERTION(*(int*)mIdentity != 0xdddddddd, "bad pointer!");
     NS_ASSERTION(*(int*)mIdentity != 0,          "bad pointer!");
 #endif
 
@@ -1222,17 +1222,18 @@ XPCWrappedNative::SystemIsBeingShutDown(
     // The long standing strategy is to leak some objects still held at shutdown.
     // The general problem is that propagating release out of xpconnect at
     // shutdown time causes a world of problems.
 
     // We leak mIdentity (see above).
 
     // short circuit future finalization
     JS_SetPrivate(mFlatJSObject, nullptr);
-    mFlatJSObject = nullptr; // This makes 'IsValid()' return false.
+    mFlatJSObject = nullptr;
+    mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID);
 
     XPCWrappedNativeProto* proto = GetProto();
 
     if (HasProto())
         proto->SystemIsBeingShutDown();
 
     if (mScriptableInfo &&
         (!HasProto() ||
@@ -1467,17 +1468,20 @@ XPCWrappedNative::ReparentWrapperIfFound
             flat = newobj;
             wrapper->SetWrapper(ww);
         } else {
             flat = xpc::TransplantObject(cx, flat, newobj);
             if (!flat)
                 MOZ_CRASH();
         }
 
+        MOZ_ASSERT(flat);
         wrapper->mFlatJSObject = flat;
+        wrapper->mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
+
         if (cache) {
             bool preserving = cache->PreservingWrapper();
             cache->SetPreservingWrapper(false);
             cache->SetWrapper(flat);
             cache->SetPreservingWrapper(preserving);
         }
         if (!JS_CopyPropertiesFrom(cx, flat, propertyHolder))
             MOZ_CRASH();
@@ -2929,17 +2933,17 @@ NS_IMETHODIMP XPCWrappedNative::DebugDum
         } else
             XPC_LOG_ALWAYS(("Scope @ %x", GetScope()));
 
         if (depth && mSet)
             mSet->DebugDump(depth);
         else
             XPC_LOG_ALWAYS(("mSet @ %x", mSet));
 
-        XPC_LOG_ALWAYS(("mFlatJSObject of %x", mFlatJSObject));
+        XPC_LOG_ALWAYS(("mFlatJSObject of %x", mFlatJSObject.getPtr()));
         XPC_LOG_ALWAYS(("mIdentity of %x", mIdentity));
         XPC_LOG_ALWAYS(("mScriptableInfo @ %x", mScriptableInfo));
 
         if (depth && mScriptableInfo) {
             XPC_LOG_INDENT();
             XPC_LOG_ALWAYS(("mScriptable @ %x", mScriptableInfo->GetCallback()));
             XPC_LOG_ALWAYS(("mFlags of %x", (uint32_t)mScriptableInfo->GetFlags()));
             XPC_LOG_ALWAYS(("mJSClass @ %x", mScriptableInfo->GetJSClass()));
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1380,16 +1380,18 @@ public:
     XPCContext *GetContext() { return mContext; }
     void ClearContext() { mContext = nullptr; }
 
     typedef js::HashSet<JSObject *,
                         js::PointerHasher<JSObject *, 3>,
                         js::SystemAllocPolicy> DOMExpandoSet;
 
     bool RegisterDOMExpandoObject(JSObject *expando) {
+        // Expandos are proxy objects, and proxies are always tenured.
+        JS::AssertGCThingMustBeTenured(expando);
         if (!mDOMExpandoSet) {
             mDOMExpandoSet = new DOMExpandoSet();
             mDOMExpandoSet->init(8);
         }
         return mDOMExpandoSet->put(expando);
     }
     void RemoveDOMExpandoObject(JSObject *expando) {
         if (mDOMExpandoSet)
@@ -2254,17 +2256,17 @@ public:
                                  &participant);
       }
     };
     void DeleteCycleCollectable() {}
 
     nsIPrincipal* GetObjectPrincipal() const;
 
     JSBool
-    IsValid() const {return nullptr != mFlatJSObject;}
+    IsValid() const { return mFlatJSObject.hasFlag(FLAT_JS_OBJECT_VALID); }
 
 #define XPC_SCOPE_WORD(s)   (intptr_t(s))
 #define XPC_SCOPE_MASK      (intptr_t(0x3))
 #define XPC_SCOPE_TAG       (intptr_t(0x1))
 #define XPC_WRAPPER_EXPIRED (intptr_t(0x2))
 
     static inline JSBool
     IsTaggedScope(XPCWrappedNativeScope* s)
@@ -2304,18 +2306,17 @@ public:
     GetIdentityObject() const {return mIdentity;}
 
     /**
      * This getter clears the gray bit before handing out the JSObject which
      * means that the object is guaranteed to be kept alive past the next CC.
      */
     JSObject*
     GetFlatJSObject() const
-        {if (mFlatJSObject != INVALID_OBJECT)
-             xpc_UnmarkGrayObject(mFlatJSObject);
+        {xpc_UnmarkGrayObject(mFlatJSObject);
          return mFlatJSObject;}
 
     /**
      * This getter does not change the color of the JSObject meaning that the
      * object returned is not guaranteed to be kept alive past the next CC.
      *
      * This should only be called if you are certain that the return value won't
      * be passed into a JS API function and that it won't be stored without
@@ -2438,36 +2439,35 @@ public:
             if (mScriptableInfo)
                 mScriptableInfo->Mark();
         }
         if (HasProto())
             GetProto()->TraceSelf(trc);
         else
             GetScope()->TraceSelf(trc);
         TraceWrapper(trc);
-        if (mFlatJSObject && mFlatJSObject != INVALID_OBJECT &&
-            JS_IsGlobalObject(mFlatJSObject))
+        if (mFlatJSObject && JS_IsGlobalObject(mFlatJSObject))
         {
             TraceXPCGlobal(trc, mFlatJSObject);
         }
     }
 
     void TraceJS(JSTracer *trc) {
         TraceInside(trc);
     }
 
     void TraceSelf(JSTracer *trc) {
         // If this got called, we're being kept alive by someone who really
         // needs us alive and whole.  Do not let our mFlatJSObject go away.
         // This is the only time we should be tracing our mFlatJSObject,
         // normally somebody else is doing that. Be careful not to trace the
         // bogus INVALID_OBJECT value we can have during init, though.
-        if (mFlatJSObject && mFlatJSObject != INVALID_OBJECT) {
-            JS_CallObjectTracer(trc, &mFlatJSObject,
-                                "XPCWrappedNative::mFlatJSObject");
+        if (mFlatJSObject) {
+            JS_CallTenuredObjectTracer(trc, &mFlatJSObject,
+                                       "XPCWrappedNative::mFlatJSObject");
         }
     }
 
     void AutoTrace(JSTracer *trc) {
         TraceSelf(trc);
     }
 
 #ifdef DEBUG
@@ -2485,51 +2485,48 @@ public:
     // Returns a string that shuld be free'd using JS_smprintf_free (or null).
     char* ToString(XPCWrappedNativeTearOff* to = nullptr) const;
 
     static void GatherProtoScriptableCreateInfo(nsIClassInfo* classInfo,
                                                 XPCNativeScriptableCreateInfo& sciProto);
 
     JSBool HasExternalReference() const {return mRefCnt > 1;}
 
-    JSBool NeedsSOW() { return !!(mWrapperWord & NEEDS_SOW); }
-    void SetNeedsSOW() { mWrapperWord |= NEEDS_SOW; }
-    JSBool NeedsCOW() { return !!(mWrapperWord & NEEDS_COW); }
-    void SetNeedsCOW() { mWrapperWord |= NEEDS_COW; }
-
-    JSObject* GetWrapperPreserveColor() const
-        {return (JSObject*)(mWrapperWord & (size_t)~(size_t)FLAG_MASK);}
+    JSBool NeedsSOW() { return mWrapper.hasFlag(WRAPPER_NEEDS_SOW); }
+    void SetNeedsSOW() { mWrapper.setFlags(WRAPPER_NEEDS_SOW); }
+    JSBool NeedsCOW() { return mWrapper.hasFlag(WRAPPER_NEEDS_COW); }
+    void SetNeedsCOW() { mWrapper.setFlags(WRAPPER_NEEDS_COW); }
+
+    JSObject* GetWrapperPreserveColor() const { return mWrapper.getPtr(); }
 
     JSObject* GetWrapper()
     {
         JSObject* wrapper = GetWrapperPreserveColor();
         if (wrapper) {
             xpc_UnmarkGrayObject(wrapper);
             // Call this to unmark mFlatJSObject.
             GetFlatJSObject();
         }
         return wrapper;
     }
     void SetWrapper(JSObject *obj)
     {
         JS::IncrementalObjectBarrier(GetWrapperPreserveColor());
-        intptr_t newval = intptr_t(obj) | (mWrapperWord & FLAG_MASK);
-        mWrapperWord = newval;
+        mWrapper.setPtr(obj);
     }
 
     void TraceWrapper(JSTracer *trc)
     {
-        JS_CallMaskedObjectTracer(trc, reinterpret_cast<uintptr_t *>(&mWrapperWord),
-                                  (uintptr_t)FLAG_MASK, "XPCWrappedNative::mWrapper");
+        JS_CallTenuredObjectTracer(trc, &mWrapper, "XPCWrappedNative::mWrapper");
     }
 
     // Returns the relevant same-compartment security if applicable, or
     // mFlatJSObject otherwise.
     //
-    // This takes care of checking mWrapperWord to see if we already have such
+    // This takes care of checking mWrapper to see if we already have such
     // a wrapper.
     JSObject *GetSameCompartmentSecurityWrapper(JSContext *cx);
 
     void NoteTearoffs(nsCycleCollectionTraversalCallback& cb);
 
     // Make ctor and dtor protected (rather than private) to placate nsCOMPtr.
 protected:
     XPCWrappedNative(); // not implemented
@@ -2545,19 +2542,22 @@ protected:
 
     virtual ~XPCWrappedNative();
     void Destroy();
 
     void UpdateScriptableInfo(XPCNativeScriptableInfo *si);
 
 private:
     enum {
-        NEEDS_SOW = JS_BIT(0),
-        NEEDS_COW = JS_BIT(1),
-        FLAG_MASK = JS_BITMASK(3)
+        // Flags bits for mWrapper:
+        WRAPPER_NEEDS_SOW = JS_BIT(0),
+        WRAPPER_NEEDS_COW = JS_BIT(1),
+
+        // Flags bits for mFlatJSObject:
+        FLAT_JS_OBJECT_VALID = JS_BIT(0)
     };
 
 private:
 
     JSBool Init(JS::HandleObject parent, const XPCNativeScriptableCreateInfo* sci);
     JSBool FinishInit();
 
     JSBool ExtendSet(XPCNativeInterface* aInterface);
@@ -2576,20 +2576,20 @@ public:
 
 private:
     union
     {
         XPCWrappedNativeScope*   mMaybeScope;
         XPCWrappedNativeProto*   mMaybeProto;
     };
     XPCNativeSet*                mSet;
-    JSObject*                    mFlatJSObject;
+    JS::TenuredHeap<JSObject*>   mFlatJSObject;
     XPCNativeScriptableInfo*     mScriptableInfo;
     XPCWrappedNativeTearOffChunk mFirstChunk;
-    intptr_t                     mWrapperWord;
+    JS::TenuredHeap<JSObject*>   mWrapper;
 };
 
 /***************************************************************************
 ****************************************************************************
 *
 * Core classes for wrapped JSObject for use from native code...
 *
 ****************************************************************************
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -558,16 +558,17 @@ CycleCollectedJSRuntime::MaybeTraceGloba
   JSContext* iter = nullptr;
   while (JSContext* acx = JS_ContextIterator(Runtime(), &iter)) {
     MOZ_ASSERT(js::HasUnrootedGlobal(acx) == mExpectUnrootedGlobals);
     if (!js::HasUnrootedGlobal(acx)) {
       continue;
     }
 
     if (JSObject* global = js::GetDefaultGlobalForContext(acx)) {
+      JS::AssertGCThingMustBeTenured(global);
       JS_CallObjectTracer(aTracer, &global, "Global Object");
     }
   }
 }
 
 void
 CycleCollectedJSRuntime::DescribeGCThing(bool aIsMarked, void* aThing,
                                          JSGCTraceKind aTraceKind,