Bug 1520759: Inline getters for jsop_getelem operations with constant property keys. r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Fri, 01 Feb 2019 06:25:55 -0800
changeset 514400 9fe2d9456e6bde0a51dd86132eff761dfe9607f0
parent 514399 550ac85a1218ca468d341655f0621c49c6d029d0
child 514401 2ef4092ab07219e3e8eb921bca04ffc578788768
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1520759
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1520759: Inline getters for jsop_getelem operations with constant property keys. r=jandem
js/src/jit-test/tests/ion/inlining/getelem-getter-bailout.js
js/src/jit-test/tests/ion/inlining/getelem-getter-frameiter.js
js/src/jit-test/tests/ion/inlining/getelem-getter-id-mismatch.js
js/src/jit-test/tests/ion/inlining/getelem-getter-megamorphic.js
js/src/jit-test/tests/ion/inlining/getelem-getter-noninlined-call.js
js/src/jit-test/tests/ion/inlining/getelem-getter-own.js
js/src/jit-test/tests/ion/inlining/getelem-getter-proto.js
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineInspector.cpp
js/src/jit/BaselineInspector.h
js/src/jit/Ion.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/JitFrames.cpp
js/src/jit/JitRealm.h
js/src/jit/Recover.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-bailout.js
@@ -0,0 +1,48 @@
+// Test bailouts in inlined jsop_getelem accesses.
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom = "prop";
+var symbol = Symbol();
+
+function testAtom() {
+    var holder = {
+        get [atom]() {
+            bailout();
+            return 1;
+        }
+    };
+
+    function f() {
+        for (var i = 0; i < 2000; ++i) {
+            var x = holder[atom];
+            assertEq(x, 1);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtom();
+
+function testSymbol() {
+    var holder = {
+        get [symbol]() {
+            bailout();
+            return 1;
+        }
+    };
+
+    function f() {
+        for (var i = 0; i < 2000; ++i) {
+            var x = holder[symbol];
+            assertEq(x, 1);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbol();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-frameiter.js
@@ -0,0 +1,48 @@
+// Test bailouts in inlined jsop_getelem accesses.
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom = "prop";
+var symbol = Symbol();
+
+function testAtom() {
+    var holder = {
+        get [atom]() {
+            new Error().stack;
+            return 1;
+        }
+    };
+
+    function f() {
+        for (var i = 0; i < 2000; ++i) {
+            var x = holder[atom];
+            assertEq(x, 1);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtom();
+
+function testSymbol() {
+    var holder = {
+        get [symbol]() {
+            new Error().stack;
+            return 1;
+        }
+    };
+
+    function f() {
+        for (var i = 0; i < 2000; ++i) {
+            var x = holder[symbol];
+            assertEq(x, 1);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbol();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-id-mismatch.js
@@ -0,0 +1,113 @@
+// Ensure BaselineInspector properly handles mixed atom/symbols when determining
+// whether or not a jsop_getelem access to a getter can be inlined.
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom1 = "prop1";
+var atom2 = "prop2";
+var sym1 = Symbol();
+var sym2 = Symbol();
+
+function testAtomAtom() {
+    var holder = {
+        get [atom1]() { return 1; },
+        get [atom2]() { return 2; },
+    };
+
+    function get(name) {
+        // Single access point called with different atoms.
+        return holder[name];
+    }
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = get(atom1);
+            var y = get(atom2);
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtomAtom();
+
+function testAtomSymbol() {
+    var holder = {
+        get [atom1]() { return 1; },
+        get [sym2]() { return 2; },
+    };
+
+    function get(name) {
+        // Single access point called with atom and symbol.
+        return holder[name];
+    }
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = get(atom1);
+            var y = get(sym2);
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtomSymbol();
+
+function testSymbolAtom() {
+    var holder = {
+        get [sym1]() { return 1; },
+        get [atom2]() { return 2; },
+    };
+
+    function get(name) {
+        // Single access point called with symbol and atom.
+        return holder[name];
+    }
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = get(sym1);
+            var y = get(atom2);
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbolAtom();
+
+function testSymbolSymbol() {
+    var holder = {
+        get [sym1]() { return 1; },
+        get [sym2]() { return 2; },
+    };
+
+    function get(name) {
+        // Single access point called with different symbols.
+        return holder[name];
+    }
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = get(sym1);
+            var y = get(sym2);
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbolSymbol();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-megamorphic.js
@@ -0,0 +1,79 @@
+// Test for inlined getters for jsop_getelem accesses, where the getter is a
+// property on the prototype and a megamorphic IC is attached.
+
+function makeObjects(name) {
+    class Base {
+        constructor(v) {
+            this._prop = v;
+        }
+        get [name]() {
+            return this._prop;
+        }
+    }
+
+    // When we hit |TYPE_FLAG_OBJECT_COUNT_LIMIT|, the objects are marked as
+    // |TYPE_FLAG_ANYOBJECT|. That means less than |TYPE_FLAG_OBJECT_COUNT_LIMIT|
+    // objects need to be created to have no unknown objects in the type set.
+    const TYPE_FLAG_OBJECT_COUNT_LIMIT = 7;
+
+    // |ICState::ICState::MaxOptimizedStubs| defines the maximum number of
+    // optimized stubs, so as soon as we hit the maximum number, the megamorphic
+    // state is entered.
+    const ICState_MaxOptimizedStubs = 6;
+
+    // Create enough classes to enter megamorphic state, but not too much to
+    // have |TYPE_FLAG_ANYOBJECT| in the TypeSet.
+    const OBJECT_COUNT = Math.min(ICState_MaxOptimizedStubs, TYPE_FLAG_OBJECT_COUNT_LIMIT);
+
+    var objects = [];
+    for (var i = 0; i < OBJECT_COUNT; ++i) {
+        objects.push(new class extends Base {}(1));
+    }
+
+    return objects;
+}
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom = "prop";
+var symbol = Symbol();
+
+function testAtom() {
+    var objects = makeObjects(atom);
+
+    function f() {
+        var actual = 0;
+        var expected = 0;
+        for (var i = 0; i < 1000; i++) {
+            var obj = objects[i % objects.length];
+            actual += obj[atom];
+            expected += obj._prop;
+        }
+        assertEq(actual, expected);
+    }
+
+    for (var i = 0; i < 2; ++i) {
+        f();
+    }
+}
+testAtom();
+
+function testSymbol() {
+    var objects = makeObjects(symbol);
+
+    function f() {
+        var actual = 0;
+        var expected = 0;
+        for (var i = 0; i < 1000; i++) {
+            var obj = objects[i % objects.length];
+            actual += obj[symbol];
+            expected += obj._prop;
+        }
+        assertEq(actual, expected);
+    }
+
+    for (var i = 0; i < 2; ++i) {
+        f();
+    }
+}
+testSymbol();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-noninlined-call.js
@@ -0,0 +1,52 @@
+// With-Statements are not supported in Ion, therefore functions containing
+// them can't be inlined in Ion. However it's still possible to inline the
+// property access to the getter into a simple guard-shape instruction.
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom = "prop";
+var symbol = Symbol();
+
+function testAtom() {
+    var holder = {
+        get [atom]() {
+            with ({}) {
+                return 1;
+            }
+        }
+    };
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = holder[atom];
+            assertEq(x, 1);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtom();
+
+function testSymbol() {
+    var holder = {
+        get [symbol]() {
+            with ({}) {
+                return 1;
+            }
+        }
+    };
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = holder[symbol];
+            assertEq(x, 1);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbol();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-own.js
@@ -0,0 +1,51 @@
+// Test for inlined getters for jsop_getelem accesses, where the getter is an
+// own property.
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom1 = "prop1";
+var atom2 = "prop2";
+var sym1 = Symbol();
+var sym2 = Symbol();
+
+function testAtom() {
+    var holder = {
+        get [atom1]() { return 1; },
+        get [atom2]() { return 2; },
+    };
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = holder[atom1];
+            var y = holder[atom2];
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtom();
+
+function testSymbol() {
+    var holder = {
+        get [sym1]() { return 1; },
+        get [sym2]() { return 2; },
+    };
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = holder[sym1];
+            var y = holder[sym2];
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbol();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/getelem-getter-proto.js
@@ -0,0 +1,55 @@
+// Test for inlined getters for jsop_getelem accesses, where the getter is a
+// property on the prototype.
+
+// Defined outside of the test functions to ensure they're recognised as
+// constants in Ion.
+var atom1 = "prop1";
+var atom2 = "prop2";
+var sym1 = Symbol();
+var sym2 = Symbol();
+
+function testAtom() {
+    var proto = {
+        get [atom1]() { return 1; },
+        get [atom2]() { return 2; },
+    };
+
+    var holder = Object.create(proto);
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = holder[atom1];
+            var y = holder[atom2];
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testAtom();
+
+function testSymbol() {
+    var proto = {
+        get [sym1]() { return 1; },
+        get [sym2]() { return 2; },
+    };
+
+    var holder = Object.create(proto);
+
+    function f() {
+        for (var i = 0; i < 1000; ++i) {
+            var x = holder[sym1];
+            var y = holder[sym2];
+            assertEq(x, 1);
+            assertEq(y, 2);
+        }
+    }
+
+    for (var i = 0; i < 2; i++) {
+        f();
+    }
+}
+testSymbol();
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -8,16 +8,17 @@
 
 #include "jsutil.h"
 
 #include "jit/arm/Simulator-arm.h"
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 #include "jit/CompileInfo.h"
+#include "jit/Ion.h"
 #include "jit/JitSpewer.h"
 #include "jit/mips32/Simulator-mips32.h"
 #include "jit/mips64/Simulator-mips64.h"
 #include "jit/Recover.h"
 #include "jit/RematerializedFrame.h"
 #include "js/Utility.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Debugger.h"
@@ -431,29 +432,32 @@ struct BaselineStackBuilder {
   void setCheckGlobalDeclarationConflicts() {
     header_->checkGlobalDeclarationConflicts = true;
   }
 };
 
 #ifdef DEBUG
 static inline bool IsInlinableFallback(ICFallbackStub* icEntry) {
   return icEntry->isCall_Fallback() || icEntry->isGetProp_Fallback() ||
-         icEntry->isSetProp_Fallback();
+         icEntry->isSetProp_Fallback() || icEntry->isGetElem_Fallback();
 }
 #endif
 
 static inline void* GetStubReturnAddress(JSContext* cx, jsbytecode* pc) {
   JitRealm* jitRealm = cx->realm()->jitRealm();
 
   if (IsGetPropPC(pc)) {
     return jitRealm->bailoutReturnAddr(BailoutReturnStub::GetProp);
   }
   if (IsSetPropPC(pc)) {
     return jitRealm->bailoutReturnAddr(BailoutReturnStub::SetProp);
   }
+  if (IsGetElemPC(pc)) {
+    return jitRealm->bailoutReturnAddr(BailoutReturnStub::GetElem);
+  }
 
   // This should be a call op of some kind, now.
   MOZ_ASSERT(IsCallPC(pc) && !IsSpreadCallPC(pc));
   if (IsConstructorCallPC(pc)) {
     return jitRealm->bailoutReturnAddr(BailoutReturnStub::New);
   }
   return jitRealm->bailoutReturnAddr(BailoutReturnStub::Call);
 }
@@ -896,25 +900,25 @@ static bool InitFromBailout(JSContext* c
   MOZ_ASSERT_IF(IsSpreadCallPC(pc), !iter.moreFrames());
 
   // Fixup inlined JSOP_FUNCALL, JSOP_FUNAPPLY, and accessors on the caller
   // side. On the caller side this must represent like the function wasn't
   // inlined.
   uint32_t pushedSlots = 0;
   AutoValueVector savedCallerArgs(cx);
   bool needToSaveArgs =
-      op == JSOP_FUNAPPLY || IsGetPropPC(pc) || IsSetPropPC(pc);
+      op == JSOP_FUNAPPLY || IsIonInlinableGetterOrSetterPC(pc);
   if (iter.moreFrames() && (op == JSOP_FUNCALL || needToSaveArgs)) {
     uint32_t inlined_args = 0;
     if (op == JSOP_FUNCALL) {
       inlined_args = 2 + GET_ARGC(pc) - 1;
     } else if (op == JSOP_FUNAPPLY) {
       inlined_args = 2 + blFrame->numActualArgs();
     } else {
-      MOZ_ASSERT(IsGetPropPC(pc) || IsSetPropPC(pc));
+      MOZ_ASSERT(IsIonInlinableGetterOrSetterPC(pc));
       inlined_args = 2 + IsSetPropPC(pc);
     }
 
     MOZ_ASSERT(exprStackSlots >= inlined_args);
     pushedSlots = exprStackSlots - inlined_args;
 
     JitSpew(JitSpew_BaselineBailouts,
             "      pushing %u expression stack slots before fixup",
@@ -1080,26 +1084,28 @@ static bool InitFromBailout(JSContext* c
 
   if (reachablePC) {
     if (op != JSOP_FUNAPPLY || !iter.moreFrames() || resumeAfter) {
       if (op == JSOP_FUNCALL) {
         // For fun.call(this, ...); the reconstructStackDepth will
         // include the this. When inlining that is not included.
         // So the exprStackSlots will be one less.
         MOZ_ASSERT(expectedDepth - exprStackSlots <= 1);
-      } else if (iter.moreFrames() && (IsGetPropPC(pc) || IsSetPropPC(pc))) {
+      } else if (iter.moreFrames() && IsIonInlinableGetterOrSetterPC(pc)) {
         // Accessors coming out of ion are inlined via a complete
         // lie perpetrated by the compiler internally. Ion just rearranges
         // the stack, and pretends that it looked like a call all along.
         // This means that the depth is actually one *more* than expected
         // by the interpreter, as there is now a JSFunction, |this| and [arg],
-        // rather than the expected |this| and [arg]
+        // rather than the expected |this| and [arg].
+        // If the inlined accessor is a getelem operation, the numbers do match,
+        // but that's just because getelem expects one more item on the stack.
         // Note that none of that was pushed, but it's still reflected
         // in exprStackSlots.
-        MOZ_ASSERT(exprStackSlots - expectedDepth == 1);
+        MOZ_ASSERT(exprStackSlots - expectedDepth == (IsGetElemPC(pc) ? 0 : 1));
       } else {
         // For fun.apply({}, arguments) the reconstructStackDepth will
         // have stackdepth 4, but it could be that we inlined the
         // funapply. In that case exprStackSlots, will have the real
         // arguments in the slots and not be 4.
         MOZ_ASSERT(exprStackSlots == expectedDepth);
       }
     }
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -494,21 +494,22 @@ void ICStubIterator::unlink(JSContext* c
     case Call_AnyScripted:
     case Call_Native:
     case Call_ClassHook:
     case Call_ScriptedApplyArray:
     case Call_ScriptedApplyArguments:
     case Call_ScriptedFunCall:
     case Call_ConstStringSplit:
     case WarmUpCounter_Fallback:
-    // These two fallback stubs don't actually make non-tail calls,
+    // These three fallback stubs don't actually make non-tail calls,
     // but the fallback code for the bailout path needs to pop the stub frame
     // pushed during the bailout.
     case GetProp_Fallback:
     case SetProp_Fallback:
+    case GetElem_Fallback:
       return true;
     default:
       return false;
   }
 }
 
 bool ICStub::makesGCCalls() const {
   switch (kind()) {
@@ -2190,32 +2191,65 @@ bool ICGetElem_Fallback::Compiler::gener
     masm.pushValue(R1);
     masm.pushValue(Address(masm.getStackPointer(), sizeof(Value) * 2));
 
     // Push arguments.
     masm.pushValue(R0);  // Receiver
     masm.pushValue(R1);  // Index
     masm.pushValue(Address(masm.getStackPointer(), sizeof(Value) * 5));  // Obj
     masm.push(ICStubReg);
-    pushStubPayload(masm, R0.scratchReg());
-
-    return tailCallVM(DoGetElemSuperFallbackInfo, masm);
-  }
-
-  // Ensure stack is fully synced for the expression decompiler.
-  masm.pushValue(R0);
-  masm.pushValue(R1);
-
-  // Push arguments.
-  masm.pushValue(R1);
-  masm.pushValue(R0);
-  masm.push(ICStubReg);
-  pushStubPayload(masm, R0.scratchReg());
-
-  return tailCallVM(DoGetElemFallbackInfo, masm);
+    masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+
+    if (!tailCallVM(DoGetElemSuperFallbackInfo, masm)) {
+      return false;
+    }
+  } else {
+    // Ensure stack is fully synced for the expression decompiler.
+    masm.pushValue(R0);
+    masm.pushValue(R1);
+
+    // Push arguments.
+    masm.pushValue(R1);
+    masm.pushValue(R0);
+    masm.push(ICStubReg);
+    masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+
+    if (!tailCallVM(DoGetElemFallbackInfo, masm)) {
+      return false;
+    }
+  }
+
+  // This is the resume point used when bailout rewrites call stack to undo
+  // Ion inlined frames. The return address pushed onto reconstructed stack
+  // will point here.
+  assumeStubFrame();
+  bailoutReturnOffset_.bind(masm.currentOffset());
+
+  leaveStubFrame(masm, true);
+
+  // When we get here, ICStubReg contains the ICGetElem_Fallback stub,
+  // which we can't use to enter the TypeMonitor IC, because it's a
+  // MonitoredFallbackStub instead of a MonitoredStub. So, we cheat. Note that
+  // we must have a non-null fallbackMonitorStub here because InitFromBailout
+  // delazifies.
+  masm.loadPtr(Address(ICStubReg,
+                       ICMonitoredFallbackStub::offsetOfFallbackMonitorStub()),
+               ICStubReg);
+  EmitEnterTypeMonitorIC(masm,
+                         ICTypeMonitor_Fallback::offsetOfFirstMonitorStub());
+
+  return true;
+}
+
+void ICGetElem_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm,
+                                                        Handle<JitCode*> code) {
+  BailoutReturnStub kind = hasReceiver_ ? BailoutReturnStub::GetElemSuper
+                                        : BailoutReturnStub::GetElem;
+  void* address = code->raw() + bailoutReturnOffset_.offset();
+  cx->realm()->jitRealm()->initBailoutReturnAddr(address, getKey(), kind);
 }
 
 static void SetUpdateStubData(ICCacheIR_Updated* stub,
                               const PropertyTypeCheckInfo* info) {
   if (info->isSet()) {
     stub->updateStubGroup() = info->group();
     stub->updateStubId() = info->id();
   }
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1689,18 +1689,21 @@ class ICGetElem_Fallback : public ICMoni
 
  public:
   void noteNegativeIndex() { extra_ |= EXTRA_NEGATIVE_INDEX; }
   bool hasNegativeIndex() const { return extra_ & EXTRA_NEGATIVE_INDEX; }
 
   // Compiler for this stub kind.
   class Compiler : public ICStubCompiler {
    protected:
+    CodeOffset bailoutReturnOffset_;
     bool hasReceiver_;
     MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
+    void postGenerateStubCode(MacroAssembler& masm,
+                              Handle<JitCode*> code) override;
 
     virtual int32_t getKey() const override {
       return static_cast<int32_t>(kind) |
              (static_cast<int32_t>(hasReceiver_) << 16);
     }
 
    public:
     explicit Compiler(JSContext* cx, bool hasReceiver = false)
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -1061,56 +1061,103 @@ static bool AddCacheIRGlobalGetter(
     return false;
   } else {
     MOZ_ASSERT(*commonGetter == getter);
   }
 
   return true;
 }
 
+static bool GuardSpecificAtomOrSymbol(CacheIRReader& reader, ICStub* stub,
+                                      const CacheIRStubInfo* stubInfo,
+                                      ValOperandId keyId, jsid id) {
+  // Try to match an id guard emitted by IRGenerator::emitIdGuard.
+  if (JSID_IS_ATOM(id)) {
+    if (!reader.matchOp(CacheOp::GuardIsString, keyId)) {
+      return false;
+    }
+    if (!reader.matchOp(CacheOp::GuardSpecificAtom, keyId)) {
+      return false;
+    }
+    JSString* str =
+        stubInfo->getStubField<JSString*>(stub, reader.stubOffset()).get();
+    if (AtomToId(&str->asAtom()) != id) {
+      return false;
+    }
+  } else {
+    MOZ_ASSERT(JSID_IS_SYMBOL(id));
+    if (!reader.matchOp(CacheOp::GuardIsSymbol, keyId)) {
+      return false;
+    }
+    if (!reader.matchOp(CacheOp::GuardSpecificSymbol, keyId)) {
+      return false;
+    }
+    Symbol* sym =
+        stubInfo->getStubField<Symbol*>(stub, reader.stubOffset()).get();
+    if (SYMBOL_TO_JSID(sym) != id) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 static bool AddCacheIRGetPropFunction(
-    ICCacheIR_Monitored* stub, bool innerized, JSObject** holder,
+    ICCacheIR_Monitored* stub, jsid id, bool innerized, JSObject** holder,
     Shape** holderShape, JSFunction** commonGetter, Shape** globalShape,
     bool* isOwnProperty, BaselineInspector::ReceiverVector& receivers,
     BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
     JSScript* script) {
   // We match either an own getter:
   //
   //   GuardIsObject objId
+  //   [..Id Guard..]
   //   [..WindowProxy innerization..]
   //   <GuardReceiver objId>
   //   Call(Scripted|Native)GetterResult objId
   //
   // Or a getter on the prototype:
   //
   //   GuardIsObject objId
+  //   [..Id Guard..]
   //   [..WindowProxy innerization..]
   //   <GuardReceiver objId>
   //   LoadObject holderId
   //   GuardShape holderId
   //   Call(Scripted|Native)GetterResult objId
   //
   // If |innerized| is true, we replaced a WindowProxy with the Window
   // object and we're only interested in Baseline getter stubs that performed
   // the same optimization. This means we expect the following ops for the
   // [..WindowProxy innerization..] above:
   //
   //   GuardClass objId WindowProxy
   //   objId = LoadWrapperTarget objId
   //   GuardSpecificObject objId, <global>
+  //
+  // If we test for a specific jsid, [..Id Guard..] is implemented through:
+  //   GuardIs(String|Symbol) keyId
+  //   GuardSpecific(Atom|Symbol) keyId, <atom|symbol>
 
   CacheIRReader reader(stub->stubInfo());
 
   ObjOperandId objId = ObjOperandId(0);
   if (!reader.matchOp(CacheOp::GuardIsObject, objId)) {
     return AddCacheIRGlobalGetter(stub, innerized, holder, holderShape,
                                   commonGetter, globalShape, isOwnProperty,
                                   receivers, convertUnboxedGroups, script);
   }
 
+  if (!JSID_IS_EMPTY(id)) {
+    ValOperandId keyId = ValOperandId(1);
+    if (!GuardSpecificAtomOrSymbol(reader, stub, stub->stubInfo(), keyId, id)) {
+      return false;
+    }
+  }
+
   if (innerized) {
     if (!reader.matchOp(CacheOp::GuardClass, objId) ||
         reader.guardClassKind() != GuardClassKind::WindowProxy) {
       return false;
     }
 
     if (!reader.matchOp(CacheOp::LoadWrapperTarget, objId)) {
       return false;
@@ -1206,33 +1253,40 @@ static bool AddCacheIRGetPropFunction(
   *holder = obj;
   *holderShape = objShape;
   *commonGetter = getter;
   *isOwnProperty = false;
   return true;
 }
 
 bool BaselineInspector::commonGetPropFunction(
-    jsbytecode* pc, bool innerized, JSObject** holder, Shape** holderShape,
-    JSFunction** commonGetter, Shape** globalShape, bool* isOwnProperty,
-    ReceiverVector& receivers, ObjectGroupVector& convertUnboxedGroups) {
+    jsbytecode* pc, jsid id, bool innerized, JSObject** holder,
+    Shape** holderShape, JSFunction** commonGetter, Shape** globalShape,
+    bool* isOwnProperty, ReceiverVector& receivers,
+    ObjectGroupVector& convertUnboxedGroups) {
   if (!hasICScript()) {
     return false;
   }
 
+  MOZ_ASSERT(IsGetPropPC(pc) || IsGetElemPC(pc) || JSOp(*pc) == JSOP_GETGNAME);
   MOZ_ASSERT(receivers.empty());
   MOZ_ASSERT(convertUnboxedGroups.empty());
 
+  // Only GetElem operations need to guard against a specific property id.
+  if (!IsGetElemPC(pc)) {
+    id = JSID_EMPTY;
+  }
+
   *globalShape = nullptr;
   *commonGetter = nullptr;
   const ICEntry& entry = icEntryFromPC(pc);
 
   for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
     if (stub->isCacheIR_Monitored()) {
-      if (!AddCacheIRGetPropFunction(stub->toCacheIR_Monitored(), innerized,
+      if (!AddCacheIRGetPropFunction(stub->toCacheIR_Monitored(), id, innerized,
                                      holder, holderShape, commonGetter,
                                      globalShape, isOwnProperty, receivers,
                                      convertUnboxedGroups, script)) {
         return false;
       }
     } else if (stub->isFallback()) {
       // If we have an unoptimizable access, don't try to optimize.
       if (stub->toFallbackStub()->state().hasFailures()) {
@@ -1249,65 +1303,87 @@ bool BaselineInspector::commonGetPropFun
 
   MOZ_ASSERT(*isOwnProperty == !*holder);
   MOZ_ASSERT(*isOwnProperty ==
              (receivers.empty() && convertUnboxedGroups.empty()));
   return true;
 }
 
 static JSFunction* GetMegamorphicGetterSetterFunction(
-    ICStub* stub, const CacheIRStubInfo* stubInfo, bool isGetter) {
+    ICStub* stub, const CacheIRStubInfo* stubInfo, jsid id, bool isGetter) {
   // We match:
   //
   //   GuardIsObject objId
+  //   [..Id Guard..]
   //   GuardHasGetterSetter objId propShape
   //
   // propShape has the getter/setter we're interested in.
+  //
+  // If we test for a specific jsid, [..Id Guard..] is implemented through:
+  //   GuardIs(String|Symbol) keyId
+  //   GuardSpecific(Atom|Symbol) keyId, <atom|symbol>
 
   CacheIRReader reader(stubInfo);
 
   ObjOperandId objId = ObjOperandId(0);
   if (!reader.matchOp(CacheOp::GuardIsObject, objId)) {
     return nullptr;
   }
 
+  if (!JSID_IS_EMPTY(id)) {
+    ValOperandId keyId = ValOperandId(1);
+    if (!GuardSpecificAtomOrSymbol(reader, stub, stubInfo, keyId, id)) {
+      return nullptr;
+    }
+  }
+
   if (!reader.matchOp(CacheOp::GuardHasGetterSetter, objId)) {
     return nullptr;
   }
   Shape* propShape = stubInfo->getStubField<Shape*>(stub, reader.stubOffset());
 
   JSObject* obj =
       isGetter ? propShape->getterObject() : propShape->setterObject();
   return &obj->as<JSFunction>();
 }
 
 bool BaselineInspector::megamorphicGetterSetterFunction(
-    jsbytecode* pc, bool isGetter, JSFunction** getterOrSetter) {
+    jsbytecode* pc, jsid id, bool isGetter, JSFunction** getterOrSetter) {
   if (!hasICScript()) {
     return false;
   }
 
+  MOZ_ASSERT(IsGetPropPC(pc) || IsGetElemPC(pc) || IsSetPropPC(pc) ||
+             JSOp(*pc) == JSOP_GETGNAME || JSOp(*pc) == JSOP_INITGLEXICAL ||
+             JSOp(*pc) == JSOP_INITPROP || JSOp(*pc) == JSOP_INITLOCKEDPROP ||
+             JSOp(*pc) == JSOP_INITHIDDENPROP);
+
+  // Only GetElem operations need to guard against a specific property id.
+  if (!IsGetElemPC(pc)) {
+    id = JSID_EMPTY;
+  }
+
   *getterOrSetter = nullptr;
   const ICEntry& entry = icEntryFromPC(pc);
 
   for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
     if (stub->isCacheIR_Monitored()) {
       MOZ_ASSERT(isGetter);
       JSFunction* getter = GetMegamorphicGetterSetterFunction(
-          stub, stub->toCacheIR_Monitored()->stubInfo(), isGetter);
+          stub, stub->toCacheIR_Monitored()->stubInfo(), id, isGetter);
       if (!getter || (*getterOrSetter && *getterOrSetter != getter)) {
         return false;
       }
       *getterOrSetter = getter;
       continue;
     }
     if (stub->isCacheIR_Updated()) {
       MOZ_ASSERT(!isGetter);
       JSFunction* setter = GetMegamorphicGetterSetterFunction(
-          stub, stub->toCacheIR_Updated()->stubInfo(), isGetter);
+          stub, stub->toCacheIR_Updated()->stubInfo(), id, isGetter);
       if (!setter || (*getterOrSetter && *getterOrSetter != setter)) {
         return false;
       }
       *getterOrSetter = setter;
       continue;
     }
     if (stub->isFallback()) {
       if (stub->toFallbackStub()->state().hasFailures()) {
@@ -1434,16 +1510,19 @@ static bool AddCacheIRSetPropFunction(
 bool BaselineInspector::commonSetPropFunction(
     jsbytecode* pc, JSObject** holder, Shape** holderShape,
     JSFunction** commonSetter, bool* isOwnProperty, ReceiverVector& receivers,
     ObjectGroupVector& convertUnboxedGroups) {
   if (!hasICScript()) {
     return false;
   }
 
+  MOZ_ASSERT(IsSetPropPC(pc) || JSOp(*pc) == JSOP_INITGLEXICAL ||
+             JSOp(*pc) == JSOP_INITPROP || JSOp(*pc) == JSOP_INITLOCKEDPROP ||
+             JSOp(*pc) == JSOP_INITHIDDENPROP);
   MOZ_ASSERT(receivers.empty());
   MOZ_ASSERT(convertUnboxedGroups.empty());
 
   *commonSetter = nullptr;
   const ICEntry& entry = icEntryFromPC(pc);
 
   for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
     if (stub->isCacheIR_Updated()) {
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -116,22 +116,23 @@ class BaselineInspector {
   LexicalEnvironmentObject* templateNamedLambdaObject();
   CallObject* templateCallObject();
 
   // If |innerized| is true, we're doing a GETPROP on a WindowProxy and
   // IonBuilder unwrapped/innerized it to do the lookup on the Window (the
   // global object) instead. In this case we should only look for Baseline
   // stubs that performed the same optimization.
   MOZ_MUST_USE bool commonGetPropFunction(
-      jsbytecode* pc, bool innerized, JSObject** holder, Shape** holderShape,
-      JSFunction** commonGetter, Shape** globalShape, bool* isOwnProperty,
-      ReceiverVector& receivers, ObjectGroupVector& convertUnboxedGroups);
+      jsbytecode* pc, jsid id, bool innerized, JSObject** holder,
+      Shape** holderShape, JSFunction** commonGetter, Shape** globalShape,
+      bool* isOwnProperty, ReceiverVector& receivers,
+      ObjectGroupVector& convertUnboxedGroups);
 
   MOZ_MUST_USE bool megamorphicGetterSetterFunction(
-      jsbytecode* pc, bool isGetter, JSFunction** getterOrSetter);
+      jsbytecode* pc, jsid id, bool isGetter, JSFunction** getterOrSetter);
 
   MOZ_MUST_USE bool commonSetPropFunction(
       jsbytecode* pc, JSObject** holder, Shape** holderShape,
       JSFunction** commonSetter, bool* isOwnProperty, ReceiverVector& receivers,
       ObjectGroupVector& convertUnboxedGroups);
 
   MOZ_MUST_USE bool instanceOfData(jsbytecode* pc, Shape** shape,
                                    uint32_t* slot, JSObject** prototypeObject);
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -164,22 +164,27 @@ static inline bool IsIonEnabled(JSContex
 #if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
   return false;
 #else
   return cx->options().ion() && cx->options().baseline() &&
          cx->runtime()->jitSupportsFloatingPoint;
 #endif
 }
 
+inline bool IsIonInlinableGetterOrSetterPC(jsbytecode* pc) {
+  // GETPROP, CALLPROP, LENGTH, GETELEM, and JSOP_CALLELEM. (Inlined Getters)
+  // SETPROP, SETNAME, SETGNAME (Inlined Setters)
+  return IsGetPropPC(pc) || IsGetElemPC(pc) || IsSetPropPC(pc);
+}
+
 inline bool IsIonInlinablePC(jsbytecode* pc) {
   // CALL, FUNCALL, FUNAPPLY, EVAL, NEW (Normal Callsites)
-  // GETPROP, CALLPROP, and LENGTH. (Inlined Getters)
-  // SETPROP, SETNAME, SETGNAME (Inlined Setters)
-  return (IsCallPC(pc) && !IsSpreadCallPC(pc)) || IsGetPropPC(pc) ||
-         IsSetPropPC(pc);
+  // or an inlinable getter or setter.
+  return (IsCallPC(pc) && !IsSpreadCallPC(pc)) ||
+         IsIonInlinableGetterOrSetterPC(pc);
 }
 
 inline bool TooManyActualArguments(unsigned nargs) {
   return nargs > JitOptions.maxStackArgs;
 }
 
 inline bool TooManyFormalArguments(unsigned nargs) {
   return nargs >= SNAPSHOT_MAX_NARGS || TooManyActualArguments(nargs);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7991,17 +7991,18 @@ AbortReasonOr<Ok> IonBuilder::jsop_getgn
     MOZ_TRY(getStaticName(&emitted, obj, name));
     if (emitted) {
       return Ok();
     }
 
     if (!forceInlineCaches() && obj->is<GlobalObject>()) {
       TemporaryTypeSet* types = bytecodeTypes(pc);
       MDefinition* globalObj = constant(ObjectValue(*obj));
-      MOZ_TRY(getPropTryCommonGetter(&emitted, globalObj, name, types));
+      MOZ_TRY(
+          getPropTryCommonGetter(&emitted, globalObj, NameToId(name), types));
       if (emitted) {
         return Ok();
       }
     }
   }
 
   return jsop_getname(name);
 }
@@ -8581,16 +8582,23 @@ AbortReasonOr<Ok> IonBuilder::getElemTry
 
   trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined);
   MOZ_TRY(getPropTryNotDefined(emitted, obj, id, types));
   if (*emitted) {
     index->setImplicitlyUsedUnchecked();
     return Ok();
   }
 
+  trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
+  MOZ_TRY(getPropTryCommonGetter(emitted, obj, id, types));
+  if (*emitted) {
+    index->setImplicitlyUsedUnchecked();
+    return Ok();
+  }
+
   return Ok();
 }
 
 AbortReasonOr<Ok> IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj,
                                               MDefinition* index) {
   MOZ_ASSERT(*emitted == false);
 
   if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
@@ -10129,20 +10137,20 @@ AbortReasonOr<Ok> IonBuilder::jsop_getel
 
   MOZ_TRY(resumeAfter(ins));
 
   TemporaryTypeSet* types = bytecodeTypes(pc);
   return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
 }
 
 NativeObject* IonBuilder::commonPrototypeWithGetterSetter(
-    TemporaryTypeSet* types, PropertyName* name, bool isGetter,
-    JSFunction* getterOrSetter, bool* guardGlobal) {
+    TemporaryTypeSet* types, jsid id, bool isGetter, JSFunction* getterOrSetter,
+    bool* guardGlobal) {
   // If there's a single object on the proto chain of all objects in |types|
-  // that contains a property |name| with |getterOrSetter| getter or setter
+  // that contains a property |id| with |getterOrSetter| getter or setter
   // function, return that object.
 
   // No sense looking if we don't know what's going on.
   if (!types || types->unknownObject()) {
     return nullptr;
   }
   *guardGlobal = false;
 
@@ -10158,17 +10166,17 @@ NativeObject* IonBuilder::commonPrototyp
         return nullptr;
       }
 
       const Class* clasp = key->clasp();
       if (!ClassHasEffectlessLookup(clasp)) {
         return nullptr;
       }
       JSObject* singleton = key->isSingleton() ? key->singleton() : nullptr;
-      if (ObjectHasExtraOwnProperty(realm, key, NameToId(name))) {
+      if (ObjectHasExtraOwnProperty(realm, key, id)) {
         if (!singleton || !singleton->is<GlobalObject>()) {
           return nullptr;
         }
         *guardGlobal = true;
       }
 
       // Look for a getter/setter on the class itself which may need
       // to be called.
@@ -10190,17 +10198,17 @@ NativeObject* IonBuilder::commonPrototyp
       // we're looking for a getter or setter on the proto chain and
       // these objects are singletons.
       if (singleton) {
         if (!singleton->is<NativeObject>()) {
           return nullptr;
         }
 
         NativeObject* singletonNative = &singleton->as<NativeObject>();
-        if (Shape* propShape = singletonNative->lookupPure(name)) {
+        if (Shape* propShape = singletonNative->lookupPure(id)) {
           // We found a property. Check if it's the getter or setter
           // we're looking for.
           Value getterSetterVal = ObjectValue(*getterOrSetter);
           if (isGetter) {
             if (propShape->getterOrUndefined() != getterSetterVal) {
               return nullptr;
             }
           } else {
@@ -10216,17 +10224,17 @@ NativeObject* IonBuilder::commonPrototyp
           }
           break;
         }
       }
 
       // Test for isOwnProperty() without freezing. If we end up
       // optimizing, freezePropertiesForCommonPropFunc will freeze the
       // property type sets later on.
-      HeapTypeSetKey property = key->property(NameToId(name));
+      HeapTypeSetKey property = key->property(id);
       if (TypeSet* types = property.maybeTypes()) {
         if (!types->empty() || types->nonDataProperty()) {
           return nullptr;
         }
       }
       if (singleton) {
         if (CanHaveEmptyPropertyTypesForOwnProperty(singleton)) {
           MOZ_ASSERT(singleton->is<GlobalObject>());
@@ -10249,18 +10257,18 @@ NativeObject* IonBuilder::commonPrototyp
       key = TypeSet::ObjectKey::get(proto);
     }
   }
 
   return foundProto;
 }
 
 AbortReasonOr<Ok> IonBuilder::freezePropertiesForCommonPrototype(
-    TemporaryTypeSet* types, PropertyName* name, JSObject* foundProto,
-    bool allowEmptyTypesforGlobal /* = false*/) {
+    TemporaryTypeSet* types, jsid id, JSObject* foundProto,
+    bool allowEmptyTypesforGlobal) {
   for (unsigned i = 0; i < types->getObjectCount(); i++) {
     // If we found a Singleton object's own-property, there's nothing to
     // freeze.
     if (types->getSingleton(i) == foundProto) {
       continue;
     }
 
     TypeSet::ObjectKey* key = types->getObject(i);
@@ -10268,72 +10276,71 @@ AbortReasonOr<Ok> IonBuilder::freezeProp
       continue;
     }
 
     while (true) {
       if (!alloc().ensureBallast()) {
         return abort(AbortReason::Alloc);
       }
 
-      HeapTypeSetKey property = key->property(NameToId(name));
+      HeapTypeSetKey property = key->property(id);
       MOZ_ALWAYS_TRUE(
           !property.isOwnProperty(constraints(), allowEmptyTypesforGlobal));
 
       // Don't mark the proto. It will be held down by the shape
       // guard. This allows us to use properties found on prototypes
       // with properties unknown to TI.
       if (key->proto() == TaggedProto(foundProto)) {
         break;
       }
       key = TypeSet::ObjectKey::get(key->proto().toObjectOrNull());
     }
   }
   return Ok();
 }
 
 AbortReasonOr<bool> IonBuilder::testCommonGetterSetter(
-    TemporaryTypeSet* types, PropertyName* name, bool isGetter,
-    JSFunction* getterOrSetter, MDefinition** guard,
-    Shape* globalShape /* = nullptr*/,
+    TemporaryTypeSet* types, jsid id, bool isGetter, JSFunction* getterOrSetter,
+    MDefinition** guard, Shape* globalShape /* = nullptr*/,
     MDefinition** globalGuard /* = nullptr */) {
   MOZ_ASSERT(getterOrSetter);
   MOZ_ASSERT_IF(globalShape, globalGuard);
   bool guardGlobal;
 
   // Check if all objects being accessed will lookup the name through
   // foundProto.
   NativeObject* foundProto = commonPrototypeWithGetterSetter(
-      types, name, isGetter, getterOrSetter, &guardGlobal);
+      types, id, isGetter, getterOrSetter, &guardGlobal);
   if (!foundProto || (guardGlobal && !globalShape)) {
     trackOptimizationOutcome(TrackedOutcome::MultiProtoPaths);
     return false;
   }
 
   // We can optimize the getter/setter, so freeze all involved properties to
   // ensure there isn't a lower shadowing getter or setter installed in the
   // future.
   MOZ_TRY(
-      freezePropertiesForCommonPrototype(types, name, foundProto, guardGlobal));
+      freezePropertiesForCommonPrototype(types, id, foundProto, guardGlobal));
 
   // Add a shape guard on the prototype we found the property on. The rest of
   // the prototype chain is guarded by TI freezes, except when name is a global
   // name. In this case, we also have to guard on the globals shape to be able
   // to optimize, because the way global property sets are handled means
   // freezing doesn't work for what we want here. Note that a shape guard is
   // good enough here, even in the proxy case, because we have ensured there
   // are no lookup hooks for this property.
   if (guardGlobal) {
     JSObject* obj = &script()->global();
     MDefinition* globalObj = constant(ObjectValue(*obj));
     *globalGuard = addShapeGuard(globalObj, globalShape, Bailout_ShapeGuard);
   }
 
   // If the getter/setter is not configurable we don't have to guard on the
   // proto's shape.
-  Shape* propShape = foundProto->lookupPure(name);
+  Shape* propShape = foundProto->lookupPure(id);
   MOZ_ASSERT_IF(isGetter, propShape->getterObject() == getterOrSetter);
   MOZ_ASSERT_IF(!isGetter, propShape->setterObject() == getterOrSetter);
   if (propShape && !propShape->configurable()) {
     return true;
   }
 
   MInstruction* wrapper = constant(ObjectValue(*foundProto));
   *guard =
@@ -10713,17 +10720,17 @@ AbortReasonOr<Ok> IonBuilder::jsop_getpr
     trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed);
     MOZ_TRY(getPropTryUnboxed(&emitted, obj, name, barrier, types));
     if (emitted) {
       return Ok();
     }
 
     // Try to inline a common property getter, or make a call.
     trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
-    MOZ_TRY(getPropTryCommonGetter(&emitted, obj, name, types));
+    MOZ_TRY(getPropTryCommonGetter(&emitted, obj, NameToId(name), types));
     if (emitted) {
       return Ok();
     }
 
     // Try to emit a monomorphic/polymorphic access based on baseline caches.
     trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess);
     MOZ_TRY(getPropTryInlineAccess(&emitted, obj, name, barrier, types));
     if (emitted) {
@@ -11323,18 +11330,17 @@ MDefinition* IonBuilder::addShapeGuardsF
 
   MDefinition* holderDef = constant(ObjectValue(*holder));
   addShapeGuard(holderDef, holderShape, Bailout_ShapeGuard);
 
   return addGuardReceiverPolymorphic(obj, receivers);
 }
 
 AbortReasonOr<Ok> IonBuilder::getPropTryCommonGetter(bool* emitted,
-                                                     MDefinition* obj,
-                                                     PropertyName* name,
+                                                     MDefinition* obj, jsid id,
                                                      TemporaryTypeSet* types,
                                                      bool innerized) {
   MOZ_ASSERT(*emitted == false);
 
   TemporaryTypeSet* objTypes = obj->resultTypeSet();
 
   JSFunction* commonGetter = nullptr;
   MDefinition* guard = nullptr;
@@ -11343,43 +11349,43 @@ AbortReasonOr<Ok> IonBuilder::getPropTry
   {
     Shape* lastProperty = nullptr;
     Shape* globalShape = nullptr;
     JSObject* foundProto = nullptr;
     bool isOwnProperty = false;
     BaselineInspector::ReceiverVector receivers(alloc());
     BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
     if (inspector->commonGetPropFunction(
-            pc, innerized, &foundProto, &lastProperty, &commonGetter,
+            pc, id, innerized, &foundProto, &lastProperty, &commonGetter,
             &globalShape, &isOwnProperty, receivers, convertUnboxedGroups)) {
       bool canUseTIForGetter = false;
       if (!isOwnProperty) {
         // If it's not an own property, try to use TI to avoid shape guards.
         // For own properties we use the path below.
         MOZ_TRY_VAR(canUseTIForGetter,
-                    testCommonGetterSetter(objTypes, name,
+                    testCommonGetterSetter(objTypes, id,
                                            /* isGetter = */ true, commonGetter,
                                            &guard, globalShape, &globalGuard));
       }
       if (!canUseTIForGetter) {
         // If it's an own property or type information is bad, we can still
         // optimize the getter if we shape guard.
         obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
                                             receivers, convertUnboxedGroups,
                                             isOwnProperty);
         if (!obj) {
           return abort(AbortReason::Alloc);
         }
       }
     } else if (inspector->megamorphicGetterSetterFunction(
-                   pc, /* isGetter = */ true, &commonGetter)) {
+                   pc, id, /* isGetter = */ true, &commonGetter)) {
       // Try to use TI to guard on this getter.
       bool canUseTIForGetter = false;
       MOZ_TRY_VAR(canUseTIForGetter,
-                  testCommonGetterSetter(objTypes, name, /* isGetter = */ true,
+                  testCommonGetterSetter(objTypes, id, /* isGetter = */ true,
                                          commonGetter, &guard));
       if (!canUseTIForGetter) {
         return Ok();
       }
     } else {
       // The Baseline IC didn't have any information we can use.
       return Ok();
     }
@@ -11905,17 +11911,17 @@ AbortReasonOr<Ok> IonBuilder::getPropTry
 
     trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName);
     MOZ_TRY(getStaticName(emitted, &script()->global(), name));
     if (*emitted) {
       return Ok();
     }
 
     trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
-    MOZ_TRY(getPropTryCommonGetter(emitted, inner, name, types,
+    MOZ_TRY(getPropTryCommonGetter(emitted, inner, NameToId(name), types,
                                    /* innerized = */ true));
     if (*emitted) {
       return Ok();
     }
   }
 
   // Passing the inner object to GetProperty IC is safe, see the
   // needsOuterizedThisObject check in IsCacheableGetPropCallNative.
@@ -12020,38 +12026,39 @@ AbortReasonOr<Ok> IonBuilder::setPropTry
     BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
     if (inspector->commonSetPropFunction(pc, &foundProto, &lastProperty,
                                          &commonSetter, &isOwnProperty,
                                          receivers, convertUnboxedGroups)) {
       bool canUseTIForSetter = false;
       if (!isOwnProperty) {
         // If it's not an own property, try to use TI to avoid shape guards.
         // For own properties we use the path below.
-        MOZ_TRY_VAR(
-            canUseTIForSetter,
-            testCommonGetterSetter(objTypes, name, /* isGetter = */ false,
-                                   commonSetter, &guard));
+        MOZ_TRY_VAR(canUseTIForSetter,
+                    testCommonGetterSetter(objTypes, NameToId(name),
+                                           /* isGetter = */ false, commonSetter,
+                                           &guard));
       }
       if (!canUseTIForSetter) {
         // If it's an own property or type information is bad, we can still
         // optimize the setter if we shape guard.
         obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
                                             receivers, convertUnboxedGroups,
                                             isOwnProperty);
         if (!obj) {
           return abort(AbortReason::Alloc);
         }
       }
     } else if (inspector->megamorphicGetterSetterFunction(
-                   pc, /* isGetter = */ false, &commonSetter)) {
+                   pc, NameToId(name), /* isGetter = */ false, &commonSetter)) {
       // Try to use TI to guard on this setter.
       bool canUseTIForSetter = false;
-      MOZ_TRY_VAR(canUseTIForSetter,
-                  testCommonGetterSetter(objTypes, name, /* isGetter = */ false,
-                                         commonSetter, &guard));
+      MOZ_TRY_VAR(
+          canUseTIForSetter,
+          testCommonGetterSetter(objTypes, NameToId(name),
+                                 /* isGetter = */ false, commonSetter, &guard));
       if (!canUseTIForSetter) {
         return Ok();
       }
     } else {
       // The Baseline IC didn't have any information we can use.
       return Ok();
     }
   }
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -271,18 +271,17 @@ class IonBuilder : public MIRGenerator,
   AbortReasonOr<Ok> getPropTryModuleNamespace(bool* emitted, MDefinition* obj,
                                               PropertyName* name,
                                               BarrierKind barrier,
                                               TemporaryTypeSet* types);
   AbortReasonOr<Ok> getPropTryUnboxed(bool* emitted, MDefinition* obj,
                                       PropertyName* name, BarrierKind barrier,
                                       TemporaryTypeSet* types);
   AbortReasonOr<Ok> getPropTryCommonGetter(bool* emitted, MDefinition* obj,
-                                           PropertyName* name,
-                                           TemporaryTypeSet* types,
+                                           jsid id, TemporaryTypeSet* types,
                                            bool innerized = false);
   AbortReasonOr<Ok> getPropTryInlineAccess(bool* emitted, MDefinition* obj,
                                            PropertyName* name,
                                            BarrierKind barrier,
                                            TemporaryTypeSet* types);
   AbortReasonOr<Ok> getPropTryInlineProtoAccess(bool* emitted, MDefinition* obj,
                                                 PropertyName* name,
                                                 TemporaryTypeSet* types);
@@ -859,29 +858,28 @@ class IonBuilder : public MIRGenerator,
 
   MDefinition* patchInlinedReturn(CallInfo& callInfo, MBasicBlock* exit,
                                   MBasicBlock* bottom);
   MDefinition* patchInlinedReturns(CallInfo& callInfo, MIRGraphReturns& returns,
                                    MBasicBlock* bottom);
   MDefinition* specializeInlinedReturn(MDefinition* rdef, MBasicBlock* exit);
 
   NativeObject* commonPrototypeWithGetterSetter(TemporaryTypeSet* types,
-                                                PropertyName* name,
-                                                bool isGetter,
+                                                jsid id, bool isGetter,
                                                 JSFunction* getterOrSetter,
                                                 bool* guardGlobal);
   AbortReasonOr<Ok> freezePropertiesForCommonPrototype(
-      TemporaryTypeSet* types, PropertyName* name, JSObject* foundProto,
-      bool allowEmptyTypesForGlobal = false);
+      TemporaryTypeSet* types, jsid id, JSObject* foundProto,
+      bool allowEmptyTypesForGlobal);
   /*
    * Callers must pass a non-null globalGuard if they pass a non-null
    * globalShape.
    */
   AbortReasonOr<bool> testCommonGetterSetter(
-      TemporaryTypeSet* types, PropertyName* name, bool isGetter,
+      TemporaryTypeSet* types, jsid id, bool isGetter,
       JSFunction* getterOrSetter, MDefinition** guard,
       Shape* globalShape = nullptr, MDefinition** globalGuard = nullptr);
   AbortReasonOr<bool> testShouldDOMCall(TypeSet* inTypes, JSFunction* func,
                                         JSJitInfo::OpType opType);
 
   MDefinition* addShapeGuardsForGetterSetter(
       MDefinition* obj, JSObject* holder, Shape* holderShape,
       const BaselineInspector::ReceiverVector& receivers,
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -2028,17 +2028,17 @@ void InlineFrameIterator::findNextFrame(
 
     // Recover the number of actual arguments from the script.
     if (JSOp(*pc_) != JSOP_FUNAPPLY) {
       numActualArgs_ = GET_ARGC(pc_);
     }
     if (JSOp(*pc_) == JSOP_FUNCALL) {
       MOZ_ASSERT(GET_ARGC(pc_) > 0);
       numActualArgs_ = GET_ARGC(pc_) - 1;
-    } else if (IsGetPropPC(pc_)) {
+    } else if (IsGetPropPC(pc_) || IsGetElemPC(pc_)) {
       numActualArgs_ = 0;
     } else if (IsSetPropPC(pc_)) {
       numActualArgs_ = 1;
     }
 
     if (numActualArgs_ == 0xbadbad) {
       MOZ_CRASH(
           "Couldn't deduce the number of arguments of an ionmonkey frame");
@@ -2202,17 +2202,17 @@ MachineState MachineState::FromBailout(R
 
 bool InlineFrameIterator::isConstructing() const {
   // Skip the current frame and look at the caller's.
   if (more()) {
     InlineFrameIterator parent(TlsContext.get(), this);
     ++parent;
 
     // Inlined Getters and Setters are never constructing.
-    if (IsGetPropPC(parent.pc()) || IsSetPropPC(parent.pc())) {
+    if (IsIonInlinableGetterOrSetterPC(parent.pc())) {
       return false;
     }
 
     // In the case of a JS frame, look up the pc from the snapshot.
     MOZ_ASSERT(IsCallPC(parent.pc()) && !IsSpreadCallPC(parent.pc()));
 
     return IsConstructorCallPC(parent.pc());
   }
--- a/js/src/jit/JitRealm.h
+++ b/js/src/jit/JitRealm.h
@@ -440,16 +440,18 @@ class JitZone {
   }
   void purgeIonCacheIRStubInfo() { ionCacheIRStubInfoSet_.clearAndCompact(); }
 };
 
 enum class BailoutReturnStub {
   GetProp,
   GetPropSuper,
   SetProp,
+  GetElem,
+  GetElemSuper,
   Call,
   New,
   Count
 };
 
 class JitRealm {
   friend class JitActivation;
 
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -8,16 +8,17 @@
 
 #include "jsapi.h"
 #include "jsmath.h"
 
 #include "builtin/RegExp.h"
 #include "builtin/String.h"
 #include "builtin/TypedObject.h"
 #include "gc/Heap.h"
+#include "jit/Ion.h"
 #include "jit/JitSpewer.h"
 #include "jit/JSJitFrameIter.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 #include "jit/VMFunctions.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 #include "vm/JSContext.h"
@@ -82,18 +83,18 @@ bool MResumePoint::writeRecoverData(Comp
     }
 
     if (reachablePC) {
       if (JSOp(*bailPC) == JSOP_FUNCALL) {
         // For fun.call(this, ...); the reconstructStackDepth will
         // include the this. When inlining that is not included.  So the
         // exprStackSlots will be one less.
         MOZ_ASSERT(stackDepth - exprStack <= 1);
-      } else if (JSOp(*bailPC) != JSOP_FUNAPPLY && !IsGetPropPC(bailPC) &&
-                 !IsSetPropPC(bailPC)) {
+      } else if (JSOp(*bailPC) != JSOP_FUNAPPLY &&
+                 !IsIonInlinableGetterOrSetterPC(bailPC)) {
         // For fun.apply({}, arguments) the reconstructStackDepth will
         // have stackdepth 4, but it could be that we inlined the
         // funapply. In that case exprStackSlots, will have the real
         // arguments in the slots and not be 4.
 
         // With accessors, we have different stack depths depending on
         // whether or not we inlined the accessor, as the inlined stack
         // contains a callee function that should never have been there