Bug 1100170 - Optimize marking for inline typed objects, r=sfink.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 24 Nov 2014 20:24:17 -0700
changeset 241625 f7705f553b85bc361857c72300bc279421cf9aa5
parent 241624 672742f81e517e7e84fa06dc27f367ce9ad5c413
child 241626 e5bccb2e00cbe1e07934712b7850389dd874265b
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1100170
milestone36.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 1100170 - Optimize marking for inline typed objects, r=sfink.
js/src/builtin/SIMD.cpp
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/builtin/TypedObjectConstants.h
js/src/gc/Marking.cpp
js/src/gc/Tracer.h
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -162,25 +162,25 @@ static bool type##SignMask(JSContext *cx
     return SignMask<type>(cx, argc, vp); \
 }
     SIGN_MASK(Float32x4);
     SIGN_MASK(Int32x4);
 #undef SIGN_MASK
 
 const Class SimdTypeDescr::class_ = {
     "SIMD",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS),
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    nullptr,             /* finalize    */
+    TypeDescr::finalize,
     call,                /* call        */
     nullptr,             /* hasInstance */
     nullptr,             /* construct   */
     nullptr
 };
 
 // These classes just exist to group together various properties and so on.
 namespace js {
@@ -262,16 +262,17 @@ CreateSimdClass(JSContext *cx,
         return nullptr;
 
     typeDescr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Simd));
     typeDescr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
     typeDescr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(SimdTypeDescr::alignment(type)));
     typeDescr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(SimdTypeDescr::size(type)));
     typeDescr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(false));
     typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(T::type));
+    typeDescr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(nullptr));
 
     if (!CreateUserSizeAndAlignmentProperties(cx, typeDescr))
         return nullptr;
 
     // Create prototype property, which inherits from Object.prototype.
 
     RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
     if (!objProto)
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -24,16 +24,17 @@
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using mozilla::AssertedCast;
 using mozilla::CheckedInt32;
 using mozilla::DebugOnly;
+using mozilla::PodCopy;
 
 using namespace js;
 
 const Class js::TypedObjectModuleObject::class_ = {
     "TypedObject",
     JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_TypedObject),
     JS_PropertyStub,         /* addProperty */
@@ -229,25 +230,25 @@ const Class js::TypedProto::class_ = {
  * Scalar type objects like `uint8`, `uint16`, are all instances of
  * the ScalarTypeDescr class. Like all type objects, they have a reserved
  * slot pointing to a TypeRepresentation object, which is used to
  * distinguish which scalar type object this actually is.
  */
 
 const Class js::ScalarTypeDescr::class_ = {
     "Scalar",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS),
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     JS_PropertyStub,       /* addProperty */
     JS_DeletePropertyStub, /* delProperty */
     JS_PropertyStub,       /* getProperty */
     JS_StrictPropertyStub, /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    nullptr,
+    TypeDescr::finalize,
     ScalarTypeDescr::call,
     nullptr,
     nullptr,
     nullptr
 };
 
 const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = {
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
@@ -325,25 +326,25 @@ ScalarTypeDescr::call(JSContext *cx, uns
  * same way that the scalar type objects do. There is one class with
  * many instances, and each instance has a reserved slot with a
  * TypeRepresentation object, which is used to distinguish which
  * reference type object this actually is.
  */
 
 const Class js::ReferenceTypeDescr::class_ = {
     "Reference",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS),
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     JS_PropertyStub,       /* addProperty */
     JS_DeletePropertyStub, /* delProperty */
     JS_PropertyStub,       /* getProperty */
     JS_StrictPropertyStub, /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    nullptr,
+    TypeDescr::finalize,
     ReferenceTypeDescr::call,
     nullptr,
     nullptr,
     nullptr
 };
 
 const JSFunctionSpec js::ReferenceTypeDescr::typeObjectMethods[] = {
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
@@ -507,25 +508,25 @@ CreatePrototypeObjectForComplexTypeInsta
         return nullptr;
 
     result->initTypeDescrSlot(*descr);
     return result;
 }
 
 const Class ArrayTypeDescr::class_ = {
     "ArrayType",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS),
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     JS_PropertyStub,
     JS_DeletePropertyStub,
     JS_PropertyStub,
     JS_StrictPropertyStub,
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    nullptr,
+    TypeDescr::finalize,
     nullptr,
     nullptr,
     TypedObject::construct,
     nullptr
 };
 
 const JSPropertySpec ArrayMetaTypeDescr::typeObjectProperties[] = {
     JS_PS_END
@@ -602,16 +603,19 @@ js::CreateUserSizeAndAlignmentProperties
         {
             return false;
         }
     }
 
     return true;
 }
 
+static bool
+CreateTraceList(JSContext *cx, HandleTypeDescr descr);
+
 ArrayTypeDescr *
 ArrayMetaTypeDescr::create(JSContext *cx,
                            HandleObject arrayTypePrototype,
                            HandleTypeDescr elementType,
                            HandleAtom stringRepr,
                            int32_t size,
                            int32_t length)
 {
@@ -648,16 +652,19 @@ ArrayMetaTypeDescr::create(JSContext *cx
     if (!prototypeObj)
         return nullptr;
 
     obj->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj));
 
     if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
         return nullptr;
 
+    if (!CreateTraceList(cx, obj))
+        return nullptr;
+
     return obj;
 }
 
 bool
 ArrayMetaTypeDescr::construct(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -735,25 +742,25 @@ js::IsTypedObjectArray(JSObject &obj)
 }
 
 /*********************************
  * StructType class
  */
 
 const Class StructTypeDescr::class_ = {
     "StructType",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS),
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     JS_PropertyStub,
     JS_DeletePropertyStub,
     JS_PropertyStub,
     JS_StrictPropertyStub,
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    nullptr, /* finalize */
+    TypeDescr::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     TypedObject::construct,
     nullptr  /* trace */
 };
 
 const JSPropertySpec StructMetaTypeDescr::typeObjectProperties[] = {
     JS_PS_END
@@ -1004,17 +1011,18 @@ StructMetaTypeDescr::create(JSContext *c
     if (!prototypeObj)
         return nullptr;
 
     descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj));
 
     if (!LinkConstructorAndPrototype(cx, descr, prototypeObj))
         return nullptr;
 
-    // Initialize type information for instances of this struct.
+    if (!CreateTraceList(cx, descr))
+        return nullptr;
 
     return descr;
 }
 
 bool
 StructMetaTypeDescr::construct(JSContext *cx, unsigned int argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -1200,16 +1208,19 @@ DefineSimpleTypeDescr(JSContext *cx,
 
     RootedValue descrValue(cx, ObjectValue(*descr));
     if (!JSObject::defineProperty(cx, module, className,
                                   descrValue, nullptr, nullptr, 0))
     {
         return false;
     }
 
+    if (!CreateTraceList(cx, descr))
+        return false;
+
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////
 
 template<typename T>
 static JSObject *
 DefineMetaTypeDescr(JSContext *cx,
@@ -2268,16 +2279,21 @@ InlineTypedObject::createCopy(JSContext 
     return res;
 }
 
 /* static */ void
 InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object)
 {
     InlineTypedObject &typedObj = object->as<InlineTypedObject>();
 
+    // Inline transparent objects do not have references and do not need to be
+    // traced. If they have an entry in the compartment's LazyArrayBufferTable,
+    // tracing that reference will be taken care of by the table itself.
+    MOZ_ASSERT(typedObj.is<InlineOpaqueTypedObject>());
+
     // When this is called for compacting GC, the related objects we touch here
     // may not have had their slots updated yet.
     TypeDescr &descr = typedObj.maybeForwardedTypeDescr();
 
     descr.traceInstances(trc, typedObj.inlineTypedMem(), 1);
 }
 
 /* static */ void
@@ -2454,17 +2470,17 @@ LazyArrayBufferTable::sizeOfIncludingThi
             nullptr,   /* getElements */                 \
             TypedObject::obj_enumerate,                  \
             nullptr, /* thisObject */                    \
         }                                                \
     }
 
 DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace);
 DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,      OutlineTypedObject::obj_trace);
-DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  InlineTypedObject::obj_trace);
+DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  nullptr);
 DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,       InlineTypedObject::obj_trace);
 
 static int32_t
 LengthForType(TypeDescr &descr)
 {
     switch (descr.kind()) {
       case type::Scalar:
       case type::Reference:
@@ -3075,31 +3091,33 @@ visitReferences(TypeDescr &descr,
     }
 
     MOZ_CRASH("Invalid type repr kind");
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Initializing instances
 
-namespace js {
+namespace {
+
 class MemoryInitVisitor {
     const JSRuntime *rt_;
 
   public:
     explicit MemoryInitVisitor(const JSRuntime *rt)
       : rt_(rt)
     {}
 
     void visitReference(ReferenceTypeDescr &descr, uint8_t *mem);
 };
-} // namespace js
+
+} // anonymous namespace
 
 void
-js::MemoryInitVisitor::visitReference(ReferenceTypeDescr &descr, uint8_t *mem)
+MemoryInitVisitor::visitReference(ReferenceTypeDescr &descr, uint8_t *mem)
 {
     switch (descr.type()) {
       case ReferenceTypeDescr::TYPE_ANY:
       {
         js::HeapValue *heapValue = reinterpret_cast<js::HeapValue *>(mem);
         heapValue->init(UndefinedValue());
         return;
       }
@@ -3142,32 +3160,34 @@ TypeDescr::initInstances(const JSRuntime
         target += size();
         memcpy(target, mem, size());
     }
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Tracing instances
 
-namespace js {
+namespace {
+
 class MemoryTracingVisitor {
     JSTracer *trace_;
 
   public:
 
     explicit MemoryTracingVisitor(JSTracer *trace)
       : trace_(trace)
     {}
 
     void visitReference(ReferenceTypeDescr &descr, uint8_t *mem);
 };
-} // namespace js
+
+} // anonymous namespace
 
 void
-js::MemoryTracingVisitor::visitReference(ReferenceTypeDescr &descr, uint8_t *mem)
+MemoryTracingVisitor::visitReference(ReferenceTypeDescr &descr, uint8_t *mem)
 {
     switch (descr.type()) {
       case ReferenceTypeDescr::TYPE_ANY:
       {
         js::HeapValue *heapValue = reinterpret_cast<js::HeapValue *>(mem);
         gc::MarkValue(trace_, heapValue, "reference-val");
         return;
       }
@@ -3200,8 +3220,88 @@ TypeDescr::traceInstances(JSTracer *trac
     MemoryTracingVisitor visitor(trace);
 
     for (size_t i = 0; i < length; i++) {
         visitReferences(*this, mem, visitor);
         mem += size();
     }
 }
 
+namespace {
+
+struct TraceListVisitor {
+    typedef Vector<int32_t, 0, SystemAllocPolicy> VectorType;
+    VectorType stringOffsets, objectOffsets, valueOffsets;
+
+    void visitReference(ReferenceTypeDescr &descr, uint8_t *mem);
+
+    bool fillList(Vector<int32_t> &entries);
+};
+
+} // anonymous namespace
+
+void
+TraceListVisitor::visitReference(ReferenceTypeDescr &descr, uint8_t *mem)
+{
+    VectorType *offsets;
+    switch (descr.type()) {
+      case ReferenceTypeDescr::TYPE_ANY: offsets = &valueOffsets; break;
+      case ReferenceTypeDescr::TYPE_OBJECT: offsets = &objectOffsets; break;
+      case ReferenceTypeDescr::TYPE_STRING: offsets = &stringOffsets; break;
+      default: MOZ_CRASH("Invalid kind");
+    }
+
+    if (!offsets->append((uintptr_t) mem))
+        CrashAtUnhandlableOOM("TraceListVisitor::visitReference");
+}
+
+bool
+TraceListVisitor::fillList(Vector<int32_t> &entries)
+{
+    return entries.appendAll(stringOffsets) &&
+           entries.append(-1) &&
+           entries.appendAll(objectOffsets) &&
+           entries.append(-1) &&
+           entries.appendAll(valueOffsets) &&
+           entries.append(-1);
+}
+
+static bool
+CreateTraceList(JSContext *cx, HandleTypeDescr descr)
+{
+    // Trace lists are only used for inline typed objects. We don't use them
+    // for larger objects, both to limit the size of the trace lists and
+    // because tracing outline typed objects is considerably more complicated
+    // than inline ones.
+    if ((size_t) descr->size() > InlineTypedObject::MaximumSize || descr->transparent()) {
+        descr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(nullptr));
+        return true;
+    }
+
+    TraceListVisitor visitor;
+    visitReferences(*descr, nullptr, visitor);
+
+    Vector<int32_t> entries(cx);
+    if (!visitor.fillList(entries))
+        return false;
+
+    // Trace lists aren't necessary for descriptors with no references.
+    MOZ_ASSERT(entries.length() >= 3);
+    if (entries.length() == 3) {
+        descr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(nullptr));
+        return true;
+    }
+
+    int32_t *list = cx->pod_malloc<int32_t>(entries.length());
+    if (!list)
+        return false;
+
+    PodCopy(list, entries.begin(), entries.length());
+
+    descr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(list));
+    return true;
+}
+
+/* static */ void
+TypeDescr::finalize(FreeOp *fop, JSObject *obj)
+{
+    js_free(const_cast<int32_t *>(obj->as<TypeDescr>().traceList()));
+}
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -191,18 +191,34 @@ class TypeDescr : public NativeObject
     int32_t alignment() const {
         return getReservedSlot(JS_DESCR_SLOT_ALIGNMENT).toInt32();
     }
 
     int32_t size() const {
         return getReservedSlot(JS_DESCR_SLOT_SIZE).toInt32();
     }
 
+    // Type descriptors may contain a list of their references for use during
+    // scanning. Marking code is optimized to use this list to mark inline
+    // typed objects, rather than the slower trace hook. This list is only
+    // specified when (a) the descriptor is short enough that it can fit in an
+    // InlineTypedObject, and (b) the descriptor contains at least one
+    // reference. Otherwise it is null.
+    //
+    // The list is three consecutive arrays of int32_t offsets, with each array
+    // terminated by -1. The arrays store offsets of string, object, and value
+    // references in the descriptor, in that order.
+    const int32_t *traceList() const {
+        return reinterpret_cast<int32_t *>(getReservedSlot(JS_DESCR_SLOT_TRACE_LIST).toPrivate());
+    }
+
     void initInstances(const JSRuntime *rt, uint8_t *mem, size_t length);
     void traceInstances(JSTracer *trace, uint8_t *mem, size_t length);
+
+    static void finalize(FreeOp *fop, JSObject *obj);
 };
 
 typedef Handle<TypeDescr*> HandleTypeDescr;
 
 class SimpleTypeDescr : public TypeDescr
 {
 };
 
--- a/js/src/builtin/TypedObjectConstants.h
+++ b/js/src/builtin/TypedObjectConstants.h
@@ -25,33 +25,32 @@
 
 // Slots on all type objects
 #define JS_DESCR_SLOT_KIND               0  // Atomized string representation
 #define JS_DESCR_SLOT_STRING_REPR        1  // Atomized string representation
 #define JS_DESCR_SLOT_ALIGNMENT          2  // Alignment in bytes
 #define JS_DESCR_SLOT_SIZE               3  // Size in bytes, else 0
 #define JS_DESCR_SLOT_OPAQUE             4  // Atomized string representation
 #define JS_DESCR_SLOT_TYPROTO            5  // Prototype for instances, if any
+#define JS_DESCR_SLOT_TRACE_LIST         6  // List of references for use in tracing
 
 // Slots on scalars, references, and x4s
-#define JS_DESCR_SLOT_TYPE               6  // Type code
-
-// Slots on all array descriptors
-#define JS_DESCR_SLOT_ARRAY_ELEM_TYPE    6
+#define JS_DESCR_SLOT_TYPE               7  // Type code
 
 // Slots on array descriptors
-#define JS_DESCR_SLOT_ARRAY_LENGTH 7
+#define JS_DESCR_SLOT_ARRAY_ELEM_TYPE    7
+#define JS_DESCR_SLOT_ARRAY_LENGTH       8
 
 // Slots on struct type objects
-#define JS_DESCR_SLOT_STRUCT_FIELD_NAMES 6
-#define JS_DESCR_SLOT_STRUCT_FIELD_TYPES 7
-#define JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS 8
+#define JS_DESCR_SLOT_STRUCT_FIELD_NAMES 7
+#define JS_DESCR_SLOT_STRUCT_FIELD_TYPES 8
+#define JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS 9
 
 // Maximum number of slots for any descriptor
-#define JS_DESCR_SLOTS                   9
+#define JS_DESCR_SLOTS                   10
 
 // These constants are for use exclusively in JS code. In C++ code,
 // prefer TypeRepresentation::Scalar etc, which allows you to
 // write a switch which will receive a warning if you omit a case.
 #define JS_TYPEREPR_SCALAR_KIND         1
 #define JS_TYPEREPR_REFERENCE_KIND      2
 #define JS_TYPEREPR_STRUCT_KIND         3
 #define JS_TYPEREPR_ARRAY_KIND          4
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1695,16 +1695,46 @@ GCMarker::processMarkStackOther(uintptr_
             pushValueArray(obj, vp, end);
         else
             pushObject(obj);
     } else if (tag == JitCodeTag) {
         MarkChildren(this, reinterpret_cast<jit::JitCode *>(addr));
     }
 }
 
+MOZ_ALWAYS_INLINE void
+GCMarker::markAndScanString(JSObject *source, JSString *str)
+{
+    if (!str->isPermanentAtom()) {
+        JS_COMPARTMENT_ASSERT_STR(runtime(), str);
+        MOZ_ASSERT(runtime()->isAtomsZone(str->zone()) || str->zone() == source->zone());
+        if (str->markIfUnmarked())
+            ScanString(this, str);
+    }
+}
+
+MOZ_ALWAYS_INLINE void
+GCMarker::markAndScanSymbol(JSObject *source, JS::Symbol *sym)
+{
+    if (!sym->isWellKnownSymbol()) {
+        JS_COMPARTMENT_ASSERT_SYM(runtime(), sym);
+        MOZ_ASSERT(runtime()->isAtomsZone(sym->zone()) || sym->zone() == source->zone());
+        if (sym->markIfUnmarked())
+            ScanSymbol(this, sym);
+    }
+}
+
+MOZ_ALWAYS_INLINE bool
+GCMarker::markObject(JSObject *source, JSObject *obj)
+{
+    JS_COMPARTMENT_ASSERT(runtime(), obj);
+    MOZ_ASSERT(obj->compartment() == source->compartment());
+    return obj->asTenured().markIfUnmarked(getMarkColor());
+}
+
 inline void
 GCMarker::processMarkStackTop(SliceBudget &budget)
 {
     /*
      * The function uses explicit goto and implements the scanning of the
      * object directly. It allows to eliminate the tail recursion and
      * significantly improve the marking performance, see bug 641025.
      */
@@ -1737,44 +1767,65 @@ GCMarker::processMarkStackTop(SliceBudge
     processMarkStackOther(tag, addr);
     return;
 
   scan_value_array:
     MOZ_ASSERT(vp <= end);
     while (vp != end) {
         const Value &v = *vp++;
         if (v.isString()) {
-            JSString *str = v.toString();
-            if (!str->isPermanentAtom()) {
-                JS_COMPARTMENT_ASSERT_STR(runtime(), str);
-                MOZ_ASSERT(runtime()->isAtomsZone(str->zone()) || str->zone() == obj->zone());
-                if (str->markIfUnmarked())
-                    ScanString(this, str);
-            }
+            markAndScanString(obj, v.toString());
         } else if (v.isObject()) {
             JSObject *obj2 = &v.toObject();
-            JS_COMPARTMENT_ASSERT(runtime(), obj2);
-            MOZ_ASSERT(obj->compartment() == obj2->compartment());
-            if (obj2->asTenured().markIfUnmarked(getMarkColor())) {
+            if (markObject(obj, obj2)) {
                 pushValueArray(obj, vp, end);
                 obj = obj2;
                 goto scan_obj;
             }
         } else if (v.isSymbol()) {
-            JS::Symbol *sym = v.toSymbol();
-            if (!sym->isWellKnownSymbol()) {
-                JS_COMPARTMENT_ASSERT_SYM(runtime(), sym);
-                MOZ_ASSERT(runtime()->isAtomsZone(sym->zone()) || sym->zone() == obj->zone());
-                if (sym->markIfUnmarked())
-                    ScanSymbol(this, sym);
-            }
+            markAndScanSymbol(obj, v.toSymbol());
         }
     }
     return;
 
+  scan_typed_obj:
+    {
+        const int32_t *list = obj->as<InlineOpaqueTypedObject>().typeDescr().traceList();
+        if (!list)
+            return;
+        uint8_t *memory = obj->as<InlineOpaqueTypedObject>().inlineTypedMem();
+        while (*list != -1) {
+            JSString *str = *reinterpret_cast<JSString **>(memory + *list);
+            markAndScanString(obj, str);
+            list++;
+        }
+        list++;
+        while (*list != -1) {
+            JSObject *obj2 = *reinterpret_cast<JSObject **>(memory + *list);
+            if (obj2 && markObject(obj, obj2))
+                pushObject(obj2);
+            list++;
+        }
+        list++;
+        while (*list != -1) {
+            const Value &v = *reinterpret_cast<Value *>(memory + *list);
+            if (v.isString()) {
+                markAndScanString(obj, v.toString());
+            } else if (v.isObject()) {
+                JSObject *obj2 = &v.toObject();
+                if (markObject(obj, obj2))
+                    pushObject(obj2);
+            } else if (v.isSymbol()) {
+                markAndScanSymbol(obj, v.toSymbol());
+            }
+            list++;
+        }
+        return;
+    }
+
   scan_obj:
     {
         JS_COMPARTMENT_ASSERT(runtime(), obj);
 
         budget.step();
         if (budget.isOverBudget()) {
             pushObject(obj);
             return;
@@ -1792,16 +1843,18 @@ GCMarker::processMarkStackTop(SliceBudge
             // Global objects all have the same trace hook. That hook is safe without barriers
             // if the global has no custom trace hook of its own, or has been moved to a different
             // compartment, and so can't have one.
             MOZ_ASSERT_IF(runtime()->gc.isIncrementalGCEnabled() &&
                           !(clasp->trace == JS_GlobalObjectTraceHook &&
                             (!obj->compartment()->options().getTrace() ||
                              !obj->isOwnGlobal())),
                           clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS);
+            if (clasp->trace == InlineTypedObject::obj_trace)
+                goto scan_typed_obj;
             clasp->trace(this, obj);
         }
 
         if (!shape->isNative())
             return;
 
         NativeObject *nobj = &obj->as<NativeObject>();
         unsigned nslots = nobj->slotSpan();
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -270,16 +270,20 @@ class GCMarker : public JSTracer
         return stack.isEmpty();
     }
 
     bool restoreValueArray(NativeObject *obj, void **vpp, void **endp);
     void saveValueRanges();
     inline void processMarkStackTop(SliceBudget &budget);
     void processMarkStackOther(uintptr_t tag, uintptr_t addr);
 
+    void markAndScanString(JSObject *source, JSString *str);
+    void markAndScanSymbol(JSObject *source, JS::Symbol *sym);
+    bool markObject(JSObject *source, JSObject *obj);
+
     void appendGrayRoot(void *thing, JSGCTraceKind kind);
 
     /* The color is only applied to objects and functions. */
     uint32_t color;
 
     /* Pointer to the top of the stack of arenas we are delaying marking on. */
     js::gc::ArenaHeader *unmarkedArenaStackTop;