Merge mozilla-central to autoland
authorarthur.iakab <aiakab@mozilla.com>
Tue, 20 Nov 2018 18:48:30 +0200
changeset 447338 d6591e3f56bbe90561faeb01470cca02d9085e36
parent 447337 bf1a01ae1faa2960e09228ecff32d1d332244232 (current diff)
parent 447288 8eff0a4f5d8f4442ce233d492185a90c460846ef (diff)
child 447339 efa49008bfc788e3354d6150eab71a7f105e3102
push id110028
push usershindli@mozilla.com
push dateTue, 20 Nov 2018 21:50:36 +0000
treeherdermozilla-inbound@772005147068 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone65.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
Merge mozilla-central to autoland
js/src/frontend/BytecodeEmitter.cpp
js/src/gc/GC.cpp
js/src/gc/Nursery.cpp
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -491,20 +491,25 @@ FrameAnimator::RequestRefresh(AnimationS
     if (!frameRes.mFrameAdvanced && currentFrameEndTime == oldFrameEndTime) {
       break;
     }
   }
 
   // We should only mark the composited frame as valid and reset the dirty rect
   // if we advanced (meaning the next frame was actually produced somehow), the
   // composited frame was previously invalid (so we may need to repaint
-  // everything) and the frame index is valid (to know we were doing blending
-  // on the main thread, instead of on the decoder threads in advance).
+  // everything) and either the frame index is valid (to know we were doing
+  // blending on the main thread, instead of on the decoder threads in advance),
+  // or the current frame is a full frame (blends off the main thread).
+  //
+  // If for some reason we forget to reset aState.mCompositedFrameInvalid, then
+  // GetCompositedFrame will fail, even if we have all the data available for
+  // display.
   if (currentFrameEndTime > aTime && aState.mCompositedFrameInvalid &&
-      mLastCompositedFrameIndex >= 0) {
+      (mLastCompositedFrameIndex >= 0 || currentFrame->IsFullFrame())) {
     aState.mCompositedFrameInvalid = false;
     ret.mDirtyRect = IntRect(IntPoint(0,0), mSize);
   }
 
   MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid);
 
   return ret;
 }
--- a/js/public/ProtoKey.h
+++ b/js/public/ProtoKey.h
@@ -86,26 +86,26 @@
     REAL(Uint16Array,           InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint16)) \
     REAL(Int32Array,            InitViaClassSpec,       TYPED_ARRAY_CLASP(Int32)) \
     REAL(Uint32Array,           InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint32)) \
     REAL(Float32Array,          InitViaClassSpec,       TYPED_ARRAY_CLASP(Float32)) \
     REAL(Float64Array,          InitViaClassSpec,       TYPED_ARRAY_CLASP(Float64)) \
     REAL(Uint8ClampedArray,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint8Clamped)) \
     REAL_IF_BIGINT(BigInt, InitViaClassSpec, OCLASP(BigInt)) \
     REAL(Proxy,                 InitProxyClass,         &js::ProxyClass) \
-    REAL(WeakMap,               InitWeakMapClass,       OCLASP(WeakMap)) \
+    REAL(WeakMap,               InitViaClassSpec,       OCLASP(WeakMap)) \
     REAL(Map,                   InitViaClassSpec,       OCLASP(Map)) \
     REAL(Set,                   InitViaClassSpec,       OCLASP(Set)) \
     REAL(DataView,              InitViaClassSpec,       OCLASP(DataView)) \
     REAL(Symbol,                InitSymbolClass,        OCLASP(Symbol)) \
     REAL(SharedArrayBuffer,     InitViaClassSpec,       OCLASP(SharedArrayBuffer)) \
     REAL_IF_INTL(Intl, InitIntlClass, CLASP(Intl)) \
     REAL_IF_BDATA(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
     REAL(Reflect,               InitReflect,            nullptr) \
-    REAL(WeakSet,               InitWeakSetClass,       OCLASP(WeakSet)) \
+    REAL(WeakSet,               InitViaClassSpec,       OCLASP(WeakSet)) \
     REAL(TypedArray,            InitViaClassSpec,       &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
     REAL(Atomics,               InitAtomicsClass,       OCLASP(Atomics)) \
     REAL(SavedFrame,            InitViaClassSpec,       &js::SavedFrame::class_) \
     REAL(Promise,               InitViaClassSpec,       OCLASP(Promise)) \
     REAL(ReadableStream,        InitViaClassSpec,       &js::ReadableStream::class_) \
     REAL(ReadableStreamDefaultReader,           InitViaClassSpec, &js::ReadableStreamDefaultReader::class_) \
     REAL(ReadableStreamDefaultController,       InitViaClassSpec, &js::ReadableStreamDefaultController::class_) \
     REAL(ReadableByteStreamController,          InitViaClassSpec, &js::ReadableByteStreamController::class_) \
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -12,26 +12,26 @@
 #include "gc/FreeOp.h"
 #include "vm/JSContext.h"
 #include "vm/SelfHosting.h"
 
 #include "vm/Interpreter-inl.h"
 
 using namespace js;
 
-MOZ_ALWAYS_INLINE bool
-IsWeakMap(HandleValue v)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakMapObject::is(HandleValue v)
 {
     return v.isObject() && v.toObject().is<WeakMapObject>();
 }
 
-MOZ_ALWAYS_INLINE bool
-WeakMap_has_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakMapObject::has_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakMap(args.thisv()));
+    MOZ_ASSERT(is(args.thisv()));
 
     if (!args.get(0).isObject()) {
         args.rval().setBoolean(false);
         return true;
     }
 
     if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
         JSObject* key = &args[0].toObject();
@@ -40,27 +40,27 @@ WeakMap_has_impl(JSContext* cx, const Ca
             return true;
         }
     }
 
     args.rval().setBoolean(false);
     return true;
 }
 
-static bool
-WeakMap_has(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakMapObject::has(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
+    return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::has_impl>(cx, args);
 }
 
-MOZ_ALWAYS_INLINE bool
-WeakMap_get_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakMapObject::get_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakMap(args.thisv()));
+    MOZ_ASSERT(WeakMapObject::is(args.thisv()));
 
     if (!args.get(0).isObject()) {
         args.rval().setUndefined();
         return true;
     }
 
     if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
         JSObject* key = &args[0].toObject();
@@ -69,27 +69,27 @@ WeakMap_get_impl(JSContext* cx, const Ca
             return true;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
-static bool
-WeakMap_get(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakMapObject::get(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args);
+    return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::get_impl>(cx, args);
 }
 
-MOZ_ALWAYS_INLINE bool
-WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakMapObject::delete_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakMap(args.thisv()));
+    MOZ_ASSERT(WeakMapObject::is(args.thisv()));
 
     if (!args.get(0).isObject()) {
         args.rval().setBoolean(false);
         return true;
     }
 
     if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
         JSObject* key = &args[0].toObject();
@@ -99,48 +99,48 @@ WeakMap_delete_impl(JSContext* cx, const
             return true;
         }
     }
 
     args.rval().setBoolean(false);
     return true;
 }
 
-static bool
-WeakMap_delete(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakMapObject::delete_(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
+    return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::delete_impl>(cx, args);
 }
 
-MOZ_ALWAYS_INLINE bool
-WeakMap_set_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakMapObject::set_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakMap(args.thisv()));
+    MOZ_ASSERT(WeakMapObject::is(args.thisv()));
 
     if (!args.get(0).isObject()) {
         ReportNotObjectWithName(cx, "WeakMap key", args.get(0));
         return false;
     }
 
     RootedObject key(cx, &args[0].toObject());
     Rooted<WeakMapObject*> map(cx, &args.thisv().toObject().as<WeakMapObject>());
 
     if (!WeakCollectionPutEntryInternal(cx, map, key, args.get(1))) {
         return false;
     }
     args.rval().set(args.thisv());
     return true;
 }
 
-static bool
-WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakMapObject::set(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
+    return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::set_impl>(cx, args);
 }
 
 bool
 WeakCollectionObject::nondeterministicGetKeys(JSContext* cx, Handle<WeakCollectionObject*> obj,
                                               MutableHandleObject ret)
 {
     RootedObject arr(cx, NewDenseEmptyArray(cx));
     if (!arr) {
@@ -229,18 +229,18 @@ JS::SetWeakMapEntry(JSContext* cx, Handl
                     HandleValue val)
 {
     CHECK_THREAD(cx);
     cx->check(key, val);
     Handle<WeakMapObject*> rootedMap = mapObj.as<WeakMapObject>();
     return WeakCollectionPutEntryInternal(cx, rootedMap, key, val);
 }
 
-static bool
-WeakMap_construct(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakMapObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1.
     if (!ThrowIfNotConstructing(cx, args, "WeakMap")) {
         return false;
     }
 
@@ -278,54 +278,47 @@ const ClassOps WeakCollectionObject::cla
     nullptr, /* mayResolve */
     WeakCollection_finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     WeakCollection_trace
 };
 
+const ClassSpec WeakMapObject::classSpec_ = {
+    GenericCreateConstructor<WeakMapObject::construct, 0, gc::AllocKind::FUNCTION>,
+    GenericCreatePrototype<WeakMapObject>,
+    nullptr,
+    nullptr,
+    WeakMapObject::methods,
+    WeakMapObject::properties,
+};
+
 const Class WeakMapObject::class_ = {
     "WeakMap",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap) |
     JSCLASS_BACKGROUND_FINALIZE,
-    &WeakCollectionObject::classOps_
+    &WeakCollectionObject::classOps_,
+    &WeakMapObject::classSpec_
 };
 
-static const JSFunctionSpec weak_map_methods[] = {
-    JS_FN("has",    WeakMap_has, 1, 0),
-    JS_FN("get",    WeakMap_get, 1, 0),
-    JS_FN("delete", WeakMap_delete, 1, 0),
-    JS_FN("set",    WeakMap_set, 2, 0),
-    JS_FS_END
+const Class WeakMapObject::protoClass_ = {
+    js_Object_str,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap),
+    JS_NULL_CLASS_OPS,
+    &WeakMapObject::classSpec_
 };
 
-JSObject*
-js::InitWeakMapClass(JSContext* cx, Handle<GlobalObject*> global)
-{
-    RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
-    if (!proto) {
-        return nullptr;
-    }
-
-    RootedFunction ctor(cx, GlobalObject::createConstructor(cx, WeakMap_construct,
-                                                            cx->names().WeakMap, 0));
-    if (!ctor) {
-        return nullptr;
-    }
+const JSPropertySpec WeakMapObject::properties[] = {
+    JS_STRING_SYM_PS(toStringTag, "WeakMap", JSPROP_READONLY),
+    JS_PS_END
+};
 
-    if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
-        return nullptr;
-    }
-
-    if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods)) {
-        return nullptr;
-    }
-    if (!DefineToStringTag(cx, proto, cx->names().WeakMap)) {
-        return nullptr;
-    }
-
-    if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, proto)) {
-        return nullptr;
-    }
-    return proto;
-}
+const JSFunctionSpec WeakMapObject::methods[] = {
+    // clang-format off
+    JS_FN("has", has, 1, 0),
+    JS_FN("get", get, 1, 0),
+    JS_FN("delete", delete_, 1, 0),
+    JS_FN("set", set, 2, 0),
+    JS_FS_END
+    // clang-format on
+};
--- a/js/src/builtin/WeakMapObject.h
+++ b/js/src/builtin/WeakMapObject.h
@@ -27,16 +27,33 @@ class WeakCollectionObject : public Nati
   protected:
     static const ClassOps classOps_;
 };
 
 class WeakMapObject : public WeakCollectionObject
 {
   public:
     static const Class class_;
-};
+    static const Class protoClass_;
+
+  private:
+    static const ClassSpec classSpec_;
+
+    static const JSPropertySpec properties[];
+    static const JSFunctionSpec methods[];
+
+    static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
-extern JSObject*
-InitWeakMapClass(JSContext* cx, Handle<GlobalObject*> global);
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool is(HandleValue v);
+
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool has_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool get_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool delete_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool set_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
+};
 
 } // namespace js
 
 #endif /* builtin_WeakMapObject_h */
--- a/js/src/builtin/WeakSetObject.cpp
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -16,28 +16,28 @@
 
 #include "builtin/WeakMapObject-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
-MOZ_ALWAYS_INLINE bool
-IsWeakSet(HandleValue v)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakSetObject::is(HandleValue v)
 {
     return v.isObject() && v.toObject().is<WeakSetObject>();
 }
 
 // ES2018 draft rev 7a2d3f053ecc2336fc19f377c55d52d78b11b296
 // 23.4.3.1 WeakSet.prototype.add ( value )
-MOZ_ALWAYS_INLINE bool
-WeakSet_add_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakSetObject::add_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakSet(args.thisv()));
+    MOZ_ASSERT(is(args.thisv()));
 
     // Step 4.
     if (!args.get(0).isObject()) {
         ReportNotObjectWithName(cx, "WeakSet value", args.get(0));
         return false;
     }
 
     // Steps 5-7.
@@ -47,30 +47,30 @@ WeakSet_add_impl(JSContext* cx, const Ca
         return false;
     }
 
     // Steps 6.a.i, 8.
     args.rval().set(args.thisv());
     return true;
 }
 
-static bool
-WeakSet_add(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakSetObject::add(JSContext* cx, unsigned argc, Value* vp)
 {
     // Steps 1-3.
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakSet, WeakSet_add_impl>(cx, args);
+    return CallNonGenericMethod<WeakSetObject::is, WeakSetObject::add_impl>(cx, args);
 }
 
 // ES2018 draft rev 7a2d3f053ecc2336fc19f377c55d52d78b11b296
 // 23.4.3.3 WeakSet.prototype.delete ( value )
-MOZ_ALWAYS_INLINE bool
-WeakSet_delete_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakSetObject::delete_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakSet(args.thisv()));
+    MOZ_ASSERT(is(args.thisv()));
 
     // Step 4.
     if (!args.get(0).isObject()) {
         args.rval().setBoolean(false);
         return true;
     }
 
     // Steps 5-6.
@@ -83,30 +83,30 @@ WeakSet_delete_impl(JSContext* cx, const
         }
     }
 
     // Step 7.
     args.rval().setBoolean(false);
     return true;
 }
 
-static bool
-WeakSet_delete(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakSetObject::delete_(JSContext* cx, unsigned argc, Value* vp)
 {
     // Steps 1-3.
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakSet, WeakSet_delete_impl>(cx, args);
+    return CallNonGenericMethod<WeakSetObject::is, WeakSetObject::delete_impl>(cx, args);
 }
 
 // ES2018 draft rev 7a2d3f053ecc2336fc19f377c55d52d78b11b296
 // 23.4.3.4 WeakSet.prototype.has ( value )
-MOZ_ALWAYS_INLINE bool
-WeakSet_has_impl(JSContext* cx, const CallArgs& args)
+/* static */ MOZ_ALWAYS_INLINE bool
+WeakSetObject::has_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsWeakSet(args.thisv()));
+    MOZ_ASSERT(is(args.thisv()));
 
     // Step 5.
     if (!args.get(0).isObject()) {
         args.rval().setBoolean(false);
         return true;
     }
 
     // Steps 4, 6.
@@ -118,74 +118,73 @@ WeakSet_has_impl(JSContext* cx, const Ca
         }
     }
 
     // Step 7.
     args.rval().setBoolean(false);
     return true;
 }
 
-static bool
-WeakSet_has(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WeakSetObject::has(JSContext* cx, unsigned argc, Value* vp)
 {
     // Steps 1-3.
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsWeakSet, WeakSet_has_impl>(cx, args);
+    return CallNonGenericMethod<WeakSetObject::is, WeakSetObject::has_impl>(cx, args);
 }
 
+const ClassSpec WeakSetObject::classSpec_ = {
+    GenericCreateConstructor<WeakSetObject::construct, 0, gc::AllocKind::FUNCTION>,
+    GenericCreatePrototype<WeakSetObject>,
+    nullptr,
+    nullptr,
+    WeakSetObject::methods,
+    WeakSetObject::properties,
+};
+
 const Class WeakSetObject::class_ = {
     "WeakSet",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_CACHED_PROTO(JSProto_WeakSet) |
     JSCLASS_BACKGROUND_FINALIZE,
-    &WeakCollectionObject::classOps_
+    &WeakCollectionObject::classOps_,
+    &WeakSetObject::classSpec_
+};
+
+const Class WeakSetObject::protoClass_ = {
+    js_Object_str,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_WeakSet),
+    JS_NULL_CLASS_OPS,
+    &WeakSetObject::classSpec_
 };
 
 const JSPropertySpec WeakSetObject::properties[] = {
+    JS_STRING_SYM_PS(toStringTag, "WeakSet", JSPROP_READONLY),
     JS_PS_END
 };
 
 const JSFunctionSpec WeakSetObject::methods[] = {
-    JS_FN("add",    WeakSet_add,    1, 0),
-    JS_FN("delete", WeakSet_delete, 1, 0),
-    JS_FN("has",    WeakSet_has,    1, 0),
+    // clang-format off
+    JS_FN("add", add, 1, 0),
+    JS_FN("delete", delete_, 1, 0),
+    JS_FN("has", has, 1, 0),
     JS_FS_END
+    // clang-format on
 };
 
-JSObject*
-WeakSetObject::initClass(JSContext* cx, Handle<GlobalObject*> global)
-{
-    RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
-    if (!proto) {
-        return nullptr;
-    }
-
-    Rooted<JSFunction*> ctor(cx, GlobalObject::createConstructor(cx, construct,
-                                                                 ClassName(JSProto_WeakSet, cx), 0));
-    if (!ctor ||
-        !LinkConstructorAndPrototype(cx, ctor, proto) ||
-        !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
-        !DefineToStringTag(cx, proto, cx->names().WeakSet) ||
-        !GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakSet, ctor, proto))
-    {
-        return nullptr;
-    }
-    return proto;
-}
-
 WeakSetObject*
 WeakSetObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
 {
     return NewObjectWithClassProto<WeakSetObject>(cx, proto);
 }
 
 bool
 WeakSetObject::isBuiltinAdd(HandleValue add)
 {
-    return IsNativeFunction(add, WeakSet_add);
+    return IsNativeFunction(add, WeakSetObject::add);
 }
 
 bool
 WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     // Based on our "Set" implementation instead of the more general ES6 steps.
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -238,23 +237,16 @@ WeakSetObject::construct(JSContext* cx, 
             }
         }
     }
 
     args.rval().setObject(*obj);
     return true;
 }
 
-
-JSObject*
-js::InitWeakSetClass(JSContext* cx, Handle<GlobalObject*> global)
-{
-    return WeakSetObject::initClass(cx, global);
-}
-
 JS_FRIEND_API bool
 JS_NondeterministicGetWeakSetKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
 {
     RootedObject obj(cx, UncheckedUnwrap(objArg));
     if (!obj || !obj->is<WeakSetObject>()) {
         ret.set(nullptr);
         return true;
     }
--- a/js/src/builtin/WeakSetObject.h
+++ b/js/src/builtin/WeakSetObject.h
@@ -11,32 +11,40 @@
 
 namespace js {
 
 class GlobalObject;
 
 class WeakSetObject : public WeakCollectionObject
 {
   public:
-    static JSObject* initClass(JSContext* cx, Handle<GlobalObject*> global);
     static const Class class_;
+    static const Class protoClass_;
 
   private:
+    static const ClassSpec classSpec_;
+
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
 
     static WeakSetObject* create(JSContext* cx, HandleObject proto = nullptr);
     static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool is(HandleValue v);
+
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool add_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool add(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool delete_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool has_impl(JSContext* cx, const CallArgs& args);
+    static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
+
     static bool isBuiltinAdd(HandleValue add);
 };
 
-extern JSObject*
-InitWeakSetClass(JSContext* cx, Handle<GlobalObject*> global);
-
 } // namespace js
 
 template<>
 inline bool
 JSObject::is<js::WeakCollectionObject>() const
 {
     return is<js::WeakMapObject>() || is<js::WeakSetObject>();
 }
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -741,52 +741,56 @@ NonLocalExitControl::prepareForNonLocalJ
                  * There's a [exception or hole, retsub pc-index] pair and the
                  * possible return value on the stack that we need to pop.
                  */
                 npops += 3;
             } else {
                 if (!flushPops(bce_)) {
                     return false;
                 }
-                if (!bce_->emitGoSub(&finallyControl.gosubs)) { // ...
+                if (!bce_->emitGoSub(&finallyControl.gosubs)) {
+                    //        [stack] ...
                     return false;
                 }
             }
             break;
           }
 
           case StatementKind::ForOfLoop:
             if (emitIteratorClose) {
                 if (!flushPops(bce_)) {
                     return false;
                 }
 
                 ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>();
                 if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es,
                                                                   /* isTarget = */ false))
-                {                                         // ...
+                {
+                    //        [stack] ...
                     return false;
                 }
             } else {
                 // The iterator next method, the iterator, and the current
                 // value are on the stack.
                 npops += 3;
             }
             break;
 
           case StatementKind::ForInLoop:
             if (!flushPops(bce_)) {
                 return false;
             }
 
             // The iterator and the current value are on the stack.
-            if (!bce_->emit1(JSOP_POP)) {                 // ... ITER
-                return false;
-            }
-            if (!bce_->emit1(JSOP_ENDITER)) {             // ...
+            if (!bce_->emit1(JSOP_POP)) {
+                //            [stack] ... ITER
+                return false;
+            }
+            if (!bce_->emit1(JSOP_ENDITER)) {
+                //            [stack] ...
                 return false;
             }
             break;
 
           default:
             break;
         }
     }
@@ -794,17 +798,18 @@ NonLocalExitControl::prepareForNonLocalJ
     if (!flushPops(bce_)) {
         return false;
     }
 
     if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) {
         ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>();
         if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es,
                                                           /* isTarget = */ true))
-        {                                                 // ... UNDEF UNDEF UNDEF
+        {
+            //                [stack] ... UNDEF UNDEF UNDEF
             return false;
         }
     }
 
     EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope;
     for (; es != targetEmitterScope; es = es->enclosingInFrame()) {
         if (!leaveScope(es)) {
             return false;
@@ -1834,25 +1839,28 @@ BytecodeEmitter::emitPropIncDec(UnaryNod
                       isSuper
                       ? PropOpEmitter::ObjKind::Super
                       : PropOpEmitter::ObjKind::Other);
     if (!poe.prepareForObj()) {
         return false;
     }
     if (isSuper) {
         UnaryNode* base = &prop->expression().as<UnaryNode>();
-        if (!emitGetThisForSuperBase(base)) {       // THIS
+        if (!emitGetThisForSuperBase(base)) {
+            //                [stack] THIS
             return false;
         }
     } else {
         if (!emitPropLHS(prop)) {
-            return false;                           // OBJ
-        }
-    }
-    if (!poe.emitIncDec(prop->key().atom())) {      // RESULT
+            //                [stack] OBJ
+            return false;
+        }
+    }
+    if (!poe.emitIncDec(prop->key().atom())) {
+        //                    [stack] RESULT
         return false;
     }
 
     return true;
 }
 
 
 bool
@@ -1884,43 +1892,51 @@ BytecodeEmitter::emitElemOpBase(JSOp op)
     checkTypeSet(op);
     return true;
 }
 
 bool
 BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe)
 {
     if (isSuper) {
-        if (!eoe.prepareForObj()) {                       //
+        if (!eoe.prepareForObj()) {
+            //                [stack]
             return false;
         }
         UnaryNode* base = &elem->expression().as<UnaryNode>();
-        if (!emitGetThisForSuperBase(base)) {             // THIS
-            return false;
-        }
-        if (!eoe.prepareForKey()) {                       // THIS
-            return false;
-        }
-        if (!emitTree(&elem->key())) {                    // THIS KEY
+        if (!emitGetThisForSuperBase(base)) {
+            //                [stack] THIS
+            return false;
+        }
+        if (!eoe.prepareForKey()) {
+            //                [stack] THIS
+            return false;
+        }
+        if (!emitTree(&elem->key())) {
+            //                [stack] THIS KEY
             return false;
         }
 
         return true;
     }
 
-    if (!eoe.prepareForObj()) {                           //
-        return false;
-    }
-    if (!emitTree(&elem->expression())) {                 // OBJ
-        return false;
-    }
-    if (!eoe.prepareForKey()) {                           // OBJ? OBJ
-        return false;
-    }
-    if (!emitTree(&elem->key())) {                        // OBJ? OBJ KEY
+    if (!eoe.prepareForObj()) {
+        //                    [stack]
+        return false;
+    }
+    if (!emitTree(&elem->expression())) {
+        //                    [stack] OBJ
+        return false;
+    }
+    if (!eoe.prepareForKey()) {
+        //                    [stack] OBJ? OBJ
+        return false;
+    }
+    if (!emitTree(&elem->key())) {
+        //                    [stack] OBJ? OBJ KEY
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitElemIncDec(UnaryNode* incDec)
@@ -1931,43 +1947,47 @@ BytecodeEmitter::emitElemIncDec(UnaryNod
     ElemOpEmitter eoe(this,
                       kind == ParseNodeKind::PostIncrement ? ElemOpEmitter::Kind::PostIncrement
                       : kind == ParseNodeKind::PreIncrement ? ElemOpEmitter::Kind::PreIncrement
                       : kind == ParseNodeKind::PostDecrement ? ElemOpEmitter::Kind::PostDecrement
                       : ElemOpEmitter::Kind::PreDecrement,
                       isSuper
                       ? ElemOpEmitter::ObjKind::Super
                       : ElemOpEmitter::ObjKind::Other);
-    if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) {     // [Super]
-        //                                                // THIS KEY
-        //                                                // [Other]
-        //                                                // OBJ KEY
-        return false;
-    }
-    if (!eoe.emitIncDec()) {                              // RESULT
-         return false;
+    if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) {
+        //                    [stack] # if Super
+        //                    [stack] THIS KEY
+        //                    [stack] # otherwise
+        //                    [stack] OBJ KEY
+        return false;
+    }
+    if (!eoe.emitIncDec()) {
+        //                    [stack] RESULT
+        return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitCallIncDec(UnaryNode* incDec)
 {
     MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrement) ||
                incDec->isKind(ParseNodeKind::PostIncrement) ||
                incDec->isKind(ParseNodeKind::PreDecrement) ||
                incDec->isKind(ParseNodeKind::PostDecrement));
 
     ParseNode* call = incDec->kid();
     MOZ_ASSERT(call->isKind(ParseNodeKind::Call));
-    if (!emitTree(call)) {                              // CALLRESULT
-        return false;
-    }
-    if (!emit1(JSOP_POS)) {                             // N
+    if (!emitTree(call)) {
+        //                    [stack] CALLRESULT
+        return false;
+    }
+    if (!emit1(JSOP_POS)) {
+        //                    [stack] N
         return false;
     }
 
     // The increment/decrement has no side effects, so proceed to throw for
     // invalid assignment target.
     return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS);
 }
 
@@ -2286,37 +2306,44 @@ BytecodeEmitter::emitSetThis(BinaryNode*
         uint8_t hops = AssertedCast<uint8_t>(coord.hops());
         lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, coord.slot());
     } else {
         MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic);
         lexicalLoc = loc;
     }
 
     NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize);
-    if (!noe.prepareForRhs()) {                           //
+    if (!noe.prepareForRhs()) {
+        //                    [stack]
         return false;
     }
 
     // Emit the new |this| value.
-    if (!emitTree(setThisNode->right()))                  // NEWTHIS
-        return false;
+    if (!emitTree(setThisNode->right())) {
+        //                    [stack] NEWTHIS
+        return false;
+    }
 
     // Get the original |this| and throw if we already initialized
     // it. Do *not* use the NameLocation argument, as that's the special
     // lexical location below to deal with super() semantics.
-    if (!emitGetName(name)) {                             // NEWTHIS THIS
-        return false;
-    }
-    if (!emit1(JSOP_CHECKTHISREINIT)) {                   // NEWTHIS THIS
-        return false;
-    }
-    if (!emit1(JSOP_POP)) {                               // NEWTHIS
-        return false;
-    }
-    if (!noe.emitAssignment()) {                          // NEWTHIS
+    if (!emitGetName(name)) {
+        //                    [stack] NEWTHIS THIS
+        return false;
+    }
+    if (!emit1(JSOP_CHECKTHISREINIT)) {
+        //                    [stack] NEWTHIS THIS
+        return false;
+    }
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] NEWTHIS
+        return false;
+    }
+    if (!noe.emitAssignment()) {
+        //                    [stack] NEWTHIS
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitScript(ParseNode* body)
@@ -2517,60 +2544,65 @@ BytecodeEmitter::emitDestructuringLHSRef
                           isSuper
                           ? PropOpEmitter::ObjKind::Super
                           : PropOpEmitter::ObjKind::Other);
         if (!poe.prepareForObj()) {
             return false;
         }
         if (isSuper) {
             UnaryNode* base = &prop->expression().as<UnaryNode>();
-            if (!emitGetThisForSuperBase(base)) {         // THIS SUPERBASE
+            if (!emitGetThisForSuperBase(base)) {
+                //            [stack] THIS SUPERBASE
                 return false;
             }
             // SUPERBASE is pushed onto THIS in poe.prepareForRhs below.
             *emitted = 2;
         } else {
-            if (!emitTree(&prop->expression())) {         // OBJ
+            if (!emitTree(&prop->expression())) {
+                //            [stack] OBJ
                 return false;
             }
             *emitted = 1;
         }
-        if (!poe.prepareForRhs()) {                       // [Super]
-            //                                            // THIS SUPERBASE
-            //                                            // [Other]
-            //                                            // OBJ
+        if (!poe.prepareForRhs()) {
+            //                [stack] # if Super
+            //                [stack] THIS SUPERBASE
+            //                [stack] # otherwise
+            //                [stack] OBJ
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &target->as<PropertyByValue>();
         bool isSuper = elem->isSuper();
         ElemOpEmitter eoe(this,
                           ElemOpEmitter::Kind::SimpleAssignment,
                           isSuper
                           ? ElemOpEmitter::ObjKind::Super
                           : ElemOpEmitter::ObjKind::Other);
-        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
-            //                                            // THIS KEY
-            //                                            // [Other]
-            //                                            // OBJ KEY
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {
+            //                [stack] # if Super
+            //                [stack] THIS KEY
+            //                [stack] # otherwise
+            //                [stack] OBJ KEY
             return false;
         }
         if (isSuper) {
             // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below.
             *emitted = 3;
         } else {
             *emitted = 2;
         }
-        if (!eoe.prepareForRhs()) {                       // [Super]
-            //                                            // THIS KEY SUPERBASE
-            //                                            // [Other]
-            //                                            // OBJ KEY
+        if (!eoe.prepareForRhs()) {
+            //                [stack] # if Super
+            //                [stack] THIS KEY SUPERBASE
+            //                [stack] # otherwise
+            //                [stack] OBJ KEY
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Call:
         MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
                                "rejects function calls as assignment "
@@ -2631,173 +2663,193 @@ BytecodeEmitter::emitSetOrInitializeDest
 
               case DestructuringAssignment:
                 loc = lookupName(name);
                 kind = NameOpEmitter::Kind::SimpleAssignment;
                 break;
             }
 
             NameOpEmitter noe(this, name, loc, kind);
-            if (!noe.prepareForRhs()) {                   // V ENV?
+            if (!noe.prepareForRhs()) {
+                //            [stack] V ENV?
                 return false;
             }
             if (noe.emittedBindOp()) {
                 // This is like ordinary assignment, but with one difference.
                 //
                 // In `a = b`, we first determine a binding for `a` (using
                 // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then
                 // a JSOP_SETNAME instruction.
                 //
                 // In `[a] = [b]`, per spec, `b` is evaluated first, then we
                 // determine a binding for `a`. Then we need to do assignment--
                 // but the operands are on the stack in the wrong order for
                 // JSOP_SETPROP, so we have to add a JSOP_SWAP.
                 //
                 // In the cases where we are emitting a name op, emit a swap
                 // because of this.
-                if (!emit1(JSOP_SWAP)) {                  // ENV V
+                if (!emit1(JSOP_SWAP)) {
+                    //        [stack] ENV V
                     return false;
                 }
             } else {
                 // In cases of emitting a frame slot or environment slot,
                 // nothing needs be done.
             }
-            if (!noe.emitAssignment()) {                  // V
+            if (!noe.emitAssignment()) {
+                //            [stack] V
                 return false;
             }
 
             break;
           }
 
           case ParseNodeKind::Dot: {
             // The reference is already pushed by emitDestructuringLHSRef.
-            //                                            // [Super]
-            //                                            // THIS SUPERBASE VAL
-            //                                            // [Other]
-            //                                            // OBJ VAL
+            //                [stack] # if Super
+            //                [stack] THIS SUPERBASE VAL
+            //                [stack] # otherwise
+            //                [stack] OBJ VAL
             PropertyAccess* prop = &target->as<PropertyAccess>();
             // TODO(khyperia): Implement private field access.
             bool isSuper = prop->isSuper();
             PropOpEmitter poe(this,
                               PropOpEmitter::Kind::SimpleAssignment,
                               isSuper
                               ? PropOpEmitter::ObjKind::Super
                               : PropOpEmitter::ObjKind::Other);
             if (!poe.skipObjAndRhs()) {
                 return false;
             }
+            //                [stack] # VAL
             if (!poe.emitAssignment(prop->key().atom())) {
-                return false;                             // VAL
+                return false;
             }
             break;
           }
 
           case ParseNodeKind::Elem: {
             // The reference is already pushed by emitDestructuringLHSRef.
-            //                                            // [Super]
-            //                                            // THIS KEY SUPERBASE VAL
-            //                                            // [Other]
-            //                                            // OBJ KEY VAL
+            //                [stack] # if Super
+            //                [stack] THIS KEY SUPERBASE VAL
+            //                [stack] # otherwise
+            //                [stack] OBJ KEY VAL
             PropertyByValue* elem = &target->as<PropertyByValue>();
             bool isSuper = elem->isSuper();
             ElemOpEmitter eoe(this,
                               ElemOpEmitter::Kind::SimpleAssignment,
                               isSuper
                               ? ElemOpEmitter::ObjKind::Super
                               : ElemOpEmitter::ObjKind::Other);
             if (!eoe.skipObjAndKeyAndRhs()) {
                 return false;
             }
-            if (!eoe.emitAssignment()) {                  // VAL
+            if (!eoe.emitAssignment()) {
+                //            [stack] VAL
                 return false;
             }
             break;
           }
 
           case ParseNodeKind::Call:
             MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
                                    "rejects function calls as assignment "
                                    "targets in destructuring assignments");
             break;
 
           default:
             MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind");
         }
 
         // Pop the assigned value.
-        if (!emit1(JSOP_POP)) {                           // !STACK EMPTY!
+        if (!emit1(JSOP_POP)) {
+            //                [stack] # empty
             return false;
         }
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitIteratorNext(const Maybe<uint32_t>& callSourceCoordOffset,
                                   IteratorKind iterKind /* = IteratorKind::Sync */,
                                   bool allowSelfHosted /* = false */)
 {
     MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
                ".next() iteration is prohibited in self-hosted code because it "
                "can run user-modifiable iteration code");
 
-    MOZ_ASSERT(this->stackDepth >= 2);                    // ... NEXT ITER
-
-    if (!emitCall(JSOP_CALL, 0, callSourceCoordOffset)) { // ... RESULT
+    //                        [stack] ... NEXT ITER
+    MOZ_ASSERT(this->stackDepth >= 2);
+
+    if (!emitCall(JSOP_CALL, 0, callSourceCoordOffset)) {
+        //                    [stack] ... RESULT
         return false;
     }
 
     if (iterKind == IteratorKind::Async) {
-        if (!emitAwaitInInnermostScope()) {               // ... RESULT
-            return false;
-        }
-    }
-
-    if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { // ... RESULT
+        if (!emitAwaitInInnermostScope()) {
+            //                [stack] ... RESULT
+            return false;
+        }
+    }
+
+    if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) {
+        //                    [stack] ... RESULT
         return false;
     }
     checkTypeSet(JSOP_CALL);
     return true;
 }
 
 bool
 BytecodeEmitter::emitPushNotUndefinedOrNull()
 {
-    MOZ_ASSERT(this->stackDepth > 0);                     // V
-
-    if (!emit1(JSOP_DUP)) {                               // V V
-        return false;
-    }
-    if (!emit1(JSOP_UNDEFINED)) {                         // V V UNDEFINED
-        return false;
-    }
-    if (!emit1(JSOP_STRICTNE)) {                          // V ?NEQL
+    //                        [stack] V
+    MOZ_ASSERT(this->stackDepth > 0);
+
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] V V
+        return false;
+    }
+    if (!emit1(JSOP_UNDEFINED)) {
+        //                    [stack] V V UNDEFINED
+        return false;
+    }
+    if (!emit1(JSOP_STRICTNE)) {
+        //                    [stack] V NEQ
         return false;
     }
 
     JumpList undefinedOrNullJump;
-    if (!emitJump(JSOP_AND, &undefinedOrNullJump)) {      // V ?NEQL
-        return false;
-    }
-
-    if (!emit1(JSOP_POP)) {                               // V
-        return false;
-    }
-    if (!emit1(JSOP_DUP)) {                               // V V
-        return false;
-    }
-    if (!emit1(JSOP_NULL)) {                              // V V NULL
-        return false;
-    }
-    if (!emit1(JSOP_STRICTNE)) {                          // V ?NEQL
-        return false;
-    }
-
-    if (!emitJumpTargetAndPatch(undefinedOrNullJump)) {   // V NOT-UNDEF-OR-NULL
+    if (!emitJump(JSOP_AND, &undefinedOrNullJump)) {
+        //                    [stack] V NEQ
+        return false;
+    }
+
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] V
+        return false;
+    }
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] V V
+        return false;
+    }
+    if (!emit1(JSOP_NULL)) {
+        //                    [stack] V V NULL
+        return false;
+    }
+    if (!emit1(JSOP_STRICTNE)) {
+        //                    [stack] V NEQ
+        return false;
+    }
+
+    if (!emitJumpTargetAndPatch(undefinedOrNullJump)) {
+        //                    [stack] V NOT-UNDEF-OR-NULL
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope,
@@ -2809,36 +2861,40 @@ BytecodeEmitter::emitIteratorCloseInScop
                ".close() on iterators is prohibited in self-hosted code because it "
                "can run user-modifiable iteration code");
 
     // Generate inline logic corresponding to IteratorClose (ES 7.4.6).
     //
     // Callers need to ensure that the iterator object is at the top of the
     // stack.
 
-    if (!emit1(JSOP_DUP)) {                               // ... ITER ITER
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] ... ITER ITER
         return false;
     }
 
     // Step 3.
     //
     // Get the "return" method.
-    if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) {  // ... ITER RET
+    if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) {
+        //                    [stack] ... ITER RET
         return false;
     }
 
     // Step 4.
     //
     // Do nothing if "return" is undefined or null.
     InternalIfEmitter ifReturnMethodIsDefined(this);
-    if (!emitPushNotUndefinedOrNull()) {                  // ... ITER RET NOT-UNDEF-OR-NULL
-        return false;
-    }
-
-    if (!ifReturnMethodIsDefined.emitThenElse()) {        // ... ITER RET
+    if (!emitPushNotUndefinedOrNull()) {
+        //                    [stack] ... ITER RET NOT-UNDEF-OR-NULL
+        return false;
+    }
+
+    if (!ifReturnMethodIsDefined.emitThenElse()) {
+        //                    [stack] ... ITER RET
         return false;
     }
 
     if (completionKind == CompletionKind::Throw) {
         // 7.4.6 IteratorClose ( iterator, completion )
         //   ...
         //   3. Let return be ? GetMethod(iterator, "return").
         //   4. If return is undefined, return Completion(completion).
@@ -2851,128 +2907,152 @@ BytecodeEmitter::emitIteratorCloseInScop
         // is callable, and throws if not.  Since step 6 doesn't match and
         // error handling in step 3 and step 7 can be merged.
         //
         // For CompletionKind::Throw case, an error thrown by JSOP_CALL for
         // step 5 is ignored by try-catch.  So we should check if RET is
         // callable here, outside of try-catch, and the throw immediately if
         // not.
         CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn;
-        if (!emitCheckIsCallable(kind)) {                 // ... ITER RET
+        if (!emitCheckIsCallable(kind)) {
+            //                [stack] ... ITER RET
             return false;
         }
     }
 
     // Steps 5, 8.
     //
     // Call "return" if it is not undefined or null, and check that it returns
     // an Object.
-    if (!emit1(JSOP_SWAP)) {                              // ... RET ITER
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] ... RET ITER
         return false;
     }
 
     Maybe<TryEmitter> tryCatch;
 
     if (completionKind == CompletionKind::Throw) {
         tryCatch.emplace(this, TryEmitter::Kind::TryCatch, TryEmitter::ControlKind::NonSyntactic);
 
         // Mutate stack to balance stack for try-catch.
-        if (!emit1(JSOP_UNDEFINED)) {                     // ... RET ITER UNDEF
-            return false;
-        }
-        if (!tryCatch->emitTry()) {                       // ... RET ITER UNDEF
-            return false;
-        }
-        if (!emitDupAt(2)) {                              // ... RET ITER UNDEF RET
-            return false;
-        }
-        if (!emitDupAt(2)) {                              // ... RET ITER UNDEF RET ITER
-            return false;
-        }
-    }
-
-    if (!emitCall(JSOP_CALL, 0)) {                        // ... ... RESULT
+        if (!emit1(JSOP_UNDEFINED)) {
+            //                [stack] ... RET ITER UNDEF
+            return false;
+        }
+        if (!tryCatch->emitTry()) {
+            //                [stack] ... RET ITER UNDEF
+            return false;
+        }
+        if (!emitDupAt(2)) {
+            //                [stack] ... RET ITER UNDEF RET
+            return false;
+        }
+        if (!emitDupAt(2)) {
+            //                [stack] ... RET ITER UNDEF RET ITER
+            return false;
+        }
+    }
+
+    if (!emitCall(JSOP_CALL, 0)) {
+        //                    [stack] ... ... RESULT
         return false;
     }
     checkTypeSet(JSOP_CALL);
 
     if (iterKind == IteratorKind::Async) {
         if (completionKind != CompletionKind::Throw) {
             // Await clobbers rval, so save the current rval.
-            if (!emit1(JSOP_GETRVAL)) {                   // ... ... RESULT RVAL
-                return false;
-            }
-            if (!emit1(JSOP_SWAP)) {                      // ... ... RVAL RESULT
-                return false;
-            }
-        }
-        if (!emitAwaitInScope(currentScope)) {            // ... ... RVAL? RESULT
+            if (!emit1(JSOP_GETRVAL)) {
+                //            [stack] ... ... RESULT RVAL
+                return false;
+            }
+            if (!emit1(JSOP_SWAP)) {
+                //            [stack] ... ... RVAL RESULT
+                return false;
+            }
+        }
+        if (!emitAwaitInScope(currentScope)) {
+            //                [stack] ... ... RVAL? RESULT
             return false;
         }
     }
 
     if (completionKind == CompletionKind::Throw) {
-        if (!emit1(JSOP_SWAP)) {                          // ... RET ITER RESULT UNDEF
-            return false;
-        }
-        if (!emit1(JSOP_POP)) {                           // ... RET ITER RESULT
-            return false;
-        }
-
-        if (!tryCatch->emitCatch()) {                     // ... RET ITER RESULT
+        if (!emit1(JSOP_SWAP)) {
+            //                [stack] ... RET ITER RESULT UNDEF
+            return false;
+        }
+        if (!emit1(JSOP_POP)) {
+            //                [stack] ... RET ITER RESULT
+            return false;
+        }
+
+        if (!tryCatch->emitCatch()) {
+            //                [stack] ... RET ITER RESULT
             return false;
         }
 
         // Just ignore the exception thrown by call and await.
-        if (!emit1(JSOP_EXCEPTION)) {                     // ... RET ITER RESULT EXC
-            return false;
-        }
-        if (!emit1(JSOP_POP)) {                           // ... RET ITER RESULT
-            return false;
-        }
-
-        if (!tryCatch->emitEnd()) {                       // ... RET ITER RESULT
+        if (!emit1(JSOP_EXCEPTION)) {
+            //                [stack] ... RET ITER RESULT EXC
+            return false;
+        }
+        if (!emit1(JSOP_POP)) {
+            //                [stack] ... RET ITER RESULT
+            return false;
+        }
+
+        if (!tryCatch->emitEnd()) {
+            //                [stack] ... RET ITER RESULT
             return false;
         }
 
         // Restore stack.
-        if (!emit2(JSOP_UNPICK, 2)) {                     // ... RESULT RET ITER
-            return false;
-        }
-        if (!emitPopN(2)) {                               // ... RESULT
+        if (!emit2(JSOP_UNPICK, 2)) {
+            //                [stack] ... RESULT RET ITER
+            return false;
+        }
+        if (!emitPopN(2)) {
+            //                [stack] ... RESULT
             return false;
         }
     } else {
-        if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // ... RVAL? RESULT
+        if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) {
+            //                [stack] ... RVAL? RESULT
             return false;
         }
 
         if (iterKind == IteratorKind::Async) {
-            if (!emit1(JSOP_SWAP)) {                      // ... RESULT RVAL
-                return false;
-            }
-            if (!emit1(JSOP_SETRVAL)) {                   // ... RESULT
-                return false;
-            }
-        }
-    }
-
-    if (!ifReturnMethodIsDefined.emitElse()) {            // ... ITER RET
-        return false;
-    }
-
-    if (!emit1(JSOP_POP)) {                               // ... ITER
+            if (!emit1(JSOP_SWAP)) {
+                //            [stack] ... RESULT RVAL
+                return false;
+            }
+            if (!emit1(JSOP_SETRVAL)) {
+                //            [stack] ... RESULT
+                return false;
+            }
+        }
+    }
+
+    if (!ifReturnMethodIsDefined.emitElse()) {
+        //                    [stack] ... ITER RET
+        return false;
+    }
+
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] ... ITER
         return false;
     }
 
     if (!ifReturnMethodIsDefined.emitEnd()) {
         return false;
     }
 
-    return emit1(JSOP_POP);                               // ...
+    return emit1(JSOP_POP);
+    //                        [stack] ...
 }
 
 template <typename InnerEmitter>
 bool
 BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter)
 {
     MOZ_ASSERT(this->stackDepth >= iterDepth);
 
@@ -2999,38 +3079,45 @@ BytecodeEmitter::wrapWithDestructuringIt
 bool
 BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern)
 {
     IfEmitter ifUndefined(this);
     if (!ifUndefined.emitIf(Nothing())) {
         return false;
     }
 
-    if (!emit1(JSOP_DUP)) {                               // VALUE VALUE
-        return false;
-    }
-    if (!emit1(JSOP_UNDEFINED)) {                         // VALUE VALUE UNDEFINED
-        return false;
-    }
-    if (!emit1(JSOP_STRICTEQ)) {                          // VALUE EQL?
-        return false;
-    }
-
-    if (!ifUndefined.emitThen()) {                        // VALUE
-        return false;
-    }
-
-    if (!emit1(JSOP_POP)) {                               //
-        return false;
-    }
-    if (!emitInitializer(defaultExpr, pattern)) {         // DEFAULTVALUE
-        return false;
-    }
-
-    if (!ifUndefined.emitEnd()) {                         // VALUE/DEFAULTVALUE
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] VALUE VALUE
+        return false;
+    }
+    if (!emit1(JSOP_UNDEFINED)) {
+        //                    [stack] VALUE VALUE UNDEFINED
+        return false;
+    }
+    if (!emit1(JSOP_STRICTEQ)) {
+        //                    [stack] VALUE EQ?
+        return false;
+    }
+
+    if (!ifUndefined.emitThen()) {
+        //                    [stack] VALUE
+        return false;
+    }
+
+    if (!emit1(JSOP_POP)) {
+        //                    [stack]
+        return false;
+    }
+    if (!emitInitializer(defaultExpr, pattern)) {
+        //                    [stack] DEFAULTVALUE
+        return false;
+    }
+
+    if (!ifUndefined.emitEnd()) {
+        //                    [stack] VALUE/DEFAULTVALUE
         return false;
     }
     return true;
 }
 
 bool
 BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name)
 {
@@ -3056,21 +3143,23 @@ BytecodeEmitter::setOrEmitSetFunName(Par
     }
 
     MOZ_ASSERT(maybeFun->isKind(ParseNodeKind::Class));
 
     uint32_t nameIndex;
     if (!makeAtomIndex(name, &nameIndex)) {
         return false;
     }
-    if (!emitIndexOp(JSOP_STRING, nameIndex)) { // FUN NAME
+    if (!emitIndexOp(JSOP_STRING, nameIndex)) {
+        //                    [stack] FUN NAME
         return false;
     }
     uint8_t kind = uint8_t(FunctionPrefixKind::None);
-    if (!emit2(JSOP_SETFUNNAME, kind)) {        // FUN
+    if (!emit2(JSOP_SETFUNNAME, kind)) {
+        //                    [stack] FUN
         return false;
     }
     return true;
 }
 
 bool
 BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern)
 {
@@ -3178,38 +3267,44 @@ BytecodeEmitter::emitDestructuringOpsArr
     //   SetOrInitialize(lref, value);        // covered by trynote
     //
     //   // === emitted after loop ===
     //   if (!done)
     //      IteratorClose(iter);
 
     // Use an iterator to destructure the RHS, instead of index lookup. We
     // must leave the *original* value on the stack.
-    if (!emit1(JSOP_DUP)) {                                       // ... OBJ OBJ
-        return false;
-    }
-    if (!emitIterator()) {                                        // ... OBJ NEXT ITER
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] ... OBJ OBJ
+        return false;
+    }
+    if (!emitIterator()) {
+        //                    [stack] ... OBJ NEXT ITER
         return false;
     }
 
     // For an empty pattern [], call IteratorClose unconditionally. Nothing
     // else needs to be done.
     if (!pattern->head()) {
-        if (!emit1(JSOP_SWAP)) {                                  // ... OBJ ITER NEXT
-            return false;
-        }
-        if (!emit1(JSOP_POP)) {                                   // ... OBJ ITER
-            return false;
-        }
-
-        return emitIteratorCloseInInnermostScope();               // ... OBJ
+        if (!emit1(JSOP_SWAP)) {
+            //                [stack] ... OBJ ITER NEXT
+            return false;
+        }
+        if (!emit1(JSOP_POP)) {
+            //                [stack] ... OBJ ITER
+            return false;
+        }
+
+        return emitIteratorCloseInInnermostScope();
+        //                    [stack] ... OBJ
     }
 
     // Push an initial FALSE value for DONE.
-    if (!emit1(JSOP_FALSE)) {                                     // ... OBJ NEXT ITER FALSE
+    if (!emit1(JSOP_FALSE)) {
+        //                    [stack] ... OBJ NEXT ITER FALSE
         return false;
     }
 
     // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value
     // to be the second to top and the top of the stack, respectively.
     // IteratorClose is called upon exception only if done is false.
     int32_t tryNoteDepth = stackDepth;
 
@@ -3223,96 +3318,112 @@ BytecodeEmitter::emitDestructuringOpsArr
         ParseNode* lhsPattern = member;
         if (lhsPattern->isKind(ParseNodeKind::Assign)) {
             lhsPattern = lhsPattern->as<AssignmentNode>().left();
         }
 
         bool isElision = lhsPattern->isKind(ParseNodeKind::Elision);
         if (!isElision) {
             auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) {
-                return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ NEXT ITER DONE *LREF
+                return bce->emitDestructuringLHSRef(lhsPattern, &emitted);
+                //            [stack] ... OBJ NEXT ITER DONE LREF*
             };
             if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef)) {
                 return false;
             }
         }
 
         // Pick the DONE value to the top of the stack.
         if (emitted) {
-            if (!emit2(JSOP_PICK, emitted)) {                     // ... OBJ NEXT ITER *LREF DONE
+            if (!emit2(JSOP_PICK, emitted)) {
+                //            [stack] ... OBJ NEXT ITER LREF* DONE
                 return false;
             }
         }
 
         if (isFirst) {
             // If this element is the first, DONE is always FALSE, so pop it.
             //
             // Non-first elements should emit if-else depending on the
             // member pattern, below.
-            if (!emit1(JSOP_POP)) {                               // ... OBJ NEXT ITER *LREF
+            if (!emit1(JSOP_POP)) {
+                //            [stack] ... OBJ NEXT ITER LREF*
                 return false;
             }
         }
 
         if (member->isKind(ParseNodeKind::Spread)) {
             InternalIfEmitter ifThenElse(this);
             if (!isFirst) {
                 // If spread is not the first element of the pattern,
                 // iterator can already be completed.
-                                                                  // ... OBJ NEXT ITER *LREF DONE
-                if (!ifThenElse.emitThenElse()) {                 // ... OBJ NEXT ITER *LREF
+                //            [stack] ... OBJ NEXT ITER LREF* DONE
+
+                if (!ifThenElse.emitThenElse()) {
+                    //        [stack] ... OBJ NEXT ITER LREF*
                     return false;
                 }
 
-                if (!emitUint32Operand(JSOP_NEWARRAY, 0)) {       // ... OBJ NEXT ITER *LREF ARRAY
+                if (!emitUint32Operand(JSOP_NEWARRAY, 0)) {
+                    //        [stack] ... OBJ NEXT ITER LREF* ARRAY
                     return false;
                 }
-                if (!ifThenElse.emitElse()) {                     // ... OBJ NEXT ITER *LREF
+                if (!ifThenElse.emitElse()) {
+                    //        [stack] ... OBJ NEXT ITER LREF*
                     return false;
                 }
             }
 
             // If iterator is not completed, create a new array with the rest
             // of the iterator.
-            if (!emitDupAt(emitted + 1)) {                        // ... OBJ NEXT ITER *LREF NEXT
-                return false;
-            }
-            if (!emitDupAt(emitted + 1)) {                        // ... OBJ NEXT ITER *LREF NEXT ITER
-                return false;
-            }
-            if (!emitUint32Operand(JSOP_NEWARRAY, 0)) {           // ... OBJ NEXT ITER *LREF NEXT ITER ARRAY
-                return false;
-            }
-            if (!emitNumberOp(0)) {                               // ... OBJ NEXT ITER *LREF NEXT ITER ARRAY INDEX
-                return false;
-            }
-            if (!emitSpread()) {                                  // ... OBJ NEXT ITER *LREF ARRAY INDEX
-                return false;
-            }
-            if (!emit1(JSOP_POP)) {                               // ... OBJ NEXT ITER *LREF ARRAY
+            if (!emitDupAt(emitted + 1)) {
+                //            [stack] ... OBJ NEXT ITER LREF* NEXT
+                return false;
+            }
+            if (!emitDupAt(emitted + 1)) {
+                //            [stack] ... OBJ NEXT ITER LREF* NEXT ITER
+                return false;
+            }
+            if (!emitUint32Operand(JSOP_NEWARRAY, 0)) {
+                //            [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY
+                return false;
+            }
+            if (!emitNumberOp(0)) {
+                //            [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY INDEX
+                return false;
+            }
+            if (!emitSpread()) {
+                //            [stack] ... OBJ NEXT ITER LREF* ARRAY INDEX
+                return false;
+            }
+            if (!emit1(JSOP_POP)) {
+                //            [stack] ... OBJ NEXT ITER LREF* ARRAY
                 return false;
             }
 
             if (!isFirst) {
                 if (!ifThenElse.emitEnd()) {
                     return false;
                 }
                 MOZ_ASSERT(ifThenElse.pushed() == 1);
             }
 
             // At this point the iterator is done. Unpick a TRUE value for DONE above ITER.
-            if (!emit1(JSOP_TRUE)) {                              // ... OBJ NEXT ITER *LREF ARRAY TRUE
-                return false;
-            }
-            if (!emit2(JSOP_UNPICK, emitted + 1)) {               // ... OBJ NEXT ITER TRUE *LREF ARRAY
+            if (!emit1(JSOP_TRUE)) {
+                //            [stack] ... OBJ NEXT ITER LREF* ARRAY TRUE
+                return false;
+            }
+            if (!emit2(JSOP_UNPICK, emitted + 1)) {
+                //            [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY
                 return false;
             }
 
             auto emitAssignment = [member, flav](BytecodeEmitter* bce) {
-                return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ NEXT ITER TRUE
+                return bce->emitSetOrInitializeDestructuring(member, flav);
+                //            [stack] ... OBJ NEXT ITER TRUE
             };
             if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) {
                 return false;
             }
 
             MOZ_ASSERT(!hasNext);
             break;
         }
@@ -3321,84 +3432,104 @@ BytecodeEmitter::emitDestructuringOpsArr
         if (member->isKind(ParseNodeKind::Assign)) {
             pndefault = member->as<AssignmentNode>().right();
         }
 
         MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread));
 
         InternalIfEmitter ifAlreadyDone(this);
         if (!isFirst) {
-                                                                  // ... OBJ NEXT ITER *LREF DONE
-            if (!ifAlreadyDone.emitThenElse()) {                  // ... OBJ NEXT ITER *LREF
-                return false;
-            }
-
-            if (!emit1(JSOP_UNDEFINED)) {                         // ... OBJ NEXT ITER *LREF UNDEF
-                return false;
-            }
-            if (!emit1(JSOP_NOP_DESTRUCTURING)) {                 // ... OBJ NEXT ITER *LREF UNDEF
+            //                [stack] ... OBJ NEXT ITER LREF* DONE
+
+            if (!ifAlreadyDone.emitThenElse()) {
+                //            [stack] ... OBJ NEXT ITER LREF*
+                return false;
+            }
+
+            if (!emit1(JSOP_UNDEFINED)) {
+                //            [stack] ... OBJ NEXT ITER LREF* UNDEF
+                return false;
+            }
+            if (!emit1(JSOP_NOP_DESTRUCTURING)) {
+                //            [stack] ... OBJ NEXT ITER LREF* UNDEF
                 return false;
             }
 
             // The iterator is done. Unpick a TRUE value for DONE above ITER.
-            if (!emit1(JSOP_TRUE)) {                              // ... OBJ NEXT ITER *LREF UNDEF TRUE
-                return false;
-            }
-            if (!emit2(JSOP_UNPICK, emitted + 1)) {               // ... OBJ NEXT ITER TRUE *LREF UNDEF
-                return false;
-            }
-
-            if (!ifAlreadyDone.emitElse()) {                      // ... OBJ NEXT ITER *LREF
-                return false;
-            }
-        }
-
-        if (!emitDupAt(emitted + 1)) {                            // ... OBJ NEXT ITER *LREF NEXT
-            return false;
-        }
-        if (!emitDupAt(emitted + 1)) {                            // ... OBJ NEXT ITER *LREF NEXT ITER
-            return false;
-        }
-        if (!emitIteratorNext(Some(pattern->pn_pos.begin))) {     // ... OBJ NEXT ITER *LREF RESULT
-            return false;
-        }
-        if (!emit1(JSOP_DUP)) {                                   // ... OBJ NEXT ITER *LREF RESULT RESULT
-            return false;
-        }
-        if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {        // ... OBJ NEXT ITER *LREF RESULT DONE
-            return false;
-        }
-
-        if (!emit1(JSOP_DUP)) {                                   // ... OBJ NEXT ITER *LREF RESULT DONE DONE
-            return false;
-        }
-        if (!emit2(JSOP_UNPICK, emitted + 2)) {                   // ... OBJ NEXT ITER DONE *LREF RESULT DONE
+            if (!emit1(JSOP_TRUE)) {
+                //            [stack] ... OBJ NEXT ITER LREF* UNDEF TRUE
+                return false;
+            }
+            if (!emit2(JSOP_UNPICK, emitted + 1)) {
+                //            [stack] ... OBJ NEXT ITER TRUE LREF* UNDEF
+                return false;
+            }
+
+            if (!ifAlreadyDone.emitElse()) {
+                //            [stack] ... OBJ NEXT ITER LREF*
+                return false;
+            }
+        }
+
+        if (!emitDupAt(emitted + 1)) {
+            //                [stack] ... OBJ NEXT ITER LREF* NEXT
+            return false;
+        }
+        if (!emitDupAt(emitted + 1)) {
+            //                [stack] ... OBJ NEXT ITER LREF* NEXT ITER
+            return false;
+        }
+        if (!emitIteratorNext(Some(pattern->pn_pos.begin))) {
+            //                [stack] ... OBJ NEXT ITER LREF* RESULT
+            return false;
+        }
+        if (!emit1(JSOP_DUP)) {
+            //                [stack] ... OBJ NEXT ITER LREF* RESULT RESULT
+            return false;
+        }
+        if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {
+            //                [stack] ... OBJ NEXT ITER LREF* RESULT DONE
+            return false;
+        }
+
+        if (!emit1(JSOP_DUP)) {
+            //                [stack] ... OBJ NEXT ITER LREF* RESULT DONE DONE
+            return false;
+        }
+        if (!emit2(JSOP_UNPICK, emitted + 2)) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF* RESULT DONE
             return false;
         }
 
         InternalIfEmitter ifDone(this);
-        if (!ifDone.emitThenElse()) {                             // ... OBJ NEXT ITER DONE *LREF RESULT
-            return false;
-        }
-
-        if (!emit1(JSOP_POP)) {                                   // ... OBJ NEXT ITER DONE *LREF
-            return false;
-        }
-        if (!emit1(JSOP_UNDEFINED)) {                             // ... OBJ NEXT ITER DONE *LREF UNDEF
-            return false;
-        }
-        if (!emit1(JSOP_NOP_DESTRUCTURING)) {                     // ... OBJ NEXT ITER DONE *LREF UNDEF
-            return false;
-        }
-
-        if (!ifDone.emitElse()) {                                 // ... OBJ NEXT ITER DONE *LREF RESULT
-            return false;
-        }
-
-        if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {       // ... OBJ NEXT ITER DONE *LREF VALUE
+        if (!ifDone.emitThenElse()) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF* RESULT
+            return false;
+        }
+
+        if (!emit1(JSOP_POP)) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF*
+            return false;
+        }
+        if (!emit1(JSOP_UNDEFINED)) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF* UNDEF
+            return false;
+        }
+        if (!emit1(JSOP_NOP_DESTRUCTURING)) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF* UNDEF
+            return false;
+        }
+
+        if (!ifDone.emitElse()) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF* RESULT
+            return false;
+        }
+
+        if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {
+            //                [stack] ... OBJ NEXT ITER DONE LREF* VALUE
             return false;
         }
 
         if (!ifDone.emitEnd()) {
             return false;
         }
         MOZ_ASSERT(ifDone.pushed() == 0);
 
@@ -3406,59 +3537,69 @@ BytecodeEmitter::emitDestructuringOpsArr
             if (!ifAlreadyDone.emitEnd()) {
                 return false;
             }
             MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
         }
 
         if (pndefault) {
             auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) {
-                return bce->emitDefault(pndefault, lhsPattern);    // ... OBJ NEXT ITER DONE *LREF VALUE
+                return bce->emitDefault(pndefault, lhsPattern);
+                //            [stack] ... OBJ NEXT ITER DONE LREF* VALUE
             };
 
             if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault)) {
                 return false;
             }
         }
 
         if (!isElision) {
             auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
-                return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ NEXT ITER DONE
+                return bce->emitSetOrInitializeDestructuring(lhsPattern, flav);
+                //            [stack] ... OBJ NEXT ITER DONE
             };
 
             if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) {
                 return false;
             }
         } else {
-            if (!emit1(JSOP_POP)) {                               // ... OBJ NEXT ITER DONE
+            if (!emit1(JSOP_POP)) {
+                //            [stack] ... OBJ NEXT ITER DONE
                 return false;
             }
         }
     }
 
     // The last DONE value is on top of the stack. If not DONE, call
     // IteratorClose.
-                                                                  // ... OBJ NEXT ITER DONE
+    //                        [stack] ... OBJ NEXT ITER DONE
+
     InternalIfEmitter ifDone(this);
-    if (!ifDone.emitThenElse()) {                                 // ... OBJ NEXT ITER
-        return false;
-    }
-    if (!emitPopN(2)) {                                           // ... OBJ
-        return false;
-    }
-    if (!ifDone.emitElse()) {                                     // ... OBJ NEXT ITER
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                                      // ... OBJ ITER NEXT
-        return false;
-    }
-    if (!emit1(JSOP_POP)) {                                       // ... OBJ ITER
-        return false;
-    }
-    if (!emitIteratorCloseInInnermostScope()) {                   // ... OBJ
+    if (!ifDone.emitThenElse()) {
+        //                    [stack] ... OBJ NEXT ITER
+        return false;
+    }
+    if (!emitPopN(2)) {
+        //                    [stack] ... OBJ
+        return false;
+    }
+    if (!ifDone.emitElse()) {
+        //                    [stack] ... OBJ NEXT ITER
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] ... OBJ ITER NEXT
+        return false;
+    }
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] ... OBJ ITER
+        return false;
+    }
+    if (!emitIteratorCloseInInnermostScope()) {
+        //                    [stack] ... OBJ
         return false;
     }
     if (!ifDone.emitEnd()) {
         return false;
     }
 
     return true;
 }
@@ -3470,30 +3611,34 @@ BytecodeEmitter::emitComputedPropertyNam
     return emitTree(computedPropName->kid()) && emit1(JSOP_TOID);
 }
 
 bool
 BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, DestructuringFlavor flav)
 {
     MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object));
 
-    MOZ_ASSERT(this->stackDepth > 0);                             // ... RHS
-
-    if (!emit1(JSOP_CHECKOBJCOERCIBLE)) {                         // ... RHS
+    //                        [stack] ... RHS
+    MOZ_ASSERT(this->stackDepth > 0);
+
+    if (!emit1(JSOP_CHECKOBJCOERCIBLE)) {
+        //                    [stack] ... RHS
         return false;
     }
 
     bool needsRestPropertyExcludedSet = pattern->count() > 1 &&
                                         pattern->last()->isKind(ParseNodeKind::Spread);
     if (needsRestPropertyExcludedSet) {
-        if (!emitDestructuringObjRestExclusionSet(pattern)) {     // ... RHS SET
-            return false;
-        }
-
-        if (!emit1(JSOP_SWAP)) {                                  // ... SET RHS
+        if (!emitDestructuringObjRestExclusionSet(pattern)) {
+            //                [stack] ... RHS SET
+            return false;
+        }
+
+        if (!emit1(JSOP_SWAP)) {
+            //                [stack] ... SET RHS
             return false;
         }
     }
 
     for (ParseNode* member : pattern->contents()) {
         ParseNode* subpattern;
         if (member->isKind(ParseNodeKind::MutateProto) ||
             member->isKind(ParseNodeKind::Spread))
@@ -3508,127 +3653,147 @@ BytecodeEmitter::emitDestructuringOpsObj
         ParseNode* lhs = subpattern;
         MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread),
                       !lhs->isKind(ParseNodeKind::Assign));
         if (lhs->isKind(ParseNodeKind::Assign)) {
             lhs = lhs->as<AssignmentNode>().left();
         }
 
         size_t emitted;
-        if (!emitDestructuringLHSRef(lhs, &emitted)) {            // ... *SET RHS *LREF
+        if (!emitDestructuringLHSRef(lhs, &emitted)) {
+            //                [stack] ... SET? RHS LREF*
             return false;
         }
 
         // Duplicate the value being destructured to use as a reference base.
-        if (!emitDupAt(emitted)) {                                // ... *SET RHS *LREF RHS
+        if (!emitDupAt(emitted)) {
+            //                [stack] ... SET? RHS LREF* RHS
             return false;
         }
 
         if (member->isKind(ParseNodeKind::Spread)) {
             if (!updateSourceCoordNotes(member->pn_pos.begin)) {
                 return false;
             }
 
-            if (!emitNewInit()) {                                 // ... *SET RHS *LREF RHS TARGET
-                return false;
-            }
-            if (!emit1(JSOP_DUP)) {                               // ... *SET RHS *LREF RHS TARGET TARGET
-                return false;
-            }
-            if (!emit2(JSOP_PICK, 2)) {                           // ... *SET RHS *LREF TARGET TARGET RHS
+            if (!emitNewInit()) {
+                //            [stack] ... SET? RHS LREF* RHS TARGET
+                return false;
+            }
+            if (!emit1(JSOP_DUP)) {
+                //            [stack] ... SET? RHS LREF* RHS TARGET TARGET
+                return false;
+            }
+            if (!emit2(JSOP_PICK, 2)) {
+                //            [stack] ... SET? RHS LREF* TARGET TARGET RHS
                 return false;
             }
 
             if (needsRestPropertyExcludedSet) {
-                if (!emit2(JSOP_PICK, emitted + 4)) {             // ... RHS *LREF TARGET TARGET RHS SET
+                if (!emit2(JSOP_PICK, emitted + 4)) {
+                    //        [stack] ... RHS LREF* TARGET TARGET RHS SET
                     return false;
                 }
             }
 
             CopyOption option = needsRestPropertyExcludedSet
                                 ? CopyOption::Filtered
                                 : CopyOption::Unfiltered;
-            if (!emitCopyDataProperties(option)) {                // ... RHS *LREF TARGET
+            if (!emitCopyDataProperties(option)) {
+                //            [stack] ... RHS LREF* TARGET
                 return false;
             }
 
             // Destructure TARGET per this member's lhs.
-            if (!emitSetOrInitializeDestructuring(lhs, flav)) {   // ... RHS
+            if (!emitSetOrInitializeDestructuring(lhs, flav)) {
+                //            [stack] ... RHS
                 return false;
             }
 
             MOZ_ASSERT(member == pattern->last(), "Rest property is always last");
             break;
         }
 
         // Now push the property name currently being matched, which is the
         // current property name "label" on the left of a colon in the object
         // initialiser.
         bool needsGetElem = true;
 
         if (member->isKind(ParseNodeKind::MutateProto)) {
-            if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) {   // ... *SET RHS *LREF PROP
+            if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) {
+                //            [stack] ... SET? RHS LREF* PROP
                 return false;
             }
             needsGetElem = false;
         } else {
             MOZ_ASSERT(member->isKind(ParseNodeKind::Colon) ||
                        member->isKind(ParseNodeKind::Shorthand));
 
             ParseNode* key = member->as<BinaryNode>().left();
             if (key->isKind(ParseNodeKind::Number)) {
                 if (!emitNumberOp(key->as<NumericLiteral>().value())) {
-                    return false;                                 // ... *SET RHS *LREF RHS KEY
+                    //        [stack]... SET? RHS LREF* RHS KEY
+                    return false;
                 }
             } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
                        key->isKind(ParseNodeKind::String))
             {
                 if (!emitAtomOp(key->as<NameNode>().atom(), JSOP_GETPROP)) {
-                    return false;                                 // ... *SET RHS *LREF PROP
+                    //        [stack] ... SET? RHS LREF* PROP
+                    return false;
                 }
                 needsGetElem = false;
             } else {
                 if (!emitComputedPropertyName(&key->as<UnaryNode>())) {
-                    return false;                                 // ... *SET RHS *LREF RHS KEY
+                    //        [stack] ... SET? RHS LREF* RHS KEY
+                    return false;
                 }
 
                 // Add the computed property key to the exclusion set.
                 if (needsRestPropertyExcludedSet) {
-                    if (!emitDupAt(emitted + 3)) {                // ... SET RHS *LREF RHS KEY SET
+                    if (!emitDupAt(emitted + 3)) {
+                        //    [stack] ... SET RHS LREF* RHS KEY SET
                         return false;
                     }
-                    if (!emitDupAt(1)) {                          // ... SET RHS *LREF RHS KEY SET KEY
+                    if (!emitDupAt(1)) {
+                        //    [stack] ... SET RHS LREF* RHS KEY SET KEY
                         return false;
                     }
-                    if (!emit1(JSOP_UNDEFINED)) {                 // ... SET RHS *LREF RHS KEY SET KEY UNDEFINED
+                    if (!emit1(JSOP_UNDEFINED)) {
+                        //    [stack] ... SET RHS LREF* RHS KEY SET KEY UNDEFINED
                         return false;
                     }
-                    if (!emit1(JSOP_INITELEM)) {                  // ... SET RHS *LREF RHS KEY SET
+                    if (!emit1(JSOP_INITELEM)) {
+                        //    [stack] ... SET RHS LREF* RHS KEY SET
                         return false;
                     }
-                    if (!emit1(JSOP_POP)) {                       // ... SET RHS *LREF RHS KEY
+                    if (!emit1(JSOP_POP)) {
+                        //    [stack] ... SET RHS LREF* RHS KEY
                         return false;
                     }
                 }
             }
         }
 
         // Get the property value if not done already.
-        if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) {      // ... *SET RHS *LREF PROP
+        if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) {
+            //                [stack] ... SET? RHS LREF* PROP
             return false;
         }
 
         if (subpattern->isKind(ParseNodeKind::Assign)) {
             if (!emitDefault(subpattern->as<AssignmentNode>().right(), lhs)) {
-                return false;                                     // ... *SET RHS *LREF VALUE
+                //            [stack] ... SET? RHS LREF* VALUE
+                return false;
             }
         }
 
         // Destructure PROP per this member's lhs.
-        if (!emitSetOrInitializeDestructuring(subpattern, flav)) {  // ... *SET RHS
+        if (!emitSetOrInitializeDestructuring(subpattern, flav)) {
+            //                [stack] ... SET? RHS
             return false;
         }
     }
 
     return true;
 }
 
 bool
@@ -3838,38 +4003,43 @@ BytecodeEmitter::emitSingleDeclaration(L
     MOZ_ASSERT(decl->isKind(ParseNodeKind::Name));
 
     // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
     if (!initializer && declList->isKind(ParseNodeKind::Var)) {
         return true;
     }
 
     NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
-    if (!noe.prepareForRhs()) {                           // ENV?
+    if (!noe.prepareForRhs()) {
+        //                    [stack] ENV?
         return false;
     }
     if (!initializer) {
         // Lexical declarations are initialized to undefined without an
         // initializer.
         MOZ_ASSERT(declList->isKind(ParseNodeKind::Let),
                    "var declarations without initializers handled above, "
                    "and const declarations must have initializers");
-        if (!emit1(JSOP_UNDEFINED)) {                     // ENV? UNDEF
+        if (!emit1(JSOP_UNDEFINED)) {
+            //                [stack] ENV? UNDEF
             return false;
         }
     } else {
         MOZ_ASSERT(initializer);
-        if (!emitInitializer(initializer, decl)) {        // ENV? V
-            return false;
-        }
-    }
-    if (!noe.emitAssignment()) {                          // V
-         return false;
-    }
-    if (!emit1(JSOP_POP)) {                               //
+        if (!emitInitializer(initializer, decl)) {
+            //                [stack] ENV? V
+            return false;
+        }
+    }
+    if (!noe.emitAssignment()) {
+        //                    [stack] V
+        return false;
+    }
+    if (!emit1(JSOP_POP)) {
+        //                    [stack]
         return false;
     }
 
     return true;
 }
 
 static bool
 EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
@@ -3921,41 +4091,46 @@ BytecodeEmitter::emitAssignment(ParseNod
     if (lhs->isKind(ParseNodeKind::Name)) {
         NameNode* nameNode = &lhs->as<NameNode>();
         RootedAtom name(cx, nameNode->name());
         NameOpEmitter noe(this,
                           name,
                           isCompound
                           ? NameOpEmitter::Kind::CompoundAssignment
                           : NameOpEmitter::Kind::SimpleAssignment);
-        if (!noe.prepareForRhs()) {                       // ENV? VAL?
+        if (!noe.prepareForRhs()) {
+            //                [stack] ENV? VAL?
             return false;
         }
 
         // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
         // the top of the stack and we need to pick the right RHS value.
         uint8_t offset = noe.emittedBindOp() ? 2 : 1;
-        if (!EmitAssignmentRhs(this, rhs, offset)) {      // ENV? VAL? RHS
+        if (!EmitAssignmentRhs(this, rhs, offset)) {
+            //                [stack] ENV? VAL? RHS
             return false;
         }
         if (rhs && rhs->isDirectRHSAnonFunction()) {
             MOZ_ASSERT(!nameNode->isInParens());
             MOZ_ASSERT(!isCompound);
-            if (!setOrEmitSetFunName(rhs, name)) {         // ENV? VAL? RHS
+            if (!setOrEmitSetFunName(rhs, name)) {
+                //            [stack] ENV? VAL? RHS
                 return false;
             }
         }
 
         // Emit the compound assignment op if there is one.
         if (isCompound) {
-            if (!emit1(compoundOp)) {                     // ENV? VAL
-                return false;
-            }
-        }
-        if (!noe.emitAssignment()) {                      // VAL
+            if (!emit1(compoundOp)) {
+                //            [stack] ENV? VAL
+                return false;
+            }
+        }
+        if (!noe.emitAssignment()) {
+            //                [stack] VAL
             return false;
         }
 
         return true;
     }
 
     Maybe<PropOpEmitter> poe;
     Maybe<ElemOpEmitter> eoe;
@@ -3974,43 +4149,46 @@ BytecodeEmitter::emitAssignment(ParseNod
                     isSuper
                     ? PropOpEmitter::ObjKind::Super
                     : PropOpEmitter::ObjKind::Other);
         if (!poe->prepareForObj()) {
             return false;
         }
         if (isSuper) {
             UnaryNode* base = &prop->expression().as<UnaryNode>();
-            if (!emitGetThisForSuperBase(base)) {         // THIS SUPERBASE
+            if (!emitGetThisForSuperBase(base)) {
+                //            [stack] THIS SUPERBASE
                 return false;
             }
             // SUPERBASE is pushed onto THIS later in poe->emitGet below.
             offset += 2;
         } else {
-            if (!emitTree(&prop->expression())) {         // OBJ
+            if (!emitTree(&prop->expression())) {
+                //            [stack] OBJ
                 return false;
             }
             offset += 1;
         }
         break;
       }
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &lhs->as<PropertyByValue>();
         bool isSuper = elem->isSuper();
         eoe.emplace(this,
                     isCompound
                     ? ElemOpEmitter::Kind::CompoundAssignment
                     : ElemOpEmitter::Kind::SimpleAssignment,
                     isSuper
                     ? ElemOpEmitter::ObjKind::Super
                     : ElemOpEmitter::ObjKind::Other);
-        if (!emitElemObjAndKey(elem, isSuper, *eoe)) {    // [Super]
-            //                                            // THIS KEY
-            //                                            // [Other]
-            //                                            // OBJ KEY
+        if (!emitElemObjAndKey(elem, isSuper, *eoe)) {
+            //                [stack] # if Super
+            //                [stack] THIS KEY
+            //                [stack] # otherwise
+            //                [stack] OBJ KEY
             return false;
         }
         if (isSuper) {
             // SUPERBASE is pushed onto KEY in eoe->emitGet below.
             offset += 3;
         } else {
             offset += 2;
         }
@@ -4040,102 +4218,111 @@ BytecodeEmitter::emitAssignment(ParseNod
     }
 
     if (isCompound) {
         MOZ_ASSERT(rhs);
         switch (lhs->getKind()) {
           case ParseNodeKind::Dot: {
             PropertyAccess* prop = &lhs->as<PropertyAccess>();
             // TODO(khyperia): Implement private field access.
-            if (!poe->emitGet(prop->key().atom())) {      // [Super]
-                //                                        // THIS SUPERBASE PROP
-                //                                        // [Other]
-                //                                        // OBJ PROP
+            if (!poe->emitGet(prop->key().atom())) {
+                //            [stack] # if Super
+                //            [stack] THIS SUPERBASE PROP
+                //            [stack] # otherwise
+                //            [stack] OBJ PROP
                 return false;
             }
             break;
           }
           case ParseNodeKind::Elem: {
-            if (!eoe->emitGet()) {                        // KEY THIS OBJ ELEM
+            if (!eoe->emitGet()) {
+                //            [stack] KEY THIS OBJ ELEM
                 return false;
             }
             break;
           }
           case ParseNodeKind::Call:
             // We just emitted a JSOP_THROWMSG and popped the call's return
             // value.  Push a random value to make sure the stack depth is
             // correct.
             if (!emit1(JSOP_NULL)) {
+                //            [stack] NULL
                 return false;
             }
             break;
           default:;
         }
     }
 
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot:
-        if (!poe->prepareForRhs()) {                      // [Simple,Super]
-            //                                            // THIS SUPERBASE
-            //                                            // [Simple,Other]
-            //                                            // OBJ
-            //                                            // [Compound,Super]
-            //                                            // THIS SUPERBASE PROP
-            //                                            // [Compound,Other]
-            //                                            // OBJ PROP
+        if (!poe->prepareForRhs()) {
+            //                [stack] # if Simple Assignment with Super
+            //                [stack] THIS SUPERBASE
+            //                [stack] # if Simple Assignment with other
+            //                [stack] OBJ
+            //                [stack] # if Compound Assignment with Super
+            //                [stack] THIS SUPERBASE PROP
+            //                [stack] # if Compound Assignment with other
+            //                [stack] OBJ PROP
             return false;
         }
         break;
       case ParseNodeKind::Elem:
-        if (!eoe->prepareForRhs()) {                      // [Simple,Super]
-            //                                            // THIS KEY SUPERBASE
-            //                                            // [Simple,Other]
-            //                                            // OBJ KEY
-            //                                            // [Compound,Super]
-            //                                            // THIS KEY SUPERBASE ELEM
-            //                                            // [Compound,Other]
-            //                                            // OBJ KEY ELEM
+        if (!eoe->prepareForRhs()) {
+            //                [stack] # if Simple Assignment with Super
+            //                [stack] THIS KEY SUPERBASE
+            //                [stack] # if Simple Assignment with other
+            //                [stack] OBJ KEY
+            //                [stack] # if Compound Assignment with Super
+            //                [stack] THIS KEY SUPERBASE ELEM
+            //                [stack] # if Compound Assignment with other
+            //                [stack] OBJ KEY ELEM
             return false;
         }
         break;
       default:
         break;
     }
 
-    if (!EmitAssignmentRhs(this, rhs, offset)) {          // ... VAL? RHS
+    if (!EmitAssignmentRhs(this, rhs, offset)) {
+        //                    [stack] ... VAL? RHS
         return false;
     }
 
     /* If += etc., emit the binary operator with a source note. */
     if (isCompound) {
         if (!newSrcNote(SRC_ASSIGNOP)) {
             return false;
         }
-        if (!emit1(compoundOp)) {                         // ... VAL
+        if (!emit1(compoundOp)) {
+            //                [stack] ... VAL
             return false;
         }
     }
 
     /* Finally, emit the specialized assignment bytecode. */
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &lhs->as<PropertyAccess>();
         // TODO(khyperia): Implement private field access.
-        if (!poe->emitAssignment(prop->key().atom())) {   // VAL
+        if (!poe->emitAssignment(prop->key().atom())) {
+            //                [stack] VAL
             return false;
         }
 
         poe.reset();
         break;
       }
       case ParseNodeKind::Call:
         // We threw above, so nothing to do here.
         break;
       case ParseNodeKind::Elem: {
-        if (!eoe->emitAssignment()) {                     // VAL
+        if (!eoe->emitAssignment()) {
+            //                [stack] VAL
             return false;
         }
 
         eoe.reset();
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
@@ -4683,55 +4870,65 @@ BytecodeEmitter::emitWith(BinaryNode* wi
 
 bool
 BytecodeEmitter::emitCopyDataProperties(CopyOption option)
 {
     DebugOnly<int32_t> depth = this->stackDepth;
 
     uint32_t argc;
     if (option == CopyOption::Filtered) {
-        MOZ_ASSERT(depth > 2);                 // TARGET SOURCE SET
+        MOZ_ASSERT(depth > 2);
+        //                    [stack] TARGET SOURCE SET
         argc = 3;
 
         if (!emitAtomOp(cx->names().CopyDataProperties,
-                        JSOP_GETINTRINSIC))    // TARGET SOURCE SET COPYDATAPROPERTIES
+                        JSOP_GETINTRINSIC))
         {
+            //                [stack] TARGET SOURCE SET COPYDATAPROPERTIES
             return false;
         }
     } else {
-        MOZ_ASSERT(depth > 1);                 // TARGET SOURCE
+        MOZ_ASSERT(depth > 1);
+        //                    [stack] TARGET SOURCE
         argc = 2;
 
         if (!emitAtomOp(cx->names().CopyDataPropertiesUnfiltered,
-                        JSOP_GETINTRINSIC))    // TARGET SOURCE COPYDATAPROPERTIES
+                        JSOP_GETINTRINSIC))
         {
-            return false;
-        }
-    }
-
-    if (!emit1(JSOP_UNDEFINED)) {              // TARGET SOURCE *SET COPYDATAPROPERTIES UNDEFINED
-        return false;
-    }
-    if (!emit2(JSOP_PICK, argc + 1)) {         // SOURCE *SET COPYDATAPROPERTIES UNDEFINED TARGET
-        return false;
-    }
-    if (!emit2(JSOP_PICK, argc + 1)) {         // *SET COPYDATAPROPERTIES UNDEFINED TARGET SOURCE
+            //                [stack] TARGET SOURCE COPYDATAPROPERTIES
+            return false;
+        }
+    }
+
+    if (!emit1(JSOP_UNDEFINED)) {
+        //                    [stack] TARGET SOURCE SET? COPYDATAPROPERTIES UNDEFINED
+        return false;
+    }
+    if (!emit2(JSOP_PICK, argc + 1)) {
+        //                    [stack] SOURCE SET? COPYDATAPROPERTIES UNDEFINED TARGET
+        return false;
+    }
+    if (!emit2(JSOP_PICK, argc + 1)) {
+        //                    [stack] SET? COPYDATAPROPERTIES UNDEFINED TARGET SOURCE
         return false;
     }
     if (option == CopyOption::Filtered) {
-        if (!emit2(JSOP_PICK, argc + 1)) {     // COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET
-            return false;
-        }
-    }
-    if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) { // IGNORED
+        if (!emit2(JSOP_PICK, argc + 1)) {
+            //                [stack] COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET
+            return false;
+        }
+    }
+    if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) {
+        //                    [stack] IGNORED
         return false;
     }
     checkTypeSet(JSOP_CALL_IGNORES_RV);
 
-    if (!emit1(JSOP_POP)) {                    // -
+    if (!emit1(JSOP_POP)) {
+        //                    [stack]
         return false;
     }
 
     MOZ_ASSERT(depth - int(argc) == this->stackDepth);
     return true;
 }
 
 #ifdef ENABLE_BIGINT
@@ -4744,132 +4941,165 @@ BytecodeEmitter::emitBigIntOp(BigInt* bi
     return emitIndex32(JSOP_BIGINT, numberList.length() - 1);
 }
 #endif
 
 bool
 BytecodeEmitter::emitIterator()
 {
     // Convert iterable to iterator.
-    if (!emit1(JSOP_DUP)) {                                       // OBJ OBJ
-        return false;
-    }
-    if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) { // OBJ OBJ @@ITERATOR
-        return false;
-    }
-    if (!emitElemOpBase(JSOP_CALLELEM)) {                         // OBJ ITERFN
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                                      // ITERFN OBJ
-        return false;
-    }
-    if (!emitCall(JSOP_CALLITER, 0)) {                            // ITER
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] OBJ OBJ
+        return false;
+    }
+    if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) {
+        //                    [stack] OBJ OBJ @@ITERATOR
+        return false;
+    }
+    if (!emitElemOpBase(JSOP_CALLELEM)) {
+        //                    [stack] OBJ ITERFN
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] ITERFN OBJ
+        return false;
+    }
+    if (!emitCall(JSOP_CALLITER, 0)) {
+        //                    [stack] ITER
         return false;
     }
     checkTypeSet(JSOP_CALLITER);
-    if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {        // ITER
-        return false;
-    }
-    if (!emit1(JSOP_DUP)) {                                       // ITER ITER
-        return false;
-    }
-    if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) {            // ITER NEXT
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                                      // NEXT ITER
+    if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {
+        //                    [stack] ITER
+        return false;
+    }
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] ITER ITER
+        return false;
+    }
+    if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) {
+        //                    [stack] ITER NEXT
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] NEXT ITER
         return false;
     }
     return true;
 }
 
 bool
 BytecodeEmitter::emitAsyncIterator()
 {
     // Convert iterable to iterator.
-    if (!emit1(JSOP_DUP)) {                                       // OBJ OBJ
-        return false;
-    }
-    if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::asyncIterator))) { // OBJ OBJ @@ASYNCITERATOR
-        return false;
-    }
-    if (!emitElemOpBase(JSOP_CALLELEM)) {                         // OBJ ITERFN
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] OBJ OBJ
+        return false;
+    }
+    if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::asyncIterator))) {
+        //                    [stack] OBJ OBJ @@ASYNCITERATOR
+        return false;
+    }
+    if (!emitElemOpBase(JSOP_CALLELEM)) {
+        //                    [stack] OBJ ITERFN
         return false;
     }
 
     InternalIfEmitter ifAsyncIterIsUndefined(this);
-    if (!emitPushNotUndefinedOrNull()) {                          // OBJ ITERFN !UNDEF-OR-NULL
-        return false;
-    }
-    if (!emit1(JSOP_NOT)) {                                       // OBJ ITERFN UNDEF-OR-NULL
-        return false;
-    }
-    if (!ifAsyncIterIsUndefined.emitThenElse()) {                 // OBJ ITERFN
-        return false;
-    }
-
-    if (!emit1(JSOP_POP)) {                                       // OBJ
-        return false;
-    }
-    if (!emit1(JSOP_DUP)) {                                       // OBJ OBJ
-        return false;
-    }
-    if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) { // OBJ OBJ @@ITERATOR
-        return false;
-    }
-    if (!emitElemOpBase(JSOP_CALLELEM)) {                         // OBJ ITERFN
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                                      // ITERFN OBJ
-        return false;
-    }
-    if (!emitCall(JSOP_CALLITER, 0)) {                            // ITER
+    if (!emitPushNotUndefinedOrNull()) {
+        //                    [stack] OBJ ITERFN !UNDEF-OR-NULL
+        return false;
+    }
+    if (!emit1(JSOP_NOT)) {
+        //                    [stack] OBJ ITERFN UNDEF-OR-NULL
+        return false;
+    }
+    if (!ifAsyncIterIsUndefined.emitThenElse()) {
+        //                    [stack] OBJ ITERFN
+        return false;
+    }
+
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] OBJ
+        return false;
+    }
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] OBJ OBJ
+        return false;
+    }
+    if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) {
+        //                    [stack] OBJ OBJ @@ITERATOR
+        return false;
+    }
+    if (!emitElemOpBase(JSOP_CALLELEM)) {
+        //                    [stack] OBJ ITERFN
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] ITERFN OBJ
+        return false;
+    }
+    if (!emitCall(JSOP_CALLITER, 0)) {
+        //                    [stack] ITER
         return false;
     }
     checkTypeSet(JSOP_CALLITER);
-    if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {        // ITER
-        return false;
-    }
-
-    if (!emit1(JSOP_DUP)) {                                       // ITER ITER
-        return false;
-    }
-    if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) {            // ITER SYNCNEXT
-        return false;
-    }
-
-    if (!emit1(JSOP_TOASYNCITER)) {                               // ITER
-        return false;
-    }
-
-    if (!ifAsyncIterIsUndefined.emitElse()) {                     // OBJ ITERFN
-        return false;
-    }
-
-    if (!emit1(JSOP_SWAP)) {                                      // ITERFN OBJ
-        return false;
-    }
-    if (!emitCall(JSOP_CALLITER, 0)) {                            // ITER
+    if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {
+        //                    [stack] ITER
+        return false;
+    }
+
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] ITER ITER
+        return false;
+    }
+    if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) {
+        //                    [stack] ITER SYNCNEXT
+        return false;
+    }
+
+    if (!emit1(JSOP_TOASYNCITER)) {
+        //                    [stack] ITER
+        return false;
+    }
+
+    if (!ifAsyncIterIsUndefined.emitElse()) {
+        //                    [stack] OBJ ITERFN
+        return false;
+    }
+
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] ITERFN OBJ
+        return false;
+    }
+    if (!emitCall(JSOP_CALLITER, 0)) {
+        //                    [stack] ITER
         return false;
     }
     checkTypeSet(JSOP_CALLITER);
-    if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {        // ITER
-        return false;
-    }
-
-    if (!ifAsyncIterIsUndefined.emitEnd()) {                      // ITER
-        return false;
-    }
-
-    if (!emit1(JSOP_DUP)) {                                       // ITER ITER
-        return false;
-    }
-    if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) {            // ITER NEXT
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                                      // NEXT ITER
+    if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {
+        //                    [stack] ITER
+        return false;
+    }
+
+    if (!ifAsyncIterIsUndefined.emitEnd()) {
+        //                    [stack] ITER
+        return false;
+    }
+
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] ITER ITER
+        return false;
+    }
+    if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) {
+        //                    [stack] ITER NEXT
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] NEXT ITER
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitSpread(bool allowSelfHosted)
@@ -4882,70 +5112,81 @@ BytecodeEmitter::emitSpread(bool allowSe
     unsigned noteIndex;
     if (!newSrcNote(SRC_FOR_OF, &noteIndex)) {
         return false;
     }
 
     // Jump down to the loop condition to minimize overhead, assuming at least
     // one iteration.  (This is also what we do for loops; whether this
     // assumption holds for spreads is an unanswered question.)
-    if (!loopInfo.emitEntryJump(this)) {                  // NEXT ITER ARR I (during the goto)
-        return false;
-    }
-
-    if (!loopInfo.emitLoopHead(this, Nothing())) {        // NEXT ITER ARR I
+    if (!loopInfo.emitEntryJump(this)) {
+        //                    [stack] NEXT ITER ARR I (during the goto)
+        return false;
+    }
+
+    if (!loopInfo.emitLoopHead(this, Nothing())) {
+        //                    [stack] NEXT ITER ARR I
         return false;
     }
 
     // When we enter the goto above, we have NEXT ITER ARR I on the stack. But
     // when we reach this point on the loop backedge (if spreading produces at
     // least one value), we've additionally pushed a RESULT iteration value.
     // Increment manually to reflect this.
     this->stackDepth++;
 
     {
 #ifdef DEBUG
         auto loopDepth = this->stackDepth;
 #endif
 
         // Emit code to assign result.value to the iteration variable.
-        if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) { // NEXT ITER ARR I VALUE
-            return false;
-        }
-        if (!emit1(JSOP_INITELEM_INC)) {                  // NEXT ITER ARR (I+1)
+        if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {
+            //                [stack] NEXT ITER ARR I VALUE
+            return false;
+        }
+        if (!emit1(JSOP_INITELEM_INC)) {
+            //                [stack] NEXT ITER ARR (I+1)
             return false;
         }
 
         MOZ_ASSERT(this->stackDepth == loopDepth - 1);
 
         // Spread operations can't contain |continue|, so don't bother setting loop
         // and enclosing "update" offsets, as we do with for-loops.
 
         // COME FROM the beginning of the loop to here.
-        if (!loopInfo.emitLoopEntry(this, Nothing())) {   // NEXT ITER ARR I
-            return false;
-        }
-
-        if (!emitDupAt(3)) {                              // NEXT ITER ARR I NEXT
-            return false;
-        }
-        if (!emitDupAt(3)) {                              // NEXT ITER ARR I NEXT ITER
+        if (!loopInfo.emitLoopEntry(this, Nothing())) {
+            //                [stack] NEXT ITER ARR I
+            return false;
+        }
+
+        if (!emitDupAt(3)) {
+            //                [stack] NEXT ITER ARR I NEXT
+            return false;
+        }
+        if (!emitDupAt(3)) {
+            //                [stack] NEXT ITER ARR I NEXT ITER
             return false;
         }
         if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) {
-            return false;                                 // ITER ARR I RESULT
-        }
-        if (!emit1(JSOP_DUP)) {                           // NEXT ITER ARR I RESULT RESULT
-            return false;
-        }
-        if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {  // NEXT ITER ARR I RESULT DONE
-            return false;
-        }
-
-        if (!loopInfo.emitLoopEnd(this, JSOP_IFEQ)) {     // NEXT ITER ARR I RESULT
+            //                [stack] ITER ARR I RESULT
+            return false;
+        }
+        if (!emit1(JSOP_DUP)) {
+            //                [stack] NEXT ITER ARR I RESULT RESULT
+            return false;
+        }
+        if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {
+            //                [stack] NEXT ITER ARR I RESULT DONE
+            return false;
+        }
+
+        if (!loopInfo.emitLoopEnd(this, JSOP_IFEQ)) {
+            //                [stack] NEXT ITER ARR I RESULT
             return false;
         }
 
         MOZ_ASSERT(this->stackDepth == loopDepth);
     }
 
     // Let Ion know where the closing jump of this loop is.
     if (!setSrcNoteOffset(noteIndex, SrcNote::ForOf::BackJumpOffset,
@@ -4959,24 +5200,27 @@ BytecodeEmitter::emitSpread(bool allowSe
     MOZ_ASSERT(loopInfo.continues.offset == -1);
 
     if (!addTryNote(JSTRY_FOR_OF, stackDepth, loopInfo.headOffset(),
                     loopInfo.breakTargetOffset()))
     {
         return false;
     }
 
-    if (!emit2(JSOP_PICK, 4)) {                           // ITER ARR FINAL_INDEX RESULT NEXT
-        return false;
-    }
-    if (!emit2(JSOP_PICK, 4)) {                           // ARR FINAL_INDEX RESULT NEXT ITER
-        return false;
-    }
-
-    return emitPopN(3);                                   // ARR FINAL_INDEX
+    if (!emit2(JSOP_PICK, 4)) {
+        //                    [stack] ITER ARR FINAL_INDEX RESULT NEXT
+        return false;
+    }
+    if (!emit2(JSOP_PICK, 4)) {
+        //                    [stack] ARR FINAL_INDEX RESULT NEXT ITER
+        return false;
+    }
+
+    return emitPopN(3);
+    //                        [stack] ARR FINAL_INDEX
 }
 
 bool
 BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
 {
     MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) ||
                forHead->isKind(ParseNodeKind::ForOf));
 
@@ -4985,17 +5229,18 @@ BytecodeEmitter::emitInitializeForInOrOf
 
     ParseNode* target = forHead->kid1();
     MOZ_ASSERT(!forHead->kid2());
 
     // If the for-in/of loop didn't have a variable declaration, per-loop
     // initialization is just assigning the iteration value to a target
     // expression.
     if (!parser->astGenerator().isDeclarationList(target)) {
-        return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL
+        return emitAssignment(target, JSOP_NOP, nullptr);
+        //                    [stack] ... ITERVAL
     }
 
     // Otherwise, per-loop initialization is (possibly) declaration
     // initialization.  If the declaration is a lexical declaration, it must be
     // initialized.  If the declaration is a variable declaration, an
     // assignment to that name (which does *not* necessarily assign to the
     // variable!) must be generated.
 
@@ -5017,18 +5262,18 @@ BytecodeEmitter::emitInitializeForInOrOf
             // iteration value *before* initializing.  Thus the initializing
             // value may be buried under a bind-specific value on the stack.
             // Swap it to the top of the stack.
             MOZ_ASSERT(stackDepth >= 2);
             if (!emit1(JSOP_SWAP)) {
                 return false;
             }
         } else {
-             // In cases of emitting a frame slot or environment slot,
-             // nothing needs be done.
+            // In cases of emitting a frame slot or environment slot,
+            // nothing needs be done.
             MOZ_ASSERT(stackDepth >= 1);
         }
         if (!noe.emitAssignment()) {
             return false;
         }
 
         // The caller handles removing the iteration value from the stack.
         return true;
@@ -5065,49 +5310,56 @@ BytecodeEmitter::emitForOf(ForNode* forO
     if (emitterMode == BytecodeEmitter::SelfHosting &&
         forHeadExpr->isKind(ParseNodeKind::Call) &&
         forHeadExpr->as<BinaryNode>().left()->isName(cx->names().allowContentIter)) {
         allowSelfHostedIter = true;
     }
 
     ForOfEmitter forOf(this, headLexicalEmitterScope, allowSelfHostedIter, iterKind);
 
-    if (!forOf.emitIterated()) {                          //
-        return false;
-    }
-
-    if (!emitTree(forHeadExpr)) {                         // ITERABLE
+    if (!forOf.emitIterated()) {
+        //                    [stack]
+        return false;
+    }
+
+    if (!emitTree(forHeadExpr)) {
+        //                    [stack] ITERABLE
         return false;
     }
 
     if (headLexicalEmitterScope) {
         DebugOnly<ParseNode*> forOfTarget = forOfHead->kid1();
         MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::Let) ||
                    forOfTarget->isKind(ParseNodeKind::Const));
     }
 
     if (!forOf.emitInitialize(Some(forOfHead->pn_pos.begin))) {
-        return false;                                     // NEXT ITER VALUE
-    }
-
-    if (!emitInitializeForInOrOfTarget(forOfHead)) {      // NEXT ITER VALUE
-        return false;
-    }
-
-    if (!forOf.emitBody()) {                              // NEXT ITER UNDEF
+        //                    [stack] NEXT ITER VALUE
+        return false;
+    }
+
+    if (!emitInitializeForInOrOfTarget(forOfHead)) {
+        //                    [stack] NEXT ITER VALUE
+        return false;
+    }
+
+    if (!forOf.emitBody()) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
     // Perform the loop body.
     ParseNode* forBody = forOfLoop->body();
-    if (!emitTree(forBody)) {                             // NEXT ITER UNDEF
-        return false;
-    }
-
-    if (!forOf.emitEnd(Some(forHeadExpr->pn_pos.begin))) {  //
+    if (!emitTree(forBody)) {
+        //                    [stack] NEXT ITER UNDEF
+        return false;
+    }
+
+    if (!forOf.emitEnd(Some(forHeadExpr->pn_pos.begin))) {
+        //                    [stack]
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitForIn(ForNode* forInLoop, const EmitterScope* headLexicalEmitterScope)
@@ -5149,51 +5401,58 @@ BytecodeEmitter::emitForIn(ForNode* forI
                 // Pop the initializer.
                 if (!emit1(JSOP_POP)) {
                     return false;
                 }
             }
         }
     }
 
-    if (!forIn.emitIterated()) {                          //
+    if (!forIn.emitIterated()) {
+        //                    [stack]
         return false;
     }
 
     // Evaluate the expression being iterated.
     ParseNode* expr = forInHead->kid3();
-    if (!emitTree(expr)) {                                // EXPR
+    if (!emitTree(expr)) {
+        //                    [stack] EXPR
         return false;
     }
 
     MOZ_ASSERT(forInLoop->iflags() == 0);
 
     MOZ_ASSERT_IF(headLexicalEmitterScope,
                   forInTarget->isKind(ParseNodeKind::Let) ||
                   forInTarget->isKind(ParseNodeKind::Const));
 
-    if (!forIn.emitInitialize()) {                        // ITER ITERVAL
-        return false;
-    }
-
-    if (!emitInitializeForInOrOfTarget(forInHead)) {      // ITER ITERVAL
-        return false;
-    }
-
-    if (!forIn.emitBody()) {                              // ITER ITERVAL
+    if (!forIn.emitInitialize()) {
+        //                    [stack] ITER ITERVAL
+        return false;
+    }
+
+    if (!emitInitializeForInOrOfTarget(forInHead)) {
+        //                    [stack] ITER ITERVAL
+        return false;
+    }
+
+    if (!forIn.emitBody()) {
+        //                    [stack] ITER ITERVAL
         return false;
     }
 
     // Perform the loop body.
     ParseNode* forBody = forInLoop->body();
-    if (!emitTree(forBody)) {                             // ITER ITERVAL
-        return false;
-    }
-
-    if (!forIn.emitEnd(Some(forInHead->pn_pos.begin))) {  //
+    if (!emitTree(forBody)) {
+        //                    [stack] ITER ITERVAL
+        return false;
+    }
+
+    if (!forIn.emitEnd(Some(forInHead->pn_pos.begin))) {
+        //                    [stack]
         return false;
     }
 
     return true;
 }
 
 /* C-style `for (init; cond; update) ...` loop. */
 bool
@@ -5204,80 +5463,91 @@ BytecodeEmitter::emitCStyleFor(ForNode* 
     ParseNode* init = forHead->kid1();
     ParseNode* cond = forHead->kid2();
     ParseNode* update = forHead->kid3();
     bool isLet = init && init->isKind(ParseNodeKind::Let);
 
     CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr);
 
     if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) {
-        return false;                                     //
+        //                    [stack]
+        return false;
     }
 
     // If the head of this for-loop declared any lexical variables, the parser
     // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope
     // representing the implicit scope of those variables. By the time we get
     // here, we have already entered that scope. So far, so good.
     if (init) {
         // Emit the `init` clause, whether it's an expression or a variable
         // declaration. (The loop variables were hoisted into an enclosing
         // scope, but we still need to emit code for the initializers.)
         if (init->isForLoopDeclaration()) {
-            if (!emitTree(init)) {                        //
+            if (!emitTree(init)) {
+                //            [stack]
                 return false;
             }
         } else {
             // 'init' is an expression, not a declaration. emitTree left its
             // value on the stack.
-            if (!emitTree(init, ValueUsage::IgnoreValue)) { // VAL
-                return false;
-            }
-            if (!emit1(JSOP_POP)) {                       //
+            if (!emitTree(init, ValueUsage::IgnoreValue)) {
+                //            [stack] VAL
+                return false;
+            }
+            if (!emit1(JSOP_POP)) {
+                //            [stack]
                 return false;
             }
         }
     }
 
     if (!cfor.emitBody(cond ? CForEmitter::Cond::Present : CForEmitter::Cond::Missing,
-                       getOffsetForLoop(forBody)))        //
+                       getOffsetForLoop(forBody)))
     {
-        return false;
-    }
-
-    if (!emitTree(forBody)) {                             //
+        //                    [stack]
+        return false;
+    }
+
+    if (!emitTree(forBody)) {
+        //                    [stack]
         return false;
     }
 
     if (!cfor.emitUpdate(update ? CForEmitter::Update::Present : CForEmitter::Update::Missing,
                          update ? Some(update->pn_pos.begin) : Nothing()))
-    {                                                     //
+    {
+        //                    [stack]
         return false;
     }
 
     // Check for update code to do before the condition (if any).
     if (update) {
-        if (!emitTree(update, ValueUsage::IgnoreValue)) { // VAL
+        if (!emitTree(update, ValueUsage::IgnoreValue)) {
+            //                [stack] VAL
             return false;
         }
     }
 
     if (!cfor.emitCond(Some(forNode->pn_pos.begin),
                        cond ? Some(cond->pn_pos.begin) : Nothing(),
-                       Some(forNode->pn_pos.end)))        //
+                       Some(forNode->pn_pos.end)))
     {
+        //                    [stack]
         return false;
     }
 
     if (cond) {
-        if (!emitTree(cond)) {                            // VAL
-            return false;
-        }
-    }
-
-    if (!cfor.emitEnd()) {                                //
+        if (!emitTree(cond)) {
+            //                [stack] VAL
+            return false;
+        }
+    }
+
+    if (!cfor.emitEnd()) {
+        //                    [stack]
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope)
@@ -5728,50 +5998,56 @@ bool
 BytecodeEmitter::emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset)
 {
     if (offset) {
         if (!updateLineNumberNotes(*offset)) {
             return false;
         }
     }
 
-    if (!emitGetName(cx->names().dotThis)) {              // THIS
+    if (!emitGetName(cx->names().dotThis)) {
+        //                    [stack] THIS
         return false;
     }
     if (sc->needsThisTDZChecks()) {
-        if (!emit1(JSOP_CHECKTHIS)) {                     // THIS
+        if (!emit1(JSOP_CHECKTHIS)) {
+            //                [stack] THIS
             return false;
         }
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase)
 {
     MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase));
     NameNode* nameNode = &superBase->kid()->as<NameNode>();
-    return emitGetFunctionThis(nameNode);                 // THIS
+    return emitGetFunctionThis(nameNode);
+    //                        [stack] THIS
 }
 
 bool
 BytecodeEmitter::emitThisLiteral(ThisLiteral* pn)
 {
     if (ParseNode* kid = pn->kid()) {
         NameNode* thisName = &kid->as<NameNode>();
-        return emitGetFunctionThis(thisName);             // THIS
+        return emitGetFunctionThis(thisName);
+        //                    [stack] THIS
     }
 
     if (sc->thisBinding() == ThisBinding::Module) {
-        return emit1(JSOP_UNDEFINED);                     // UNDEF
+        return emit1(JSOP_UNDEFINED);
+        //                    [stack] UNDEF
     }
 
     MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global);
-    return emit1(JSOP_GLOBALTHIS);                        // THIS
+    return emit1(JSOP_GLOBALTHIS);
+    //                        [stack] THIS
 }
 
 bool
 BytecodeEmitter::emitCheckDerivedClassConstructorReturn()
 {
     MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot());
     if (!emitGetName(cx->names().dotThis)) {
         return false;
@@ -5920,48 +6196,55 @@ bool
 BytecodeEmitter::emitYield(UnaryNode* yieldNode)
 {
     MOZ_ASSERT(sc->isFunctionBox());
     MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::Yield));
 
     bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult();
     if (needsIteratorResult) {
         if (!emitPrepareIteratorResult()) {
+            //                [stack] ITEROBJ
             return false;
         }
     }
     if (ParseNode* expr = yieldNode->kid()) {
         if (!emitTree(expr)) {
+            //                [stack] ITEROBJ VAL
             return false;
         }
     } else {
         if (!emit1(JSOP_UNDEFINED)) {
+            //                [stack] ITEROBJ UNDEFINED
             return false;
         }
     }
 
     // 11.4.3.7 AsyncGeneratorYield step 5.
     bool isAsyncGenerator = sc->asFunctionBox()->isAsync();
     if (isAsyncGenerator) {
-        if (!emitAwaitInInnermostScope()) {               // RESULT
+        if (!emitAwaitInInnermostScope()) {
+            //                [stack] ITEROBJ RESULT
             return false;
         }
     }
 
     if (needsIteratorResult) {
         if (!emitFinishIteratorResult(false)) {
+            //                [stack] ITEROBJ
             return false;
         }
     }
 
     if (!emitGetDotGeneratorInInnermostScope()) {
+        //                    [stack] ITEROBJ .GENERATOR
         return false;
     }
 
     if (!emitYieldOp(JSOP_YIELD)) {
+        //                    [stack] YIELDRESULT
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode)
@@ -5973,33 +6256,38 @@ BytecodeEmitter::emitAwaitInInnermostSco
         return false;
     }
     return emitAwaitInInnermostScope();
 }
 
 bool
 BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope)
 {
-    if (!emit1(JSOP_TRYSKIPAWAIT)) {            // VALUE_OR_RESOLVED CANSKIP
-        return false;
-    }
-
-    if (!emit1(JSOP_NOT)) {                     // VALUE_OR_RESOLVED !CANSKIP
+    if (!emit1(JSOP_TRYSKIPAWAIT)) {
+        //                    [stack] VALUE_OR_RESOLVED CANSKIP
+        return false;
+    }
+
+    if (!emit1(JSOP_NOT)) {
+        //                    [stack] VALUE_OR_RESOLVED !CANSKIP
         return false;
     }
 
     InternalIfEmitter ifCanSkip(this);
-    if (!ifCanSkip.emitThen()) {                // VALUE_OR_RESOLVED
+    if (!ifCanSkip.emitThen()) {
+        //                    [stack] VALUE_OR_RESOLVED
         return false;
     }
 
     if (!emitGetDotGeneratorInScope(currentScope)) {
-        return false;                           // VALUE GENERATOR
-    }
-    if (!emitYieldOp(JSOP_AWAIT)) {             // RESOLVED
+        //                    [stack] VALUE GENERATOR
+        return false;
+    }
+    if (!emitYieldOp(JSOP_AWAIT)) {
+        //                    [stack] RESOLVED
         return false;
     }
 
     if (!ifCanSkip.emitEnd()) {
         return false;
     }
 
     MOZ_ASSERT(ifCanSkip.popped() == 0);
@@ -6012,356 +6300,433 @@ BytecodeEmitter::emitYieldStar(ParseNode
 {
     MOZ_ASSERT(sc->isFunctionBox());
     MOZ_ASSERT(sc->asFunctionBox()->isGenerator());
 
     IteratorKind iterKind = sc->asFunctionBox()->isAsync()
                             ? IteratorKind::Async
                             : IteratorKind::Sync;
 
-    if (!emitTree(iter)) {                                // ITERABLE
+    if (!emitTree(iter)) {
+        //                    [stack] ITERABLE
         return false;
     }
     if (iterKind == IteratorKind::Async) {
-        if (!emitAsyncIterator()) {                       // NEXT ITER
+        if (!emitAsyncIterator()) {
+            //                [stack] NEXT ITER
             return false;
         }
     } else {
-        if (!emitIterator()) {                            // NEXT ITER
+        if (!emitIterator()) {
+            //                [stack] NEXT ITER
             return false;
         }
     }
 
     // Initial send value is undefined.
-    if (!emit1(JSOP_UNDEFINED)) {                         // NEXT ITER RECEIVED
+    if (!emit1(JSOP_UNDEFINED)) {
+        //                    [stack] NEXT ITER RECEIVED
         return false;
     }
 
     int32_t savedDepthTemp;
     int32_t startDepth = stackDepth;
     MOZ_ASSERT(startDepth >= 3);
 
     TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally,
                         TryEmitter::ControlKind::NonSyntactic);
-    if (!tryCatch.emitJumpOverCatchAndFinally()) {        // NEXT ITER RESULT
+    if (!tryCatch.emitJumpOverCatchAndFinally()) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
 
     JumpTarget tryStart{ offset() };
-    if (!tryCatch.emitTry()) {                            // NEXT ITER RESULT
+    if (!tryCatch.emitTry()) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
 
     MOZ_ASSERT(this->stackDepth == startDepth);
 
     // 11.4.3.7 AsyncGeneratorYield step 5.
     if (iterKind == IteratorKind::Async) {
-        if (!emitAwaitInInnermostScope()) {               // NEXT ITER RESULT
+        if (!emitAwaitInInnermostScope()) {
+            //                [stack] NEXT ITER RESULT
             return false;
         }
     }
 
     // Load the generator object.
-    if (!emitGetDotGeneratorInInnermostScope()) {         // NEXT ITER RESULT GENOBJ
+    if (!emitGetDotGeneratorInInnermostScope()) {
+        //                    [stack] NEXT ITER RESULT GENOBJ
         return false;
     }
 
     // Yield RESULT as-is, without re-boxing.
-    if (!emitYieldOp(JSOP_YIELD)) {                       // NEXT ITER RECEIVED
-        return false;
-    }
-
-    if (!tryCatch.emitCatch()) {                          // NEXT ITER RESULT
+    if (!emitYieldOp(JSOP_YIELD)) {
+        //                    [stack] NEXT ITER RECEIVED
+        return false;
+    }
+
+    if (!tryCatch.emitCatch()) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
 
     MOZ_ASSERT(stackDepth == startDepth);
 
-    if (!emit1(JSOP_EXCEPTION)) {                         // NEXT ITER RESULT EXCEPTION
-        return false;
-    }
-    if (!emitDupAt(2)) {                                  // NEXT ITER RESULT EXCEPTION ITER
-        return false;
-    }
-    if (!emit1(JSOP_DUP)) {                               // NEXT ITER RESULT EXCEPTION ITER ITER
-        return false;
-    }
-    if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) { // NEXT ITER RESULT EXCEPTION ITER THROW
+    if (!emit1(JSOP_EXCEPTION)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION
+        return false;
+    }
+    if (!emitDupAt(2)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER
+        return false;
+    }
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER ITER
+        return false;
+    }
+    if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER THROW
         return false;
     }
 
     savedDepthTemp = stackDepth;
     InternalIfEmitter ifThrowMethodIsNotDefined(this);
-    if (!emitPushNotUndefinedOrNull()) {                  // NEXT ITER RESULT EXCEPTION ITER THROW NOT-UNDEF-OR-NULL
-        return false;
-    }
-
-    if (!ifThrowMethodIsNotDefined.emitThenElse()) {      // NEXT ITER RESULT EXCEPTION ITER THROW
-        return false;
-    }
+    if (!emitPushNotUndefinedOrNull()) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER THROW NOT-UNDEF-OR-NULL
+        return false;
+    }
+
+    if (!ifThrowMethodIsNotDefined.emitThenElse()) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER THROW
+        return false;
+    }
+
+    //                        [stack] NEXT ITER OLDRESULT EXCEPTION ITER THROW
 
     // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4.
-    // RESULT = ITER.throw(EXCEPTION)                     // NEXT ITER OLDRESULT EXCEPTION ITER THROW
-    if (!emit1(JSOP_SWAP)) {                              // NEXT ITER OLDRESULT EXCEPTION THROW ITER
-        return false;
-    }
-    if (!emit2(JSOP_PICK, 2)) {                           // NEXT ITER OLDRESULT THROW ITER EXCEPTION
-        return false;
-    }
-    if (!emitCall(JSOP_CALL, 1, iter)) {                  // NEXT ITER OLDRESULT RESULT
+    // RESULT = ITER.throw(EXCEPTION)
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] NEXT ITER OLDRESULT EXCEPTION THROW ITER
+        return false;
+    }
+    if (!emit2(JSOP_PICK, 2)) {
+        //                    [stack] NEXT ITER OLDRESULT THROW ITER EXCEPTION
+        return false;
+    }
+    if (!emitCall(JSOP_CALL, 1, iter)) {
+        //                    [stack] NEXT ITER OLDRESULT RESULT
         return false;
     }
     checkTypeSet(JSOP_CALL);
 
     if (iterKind == IteratorKind::Async) {
-        if (!emitAwaitInInnermostScope()) {               // NEXT ITER OLDRESULT RESULT
-            return false;
-        }
-    }
-
-    if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) { // NEXT ITER OLDRESULT RESULT
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                              // NEXT ITER RESULT OLDRESULT
-        return false;
-    }
-    if (!emit1(JSOP_POP)) {                               // NEXT ITER RESULT
+        if (!emitAwaitInInnermostScope()) {
+            //                [stack] NEXT ITER OLDRESULT RESULT
+            return false;
+        }
+    }
+
+    if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) {
+        //                    [stack] NEXT ITER OLDRESULT RESULT
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] NEXT ITER RESULT OLDRESULT
+        return false;
+    }
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
     MOZ_ASSERT(this->stackDepth == startDepth);
     JumpList checkResult;
     // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii.
     //
     // Note that there is no GOSUB to the finally block here. If the iterator has a
     // "throw" method, it does not perform IteratorClose.
-    if (!emitJump(JSOP_GOTO, &checkResult)) {             // goto checkResult
+    if (!emitJump(JSOP_GOTO, &checkResult)) {
+        //                    [stack] NEXT ITER RESULT
+        //                    [stack] # goto checkResult
         return false;
     }
 
     stackDepth = savedDepthTemp;
-    if (!ifThrowMethodIsNotDefined.emitElse()) {          // NEXT ITER RESULT EXCEPTION ITER THROW
-        return false;
-    }
-
-    if (!emit1(JSOP_POP)) {                               // NEXT ITER RESULT EXCEPTION ITER
+    if (!ifThrowMethodIsNotDefined.emitElse()) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER THROW
+        return false;
+    }
+
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION ITER
         return false;
     }
     // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.2
     //
     // If the iterator does not have a "throw" method, it calls IteratorClose
     // and then throws a TypeError.
-    if (!emitIteratorCloseInInnermostScope(iterKind)) {   // NEXT ITER RESULT EXCEPTION
-        return false;
-    }
-    if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) { // throw
+    if (!emitIteratorCloseInInnermostScope(iterKind)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION
+        return false;
+    }
+    if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) {
+        //                    [stack] NEXT ITER RESULT EXCEPTION
+        //                    [stack] # throw
         return false;
     }
 
     stackDepth = savedDepthTemp;
     if (!ifThrowMethodIsNotDefined.emitEnd()) {
         return false;
     }
 
     stackDepth = startDepth;
     if (!tryCatch.emitFinally()) {
-         return false;
+        return false;
     }
 
     // ES 14.4.13, yield * AssignmentExpression, step 5.c
     //
     // Call iterator.return() for receiving a "forced return" completion from
     // the generator.
 
     InternalIfEmitter ifGeneratorClosing(this);
-    if (!emit1(JSOP_ISGENCLOSING)) {                      // NEXT ITER RESULT FTYPE FVALUE CLOSING
-        return false;
-    }
-    if (!ifGeneratorClosing.emitThen()) {                 // NEXT ITER RESULT FTYPE FVALUE
+    if (!emit1(JSOP_ISGENCLOSING)) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE CLOSING
+        return false;
+    }
+    if (!ifGeneratorClosing.emitThen()) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE
         return false;
     }
 
     // Step ii.
     //
     // Get the "return" method.
-    if (!emitDupAt(3)) {                                  // NEXT ITER RESULT FTYPE FVALUE ITER
-        return false;
-    }
-    if (!emit1(JSOP_DUP)) {                               // NEXT ITER RESULT FTYPE FVALUE ITER ITER
-        return false;
-    }
-    if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) {  // NEXT ITER RESULT FTYPE FVALUE ITER RET
+    if (!emitDupAt(3)) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE ITER
+        return false;
+    }
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE ITER ITER
+        return false;
+    }
+    if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE ITER RET
         return false;
     }
 
     // Step iii.
     //
     // Do nothing if "return" is undefined or null.
     InternalIfEmitter ifReturnMethodIsDefined(this);
-    if (!emitPushNotUndefinedOrNull()) {                  // NEXT ITER RESULT FTYPE FVALUE ITER RET NOT-UNDEF-OR-NULL
+    if (!emitPushNotUndefinedOrNull()) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE ITER RET NOT-UNDEF-OR-NULL
         return false;
     }
 
     // Step iv.
     //
     // Call "return" with the argument passed to Generator.prototype.return,
     // which is currently in rval.value.
-    if (!ifReturnMethodIsDefined.emitThenElse()) {        // NEXT ITER OLDRESULT FTYPE FVALUE ITER RET
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                              // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER
-        return false;
-    }
-    if (!emit1(JSOP_GETRVAL)) {                           // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER RVAL
-        return false;
-    }
-    if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {   // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER VALUE
-        return false;
-    }
-    if (!emitCall(JSOP_CALL, 1)) {                        // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
+    if (!ifReturnMethodIsDefined.emitThenElse()) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE ITER RET
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RET ITER
+        return false;
+    }
+    if (!emit1(JSOP_GETRVAL)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RET ITER RVAL
+        return false;
+    }
+    if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RET ITER VALUE
+        return false;
+    }
+    if (!emitCall(JSOP_CALL, 1)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT
         return false;
     }
     checkTypeSet(JSOP_CALL);
 
     if (iterKind == IteratorKind::Async) {
-        if (!emitAwaitInInnermostScope()) {               // ... FTYPE FVALUE RESULT
+        if (!emitAwaitInInnermostScope()) {
+            //                [stack] ... FTYPE FVALUE RESULT
             return false;
         }
     }
 
     // Step v.
-    if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
+    if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT
         return false;
     }
 
     // Steps vi-viii.
     //
     // Check if the returned object from iterator.return() is done. If not,
     // continuing yielding.
     InternalIfEmitter ifReturnDone(this);
-    if (!emit1(JSOP_DUP)) {                               // NEXT ITER OLDRESULT FTYPE FVALUE RESULT RESULT
-        return false;
-    }
-    if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {    // NEXT ITER OLDRESULT FTYPE FVALUE RESULT DONE
-        return false;
-    }
-    if (!ifReturnDone.emitThenElse()) {                   // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
-        return false;
-    }
-    if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {   // NEXT ITER OLDRESULT FTYPE FVALUE VALUE
-        return false;
-    }
-
-    if (!emitPrepareIteratorResult()) {                   // NEXT ITER OLDRESULT FTYPE FVALUE VALUE RESULT
-        return false;
-    }
-    if (!emit1(JSOP_SWAP)) {                              // NEXT ITER OLDRESULT FTYPE FVALUE RESULT VALUE
-        return false;
-    }
-    if (!emitFinishIteratorResult(true)) {                // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
-        return false;
-    }
-    if (!emit1(JSOP_SETRVAL)) {                           // NEXT ITER OLDRESULT FTYPE FVALUE
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT RESULT
+        return false;
+    }
+    if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT DONE
+        return false;
+    }
+    if (!ifReturnDone.emitThenElse()) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT
+        return false;
+    }
+    if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE VALUE
+        return false;
+    }
+
+    if (!emitPrepareIteratorResult()) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE VALUE RESULT
+        return false;
+    }
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT VALUE
+        return false;
+    }
+    if (!emitFinishIteratorResult(true)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT
+        return false;
+    }
+    if (!emit1(JSOP_SETRVAL)) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE
         return false;
     }
     savedDepthTemp = this->stackDepth;
-    if (!ifReturnDone.emitElse()) {                       // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
-        return false;
-    }
-    if (!emit2(JSOP_UNPICK, 3)) {                         // NEXT ITER RESULT OLDRESULT FTYPE FVALUE
-        return false;
-    }
-    if (!emitPopN(3)) {                                   // NEXT ITER RESULT
+    if (!ifReturnDone.emitElse()) {
+        //                    [stack] NEXT ITER OLDRESULT FTYPE FVALUE RESULT
+        return false;
+    }
+    if (!emit2(JSOP_UNPICK, 3)) {
+        //                    [stack] NEXT ITER RESULT OLDRESULT FTYPE FVALUE
+        return false;
+    }
+    if (!emitPopN(3)) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
     {
         // goto tryStart;
         JumpList beq;
         JumpTarget breakTarget{ -1 };
-        if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq, &breakTarget)) { // NEXT ITER RESULT
+        if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq, &breakTarget)) {
+            //                [stack] NEXT ITER RESULT
             return false;
         }
     }
     this->stackDepth = savedDepthTemp;
     if (!ifReturnDone.emitEnd()) {
         return false;
     }
 
-    if (!ifReturnMethodIsDefined.emitElse()) {            // NEXT ITER RESULT FTYPE FVALUE ITER RET
-        return false;
-    }
-    if (!emitPopN(2)) {                                   // NEXT ITER RESULT FTYPE FVALUE
+    if (!ifReturnMethodIsDefined.emitElse()) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE ITER RET
+        return false;
+    }
+    if (!emitPopN(2)) {
+        //                    [stack] NEXT ITER RESULT FTYPE FVALUE
         return false;
     }
     if (!ifReturnMethodIsDefined.emitEnd()) {
         return false;
     }
 
     if (!ifGeneratorClosing.emitEnd()) {
         return false;
     }
 
     if (!tryCatch.emitEnd()) {
         return false;
     }
 
+    //                        [stack] NEXT ITER RECEIVED
+
     // After the try-catch-finally block: send the received value to the iterator.
-    // result = iter.next(received)                              // NEXT ITER RECEIVED
-    if (!emit2(JSOP_UNPICK, 2)) {                                // RECEIVED NEXT ITER
-        return false;
-    }
-    if (!emit1(JSOP_DUP2)) {                                     // RECEIVED NEXT ITER NEXT ITER
-        return false;
-    }
-    if (!emit2(JSOP_PICK, 4)) {                                  // NEXT ITER NEXT ITER RECEIVED
-        return false;
-    }
-    if (!emitCall(JSOP_CALL, 1, iter)) {                         // NEXT ITER RESULT
+    // result = iter.next(received)
+    if (!emit2(JSOP_UNPICK, 2)) {
+        //                    [stack] RECEIVED NEXT ITER
+        return false;
+    }
+    if (!emit1(JSOP_DUP2)) {
+        //                    [stack] RECEIVED NEXT ITER NEXT ITER
+        return false;
+    }
+    if (!emit2(JSOP_PICK, 4)) {
+        //                    [stack] NEXT ITER NEXT ITER RECEIVED
+        return false;
+    }
+    if (!emitCall(JSOP_CALL, 1, iter)) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
     checkTypeSet(JSOP_CALL);
 
     if (iterKind == IteratorKind::Async) {
-        if (!emitAwaitInInnermostScope()) {                      // NEXT ITER RESULT RESULT
-            return false;
-        }
-    }
-
-    if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) {      // NEXT ITER RESULT
+        if (!emitAwaitInInnermostScope()) {
+            //                [stack] NEXT ITER RESULT RESULT
+            return false;
+        }
+    }
+
+    if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
     MOZ_ASSERT(this->stackDepth == startDepth);
 
-    if (!emitJumpTargetAndPatch(checkResult)) {                  // checkResult:
-        return false;
-    }
-
-    // if (!result.done) goto tryStart;                          // NEXT ITER RESULT
-    if (!emit1(JSOP_DUP)) {                                      // NEXT ITER RESULT RESULT
-        return false;
-    }
-    if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {           // NEXT ITER RESULT DONE
+    if (!emitJumpTargetAndPatch(checkResult)) {
+        //                    [stack] NEXT ITER RESULT
+        //                    [stack] # checkResult:
+        return false;
+    }
+
+    //                        [stack] NEXT ITER RESULT
+
+    // if (!result.done) goto tryStart;
+    if (!emit1(JSOP_DUP)) {
+        //                    [stack] NEXT ITER RESULT RESULT
+        return false;
+    }
+    if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) {
+        //                    [stack] NEXT ITER RESULT DONE
         return false;
     }
     // if (!DONE) goto tryStart;
     {
         JumpList beq;
         JumpTarget breakTarget{ -1 };
-        if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) { // NEXT ITER RESULT
+        if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) {
+            //                [stack] NEXT ITER RESULT
             return false;
         }
     }
 
     // result.value
-    if (!emit2(JSOP_UNPICK, 2)) {                                // RESULT NEXT ITER
-        return false;
-    }
-    if (!emitPopN(2)) {                                          // RESULT
-        return false;
-    }
-    if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {          // VALUE
+    if (!emit2(JSOP_UNPICK, 2)) {
+        //                    [stack] RESULT NEXT ITER
+        return false;
+    }
+    if (!emitPopN(2)) {
+        //                    [stack] RESULT
+        return false;
+    }
+    if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) {
+        //                    [stack] VALUE
         return false;
     }
 
     MOZ_ASSERT(this->stackDepth == startDepth - 2);
 
     return true;
 }
 
@@ -6493,32 +6858,35 @@ BytecodeEmitter::emitDeleteProperty(Unar
                       ? PropOpEmitter::ObjKind::Super
                       : PropOpEmitter::ObjKind::Other);
     if (propExpr->isSuper()) {
         // The expression |delete super.foo;| has to evaluate |super.foo|,
         // which could throw if |this| hasn't yet been set by a |super(...)|
         // call or the super-base is not an object, before throwing a
         // ReferenceError for attempting to delete a super-reference.
         UnaryNode* base = &propExpr->expression().as<UnaryNode>();
-        if (!emitGetThisForSuperBase(base)) {             // THIS
+        if (!emitGetThisForSuperBase(base)) {
+            //                [stack] THIS
             return false;
         }
     } else {
         if (!poe.prepareForObj()) {
             return false;
         }
-        if (!emitPropLHS(propExpr)) {                         // OBJ
-            return false;
-        }
-    }
-
-    if (!poe.emitDelete(propExpr->key().atom())) {        // [Super]
-        //                                                // THIS
-        //                                                // [Other]
-        //                                                // SUCCEEDED
+        if (!emitPropLHS(propExpr)) {
+            //                [stack] OBJ
+            return false;
+        }
+    }
+
+    if (!poe.emitDelete(propExpr->key().atom())) {
+        //                    [stack] # if Super
+        //                    [stack] THIS
+        //                    [stack] # otherwise
+        //                    [stack] SUCCEEDED
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode)
@@ -6533,39 +6901,45 @@ BytecodeEmitter::emitDeleteElement(Unary
                       ? ElemOpEmitter::ObjKind::Super
                       : ElemOpEmitter::ObjKind::Other);
     if (isSuper) {
         // The expression |delete super[foo];| has to evaluate |super[foo]|,
         // which could throw if |this| hasn't yet been set by a |super(...)|
         // call, or trigger side-effects when evaluating ToPropertyKey(foo),
         // or also throw when the super-base is not an object, before throwing
         // a ReferenceError for attempting to delete a super-reference.
-        if (!eoe.prepareForObj()) {                       //
+        if (!eoe.prepareForObj()) {
+            //                [stack]
             return false;
         }
 
         UnaryNode* base = &elemExpr->expression().as<UnaryNode>();
-        if (!emitGetThisForSuperBase(base)) {             // THIS
-            return false;
-        }
-        if (!eoe.prepareForKey()) {                       // THIS
-            return false;
-        }
-        if (!emitTree(&elemExpr->key())) {                // THIS KEY
+        if (!emitGetThisForSuperBase(base)) {
+            //                [stack] THIS
+            return false;
+        }
+        if (!eoe.prepareForKey()) {
+            //                [stack] THIS
+            return false;
+        }
+        if (!emitTree(&elemExpr->key())) {
+            //                [stack] THIS KEY
             return false;
         }
     } else {
-        if (!emitElemObjAndKey(elemExpr, false, eoe)) {   // OBJ KEY
-            return false;
-        }
-    }
-    if (!eoe.emitDelete()) {                              // [Super]
-        //                                                // THIS
-        //                                                // [Other]
-        //                                                // SUCCEEDED
+        if (!emitElemObjAndKey(elemExpr, false, eoe)) {
+            //                [stack] OBJ KEY
+            return false;
+        }
+    }
+    if (!eoe.emitDelete()) {
+        //                    [stack] # if Super
+        //                    [stack] THIS
+        //                    [stack] # otherwise
+        //                    [stack] SUCCEEDED
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode)
@@ -6877,116 +7251,129 @@ BytecodeEmitter::isRestParameter(ParseNo
 }
 
 bool
 BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone)
 {
     switch (callee->getKind()) {
       case ParseNodeKind::Name:
         if (!cone.emitNameCallee(callee->as<NameNode>().name())) {
-            return false;                                 // CALLEE THIS
+            //                [stack] CALLEE THIS
+            return false;
         }
         break;
       case ParseNodeKind::Dot: {
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyAccess* prop = &callee->as<PropertyAccess>();
         // TODO(khyperia): Implement private field access.
         bool isSuper = prop->isSuper();
 
         PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
         if (!poe.prepareForObj()) {
             return false;
         }
         if (isSuper) {
             UnaryNode* base = &prop->expression().as<UnaryNode>();
-            if (!emitGetThisForSuperBase(base)) {        // THIS
+            if (!emitGetThisForSuperBase(base)) {
+                //            [stack] THIS
                 return false;
             }
         } else {
-            if (!emitPropLHS(prop)) {                    // OBJ
-                return false;
-            }
-        }
-        if (!poe.emitGet(prop->key().atom())) {           // CALLEE THIS?
+            if (!emitPropLHS(prop)) {
+                //            [stack] OBJ
+                return false;
+            }
+        }
+        if (!poe.emitGet(prop->key().atom())) {
+            //                [stack] CALLEE THIS?
             return false;
         }
 
         break;
       }
       case ParseNodeKind::Elem: {
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyByValue* elem = &callee->as<PropertyByValue>();
         bool isSuper = elem->isSuper();
 
         ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
-        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
-            //                                            // THIS? THIS KEY
-            //                                            // [needsThis,Other]
-            //                                            // OBJ? OBJ KEY
-            return false;
-        }
-        if (!eoe.emitGet()) {                             // CALLEE? THIS
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {
+            //                [stack] # if Super
+            //                [stack] THIS? THIS KEY
+            //                [stack] # otherwise
+            //                [stack] OBJ? OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {
+            //                [stack] CALLEE? THIS
             return false;
         }
 
         break;
       }
       case ParseNodeKind::Function:
         if (!cone.prepareForFunctionCallee()) {
             return false;
         }
-        if (!emitTree(callee)) {                          // CALLEE
+        if (!emitTree(callee)) {
+            //                [stack] CALLEE
             return false;
         }
         break;
       case ParseNodeKind::SuperBase:
         MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCall));
         MOZ_ASSERT(parser->astGenerator().isSuperBase(callee));
-        if (!cone.emitSuperCallee()) {                    // CALLEE THIS
+        if (!cone.emitSuperCallee()) {
+            //                [stack] CALLEE THIS
             return false;
         }
         break;
       default:
         if (!cone.prepareForOtherCallee()) {
             return false;
         }
         if (!emitTree(callee)) {
             return false;
         }
         break;
     }
 
-    if (!cone.emitThis()) {                               // CALLEE THIS
+    if (!cone.emitThis()) {
+        //                    [stack] CALLEE THIS
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitPipeline(ListNode* node)
 {
     MOZ_ASSERT(node->count() >= 2);
 
-    if (!emitTree(node->head())) {                        // ARG
+    if (!emitTree(node->head())) {
+        //                    [stack] ARG
         return false;
     }
 
     ParseNode* callee = node->head()->pn_next;
     CallOrNewEmitter cone(this, JSOP_CALL,
                           CallOrNewEmitter::ArgumentsKind::Other,
                           ValueUsage::WantValue);
     do {
-        if (!emitCalleeAndThis(callee, node, cone)) {     // ARG CALLEE THIS
-            return false;
-        }
-        if (!emit2(JSOP_PICK, 2)) {                       // CALLEE THIS ARG
-            return false;
-        }
-        if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { // RVAL
+        if (!emitCalleeAndThis(callee, node, cone)) {
+            //                [stack] ARG CALLEE THIS
+            return false;
+        }
+        if (!emit2(JSOP_PICK, 2)) {
+            //                [stack] CALLEE THIS ARG
+            return false;
+        }
+        if (!cone.emitEnd(1, Some(node->pn_pos.begin))) {
+            //                [stack] RVAL
             return false;
         }
 
         cone.reset();
 
         checkTypeSet(JSOP_CALL);
     } while ((callee = callee->pn_next));
 
@@ -6998,35 +7385,40 @@ BytecodeEmitter::emitArguments(ListNode*
                                CallOrNewEmitter& cone)
 {
     uint32_t argc = argsList->count();
     if (argc >= ARGC_LIMIT) {
         reportError(argsList, isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS);
         return false;
     }
     if (!isSpread) {
-        if (!cone.prepareForNonSpreadArguments()) {       // CALLEE THIS
+        if (!cone.prepareForNonSpreadArguments()) {
+            //                [stack] CALLEE THIS
             return false;
         }
         for (ParseNode* arg : argsList->contents()) {
             if (!emitTree(arg)) {
+                //            [stack] CALLEE THIS ARG*
                 return false;
             }
         }
     } else {
         if (cone.wantSpreadOperand()) {
             UnaryNode* spreadNode = &argsList->head()->as<UnaryNode>();
-            if (!emitTree(spreadNode->kid())) {           // CALLEE THIS ARG0
-                return false;
-            }
-        }
-        if (!cone.emitSpreadArgumentsTest()) {            // CALLEE THIS
-            return false;
-        }
-        if (!emitArray(argsList->head(), argc)) {         // CALLEE THIS ARR
+            if (!emitTree(spreadNode->kid())) {
+                //            [stack] CALLEE THIS ARG0
+                return false;
+            }
+        }
+        if (!cone.emitSpreadArgumentsTest()) {
+            //                [stack] CALLEE THIS
+            return false;
+        }
+        if (!emitArray(argsList->head(), argc)) {
+            //                [stack] CALLEE THIS ARR
             return false;
         }
     }
 
     return true;
 }
 
 bool
@@ -7090,21 +7482,23 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
     JSOp op = callNode->getOp();
     uint32_t argc = argsList->count();
     CallOrNewEmitter cone(this, op,
                           isSpread && (argc == 1) &&
                           isRestParameter(argsList->head()->as<UnaryNode>().kid())
                           ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest
                           : CallOrNewEmitter::ArgumentsKind::Other,
                           valueUsage);
-    if (!emitCalleeAndThis(calleeNode, callNode, cone)) { // CALLEE THIS
+    if (!emitCalleeAndThis(calleeNode, callNode, cone)) {
+        //                    [stack] CALLEE THIS
         return false;
     }
     if (!emitArguments(argsList, isCall, isSpread, cone)) {
-        return false;                                     // CALLEE THIS ARGS...
+        //                    [stack] CALLEE THIS ARGS...
+        return false;
     }
 
     ParseNode* coordNode = callNode;
     if (op == JSOP_CALL || op == JSOP_SPREADCALL) {
         switch (calleeNode->getKind()) {
           case ParseNodeKind::Dot: {
 
             // Check if this member is a simple chain of simple chain of
@@ -7142,17 +7536,18 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
             //          ^  // column coord
             coordNode = argsList;
             break;
           default:
             break;
         }
     }
     if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
-        return false;                                     // RVAL
+        //                    [stack] RVAL
+        return false;
     }
 
     return true;
 }
 
 static const JSOp ParseNodeKindToJSOp[] = {
     // JSOP_NOP is for pipeline operator which does not emit its own JSOp
     // but has highest precedence in binary operators
@@ -7737,27 +8132,29 @@ BytecodeEmitter::emitArray(ParseNode* ar
                   "and DoSetElemFallback's handling of JSOP_INITELEM_ARRAY");
     MOZ_ASSERT(count >= nspread);
     MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT,
                "the parser must throw an error if the array exceeds maximum "
                "length");
 
     // For arrays with spread, this is a very pessimistic allocation, the
     // minimum possible final size.
-    if (!emitUint32Operand(JSOP_NEWARRAY, count - nspread)) {       // ARRAY
+    if (!emitUint32Operand(JSOP_NEWARRAY, count - nspread)) {
+        //                    [stack] ARRAY
         return false;
     }
 
     ParseNode* elem = arrayHead;
     uint32_t index;
     bool afterSpread = false;
     for (index = 0; elem; index++, elem = elem->pn_next) {
         if (!afterSpread && elem->isKind(ParseNodeKind::Spread)) {
             afterSpread = true;
-            if (!emitNumberOp(index)) {                             // ARRAY INDEX
+            if (!emitNumberOp(index)) {
+                //            [stack] ARRAY INDEX
                 return false;
             }
         }
         if (!updateSourceCoordNotes(elem->pn_pos.begin)) {
             return false;
         }
 
         bool allowSelfHostedIter = false;
@@ -7773,46 +8170,52 @@ BytecodeEmitter::emitArray(ParseNode* ar
                 if (emitterMode == BytecodeEmitter::SelfHosting &&
                     expr->isKind(ParseNodeKind::Call) &&
                     expr->as<BinaryNode>().left()->isName(cx->names().allowContentIter)) {
                     allowSelfHostedIter = true;
                 }
             } else {
                 expr = elem;
             }
-            if (!emitTree(expr)) {                                       // ARRAY INDEX? VALUE
+            if (!emitTree(expr)) {
+                //            [stack] ARRAY INDEX? VALUE
                 return false;
             }
         }
         if (elem->isKind(ParseNodeKind::Spread)) {
-            if (!emitIterator()) {                                       // ARRAY INDEX NEXT ITER
-                return false;
-            }
-            if (!emit2(JSOP_PICK, 3)) {                                  // INDEX NEXT ITER ARRAY
-                return false;
-            }
-            if (!emit2(JSOP_PICK, 3)) {                                  // NEXT ITER ARRAY INDEX
-                return false;
-            }
-            if (!emitSpread(allowSelfHostedIter)) {                      // ARRAY INDEX
+            if (!emitIterator()) {
+                //            [stack] ARRAY INDEX NEXT ITER
+                return false;
+            }
+            if (!emit2(JSOP_PICK, 3)) {
+                //            [stack] INDEX NEXT ITER ARRAY
+                return false;
+            }
+            if (!emit2(JSOP_PICK, 3)) {
+                //            [stack] NEXT ITER ARRAY INDEX
+                return false;
+            }
+            if (!emitSpread(allowSelfHostedIter)) {
+                //            [stack] ARRAY INDEX
                 return false;
             }
         } else if (afterSpread) {
             if (!emit1(JSOP_INITELEM_INC)) {
                 return false;
             }
         } else {
             if (!emitUint32Operand(JSOP_INITELEM_ARRAY, index)) {
                 return false;
             }
         }
     }
     MOZ_ASSERT(index == count);
     if (afterSpread) {
-        if (!emit1(JSOP_POP)) {                                          // ARRAY
+        if (!emit1(JSOP_POP)) {
+            //                [stack] ARRAY
             return false;
         }
     }
     return true;
 }
 
 static inline JSOp
 UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk)
@@ -8018,22 +8421,24 @@ BytecodeEmitter::emitFunctionFormalParam
         // First push the RHS if there is a default expression or if it is
         // rest.
 
         if (initializer) {
             // If we have an initializer, emit the initializer and assign it
             // to the argument slot. TDZ is taken care of afterwards.
             MOZ_ASSERT(hasParameterExprs);
 
-            if (!emitArgOp(JSOP_GETARG, argSlot)) {       // ARG
+            if (!emitArgOp(JSOP_GETARG, argSlot)) {
+                //            [stack] ARG
                 return false;
             }
 
             if (!emitDefault(initializer, bindingElement)) {
-                return false;                             // ARG/DEFAULT
+                //            [stack] ARG/DEFAULT
+                return false;
             }
         } else if (isRest) {
             if (!emit1(JSOP_REST)) {
                 return false;
             }
             checkTypeSet(JSOP_REST);
         }
 
@@ -8312,155 +8717,181 @@ BytecodeEmitter::emitClass(ClassNode* cl
 
     // This is kind of silly. In order to the get the home object defined on
     // the constructor, we have to make it second, but we want the prototype
     // on top for EmitPropertyList, because we expect static properties to be
     // rarer. The result is a few more swaps than we would like. Such is life.
     if (heritageExpression) {
         InternalIfEmitter ifThenElse(this);
 
-        if (!emitTree(heritageExpression)) {                    // ... HERITAGE
+        if (!emitTree(heritageExpression)) {
+            //                [stack] ... HERITAGE
             return false;
         }
 
         // Heritage must be null or a non-generator constructor
-        if (!emit1(JSOP_CHECKCLASSHERITAGE)) {                  // ... HERITAGE
+        if (!emit1(JSOP_CHECKCLASSHERITAGE)) {
+            //                [stack] ... HERITAGE
             return false;
         }
 
         // [IF] (heritage !== null)
-        if (!emit1(JSOP_DUP)) {                                 // ... HERITAGE HERITAGE
-            return false;
-        }
-        if (!emit1(JSOP_NULL)) {                                // ... HERITAGE HERITAGE NULL
-            return false;
-        }
-        if (!emit1(JSOP_STRICTNE)) {                            // ... HERITAGE NE
+        if (!emit1(JSOP_DUP)) {
+            //                [stack] ... HERITAGE HERITAGE
+            return false;
+        }
+        if (!emit1(JSOP_NULL)) {
+            //                [stack] ... HERITAGE HERITAGE NULL
+            return false;
+        }
+        if (!emit1(JSOP_STRICTNE)) {
+            //                [stack] ... HERITAGE NE
             return false;
         }
 
         // [THEN] funProto = heritage, objProto = heritage.prototype
         if (!ifThenElse.emitThenElse()) {
             return false;
         }
-        if (!emit1(JSOP_DUP)) {                                 // ... HERITAGE HERITAGE
-            return false;
-        }
-        if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) { // ... HERITAGE PROTO
+        if (!emit1(JSOP_DUP)) {
+            //                [stack] ... HERITAGE HERITAGE
+            return false;
+        }
+        if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) {
+            //                [stack] ... HERITAGE PROTO
             return false;
         }
 
         // [ELSE] funProto = %FunctionPrototype%, objProto = null
         if (!ifThenElse.emitElse()) {
             return false;
         }
-        if (!emit1(JSOP_POP)) {                                 // ...
-            return false;
-        }
-        if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) {      // ... PROTO
-            return false;
-        }
-        if (!emit1(JSOP_NULL)) {                                // ... PROTO NULL
+        if (!emit1(JSOP_POP)) {
+            //                [stack] ...
+            return false;
+        }
+        if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) {
+            //                [stack] ... PROTO
+            return false;
+        }
+        if (!emit1(JSOP_NULL)) {
+            //                [stack] ... PROTO NULL
             return false;
         }
 
         // [ENDIF]
         if (!ifThenElse.emitEnd()) {
             return false;
         }
 
-        if (!emit1(JSOP_OBJWITHPROTO)) {                        // ... HERITAGE HOMEOBJ
-            return false;
-        }
-        if (!emit1(JSOP_SWAP)) {                                // ... HOMEOBJ HERITAGE
+        if (!emit1(JSOP_OBJWITHPROTO)) {
+            //                [stack] ... HERITAGE HOMEOBJ
+            return false;
+        }
+        if (!emit1(JSOP_SWAP)) {
+            //                [stack] ... HOMEOBJ HERITAGE
             return false;
         }
     } else {
-        if (!emitNewInit()) {                                   // ... HOMEOBJ
+        if (!emitNewInit()) {
+            //                [stack] ... HOMEOBJ
             return false;
         }
     }
 
     // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE
     // is not used, an implicit value of %FunctionPrototype% is implied.
 
     if (constructor) {
-        if (!emitFunction(constructor, !!heritageExpression)) { // ... HOMEOBJ CONSTRUCTOR
+        if (!emitFunction(constructor, !!heritageExpression)) {
+            //                [stack] ... HOMEOBJ CONSTRUCTOR
             return false;
         }
         if (constructor->funbox()->needsHomeObject()) {
-            if (!emit2(JSOP_INITHOMEOBJECT, 0)) {               // ... HOMEOBJ CONSTRUCTOR
+            if (!emit2(JSOP_INITHOMEOBJECT, 0)) {
+                //            [stack] ... HOMEOBJ CONSTRUCTOR
                 return false;
             }
         }
     } else {
         // In the case of default class constructors, emit the start and end
         // offsets in the source buffer as source notes so that when we
         // actually make the constructor during execution, we can give it the
         // correct toString output.
         ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin);
         ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end);
         if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) {
             return false;
         }
 
         JSAtom *name = names ? names->innerBinding()->as<NameNode>().atom() : cx->names().empty;
         if (heritageExpression) {
-            if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) {   // ... HOMEOBJ CONSTRUCTOR
+            if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) {
+                //            [stack] ... HOMEOBJ CONSTRUCTOR
                 return false;
             }
         } else {
-            if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) {     // ... HOMEOBJ CONSTRUCTOR
-                return false;
-            }
-        }
-    }
-
-    if (!emit1(JSOP_SWAP)) {                                    // ... CONSTRUCTOR HOMEOBJ
-        return false;
-    }
-
-    if (!emit1(JSOP_DUP2)) {                                        // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ
-        return false;
-    }
-    if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) {  // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
-        return false;
-    }
-    if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) {  // ... CONSTRUCTOR HOMEOBJ
+            if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) {
+                //            [stack] ... HOMEOBJ CONSTRUCTOR
+                return false;
+            }
+        }
+    }
+
+    if (!emit1(JSOP_SWAP)) {
+        //                    [stack] ... CONSTRUCTOR HOMEOBJ
+        return false;
+    }
+
+    if (!emit1(JSOP_DUP2)) {
+        //                    [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ
+        return false;
+    }
+    if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) {
+        //                    [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
+        return false;
+    }
+    if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) {
+        //                    [stack] ... CONSTRUCTOR HOMEOBJ
         return false;
     }
 
     RootedPlainObject obj(cx);
-    if (!emitPropertyList(classMembers, &obj, ClassBody)) {     // ... CONSTRUCTOR HOMEOBJ
-        return false;
-    }
-
-    if (!emit1(JSOP_POP)) {                                     // ... CONSTRUCTOR
+    if (!emitPropertyList(classMembers, &obj, ClassBody)) {
+        //                    [stack] ... CONSTRUCTOR HOMEOBJ
+        return false;
+    }
+
+    if (!emit1(JSOP_POP)) {
+        //                    [stack] ... CONSTRUCTOR
         return false;
     }
 
     if (names) {
         NameNode* innerName = names->innerBinding();
-        if (!emitLexicalInitialization(innerName)) {            // ... CONSTRUCTOR
+        if (!emitLexicalInitialization(innerName)) {
+            //                [stack] ... CONSTRUCTOR
             return false;
         }
 
         // Pop the inner scope.
         if (!emitterScope->leave(this)) {
             return false;
         }
         emitterScope.reset();
 
         if (NameNode* outerName = names->outerBinding()) {
-            if (!emitLexicalInitialization(outerName)) {        // ... CONSTRUCTOR
+            if (!emitLexicalInitialization(outerName)) {
+                //            [stack] ... CONSTRUCTOR
                 return false;
             }
             // Only class statements make outer bindings, and they do not leave
             // themselves on the stack.
-            if (!emit1(JSOP_POP)) {                             // ...
+            if (!emit1(JSOP_POP)) {
+                //            [stack] ...
                 return false;
             }
         }
     }
 
     // The CONSTRUCTOR is left on stack if this is an expression.
 
     MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
@@ -8808,45 +9239,50 @@ BytecodeEmitter::emitTree(ParseNode* pn,
                           isSuper
                           ? PropOpEmitter::ObjKind::Super
                           : PropOpEmitter::ObjKind::Other);
         if (!poe.prepareForObj()) {
             return false;
         }
         if (isSuper) {
             UnaryNode* base = &prop->expression().as<UnaryNode>();
-            if (!emitGetThisForSuperBase(base)) {         // THIS
+            if (!emitGetThisForSuperBase(base)) {
+                //            [stack] THIS
                 return false;
             }
         } else {
-            if (!emitPropLHS(prop)) {                     // OBJ
-                return false;
-            }
-        }
-        if (!poe.emitGet(prop->key().atom())) {           // PROP
+            if (!emitPropLHS(prop)) {
+                //            [stack] OBJ
+                return false;
+            }
+        }
+        if (!poe.emitGet(prop->key().atom())) {
+            //                [stack] PROP
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &pn->as<PropertyByValue>();
         bool isSuper = elem->isSuper();
         ElemOpEmitter eoe(this,
                           ElemOpEmitter::Kind::Get,
                           isSuper
                           ? ElemOpEmitter::ObjKind::Super
                           : ElemOpEmitter::ObjKind::Other);
-        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
-            //                                            // THIS KEY
-            //                                            // [Other]
-            //                                            // OBJ KEY
-            return false;
-        }
-        if (!eoe.emitGet()) {                             // ELEM
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {
+            //                [stack] # if Super
+            //                [stack] THIS KEY
+            //                [stack] # otherwise
+            //                [stack] OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {
+            //                [stack] ELEM
             return false;
         }
 
         break;
       }
 
       case ParseNodeKind::New:
       case ParseNodeKind::TaggedTemplate:
--- a/js/src/frontend/CForEmitter.cpp
+++ b/js/src/frontend/CForEmitter.cpp
@@ -70,34 +70,37 @@ CForEmitter::emitBody(Cond cond, const M
             }
         }
     }
 
     if (!bce_->newSrcNote(SRC_FOR, &noteIndex_)) {
         return false;
     }
     if (!bce_->emit1(JSOP_NOP)) {
+        //                    [stack]
         return false;
     }
 
     biasedTop_ = bce_->offset();
 
     if (cond_ == Cond::Present) {
         // Goto the loop condition, which branches back to iterate.
         if (!loopInfo_->emitEntryJump(bce_)) {
             return false;
         }
     }
 
     if (!loopInfo_->emitLoopHead(bce_, bodyPos)) {
+        //                    [stack]
         return false;
     }
 
     if (cond_ == Cond::Missing) {
         if (!loopInfo_->emitLoopEntry(bce_, bodyPos)) {
+            //                [stack]
             return false;
         }
     }
 
     tdzCache_.emplace(bce_);
 
 #ifdef DEBUG
     state_ = State::Body;
@@ -152,17 +155,20 @@ CForEmitter::emitUpdate(Update update, c
 bool
 CForEmitter::emitCond(const Maybe<uint32_t>& forPos,
                       const Maybe<uint32_t>& condPos,
                       const Maybe<uint32_t>& endPos)
 {
     MOZ_ASSERT(state_ == State::Update);
 
     if (update_ == Update::Present) {
-        if (!bce_->emit1(JSOP_POP)) {                 //
+        //                    [stack] UPDATE
+
+        if (!bce_->emit1(JSOP_POP)) {
+            //                [stack]
             return false;
         }
 
         // Restore the absolute line number for source note readers.
         if (endPos) {
             uint32_t lineNum =
                 bce_->parser->errorReporter().lineAt(*endPos);
             if (bce_->currentLine() != lineNum) {
@@ -178,16 +184,17 @@ CForEmitter::emitCond(const Maybe<uint32
     if (update_ == Update::Present) {
         tdzCache_.reset();
     }
 
     condOffset_ = bce_->offset();
 
     if (cond_ == Cond::Present) {
         if (!loopInfo_->emitLoopEntry(bce_, condPos)) {
+            //                [stack]
             return false;
         }
     } else if (update_ == Update::Missing) {
         // If there is no condition clause and no update clause, mark
         // the loop-ending "goto" with the location of the "for".
         // This ensures that the debugger will stop on each loop
         // iteration.
         if (forPos) {
@@ -216,17 +223,18 @@ CForEmitter::emitEnd()
     if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::For::UpdateOffset,
                                 loopInfo_->continueTargetOffset() - biasedTop_))
     {
         return false;
     }
 
     // If no loop condition, just emit a loop-closing jump.
     if (!loopInfo_->emitLoopEnd(bce_, cond_ == Cond::Present ? JSOP_IFNE : JSOP_GOTO)) {
-        return false;                                 //
+        //                    [stack]
+        return false;
     }
 
     // The third note offset helps us find the loop-closing jump.
     if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::For::BackJumpOffset,
                                 loopInfo_->loopEndOffset() - biasedTop_))
 
     {
         return false;
@@ -234,16 +242,17 @@ CForEmitter::emitEnd()
 
     if (!bce_->addTryNote(JSTRY_LOOP, bce_->stackDepth, loopInfo_->headOffset(),
                           loopInfo_->breakTargetOffset()))
     {
         return false;
     }
 
     if (!loopInfo_->patchBreaksAndContinues(bce_)) {
+        //                    [stack]
         return false;
     }
 
     loopInfo_.reset();
 
 #ifdef DEBUG
     state_ = State::End;
 #endif
--- a/js/src/frontend/CallOrNewEmitter.cpp
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -47,17 +47,18 @@ bool
 CallOrNewEmitter::emitNameCallee(JSAtom* name)
 {
     MOZ_ASSERT(state_ == State::Start);
 
     NameOpEmitter noe(bce_, name,
                       isCall()
                       ? NameOpEmitter::Kind::Call
                       : NameOpEmitter::Kind::Get);
-    if (!noe.emitGet()) {                             // CALLEE THIS
+    if (!noe.emitGet()) {
+        //                    [stack] CALLEE THIS
         return false;
     }
 
     state_ = State::NameCallee;
     return true;
 }
 
 MOZ_MUST_USE PropOpEmitter&
@@ -115,20 +116,22 @@ CallOrNewEmitter::prepareForFunctionCall
     return true;
 }
 
 bool
 CallOrNewEmitter::emitSuperCallee()
 {
     MOZ_ASSERT(state_ == State::Start);
 
-    if (!bce_->emit1(JSOP_SUPERFUN)) {                // CALLEE
+    if (!bce_->emit1(JSOP_SUPERFUN)) {
+        //                    [stack] CALLEE
         return false;
     }
-    if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) {         // CALLEE THIS
+    if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) {
+        //                    [stack] CALLEE THIS
         return false;
     }
 
     state_ = State::SuperCallee;
     return true;
 }
 
 bool
@@ -177,21 +180,23 @@ CallOrNewEmitter::emitThis()
         break;
       case State::OtherCallee:
         needsThis = true;
         break;
       default:;
     }
     if (needsThis) {
         if (isNew() || isSuperCall()) {
-            if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
+            if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) {
+                //            [stack] CALLEE THIS
                 return false;
             }
         } else {
-            if (!bce_->emit1(JSOP_UNDEFINED)) {       // CALLEE THIS
+            if (!bce_->emit1(JSOP_UNDEFINED)) {
+                //            [stack] CALLEE THIS
                 return false;
             }
         }
     }
 
     state_ = State::This;
     return true;
 }
@@ -241,72 +246,82 @@ CallOrNewEmitter::emitSpreadArgumentsTes
         //     g(...args);
         //   }
         //
         // If the spread operand is a rest parameter and it's optimizable
         // array, skip spread operation and pass it directly to spread call
         // operation.  See the comment in OptimizeSpreadCall in
         // Interpreter.cpp for the optimizable conditons.
 
+        //                    [stack] CALLEE THIS ARG0
+
         ifNotOptimizable_.emplace(bce_);
-        //                                            // CALLEE THIS ARG0
-        if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) { // CALLEE THIS ARG0 OPTIMIZED
+        if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) {
+            //                [stack] CALLEE THIS ARG0 OPTIMIZED
             return false;
         }
-        if (!bce_->emit1(JSOP_NOT)) {                 // CALLEE THIS ARG0 !OPTIMIZED
+        if (!bce_->emit1(JSOP_NOT)) {
+            //                [stack] CALLEE THIS ARG0 !OPTIMIZED
             return false;
         }
-        if (!ifNotOptimizable_->emitThen()) {         // CALLEE THIS ARG0
+        if (!ifNotOptimizable_->emitThen()) {
+            //                [stack] CALLEE THIS ARG0
             return false;
         }
-        if (!bce_->emit1(JSOP_POP)) {                 // CALLEE THIS
+        if (!bce_->emit1(JSOP_POP)) {
+            //                [stack] CALLEE THIS
             return false;
         }
     }
 
     state_ = State::Arguments;
     return true;
 }
 
 bool
 CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe<uint32_t>& beginPos)
 {
     MOZ_ASSERT(state_ == State::Arguments);
 
     if (isSingleSpreadRest()) {
-        if (!ifNotOptimizable_->emitEnd()) {          // CALLEE THIS ARR
+        if (!ifNotOptimizable_->emitEnd()) {
+            //                [stack] CALLEE THIS ARR
             return false;
         }
 
         ifNotOptimizable_.reset();
     }
     if (isNew() || isSuperCall()) {
         if (isSuperCall()) {
-            if (!bce_->emit1(JSOP_NEWTARGET)) {       // CALLEE THIS ARG.. NEW.TARGET
+            if (!bce_->emit1(JSOP_NEWTARGET)) {
+                //            [stack] CALLEE THIS ARG.. NEW.TARGET
                 return false;
             }
         } else {
             // Repush the callee as new.target
             uint32_t effectiveArgc = isSpread() ? 1 : argc;
             if (!bce_->emitDupAt(effectiveArgc + 1)) {
-                return false;                         // CALLEE THIS ARR CALLEE
+                //            [stack] CALLEE THIS ARR CALLEE
+                return false;
             }
         }
     }
     if (!isSpread()) {
-        if (!bce_->emitCall(op_, argc, beginPos)) {   // RVAL
+        if (!bce_->emitCall(op_, argc, beginPos)) {
+            //                [stack] RVAL
             return false;
         }
     } else {
         if (beginPos) {
             if (!bce_->updateSourceCoordNotes(*beginPos)) {
                 return false;
             }
         }
-        if (!bce_->emit1(op_)) {                      // RVAL
+        if (!bce_->emit1(op_)) {
+            //                [stack] RVAL
             return false;
         }
     }
     bce_->checkTypeSet(op_);
 
     if (isEval() && beginPos) {
         uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos);
         if (!bce_->emitUint32Operand(JSOP_LINENO, lineNum)) {
--- a/js/src/frontend/ElemOpEmitter.cpp
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -31,95 +31,103 @@ ElemOpEmitter::prepareForObj()
 }
 
 bool
 ElemOpEmitter::prepareForKey()
 {
     MOZ_ASSERT(state_ == State::Obj);
 
     if (!isSuper() && isIncDec()) {
-        if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) {   // OBJ
+        if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) {
+            //                [stack] OBJ
             return false;
         }
     }
     if (isCall()) {
-        if (!bce_->emit1(JSOP_DUP)) {                 // [Super]
-            //                                        // THIS THIS
-            //                                        // [Other]
-            //                                        // OBJ OBJ
+        if (!bce_->emit1(JSOP_DUP)) {
+            //                [stack] # if Super
+            //                [stack] THIS THIS
+            //                [stack] # otherwise
+            //                [stack] OBJ OBJ
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::Key;
 #endif
     return true;
 }
 
 bool
 ElemOpEmitter::emitGet()
 {
     MOZ_ASSERT(state_ == State::Key);
 
     if (isIncDec() || isCompoundAssignment()) {
-        if (!bce_->emit1(JSOP_TOID)) {                // [Super]
-            //                                        // THIS KEY
-            //                                        // [Other]
-            //                                        // OBJ KEY
+        if (!bce_->emit1(JSOP_TOID)) {
+            //                [stack] # if Super
+            //                [stack] THIS KEY
+            //                [stack] # otherwise
+            //                [stack] OBJ KEY
             return false;
         }
     }
     if (isSuper()) {
-        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS? THIS KEY SUPERBASE
+        if (!bce_->emit1(JSOP_SUPERBASE)) {
+            //                [stack] THIS? THIS KEY SUPERBASE
             return false;
         }
     }
     if (isIncDec() || isCompoundAssignment()) {
         if (isSuper()) {
             // There's no such thing as JSOP_DUP3, so we have to be creative.
             // Note that pushing things again is no fewer JSOps.
-            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS
+            if (!bce_->emitDupAt(2)) {
+                //            [stack] THIS KEY SUPERBASE THIS
                 return false;
             }
-            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS KEY
+            if (!bce_->emitDupAt(2)) {
+                //            [stack] THIS KEY SUPERBASE THIS KEY
                 return false;
             }
-            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS KEY SUPERBASE
+            if (!bce_->emitDupAt(2)) {
+                //            [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE
                 return false;
             }
         } else {
-            if (!bce_->emit1(JSOP_DUP2)) {            // OBJ KEY OBJ KEY
+            if (!bce_->emit1(JSOP_DUP2)) {
+                //            [stack] OBJ KEY OBJ KEY
                 return false;
             }
         }
     }
 
     JSOp op;
     if (isSuper()) {
         op = JSOP_GETELEM_SUPER;
     } else if (isCall()) {
         op = JSOP_CALLELEM;
     } else {
         op = JSOP_GETELEM;
     }
-    if (!bce_->emitElemOpBase(op)) {                  // [Get]
-        //                                            // ELEM
-        //                                            // [Call]
-        //                                            // THIS ELEM
-        //                                            // [Inc/Dec/Assignment,
-        //                                            //  Super]
-        //                                            // THIS KEY SUPERBASE ELEM
-        //                                            // [Inc/Dec/Assignment,
-        //                                            //  Other]
-        //                                            // OBJ KEY ELEM
+    if (!bce_->emitElemOpBase(op)) {
+        //                    [stack] # if Get
+        //                    [stack] ELEM
+        //                    [stack] # if Call
+        //                    [stack] THIS ELEM
+        //                    [stack] # if Inc/Dec/Assignment, with Super
+        //                    [stack] THIS KEY SUPERBASE ELEM
+        //                    [stack] # if Inc/Dec/Assignment, other
+        //                    [stack] OBJ KEY ELEM
         return false;
     }
     if (isCall()) {
-        if (!bce_->emit1(JSOP_SWAP)) {                // ELEM THIS
+        if (!bce_->emit1(JSOP_SWAP)) {
+            //                [stack] ELEM THIS
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::Get;
 #endif
     return true;
@@ -130,17 +138,18 @@ ElemOpEmitter::prepareForRhs()
 {
     MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
     MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key);
     MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
 
     if (isSimpleAssignment()) {
         // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
         if (isSuper()) {
-            if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS KEY SUPERBASE
+            if (!bce_->emit1(JSOP_SUPERBASE)) {
+                //            [stack] THIS KEY SUPERBASE
                 return false;
             }
         }
     }
 
 #ifdef DEBUG
     state_ = State::Rhs;
 #endif
@@ -161,36 +170,41 @@ ElemOpEmitter::skipObjAndKeyAndRhs()
 
 bool
 ElemOpEmitter::emitDelete()
 {
     MOZ_ASSERT(state_ == State::Key);
     MOZ_ASSERT(isDelete());
 
     if (isSuper()) {
-        if (!bce_->emit1(JSOP_TOID)) {                // THIS KEY
+        if (!bce_->emit1(JSOP_TOID)) {
+            //                [stack] THIS KEY
             return false;
         }
-        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS KEY SUPERBASE
+        if (!bce_->emit1(JSOP_SUPERBASE)) {
+            //                [stack] THIS KEY SUPERBASE
             return false;
         }
 
         // Unconditionally throw when attempting to delete a super-reference.
         if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
-            return false;                             // THIS KEY SUPERBASE
+            //                [stack] THIS KEY SUPERBASE
+            return false;
         }
 
         // Another wrinkle: Balance the stack from the emitter's point of view.
         // Execution will not reach here, as the last bytecode threw.
-        if (!bce_->emitPopN(2)) {                     // THIS
+        if (!bce_->emitPopN(2)) {
+            //                [stack] THIS
             return false;
         }
     } else {
         JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
-        if (!bce_->emitElemOpBase(op)){              // SUCCEEDED
+        if (!bce_->emitElemOpBase(op)) {
+            // SUCCEEDED
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::Delete;
 #endif
     return true;
@@ -200,88 +214,107 @@ bool
 ElemOpEmitter::emitAssignment()
 {
     MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
     MOZ_ASSERT(state_ == State::Rhs);
 
     JSOp setOp = isSuper()
                  ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
                  : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
-    if (!bce_->emitElemOpBase(setOp)) {               // ELEM
+    if (!bce_->emitElemOpBase(setOp)) {
+        //                    [stack] ELEM
         return false;
     }
 
 #ifdef DEBUG
     state_ = State::Assignment;
 #endif
     return true;
 }
 
 bool
 ElemOpEmitter::emitIncDec()
 {
     MOZ_ASSERT(state_ == State::Key);
     MOZ_ASSERT(isIncDec());
 
-    if (!emitGet()) {                                 // ... ELEM
+    if (!emitGet()) {
+        //                    [stack] ... ELEM
         return false;
     }
 
     MOZ_ASSERT(state_ == State::Get);
 
     JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
-    if (!bce_->emit1(JSOP_POS)) {                     // ... N
+    if (!bce_->emit1(JSOP_POS)) {
+        //                    [stack] ... N
         return false;
     }
     if (isPostIncDec()) {
-        if (!bce_->emit1(JSOP_DUP)) {                 // ... N? N
+        if (!bce_->emit1(JSOP_DUP)) {
+            //                [stack] ... N? N
             return false;
         }
     }
-    if (!bce_->emit1(JSOP_ONE)) {                     // ... N? N 1
+    if (!bce_->emit1(JSOP_ONE)) {
+        //                    [stack] ... N? N 1
         return false;
     }
-    if (!bce_->emit1(binOp)) {                        // ... N? N+1
+    if (!bce_->emit1(binOp)) {
+        //                    [stack] ... N? N+1
         return false;
     }
     if (isPostIncDec()) {
-        if (isSuper()) {                              // THIS KEY OBJ N N+1
-            if (!bce_->emit2(JSOP_PICK, 4)) {         // KEY SUPERBASE N N+1 THIS
+        if (isSuper()) {
+            //                [stack] THIS KEY OBJ N N+1
+
+            if (!bce_->emit2(JSOP_PICK, 4)) {
+                //            [stack] KEY SUPERBASE N N+1 THIS
                 return false;
             }
-            if (!bce_->emit2(JSOP_PICK, 4)) {         // SUPERBASE N N+1 THIS KEY
+            if (!bce_->emit2(JSOP_PICK, 4)) {
+                //            [stack] SUPERBASE N N+1 THIS KEY
                 return false;
             }
-            if (!bce_->emit2(JSOP_PICK, 4)) {         // N N+1 THIS KEY SUPERBASE
+            if (!bce_->emit2(JSOP_PICK, 4)) {
+                //            [stack] N N+1 THIS KEY SUPERBASE
                 return false;
             }
-            if (!bce_->emit2(JSOP_PICK, 3)) {         // N THIS KEY SUPERBASE N+1
+            if (!bce_->emit2(JSOP_PICK, 3)) {
+                //            [stack] N THIS KEY SUPERBASE N+1
                 return false;
             }
-        } else {                                      // OBJ KEY N N+1
-            if (!bce_->emit2(JSOP_PICK, 3)) {         // KEY N N+1 OBJ
+        } else {
+            //                [stack] OBJ KEY N N+1
+
+            if (!bce_->emit2(JSOP_PICK, 3)) {
+                //            [stack] KEY N N+1 OBJ
                 return false;
             }
-            if (!bce_->emit2(JSOP_PICK, 3)) {         // N N+1 OBJ KEY
+            if (!bce_->emit2(JSOP_PICK, 3)) {
+                //            [stack] N N+1 OBJ KEY
                 return false;
             }
-            if (!bce_->emit2(JSOP_PICK, 2)) {         // N OBJ KEY N+1
+            if (!bce_->emit2(JSOP_PICK, 2)) {
+                //            [stack] N OBJ KEY N+1
                 return false;
             }
         }
     }
 
     JSOp setOp = isSuper()
                  ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
                  : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
-    if (!bce_->emitElemOpBase(setOp)) {               // N? N+1
+    if (!bce_->emitElemOpBase(setOp)) {
+        //                    [stack] N? N+1
         return false;
     }
     if (isPostIncDec()) {
-        if (!bce_->emit1(JSOP_POP)) {                 // N
+        if (!bce_->emit1(JSOP_POP)) {
+            //                [stack] N
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::IncDec;
 #endif
     return true;
--- a/js/src/frontend/ExpressionStatementEmitter.cpp
+++ b/js/src/frontend/ExpressionStatementEmitter.cpp
@@ -39,18 +39,24 @@ ExpressionStatementEmitter::prepareForEx
 }
 
 bool
 ExpressionStatementEmitter::emitEnd()
 {
     MOZ_ASSERT(state_ == State::Expr);
     MOZ_ASSERT(bce_->stackDepth == depth_ + 1);
 
+    //                        [stack] VAL
+
     JSOp op = valueUsage_ == ValueUsage::WantValue ? JSOP_SETRVAL : JSOP_POP;
     if (!bce_->emit1(op)) {
+        //                    [stack] # if WantValue
+        //                    [stack] VAL
+        //                    [stack] # otherwise
+        //                    [stack]
         return false;
     }
 
 #ifdef DEBUG
     state_ = State::End;
 #endif
     return true;
 }
--- a/js/src/frontend/ForInEmitter.cpp
+++ b/js/src/frontend/ForInEmitter.cpp
@@ -36,40 +36,44 @@ ForInEmitter::emitIterated()
 }
 
 bool
 ForInEmitter::emitInitialize()
 {
     MOZ_ASSERT(state_ == State::Iterated);
     tdzCacheForIteratedValue_.reset();
 
-    if (!bce_->emit1(JSOP_ITER)) {                    // ITER
+    if (!bce_->emit1(JSOP_ITER)) {
+        //                    [stack] ITER
         return false;
     }
 
     // For-in loops have both the iterator and the value on the stack. Push
     // undefined to balance the stack.
-    if (!bce_->emit1(JSOP_UNDEFINED)) {               // ITER ITERVAL
+    if (!bce_->emit1(JSOP_UNDEFINED)) {
+        //                    [stack] ITER ITERVAL
         return false;
     }
 
     loopInfo_.emplace(bce_, StatementKind::ForInLoop);
 
     // Annotate so IonMonkey can find the loop-closing jump.
     if (!bce_->newSrcNote(SRC_FOR_IN, &noteIndex_)) {
         return false;
     }
 
     // Jump down to the loop condition to minimize overhead (assuming at
     // least one iteration, just like the other loop forms).
-    if (!loopInfo_->emitEntryJump(bce_)) {            // ITER ITERVAL
+    if (!loopInfo_->emitEntryJump(bce_)) {
+        //                    [stack] ITER ITERVAL
         return false;
     }
 
-    if (!loopInfo_->emitLoopHead(bce_, Nothing())) {  // ITER ITERVAL
+    if (!loopInfo_->emitLoopHead(bce_, Nothing())) {
+        //                    [stack] ITER ITERVAL
         return false;
     }
 
     // If the loop had an escaping lexical declaration, reset the declaration's
     // bindings to uninitialized to implement TDZ semantics.
     if (headLexicalEmitterScope_) {
         // The environment chain only includes an environment for the
         // for-in loop head *if* a scope binding is captured, thereby
@@ -77,32 +81,34 @@ ForInEmitter::emitInitialize()
         // for the head, it must be the innermost one. If that scope has
         // closed-over bindings inducing an environment, recreate the
         // current environment.
         MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope());
         MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_)->kind() == ScopeKind::Lexical);
 
         if (headLexicalEmitterScope_->hasEnvironment()) {
             if (!bce_->emit1(JSOP_RECREATELEXICALENV)) {
-                return false;                         // ITER ITERVAL
+                //            [stack] ITER ITERVAL
+                return false;
             }
         }
 
         // For uncaptured bindings, put them back in TDZ.
         if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) {
             return false;
         }
     }
 
 #ifdef DEBUG
     loopDepth_ = bce_->stackDepth;
 #endif
     MOZ_ASSERT(loopDepth_ >= 2);
 
-    if (!bce_->emit1(JSOP_ITERNEXT)) {                // ITER ITERVAL
+    if (!bce_->emit1(JSOP_ITERNEXT)) {
+        //                    [stack] ITER ITERVAL
         return false;
     }
 
 #ifdef DEBUG
     state_ = State::Initialize;
 #endif
     return true;
 }
@@ -130,56 +136,63 @@ ForInEmitter::emitEnd(const Maybe<uint32
 
     if (forPos) {
         // Make sure this code is attributed to the "for".
         if (!bce_->updateSourceCoordNotes(*forPos)) {
             return false;
         }
     }
 
-    if (!loopInfo_->emitLoopEntry(bce_, Nothing())) { // ITER ITERVAL
+    if (!loopInfo_->emitLoopEntry(bce_, Nothing())) {
+        //                    [stack] ITER ITERVAL
         return false;
     }
-    if (!bce_->emit1(JSOP_POP)) {                     // ITER
+    if (!bce_->emit1(JSOP_POP)) {
+        //                    [stack] ITER
         return false;
     }
-    if (!bce_->emit1(JSOP_MOREITER)) {                // ITER NEXTITERVAL?
+    if (!bce_->emit1(JSOP_MOREITER)) {
+        //                    [stack] ITER NEXTITERVAL?
         return false;
     }
-    if (!bce_->emit1(JSOP_ISNOITER)) {                // ITER NEXTITERVAL? ISNOITER
+    if (!bce_->emit1(JSOP_ISNOITER)) {
+        //                    [stack] ITER NEXTITERVAL? ISNOITER
         return false;
     }
 
-    if (!loopInfo_->emitLoopEnd(bce_, JSOP_IFEQ)) {   // ITER NEXTITERVAL
+    if (!loopInfo_->emitLoopEnd(bce_, JSOP_IFEQ)) {
+        //                    [stack] ITER NEXTITERVAL
         return false;
     }
 
     // Set the srcnote offset so we can find the closing jump.
     if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::ForIn::BackJumpOffset,
                                 loopInfo_->loopEndOffsetFromEntryJump()))
     {
         return false;
     }
 
     if (!loopInfo_->patchBreaksAndContinues(bce_)) {
         return false;
     }
 
     // Pop the enumeration value.
-    if (!bce_->emit1(JSOP_POP)) {                     // ITER
+    if (!bce_->emit1(JSOP_POP)) {
+        //                    [stack] ITER
         return false;
     }
 
     if (!bce_->addTryNote(JSTRY_FOR_IN, bce_->stackDepth, loopInfo_->headOffset(),
                           bce_->offset()))
     {
         return false;
     }
 
-    if (!bce_->emit1(JSOP_ENDITER)) {                 //
+    if (!bce_->emit1(JSOP_ENDITER)) {
+        //                    [stack]
         return false;
     }
 
     loopInfo_.reset();
 
 #ifdef DEBUG
     state_ = State::End;
 #endif
--- a/js/src/frontend/ForOfEmitter.cpp
+++ b/js/src/frontend/ForOfEmitter.cpp
@@ -46,63 +46,69 @@ ForOfEmitter::emitIterated()
 bool
 ForOfEmitter::emitInitialize(const Maybe<uint32_t>& forPos)
 {
     MOZ_ASSERT(state_ == State::Iterated);
 
     tdzCacheForIteratedValue_.reset();
 
     if (iterKind_ == IteratorKind::Async) {
-        if (!bce_->emitAsyncIterator()) {             // NEXT ITER
+        if (!bce_->emitAsyncIterator()) {
+            //                [stack] NEXT ITER
             return false;
         }
     } else {
-        if (!bce_->emitIterator()) {                  // NEXT ITER
+        if (!bce_->emitIterator()) {
+            //                [stack] NEXT ITER
             return false;
         }
     }
 
     int32_t iterDepth = bce_->stackDepth;
 
     // For-of loops have the iterator next method, the iterator itself, and
     // the result.value on the stack.
     // Push an undefined to balance the stack.
-    if (!bce_->emit1(JSOP_UNDEFINED)) {               // NEXT ITER UNDEF
+    if (!bce_->emit1(JSOP_UNDEFINED)) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
     loopInfo_.emplace(bce_, iterDepth, allowSelfHostedIter_, iterKind_);
 
     // Annotate so IonMonkey can find the loop-closing jump.
     if (!bce_->newSrcNote(SRC_FOR_OF, &noteIndex_)) {
         return false;
     }
 
-    if (!loopInfo_->emitEntryJump(bce_)) {            // NEXT ITER UNDEF
+    if (!loopInfo_->emitEntryJump(bce_)) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
-    if (!loopInfo_->emitLoopHead(bce_, Nothing())) {  // NEXT ITER UNDEF
+    if (!loopInfo_->emitLoopHead(bce_, Nothing())) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
     // If the loop had an escaping lexical declaration, replace the current
     // environment with an dead zoned one to implement TDZ semantics.
     if (headLexicalEmitterScope_) {
         // The environment chain only includes an environment for the for-of
         // loop head *if* a scope binding is captured, thereby requiring
         // recreation each iteration. If a lexical scope exists for the head,
         // it must be the innermost one. If that scope has closed-over
         // bindings inducing an environment, recreate the current environment.
         MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope());
         MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_)->kind() == ScopeKind::Lexical);
 
         if (headLexicalEmitterScope_->hasEnvironment()) {
             if (!bce_->emit1(JSOP_RECREATELEXICALENV)) {
-                return false;                         // NEXT ITER UNDEF
+                //            [stack] NEXT ITER UNDEF
+                return false;
             }
         }
 
         // For uncaptured bindings, put them back in TDZ.
         if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) {
             return false;
         }
     }
@@ -113,64 +119,75 @@ ForOfEmitter::emitInitialize(const Maybe
 
     // Make sure this code is attributed to the "for".
     if (forPos) {
         if (!bce_->updateSourceCoordNotes(*forPos)) {
             return false;
         }
     }
 
-    if (!bce_->emit1(JSOP_POP)) {                     // NEXT ITER
+    if (!bce_->emit1(JSOP_POP)) {
+        //                    [stack] NEXT ITER
         return false;
     }
-    if (!bce_->emit1(JSOP_DUP2)) {                    // NEXT ITER NEXT ITER
+    if (!bce_->emit1(JSOP_DUP2)) {
+        //                    [stack] NEXT ITER NEXT ITER
         return false;
     }
 
     if (!bce_->emitIteratorNext(forPos, iterKind_, allowSelfHostedIter_)) {
-        return false;                                 // NEXT ITER RESULT
+        //                    [stack] NEXT ITER RESULT
+        return false;
     }
 
-    if (!bce_->emit1(JSOP_DUP)) {                     // NEXT ITER RESULT RESULT
+    if (!bce_->emit1(JSOP_DUP)) {
+        //                    [stack] NEXT ITER RESULT RESULT
         return false;
     }
     if (!bce_->emitAtomOp(bce_->cx->names().done, JSOP_GETPROP)) {
-        return false;                                 // NEXT ITER RESULT DONE
+        //                    [stack] NEXT ITER RESULT DONE
+        return false;
     }
 
     InternalIfEmitter ifDone(bce_);
 
-    if (!ifDone.emitThen()) {                         // NEXT ITER RESULT
+    if (!ifDone.emitThen()) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
 
     // Remove RESULT from the stack to release it.
-    if (!bce_->emit1(JSOP_POP)) {                     // NEXT ITER
+    if (!bce_->emit1(JSOP_POP)) {
+        //                    [stack] NEXT ITER
         return false;
     }
-    if (!bce_->emit1(JSOP_UNDEFINED)) {               // NEXT ITER UNDEF
+    if (!bce_->emit1(JSOP_UNDEFINED)) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
     // If the iteration is done, leave loop here, instead of the branch at
     // the end of the loop.
-    if (!loopInfo_->emitSpecialBreakForDone(bce_)) {  // NEXT ITER UNDEF
+    if (!loopInfo_->emitSpecialBreakForDone(bce_)) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
-    if (!ifDone.emitEnd()) {                          // NEXT ITER RESULT
+    if (!ifDone.emitEnd()) {
+        //                    [stack] NEXT ITER RESULT
         return false;
     }
 
     // Emit code to assign result.value to the iteration variable.
     //
     // Note that ES 13.7.5.13, step 5.c says getting result.value does not
     // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP.
     if (!bce_->emitAtomOp(bce_->cx->names().value, JSOP_GETPROP)) {
-        return false;                                 // NEXT ITER VALUE
+        //                    [stack] NEXT ITER VALUE
+        return false;
     }
 
     if (!loopInfo_->emitBeginCodeNeedingIteratorClose(bce_)) {
         return false;
     }
 
 #ifdef DEBUG
     state_ = State::Initialize;
@@ -183,20 +200,22 @@ ForOfEmitter::emitBody()
 {
     MOZ_ASSERT(state_ == State::Initialize);
 
     MOZ_ASSERT(bce_->stackDepth == loopDepth_,
                "the stack must be balanced around the initializing "
                "operation");
 
     // Remove VALUE from the stack to release it.
-    if (!bce_->emit1(JSOP_POP)) {                     // NEXT ITER
+    if (!bce_->emit1(JSOP_POP)) {
+        //                    [stack] NEXT ITER
         return false;
     }
-    if (!bce_->emit1(JSOP_UNDEFINED)) {               // NEXT ITER UNDEF
+    if (!bce_->emit1(JSOP_UNDEFINED)) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
 #ifdef DEBUG
     state_ = State::Body;
 #endif
     return true;
 }
@@ -218,20 +237,22 @@ ForOfEmitter::emitEnd(const Maybe<uint32
     // We use the iterated value's position to attribute JSOP_LOOPENTRY,
     // which corresponds to the iteration protocol.
     // This is a bit misleading for 2nd and later iterations and might need
     // some fix (bug 1482003).
     if (!loopInfo_->emitLoopEntry(bce_, iteratedPos)) {
         return false;
     }
 
-    if (!bce_->emit1(JSOP_FALSE)) {                   // NEXT ITER UNDEF FALSE
+    if (!bce_->emit1(JSOP_FALSE)) {
+        //                    [stack] NEXT ITER UNDEF FALSE
         return false;
     }
-    if (!loopInfo_->emitLoopEnd(bce_, JSOP_IFEQ)) {   // NEXT ITER UNDEF
+    if (!loopInfo_->emitLoopEnd(bce_, JSOP_IFEQ)) {
+        //                    [stack] NEXT ITER UNDEF
         return false;
     }
 
     MOZ_ASSERT(bce_->stackDepth == loopDepth_);
 
     // Let Ion know where the closing jump of this loop is.
     if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::ForOf::BackJumpOffset,
                                 loopInfo_->loopEndOffsetFromEntryJump()))
@@ -244,17 +265,18 @@ ForOfEmitter::emitEnd(const Maybe<uint32
     }
 
     if (!bce_->addTryNote(JSTRY_FOR_OF, bce_->stackDepth, loopInfo_->headOffset(),
                           loopInfo_->breakTargetOffset()))
     {
         return false;
     }
 
-    if (!bce_->emitPopN(3)) {                         //
+    if (!bce_->emitPopN(3)) {
+        //                    [stack]
         return false;
     }
 
     loopInfo_.reset();
 
 #ifdef DEBUG
     state_ = State::End;
 #endif
--- a/js/src/frontend/ForOfLoopControl.cpp
+++ b/js/src/frontend/ForOfLoopControl.cpp
@@ -35,82 +35,96 @@ ForOfLoopControl::emitBeginCodeNeedingIt
     numYieldsAtBeginCodeNeedingIterClose_ = bce->numYields;
 
     return true;
 }
 
 bool
 ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce)
 {
-    if (!tryCatch_->emitCatch()) {            // ITER ...
+    if (!tryCatch_->emitCatch()) {
+        //                    [stack] ITER ...
         return false;
     }
 
-    if (!bce->emit1(JSOP_EXCEPTION)) {        // ITER ... EXCEPTION
+    if (!bce->emit1(JSOP_EXCEPTION)) {
+        //                    [stack] ITER ... EXCEPTION
         return false;
     }
     unsigned slotFromTop = bce->stackDepth - iterDepth_;
-    if (!bce->emitDupAt(slotFromTop)) {       // ITER ... EXCEPTION ITER
+    if (!bce->emitDupAt(slotFromTop)) {
+        //                    [stack] ITER ... EXCEPTION ITER
         return false;
     }
 
     // If ITER is undefined, it means the exception is thrown by
     // IteratorClose for non-local jump, and we should't perform
     // IteratorClose again here.
-    if (!bce->emit1(JSOP_UNDEFINED)) {        // ITER ... EXCEPTION ITER UNDEF
+    if (!bce->emit1(JSOP_UNDEFINED)) {
+        //                    [stack] ITER ... EXCEPTION ITER UNDEF
         return false;
     }
-    if (!bce->emit1(JSOP_STRICTNE)) {         // ITER ... EXCEPTION NE
+    if (!bce->emit1(JSOP_STRICTNE)) {
+        //                    [stack] ITER ... EXCEPTION NE
         return false;
     }
 
     InternalIfEmitter ifIteratorIsNotClosed(bce);
-    if (!ifIteratorIsNotClosed.emitThen()) {  // ITER ... EXCEPTION
+    if (!ifIteratorIsNotClosed.emitThen()) {
+        //                    [stack] ITER ... EXCEPTION
         return false;
     }
 
     MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
-    if (!bce->emitDupAt(slotFromTop)) {       // ITER ... EXCEPTION ITER
+    if (!bce->emitDupAt(slotFromTop)) {
+        //                    [stack] ITER ... EXCEPTION ITER
         return false;
     }
     if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Throw)) {
         return false;                         // ITER ... EXCEPTION
     }
 
-    if (!ifIteratorIsNotClosed.emitEnd()) {   // ITER ... EXCEPTION
+    if (!ifIteratorIsNotClosed.emitEnd()) {
+        //                    [stack] ITER ... EXCEPTION
         return false;
     }
 
-    if (!bce->emit1(JSOP_THROW)) {            // ITER ...
+    if (!bce->emit1(JSOP_THROW)) {
+        //                    [stack] ITER ...
         return false;
     }
 
     // If any yields were emitted, then this for-of loop is inside a star
     // generator and must handle the case of Generator.return. Like in
     // yield*, it is handled with a finally block.
     uint32_t numYieldsEmitted = bce->numYields;
     if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
         if (!tryCatch_->emitFinally()) {
             return false;
         }
 
         InternalIfEmitter ifGeneratorClosing(bce);
-        if (!bce->emit1(JSOP_ISGENCLOSING)) { // ITER ... FTYPE FVALUE CLOSING
+        if (!bce->emit1(JSOP_ISGENCLOSING)) {
+            //                [stack] ITER ... FTYPE FVALUE CLOSING
             return false;
         }
-        if (!ifGeneratorClosing.emitThen()) { // ITER ... FTYPE FVALUE
+        if (!ifGeneratorClosing.emitThen()) {
+            //                [stack] ITER ... FTYPE FVALUE
             return false;
         }
-        if (!bce->emitDupAt(slotFromTop + 1)) { // ITER ... FTYPE FVALUE ITER
+        if (!bce->emitDupAt(slotFromTop + 1)) {
+            //                [stack] ITER ... FTYPE FVALUE ITER
             return false;
         }
         if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal)) {
-            return false;                     // ITER ... FTYPE FVALUE
+            //                [stack] ITER ... FTYPE FVALUE
+            return false;
         }
-        if (!ifGeneratorClosing.emitEnd()) {  // ITER ... FTYPE FVALUE
+        if (!ifGeneratorClosing.emitEnd()) {
+            //                [stack] ITER ... FTYPE FVALUE
             return false;
         }
     }
 
     if (!tryCatch_->emitEnd()) {
         return false;
     }
 
@@ -155,51 +169,60 @@ bool
 ForOfLoopControl::emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
                                                       EmitterScope& currentScope,
                                                       bool isTarget)
 {
     // Pop unnecessary value from the stack.  Effectively this means
     // leaving try-catch block.  However, the performing IteratorClose can
     // reach the depth for try-catch, and effectively re-enter the
     // try-catch block.
-    if (!bce->emit1(JSOP_POP)) {                      // NEXT ITER
+    if (!bce->emit1(JSOP_POP)) {
+        //                    [stack] NEXT ITER
         return false;
     }
 
     // Pop the iterator's next method.
-    if (!bce->emit1(JSOP_SWAP)) {                     // ITER NEXT
+    if (!bce->emit1(JSOP_SWAP)) {
+        //                    [stack] ITER NEXT
         return false;
     }
-    if (!bce->emit1(JSOP_POP)) {                      // ITER
+    if (!bce->emit1(JSOP_POP)) {
+        //                    [stack] ITER
         return false;
     }
 
     // Clear ITER slot on the stack to tell catch block to avoid performing
     // IteratorClose again.
-    if (!bce->emit1(JSOP_UNDEFINED)) {                // ITER UNDEF
+    if (!bce->emit1(JSOP_UNDEFINED)) {
+        //                    [stack] ITER UNDEF
         return false;
     }
-    if (!bce->emit1(JSOP_SWAP)) {                     // UNDEF ITER
+    if (!bce->emit1(JSOP_SWAP)) {
+        //                    [stack] UNDEF ITER
         return false;
     }
 
-    if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) { // UNDEF
+    if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) {
+        //                    [stack] UNDEF
         return false;
     }
 
     if (isTarget) {
         // At the level of the target block, there's bytecode after the
         // loop that will pop the next method, the iterator, and the
         // value, so push two undefineds to balance the stack.
-        if (!bce->emit1(JSOP_UNDEFINED)) {            // UNDEF UNDEF
+        if (!bce->emit1(JSOP_UNDEFINED)) {
+            //                [stack] UNDEF UNDEF
             return false;
         }
-        if (!bce->emit1(JSOP_UNDEFINED)) {            // UNDEF UNDEF UNDEF
+        if (!bce->emit1(JSOP_UNDEFINED)) {
+            //                [stack] UNDEF UNDEF UNDEF
             return false;
         }
     } else {
-        if (!bce->emit1(JSOP_POP)) {                  //
+        if (!bce->emit1(JSOP_POP)) {
+            //                [stack]
             return false;
         }
     }
 
     return true;
 }
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -33,90 +33,101 @@ NameOpEmitter::NameOpEmitter(BytecodeEmi
 
 bool
 NameOpEmitter::emitGet()
 {
     MOZ_ASSERT(state_ == State::Start);
 
     switch (loc_.kind()) {
       case NameLocation::Kind::Dynamic:
-        if (!bce_->emitAtomOp(name_, JSOP_GETNAME)) { // VAL
+        if (!bce_->emitAtomOp(name_, JSOP_GETNAME)) {
+            //                [stack] VAL
             return false;
         }
         break;
       case NameLocation::Kind::Global:
-        if (!bce_->emitAtomOp(name_, JSOP_GETGNAME)) {// VAL
+        if (!bce_->emitAtomOp(name_, JSOP_GETGNAME)) {
+            //                [stack] VAL
             return false;
         }
         break;
       case NameLocation::Kind::Intrinsic:
         if (!bce_->emitAtomOp(name_, JSOP_GETINTRINSIC)) {
-            return false;                             // VAL
+            //                [stack] VAL
+            return false;
         }
         break;
       case NameLocation::Kind::NamedLambdaCallee:
-        if (!bce_->emit1(JSOP_CALLEE)) {              // VAL
+        if (!bce_->emit1(JSOP_CALLEE)) {
+            //                [stack] VAL
             return false;
         }
         break;
       case NameLocation::Kind::Import:
         if (!bce_->emitAtomOp(name_, JSOP_GETIMPORT)) {
-            return false;                             // VAL
+            //                [stack] VAL
+            return false;
         }
         break;
       case NameLocation::Kind::ArgumentSlot:
         if (!bce_->emitArgOp(JSOP_GETARG, loc_.argumentSlot())) {
-            return false;                             // VAL
+            //                [stack] VAL
+            return false;
         }
         break;
       case NameLocation::Kind::FrameSlot:
         if (loc_.isLexical()) {
             if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
                 return false;
             }
         }
         if (!bce_->emitLocalOp(JSOP_GETLOCAL, loc_.frameSlot())) {
-            return false;                             // VAL
+            //                [stack] VAL
+            return false;
         }
         break;
       case NameLocation::Kind::EnvironmentCoordinate:
         if (loc_.isLexical()) {
             if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
                 return false;
             }
         }
         if (!bce_->emitEnvCoordOp(JSOP_GETALIASEDVAR, loc_.environmentCoordinate())) {
-            return false;                             // VAL
+            //                [stack] VAL
+            return false;
         }
         break;
       case NameLocation::Kind::DynamicAnnexBVar:
         MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
     }
 
     if (isCall()) {
         switch (loc_.kind()) {
           case NameLocation::Kind::Dynamic: {
             JSOp thisOp = bce_->needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
-            if (!bce_->emitAtomOp(name_, thisOp)) {   // CALLEE THIS
+            if (!bce_->emitAtomOp(name_, thisOp)) {
+                //            [stack] CALLEE THIS
                 return false;
             }
             break;
           }
           case NameLocation::Kind::Global:
             if (!bce_->emitAtomOp(name_, JSOP_GIMPLICITTHIS)) {
-                return false;                         // CALLEE THIS
+                //            [stack] CALLEE THIS
+                return false;
             }
             break;
           case NameLocation::Kind::Intrinsic:
           case NameLocation::Kind::NamedLambdaCallee:
           case NameLocation::Kind::Import:
           case NameLocation::Kind::ArgumentSlot:
           case NameLocation::Kind::FrameSlot:
           case NameLocation::Kind::EnvironmentCoordinate:
-            if (!bce_->emit1(JSOP_UNDEFINED)) {       // CALLEE UNDEF
+            if (!bce_->emit1(JSOP_UNDEFINED)) {
+                //            [stack] CALLEE UNDEF
                 return false;
             }
             break;
           case NameLocation::Kind::DynamicAnnexBVar:
             MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
         }
     }
 
@@ -137,37 +148,40 @@ NameOpEmitter::prepareForRhs()
       case NameLocation::Kind::DynamicAnnexBVar:
         if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
             return false;
         }
         if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) {
             // Annex B vars always go on the nearest variable environment,
             // even if lexical environments in between contain same-named
             // bindings.
-            if (!bce_->emit1(JSOP_BINDVAR)) {         // ENV
+            if (!bce_->emit1(JSOP_BINDVAR)) {
+                //            [stack] ENV
                 return false;
             }
         } else {
             if (!bce_->emitIndexOp(JSOP_BINDNAME, atomIndex_)) {
-                return false;                         // ENV
+                //            [stack] ENV
+                return false;
             }
         }
         emittedBindOp_ = true;
         break;
       case NameLocation::Kind::Global:
         if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
             return false;
         }
         if (loc_.isLexical() && isInitialize()) {
             // INITGLEXICAL always gets the global lexical scope. It doesn't
             // need a BINDGNAME.
             MOZ_ASSERT(bce_->innermostScope()->is<GlobalScope>());
         } else {
             if (!bce_->emitIndexOp(JSOP_BINDGNAME, atomIndex_)) {
-                return false;                         // ENV
+                //            [stack] ENV
+                return false;
             }
             emittedBindOp_ = true;
         }
         break;
       case NameLocation::Kind::Intrinsic:
         break;
       case NameLocation::Kind::NamedLambdaCallee:
         break;
@@ -197,24 +211,27 @@ NameOpEmitter::prepareForRhs()
         if (loc_.kind() == NameLocation::Kind::Dynamic) {
             // For dynamic accesses we need to emit GETBOUNDNAME instead of
             // GETNAME for correctness: looking up @@unscopables on the
             // environment chain (due to 'with' environments) must only happen
             // once.
             //
             // GETBOUNDNAME uses the environment already pushed on the stack
             // from the earlier BINDNAME.
-            if (!bce_->emit1(JSOP_DUP)) {             // ENV ENV
+            if (!bce_->emit1(JSOP_DUP)) {
+                //            [stack] ENV ENV
                 return false;
             }
             if (!bce_->emitAtomOp(name_, JSOP_GETBOUNDNAME)) {
-                return false;                         // ENV V
+                //            [stack] ENV V
+                return false;
             }
         } else {
-            if (!emitGet()) {                         // ENV? V
+            if (!emitGet()) {
+                //            [stack] ENV? V
                 return false;
             }
         }
     }
 
 #ifdef DEBUG
     state_ = State::Rhs;
 #endif
@@ -335,46 +352,55 @@ NameOpEmitter::emitAssignment()
 }
 
 bool
 NameOpEmitter::emitIncDec()
 {
     MOZ_ASSERT(state_ == State::Start);
 
     JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
-    if (!prepareForRhs()) {                           // ENV? V
+    if (!prepareForRhs()) {
+        //                    [stack] ENV? V
         return false;
     }
-    if (!bce_->emit1(JSOP_POS)) {                     // ENV? N
+    if (!bce_->emit1(JSOP_POS)) {
+        //                    [stack] ENV? N
         return false;
     }
     if (isPostIncDec()) {
-        if (!bce_->emit1(JSOP_DUP)) {                 // ENV? N? N
+        if (!bce_->emit1(JSOP_DUP)) {
+            //                [stack] ENV? N? N
             return false;
         }
     }
-    if (!bce_->emit1(JSOP_ONE)) {                     // ENV? N? N 1
+    if (!bce_->emit1(JSOP_ONE)) {
+        //                    [stack] ENV? N? N 1
         return false;
     }
-    if (!bce_->emit1(binOp)) {                        // ENV? N? N+1
+    if (!bce_->emit1(binOp)) {
+        //                    [stack] ENV? N? N+1
         return false;
     }
     if (isPostIncDec() && emittedBindOp()) {
-        if (!bce_->emit2(JSOP_PICK, 2)) {             // N? N+1 ENV?
+        if (!bce_->emit2(JSOP_PICK, 2)) {
+            //                [stack] N? N+1 ENV?
             return false;
         }
-        if (!bce_->emit1(JSOP_SWAP)) {                // N? ENV? N+1
+        if (!bce_->emit1(JSOP_SWAP)) {
+            //                [stack] N? ENV? N+1
             return false;
         }
     }
-    if (!emitAssignment()) {                          // N? N+1
+    if (!emitAssignment()) {
+        //                    [stack] N? N+1
         return false;
     }
     if (isPostIncDec()) {
-        if (!bce_->emit1(JSOP_POP)) {                 // N
+        if (!bce_->emit1(JSOP_POP)) {
+            //                [stack] N
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::IncDec;
 #endif
     return true;
--- a/js/src/frontend/PropOpEmitter.cpp
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -46,62 +46,66 @@ bool
 PropOpEmitter::emitGet(JSAtom* prop)
 {
     MOZ_ASSERT(state_ == State::Obj);
 
     if (!prepareAtomIndex(prop)) {
         return false;
     }
     if (isCall()) {
-        if (!bce_->emit1(JSOP_DUP)) {                 // [Super]
-            //                                        // THIS THIS
-            //                                        // [Other]
-            //                                        // OBJ OBJ
+        if (!bce_->emit1(JSOP_DUP)) {
+            //                [stack] # if Super
+            //                [stack] THIS THIS
+            //                [stack] # otherwise
+            //                [stack] OBJ OBJ
             return false;
         }
     }
     if (isSuper()) {
-        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS? THIS SUPERBASE
+        if (!bce_->emit1(JSOP_SUPERBASE)) {
+            //                [stack] THIS? THIS SUPERBASE
             return false;
         }
     }
     if (isIncDec() || isCompoundAssignment()) {
         if (isSuper()) {
-            if (!bce_->emit1(JSOP_DUP2)) {            // THIS SUPERBASE THIS SUPERBASE
+            if (!bce_->emit1(JSOP_DUP2)) {
+                //            [stack] THIS SUPERBASE THIS SUPERBASE
                 return false;
             }
         } else {
-            if (!bce_->emit1(JSOP_DUP)) {             // OBJ OBJ
+            if (!bce_->emit1(JSOP_DUP)) {
+                //            [stack] OBJ OBJ
                 return false;
             }
         }
     }
 
     JSOp op;
     if (isSuper()) {
         op = JSOP_GETPROP_SUPER;
     } else if (isCall()) {
         op = JSOP_CALLPROP;
     } else {
         op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP;
     }
-    if (!bce_->emitAtomOp(propAtomIndex_, op)) {      // [Get]
-        //                                            // PROP
-        //                                            // [Call]
-        //                                            // THIS PROP
-        //                                            // [Inc/Dec/Compound,
-        //                                            //  Super]
-        //                                            // THIS SUPERBASE PROP
-        //                                            // [Inc/Dec/Compound,
-        //                                            //  Other]
-        //                                            // OBJ PROP
+    if (!bce_->emitAtomOp(propAtomIndex_, op)) {
+        //                    [stack] # if Get
+        //                    [stack] PROP
+        //                    [stack] # if Call
+        //                    [stack] THIS PROP
+        //                    [stack] # if Inc/Dec/Compound, Super]
+        //                    [stack] THIS SUPERBASE PROP
+        //                    [stack] # if Inc/Dec/Compound, other
+        //                    [stack] OBJ PROP
         return false;
     }
     if (isCall()) {
-        if (!bce_->emit1(JSOP_SWAP)) {                // PROP THIS
+        if (!bce_->emit1(JSOP_SWAP)) {
+            //                [stack] PROP THIS
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::Get;
 #endif
     return true;
@@ -112,17 +116,18 @@ PropOpEmitter::prepareForRhs()
 {
     MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
     MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj);
     MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
 
     if (isSimpleAssignment()) {
         // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
         if (isSuper()) {
-            if (!bce_->emit1(JSOP_SUPERBASE)) {       // THIS SUPERBASE
+            if (!bce_->emit1(JSOP_SUPERBASE)) {
+                //            [stack] THIS SUPERBASE
                 return false;
             }
         }
     }
 
 #ifdef DEBUG
     state_ = State::Rhs;
 #endif
@@ -147,33 +152,37 @@ PropOpEmitter::emitDelete(JSAtom* prop)
     MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj);
     MOZ_ASSERT_IF(isSuper(), state_ == State::Start);
     MOZ_ASSERT(isDelete());
 
     if (!prepareAtomIndex(prop)) {
         return false;
     }
     if (isSuper()) {
-        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS SUPERBASE
+        if (!bce_->emit1(JSOP_SUPERBASE)) {
+            //                [stack] THIS SUPERBASE
             return false;
         }
 
         // Unconditionally throw when attempting to delete a super-reference.
         if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
-            return false;                             // THIS SUPERBASE
+            //                [stack] THIS SUPERBASE
+            return false;
         }
 
         // Another wrinkle: Balance the stack from the emitter's point of view.
         // Execution will not reach here, as the last bytecode threw.
-        if (!bce_->emit1(JSOP_POP)) {                 // THIS
+        if (!bce_->emit1(JSOP_POP)) {
+            //                [stack] THIS
             return false;
         }
     } else {
         JSOp op = bce_->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
-        if (!bce_->emitAtomOp(propAtomIndex_, op)) {  // SUCCEEDED
+        if (!bce_->emitAtomOp(propAtomIndex_, op)) {
+            //                [stack] SUCCEEDED
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::Delete;
 #endif
     return true;
@@ -189,17 +198,18 @@ PropOpEmitter::emitAssignment(JSAtom* pr
         if (!prepareAtomIndex(prop)) {
             return false;
         }
     }
 
     JSOp setOp = isSuper()
                  ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
                  : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
-    if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {   // VAL
+    if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {
+        //                    [stack] VAL
         return false;
     }
 
 #ifdef DEBUG
     state_ = State::Assignment;
 #endif
     return true;
 }
@@ -213,62 +223,76 @@ PropOpEmitter::emitIncDec(JSAtom* prop)
     if (!emitGet(prop)) {
         return false;
     }
 
     MOZ_ASSERT(state_ == State::Get);
 
     JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
 
-    if (!bce_->emit1(JSOP_POS)) {                     // ... N
+    if (!bce_->emit1(JSOP_POS)) {
+        //                    [stack] ... N
         return false;
     }
     if (isPostIncDec()) {
-        if (!bce_->emit1(JSOP_DUP)) {                 // ... N N
+        if (!bce_->emit1(JSOP_DUP)) {
+            //                [stack] .. N N
             return false;
         }
     }
-    if (!bce_->emit1(JSOP_ONE)) {                     // ... N? N 1
+    if (!bce_->emit1(JSOP_ONE)) {
+        //                    [stack] ... N? N 1
         return false;
     }
-    if (!bce_->emit1(binOp)) {                        // ... N? N+1
+    if (!bce_->emit1(binOp)) {
+        //                    [stack] ... N? N+1
         return false;
     }
     if (isPostIncDec()) {
-        if (isSuper()) {                              // THIS OBJ N N+1
-            if (!bce_->emit2(JSOP_PICK, 3)) {         // OBJ N N+1 THIS
+        if (isSuper()) {
+            //                [stack] THIS OBJ N N+1
+            if (!bce_->emit2(JSOP_PICK, 3)) {
+                //            [stack] OBJ N N+1 THIS
                 return false;
             }
-            if (!bce_->emit1(JSOP_SWAP)) {            // OBJ N THIS N+1
+            if (!bce_->emit1(JSOP_SWAP)) {
+                //            [stack] OBJ N THIS N+1
                 return false;
             }
-            if (!bce_->emit2(JSOP_PICK, 3)) {         // N THIS N+1 OBJ
+            if (!bce_->emit2(JSOP_PICK, 3)) {
+                //            [stack] N THIS N+1 OBJ
                 return false;
             }
-            if (!bce_->emit1(JSOP_SWAP)) {            // N THIS OBJ N+1
+            if (!bce_->emit1(JSOP_SWAP)) {
+                //            [stack] N THIS OBJ N+1
                 return false;
             }
-        } else {                                      // OBJ N N+1
-            if (!bce_->emit2(JSOP_PICK, 2)) {         // N N+1 OBJ
+        } else {
+            //                [stack] OBJ N N+1
+            if (!bce_->emit2(JSOP_PICK, 2)) {
+                //            [stack] N N+1 OBJ
                 return false;
             }
-            if (!bce_->emit1(JSOP_SWAP)) {            // N OBJ N+1
+            if (!bce_->emit1(JSOP_SWAP)) {
+                //            [stack] N OBJ N+1
                 return false;
             }
         }
     }
 
     JSOp setOp = isSuper()
                  ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
                  : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
-    if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {   // N? N+1
+    if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {
+        //                    [stack] N? N+1
         return false;
     }
     if (isPostIncDec()) {
-        if (!bce_->emit1(JSOP_POP)) {                 // N
+        if (!bce_->emit1(JSOP_POP)) {
+            //                [stack] N
             return false;
         }
     }
 
 #ifdef DEBUG
     state_ = State::IncDec;
 #endif
     return true;
new file mode 100755
--- /dev/null
+++ b/js/src/frontend/align_stack_comment.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python -B
+
+""" Usage: align_stack_comment.py FILE
+
+    This script aligns the stack transition comment in BytecodeEmitter and
+    its helper classes.
+
+    The stack transition comment looks like the following:
+      //        [stack] VAL1 VAL2 VAL3
+"""
+
+from __future__ import print_function
+import re
+import sys
+
+# The column index of '[' of '[stack]'
+ALIGNMENT_COLUMN = 30
+
+# The maximum column for comment
+MAX_CHARS_PER_LINE = 80
+
+stack_comment_pat = re.compile('^( *//) *(\[stack\].*)$')
+
+
+def align_stack_comment(path):
+    lines = []
+
+    with open(path) as f:
+        max_head_len = 0
+        max_comment_len = 0
+
+        line_num = 0
+
+        for line in f:
+            line_num += 1
+
+            m = stack_comment_pat.search(line)
+            if m:
+                head = m.group(1) + ' '
+                head_len = len(head)
+                comment = m.group(2)
+                comment_len = len(comment)
+
+                if head_len > ALIGNMENT_COLUMN:
+                    print('Warning: line {} overflows from alignment column {}: {}'.format(
+                        line_num, ALIGNMENT_COLUMN, head_len), file=sys.stderr)
+
+                line_len = max(head_len, ALIGNMENT_COLUMN) + comment_len
+                if line_len > MAX_CHARS_PER_LINE:
+                    print('Warning: line {} overflows from {} chars: {}'.format(
+                        line_num, MAX_CHARS_PER_LINE, line_len), file=sys.stderr)
+
+                max_head_len = max(max_head_len, head_len)
+                max_comment_len = max(max_comment_len, comment_len)
+
+                lines.append((True, head, comment))
+            else:
+                lines.append((False, line.rstrip(), None))
+
+        print('Info: Minimum column number for [stack]: {}'.format(
+            max_head_len), file=sys.stderr)
+        print('Info: Alignment column number for [stack]: {}'.format(
+            ALIGNMENT_COLUMN), file=sys.stderr)
+        print('Info: Max length of stack transition comments: {}'.format(
+            max_comment_len), file=sys.stderr)
+
+    with open(path, 'w') as f:
+        for is_stack_comment, head_or_line, comment in lines:
+            if is_stack_comment:
+                print(head_or_line, file=f, end='')
+                spaces = max(ALIGNMENT_COLUMN - len(head_or_line), 0)
+                print(' ' * spaces, file=f, end='')
+                print(comment, file=f)
+            else:
+                print(head_or_line, file=f)
+
+
+if __name__ == '__main__':
+    if len(sys.argv) < 2:
+        print('Usage: align_stack_comment.py FILE',
+              file=sys.stderr)
+        sys.exit(1)
+    path = sys.argv[1]
+
+    align_stack_comment(path)
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -762,17 +762,17 @@ BackgroundAllocTask::run()
 
 /* static */ Chunk*
 Chunk::allocate(JSRuntime* rt)
 {
     Chunk* chunk = static_cast<Chunk*>(MapAlignedPages(ChunkSize, ChunkSize));
     if (!chunk) {
         return nullptr;
     }
-    rt->gc.stats().count(gcstats::STAT_NEW_CHUNK);
+    rt->gc.stats().count(gcstats::COUNT_NEW_CHUNK);
     return chunk;
 }
 
 void
 Chunk::init(JSRuntime* rt)
 {
     /* The chunk may still have some regions marked as no-access. */
     MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -841,17 +841,17 @@ GCRuntime::freeEmptyChunks(const AutoLoc
     FreeChunkPool(emptyChunks(lock));
 }
 
 inline void
 GCRuntime::prepareToFreeChunk(ChunkInfo& info)
 {
     MOZ_ASSERT(numArenasFreeCommitted >= info.numArenasFreeCommitted);
     numArenasFreeCommitted -= info.numArenasFreeCommitted;
-    stats().count(gcstats::STAT_DESTROY_CHUNK);
+    stats().count(gcstats::COUNT_DESTROY_CHUNK);
 #ifdef DEBUG
     /*
      * Let FreeChunkPool detect a missing prepareToFreeChunk call before it
      * frees chunk.
      */
     info.numArenasFreeCommitted = 0;
 #endif
 }
@@ -2493,17 +2493,17 @@ ArenaList::relocateArenas(Arena* toReloc
     check();
 
     while (Arena* arena = toRelocate) {
         toRelocate = arena->next;
         RelocateArena(arena, sliceBudget);
         // Prepend to list of relocated arenas
         arena->next = relocated;
         relocated = arena;
-        stats.count(gcstats::STAT_ARENA_RELOCATED);
+        stats.count(gcstats::COUNT_ARENA_RELOCATED);
     }
 
     check();
 
     return relocated;
 }
 
 // Skip compacting zones unless we can free a certain proportion of their GC
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -652,16 +652,21 @@ js::Nursery::renderProfileJSON(JSONPrint
     // and then there's no guarentee.
     if (runtime()->geckoProfiler().enabled()) {
         json.property("cells_allocated_nursery",
             stats().allocsSinceMinorGCNursery());
         json.property("cells_allocated_tenured",
             stats().allocsSinceMinorGCTenured());
     }
 
+    if (stats().getStat(gcstats::STAT_OBJECT_GROUPS_PRETENURED)) {
+        json.property("groups_pretenured",
+            stats().getStat(gcstats::STAT_OBJECT_GROUPS_PRETENURED));
+    }
+
     json.beginObjectProperty("phase_times");
 
 #define EXTRACT_NAME(name, text) #name,
     static const char* const names[] = {
 FOR_EACH_NURSERY_PROFILE_TIME(EXTRACT_NAME)
 #undef EXTRACT_NAME
     "" };
 
@@ -820,16 +825,17 @@ js::Nursery::collect(JS::gcreason::Reaso
                 AutoSweepObjectGroup sweep(group);
                 if (group->canPreTenure(sweep)) {
                     group->setShouldPreTenure(sweep, cx);
                     pretenureCount++;
                 }
             }
         }
     }
+    stats().setStat(gcstats::STAT_OBJECT_GROUPS_PRETENURED, pretenureCount);
 
     mozilla::Maybe<AutoGCSession> session;
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         if (shouldPretenure && zone->allocNurseryStrings && zone->tenuredStrings >= 30 * 1000) {
             if (!session.isSome()) {
                 session.emplace(rt, JS::HeapState::MinorCollecting);
             }
             CancelOffThreadIonCompile(zone);
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -352,28 +352,28 @@ Statistics::formatCompactSummaryMessage(
 
     SprintfLiteral(buffer,
                    "Zones: %d of %d (-%d); Compartments: %d of %d (-%d); HeapSize: %.3f MiB; " \
                    "HeapChange (abs): %+d (%d); ",
                    zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.sweptZoneCount,
                    zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
                    zoneStats.sweptCompartmentCount,
                    double(preBytes) / bytesPerMiB,
-                   counts[STAT_NEW_CHUNK] - counts[STAT_DESTROY_CHUNK],
-                   counts[STAT_NEW_CHUNK] + counts[STAT_DESTROY_CHUNK]);
+                   counts[COUNT_NEW_CHUNK] - counts[COUNT_DESTROY_CHUNK],
+                   counts[COUNT_NEW_CHUNK] + counts[COUNT_DESTROY_CHUNK]);
     if (!fragments.append(DuplicateString(buffer))) {
         return UniqueChars(nullptr);
     }
 
-    MOZ_ASSERT_IF(counts[STAT_ARENA_RELOCATED], gckind == GC_SHRINK);
+    MOZ_ASSERT_IF(counts[COUNT_ARENA_RELOCATED], gckind == GC_SHRINK);
     if (gckind == GC_SHRINK) {
         SprintfLiteral(buffer,
                        "Kind: %s; Relocated: %.3f MiB; ",
                        ExplainInvocationKind(gckind),
-                       double(ArenaSize * counts[STAT_ARENA_RELOCATED]) / bytesPerMiB);
+                       double(ArenaSize * counts[COUNT_ARENA_RELOCATED]) / bytesPerMiB);
         if (!fragments.append(DuplicateString(buffer))) {
             return UniqueChars(nullptr);
         }
     }
 
     return Join(fragments);
 }
 
@@ -477,24 +477,24 @@ Statistics::formatDetailedDescription() 
         buffer, format,
         ExplainInvocationKind(gckind),
         ExplainReason(slices_[0].reason),
         nonincremental() ? "no - " : "yes",
         nonincremental() ? ExplainAbortReason(nonincrementalReason_) : "",
         zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.sweptZoneCount,
         zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
         zoneStats.sweptCompartmentCount,
-        getCount(STAT_MINOR_GC),
-        getCount(STAT_STOREBUFFER_OVERFLOW),
+        getCount(COUNT_MINOR_GC),
+        getCount(COUNT_STOREBUFFER_OVERFLOW),
         mmu20 * 100., mmu50 * 100.,
         t(sccTotal), t(sccLongest),
         double(preBytes) / bytesPerMiB,
-        getCount(STAT_NEW_CHUNK) - getCount(STAT_DESTROY_CHUNK),
-        getCount(STAT_NEW_CHUNK) + getCount(STAT_DESTROY_CHUNK),
-        double(ArenaSize * getCount(STAT_ARENA_RELOCATED)) / bytesPerMiB,
+        getCount(COUNT_NEW_CHUNK) - getCount(COUNT_DESTROY_CHUNK),
+        getCount(COUNT_NEW_CHUNK) + getCount(COUNT_DESTROY_CHUNK),
+        double(ArenaSize * getCount(COUNT_ARENA_RELOCATED)) / bytesPerMiB,
         thresholdBuffer);
 
     return DuplicateString(buffer);
 }
 
 UniqueChars
 Statistics::formatDetailedSliceDescription(unsigned i, const SliceData& slice) const
 {
@@ -699,18 +699,18 @@ Statistics::formatJsonDescription(uint64
     json.property("max_pause", longest, JSONPrinter::MILLISECONDS); // #3
     json.property("total_time", total, JSONPrinter::MILLISECONDS); // #4
     // We might be able to omit reason if perf.html was able to retrive it
     // from the first slice.  But it doesn't do this yet.
     json.property("reason", ExplainReason(slices_[0].reason)); // #5
     json.property("zones_collected", zoneStats.collectedZoneCount); // #6
     json.property("total_zones", zoneStats.zoneCount); // #7
     json.property("total_compartments", zoneStats.compartmentCount); // #8
-    json.property("minor_gcs", getCount(STAT_MINOR_GC)); // #9
-    uint32_t storebufferOverflows = getCount(STAT_STOREBUFFER_OVERFLOW);
+    json.property("minor_gcs", getCount(COUNT_MINOR_GC)); // #9
+    uint32_t storebufferOverflows = getCount(COUNT_STOREBUFFER_OVERFLOW);
     if (storebufferOverflows) {
         json.property("store_buffer_overflows", storebufferOverflows); // #10
     }
     json.property("slices", slices_.length()); // #11
 
     const double mmu20 = computeMMU(TimeDuration::FromMilliseconds(20));
     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
     json.property("mmu_20ms", int(mmu20 * 100)); // #12
@@ -720,21 +720,21 @@ Statistics::formatJsonDescription(uint64
     sccDurations(&sccTotal, &sccLongest);
     json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS); // #14
     json.property("scc_sweep_max_pause", sccLongest, JSONPrinter::MILLISECONDS); // #15
 
     if (nonincrementalReason_ != AbortReason::None) {
         json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_)); // #16
     }
     json.property("allocated_bytes", preBytes); // #17
-    uint32_t addedChunks = getCount(STAT_NEW_CHUNK);
+    uint32_t addedChunks = getCount(COUNT_NEW_CHUNK);
     if (addedChunks) {
         json.property("added_chunks", addedChunks); // #18
     }
-    uint32_t removedChunks = getCount(STAT_DESTROY_CHUNK);
+    uint32_t removedChunks = getCount(COUNT_DESTROY_CHUNK);
     if (removedChunks) {
         json.property("removed_chunks", removedChunks); // #19
     }
     json.property("major_gc_number", startingMajorGCNumber); // #20
     json.property("minor_gc_number", startingMinorGCNumber); // #21
     json.property("slice_number", startingSliceNumber); // #22
 }
 
@@ -804,16 +804,20 @@ Statistics::Statistics(JSRuntime* rt)
     aborted(false),
     enableProfiling_(false),
     sliceCount_(0)
 {
     for (auto& count : counts) {
         count = 0;
     }
 
+    for (auto& stat : stats) {
+        stat = 0;
+    }
+
 #ifdef DEBUG
     for (const auto& duration : totalTimes_) {
 #if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_UNIX) && !defined(__clang__))
         // build-linux64-asan/debug and static-analysis-linux64-st-an/debug
         // currently use an STL that lacks std::is_trivially_constructible.
         // This #ifdef probably isn't as precise as it could be, but given
         // |totalTimes_| contains |TimeDuration| defined platform-independently
         // it's not worth the trouble to nail down a more exact condition.
@@ -1072,17 +1076,17 @@ Statistics::endGC()
     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
     runtime->addTelemetry(JS_TELEMETRY_GC_MMU_50, mmu50 * 100);
     thresholdTriggered = false;
 }
 
 void
 Statistics::beginNurseryCollection(JS::gcreason::Reason reason)
 {
-    count(STAT_MINOR_GC);
+    count(COUNT_MINOR_GC);
     startingMinorGCNumber = runtime->gc.minorGCCount();
     if (nurseryCollectionCallback) {
         (*nurseryCollectionCallback)(runtime->mainContextFromOwnThread(),
                                      JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START,
                                      reason);
     }
 }
 
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -26,27 +26,37 @@
 namespace js {
 namespace gcstats {
 
 // Phase data is generated by a script. If you need to add phases, edit
 // js/src/gc/GenerateStatsPhases.py
 
 #include "gc/StatsPhasesGenerated.h"
 
-enum Stat {
-    STAT_NEW_CHUNK,
-    STAT_DESTROY_CHUNK,
-    STAT_MINOR_GC,
+// Counts can be incremented with Statistics::count(). They're reset at the end
+// of a Major GC.
+enum Count {
+    COUNT_NEW_CHUNK,
+    COUNT_DESTROY_CHUNK,
+    COUNT_MINOR_GC,
 
     // Number of times a 'put' into a storebuffer overflowed, triggering a
     // compaction
-    STAT_STOREBUFFER_OVERFLOW,
+    COUNT_STOREBUFFER_OVERFLOW,
 
     // Number of arenas relocated by compacting GC.
-    STAT_ARENA_RELOCATED,
+    COUNT_ARENA_RELOCATED,
+
+    COUNT_LIMIT
+};
+
+// Stats can be set with Statistics::setStat(). They're not reset automatically.
+enum Stat {
+    // Number of object types pretenured this minor GC.
+    STAT_OBJECT_GROUPS_PRETENURED,
 
     STAT_LIMIT
 };
 
 struct ZoneGCStats
 {
     /* Number of zones collected in this GC. */
     int collectedZoneCount = 0;
@@ -174,24 +184,32 @@ struct Statistics
     bool nonincremental() const {
         return nonincrementalReason_ != gc::AbortReason::None;
     }
 
     const char* nonincrementalReason() const {
         return ExplainAbortReason(nonincrementalReason_);
     }
 
-    void count(Stat s) {
+    void count(Count s) {
         counts[s]++;
     }
 
-    uint32_t getCount(Stat s) const {
+    uint32_t getCount(Count s) const {
         return uint32_t(counts[s]);
     }
 
+    void setStat(Stat s, uint32_t value) {
+        stats[s] = value;
+    }
+
+    uint32_t getStat(Stat s) const {
+        return stats[s];
+    }
+
     void recordTrigger(double amount, double threshold) {
         triggerAmount = amount;
         triggerThreshold = threshold;
         thresholdTriggered = true;
     }
 
     void noteNurseryAlloc() {
         allocsSinceMinorGC.nursery++;
@@ -327,20 +345,25 @@ struct Statistics
     TimeStamp timedGCStart;
     TimeDuration timedGCTime;
 
     /* Total time in a given phase for this GC. */
     PhaseTimeTable phaseTimes;
     PhaseTimeTable parallelTimes;
 
     /* Number of events of this type for this GC. */
+    EnumeratedArray<Count,
+                    COUNT_LIMIT,
+                    mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
+                                    mozilla::recordreplay::Behavior::DontPreserve>> counts;
+
+    /* Other GC statistics. */
     EnumeratedArray<Stat,
                     STAT_LIMIT,
-                    mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
-                                    mozilla::recordreplay::Behavior::DontPreserve>> counts;
+                    uint32_t> stats;
 
     /*
      * These events cannot be kept in the above array, we need to take their
      * address.
      */
     struct {
         uint32_t nursery;
         uint32_t tenured;
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -93,17 +93,17 @@ StoreBuffer::clear()
     bufferGeneric.clear();
 }
 
 void
 StoreBuffer::setAboutToOverflow(JS::gcreason::Reason reason)
 {
     if (!aboutToOverflow_) {
         aboutToOverflow_ = true;
-        runtime_->gc.stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW);
+        runtime_->gc.stats().count(gcstats::COUNT_STOREBUFFER_OVERFLOW);
     }
     nursery_.requestMinorGC(reason);
 }
 
 void
 StoreBuffer::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::GCSizes
 *sizes)
 {
--- a/js/xpconnect/tests/chrome/test_bug1042436.xul
+++ b/js/xpconnect/tests/chrome/test_bug1042436.xul
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
   /** Test for Bug 1042436 **/
   SimpleTest.waitForExplicitFinish();
 
   var contentSb = Cu.Sandbox('http://www.example.com');
-  var nonXrayableObj = new contentSb.WeakMap();
+  var nonXrayableObj = contentSb.eval("new Map()[Symbol.iterator]()");
   nonXrayableObj.wrappedJSObject.someExpandoProperty = 42;
   nonXrayableObj.wrappedJSObject.someOtherExpandoProperty = 52;
 
 
   SimpleTest.expectConsoleMessages(function() {
       nonXrayableObj.someExpandoProperty;
       nonXrayableObj.someOtherExpandoProperty;
 
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -41,17 +41,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   errorObjectClasses = ['Error', 'InternalError', 'EvalError', 'RangeError', 'ReferenceError',
                         'SyntaxError', 'TypeError', 'URIError'];
 
   // A simpleConstructors entry can either be the name of a constructor as a
   // string, or an object containing the properties `name`, and `args`.
   // In the former case, the constructor is invoked without any args; in the
   // latter case, it is invoked with `args` as the arguments list.
   simpleConstructors = ['Object', 'Function', 'Array', 'Boolean', 'Date', 'Number',
-                        'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'Map', 'Set',
+                        'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'WeakSet', 'Map', 'Set',
                         {name: 'Promise', args: [function(){}]}].concat(typedArrayClasses)
                                                                 .concat(errorObjectClasses);
 
   function go() {
     window.iwin = document.getElementById('ifr').contentWindow;
 
     // Test constructors that can be instantiated with zero arguments, or with
     // a fixed set of arguments provided using `...rest`.
@@ -147,16 +147,20 @@ https://bugzilla.mozilla.org/show_bug.cg
     testPromise();
 
     testArrayBuffer();
 
     testMap();
 
     testSet();
 
+    testWeakMap();
+
+    testWeakSet();
+
     testProxy();
 
     testDataView();
 
     testNumber();
 
     SimpleTest.finish();
   }
@@ -265,16 +269,26 @@ https://bugzilla.mozilla.org/show_bug.cg
     constructorProps([Symbol.species]);
 
   gPrototypeProperties['Set'] =
     ["constructor", "size", Symbol.toStringTag, "has", "add", "delete",
      "keys", "values", "clear", "forEach", "entries", Symbol.iterator];
   gConstructorProperties['Set'] =
     constructorProps([Symbol.species]);
 
+  gPrototypeProperties['WeakMap'] =
+    ["constructor", Symbol.toStringTag, "get", "has", "set", "delete"];
+  gConstructorProperties['WeakMap'] =
+    constructorProps([]);
+
+  gPrototypeProperties['WeakSet'] =
+    ["constructor", Symbol.toStringTag, "has", "add", "delete"];
+  gConstructorProperties['WeakSet'] =
+    constructorProps([]);
+
   gPrototypeProperties['DataView'] =
     ["constructor", "buffer", "byteLength", "byteOffset", Symbol.toStringTag,
      "getInt8", "getUint8", "getInt16", "getUint16",
      "getInt32", "getUint32", "getFloat32", "getFloat64",
      "setInt8", "setUint8", "setInt16", "setUint16",
      "setInt32", "setUint32", "setFloat32", "setFloat64"];
   gConstructorProperties['DataView'] = constructorProps([]);
 
@@ -312,18 +326,18 @@ https://bugzilla.mozilla.org/show_bug.cg
       function lookupCallable(obj) {
         let desc = null;
         do {
           desc = Object.getOwnPropertyDescriptor(obj, name);
           obj = Object.getPrototypeOf(obj);
         } while (!desc);
         return desc.get || desc.value;
       };
-      ok(xrayProto.hasOwnProperty(name), "proto should have the property as own");
-      ok(!xray.hasOwnProperty(name), "instance should not have the property as own");
+      ok(xrayProto.hasOwnProperty(name), `proto should have the property '${name}' as own`);
+      ok(!xray.hasOwnProperty(name), `instance should not have the property '${name}' as own`);
       let method = lookupCallable(xrayProto);
       is(typeof method, 'function', "Methods from Xrays are functions");
       is(global(method), window, "Methods from Xrays are local");
       ok(method instanceof Function, "instanceof works on methods from Xrays");
       is(lookupCallable(xrayProto), method, "Holder caching works properly");
       is(lookupCallable(xray), method, "Proto props resolve on the instance");
       let local = lookupCallable(localProto);
       is(method.length, local.length, "Function.length identical");
@@ -560,17 +574,17 @@ https://bugzilla.mozilla.org/show_bug.cg
                  var o = new Object({
                    primitiveProp: 42, objectProp: { foo: 2 },
                    xoProp: top, hasOwnProperty: 10,
                    get getterProp() { return 2; },
                    set setterProp(x) { },
                    get getterSetterProp() { return 3; },
                    set getterSetterProp(x) { },
                    callableProp: function() { },
-                   nonXrayableProp: new WeakMap()
+                   nonXrayableProp: new Map()[Symbol.iterator]()
                    ${symbolProps}
                  });
                  Object.defineProperty(o, "nonConfigurableGetterSetterProp",
                     { get: function() { return 5; }, set: function() {} });
                  return o;
                  })()`);
     testTrickyObject(trickyObject);
   }
@@ -600,17 +614,17 @@ https://bugzilla.mozilla.org/show_bug.cg
                  trickyArray.objectProp = { foo: 2 };
                  trickyArray.xoProp = top;
                  trickyArray.hasOwnProperty = 10;
                  Object.defineProperty(trickyArray, 'getterProp', { get: function() { return 2; }});
                  Object.defineProperty(trickyArray, 'setterProp', { set: function(x) {}});
                  Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}, configurable: true});
                  Object.defineProperty(trickyArray, 'nonConfigurableGetterSetterProp', { get: function() { return 5; }, set: function(x) {}});
                  trickyArray.callableProp = function() {};
-                 trickyArray.nonXrayableProp = new WeakMap();
+                 trickyArray.nonXrayableProp = new Map()[Symbol.iterator]();
                  ${symbolProps}
                  trickyArray;`);
 
     // Test indexed access.
     trickyArray.wrappedJSObject[9] = "some indexed property";
     is(trickyArray[9], "some indexed property", "indexed properties work correctly over Xrays");
     is(trickyArray.length, 10, "Length works correctly over Xrays");
     checkThrows(function() { "use strict"; delete trickyArray.length; }, /config/, "Can't delete non-configurable 'length' property");
@@ -980,16 +994,61 @@ for (var prop of props) {
     let values = [];
     t.forEach(value => values.push(value));
     is(values.toString(), "1,5", "forEach enumerates values correctly");
 
     t.clear();
     is(t.size, 0, "Set is empty after calling clear");
   }
 
+  function testWeakMap() {
+    testXray('WeakMap', new iwin.WeakMap(), new iwin.WeakMap());
+
+    var key1 = iwin.eval(`var key1 = {}; key1`);
+    var key2 = iwin.eval(`var key2 = []; key2`);
+    var key3 = iwin.eval(`var key3 = /a/; key3`);
+    var key4 = {};
+    var key5 = [];
+    var t = iwin.eval(`new WeakMap([[key1, "a"], [key2, "b"]])`);
+    is(t.get(key1), "a", "key1 has the correct value");
+    is(t.get(key2), "b", "key2 has the correct value");
+    is(t.has(key1), true, "Has key1");
+    is(t.has(key3), false, "Doesn't have key3");
+    is(t.has(key5), false, "Doesn't have key5");
+    is(t.set(key4, 5).get(key4), 5, "Correctly sets key");
+    is(t.get(key1), "a", "key1 has the correct value after modification");
+    is(t.get(key2), "b", "key2 has the correct value after modification");
+    is(t.delete(key1), true, "key1 can be deleted");
+    is(t.delete(key2), true, "key2 can be deleted");
+    is(t.delete(key3), false, "key3 cannot be deleted");
+    is(t.delete(key4), true, "key4 can be deleted");
+    is(t.delete(key5), false, "key5 cannot be deleted");
+  }
+
+  function testWeakSet() {
+    testXray('WeakSet', new iwin.WeakSet(), new iwin.WeakSet());
+
+    var key1 = iwin.eval(`var key1 = {}; key1`);
+    var key2 = iwin.eval(`var key2 = []; key2`);
+    var key3 = iwin.eval(`var key3 = /a/; key3`);
+    var key4 = {};
+    var key5 = [];
+    var t = iwin.eval(`new WeakSet([key1, key2])`);
+    is(t.has(key1), true, "Has key1");
+    is(t.has(key2), true, "Has key2");
+    is(t.has(key3), false, "Doesn't have key3");
+    is(t.has(key5), false, "Doesn't have key5");
+    is(t.add(key4, 5).has(key4), true, "Can add value to set");
+    is(t.delete(key1), true, "key1 can be deleted");
+    is(t.delete(key2), true, "key2 can be deleted");
+    is(t.delete(key3), false, "key3 cannot be deleted");
+    is(t.delete(key4), true, "key4 can be deleted");
+    is(t.delete(key5), false, "key5 cannot be deleted");
+  }
+
   function testProxy() {
     let ProxyCtor = iwin.Proxy;
     is(Object.getOwnPropertyNames(ProxyCtor).sort().toSource(),
        ["length", "name"].sort().toSource(),
        "Xrayed Proxy constructor should not have any properties");
     is(ProxyCtor.prototype, undefined, "Proxy.prototype should not be set");
     // Proxy.revocable can safely be exposed, but it is not.
     // Until it is supported, check that the property is not set.
--- a/js/xpconnect/tests/unit/test_bug856067.js
+++ b/js/xpconnect/tests/unit/test_bug856067.js
@@ -1,8 +1,8 @@
 function run_test() {
   var sb = new Cu.Sandbox('http://www.example.com');
-  let w = Cu.evalInSandbox('var w = new WeakMap(); w.__proto__ = new Set(); w.foopy = 12; w', sb);
+  let w = Cu.evalInSandbox('var w = new Map()[Symbol.iterator](); w.__proto__ = new Set(); w.foopy = 12; w', sb);
   Assert.equal(Object.getPrototypeOf(w), sb.Object.prototype);
   Assert.equal(Object.getOwnPropertyNames(w).length, 0);
   Assert.equal(w.wrappedJSObject.foopy, 12);
   Assert.equal(w.foopy, undefined);
 }
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -89,16 +89,18 @@ IsJSXraySupported(JSProtoKey key)
       case JSProto_TypedArray:
       case JSProto_SavedFrame:
       case JSProto_RegExp:
       case JSProto_Promise:
       case JSProto_ArrayBuffer:
       case JSProto_SharedArrayBuffer:
       case JSProto_Map:
       case JSProto_Set:
+      case JSProto_WeakMap:
+      case JSProto_WeakSet:
         return true;
       default:
         return false;
     }
 }
 
 XrayType
 GetXrayType(JSObject* obj)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4680,17 +4680,17 @@ pref("image.animated.decode-on-demand.ba
 
 // Whether we should recycle already displayed frames instead of discarding
 // them. This saves on the allocation itself, and may be able to reuse the
 // contents as well. Only applies if generating full frames.
 pref("image.animated.decode-on-demand.recycle", true);
 
 // Whether we should generate full frames at decode time or partial frames which
 // are combined at display time (historical behavior and default).
-pref("image.animated.generate-full-frames", false);
+pref("image.animated.generate-full-frames", true);
 
 // Resume an animated image from the last displayed frame rather than
 // advancing when out of view.
 pref("image.animated.resume-from-last-displayed", true);
 
 // Maximum number of surfaces for an image before entering "factor of 2" mode.
 // This in addition to the number of "native" sizes of an image. A native size
 // is a size for which we can decode a frame without up or downscaling. Most
--- a/testing/talos/talos/tests/layout/benchmarks/displaylist_flattened_mutate.html
+++ b/testing/talos/talos/tests/layout/benchmarks/displaylist_flattened_mutate.html
@@ -13,16 +13,19 @@
 <body id="body">
 </body>
 <script>
 
 var start = null;
 var divCount = 2500;
 var maxIterations = 600;
 
+// ensure contentful paint occurs
+document.body.innerHTML = "DisplayList flattened mutate";
+
 for (var i = 0; i < divCount; i++) {
   var div = document.createElement("div");
   div.id = i;
   document.getElementById("body").appendChild(div);
 }
 
 var iteration = 0;
 function runFrame() {
--- a/testing/talos/talos/tests/layout/benchmarks/displaylist_inactive_mutate.html
+++ b/testing/talos/talos/tests/layout/benchmarks/displaylist_inactive_mutate.html
@@ -13,16 +13,19 @@
 <body id="body">
 </body>
 <script>
 
 var start = null;
 var divCount = 1000;
 var maxIterations = 600;
 
+// ensure contentful paint occurs
+document.body.innerHTML = "DisplayList inactive mutate";
+
 for (var i = 0; i < divCount; i++) {
   var div = document.createElement("div");
   div.id = i;
   document.getElementById("body").appendChild(div);
 }
 
 var iteration = 0;
 function runFrame() {
--- a/testing/talos/talos/tests/layout/benchmarks/displaylist_mutate.html
+++ b/testing/talos/talos/tests/layout/benchmarks/displaylist_mutate.html
@@ -12,16 +12,19 @@
 <body id="body">
 </body>
 <script>
 
 var start = null;
 var divCount = 10000;
 var maxIterations = 600;
 
+// ensure contentful paint occurs
+document.body.innerHTML = "DisplayList mutate";
+
 for (var i = 0; i < divCount; i++) {
   var div = document.createElement("div");
   div.id = i;
   document.getElementById("body").appendChild(div);
 }
 
 var iteration = 0;
 function runFrame() {
--- a/xpcom/ds/nsObserverList.cpp
+++ b/xpcom/ds/nsObserverList.cpp
@@ -1,105 +1,73 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "nsObserverList.h"
 
+#include "mozilla/ResultExtensions.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "xpcpublic.h"
 
 nsresult
 nsObserverList::AddObserver(nsIObserver* anObserver, bool ownsWeak)
 {
   NS_ASSERTION(anObserver, "Null input");
 
-  if (!ownsWeak) {
-    ObserverRef* o = mObservers.AppendElement(anObserver);
-    if (!o) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    return NS_OK;
-  }
-
-  nsWeakPtr weak = do_GetWeakReference(anObserver);
-  if (!weak) {
-    return NS_NOINTERFACE;
-  }
-
-  ObserverRef* o = mObservers.AppendElement(weak);
-  if (!o) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
+  MOZ_TRY(mObservers.AppendWeakElement(anObserver, ownsWeak));
   return NS_OK;
 }
 
 nsresult
 nsObserverList::RemoveObserver(nsIObserver* anObserver)
 {
   NS_ASSERTION(anObserver, "Null input");
 
-  if (mObservers.RemoveElement(static_cast<nsISupports*>(anObserver))) {
-    return NS_OK;
-  }
-
-  nsWeakPtr observerRef = do_GetWeakReference(anObserver);
-  if (!observerRef) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!mObservers.RemoveElement(observerRef)) {
-    return NS_ERROR_FAILURE;
-  }
-
+  MOZ_TRY(mObservers.RemoveWeakElement(anObserver));
   return NS_OK;
 }
 
 void
 nsObserverList::GetObserverList(nsISimpleEnumerator** anEnumerator)
 {
   RefPtr<nsObserverEnumerator> e(new nsObserverEnumerator(this));
   e.forget(anEnumerator);
 }
 
 void
 nsObserverList::FillObserverArray(nsCOMArray<nsIObserver>& aArray)
 {
   aArray.SetCapacity(mObservers.Length());
 
-  nsTArray<ObserverRef> observers(mObservers);
+  nsMaybeWeakPtrArray<nsIObserver> observers(mObservers);
 
   for (int32_t i = observers.Length() - 1; i >= 0; --i) {
-    if (observers[i].isWeakRef) {
-      nsCOMPtr<nsIObserver> o(do_QueryReferent(observers[i].asWeak()));
-      if (o) {
-        aArray.AppendObject(o);
-      } else {
-        // the object has gone away, remove the weakref
-        mObservers.RemoveElement(observers[i].asWeak());
-      }
+    nsCOMPtr<nsIObserver> observer = observers[i].GetValue();
+    if (observer) {
+      aArray.AppendObject(observer);
     } else {
-      aArray.AppendObject(observers[i].asObserver());
+      // the object has gone away, remove the weakref
+      mObservers.RemoveElementAt(i);
     }
   }
 }
 
 void
 nsObserverList::AppendStrongObservers(nsCOMArray<nsIObserver>& aArray)
 {
   aArray.SetCapacity(aArray.Length() + mObservers.Length());
 
   for (int32_t i = mObservers.Length() - 1; i >= 0; --i) {
-    if (!mObservers[i].isWeakRef) {
-      aArray.AppendObject(mObservers[i].asObserver());
+    if (!mObservers[i].IsWeak()) {
+      nsCOMPtr<nsIObserver> observer = mObservers[i].GetValue();
+      aArray.AppendObject(observer);
     }
   }
 }
 
 void
 nsObserverList::NotifyObservers(nsISupports* aSubject,
                                 const char* aTopic,
                                 const char16_t* someData)
--- a/xpcom/ds/nsObserverList.h
+++ b/xpcom/ds/nsObserverList.h
@@ -3,49 +3,24 @@
 /* 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 nsObserverList_h___
 #define nsObserverList_h___
 
 #include "nsISupports.h"
-#include "nsTArray.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIObserver.h"
-#include "nsIWeakReference.h"
 #include "nsHashKeys.h"
+#include "nsMaybeWeakPtr.h"
 #include "nsSimpleEnumerator.h"
 #include "mozilla/Attributes.h"
 
-struct ObserverRef
-{
-  ObserverRef(const ObserverRef& aO) : isWeakRef(aO.isWeakRef), ref(aO.ref) {}
-  explicit ObserverRef(nsIObserver* aObserver) : isWeakRef(false), ref(aObserver) {}
-  explicit ObserverRef(nsIWeakReference* aWeak) : isWeakRef(true), ref(aWeak) {}
-
-  bool isWeakRef;
-  nsCOMPtr<nsISupports> ref;
-
-  nsIObserver* asObserver()
-  {
-    NS_ASSERTION(!isWeakRef, "Isn't a strong ref.");
-    return static_cast<nsIObserver*>((nsISupports*)ref);
-  }
-
-  nsIWeakReference* asWeak()
-  {
-    NS_ASSERTION(isWeakRef, "Isn't a weak ref.");
-    return static_cast<nsIWeakReference*>((nsISupports*)ref);
-  }
-
-  bool operator==(nsISupports* aRhs) const { return ref == aRhs; }
-};
-
 class nsObserverList : public nsCharPtrHashKey
 {
   friend class nsObserverService;
 
 public:
   explicit nsObserverList(const char* aKey) : nsCharPtrHashKey(aKey)
   {
     MOZ_COUNT_CTOR(nsObserverList);
@@ -73,17 +48,17 @@ public:
   // Fill an array with the observers of this category.
   // The array is filled in last-added-first order.
   void FillObserverArray(nsCOMArray<nsIObserver>& aArray);
 
   // Like FillObserverArray(), but only for strongly held observers.
   void AppendStrongObservers(nsCOMArray<nsIObserver>& aArray);
 
 private:
-  nsTArray<ObserverRef> mObservers;
+  nsMaybeWeakPtrArray<nsIObserver> mObservers;
 };
 
 class nsObserverEnumerator final : public nsSimpleEnumerator
 {
 public:
   NS_DECL_NSISIMPLEENUMERATOR
 
   explicit nsObserverEnumerator(nsObserverList* aObserverList);
--- a/xpcom/ds/nsObserverService.cpp
+++ b/xpcom/ds/nsObserverService.cpp
@@ -12,16 +12,17 @@
 #include "nsIScriptError.h"
 #include "nsObserverService.h"
 #include "nsObserverList.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsEnumeratorUtils.h"
 #include "xpcpublic.h"
 #include "mozilla/net/NeckoCommon.h"
+#include "mozilla/ResultExtensions.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "nsString.h"
 #include "GeckoProfiler.h"
 
 static const uint32_t kMinTelemetryNotifyObserversLatencyMs = 1;
 
@@ -63,22 +64,21 @@ nsObserverService::CollectReports(nsIHan
     if (!observerList) {
       continue;
     }
 
     size_t topicNumStrong = 0;
     size_t topicNumWeakAlive = 0;
     size_t topicNumWeakDead = 0;
 
-    nsTArray<ObserverRef>& observers = observerList->mObservers;
+    nsMaybeWeakPtrArray<nsIObserver>& observers = observerList->mObservers;
     for (uint32_t i = 0; i < observers.Length(); i++) {
-      if (observers[i].isWeakRef) {
-        nsCOMPtr<nsIObserver> observerRef(
-          do_QueryReferent(observers[i].asWeak()));
-        if (observerRef) {
+      if (observers[i].IsWeak()) {
+        nsCOMPtr<nsIObserver> ref = observers[i].GetValue();
+        if (ref) {
           topicNumWeakAlive++;
         } else {
           topicNumWeakDead++;
         }
       } else {
         topicNumStrong++;
       }
     }
@@ -153,20 +153,22 @@ void
 nsObserverService::RegisterReporter()
 {
   RegisterWeakMemoryReporter(this);
 }
 
 void
 nsObserverService::Shutdown()
 {
-  UnregisterWeakMemoryReporter(this);
+  if (mShuttingDown) {
+    return;
+  }
 
   mShuttingDown = true;
-
+  UnregisterWeakMemoryReporter(this);
   mObserverTopicTable.Clear();
 }
 
 nsresult
 nsObserverService::Create(nsISupports* aOuter, const nsIID& aIID,
                           void** aInstancePtr)
 {
   LOG(("nsObserverService::Create()"));
@@ -183,88 +185,106 @@ nsObserverService::Create(nsISupports* a
   NS_DispatchToCurrentThread(
     NewRunnableMethod("nsObserverService::RegisterReporter",
                       os,
                       &nsObserverService::RegisterReporter));
 
   return os->QueryInterface(aIID, aInstancePtr);
 }
 
-#define NS_ENSURE_VALIDCALL \
-    if (!NS_IsMainThread()) {                                     \
-        MOZ_CRASH("Using observer service off the main thread!"); \
-        return NS_ERROR_UNEXPECTED;                               \
-    }                                                             \
-    if (mShuttingDown) {                                          \
-        NS_ERROR("Using observer service after XPCOM shutdown!"); \
-        return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;                  \
-    }
-
-NS_IMETHODIMP
-nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic,
-                               bool aOwnsWeak)
-{
-  LOG(("nsObserverService::AddObserver(%p: %s)",
-       (void*)aObserver, aTopic));
-
-  NS_ENSURE_VALIDCALL
-  if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
-    return NS_ERROR_INVALID_ARG;
+nsresult
+nsObserverService::EnsureValidCall() const {
+  if (!NS_IsMainThread()) {
+    MOZ_CRASH("Using observer service off the main thread!");
+    return NS_ERROR_UNEXPECTED;
   }
 
+  if (mShuttingDown) {
+    NS_ERROR("Using observer service after XPCOM shutdown!");
+    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsObserverService::FilterHttpOnTopics(const char* aTopic)
+{
   // Specifically allow http-on-opening-request and http-on-stop-request in the
   // child process; see bug 1269765.
   if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8) &&
       strcmp(aTopic, "http-on-opening-request") &&
       strcmp(aTopic, "http-on-stop-request")) {
     nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
     nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
     error->Init(NS_LITERAL_STRING("http-on-* observers only work in the parent process"),
                 EmptyString(), EmptyString(), 0, 0,
                 nsIScriptError::warningFlag, "chrome javascript",
                 false /* from private window */);
     console->LogMessage(error);
 
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic,
+                               bool aOwnsWeak)
+{
+  LOG(("nsObserverService::AddObserver(%p: %s, %s)",
+       (void*)aObserver, aTopic, aOwnsWeak ? "weak" : "strong"));
+
+  MOZ_TRY(EnsureValidCall());
+  if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  MOZ_TRY(FilterHttpOnTopics(aTopic));
+
   nsObserverList* observerList = mObserverTopicTable.PutEntry(aTopic);
   if (!observerList) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   return observerList->AddObserver(aObserver, aOwnsWeak);
 }
 
 NS_IMETHODIMP
 nsObserverService::RemoveObserver(nsIObserver* aObserver, const char* aTopic)
 {
   LOG(("nsObserverService::RemoveObserver(%p: %s)",
        (void*)aObserver, aTopic));
-  NS_ENSURE_VALIDCALL
+
+  MOZ_TRY(EnsureValidCall());
   if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
   if (!observerList) {
     return NS_ERROR_FAILURE;
   }
 
-  /* This death grip is to protect against stupid consumers who call
-     RemoveObserver from their Destructor, see bug 485834/bug 325392. */
+  /* This death grip is needed to protect against consumers who call
+   * RemoveObserver from their Destructor thus potentially thus causing
+   * infinite recursion, see bug 485834/bug 325392. */
   nsCOMPtr<nsIObserver> kungFuDeathGrip(aObserver);
   return observerList->RemoveObserver(aObserver);
 }
 
 NS_IMETHODIMP
 nsObserverService::EnumerateObservers(const char* aTopic,
                                       nsISimpleEnumerator** anEnumerator)
 {
-  NS_ENSURE_VALIDCALL
+  LOG(("nsObserverService::EnumerateObservers(%s)",
+       aTopic));
+
+  MOZ_TRY(EnsureValidCall());
   if (NS_WARN_IF(!anEnumerator) || NS_WARN_IF(!aTopic)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
   if (!observerList) {
     return NS_NewEmptyEnumerator(anEnumerator);
   }
@@ -275,17 +295,17 @@ nsObserverService::EnumerateObservers(co
 
 // Enumerate observers of aTopic and call Observe on each.
 NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports* aSubject,
                                                  const char* aTopic,
                                                  const char16_t* aSomeData)
 {
   LOG(("nsObserverService::NotifyObservers(%s)", aTopic));
 
-  NS_ENSURE_VALIDCALL
+  MOZ_TRY(EnsureValidCall());
   if (NS_WARN_IF(!aTopic)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mozilla::TimeStamp start = TimeStamp::Now();
 
   AUTO_PROFILER_LABEL_DYNAMIC_CSTR(
     "nsObserverService::NotifyObservers", OTHER, aTopic);
@@ -303,17 +323,17 @@ NS_IMETHODIMP nsObserverService::NotifyO
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsObserverService::UnmarkGrayStrongObservers()
 {
-  NS_ENSURE_VALIDCALL
+  MOZ_TRY(EnsureValidCall());
 
   nsCOMArray<nsIObserver> strongObservers;
   for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) {
     nsObserverList* aObserverList = iter.Get();
     if (aObserverList) {
       aObserverList->AppendStrongObservers(strongObservers);
     }
   }
--- a/xpcom/ds/nsObserverService.h
+++ b/xpcom/ds/nsObserverService.h
@@ -39,16 +39,18 @@ public:
 
   // Unmark any strongly held observers implemented in JS so the cycle
   // collector will not traverse them.
   NS_IMETHOD UnmarkGrayStrongObservers();
 
 private:
   ~nsObserverService(void);
   void RegisterReporter();
+  nsresult EnsureValidCall() const;
+  nsresult FilterHttpOnTopics(const char* aTopic);
 
   static const size_t kSuspectReferentCount = 100;
   bool mShuttingDown;
   nsTHashtable<nsObserverList> mObserverTopicTable;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsObserverService, NS_OBSERVERSERVICE_CID)