Bug 1331034 - Introduce CacheIRSpewer. r=h4writer
authorTom Schuster <evilpies@gmail.com>
Wed, 08 Feb 2017 14:46:17 +0100
changeset 341341 5c031de2ce9025cf8fef6901fe3753eb998d8d7d
parent 341340 a04acd9bb5d2a8870c16da59f1a6d9e7057b8108
child 341342 ff631fc098b45064d45d20614d35b5eb9ab02462
push id86690
push userevilpies@gmail.com
push dateWed, 08 Feb 2017 13:46:28 +0000
treeherdermozilla-inbound@5c031de2ce90 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs1331034
milestone54.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 1331034 - Introduce CacheIRSpewer. r=h4writer
js/src/jit/BaselineIC.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRSpewer.cpp
js/src/jit/CacheIRSpewer.h
js/src/jit/IonIC.cpp
js/src/jit/JSONPrinter.cpp
js/src/jit/JSONPrinter.h
js/src/jit/JitSpewer.cpp
js/src/jit/SharedIC.cpp
js/src/moz.build
js/src/vm/MutexIDs.h
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -815,18 +815,18 @@ DoGetElemFallback(JSContext* cx, Baselin
         // But for now we just bail.
         stub->noteUnoptimizableAccess();
         attached = true;
     }
 
     bool isTemporarilyUnoptimizable = false;
     if (!attached && !JitOptions.disableCacheIR) {
         ICStubEngine engine = ICStubEngine::Baseline;
-        GetPropIRGenerator gen(cx, pc, CacheKind::GetElem, engine, &isTemporarilyUnoptimizable,
-                               lhs, rhs, CanAttachGetter::Yes);
+        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, engine,
+                               &isTemporarilyUnoptimizable, lhs, rhs, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         engine, info.outerScript(cx), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
@@ -1105,17 +1105,17 @@ DoSetElemFallback(JSContext* cx, Baselin
         return false;
 
     bool isTemporarilyUnoptimizable = false;
 
     bool attached = false;
     if (stub->numOptimizedStubs() < ICSetElem_Fallback::MAX_OPTIMIZED_STUBS &&
         !JitOptions.disableCacheIR)
     {
-        SetPropIRGenerator gen(cx, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
                                objv, index, rhs);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         ICStubEngine::Baseline, frame->script(), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
 
@@ -1188,28 +1188,30 @@ DoSetElemFallback(JSContext* cx, Baselin
 
     if (stub->numOptimizedStubs() >= ICSetElem_Fallback::MAX_OPTIMIZED_STUBS) {
         // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
         // But for now we just bail.
         return true;
     }
 
     if (!JitOptions.disableCacheIR) {
-        SetPropIRGenerator gen(cx, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
                                objv, index, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         ICStubEngine::Baseline, frame->script(), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
                 newStub->toCacheIR_Updated()->updateStubGroup() = gen.updateStubGroup();
                 newStub->toCacheIR_Updated()->updateStubId() = gen.updateStubId();
                 return true;
             }
+        } else {
+            gen.trackNotAttached();
         }
     }
 
     // Try to generate new stubs.
     if (IsNativeOrUnboxedDenseElementAccess(obj, index) && !rhs.isMagic(JS_ELEMENTS_HOLE)) {
         bool addingCase;
         size_t protoDepth;
 
@@ -1958,25 +1960,26 @@ DoInFallback(JSContext* cx, BaselineFram
         return false;
     }
 
     bool attached = false;
 
     if (stub->numOptimizedStubs() >= ICIn_Fallback::MAX_OPTIMIZED_STUBS)
         attached = true;
 
+    RootedScript script(cx, frame->script());
     RootedObject obj(cx, &objValue.toObject());
-    jsbytecode* pc = stub->icEntry()->pc(frame->script());
+    jsbytecode* pc = stub->icEntry()->pc(script);
 
     if (!attached && !JitOptions.disableCacheIR) {
         ICStubEngine engine = ICStubEngine::Baseline;
-        InIRGenerator gen(cx, pc, key, obj);
+        InIRGenerator gen(cx, script, pc, key, obj);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        engine, frame->script(), stub);
+                                                        engine, script, stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
             }
         }
     }
 
     bool cond = false;
@@ -2034,17 +2037,17 @@ DoGetNameFallback(JSContext* cx, Baselin
     // Attach new stub.
     if (stub->numOptimizedStubs() >= ICGetName_Fallback::MAX_OPTIMIZED_STUBS) {
         // TODO: Discard all stubs in this IC and replace with generic stub.
         attached = true;
     }
 
     if (!attached && !JitOptions.disableCacheIR) {
         ICStubEngine engine = ICStubEngine::Baseline;
-        GetNameIRGenerator gen(cx, pc, script, envChain, name);
+        GetNameIRGenerator gen(cx, script, pc, envChain, name);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         engine, info.outerScript(cx), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
             }
         }
@@ -2266,17 +2269,17 @@ DoSetPropFallback(JSContext* cx, Baselin
     // end up attaching a stub for the exact same access later.
     bool isTemporarilyUnoptimizable = false;
 
     bool attached = false;
     if (stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
         !JitOptions.disableCacheIR)
     {
         RootedValue idVal(cx, StringValue(name));
-        SetPropIRGenerator gen(cx, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
                                lhs, idVal, rhs);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         ICStubEngine::Baseline, frame->script(), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
 
@@ -2335,27 +2338,29 @@ DoSetPropFallback(JSContext* cx, Baselin
     if (stub.invalid())
         return true;
 
     if (!attached &&
         stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
         !JitOptions.disableCacheIR)
     {
         RootedValue idVal(cx, StringValue(name));
-        SetPropIRGenerator gen(cx, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
                                lhs, idVal, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         ICStubEngine::Baseline, frame->script(), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
                 newStub->toCacheIR_Updated()->updateStubGroup() = gen.updateStubGroup();
                 newStub->toCacheIR_Updated()->updateStubId() = gen.updateStubId();
             }
+        } else {
+            gen.trackNotAttached();
         }
     }
 
     if (!attached && !isTemporarilyUnoptimizable)
         stub->noteUnoptimizableAccess();
 
     return true;
 }
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -4,41 +4,50 @@
  * 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/. */
 
 #include "jit/CacheIR.h"
 
 #include "mozilla/FloatingPoint.h"
 
 #include "jit/BaselineIC.h"
+#include "jit/CacheIRSpewer.h"
 #include "jit/IonCaches.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::Maybe;
 
-IRGenerator::IRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind)
+const char* js::jit::CacheKindNames[] = {
+#define DEFINE_KIND(kind) #kind,
+    CACHE_IR_KINDS(DEFINE_KIND)
+#undef DEFINE_KIND
+};
+
+
+IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind)
   : writer(cx),
     cx_(cx),
+    script_(script),
     pc_(pc),
     cacheKind_(cacheKind)
 {}
 
-GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
-                                       ICStubEngine engine,
+GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+                                       CacheKind cacheKind, ICStubEngine engine,
                                        bool* isTemporarilyUnoptimizable,
                                        HandleValue val, HandleValue idVal,
                                        CanAttachGetter canAttachGetter)
-  : IRGenerator(cx, pc, cacheKind),
+  : IRGenerator(cx, script, pc, cacheKind),
     val_(val),
     idVal_(idVal),
     engine_(engine),
     isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
     canAttachGetter_(canAttachGetter),
     preliminaryObjectAction_(PreliminaryObjectAction::None)
 {}
 
@@ -149,16 +158,18 @@ GetPropIRGenerator::tryAttachStub()
             if (tryAttachModuleNamespace(obj, objId, id))
                 return true;
             if (tryAttachWindowProxy(obj, objId, id))
                 return true;
             if (tryAttachFunction(obj, objId, id))
                 return true;
             if (tryAttachProxy(obj, objId, id))
                 return true;
+
+            trackNotAttached();
             return false;
         }
 
         MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
 
         if (tryAttachProxyElement(obj, objId))
             return true;
 
@@ -170,41 +181,49 @@ GetPropIRGenerator::tryAttachStub()
             if (tryAttachDenseElement(obj, objId, index, indexId))
                 return true;
             if (tryAttachDenseElementHole(obj, objId, index, indexId))
                 return true;
             if (tryAttachUnboxedArrayElement(obj, objId, index, indexId))
                 return true;
             if (tryAttachArgumentsObjectArg(obj, objId, index, indexId))
                 return true;
+
+            trackNotAttached();
             return false;
         }
 
+        trackNotAttached();
         return false;
     }
 
     if (nameOrSymbol) {
         if (tryAttachPrimitive(valId, id))
             return true;
         if (tryAttachStringLength(valId, id))
             return true;
         if (tryAttachMagicArgumentsName(valId, id))
             return true;
+
+        trackNotAttached();
         return false;
     }
 
     if (idVal_.isInt32()) {
         ValOperandId indexId = getElemKeyValueId();
         if (tryAttachStringChar(valId, indexId))
             return true;
         if (tryAttachMagicArgument(valId, indexId))
             return true;
+
+        trackNotAttached();
         return false;
     }
 
+    trackNotAttached();
     return false;
 }
 
 bool
 GetPropIRGenerator::tryAttachIdempotentStub()
 {
     // For idempotent ICs, only attach stubs for plain data properties.
     // This ensures (1) the lookup has no side-effects and (2) Ion has complete
@@ -494,20 +513,24 @@ GetPropIRGenerator::tryAttachNative(Hand
                 if (IsPreliminaryObject(obj))
                     preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
                 else
                     preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
             }
         }
         EmitReadSlotResult(writer, obj, holder, shape, objId);
         EmitReadSlotReturn(writer, obj, holder, shape);
+
+        trackAttached("NativeSlot");
         return true;
       case CanAttachCallGetter:
         maybeEmitIdGuard(id);
         EmitCallGetterResult(writer, obj, holder, shape, objId);
+
+        trackAttached("NativeGetter");
         return true;
     }
 
     MOZ_CRASH("Bad NativeGetPropCacheability");
 }
 
 bool
 GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id)
@@ -537,16 +560,18 @@ GetPropIRGenerator::tryAttachWindowProxy
 
       case CanAttachReadSlot: {
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::WindowProxy);
 
         ObjOperandId windowObjId = writer.loadObject(windowObj);
         EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
         EmitReadSlotReturn(writer, windowObj, holder, shape);
+
+        trackAttached("WindowProxySlot");
         return true;
       }
 
       case CanAttachCallGetter: {
         if (!IsCacheableGetPropCallNative(windowObj, holder, shape))
             return false;
 
         // Make sure the native getter is okay with the IC passing the Window
@@ -557,16 +582,18 @@ GetPropIRGenerator::tryAttachWindowProxy
             return false;
 
         // Guard the incoming object is a WindowProxy and inline a getter call based
         // on the Window object.
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::WindowProxy);
         ObjOperandId windowObjId = writer.loadObject(windowObj);
         EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId);
+
+        trackAttached("WindowProxyGetter");
         return true;
       }
     }
 
     MOZ_CRASH("Unreachable");
 }
 
 bool
@@ -586,16 +613,18 @@ GetPropIRGenerator::tryAttachGenericProx
         // We could call maybeEmitIdGuard here and then emit CallProxyGetResult,
         // but for GetElem we prefer to attach a stub that can handle any Value
         // so we don't attach a new stub for every id.
         MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
         writer.callProxyGetByValueResult(objId, getElemKeyValueId());
     }
 
     writer.typeMonitorResult();
+
+    trackAttached("GenericProxy");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
@@ -646,32 +675,36 @@ GetPropIRGenerator::tryAttachDOMProxyExp
         EmitLoadSlotResult(writer, expandoObjId, &expandoObj->as<NativeObject>(), propShape);
         writer.typeMonitorResult();
     } else {
         // Call the getter. Note that we pass objId, the DOM proxy, as |this|
         // and not the expando object.
         MOZ_ASSERT(canCache == CanAttachCallGetter);
         EmitCallGetterResultNoGuards(writer, expandoObj, expandoObj, propShape, objId);
     }
+
+    trackAttached("DOMProxyExpando");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
     maybeEmitIdGuard(id);
     writer.guardShape(objId, obj->maybeShape());
 
     // No need for more guards: we know this is a DOM proxy, since the shape
     // guard enforces a given JSClass, so just go ahead and emit the call to
     // ProxyGet.
     writer.callProxyGetResult(objId, id);
     writer.typeMonitorResult();
+
+    trackAttached("DOMProxyShadowed");
     return true;
 }
 
 // Callers are expected to have already guarded on the shape of the
 // object, which guarantees the object is a DOM proxy.
 static void
 CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer, JSObject* obj, jsid id,
                                   ObjOperandId objId)
@@ -750,16 +783,17 @@ GetPropIRGenerator::tryAttachDOMProxyUns
         }
     } else {
         // Property was not found on the prototype chain. Deoptimize down to
         // proxy get call.
         writer.callProxyGetResult(objId, id);
         writer.typeMonitorResult();
     }
 
+    trackAttached("DOMProxyUnshadowed");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     switch (GetProxyStubType(cx_, obj, id)) {
       case ProxyStubType::None:
@@ -801,16 +835,18 @@ GetPropIRGenerator::tryAttachUnboxed(Han
     writer.loadUnboxedPropertyResult(objId, property->type,
                                      UnboxedPlainObject::offsetOfData() + property->offset);
     if (property->type == JSVAL_TYPE_OBJECT)
         writer.typeMonitorResult();
     else
         writer.returnFromIC();
 
     preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
+
+    trackAttached("Unboxed");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     if (!obj->is<UnboxedPlainObject>())
         return false;
@@ -821,16 +857,18 @@ GetPropIRGenerator::tryAttachUnboxedExpa
 
     Shape* shape = expando->lookup(cx_, id);
     if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
         return false;
 
     maybeEmitIdGuard(id);
     EmitReadSlotResult(writer, obj, obj, shape, objId);
     EmitReadSlotReturn(writer, obj, obj, shape);
+
+    trackAttached("UnboxedExpando");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     if (!obj->is<TypedObject>() ||
         !cx_->runtime()->jitSupportsFloatingPoint ||
@@ -873,16 +911,17 @@ GetPropIRGenerator::tryAttachTypedObject
         monitorLoad = type != ReferenceTypeDescr::TYPE_STRING;
     }
 
     if (monitorLoad)
         writer.typeMonitorResult();
     else
         writer.returnFromIC();
 
+    trackAttached("TypedObject");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     if (!JSID_IS_ATOM(id, cx_->names().length))
         return false;
@@ -892,37 +931,43 @@ GetPropIRGenerator::tryAttachObjectLengt
         // the stub can return int32 values without monitoring the result.
         if (obj->as<ArrayObject>().length() > INT32_MAX)
             return false;
 
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::Array);
         writer.loadInt32ArrayLengthResult(objId);
         writer.returnFromIC();
+
+        trackAttached("ArrayLength");
         return true;
     }
 
     if (obj->is<UnboxedArrayObject>()) {
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::UnboxedArray);
         writer.loadUnboxedArrayLengthResult(objId);
         writer.returnFromIC();
+
+        trackAttached("UnboxedArrayLength");
         return true;
     }
 
     if (obj->is<ArgumentsObject>() && !obj->as<ArgumentsObject>().hasOverriddenLength()) {
         maybeEmitIdGuard(id);
         if (obj->is<MappedArgumentsObject>()) {
             writer.guardClass(objId, GuardClassKind::MappedArguments);
         } else {
             MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
             writer.guardClass(objId, GuardClassKind::UnmappedArguments);
         }
         writer.loadArgumentsObjectLengthResult(objId);
         writer.returnFromIC();
+
+        trackAttached("ArgumentsObjectLength");
         return true;
     }
 
     return false;
 }
 
 bool
 GetPropIRGenerator::tryAttachFunction(HandleObject obj, ObjOperandId objId, HandleId id)
@@ -949,16 +994,18 @@ GetPropIRGenerator::tryAttachFunction(Ha
         // Lazy functions don't store the length.
         if (fun->isInterpretedLazy())
             return false;
 
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::JSFunction);
         writer.loadFunctionLengthResult(objId);
         writer.returnFromIC();
+
+        trackAttached("FunctionLength");
         return true;
     }
 
     return false;
 }
 
 bool
 GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId, HandleId id)
@@ -981,16 +1028,18 @@ GetPropIRGenerator::tryAttachModuleNames
 
     // Check for the specific namespace object.
     maybeEmitIdGuard(id);
     writer.guardSpecificObject(objId, ns);
 
     ObjOperandId envId = writer.loadObject(env);
     EmitLoadSlotResult(writer, envId, env, shape);
     writer.typeMonitorResult();
+
+    trackAttached("ModuleNamespace");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, HandleId id)
 {
     JSValueType primitiveType;
     RootedNativeObject proto(cx_);
@@ -1032,29 +1081,33 @@ GetPropIRGenerator::tryAttachPrimitive(V
     }
 
     writer.guardType(valId, primitiveType);
     maybeEmitIdGuard(id);
 
     ObjOperandId protoId = writer.loadObject(proto);
     EmitReadSlotResult(writer, proto, holder, shape, protoId);
     EmitReadSlotReturn(writer, proto, holder, shape);
+
+    trackAttached("Primitive");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachStringLength(ValOperandId valId, HandleId id)
 {
     if (!val_.isString() || !JSID_IS_ATOM(id, cx_->names().length))
         return false;
 
     StringOperandId strId = writer.guardIsString(valId);
     maybeEmitIdGuard(id);
     writer.loadStringLengthResult(strId);
     writer.returnFromIC();
+
+    trackAttached("StringLength");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId)
 {
     MOZ_ASSERT(idVal_.isInt32());
 
@@ -1069,16 +1122,18 @@ GetPropIRGenerator::tryAttachStringChar(
     {
         return false;
     }
 
     StringOperandId strId = writer.guardIsString(valId);
     Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId);
     writer.loadStringCharResult(strId, int32IndexId);
     writer.returnFromIC();
+
+    trackAttached("StringChar");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachMagicArgumentsName(ValOperandId valId, HandleId id)
 {
     if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS))
         return false;
@@ -1094,16 +1149,17 @@ GetPropIRGenerator::tryAttachMagicArgume
         writer.loadFrameNumActualArgsResult();
         writer.returnFromIC();
     } else {
         MOZ_ASSERT(JSID_IS_ATOM(id, cx_->names().callee));
         writer.loadFrameCalleeResult();
         writer.typeMonitorResult();
     }
 
+    trackAttached("MagicArgumentsName");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachMagicArgument(ValOperandId valId, ValOperandId indexId)
 {
     MOZ_ASSERT(idVal_.isInt32());
 
@@ -1111,16 +1167,18 @@ GetPropIRGenerator::tryAttachMagicArgume
         return false;
 
     writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS);
     writer.guardFrameHasNoArgumentsObject();
 
     Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId);
     writer.loadFrameArgumentResult(int32IndexId);
     writer.typeMonitorResult();
+
+    trackAttached("MagicArgument");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachArgumentsObjectArg(HandleObject obj, ObjOperandId objId,
                                                 uint32_t index, Int32OperandId indexId)
 {
     if (!obj->is<ArgumentsObject>() || obj->as<ArgumentsObject>().hasOverriddenElement())
@@ -1130,32 +1188,36 @@ GetPropIRGenerator::tryAttachArgumentsOb
         writer.guardClass(objId, GuardClassKind::MappedArguments);
     } else {
         MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
         writer.guardClass(objId, GuardClassKind::UnmappedArguments);
     }
 
     writer.loadArgumentsObjectArgResult(objId, indexId);
     writer.typeMonitorResult();
+
+    trackAttached("ArgumentsObjectArg");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachDenseElement(HandleObject obj, ObjOperandId objId,
                                           uint32_t index, Int32OperandId indexId)
 {
     if (!obj->isNative())
         return false;
 
     if (!obj->as<NativeObject>().containsDenseElement(index))
         return false;
 
     writer.guardShape(objId, obj->as<NativeObject>().lastProperty());
     writer.loadDenseElementResult(objId, indexId);
     writer.typeMonitorResult();
+
+    trackAttached("DenseElement");
     return true;
 }
 
 static bool
 CanAttachDenseElementHole(JSObject* obj)
 {
     // Make sure the objects on the prototype don't have any indexed properties
     // or that such properties can't appear without a shape change.
@@ -1224,16 +1286,18 @@ GetPropIRGenerator::tryAttachDenseElemen
         // Also make sure there are no dense elements.
         writer.guardNoDenseElements(protoId);
 
         pobj = pobj->staticPrototype();
     }
 
     writer.loadDenseElementHoleResult(objId, indexId);
     writer.typeMonitorResult();
+
+    trackAttached("DenseElementHole");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachUnboxedArrayElement(HandleObject obj, ObjOperandId objId,
                                                  uint32_t index, Int32OperandId indexId)
 {
     if (!obj->is<UnboxedArrayObject>())
@@ -1248,16 +1312,17 @@ GetPropIRGenerator::tryAttachUnboxedArra
     writer.loadUnboxedArrayElementResult(objId, indexId, elementType);
 
     // Only monitor the result if its type might change.
     if (elementType == JSVAL_TYPE_OBJECT)
         writer.typeMonitorResult();
     else
         writer.returnFromIC();
 
+    trackAttached("UnboxedArrayElement");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachTypedElement(HandleObject obj, ObjOperandId objId,
                                           uint32_t index, Int32OperandId indexId)
 {
     if (!obj->is<TypedArrayObject>() && !IsPrimitiveArrayTypedObject(obj))
@@ -1284,16 +1349,18 @@ GetPropIRGenerator::tryAttachTypedElemen
     writer.loadTypedElementResult(objId, indexId, layout, TypedThingElementType(obj));
 
     // Reading from Uint32Array may produce an int32 now but a double value
     // later, so ensure we monitor the result.
     if (TypedThingElementType(obj) == Scalar::Type::Uint32)
         writer.typeMonitorResult();
     else
         writer.returnFromIC();
+
+    trackAttached("TypedElement");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachProxyElement(HandleObject obj, ObjOperandId objId)
 {
     if (!obj->is<ProxyObject>())
         return false;
@@ -1303,20 +1370,53 @@ GetPropIRGenerator::tryAttachProxyElemen
     // We are not guarding against DOM proxies here, because there is no other
     // specialized DOM IC we could attach.
     // We could call maybeEmitIdGuard here and then emit CallProxyGetResult,
     // but for GetElem we prefer to attach a stub that can handle any Value
     // so we don't attach a new stub for every id.
     MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
     writer.callProxyGetByValueResult(objId, getElemKeyValueId());
     writer.typeMonitorResult();
+
+    trackAttached("ProxyElement");
     return true;
 }
 
 void
+GetPropIRGenerator::trackAttached(const char* name)
+{
+#ifdef JS_JITSPEW
+    CacheIRSpewer& sp = GetCacheIRSpewerSingleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        sp.valueProperty(guard, "base", val_);
+        sp.valueProperty(guard, "property", idVal_);
+        sp.attached(guard, name);
+        sp.endCache(guard);
+    }
+#endif
+}
+
+void
+GetPropIRGenerator::trackNotAttached()
+{
+#ifdef JS_JITSPEW
+    CacheIRSpewer& sp = GetCacheIRSpewerSingleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        sp.valueProperty(guard, "base", val_);
+        sp.valueProperty(guard, "property", idVal_);
+        sp.endCache(guard);
+    }
+#endif
+}
+
+void
 IRGenerator::emitIdGuard(ValOperandId valId, jsid id)
 {
     if (JSID_IS_SYMBOL(id)) {
         SymbolOperandId symId = writer.guardIsSymbol(valId);
         writer.guardSpecificSymbol(symId, JSID_TO_SYMBOL(id));
     } else {
         MOZ_ASSERT(JSID_IS_ATOM(id));
         StringOperandId strId = writer.guardIsString(valId);
@@ -1345,20 +1445,20 @@ SetPropIRGenerator::maybeEmitIdGuard(jsi
         MOZ_ASSERT(&idVal_.toString()->asAtom() == JSID_TO_ATOM(id));
         return;
     }
 
     MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
     emitIdGuard(setElemKeyValueId(), id);
 }
 
-GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, jsbytecode* pc, HandleScript script,
+
+GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                                        HandleObject env, HandlePropertyName name)
-  : IRGenerator(cx, pc, CacheKind::GetName),
-    script_(script),
+  : IRGenerator(cx, script, pc, CacheKind::GetName),
     env_(env),
     name_(name)
 {}
 
 bool
 GetNameIRGenerator::tryAttachStub()
 {
     MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
@@ -1567,33 +1667,36 @@ GetNameIRGenerator::tryAttachEnvironment
         size_t dynamicSlotOffset = holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
         writer.loadEnvironmentDynamicSlotResult(lastObjId, dynamicSlotOffset);
     }
 
     writer.typeMonitorResult();
     return true;
 }
 
-InIRGenerator::InIRGenerator(JSContext* cx, jsbytecode* pc, HandleValue key, HandleObject obj)
-  : IRGenerator(cx, pc, CacheKind::In),
+InIRGenerator::InIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+                             HandleValue key, HandleObject obj)
+  : IRGenerator(cx, script, pc, CacheKind::In),
     key_(key), obj_(obj)
 { }
 
 bool
 InIRGenerator::tryAttachDenseIn(uint32_t index, Int32OperandId indexId,
                                 HandleObject obj, ObjOperandId objId)
 {
     if (!obj->isNative())
         return false;
     if (!obj->as<NativeObject>().containsDenseElement(index))
         return false;
 
     writer.guardShape(objId, obj->as<NativeObject>().lastProperty());
     writer.loadDenseElementExistsResult(objId, indexId);
     writer.returnFromIC();
+
+    trackAttached("DenseIn");
     return true;
 }
 
 bool
 InIRGenerator::tryAttachNativeIn(HandleId key, ValOperandId keyId,
                                  HandleObject obj, ObjOperandId objId)
 {
     PropertyResult prop;
@@ -1608,32 +1711,34 @@ InIRGenerator::tryAttachNativeIn(HandleI
         return false;
 
     Maybe<ObjOperandId> holderId;
     emitIdGuard(keyId, key);
     EmitReadSlotGuard(writer, obj, holder, prop.maybeShape(), objId, &holderId);
     writer.loadBooleanResult(true);
     writer.returnFromIC();
 
+    trackAttached("NativeIn");
     return true;
 }
 
 bool
 InIRGenerator::tryAttachNativeInDoesNotExist(HandleId key, ValOperandId keyId,
                                              HandleObject obj, ObjOperandId objId)
 {
     if (!CheckHasNoSuchProperty(cx_, obj, key))
         return false;
 
     Maybe<ObjOperandId> holderId;
     emitIdGuard(keyId, key);
     EmitReadSlotGuard(writer, obj, nullptr, nullptr, objId, &holderId);
     writer.loadBooleanResult(false);
     writer.returnFromIC();
 
+    trackAttached("NativeInDoesNotExist");
     return true;
 }
 
 bool
 InIRGenerator::tryAttachStub()
 {
     MOZ_ASSERT(cacheKind_ == CacheKind::In);
 
@@ -1650,30 +1755,68 @@ InIRGenerator::tryAttachStub()
         return false;
     }
 
     if (nameOrSymbol) {
         if (tryAttachNativeIn(id, keyId, obj_, objId))
             return true;
         if (tryAttachNativeInDoesNotExist(id, keyId, obj_, objId))
             return true;
+
+        trackNotAttached();
         return false;
     }
 
     uint32_t index;
     Int32OperandId indexId;
     if (maybeGuardInt32Index(key_, keyId, &index, &indexId)) {
         if (tryAttachDenseIn(index, indexId, obj_, objId))
             return true;
+
+        trackNotAttached();
         return false;
     }
 
+    trackNotAttached();
     return false;
 }
 
+void
+InIRGenerator::trackAttached(const char* name)
+{
+#ifdef JS_JITSPEW
+    CacheIRSpewer& sp = GetCacheIRSpewerSingleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        RootedValue objV(cx_, ObjectValue(*obj_));
+        sp.valueProperty(guard, "base", objV);
+        sp.valueProperty(guard, "property", key_);
+        sp.attached(guard, name);
+        sp.endCache(guard);
+    }
+#endif
+}
+
+void
+InIRGenerator::trackNotAttached()
+{
+#ifdef JS_JITSPEW
+    CacheIRSpewer& sp = GetCacheIRSpewerSingleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        RootedValue objV(cx_, ObjectValue(*obj_));
+        sp.valueProperty(guard, "base", objV);
+        sp.valueProperty(guard, "property", key_);
+        sp.endCache(guard);
+    }
+#endif
+}
+
 bool
 IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId,
                                   uint32_t* int32Index, Int32OperandId* int32IndexId)
 {
     if (index.isNumber()) {
         int32_t indexSigned;
         if (index.isInt32()) {
             indexSigned = index.toInt32();
@@ -1702,20 +1845,20 @@ IRGenerator::maybeGuardInt32Index(const 
         *int32Index = uint32_t(indexSigned);
         *int32IndexId = writer.guardAndGetIndexFromString(strId);
         return true;
     }
 
     return false;
 }
 
-SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
-                                       bool* isTemporarilyUnoptimizable, HandleValue lhsVal,
-                                       HandleValue idVal, HandleValue rhsVal)
-  : IRGenerator(cx, pc, cacheKind),
+SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+                                       CacheKind cacheKind, bool* isTemporarilyUnoptimizable,
+                                       HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal)
+  : IRGenerator(cx, script, pc, cacheKind),
     lhsVal_(lhsVal),
     idVal_(idVal),
     rhsVal_(rhsVal),
     isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
     preliminaryObjectAction_(PreliminaryObjectAction::None),
     updateStubGroup_(cx),
     updateStubId_(cx, JSID_EMPTY),
     needUpdateStub_(false)
@@ -1761,17 +1904,16 @@ SetPropIRGenerator::tryAttachStub()
                 return true;
             if (tryAttachTypedObjectProperty(obj, objId, id, rhsValId))
                 return true;
             if (tryAttachSetArrayLength(obj, objId, id, rhsValId))
                 return true;
         }
         return false;
     }
-
     return false;
 }
 
 static void
 EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId, NativeObject* nobj, Shape* shape,
                        ValOperandId rhsId)
 {
     if (nobj->isFixedSlot(shape->slot())) {
@@ -1832,16 +1974,18 @@ SetPropIRGenerator::tryAttachNativeSetSl
 
     if (IsPreliminaryObject(obj))
         preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
     else
         preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
 
     setUpdateStubInfo(nobj->group(), id);
     EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId);
+
+    trackAttached("NativeSlot");
     return true;
 }
 
 bool
 SetPropIRGenerator::tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId,
                                                    HandleId id, ValOperandId rhsId)
 {
     if (!obj->is<UnboxedPlainObject>())
@@ -1859,16 +2003,18 @@ SetPropIRGenerator::tryAttachUnboxedExpa
     writer.guardGroup(objId, obj->group());
     ObjOperandId expandoId = writer.guardAndLoadUnboxedExpando(objId);
     writer.guardShape(expandoId, expando->lastProperty());
 
     // Property types must be added to the unboxed object's group, not the
     // expando's group (it has unknown properties).
     setUpdateStubInfo(obj->group(), id);
     EmitStoreSlotAndReturn(writer, expandoId, expando, propShape, rhsId);
+
+    trackAttached("UnboxedExpando");
     return true;
 }
 
 static void
 EmitGuardUnboxedPropertyType(CacheIRWriter& writer, JSValueType propType, ValOperandId valId)
 {
     if (propType == JSVAL_TYPE_OBJECT) {
         // Unboxed objects store NullValue as nullptr object.
@@ -1894,16 +2040,18 @@ SetPropIRGenerator::tryAttachUnboxedProp
     EmitGuardUnboxedPropertyType(writer, property->type, rhsId);
     writer.storeUnboxedProperty(objId, property->type,
                                 UnboxedPlainObject::offsetOfData() + property->offset,
                                 rhsId);
     writer.returnFromIC();
 
     setUpdateStubInfo(obj->group(), id);
     preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
+
+    trackAttached("Unboxed");
     return true;
 }
 
 bool
 SetPropIRGenerator::tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId objId, HandleId id,
                                                  ValOperandId rhsId)
 {
     if (!obj->is<TypedObject>() || !cx_->runtime()->jitSupportsFloatingPoint)
@@ -1934,16 +2082,18 @@ SetPropIRGenerator::tryAttachTypedObject
 
     setUpdateStubInfo(obj->group(), id);
 
     // Scalar types can always be stored without a type update stub.
     if (fieldDescr->is<ScalarTypeDescr>()) {
         Scalar::Type type = fieldDescr->as<ScalarTypeDescr>().type();
         writer.storeTypedObjectScalarProperty(objId, fieldOffset, layout, type, rhsId);
         writer.returnFromIC();
+
+        trackAttached("TypedObject");
         return true;
     }
 
     // For reference types, guard on the RHS type first, so that
     // StoreTypedObjectReferenceProperty is infallible.
     ReferenceTypeDescr::Type type = fieldDescr->as<ReferenceTypeDescr>().type();
     switch (type) {
       case ReferenceTypeDescr::TYPE_ANY:
@@ -1953,19 +2103,54 @@ SetPropIRGenerator::tryAttachTypedObject
         break;
       case ReferenceTypeDescr::TYPE_STRING:
         writer.guardType(rhsId, JSVAL_TYPE_STRING);
         break;
     }
 
     writer.storeTypedObjectReferenceProperty(objId, fieldOffset, layout, type, rhsId);
     writer.returnFromIC();
+
+    trackAttached("TypedObject");
     return true;
 }
 
+void
+SetPropIRGenerator::trackAttached(const char* name)
+{
+#ifdef JS_JITSPEW
+    CacheIRSpewer& sp = GetCacheIRSpewerSingleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        sp.valueProperty(guard, "base", lhsVal_);
+        sp.valueProperty(guard, "property", idVal_);
+        sp.valueProperty(guard, "value", rhsVal_);
+        sp.attached(guard, name);
+        sp.endCache(guard);
+    }
+#endif
+}
+
+void
+SetPropIRGenerator::trackNotAttached()
+{
+#ifdef JS_JITSPEW
+    CacheIRSpewer& sp = GetCacheIRSpewerSingleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        sp.valueProperty(guard, "base", lhsVal_);
+        sp.valueProperty(guard, "property", idVal_);
+        sp.valueProperty(guard, "value", rhsVal_);
+        sp.endCache(guard);
+    }
+#endif
+}
+
 static void
 EmitCallSetterResultNoGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
                              Shape* shape, ObjOperandId objId, ValOperandId rhsId)
 {
     if (IsCacheableSetPropCallNative(obj, holder, shape)) {
         JSFunction* target = &shape->setterValue().toObject().as<JSFunction>();
         MOZ_ASSERT(target->isNative());
         writer.callNativeSetter(objId, target, rhsId);
@@ -2014,30 +2199,34 @@ SetPropIRGenerator::tryAttachSetter(Hand
         GeneratePrototypeGuards(writer, obj, holder, objId);
 
         // Guard on the holder's shape.
         ObjOperandId holderId = writer.loadObject(holder);
         writer.guardShape(holderId, holder->as<NativeObject>().lastProperty());
     }
 
     EmitCallSetterResultNoGuards(writer, obj, holder, shape, objId, rhsId);
+
+    trackAttached("Setter");
     return true;
 }
 
 bool
 SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id,
                                             ValOperandId rhsId)
 {
     if (!obj->is<ArrayObject>() || !JSID_IS_ATOM(id, cx_->names().length))
         return false;
 
     maybeEmitIdGuard(id);
     writer.guardClass(objId, GuardClassKind::Array);
     writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
     writer.returnFromIC();
+
+    trackAttached("SetArrayLength");
     return true;
 }
 
 bool
 SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape)
 {
     AutoAssertNoPendingException aanpe(cx_);
 
@@ -2184,26 +2373,29 @@ SetPropIRGenerator::tryAttachAddSlotStub
     // PlainObject.
     bool changeGroup = oldGroup != newGroup;
     MOZ_ASSERT_IF(changeGroup, obj->is<PlainObject>());
 
     if (holderOrExpando->isFixedSlot(propShape->slot())) {
         size_t offset = NativeObject::getFixedSlotOffset(propShape->slot());
         writer.addAndStoreFixedSlot(holderId, offset, rhsValId, propShape,
                                     changeGroup, newGroup);
+        trackAttached("AddSlot");
     } else {
         size_t offset = holderOrExpando->dynamicSlotIndex(propShape->slot()) * sizeof(Value);
         uint32_t numOldSlots = NativeObject::dynamicSlotsCount(oldShape);
         uint32_t numNewSlots = NativeObject::dynamicSlotsCount(propShape);
         if (numOldSlots == numNewSlots) {
             writer.addAndStoreDynamicSlot(holderId, offset, rhsValId, propShape,
                                           changeGroup, newGroup);
+            trackAttached("AddSlot");
         } else {
             MOZ_ASSERT(numNewSlots > numOldSlots);
             writer.allocateAndStoreDynamicSlot(holderId, offset, rhsValId, propShape,
                                                changeGroup, newGroup, numNewSlots);
+            trackAttached("AllocateSlot");
         }
     }
     writer.returnFromIC();
 
     setUpdateStubInfo(oldGroup, id);
     return true;
 }
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -124,26 +124,33 @@ class TypedOperandId : public OperandId
     {}
     MOZ_IMPLICIT TypedOperandId(Int32OperandId id)
       : OperandId(id.id()), type_(JSVAL_TYPE_INT32)
     {}
 
     JSValueType type() const { return type_; }
 };
 
+#define CACHE_IR_KINDS(_)   \
+    _(GetProp)              \
+    _(GetElem)              \
+    _(GetName)              \
+    _(SetProp)              \
+    _(SetElem)              \
+    _(In)
+
 enum class CacheKind : uint8_t
 {
-    GetProp,
-    GetElem,
-    GetName,
-    SetProp,
-    SetElem,
-    In,
+#define DEFINE_KIND(kind) kind,
+    CACHE_IR_KINDS(DEFINE_KIND)
+#undef DEFINE_KIND
 };
 
+extern const char* CacheKindNames[];
+
 #define CACHE_IR_OPS(_)                   \
     _(GuardIsObject)                      \
     _(GuardIsObjectOrNull)                \
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardShape)                         \
@@ -839,29 +846,32 @@ class MOZ_RAII CacheIRReader
     }
 };
 
 class MOZ_RAII IRGenerator
 {
   protected:
     CacheIRWriter writer;
     JSContext* cx_;
+    HandleScript script_;
     jsbytecode* pc_;
     CacheKind cacheKind_;
 
     IRGenerator(const IRGenerator&) = delete;
     IRGenerator& operator=(const IRGenerator&) = delete;
 
     bool maybeGuardInt32Index(const Value& index, ValOperandId indexId,
                               uint32_t* int32Index, Int32OperandId* int32IndexId);
 
     void emitIdGuard(ValOperandId valId, jsid id);
 
+    friend class CacheIRSpewer;
+
   public:
-    explicit IRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind);
+    explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind);
 
     const CacheIRWriter& writerRef() const { return writer; }
     CacheKind cacheKind() const { return cacheKind_; }
 };
 
 enum class CanAttachGetter { Yes, No };
 
 // GetPropIRGenerator generates CacheIR for a GetProp IC.
@@ -919,45 +929,47 @@ class MOZ_RAII GetPropIRGenerator : publ
     // No pc if idempotent, as there can be multiple bytecode locations
     // due to GVN.
     bool idempotent() const { return pc_ == nullptr; }
 
     // If this is a GetElem cache, emit instructions to guard the incoming Value
     // matches |id|.
     void maybeEmitIdGuard(jsid id);
 
+    void trackAttached(const char* name);
+    void trackNotAttached();
+
   public:
-    GetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind, ICStubEngine engine,
-                       bool* isTemporarilyUnoptimizable, HandleValue val, HandleValue idVal,
-                       CanAttachGetter canAttachGetter);
+    GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
+                       ICStubEngine engine, bool* isTemporarilyUnoptimizable,
+                       HandleValue val,HandleValue idVal, CanAttachGetter canAttachGetter);
 
     bool tryAttachStub();
     bool tryAttachIdempotentStub();
 
     bool shouldUnlinkPreliminaryObjectStubs() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
     }
     bool shouldNotePreliminaryObjectStub() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary;
     }
 };
 
-// GetPropIRGenerator generates CacheIR for a GetName IC.
+// GetNameIRGenerator generates CacheIR for a GetName IC.
 class MOZ_RAII GetNameIRGenerator : public IRGenerator
 {
-    HandleScript script_;
     HandleObject env_;
     HandlePropertyName name_;
 
     bool tryAttachGlobalNameValue(ObjOperandId objId, HandleId id);
     bool tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id);
     bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id);
 
   public:
-    GetNameIRGenerator(JSContext* cx, jsbytecode* pc, HandleScript script,
+    GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                        HandleObject env, HandlePropertyName name);
 
     bool tryAttachStub();
 };
 
 // SetPropIRGenerator generates CacheIR for a SetProp IC.
 class MOZ_RAII SetPropIRGenerator : public IRGenerator
 {
@@ -1004,23 +1016,26 @@ class MOZ_RAII SetPropIRGenerator : publ
                                   ValOperandId rhsId);
     bool tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId objId, HandleId id,
                                       ValOperandId rhsId);
     bool tryAttachSetter(HandleObject obj, ObjOperandId objId, HandleId id,
                          ValOperandId rhsId);
     bool tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id,
                                  ValOperandId rhsId);
 
+    void trackAttached(const char* name);
+
   public:
-    SetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
+    SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
                        bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal,
                        HandleValue rhsVal);
 
     bool tryAttachStub();
     bool tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape);
+    void trackNotAttached();
 
     bool shouldUnlinkPreliminaryObjectStubs() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
     }
     bool shouldNotePreliminaryObjectStub() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary;
     }
 
@@ -1044,18 +1059,21 @@ class MOZ_RAII InIRGenerator : public IR
 
     bool tryAttachDenseIn(uint32_t index, Int32OperandId indexId,
                           HandleObject obj, ObjOperandId objId);
     bool tryAttachNativeIn(HandleId key, ValOperandId keyId,
                            HandleObject obj, ObjOperandId objId);
     bool tryAttachNativeInDoesNotExist(HandleId key, ValOperandId keyId,
                                        HandleObject obj, ObjOperandId objId);
 
+    void trackAttached(const char* name);
+    void trackNotAttached();
+
   public:
-    InIRGenerator(JSContext* cx, jsbytecode* pc, HandleValue key, HandleObject obj);
+    InIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, HandleValue key, HandleObject obj);
 
     bool tryAttachStub();
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_CacheIR_h */
new file mode 100644
--- /dev/null
+++ b/js/src/jit/CacheIRSpewer.cpp
@@ -0,0 +1,169 @@
+/* -*- 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/. */
+
+#ifdef JS_JITSPEW
+
+#include "jit/CacheIRSpewer.h"
+
+#include "mozilla/SizePrintfMacros.h"
+#include "mozilla/Sprintf.h"
+
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h>
+#endif
+
+#include <stdarg.h>
+
+#include "jsfun.h"
+#include "jsscript.h"
+
+using namespace js;
+using namespace js::jit;
+
+CacheIRSpewer cacheIRspewer;
+
+CacheIRSpewer&
+jit::GetCacheIRSpewerSingleton()
+{
+    return cacheIRspewer;
+}
+
+CacheIRSpewer::CacheIRSpewer()
+  : outputLock(mutexid::CacheIRSpewer)
+{ }
+
+CacheIRSpewer::~CacheIRSpewer()
+{
+    if (!enabled())
+        return;
+
+    json.ref().endList();
+    output.flush();
+    output.finish();
+}
+
+#ifndef JIT_SPEW_DIR
+# if defined(_WIN32)
+#  define JIT_SPEW_DIR "."
+# elif defined(__ANDROID__)
+#  define JIT_SPEW_DIR "/data/local/tmp"
+# else
+#  define JIT_SPEW_DIR "/tmp"
+# endif
+#endif
+
+bool
+CacheIRSpewer::init()
+{
+    if (enabled())
+        return true;
+
+    char name[256];
+    uint32_t pid = getpid();
+    SprintfLiteral(name, JIT_SPEW_DIR "/cacheir%" PRIu32 ".json", pid);
+
+    if (!output.init(name))
+        return false;
+    output.put("[");
+
+    json.emplace(output);
+    return true;
+}
+
+void
+CacheIRSpewer::beginCache(LockGuard<Mutex>&, const IRGenerator& gen)
+{
+    MOZ_ASSERT(enabled());
+    JSONPrinter& j = json.ref();
+
+    j.beginObject();
+    j.stringProperty("name", "%s", CacheKindNames[uint8_t(gen.cacheKind_)]);
+    j.stringProperty("file", "%s", gen.script_->filename());
+    if (jsbytecode* pc = gen.pc_) {
+        unsigned column;
+        j.integerProperty("line", PCToLineNumber(gen.script_, pc, &column));
+        j.integerProperty("column", column);
+        j.stringProperty("pc", "%p", pc);
+    }
+}
+
+template <typename CharT>
+static void
+QuoteString(GenericPrinter& out, const CharT* s, size_t length)
+{
+    const CharT* end = s + length;
+    for (const CharT* t = s; t < end; s = ++t) {
+        // This quote implementation is probably correct,
+        // but uses \u even when not strictly necessary.
+        char16_t c = *t;
+        if (c == '"' || c == '\\') {
+            out.printf("\\");
+            out.printf("%c", char(c));
+        } else if (c < ' ' || c >= 127 || !isprint(c)) {
+            out.printf("\\u%04x", c);
+        } else {
+            out.printf("%c", char(c));
+        }
+    }
+}
+
+static void
+QuoteString(GenericPrinter& out, JSLinearString* str)
+{
+    JS::AutoCheckCannotGC nogc;
+    if (str->hasLatin1Chars())
+        QuoteString(out, str->latin1Chars(nogc), str->length());
+    else
+        QuoteString(out, str->twoByteChars(nogc), str->length());
+}
+
+void
+CacheIRSpewer::valueProperty(LockGuard<Mutex>&, const char* name, HandleValue v)
+{
+    MOZ_ASSERT(enabled());
+    JSONPrinter& j = json.ref();
+
+    j.beginObjectProperty(name);
+
+    const char* type = InformalValueTypeName(v);
+    if (v.isInt32())
+        type = "int32";
+    j.stringProperty("type", "%s", type);
+
+    if (v.isInt32()) {
+        j.integerProperty("value", v.toInt32());
+    } else if (v.isDouble()) {
+        j.doubleProperty("value", v.toDouble());
+    } else if (v.isString() || v.isSymbol()) {
+        JSString* str = v.isString() ? v.toString() : v.toSymbol()->description();
+        if (str && str->isLinear()) {
+            j.beginStringProperty("value");
+            QuoteString(output, &str->asLinear());
+            j.endStringProperty();
+        }
+    }
+
+    j.endObject();
+}
+
+void
+CacheIRSpewer::attached(LockGuard<Mutex>&, const char* name)
+{
+    MOZ_ASSERT(enabled());
+    json.ref().stringProperty("attached", "%s", name);
+}
+
+void
+CacheIRSpewer::endCache(LockGuard<Mutex>&)
+{
+    MOZ_ASSERT(enabled());
+    json.ref().endObject();
+}
+
+#endif /* JS_JITSPEW */
new file mode 100644
--- /dev/null
+++ b/js/src/jit/CacheIRSpewer.h
@@ -0,0 +1,53 @@
+/* -*- 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 jit_CacheIRSpewer_h
+#define jit_CacheIRSpewer_h
+
+#ifdef JS_JITSPEW
+
+#include "mozilla/Maybe.h"
+
+#include "jit/CacheIR.h"
+#include "jit/JSONPrinter.h"
+#include "js/TypeDecls.h"
+#include "threading/LockGuard.h"
+#include "vm/MutexIDs.h"
+
+namespace js {
+namespace jit {
+
+class CacheIRSpewer
+{
+    Mutex outputLock;
+    Fprinter output;
+    mozilla::Maybe<JSONPrinter> json;
+
+  public:
+    CacheIRSpewer();
+    ~CacheIRSpewer();
+
+    bool init();
+    bool enabled() { return json.isSome(); }
+
+    // These methods can only be called when enabled() is true.
+    Mutex& lock() { MOZ_ASSERT(enabled()); return outputLock; }
+
+    void beginCache(LockGuard<Mutex>&, const IRGenerator& generator);
+    void valueProperty(LockGuard<Mutex>&, const char* name, HandleValue v);
+    void attached(LockGuard<Mutex>&, const char* name);
+    void endCache(LockGuard<Mutex>&);
+};
+
+CacheIRSpewer&
+GetCacheIRSpewerSingleton();
+
+} // namespace jit
+} // namespace js
+
+#endif /* JS_JITSPEW */
+
+#endif /* jit_CacheIRSpewer_h */
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -133,17 +133,17 @@ IonGetPropertyIC::update(JSContext* cx, 
             // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it
             // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier
             // does not account for getters, so we should only attach a getter
             // stub if we inserted a type barrier.
             CanAttachGetter canAttachGetter =
                 ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No;
             jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc();
             bool isTemporarilyUnoptimizable;
-            GetPropIRGenerator gen(cx, pc, ic->kind(), ICStubEngine::IonIC,
+            GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), ICStubEngine::IonIC,
                                    &isTemporarilyUnoptimizable,
                                    val, idVal, canAttachGetter);
             if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub()) {
                 attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                  outerScript);
             }
         }
         ic->maybeDisable(cx->zone(), attached);
--- a/js/src/jit/JSONPrinter.cpp
+++ b/js/src/jit/JSONPrinter.cpp
@@ -1,19 +1,18 @@
 /* -*- 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/. */
 
-#ifdef JS_JITSPEW
-
 #include "jit/JSONPrinter.h"
 
 #include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
 #include "mozilla/SizePrintfMacros.h"
 
 #include <stdarg.h>
 
 using namespace js;
 using namespace js::jit;
 
 void
@@ -118,24 +117,32 @@ JSONPrinter::integerValue(int value)
 {
     if (!first_)
         out_.printf(",");
     out_.printf("%d", value);
     first_ = false;
 }
 
 void
+JSONPrinter::doubleProperty(const char* name, double value)
+{
+    property(name);
+    if (mozilla::IsFinite(value))
+        out_.printf("%f", value);
+    else
+        out_.printf("null");
+}
+
+void
 JSONPrinter::endObject()
 {
     indentLevel_--;
     indent();
     out_.printf("}");
     first_ = false;
 }
 
 void
 JSONPrinter::endList()
 {
     out_.printf("]");
     first_ = false;
 }
-
-#endif /* JS_JITSPEW */
--- a/js/src/jit/JSONPrinter.h
+++ b/js/src/jit/JSONPrinter.h
@@ -2,18 +2,16 @@
  * 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 jit_JSONPrinter_h
 #define jit_JSONPrinter_h
 
-#ifdef JS_JITSPEW
-
 #include <stdio.h>
 
 #include "js/TypeDecls.h"
 #include "vm/Printer.h"
 
 namespace js {
 namespace jit {
 
@@ -21,35 +19,34 @@ class JSONPrinter
 {
   protected:
     int indentLevel_;
     bool first_;
     GenericPrinter& out_;
 
     void indent();
 
+  public:
+    explicit JSONPrinter(GenericPrinter& out)
+      : indentLevel_(0),
+        first_(true),
+        out_(out)
+    { }
+
     void property(const char* name);
     void beginObject();
     void beginObjectProperty(const char* name);
     void beginListProperty(const char* name);
     void stringValue(const char* format, ...) MOZ_FORMAT_PRINTF(2, 3);
     void stringProperty(const char* name, const char* format, ...) MOZ_FORMAT_PRINTF(3, 4);
     void beginStringProperty(const char* name);
     void endStringProperty();
     void integerValue(int value);
     void integerProperty(const char* name, int value);
+    void doubleProperty(const char* name, double value);
     void endObject();
     void endList();
-
-  public:
-    explicit JSONPrinter(GenericPrinter& out)
-      : indentLevel_(0),
-        first_(true),
-        out_(out)
-    { }
 };
 
 } // namespace jit
 } // namespace js
 
-#endif /* JS_JITSPEW */
-
 #endif /* jit_JSONPrinter_h */
--- a/js/src/jit/JitSpewer.cpp
+++ b/js/src/jit/JitSpewer.cpp
@@ -14,16 +14,17 @@
 #include <process.h>
 #define getpid _getpid
 #else
 #include <unistd.h>
 #endif
 
 #include "jsprf.h"
 
+#include "jit/CacheIRSpewer.h"
 #include "jit/Ion.h"
 #include "jit/MIR.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MIRGraph.h"
 
 #include "threading/LockGuard.h"
 
 #include "vm/HelperThreads.h"
@@ -549,16 +550,19 @@ jit::CheckLogging()
         EnableChannel(JitSpew_BaselineOp);
         EnableChannel(JitSpew_BaselineIC);
         EnableChannel(JitSpew_BaselineICFallback);
         EnableChannel(JitSpew_BaselineOSR);
         EnableChannel(JitSpew_BaselineBailouts);
         EnableChannel(JitSpew_BaselineDebugModeOSR);
     }
 
+    if (ContainsFlag(env, "cacheir-logs"))
+        GetCacheIRSpewerSingleton().init();
+
     JitSpewPrinter().init(stderr);
 }
 
 JitSpewIndent::JitSpewIndent(JitSpewChannel channel)
   : channel_(channel)
 {
     ChannelIndentLevel[channel]++;
 }
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2140,18 +2140,18 @@ DoGetPropFallback(JSContext* cx, void* p
         if (!newStub)
             return false;
         stub->addNewStub(newStub);
         attached = true;
     }
 
     if (!attached && !JitOptions.disableCacheIR) {
         RootedValue idVal(cx, StringValue(name));
-        GetPropIRGenerator gen(cx, pc, CacheKind::GetProp, engine, &isTemporarilyUnoptimizable,
-                               val, idVal, CanAttachGetter::Yes);
+        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, engine,
+                               &isTemporarilyUnoptimizable, val, idVal, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         engine, info.outerScript(cx), stub);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 attached = true;
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -221,16 +221,17 @@ UNIFIED_SOURCES += [
     'jit/BaselineIC.cpp',
     'jit/BaselineInspector.cpp',
     'jit/BaselineJIT.cpp',
     'jit/BitSet.cpp',
     'jit/BytecodeAnalysis.cpp',
     'jit/C1Spewer.cpp',
     'jit/CacheIR.cpp',
     'jit/CacheIRCompiler.cpp',
+    'jit/CacheIRSpewer.cpp',
     'jit/CodeGenerator.cpp',
     'jit/CompileWrappers.cpp',
     'jit/Disassembler.cpp',
     'jit/EagerSimdUnbox.cpp',
     'jit/EdgeCaseAnalysis.cpp',
     'jit/EffectiveAddressAnalysis.cpp',
     'jit/ExecutableAllocator.cpp',
     'jit/FlowAliasAnalysis.cpp',
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -31,16 +31,17 @@
   _(GeckoProfilerStrings,        500) \
   _(ProtectedRegionTree,         500) \
   _(WasmSigIdSet,                500) \
   _(ShellOffThreadState,         500) \
   _(SimulatorCacheLock,          500) \
   _(Arm64SimulatorLock,          500) \
   _(IonSpewer,                   500) \
   _(PerfSpewer,                  500) \
+  _(CacheIRSpewer,               500) \
   _(TraceLoggerThreadState,      500) \
   _(DateTimeInfoMutex,           500) \
   _(IcuTimeZoneStateMutex,       500) \
   _(ProcessExecutableRegion,     500) \
                                       \
   _(TraceLoggerGraphState,       600)
 
 namespace js {