Bug 1205054 - Remove isNullLike and other imprecise null checks; r=sfink
authorTerrence Cole <terrence@mozilla.com>
Wed, 16 Sep 2015 11:19:44 -0700
changeset 295929 c167178109febd1b9c8eac5656c710ee09c4c58d
parent 295928 1d45333a4690ec40fcb16c961c73699dc9772d18
child 295930 2a56db89602545da44a7dd7396a683ddf1d695f1
push idunknown
push userunknown
push dateunknown
reviewerssfink
bugs1205054
milestone43.0a1
Bug 1205054 - Remove isNullLike and other imprecise null checks; r=sfink
js/src/gc/Heap.h
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/gc/Tracer.cpp
js/src/jsgc.h
js/src/jsobj.h
js/src/moz.build
js/src/vm/ObjectGroup.cpp
js/src/vm/ObjectGroup.h
js/src/vm/TaggedProto.cpp
js/src/vm/TaggedProto.h
js/src/vm/TypeInference.h
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -1446,17 +1446,18 @@ TenuredCell::readBarrier(TenuredCell* th
     if (thing->isMarked(GRAY))
         UnmarkGrayCellRecursively(thing, thing->getTraceKind());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 TenuredCell::writeBarrierPre(TenuredCell* thing)
 {
     MOZ_ASSERT(!CurrentThreadIsIonCompiling());
-    if (isNullLike(thing) || thing->shadowRuntimeFromAnyThread()->isHeapBusy())
+    MOZ_ASSERT_IF(thing, !isNullLike(thing));
+    if (!thing || thing->shadowRuntimeFromAnyThread()->isHeapBusy())
         return;
 
     JS::shadow::Zone* shadowZone = thing->shadowZoneFromAnyThread();
     if (shadowZone->needsIncrementalBarrier()) {
         MOZ_ASSERT(!RuntimeFromMainThreadIsHeapMajorCollecting(shadowZone));
         Cell* tmp = thing;
         TraceManuallyBarrieredGenericPointerEdge(shadowZone->barrierTracer(), &tmp, "pre barrier");
         MOZ_ASSERT(tmp == thing);
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -252,16 +252,23 @@ CheckTracedThing<Value>(JSTracer* trc, V
 
 template <>
 void
 CheckTracedThing<jsid>(JSTracer* trc, jsid id)
 {
     DispatchIdTyped(CheckTracedFunctor<jsid>(), id, trc);
 }
 
+template <>
+void
+CheckTracedThing<TaggedProto>(JSTracer* trc, TaggedProto proto)
+{
+    DispatchTaggedProtoTyped(CheckTracedFunctor<TaggedProto>(), proto, trc);
+}
+
 #define IMPL_CHECK_TRACED_THING(_, type, __) \
     template void CheckTracedThing<type*>(JSTracer*, type*);
 JS_FOR_EACH_TRACEKIND(IMPL_CHECK_TRACED_THING);
 #undef IMPL_CHECK_TRACED_THING
 } // namespace js
 
 static bool
 ShouldMarkCrossCompartment(JSTracer* trc, JSObject* src, Cell* cell)
@@ -379,17 +386,18 @@ AssertRootMarkingPhase(JSTracer* trc)
     D(JSAtom*) \
     D(JSString*) \
     D(JSFlatString*) \
     D(JSLinearString*) \
     D(PropertyName*) \
     D(JS::Symbol*) \
     D(js::ObjectGroup*) \
     D(Value) \
-    D(jsid)
+    D(jsid) \
+    D(TaggedProto)
 
 // The second parameter to BaseGCType is derived automatically based on T. The
 // relation here is that for any T, the TraceKind will automatically,
 // statically select the correct Cell layout for marking. Below, we instantiate
 // each override with a declaration of the most derived layout type.
 //
 // Usage:
 //   BaseGCType<T>::type
@@ -416,16 +424,17 @@ JS_FOR_EACH_TRACEKIND(IMPL_BASE_GC_TYPE)
 
 // Our barrier templates are parameterized on the pointer types so that we can
 // share the definitions with Value and jsid. Thus, we need to strip the
 // pointer before sending the type to BaseGCType and re-add it on the other
 // side. As such:
 template <typename T> struct PtrBaseGCType {};
 template <> struct PtrBaseGCType<Value> { typedef Value type; };
 template <> struct PtrBaseGCType<jsid> { typedef jsid type; };
+template <> struct PtrBaseGCType<TaggedProto> { typedef TaggedProto type; };
 template <typename T> struct PtrBaseGCType<T*> { typedef typename BaseGCType<T>::type* type; };
 
 template <typename T>
 typename PtrBaseGCType<T>::type*
 ConvertToBase(T* thingp)
 {
     return reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp);
 }
@@ -588,17 +597,18 @@ js::TraceManuallyBarrieredGenericPointer
 template <typename T>
 void
 DispatchToTracer(JSTracer* trc, T* thingp, const char* name)
 {
 #define IS_SAME_TYPE_OR(name, type, _) mozilla::IsSame<type*, T>::value ||
     static_assert(
             JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR)
             mozilla::IsSame<T, JS::Value>::value ||
-            mozilla::IsSame<T, jsid>::value,
+            mozilla::IsSame<T, jsid>::value ||
+            mozilla::IsSame<T, TaggedProto>::value,
             "Only the base cell layout types are allowed into marking/tracing internals");
 #undef IS_SAME_TYPE_OR
     if (trc->isMarkingTracer())
         return DoMarking(static_cast<GCMarker*>(trc), *thingp);
     if (trc->isTenuringTracer())
         return static_cast<TenuringTracer*>(trc)->traverse(thingp);
     MOZ_ASSERT(trc->isCallbackTracer());
     DoCallback(trc->asCallbackTracer(), thingp, name);
@@ -751,16 +761,24 @@ DoMarking<Value>(GCMarker* gcmarker, Val
 
 template <>
 void
 DoMarking<jsid>(GCMarker* gcmarker, jsid id)
 {
     DispatchIdTyped(DoMarkingFunctor<jsid>(), id, gcmarker);
 }
 
+template <>
+void
+DoMarking<TaggedProto>(GCMarker* gcmarker, TaggedProto proto)
+{
+    if (proto.isObject())
+        DoMarking<JSObject*>(gcmarker, proto.toObject());
+}
+
 // The simplest traversal calls out to the fully generic traceChildren function
 // to visit the child edges. In the absence of other traversal mechanisms, this
 // function will rapidly grow the stack past its bounds and crash the process.
 // Thus, this generic tracing should only be used in cases where subsequent
 // tracing will not recurse.
 template <typename T>
 void
 js::GCMarker::markAndTraceChildren(T* thing)
@@ -1094,17 +1112,17 @@ js::ObjectGroup::traceChildren(JSTracer*
 {
     unsigned count = getPropertyCount();
     for (unsigned i = 0; i < count; i++) {
         if (ObjectGroup::Property* prop = getProperty(i))
             TraceEdge(trc, &prop->id, "group_property");
     }
 
     if (proto().isObject())
-        TraceEdge(trc, &protoRaw(), "group_proto");
+        TraceEdge(trc, &proto(), "group_proto");
 
     if (newScript())
         newScript()->trace(trc);
 
     if (maybePreliminaryObjects())
         maybePreliminaryObjects()->trace(trc);
 
     if (maybeUnboxedLayout())
@@ -1896,16 +1914,28 @@ TenuringTracer::traverse(Value* valp)
     if (!valp->isObject())
         return;
 
     JSObject *obj = &valp->toObject();
     traverse(&obj);
     valp->setObject(*obj);
 }
 
+template <>
+void
+TenuringTracer::traverse(TaggedProto* protop)
+{
+    if (!protop->isObject())
+        return;
+
+    JSObject *obj = protop->toObject();
+    traverse(&obj);
+    *protop = TaggedProto(obj);
+}
+
 template <> void js::TenuringTracer::traverse(js::BaseShape**) {}
 template <> void js::TenuringTracer::traverse(js::jit::JitCode**) {}
 template <> void js::TenuringTracer::traverse(JSScript**) {}
 template <> void js::TenuringTracer::traverse(js::LazyScript**) {}
 template <> void js::TenuringTracer::traverse(js::Shape**) {}
 template <> void js::TenuringTracer::traverse(JSString**) {}
 template <> void js::TenuringTracer::traverse(JS::Symbol**) {}
 template <> void js::TenuringTracer::traverse(js::ObjectGroup**) {}
@@ -2284,17 +2314,17 @@ IsMarkedInternal(JSObject** thingp)
     }
     return IsMarkedInternalCommon(thingp);
 }
 
 template <typename S>
 struct IsMarkedFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, bool* rv) {
         *rv = IsMarkedInternal(&t);
-        return js::gc::RewrapValueOrId<S, T*>::wrap(t);
+        return js::gc::RewrapTaggedPointer<S, T*>::wrap(t);
     }
 };
 
 template <>
 bool
 IsMarkedInternal<Value>(Value* valuep)
 {
     bool rv = true;
@@ -2306,16 +2336,25 @@ template <>
 bool
 IsMarkedInternal<jsid>(jsid* idp)
 {
     bool rv = true;
     *idp = DispatchIdTyped(IsMarkedFunctor<jsid>(), *idp, &rv);
     return rv;
 }
 
+template <>
+bool
+IsMarkedInternal<TaggedProto>(TaggedProto* protop)
+{
+    bool rv = true;
+    *protop = DispatchTaggedProtoTyped(IsMarkedFunctor<TaggedProto>(), *protop, &rv);
+    return rv;
+}
+
 template <typename T>
 static bool
 IsAboutToBeFinalizedInternal(T* thingp)
 {
     CheckIsMarkedThing(thingp);
     T thing = *thingp;
     JSRuntime* rt = thing->runtimeFromAnyThread();
 
@@ -2344,17 +2383,17 @@ IsAboutToBeFinalizedInternal(T* thingp)
 
     return false;
 }
 
 template <typename S>
 struct IsAboutToBeFinalizedFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, bool* rv) {
         *rv = IsAboutToBeFinalizedInternal(&t);
-        return js::gc::RewrapValueOrId<S, T*>::wrap(t);
+        return js::gc::RewrapTaggedPointer<S, T*>::wrap(t);
     }
 };
 
 template <>
 bool
 IsAboutToBeFinalizedInternal<Value>(Value* valuep)
 {
     bool rv = false;
@@ -2366,16 +2405,25 @@ template <>
 bool
 IsAboutToBeFinalizedInternal<jsid>(jsid* idp)
 {
     bool rv = false;
     *idp = DispatchIdTyped(IsAboutToBeFinalizedFunctor<jsid>(), *idp, &rv);
     return rv;
 }
 
+template <>
+bool
+IsAboutToBeFinalizedInternal<TaggedProto>(TaggedProto* protop)
+{
+    bool rv = false;
+    *protop = DispatchTaggedProtoTyped(IsAboutToBeFinalizedFunctor<TaggedProto>(), *protop, &rv);
+    return rv;
+}
+
 namespace js {
 namespace gc {
 
 template <typename T>
 bool
 IsMarkedUnbarriered(T* thingp)
 {
     return IsMarkedInternal(ConvertToBase(thingp));
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -15,16 +15,17 @@
 
 #include "ds/OrderedHashTable.h"
 #include "gc/Heap.h"
 #include "gc/Tracer.h"
 #include "js/GCAPI.h"
 #include "js/HeapAPI.h"
 #include "js/SliceBudget.h"
 #include "js/TracingAPI.h"
+#include "vm/TaggedProto.h"
 
 class JSLinearString;
 class JSRope;
 namespace js {
 class BaseShape;
 class GCMarker;
 class LazyScript;
 class NativeObject;
@@ -448,26 +449,27 @@ class HashKeyRef : public BufferableRef
         TraceManuallyBarrieredEdge(trc, &key, "HashKeyRef");
         map->rekeyIfMoved(prior, key);
     }
 };
 
 // Wrap a GC thing pointer into a new Value or jsid. The type system enforces
 // that the thing pointer is a wrappable type.
 template <typename S, typename T>
-struct RewrapValueOrId {};
+struct RewrapTaggedPointer{};
 #define DECLARE_REWRAP(S, T, method, prefix) \
-    template <> struct RewrapValueOrId<S, T> { \
+    template <> struct RewrapTaggedPointer<S, T> { \
         static S wrap(T thing) { return method ( prefix thing ); } \
     }
 DECLARE_REWRAP(JS::Value, JSObject*, JS::ObjectOrNullValue, );
 DECLARE_REWRAP(JS::Value, JSString*, JS::StringValue, );
 DECLARE_REWRAP(JS::Value, JS::Symbol*, JS::SymbolValue, );
 DECLARE_REWRAP(jsid, JSString*, NON_INTEGER_ATOM_TO_JSID, (JSAtom*));
 DECLARE_REWRAP(jsid, JS::Symbol*, SYMBOL_TO_JSID, );
+DECLARE_REWRAP(js::TaggedProto, JSObject*, js::TaggedProto, );
 
 } /* namespace gc */
 
 bool
 UnmarkGrayShapeRecursively(Shape* shape);
 
 template<typename T>
 void
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -54,17 +54,17 @@ DoCallback(JS::CallbackTracer* trc, T* t
 #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \
     template type* DoCallback<type*>(JS::CallbackTracer*, type**, const char*);
 JS_FOR_EACH_TRACEKIND(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS);
 #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
 
 template <typename S>
 struct DoCallbackFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, JS::CallbackTracer* trc, const char* name) {
-        return js::gc::RewrapValueOrId<S, T*>::wrap(DoCallback(trc, &t, name));
+        return js::gc::RewrapTaggedPointer<S, T*>::wrap(DoCallback(trc, &t, name));
     }
 };
 
 template <>
 Value
 DoCallback<Value>(JS::CallbackTracer* trc, Value* vp, const char* name)
 {
     *vp = DispatchValueTyped(DoCallbackFunctor<Value>(), *vp, trc, name);
@@ -74,16 +74,24 @@ DoCallback<Value>(JS::CallbackTracer* tr
 template <>
 jsid
 DoCallback<jsid>(JS::CallbackTracer* trc, jsid* idp, const char* name)
 {
     *idp = DispatchIdTyped(DoCallbackFunctor<jsid>(), *idp, trc, name);
     return *idp;
 }
 
+template <>
+TaggedProto
+DoCallback<TaggedProto>(JS::CallbackTracer* trc, TaggedProto* protop, const char* name)
+{
+    *protop = DispatchTaggedProtoTyped(DoCallbackFunctor<TaggedProto>(), *protop, trc, name);
+    return *protop;
+}
+
 void
 JS::CallbackTracer::getTracingEdgeName(char* buffer, size_t bufferSize)
 {
     MOZ_ASSERT(bufferSize > 0);
     if (contextFunctor_) {
         (*contextFunctor_)(this, buffer, bufferSize);
         return;
     }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1195,17 +1195,17 @@ Forwarded(T* t)
 {
     RelocationOverlay* overlay = RelocationOverlay::fromCell(t);
     MOZ_ASSERT(overlay->isForwarded());
     return reinterpret_cast<T*>(overlay->forwardingAddress());
 }
 
 struct ForwardedFunctor : public IdentityDefaultAdaptor<Value> {
     template <typename T> inline Value operator()(T* t) {
-        return js::gc::RewrapValueOrId<Value, T*>::wrap(Forwarded(t));
+        return js::gc::RewrapTaggedPointer<Value, T*>::wrap(Forwarded(t));
     }
 };
 
 inline Value
 Forwarded(const JS::Value& value)
 {
     return DispatchValueTyped(ForwardedFunctor(), value);
 }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -613,47 +613,51 @@ struct JSObject_Slots2 : JSObject { void
 struct JSObject_Slots4 : JSObject { void* data[3]; js::Value fslots[4]; };
 struct JSObject_Slots8 : JSObject { void* data[3]; js::Value fslots[8]; };
 struct JSObject_Slots12 : JSObject { void* data[3]; js::Value fslots[12]; };
 struct JSObject_Slots16 : JSObject { void* data[3]; js::Value fslots[16]; };
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::readBarrier(JSObject* obj)
 {
-    if (!isNullLike(obj) && obj->isTenured())
+    MOZ_ASSERT_IF(obj, !isNullLike(obj));
+    if (obj && obj->isTenured())
         obj->asTenured().readBarrier(&obj->asTenured());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::writeBarrierPre(JSObject* obj)
 {
-    if (!isNullLike(obj) && obj->isTenured())
+    MOZ_ASSERT_IF(obj, !isNullLike(obj));
+    if (obj && obj->isTenured())
         obj->asTenured().writeBarrierPre(&obj->asTenured());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::writeBarrierPost(void* cellp, JSObject* prev, JSObject* next)
 {
     MOZ_ASSERT(cellp);
+    MOZ_ASSERT_IF(next, !IsNullTaggedPointer(next));
+    MOZ_ASSERT_IF(prev, !IsNullTaggedPointer(prev));
 
     // If the target needs an entry, add it.
     js::gc::StoreBuffer* buffer;
-    if (!IsNullTaggedPointer(next) && (buffer = next->storeBuffer())) {
+    if (next && (buffer = next->storeBuffer())) {
         // If we know that the prev has already inserted an entry, we can skip
         // doing the lookup to add the new entry.
-        if (!IsNullTaggedPointer(prev) && prev->storeBuffer()) {
+        if (prev && prev->storeBuffer()) {
             buffer->assertHasCellEdge(static_cast<js::gc::Cell**>(cellp));
             return;
         }
         buffer->putCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
         return;
     }
 
     // Remove the prev entry if the new value does not need it.
-    if (!IsNullTaggedPointer(prev) && (buffer = prev->storeBuffer()))
+    if (prev && (buffer = prev->storeBuffer()))
         buffer->unputCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
 }
 
 namespace js {
 
 inline bool
 IsCallable(const Value& v)
 {
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -317,16 +317,17 @@ UNIFIED_SOURCES += [
     'vm/SharedArrayObject.cpp',
     'vm/SharedTypedArrayObject.cpp',
     'vm/SPSProfiler.cpp',
     'vm/Stack.cpp',
     'vm/String.cpp',
     'vm/StringBuffer.cpp',
     'vm/StructuredClone.cpp',
     'vm/Symbol.cpp',
+    'vm/TaggedProto.cpp',
     'vm/Time.cpp',
     'vm/TypedArrayObject.cpp',
     'vm/TypeInference.cpp',
     'vm/UbiNode.cpp',
     'vm/UbiNodeCensus.cpp',
     'vm/UnboxedObject.cpp',
     'vm/Unicode.cpp',
     'vm/Value.cpp',
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -31,17 +31,17 @@ ObjectGroup::ObjectGroup(const Class* cl
                          ObjectGroupFlags initialFlags)
 {
     PodZero(this);
 
     /* Inner objects may not appear on prototype chains. */
     MOZ_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject);
 
     this->clasp_ = clasp;
-    this->proto_ = proto.raw();
+    this->proto_ = proto;
     this->compartment_ = comp;
     this->flags_ = initialFlags;
 
     setGeneration(zone()->types.generation);
 }
 
 void
 ObjectGroup::finalize(FreeOp* fop)
@@ -53,18 +53,19 @@ ObjectGroup::finalize(FreeOp* fop)
     if (maybePreliminaryObjectsDontCheckGeneration())
         maybePreliminaryObjectsDontCheckGeneration()->clear();
     fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
 }
 
 void
 ObjectGroup::setProtoUnchecked(TaggedProto proto)
 {
-    proto_ = proto.raw();
-    MOZ_ASSERT_IF(proto_ && proto_->isNative(), proto_->isDelegate());
+    proto_ = proto;
+    MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(),
+                  proto_.toObject()->isDelegate());
 }
 
 void
 ObjectGroup::setProto(TaggedProto proto)
 {
     MOZ_ASSERT(singleton());
     setProtoUnchecked(proto);
 }
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -7,119 +7,30 @@
 #ifndef vm_ObjectGroup_h
 #define vm_ObjectGroup_h
 
 #include "jsbytecode.h"
 #include "jsfriendapi.h"
 
 #include "ds/IdValuePair.h"
 #include "gc/Barrier.h"
+#include "vm/TaggedProto.h"
 #include "vm/TypeInference.h"
 
 namespace js {
 
 class TypeDescr;
 class UnboxedLayout;
 
 class PreliminaryObjectArrayWithTemplate;
 class TypeNewScript;
 class HeapTypeSet;
 class AutoClearTypeInferenceStateOnOOM;
 class CompilerConstraintList;
 
-// Information about an object prototype, which can be either a particular
-// object, null, or a lazily generated object. The latter is only used by
-// certain kinds of proxies.
-class TaggedProto
-{
-  public:
-    static JSObject * const LazyProto;
-
-    TaggedProto() : proto(nullptr) {}
-    explicit TaggedProto(JSObject* proto) : proto(proto) {}
-
-    uintptr_t toWord() const { return uintptr_t(proto); }
-
-    bool isLazy() const {
-        return proto == LazyProto;
-    }
-    bool isObject() const {
-        /* Skip nullptr and LazyProto. */
-        return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto);
-    }
-    JSObject* toObject() const {
-        MOZ_ASSERT(isObject());
-        return proto;
-    }
-    JSObject* toObjectOrNull() const {
-        MOZ_ASSERT(!proto || isObject());
-        return proto;
-    }
-    JSObject* raw() const { return proto; }
-
-    bool operator ==(const TaggedProto& other) { return proto == other.proto; }
-    bool operator !=(const TaggedProto& other) { return proto != other.proto; }
-
-  private:
-    JSObject* proto;
-};
-
-template <>
-struct RootKind<TaggedProto>
-{
-    static ThingRootKind rootKind() { return THING_ROOT_OBJECT; }
-};
-
-template <> struct GCMethods<const TaggedProto>
-{
-    static TaggedProto initial() { return TaggedProto(); }
-};
-
-template <> struct GCMethods<TaggedProto>
-{
-    static TaggedProto initial() { return TaggedProto(); }
-};
-
-template<class Outer>
-class TaggedProtoOperations
-{
-    const TaggedProto& value() const {
-        return static_cast<const Outer*>(this)->get();
-    }
-
-  public:
-    uintptr_t toWord() const { return value().toWord(); }
-    inline bool isLazy() const { return value().isLazy(); }
-    inline bool isObject() const { return value().isObject(); }
-    inline JSObject* toObject() const { return value().toObject(); }
-    inline JSObject* toObjectOrNull() const { return value().toObjectOrNull(); }
-    JSObject* raw() const { return value().raw(); }
-};
-
-template <>
-class HandleBase<TaggedProto> : public TaggedProtoOperations<Handle<TaggedProto> >
-{};
-
-template <>
-class RootedBase<TaggedProto> : public TaggedProtoOperations<Rooted<TaggedProto> >
-{};
-
-// Since JSObject pointers are either nullptr or a valid object and since the
-// object layout of TaggedProto is identical to a bare object pointer, we can
-// safely treat a pointer to an already-rooted object (e.g. HandleObject) as a
-// pointer to a TaggedProto.
-inline Handle<TaggedProto>
-AsTaggedProto(HandleObject obj)
-{
-    static_assert(sizeof(JSObject*) == sizeof(TaggedProto),
-                  "TaggedProto must be binary compatible with JSObject");
-    return Handle<TaggedProto>::fromMarkedLocation(
-            reinterpret_cast<TaggedProto const*>(obj.address()));
-}
-
 namespace gc {
 void MergeCompartments(JSCompartment* source, JSCompartment* target);
 } // namespace gc
 
 /*
  * The NewObjectKind allows an allocation site to specify the type properties
  * and lifetime requirements that must be fixed at allocation time.
  */
@@ -167,37 +78,38 @@ enum NewObjectKind {
 class ObjectGroup : public gc::TenuredCell
 {
     friend void gc::MergeCompartments(JSCompartment* source, JSCompartment* target);
 
     /* Class shared by objects in this group. */
     const Class* clasp_;
 
     /* Prototype shared by objects in this group. */
-    HeapPtrObject proto_;
+    HeapPtr<TaggedProto> proto_;
 
     /* Compartment shared by objects in this group. */
     JSCompartment* compartment_;
 
   public:
 
     const Class* clasp() const {
         return clasp_;
     }
 
     void setClasp(const Class* clasp) {
         clasp_ = clasp;
     }
 
-    TaggedProto proto() const {
-        return TaggedProto(proto_);
+    const HeapPtr<TaggedProto>& proto() const {
+        return proto_;
     }
 
-    // For use during marking, don't call otherwise.
-    HeapPtrObject& protoRaw() { return proto_; }
+    HeapPtr<TaggedProto>& proto() {
+        return proto_;
+    }
 
     void setProto(TaggedProto proto);
     void setProtoUnchecked(TaggedProto proto);
 
     bool singleton() const {
         return flagsDontCheckGeneration() & OBJECT_FLAG_SINGLETON;
     }
 
new file mode 100644
--- /dev/null
+++ b/js/src/vm/TaggedProto.cpp
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* static */ void
+js::InternalGCMethods<TaggedProto>::preBarrier(TaggedProto& proto)
+{
+    InternalGCMethods<JSObject*>::preBarrier(proto.toObjectOrNull());
+}
+
+/* static */ void
+js::InternalGCMethods<TaggedProto>::postBarrier(TaggedProto* vp, TaggedProto prev, TaggedProto next)
+{
+    JSObject* prevObj = prev.isObject() ? prev.toObject() : nullptr;
+    JSObject* nextObj = next.isObject() ? next.toObject() : nullptr;
+    InternalGCMethods<JSObject*>::postBarrier(reinterpret_cast<JSObject**>(vp), prevObj,
+                                              nextObj);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/vm/TaggedProto.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_TaggedProto_h
+#define vm_TaggedProto_h
+
+#include "gc/Tracer.h"
+
+namespace js {
+
+// Information about an object prototype, which can be either a particular
+// object, null, or a lazily generated object. The latter is only used by
+// certain kinds of proxies.
+class TaggedProto : public JS::Traceable
+{
+  public:
+    static JSObject * const LazyProto;
+
+    TaggedProto() : proto(nullptr) {}
+    explicit TaggedProto(JSObject* proto) : proto(proto) {}
+
+    uintptr_t toWord() const { return uintptr_t(proto); }
+
+    bool isLazy() const {
+        return proto == LazyProto;
+    }
+    bool isObject() const {
+        /* Skip nullptr and LazyProto. */
+        return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto);
+    }
+    JSObject* toObject() const {
+        MOZ_ASSERT(isObject());
+        return proto;
+    }
+    JSObject* toObjectOrNull() const {
+        MOZ_ASSERT(!proto || isObject());
+        return proto;
+    }
+    JSObject* raw() const { return proto; }
+
+    bool operator ==(const TaggedProto& other) const { return proto == other.proto; }
+    bool operator !=(const TaggedProto& other) const { return proto != other.proto; }
+
+    static void trace(TaggedProto* protop, JSTracer* trc) {
+        TraceManuallyBarrieredEdge(trc, protop, "TaggedProto");
+    }
+
+  private:
+    JSObject* proto;
+};
+
+template <> struct GCMethods<TaggedProto>
+{
+    static TaggedProto initial() { return TaggedProto(); }
+};
+
+template <> struct InternalGCMethods<TaggedProto>
+{
+    static void preBarrier(TaggedProto& proto);
+
+    static void postBarrier(TaggedProto* vp, TaggedProto prev, TaggedProto next);
+
+    static bool isMarkableTaggedPointer(TaggedProto proto) {
+        return proto.isObject();
+    }
+
+    static bool isMarkable(TaggedProto proto) {
+        return proto.isObject();
+    }
+};
+
+template<class Outer>
+class TaggedProtoOperations
+{
+    const TaggedProto& value() const {
+        return static_cast<const Outer*>(this)->get();
+    }
+
+  public:
+    uintptr_t toWord() const { return value().toWord(); }
+    inline bool isLazy() const { return value().isLazy(); }
+    inline bool isObject() const { return value().isObject(); }
+    inline JSObject* toObject() const { return value().toObject(); }
+    inline JSObject* toObjectOrNull() const { return value().toObjectOrNull(); }
+    JSObject* raw() const { return value().raw(); }
+};
+
+template <>
+class HandleBase<TaggedProto> : public TaggedProtoOperations<Handle<TaggedProto>>
+{};
+
+template <>
+class RootedBase<TaggedProto> : public TaggedProtoOperations<Rooted<TaggedProto>>
+{};
+
+template <>
+class BarrieredBaseMixins<TaggedProto> : public TaggedProtoOperations<HeapPtr<TaggedProto>>
+{};
+
+// If the TaggedProto is a JSObject pointer, convert to that type and call |f|
+// with the pointer. If the TaggedProto is lazy, calls F::defaultValue.
+template <typename F, typename... Args>
+auto
+DispatchTaggedProtoTyped(F f, TaggedProto& proto, Args&&... args)
+  -> decltype(f(static_cast<JSObject*>(nullptr), mozilla::Forward<Args>(args)...))
+{
+    if (proto.isObject())
+        return f(proto.toObject(), mozilla::Forward<Args>(args)...);
+    return F::defaultValue(proto);
+}
+
+// Since JSObject pointers are either nullptr or a valid object and since the
+// object layout of TaggedProto is identical to a bare object pointer, we can
+// safely treat a pointer to an already-rooted object (e.g. HandleObject) as a
+// pointer to a TaggedProto.
+inline Handle<TaggedProto>
+AsTaggedProto(HandleObject obj)
+{
+    static_assert(sizeof(JSObject*) == sizeof(TaggedProto),
+                  "TaggedProto must be binary compatible with JSObject");
+    return Handle<TaggedProto>::fromMarkedLocation(
+            reinterpret_cast<TaggedProto const*>(obj.address()));
+}
+
+} // namespace js
+
+#endif // vm_TaggedProto_h
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -18,26 +18,26 @@
 #include "ds/IdValuePair.h"
 #include "ds/LifoAlloc.h"
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
 #include "jit/IonTypes.h"
 #include "js/UbiNode.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
+#include "vm/TaggedProto.h"
 
 namespace js {
 
 namespace jit {
     struct IonScript;
     class JitAllocPolicy;
     class TempAllocator;
 } // namespace jit
 
-class TaggedProto;
 struct TypeZone;
 class TypeConstraint;
 class TypeNewScript;
 class CompilerConstraintList;
 class HeapTypeSetKey;
 
 /*
  * Type inference memory management overview.