Bug 1653567 - Redesign Private Fields implementation to reduce complexity r=jorendorff
authorMatthew Gaudet <mgaudet@mozilla.com>
Mon, 27 Jul 2020 20:11:25 +0000
changeset 542228 aa11e196e0c785328739ac22ef4dc6f4092f1825
parent 542227 f72866e9ebd47bbccf0f208333dd1bf91d971e06
child 542229 cc4f3412856dd5ffca459a893780aadc82b06b7a
push id37645
push userccoroiu@mozilla.com
push dateTue, 28 Jul 2020 09:47:25 +0000
treeherdermozilla-central@eba7e3ce9382 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1653567
milestone81.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 1653567 - Redesign Private Fields implementation to reduce complexity r=jorendorff Adds CheckPrivateField, and uses it to guard private field access before calling regular element ops, rather than having custom element ops CheckPrivateField takes two arguments: A ThrowCondition, a message, then using the top two elements of the stack determines if a throw is required, and if it is not, places on top of the stack whether or not the field exists. This design is nice because it means that we'll be able to easily use this opcode for implementing private methods and ergonomic brand checks for private fields. This patch implements the new opcode, deletes the old PrivateElem ops, and provides Baseline callVM support. It's future work to provide IC, Ion and Warp support. Differential Revision: https://phabricator.services.mozilla.com/D83941
dom/bindings/DOMJSProxyHandler.cpp
js/src/debugger/Script.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ElemOpEmitter.cpp
js/src/frontend/ElemOpEmitter.h
js/src/frontend/Parser.cpp
js/src/jit-test/tests/fields/private-field-basics.js
js/src/jit-test/tests/fields/private-field-destructuring.js
js/src/jit-test/tests/fields/private-field-details.js
js/src/jit/BaselineCodeGen.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/CacheIR.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/VMFunctionList-inl.h
js/src/jit/WarpBuilder.h
js/src/proxy/BaseProxyHandler.cpp
js/src/vm/BytecodeFormatFlags.h
js/src/vm/BytecodeUtil.cpp
js/src/vm/BytecodeUtil.h
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
js/src/vm/ThrowMsgKind.cpp
js/src/vm/ThrowMsgKind.h
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -218,35 +218,29 @@ bool DOMProxyHandler::defineProperty(JSC
   *done = true;
   return true;
 }
 
 bool DOMProxyHandler::definePrivateField(JSContext* cx, HandleObject proxy,
                                          HandleId id,
                                          Handle<PropertyDescriptor> desc,
                                          ObjectOpResult& result) const {
-  // Delegate to defineProperty, since InitPrivateElemOperation will
-  // do the pre-existence check for us.
   return this->defineProperty(cx, proxy, id, desc, result);
 }
 
 bool DOMProxyHandler::setPrivate(JSContext* cx, Handle<JSObject*> proxy,
                                  Handle<jsid> id, Handle<JS::Value> v,
                                  Handle<JS::Value> receiver,
                                  ObjectOpResult& result) const {
-  // Delegate to set, since SetPrivateElemOperation will
-  // do the pre-existence check for us.
   return this->set(cx, proxy, id, v, receiver, result);
 }
 
 bool DOMProxyHandler::getPrivate(JSContext* cx, HandleObject proxy,
                                  HandleValue receiver, HandleId id,
                                  MutableHandleValue vp) const {
-  // Delegate to set, since GetPrivateElemOperation will
-  // do the pre-existence check for us.
   return this->get(cx, proxy, receiver, id, vp);
 }
 
 bool DOMProxyHandler::hasPrivate(JSContext* cx, HandleObject proxy, HandleId id,
                                  bool* bp) const {
   JS::Rooted<JSObject*> expando(cx, GetExpandoObject(proxy));
   // If there is no expando object, then there is no private field.
   if (!expando) {
--- a/js/src/debugger/Script.cpp
+++ b/js/src/debugger/Script.cpp
@@ -1331,32 +1331,30 @@ bool DebuggerScript::CallData::getOffset
 static bool BytecodeIsEffectful(JSOp op) {
   switch (op) {
     case JSOp::SetProp:
     case JSOp::StrictSetProp:
     case JSOp::SetPropSuper:
     case JSOp::StrictSetPropSuper:
     case JSOp::SetElem:
     case JSOp::StrictSetElem:
-    case JSOp::SetPrivateElem:
     case JSOp::SetElemSuper:
     case JSOp::StrictSetElemSuper:
     case JSOp::SetName:
     case JSOp::StrictSetName:
     case JSOp::SetGName:
     case JSOp::StrictSetGName:
     case JSOp::DelProp:
     case JSOp::StrictDelProp:
     case JSOp::DelElem:
     case JSOp::StrictDelElem:
     case JSOp::DelName:
     case JSOp::SetAliasedVar:
     case JSOp::InitHomeObject:
     case JSOp::InitAliasedLexical:
-    case JSOp::InitPrivateElem:
     case JSOp::SetIntrinsic:
     case JSOp::InitGLexical:
     case JSOp::DefVar:
     case JSOp::DefLet:
     case JSOp::DefConst:
     case JSOp::DefFun:
     case JSOp::SetFunName:
     case JSOp::MutateProto:
@@ -1483,17 +1481,16 @@ static bool BytecodeIsEffectful(JSOp op)
     case JSOp::Pick:
     case JSOp::Unpick:
     case JSOp::GetAliasedVar:
     case JSOp::Uint24:
     case JSOp::ResumeIndex:
     case JSOp::Int32:
     case JSOp::LoopHead:
     case JSOp::GetElem:
-    case JSOp::GetPrivateElem:
     case JSOp::CallElem:
     case JSOp::Length:
     case JSOp::Not:
     case JSOp::FunctionThis:
     case JSOp::GlobalThis:
     case JSOp::Callee:
     case JSOp::EnvCallee:
     case JSOp::SuperBase:
@@ -1517,16 +1514,17 @@ static bool BytecodeIsEffectful(JSOp op)
     case JSOp::FreshenLexicalEnv:
     case JSOp::RecreateLexicalEnv:
     case JSOp::Iter:
     case JSOp::MoreIter:
     case JSOp::IsNoIter:
     case JSOp::EndIter:
     case JSOp::In:
     case JSOp::HasOwn:
+    case JSOp::CheckPrivateField:
     case JSOp::SetRval:
     case JSOp::Instanceof:
     case JSOp::DebugLeaveLexicalEnv:
     case JSOp::Debugger:
     case JSOp::GImplicitThis:
     case JSOp::ImplicitThis:
     case JSOp::NewTarget:
     case JSOp::CheckIsObj:
@@ -1890,17 +1888,18 @@ struct DebuggerScript::SetBreakpointMatc
 
   bool wrapCrossCompartmentEdges() {
     if (!cx_->compartment()->wrap(cx_, &handler_) ||
         !cx_->compartment()->wrap(cx_, &debuggerObject_)) {
       return false;
     }
 
     // If the Debugger's compartment has killed incoming wrappers, we may not
-    // have gotten usable results from the 'wrap' calls. Treat it as a failure.
+    // have gotten usable results from the 'wrap' calls. Treat it as a
+    // failure.
     if (IsDeadProxyObject(handler_) || IsDeadProxyObject(debuggerObject_)) {
       ReportAccessDenied(cx_);
       return false;
     }
 
     return true;
   }
 
@@ -2075,38 +2074,39 @@ class DebuggerScript::ClearBreakpointMat
   ReturnType match(Handle<BaseScript*> base) {
     RootedScript script(cx_, DelazifyScript(cx_, base));
     if (!script) {
       return false;
     }
 
     // A Breakpoint belongs logically to its script's compartment, so it holds
     // its handler via a cross-compartment wrapper. But the handler passed to
-    // `clearBreakpoint` is same-compartment with the Debugger. Wrap it here, so
-    // that `DebugScript::clearBreakpointsIn` gets the right value to search
-    // for.
+    // `clearBreakpoint` is same-compartment with the Debugger. Wrap it here,
+    // so that `DebugScript::clearBreakpointsIn` gets the right value to
+    // search for.
     AutoRealm ar(cx_, script);
     if (!cx_->compartment()->wrap(cx_, &handler_)) {
       return false;
     }
 
     DebugScript::clearBreakpointsIn(cx_->runtime()->defaultFreeOp(), script,
                                     dbg_, handler_);
     return true;
   }
   ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
     wasm::Instance& instance = instanceObj->instance();
     if (!instance.debugEnabled()) {
       return true;
     }
 
-    // A Breakpoint belongs logically to its instance's compartment, so it holds
-    // its handler via a cross-compartment wrapper. But the handler passed to
-    // `clearBreakpoint` is same-compartment with the Debugger. Wrap it here, so
-    // that `DebugState::clearBreakpointsIn` gets the right value to search for.
+    // A Breakpoint belongs logically to its instance's compartment, so it
+    // holds its handler via a cross-compartment wrapper. But the handler
+    // passed to `clearBreakpoint` is same-compartment with the Debugger. Wrap
+    // it here, so that `DebugState::clearBreakpointsIn` gets the right value
+    // to search for.
     AutoRealm ar(cx_, instanceObj);
     if (!cx_->compartment()->wrap(cx_, &handler_)) {
       return false;
     }
 
     instance.debug().clearBreakpointsIn(cx_->runtime()->defaultFreeOp(),
                                         instanceObj, dbg_, handler_);
     return true;
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -10186,23 +10186,21 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
     case JSOp::SetProp:
     case JSOp::StrictSetProp:
       return emitInstrumentationSlow(
           InstrumentationKind::SetProperty, [=](uint32_t pushed) {
             return emitDupAt(pushed + 1) &&
                    emitAtomOp(JSOp::String, atomIndex) && emitDupAt(pushed + 2);
           });
     case JSOp::GetElem:
-    case JSOp::GetPrivateElem:
     case JSOp::CallElem:
       return emitInstrumentationSlow(
           InstrumentationKind::GetElement,
           [=](uint32_t pushed) { return emitDupAt(pushed + 1, 2); });
     case JSOp::SetElem:
-    case JSOp::SetPrivateElem:
     case JSOp::StrictSetElem:
       return emitInstrumentationSlow(
           InstrumentationKind::SetElement,
           [=](uint32_t pushed) { return emitDupAt(pushed + 2, 3); });
     default:
       return true;
   }
 }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -48,16 +48,17 @@
 #include "vm/Instrumentation.h"            // InstrumentationKind
 #include "vm/Iteration.h"                  // IteratorKind
 #include "vm/JSFunction.h"                 // JSFunction
 #include "vm/JSScript.h"       // JSScript, BaseScript, FieldInitializers
 #include "vm/Runtime.h"        // ReportOutOfMemory
 #include "vm/SharedStencil.h"  // GCThingIndex
 #include "vm/StencilEnums.h"   // TryNoteKind
 #include "vm/StringType.h"     // JSAtom
+#include "vm/ThrowMsgKind.h"   // ThrowMsgKind, ThrowCondition
 
 namespace js {
 namespace frontend {
 
 class CallOrNewEmitter;
 class ClassEmitter;
 class ElemOpEmitter;
 class EmitterScope;
@@ -831,16 +832,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
 
   MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode);
 
   MOZ_MUST_USE bool emitReturnRval() {
     return emitInstrumentation(InstrumentationKind::Exit) &&
            emit1(JSOp::RetRval);
   }
 
+  MOZ_MUST_USE bool emitCheckPrivateField(ThrowCondition throwCondition,
+                                          ThrowMsgKind msgKind) {
+    return emit3(JSOp::CheckPrivateField, uint8_t(throwCondition),
+                 uint8_t(msgKind));
+  }
+
   MOZ_MUST_USE bool emitInstrumentation(InstrumentationKind kind,
                                         uint32_t npopped = 0) {
     return MOZ_LIKELY(!instrumentationKinds) ||
            emitInstrumentationSlow(kind, std::function<bool(uint32_t)>());
   }
 
   MOZ_MUST_USE bool emitInstrumentationForOpcode(JSOp op,
                                                  GCThingIndex atomIndex) {
--- a/js/src/frontend/ElemOpEmitter.cpp
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -51,28 +51,62 @@ bool ElemOpEmitter::prepareForKey() {
   }
 
 #ifdef DEBUG
   state_ = State::Key;
 #endif
   return true;
 }
 
+bool ElemOpEmitter::emitPrivateGuard() {
+  MOZ_ASSERT(state_ == State::Key);
+
+  if (!isPrivate()) {
+    return true;
+  }
+
+  if (isPropInit()) {
+    //            [stack] OBJ KEY
+    if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHas,
+                                     ThrowMsgKind::PrivateDoubleInit)) {
+      //            [stack] OBJ KEY BOOL
+      return false;
+    }
+  } else {
+    if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHasNot,
+                                     isPrivateGet()
+                                         ? ThrowMsgKind::MissingPrivateOnGet
+                                         : ThrowMsgKind::MissingPrivateOnSet)) {
+      //            [stack] OBJ KEY BOOL
+      return false;
+    }
+  }
+
+  // CheckPrivate leaves the result of the HasOwnCheck on the stack. Pop it off.
+  return bce_->emit1(JSOp::Pop);
+  //            [stack] OBJ KEY
+}
+
 bool ElemOpEmitter::emitGet() {
   MOZ_ASSERT(state_ == State::Key);
 
   if (isIncDec() || isCompoundAssignment()) {
     if (!bce_->emit1(JSOp::ToPropertyKey)) {
       //            [stack] # if Super
       //            [stack] THIS KEY
       //            [stack] # otherwise
       //            [stack] OBJ KEY
       return false;
     }
   }
+
+  if (!emitPrivateGuard()) {
+    return false;
+  }
+
   if (isSuper()) {
     if (!bce_->emitSuperBase()) {
       //            [stack] THIS? THIS KEY SUPERBASE
       return false;
     }
   }
   if (isIncDec() || isCompoundAssignment()) {
     if (isSuper()) {
@@ -88,18 +122,16 @@ bool ElemOpEmitter::emitGet() {
     }
   }
 
   JSOp op;
   if (isSuper()) {
     op = JSOp::GetElemSuper;
   } else if (isCall()) {
     op = JSOp::CallElem;
-  } else if (isPrivateGet()) {
-    op = JSOp::GetPrivateElem;
   } else {
     op = JSOp::GetElem;
   }
   if (!bce_->emitElemOpBase(op, ShouldInstrument::Yes)) {
     //              [stack] # if Get
     //              [stack] ELEM
     //              [stack] # if Call
     //              [stack] THIS ELEM
@@ -123,16 +155,19 @@ bool ElemOpEmitter::emitGet() {
 }
 
 bool ElemOpEmitter::prepareForRhs() {
   MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
   MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key);
   MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
 
   if (isSimpleAssignment() || isPropInit()) {
+    if (!emitPrivateGuard()) {
+      return false;
+    }
     // For CompoundAssignment, SuperBase is already emitted by emitGet.
     if (isSuper()) {
       if (!bce_->emitSuperBase()) {
         //          [stack] THIS KEY SUPERBASE
         return false;
       }
     }
   }
@@ -176,16 +211,17 @@ bool ElemOpEmitter::emitDelete() {
 
     // 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)) {
       //            [stack] THIS
       return false;
     }
   } else {
+    MOZ_ASSERT(!isPrivate());
     JSOp op = bce_->sc->strict() ? JSOp::StrictDelElem : JSOp::DelElem;
     if (!bce_->emitElemOpBase(op)) {
       // SUCCEEDED
       return false;
     }
   }
 
 #ifdef DEBUG
@@ -196,23 +232,21 @@ bool ElemOpEmitter::emitDelete() {
 
 bool ElemOpEmitter::emitAssignment() {
   MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
   MOZ_ASSERT(state_ == State::Rhs);
 
   MOZ_ASSERT_IF(isPropInit(), !isSuper());
 
   JSOp setOp = isPropInit()
-                   ? (isPrivate() ? JSOp::InitPrivateElem : JSOp::InitElem)
+                   ? JSOp::InitElem
                    : isSuper() ? bce_->sc->strict() ? JSOp::StrictSetElemSuper
                                                     : JSOp::SetElemSuper
-                               : isPrivate()
-                                     ? JSOp::SetPrivateElem
-                                     : bce_->sc->strict() ? JSOp::StrictSetElem
-                                                          : JSOp::SetElem;
+                               : bce_->sc->strict() ? JSOp::StrictSetElem
+                                                    : JSOp::SetElem;
   if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) {
     //              [stack] ELEM
     return false;
   }
 
 #ifdef DEBUG
   state_ = State::Assignment;
 #endif
@@ -249,19 +283,17 @@ bool ElemOpEmitter::emitIncDec() {
   if (!bce_->emit1(incOp)) {
     //              [stack] ... N+1
     return false;
   }
 
   JSOp setOp =
       isSuper()
           ? (bce_->sc->strict() ? JSOp::StrictSetElemSuper : JSOp::SetElemSuper)
-          : isPrivate()
-                ? JSOp::SetPrivateElem
-                : (bce_->sc->strict() ? JSOp::StrictSetElem : JSOp::SetElem);
+          : (bce_->sc->strict() ? JSOp::StrictSetElem : JSOp::SetElem);
   if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) {
     //              [stack] N? N+1
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOp::Pop)) {
       //            [stack] N
       return false;
--- a/js/src/frontend/ElemOpEmitter.h
+++ b/js/src/frontend/ElemOpEmitter.h
@@ -148,18 +148,16 @@ class MOZ_STACK_CLASS ElemOpEmitter {
   //           +------------------------------------------------+
   //           |                                                |
   // +-------+ | prepareForObj +-----+ prepareForKey +-----+    |
   // | Start |-+-------------->| Obj |-------------->| Key |-+  |
   // +-------+                 +-----+               +-----+ |  |
   //                                                         |  |
   // +-------------------------------------------------------+  |
   // |                                                          |
-  // |                                                          |
-  // |                                                          |
   // | [Get]                                                    |
   // | [Call]                                                   |
   // |   emitGet +-----+                                        |
   // +---------->| Get |                                        |
   // |           +-----+                                        |
   // |                                                          |
   // | [Delete]                                                 |
   // |   emitDelete +--------+                                  |
@@ -263,14 +261,19 @@ class MOZ_STACK_CLASS ElemOpEmitter {
   MOZ_MUST_USE bool prepareForRhs();
   MOZ_MUST_USE bool skipObjAndKeyAndRhs();
 
   MOZ_MUST_USE bool emitDelete();
 
   MOZ_MUST_USE bool emitAssignment();
 
   MOZ_MUST_USE bool emitIncDec();
+
+ private:
+  // When we have private names, we may need to emit a CheckPrivateField
+  // op to potentially throw errors where required.
+  MOZ_MUST_USE bool emitPrivateGuard();
 };
 
 } /* namespace frontend */
 }  // namespace js
 
 #endif /* frontend_ElemOpEmitter_h */
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -7758,21 +7758,21 @@ GeneralParser<ParseHandler, Unit>::field
 
     propAssignFieldAccess = handler_.newPropertyByValue(
         propAssignThis, fieldKeyValue, wholeInitializerPos.end);
     if (!propAssignFieldAccess) {
       return null();
     }
   } else if (handler_.isPrivateName(propName)) {
     // It would be nice if we could tweak this here such that only if
-    // HasHeritage::Yes we end up emitting InitPrivateElem, but otherwise we
+    // HasHeritage::Yes we end up emitting CheckPrivateField, but otherwise we
     // emit InitElem -- this is an optimization to minimize HasOwn checks
     // in InitElem for classes without heritage.
     //
-    // Further tweaking would be to ultimately only do InitPrivateElem for the
+    // Further tweaking would be to ultimately only do CheckPrivateField for the
     // -first- field in a derived class, which would suffice to match the
     // semantic check.
 
     RootedPropertyName privateName(cx_, propAtom->asPropertyName());
     NameNodeType privateNameNode = privateNameReference(privateName);
     if (!privateNameNode) {
       return null();
     }
--- a/js/src/jit-test/tests/fields/private-field-basics.js
+++ b/js/src/jit-test/tests/fields/private-field-basics.js
@@ -1,10 +1,10 @@
 // |jit-test| --enable-private-fields;
-// Very basic InitPrivateElem, SetPrivateElem, GetPrivateElem testing
+
 
 class A {
   #x = 10
 
   x() {
     return this.#x;
   }
   ix() {
@@ -33,16 +33,24 @@ class A {
 
   static sgz() {
     return this.#z;
   }
 
   static ssz(o) {
     this.#z = o;
   }
+
+  static six(o) {
+    o.#x++;
+  }
+
+  static dix(o) {
+    o.#x--;
+  }
 };
 
 for (var i = 0; i < 1000; i++) {
   var a = new A();
   assertEq(a.x(), 10);
   a.ix();
   assertEq(a.x(), 11);
   assertEq(A.readx(a), 11);
@@ -70,19 +78,26 @@ function assertThrows(fun, errorType) {
     throw 'Expected error, but none was thrown';
   } catch (e) {
     if (!(e instanceof errorType)) {
       throw 'Wrong error type thrown';
     }
   }
 }
 
-assertThrows(() => A.readx(), TypeError);    // Undefined
-assertThrows(() => A.readx({}), TypeError);  // Random object
-assertThrows(() => A.readx(1), TypeError);   // Random primitive
+function testTypeErrors(v) {
+  assertThrows(() => A.readx(v), TypeError);  // Read value
+  assertThrows(() => A.six(v), TypeError);    // increment
+  assertThrows(() => A.dix(v), TypeError);    // decrement
+}
+
+testTypeErrors(undefined);  // Undefined
+testTypeErrors({});         // Random object
+testTypeErrors(1);          // Random primitive
+
 assertThrows(
     () => eval('class B extends class { #x; } { g() { return super.#x; } }'),
     SyntaxError);  // Access super.#private
 assertThrows(
     () => eval('class C { #x = 10; static #x = 14; }'),
     SyntaxError);  // Duplicate name declaration.
 assertThrows(
     () => eval('delete this.#x'),
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/fields/private-field-destructuring.js
@@ -0,0 +1,43 @@
+// |jit-test| --enable-private-fields;
+
+function assertThrows(fun, errorType) {
+  try {
+    fun();
+    throw 'Expected error, but none was thrown';
+  } catch (e) {
+    if (!(e instanceof errorType)) {
+      throw 'Wrong error type thrown';
+    }
+  }
+}
+
+class A {
+  #a;
+  #b;
+  #c;
+  #d;
+  #e;
+  static destructure(o, x) {
+    [o.#a, o.#b, o.#c, o.#d, ...o.#e] = x;
+  }
+
+  static get(o) {
+    return {a: o.#a, b: o.#b, c: o.#c, d: o.#d, e: o.#e};
+  }
+};
+
+for (var i = 0; i < 1000; i++) {
+  var a = new A();
+  A.destructure(a, [1, 2, 3, 4, 5]);
+  var res = A.get(a);
+  assertEq(res.a, 1);
+  assertEq(res.b, 2);
+  assertEq(res.c, 3);
+  assertEq(res.d, 4);
+  assertEq(res.e.length, 1);
+  assertEq(res.e[0], 5);
+
+  var obj = {};
+  assertThrows(() => A.destructure(obj, [1, 2, 3, 4, 5]), TypeError);
+  assertThrows(() => A.get(obj), TypeError);
+}
\ No newline at end of file
--- a/js/src/jit-test/tests/fields/private-field-details.js
+++ b/js/src/jit-test/tests/fields/private-field-details.js
@@ -1,10 +1,9 @@
 // |jit-test| --enable-private-fields;
-// Very basic InitPrivateElem, SetPrivateElem, GetPrivateElem testing
 
 var shouldBeThis;
 
 class A {
   #nullReturn = false;
   constructor(nullReturn) {
     this.#nullReturn = nullReturn;
   }
--- a/js/src/jit/BaselineCodeGen.cpp
+++ b/js/src/jit/BaselineCodeGen.cpp
@@ -3054,21 +3054,16 @@ bool BaselineCodeGen<Handler>::emit_Init
   }
 
   // Pop the rhs, so that the object is on the top of the stack.
   frame.pop();
   return true;
 }
 
 template <typename Handler>
-bool BaselineCodeGen<Handler>::emit_InitPrivateElem() {
-  return emit_InitElem();
-}
-
-template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_InitHiddenElem() {
   return emit_InitElem();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_MutateProto() {
   // Keep values on the stack for the decompiler.
   frame.syncStack(0);
@@ -3128,21 +3123,16 @@ bool BaselineCodeGen<Handler>::emit_GetE
   }
 
   // Mark R0 as pushed stack value.
   frame.push(R0);
   return true;
 }
 
 template <typename Handler>
-bool BaselineCodeGen<Handler>::emit_GetPrivateElem() {
-  return emit_GetElem();
-}
-
-template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_GetElemSuper() {
   // Store obj in the scratch slot.
   frame.storeStackValue(-1, frame.addressOfScratchValue(), R2);
   frame.pop();
 
   // Keep receiver and index in R0 and R1.
   frame.popRegsAndSync(2);
 
@@ -3184,21 +3174,16 @@ bool BaselineCodeGen<Handler>::emit_SetE
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_StrictSetElem() {
   return emit_SetElem();
 }
 
 template <typename Handler>
-bool BaselineCodeGen<Handler>::emit_SetPrivateElem() {
-  return emit_SetElem();
-}
-
-template <typename Handler>
 bool BaselineCodeGen<Handler>::emitSetElemSuper(bool strict) {
   // Incoming stack is |receiver, propval, obj, rval|. We need to shuffle
   // stack to leave rval when operation is complete.
 
   // Pop rval into R0, then load receiver into R1 and replace with rval.
   frame.popRegsAndSync(1);
   masm.loadValue(frame.addressOfStackValue(-3), R1);
   masm.storeValue(R0, frame.addressOfStackValue(-3));
@@ -3291,16 +3276,43 @@ bool BaselineCodeGen<Handler>::emit_HasO
   if (!emitNextIC()) {
     return false;
   }
 
   frame.push(R0);
   return true;
 }
 
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_CheckPrivateField() {
+  // Keep key and val on the stack.
+  frame.syncStack(0);
+  masm.loadValue(frame.addressOfStackValue(-2), R0);
+  masm.loadValue(frame.addressOfStackValue(-1), R1);
+
+  prepareVMCall();
+
+  // Key
+  pushArg(R1);
+  // val
+  pushArg(R0);
+  // pc
+  pushBytecodePCArg();
+
+  using Fn = bool (*)(JSContext*, jsbytecode*, HandleValue, HandleValue, bool*);
+  if (!callVM<Fn, CheckPrivateFieldOperation>()) {
+    return false;
+  }
+
+  masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R0);
+  frame.push(R0);
+
+  return true;
+}
+
 template <>
 bool BaselineCompilerCodeGen::tryOptimizeGetGlobalName() {
   PropertyName* name = handler.script()->getName(handler.pc());
 
   // These names are non-configurable on the global and cannot be shadowed.
   if (name == cx->names().undefined) {
     frame.push(UndefinedValue());
     return true;
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -319,22 +319,20 @@ bool ICScript::initICEntries(JSContext* 
       case JSOp::NewInit: {
         ICStub* stub = alloc.newStub<ICNewObject_Fallback>(Kind::NewObject);
         if (!addIC(loc, stub)) {
           return false;
         }
         break;
       }
       case JSOp::InitElem:
-      case JSOp::InitPrivateElem:
       case JSOp::InitHiddenElem:
       case JSOp::InitElemArray:
       case JSOp::InitElemInc:
       case JSOp::SetElem:
-      case JSOp::SetPrivateElem:
       case JSOp::StrictSetElem: {
         ICStub* stub = alloc.newStub<ICSetElem_Fallback>(Kind::SetElem);
         if (!addIC(loc, stub)) {
           return false;
         }
         break;
       }
       case JSOp::InitProp:
@@ -366,17 +364,16 @@ bool ICScript::initICEntries(JSContext* 
       case JSOp::GetPropSuper: {
         ICStub* stub = alloc.newStub<ICGetProp_Fallback>(Kind::GetPropSuper);
         if (!addIC(loc, stub)) {
           return false;
         }
         break;
       }
       case JSOp::GetElem:
-      case JSOp::GetPrivateElem:
       case JSOp::CallElem: {
         ICStub* stub = alloc.newStub<ICGetElem_Fallback>(Kind::GetElem);
         if (!addIC(loc, stub)) {
           return false;
         }
         break;
       }
       case JSOp::GetElemSuper: {
@@ -1839,142 +1836,38 @@ static bool TryAttachGetPropStub(const c
       case AttachDecision::Deferred:
         MOZ_ASSERT_UNREACHABLE("No deferred GetProp stubs");
         break;
     }
   }
   return attached;
 }
 
-// This is a separate function rather than recycling what already exists in
-// the SetPrivateElementOperation etc so that we can use HasOwnDataPropertyPure
-// to avoid side-effects.
-//
-// This is a conservative allow-list, to ensure safety.
-static MOZ_ALWAYS_INLINE bool CanAttachPrivateSetGetIC(JSContext* cx,
-                                                       HandleObject obj,
-                                                       HandleId id) {
-  // Only allow plain objects.
-  if (!obj->is<PlainObject>()) {
-    return false;
-  }
-
-  bool hasOwn = false;
-  if (!HasOwnDataPropertyPure(cx, obj, id, &hasOwn)) {
-    return false;
-  }
-
-  // Set/Get require the prop already exist. This will have to be rethought for
-  // getters and setters when they are implemented.
-  return hasOwn;
-}
-
-static void VerifyPrivateElemThrow(JSContext* cx, bool attached,
-                                   bool mayThrow) {
-  // If we attached a stub but threw for OOM, that doesn't mean that the stub
-  // attached is broken; so the below checks don't apply.
-  //
-  // Setters and Getters will require changing this when they are implemented.
-  bool throwingOOM =
-      cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed();
-  if (!throwingOOM) {
-    // If attached, then we attached a broken stub.
-    MOZ_RELEASE_ASSERT(!attached);
-    // if !mayThrow, then the throw check was insufficiently stringent,
-    // and should we expand the set of ICs, we could get into a situation
-    // where we -could- attach a broken stub.
-    MOZ_RELEASE_ASSERT(mayThrow);
-  }
-}
-
-bool DoPrivateGetElemFallback(JSContext* cx, BaselineFrame* frame,
-                              ICGetElem_Fallback* stub, HandleValue lhs,
-                              HandleValue rhs, MutableHandleValue res) {
-  RootedScript script(cx, frame->script());
-  jsbytecode* pc = stub->icEntry()->pc(frame->script());
-
-  MOZ_ASSERT(JSOp(*pc) == JSOp::GetPrivateElem);
-  MOZ_ASSERT(rhs.isSymbol());
-  MOZ_ASSERT(rhs.toSymbol()->isPrivateName());
-
-  // To simplify compatibility with our IC implementation below, ensure that
-  // the PrivateElem operations throw their potential exceptions, before we
-  // attach a stub.
-  //
-  // Accessing a private field that does not exist throws a TypeError. We
-  // handle that case in the fallback stub by calling
-  // GetPrivateElemOperation. We have to ensure that GetPropIRGenerator does
-  // not attach a stub in this situation, because that might prevent us from
-  // reaching the fallback in the future.
-  //
-  // Accessing an existing private field is just like accessing a regular
-  // property. Once we have verified that the field exists on the object, we
-  // can use our regular guards, so regular stubs apply so long as we
-  // ensure we never throw.
-
-  RootedId id(cx);
-  if (!ToPropertyKey(cx, rhs, &id)) {
-    return false;
-  }
-
-  // LHS must be an object.
-  if (!lhs.isObject()) {
-    ReportNotObject(cx, lhs);
-    return false;
-  }
-
-  RootedObject obj(cx, &lhs.toObject());
-  bool mayThrow = !CanAttachPrivateSetGetIC(cx, obj, id);
-
-  // We don't handle magic arguments here.
-  MOZ_ASSERT(!lhs.isMagic(JS_OPTIMIZED_ARGUMENTS));
-
-  bool attached = false;
-  if (!mayThrow) {
-    attached = TryAttachGetPropStub("GetPrivateElem", cx, frame, stub,
-                                    CacheKind::GetElem, lhs, rhs, lhs);
-  }
-
-  if (!GetPrivateElemOperation(cx, pc, lhs, rhs, res)) {
-    VerifyPrivateElemThrow(cx, attached, mayThrow);
-    return false;
-  }
-
-  return true;
-}
-
 //
 // GetElem_Fallback
 //
-// - GetPrivateElem shares this trampoline target for now.
 
 bool DoGetElemFallback(JSContext* cx, BaselineFrame* frame,
                        ICGetElem_Fallback* stub, HandleValue lhs,
                        HandleValue rhs, MutableHandleValue res) {
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc = stub->icEntry()->pc(frame->script());
 
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "GetElem(%s)", CodeName(op));
 
-  MOZ_ASSERT(op == JSOp::GetElem || op == JSOp::GetPrivateElem ||
-             op == JSOp::CallElem);
-
-  if (op == JSOp::GetPrivateElem) {
-    return DoPrivateGetElemFallback(cx, frame, stub, lhs, rhs, res);
-  }
+  MOZ_ASSERT(op == JSOp::GetElem || op == JSOp::CallElem);
 
   // Don't pass lhs directly, we need it when generating stubs.
   RootedValue lhsCopy(cx, lhs);
 
   bool isOptimizedArgs = false;
   if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
-    MOZ_ASSERT(op != JSOp::GetPrivateElem);
     // Handle optimized arguments[i] access.
     if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res,
                                    &isOptimizedArgs)) {
       return false;
     }
     if (isOptimizedArgs) {
       if (!TypeMonitorResult(cx, stub, frame, script, pc, res)) {
         return false;
@@ -2189,19 +2082,18 @@ bool DoSetElemFallback(JSContext* cx, Ba
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   RootedScript outerScript(cx, script);
   jsbytecode* pc = stub->icEntry()->pc(script);
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "SetElem(%s)", CodeName(JSOp(*pc)));
 
-  MOZ_ASSERT(op == JSOp::SetElem || op == JSOp::SetPrivateElem ||
-             op == JSOp::StrictSetElem || op == JSOp::InitElem ||
-             op == JSOp::InitPrivateElem || op == JSOp::InitHiddenElem ||
+  MOZ_ASSERT(op == JSOp::SetElem || op == JSOp::StrictSetElem ||
+             op == JSOp::InitElem || op == JSOp::InitHiddenElem ||
              op == JSOp::InitElemArray || op == JSOp::InitElemInc);
 
   int objvIndex = -3;
   RootedObject obj(
       cx, ToObjectFromStackForPropertyAccess(cx, objv, objvIndex, index));
   if (!obj) {
     return false;
   }
@@ -2211,32 +2103,16 @@ bool DoSetElemFallback(JSContext* cx, Ba
   if (!oldGroup) {
     return false;
   }
 
   // We cannot attach a stub if the operation executed after the stub
   // is attached may throw.
   bool mayThrow = false;
 
-  // Also used in the call to SetPrivateElementOperation below, so
-  // we declare and fill in here.
-  RootedId id(cx);
-  if (op == JSOp::InitPrivateElem || op == JSOp::SetPrivateElem) {
-    if (!ToPropertyKey(cx, index, &id)) {
-      return false;
-    }
-
-    if (op == JSOp::InitPrivateElem) {
-      mayThrow = !CanAttachPrivateInitIC(cx, obj, id);
-    } else {
-      MOZ_ASSERT(op == JSOp::SetPrivateElem);
-      mayThrow = !CanAttachPrivateSetGetIC(cx, obj, id);
-    }
-  }
-
   DeferType deferType = DeferType::None;
   bool attached = false;
 
   if (stub->state().maybeTransition()) {
     stub->discardStubs(cx, frame->invalidationScript());
   }
 
   if (stub->state().canAttachStub() && !mayThrow) {
@@ -2291,26 +2167,16 @@ bool DoSetElemFallback(JSContext* cx, Ba
                                 rhs)) {
       return false;
     }
   } else if (op == JSOp::InitElemInc) {
     if (!InitArrayElemOperation(cx, pc, obj.as<ArrayObject>(), index.toInt32(),
                                 rhs)) {
       return false;
     }
-  } else if (op == JSOp::InitPrivateElem) {
-    if (!InitPrivateElemOperation(cx, pc, obj, index, rhs)) {
-      VerifyPrivateElemThrow(cx, attached, mayThrow);
-      return false;
-    }
-  } else if (op == JSOp::SetPrivateElem) {
-    if (!SetPrivateElementOperation(cx, obj, id, rhs, objv)) {
-      VerifyPrivateElemThrow(cx, attached, mayThrow);
-      return false;
-    }
   } else {
     if (!SetObjectElement(cx, obj, index, rhs, objv,
                           JSOp(*pc) == JSOp::StrictSetElem, script, pc)) {
       return false;
     }
   }
 
   // Don't try to attach stubs that wish to be hidden. We don't know how to
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -584,22 +584,16 @@ static bool IsCacheableNoProperty(JSCont
   }
 
   // If we're doing a name lookup, we have to throw a ReferenceError.
   // Note that Ion does not generate idempotent caches for JSOp::GetBoundName.
   if (pc && JSOp(*pc) == JSOp::GetBoundName) {
     return false;
   }
 
-  // If we don't have the private field already (which we know from the
-  // if (shape) check above, we're going to throw so don't attach.
-  if (pc && JSOp(*pc) == JSOp::GetPrivateElem) {
-    return false;
-  }
-
   return CheckHasNoSuchProperty(cx, obj, id);
 }
 
 static NativeGetPropCacheability CanAttachNativeGetProp(
     JSContext* cx, HandleObject obj, HandleId id,
     MutableHandleNativeObject holder, MutableHandleShape shape, jsbytecode* pc,
     GetPropertyResultFlags resultFlags) {
   MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
@@ -1703,22 +1697,16 @@ AttachDecision GetPropIRGenerator::tryAt
 AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
                                                   ObjOperandId objId,
                                                   HandleId id) {
   ProxyStubType type = GetProxyStubType(cx_, obj, id);
   if (type == ProxyStubType::None) {
     return AttachDecision::NoAction;
   }
 
-  // Don't attach a proxy stub for private fields, as they
-  // may throw.
-  if (JSOp(*pc_) == JSOp::GetPrivateElem) {
-    return AttachDecision::NoAction;
-  }
-
   // The proxy stubs don't currently support |super| access.
   if (isSuper()) {
     return AttachDecision::NoAction;
   }
 
   if (mode_ == ICState::Mode::Megamorphic) {
     return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
   }
@@ -4407,18 +4395,17 @@ AttachDecision SetPropIRGenerator::tryAt
   trackAttached("ProxyElement");
   return AttachDecision::Attach;
 }
 
 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement(
     HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
   MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
 
-  if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem ||
-      IsPrivateElemOp(JSOp(*pc_))) {
+  if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) {
     return AttachDecision::NoAction;
   }
 
   // The generic proxy stubs are faster.
   if (obj->is<ProxyObject>()) {
     return AttachDecision::NoAction;
   }
 
@@ -4501,18 +4488,19 @@ bool SetPropIRGenerator::canAttachAddSlo
     }
     if (prop) {
       return false;
     }
   }
 
   // Object must be extensible, or we must be initializing a private
   // elem.
-  bool isInitPrivateElem = JSOp(*pc_) == JSOp::InitPrivateElem;
-  bool canAddNewProperty = obj->nonProxyIsExtensible() || isInitPrivateElem;
+  bool canAddNewProperty =
+      obj->nonProxyIsExtensible() ||
+      (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id)->isPrivateName());
   if (!canAddNewProperty) {
     return false;
   }
 
   // Also watch out for addProperty hooks. Ignore the Array addProperty hook,
   // because it doesn't do anything for non-index properties.
   DebugOnly<uint32_t> index;
   MOZ_ASSERT_IF(obj->is<ArrayObject>(), !IdIsIndex(id, &index));
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2448,19 +2448,17 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
     case JSOp::SetIntrinsic:
     case JSOp::ThrowMsg:
       // === !! WARNING WARNING WARNING !! ===
       // Do you really want to sacrifice performance by not implementing this
       // operation in the optimizing compiler?
       break;
 
     // Private Fields
-    case JSOp::InitPrivateElem:
-    case JSOp::GetPrivateElem:
-    case JSOp::SetPrivateElem:
+    case JSOp::CheckPrivateField:
       break;
 
     case JSOp::ForceInterpreter:
       // Intentionally not implemented.
       break;
   }
 
 #ifdef DEBUG
--- a/js/src/jit/VMFunctionList-inl.h
+++ b/js/src/jit/VMFunctionList-inl.h
@@ -78,16 +78,17 @@ namespace jit {
   _(CallNativeGetterByValue, js::jit::CallNativeGetterByValue)                 \
   _(CallNativeSetter, js::jit::CallNativeSetter)                               \
   _(CharCodeAt, js::jit::CharCodeAt)                                           \
   _(CheckClassHeritageOperation, js::CheckClassHeritageOperation)              \
   _(CheckGlobalOrEvalDeclarationConflicts,                                     \
     js::CheckGlobalOrEvalDeclarationConflicts)                                 \
   _(CheckOverRecursed, js::jit::CheckOverRecursed)                             \
   _(CheckOverRecursedBaseline, js::jit::CheckOverRecursedBaseline)             \
+  _(CheckPrivateFieldOperation, js::CheckPrivateFieldOperation)                \
   _(CloneRegExpObject, js::CloneRegExpObject)                                  \
   _(ConcatStrings, js::ConcatStrings<CanGC>)                                   \
   _(ConvertElementsToDoubles, js::ObjectElements::ConvertElementsToDoubles)    \
   _(CopyElementsForWrite, js::NativeObject::CopyElementsForWrite)              \
   _(CopyLexicalEnvironmentObject, js::jit::CopyLexicalEnvironmentObject)       \
   _(CreateAsyncFromSyncIterator, js::CreateAsyncFromSyncIterator)              \
   _(CreateBigIntFromInt64, js::jit::CreateBigIntFromInt64)                     \
   _(CreateBigIntFromUint64, js::jit::CreateBigIntFromUint64)                   \
--- a/js/src/jit/WarpBuilder.h
+++ b/js/src/jit/WarpBuilder.h
@@ -59,19 +59,17 @@ namespace jit {
   _(Gosub)                               \
   _(Retsub)                              \
   /* Misc */                             \
   _(DelName)                             \
   _(GetRval)                             \
   _(SetIntrinsic)                        \
   _(ThrowMsg)                            \
   /* Private Fields */                   \
-  _(InitPrivateElem)                     \
-  _(GetPrivateElem)                      \
-  _(SetPrivateElem)                      \
+  _(CheckPrivateField)                   \
   // === !! WARNING WARNING WARNING !! ===
   // Do you really want to sacrifice performance by not implementing this
   // operation in the optimizing compiler?
 
 class MIRGenerator;
 class MIRGraph;
 class WarpSnapshot;
 
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -175,18 +175,16 @@ bool BaseProxyHandler::setPrivate(JSCont
                                   HandleValue receiver,
                                   ObjectOpResult& result) const {
   assertEnteredPolicy(cx, proxy, id, SET);
   MOZ_ASSERT(JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id)->isPrivateName());
 
   // For BaseProxyHandler, private names are stored in the expando object.
   RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull());
 
-  // SetPrivateElementOperation checks for hasPrivate first, which ensures the
-  // expando exsists.
   MOZ_ASSERT(expando);
 
   Rooted<PropertyDescriptor> ownDesc(cx);
   if (!GetOwnPropertyDescriptor(cx, expando, id, &ownDesc)) {
     return false;
   }
   ownDesc.assertCompleteIfFound();
 
--- a/js/src/vm/BytecodeFormatFlags.h
+++ b/js/src/vm/BytecodeFormatFlags.h
@@ -29,17 +29,17 @@ enum {
   JOF_OBJECT = 15,      /* uint32_t object index */
   JOF_REGEXP = 16,      /* uint32_t regexp index */
   JOF_DOUBLE = 17,      /* inline DoubleValue */
   JOF_SCOPE = 18,       /* uint32_t scope index */
   JOF_ICINDEX = 19,     /* uint32_t IC index */
   JOF_LOOPHEAD = 20,    /* JSOp::LoopHead, combines JOF_ICINDEX and JOF_UINT8 */
   JOF_BIGINT = 21,      /* uint32_t index for BigInt value */
   JOF_CLASS_CTOR = 22,  /* uint32_t atom index, sourceStart, sourceEnd */
-  // (23 is unused.)
+  JOF_TWO_UINT8 = 23,   /* A pair of unspecified uint8_t arguments */
   JOF_TYPEMASK = 0x001f, /* mask for above immediate types */
 
   JOF_NAME = 1 << 5,     /* name operation */
   JOF_PROP = 2 << 5,     /* obj.prop operation */
   JOF_ELEM = 3 << 5,     /* obj[index] operation */
   JOF_MODEMASK = 3 << 5, /* mask for above addressing modes */
 
   JOF_PROPSET = 1 << 7,  /* property/element/name set operation */
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -1580,16 +1580,28 @@ static unsigned Disassemble1(JSContext* 
         return 0;
       }
       if (!sp->jsprintf(" %s (off: %u-%u)", bytes.get(), classStartOffset,
                         classEndOffset)) {
         return 0;
       }
       break;
     }
+    case JOF_TWO_UINT8: {
+      int one = (int)GET_UINT8(pc);
+      int two = (int)GET_UINT8(pc + 1);
+
+      if (!sp->jsprintf(" %d", one)) {
+        return 0;
+      }
+      if (!sp->jsprintf(" %d", two)) {
+        return 0;
+      }
+      break;
+    }
 
     case JOF_ARGC:
     case JOF_UINT16:
       i = (int)GET_UINT16(pc);
       goto print_int;
 
     case JOF_RESUMEINDEX:
     case JOF_UINT24:
--- a/js/src/vm/BytecodeUtil.h
+++ b/js/src/vm/BytecodeUtil.h
@@ -493,21 +493,16 @@ inline bool IsGlobalOp(JSOp op) { return
 inline bool IsPropertySetOp(JSOp op) {
   return CodeSpec(op).format & JOF_PROPSET;
 }
 
 inline bool IsPropertyInitOp(JSOp op) {
   return CodeSpec(op).format & JOF_PROPINIT;
 }
 
-inline bool IsPrivateElemOp(JSOp op) {
-  return op == JSOp::SetPrivateElem || op == JSOp::InitPrivateElem ||
-         op == JSOp::GetPrivateElem;
-}
-
 inline bool IsLooseEqualityOp(JSOp op) {
   return op == JSOp::Eq || op == JSOp::Ne;
 }
 
 inline bool IsStrictEqualityOp(JSOp op) {
   return op == JSOp::StrictEq || op == JSOp::StrictNe;
 }
 
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -11,16 +11,17 @@
 
 #include "jsnum.h"
 
 #include "jit/Ion.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/BytecodeUtil.h"  // JSDVG_SEARCH_STACK
 #include "vm/Realm.h"
 #include "vm/SharedStencil.h"  // GCThingIndex
+#include "vm/ThrowMsgKind.h"
 
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/GlobalObject-inl.h"
 #include "vm/JSAtom-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/ObjectOperations-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/StringType-inl.h"
@@ -534,53 +535,16 @@ static MOZ_ALWAYS_INLINE bool GetElemOpt
     JSScript::argumentsOptimizationFailed(cx, script);
 
     lref.set(ObjectValue(frame.argsObj()));
   }
 
   return true;
 }
 
-static MOZ_ALWAYS_INLINE bool GetPrivateElemOperation(JSContext* cx,
-                                                      jsbytecode* pc,
-                                                      HandleValue lhsValue,
-                                                      HandleValue key,
-                                                      MutableHandleValue res) {
-  // Private names represented as PrivateSymbol properties on objects.
-  MOZ_ASSERT(key.isSymbol());
-  MOZ_ASSERT(key.toSymbol()->isPrivateName());
-
-  RootedId id(cx);
-  if (!ToPropertyKey(cx, key, &id)) {
-    return false;
-  }
-
-  // LHS must be an object.
-  if (!lhsValue.isObject()) {
-    ReportNotObject(cx, lhsValue);
-    return false;
-  }
-
-  RootedObject obj(cx, &lhsValue.toObject());
-
-  // Check obj has required property already.
-  bool hasField = false;
-  if (!HasOwnProperty(cx, obj, id, &hasField)) {
-    return false;
-  }
-
-  if (!hasField) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_UNDECLARED_PRIVATE);
-    return false;
-  }
-
-  return GetProperty(cx, obj, obj, id, res);
-}
-
 static MOZ_ALWAYS_INLINE bool GetElementOperationWithStackIndex(
     JSContext* cx, JSOp op, HandleValue lref, int lrefIndex, HandleValue rref,
     MutableHandleValue res) {
   MOZ_ASSERT(op == JSOp::GetElem || op == JSOp::CallElem);
 
   uint32_t index;
   if (lref.isString() && IsDefinitelyIndex(rref, &index)) {
     JSString* str = lref.toString();
@@ -626,70 +590,79 @@ static MOZ_ALWAYS_INLINE bool InitElemOp
   MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE));
 
   RootedId id(cx);
   if (!ToPropertyKey(cx, idval, &id)) {
     return false;
   }
 
   unsigned flags = GetInitDataPropAttrs(JSOp(*pc));
+  if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id)->isPrivateName()) {
+    // Clear enumerate flag off of private names.
+    flags &= ~JSPROP_ENUMERATE;
+  }
   return DefineDataProperty(cx, obj, id, val, flags);
 }
 
-static MOZ_ALWAYS_INLINE bool InitPrivateElemOperation(JSContext* cx,
-                                                       jsbytecode* pc,
-                                                       HandleObject obj,
-                                                       HandleValue idval,
-                                                       HandleValue val) {
-  MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE));
+static inline void GetCheckPrivateFieldOperands(jsbytecode* pc,
+                                                ThrowCondition* throwCondition,
+                                                ThrowMsgKind* throwKind) {
+  static_assert(sizeof(ThrowCondition) == sizeof(uint8_t));
+  static_assert(sizeof(ThrowMsgKind) == sizeof(uint8_t));
+
+  MOZ_ASSERT(JSOp(*pc) == JSOp::CheckPrivateField);
+  uint8_t throwConditionByte = GET_UINT8(pc);
+  uint8_t throwKindByte = GET_UINT8(pc + 1);
+
+  *throwCondition = static_cast<ThrowCondition>(throwConditionByte);
+  *throwKind = static_cast<ThrowMsgKind>(throwKindByte);
+
+  MOZ_ASSERT(*throwCondition == ThrowCondition::ThrowHas ||
+             *throwCondition == ThrowCondition::ThrowHasNot ||
+             *throwCondition == ThrowCondition::NoThrow);
 
-  // Private names represented as PrivateSymbol properties on objects.
+  MOZ_ASSERT(*throwKind == ThrowMsgKind::PrivateDoubleInit ||
+             *throwKind == ThrowMsgKind::MissingPrivateOnGet ||
+             *throwKind == ThrowMsgKind::MissingPrivateOnSet);
+}
+
+static MOZ_ALWAYS_INLINE bool CheckPrivateFieldOperation(JSContext* cx,
+                                                         jsbytecode* pc,
+                                                         HandleValue val,
+                                                         HandleValue idval,
+                                                         bool* result) {
+  // Result had better not be a nullptr.
+  MOZ_ASSERT(result);
+
+  ThrowCondition condition;
+  ThrowMsgKind msgKind;
+  GetCheckPrivateFieldOperands(pc, &condition, &msgKind);
+
   MOZ_ASSERT(idval.isSymbol());
   MOZ_ASSERT(idval.toSymbol()->isPrivateName());
 
-  RootedId id(cx);
-  if (!ToPropertyKey(cx, idval, &id)) {
-    return false;
-  }
-
-  bool hasField = false;
-  if (!HasOwnProperty(cx, obj, id, &hasField)) {
+  if (!HasOwnProperty(cx, val, idval, result)) {
     return false;
   }
 
-  // PrivateFieldAdd: Step 4: We must throw a type error if obj already has
-  // this property.
-  if (hasField) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_PRIVATE_FIELD_DOUBLE);
-    return false;
-  }
-  unsigned flags = GetInitDataPropAttrs(JSOp(*pc));
-  return DefineDataProperty(cx, obj, id, val, flags);
-}
-
-static MOZ_ALWAYS_INLINE bool SetPrivateElementOperation(JSContext* cx,
-                                                         HandleObject obj,
-                                                         HandleId id,
-                                                         HandleValue value,
-                                                         HandleValue receiver) {
-  bool hasField = false;
-  if (!HasOwnProperty(cx, obj, id, &hasField)) {
-    return false;
+  if (condition == ThrowCondition::NoThrow) {
+    // Result was set directly, nothing more do do, return.
+    return true;
   }
 
-  if (!hasField) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_UNDECLARED_PRIVATE);
-    return false;
+  if ((condition == ThrowCondition::ThrowHasNot && *result) ||
+      (condition == ThrowCondition::ThrowHas && !*result)) {
+    // Didn't meet the throw condition
+    return true;
   }
 
-  ObjectOpResult result;
-  return SetProperty(cx, obj, id, value, receiver, result) &&
-         result.checkStrictModeError(cx, obj, id, true);
+  // Throw!
+  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                            ThrowMsgKindToErrNum(msgKind));
+  return false;
 }
 
 static MOZ_ALWAYS_INLINE bool InitArrayElemOperation(JSContext* cx,
                                                      jsbytecode* pc,
                                                      HandleArrayObject arr,
                                                      uint32_t index,
                                                      HandleValue val) {
   JSOp op = JSOp(*pc);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2460,16 +2460,30 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         goto error;
       }
 
       REGS.sp--;
       REGS.sp[-1].setBoolean(found);
     }
     END_CASE(HasOwn)
 
+    CASE(CheckPrivateField) {
+      /* Load the object being initialized into lval/val. */
+      HandleValue val = REGS.stackHandleAt(-2);
+      HandleValue idval = REGS.stackHandleAt(-1);
+
+      bool result = false;
+      if (!CheckPrivateFieldOperation(cx, REGS.pc, val, idval, &result)) {
+        goto error;
+      }
+
+      PUSH_BOOLEAN(result);
+    }
+    END_CASE(CheckPrivateField)
+
     CASE(Iter) {
       MOZ_ASSERT(REGS.stackDepth() >= 1);
       HandleValue val = REGS.stackHandleAt(-1);
       JSObject* iter = ValueToIterator(cx, val);
       if (!iter) {
         goto error;
       }
       REGS.sp[-1].setObject(*iter);
@@ -3105,31 +3119,16 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         }
       }
 
       JitScript::MonitorBytecodeType(cx, script, REGS.pc, res);
       REGS.sp--;
     }
     END_CASE(GetElem)
 
-    CASE(GetPrivateElem) {
-      int lvalIndex = -2;
-      MutableHandleValue lval = REGS.stackHandleAt(lvalIndex);
-      HandleValue rval = REGS.stackHandleAt(-1);
-      MutableHandleValue res = REGS.stackHandleAt(-2);
-
-      if (!GetPrivateElemOperation(cx, REGS.pc, lval, rval, res)) {
-        goto error;
-      }
-
-      JitScript::MonitorBytecodeType(cx, script, REGS.pc, res);
-      REGS.sp--;
-    }
-    END_CASE(GetPrivateElem)
-
     CASE(GetElemSuper) {
       ReservedRooted<Value> receiver(&rootValue1, REGS.sp[-3]);
       ReservedRooted<Value> rval(&rootValue0, REGS.sp[-2]);
       ReservedRooted<JSObject*> obj(&rootObject1, &REGS.sp[-1].toObject());
 
       MutableHandleValue res = REGS.stackHandleAt(-3);
 
       // Since we have asserted that obj has to be an object, it cannot be
@@ -3164,36 +3163,16 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
                                      JSOp(*REGS.pc) == JSOp::StrictSetElem)) {
         goto error;
       }
       REGS.sp[-3] = value;
       REGS.sp -= 2;
     }
     END_CASE(SetElem)
 
-    CASE(SetPrivateElem) {
-      int receiverIndex = -3;
-      HandleValue receiver = REGS.stackHandleAt(receiverIndex);
-      ReservedRooted<JSObject*> obj(&rootObject0);
-      obj = ToObjectFromStackForPropertyAccess(cx, receiver, receiverIndex,
-                                               REGS.stackHandleAt(-2));
-      if (!obj) {
-        goto error;
-      }
-      ReservedRooted<jsid> id(&rootId0);
-      FETCH_ELEMENT_ID(-2, id);
-      HandleValue value = REGS.stackHandleAt(-1);
-      if (!SetPrivateElementOperation(cx, obj, id, value, receiver)) {
-        goto error;
-      }
-      REGS.sp[-3] = value;
-      REGS.sp -= 2;
-    }
-    END_CASE(SetPrivateElem)
-
     CASE(SetElemSuper)
     CASE(StrictSetElemSuper) {
       static_assert(
           JSOpLength_SetElemSuper == JSOpLength_StrictSetElemSuper,
           "setelem-super and strictsetelem-super must be the same size");
 
       ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-4]);
       ReservedRooted<Value> index(&rootValue1, REGS.sp[-3]);
@@ -3390,18 +3369,18 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
           }
         }
 
         funScript = fun->nonLazyScript();
 
         if (!activation.pushInlineFrame(args, funScript, construct)) {
           goto error;
         }
-        leaveRealmGuard.release();  // We leave the callee's realm when we call
-                                    // popInlineFrame.
+        leaveRealmGuard.release();  // We leave the callee's realm when we
+                                    // call popInlineFrame.
       }
 
       SET_SCRIPT(REGS.fp()->script());
 
       {
         TraceLoggerEvent event(TraceLogger_Scripts, script);
         TraceLogStartEvent(logger, event);
         TraceLogStartEvent(logger, TraceLogger_Interpreter);
@@ -3752,18 +3731,18 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         JSOp next = JSOp(*GetNextPc(REGS.pc));
         MOZ_ASSERT(next == JSOp::CheckThis || next == JSOp::CheckReturn ||
                    next == JSOp::CheckThisReinit || next == JSOp::CheckLexical);
       }
 
       /*
        * Skip the same-compartment assertion if the local will be immediately
        * popped. We do not guarantee sync for dead locals when coming in from
-       * the method JIT, and a GetLocal followed by Pop is not considered to be
-       * a use of the variable.
+       * the method JIT, and a GetLocal followed by Pop is not considered to
+       * be a use of the variable.
        */
       if (JSOp(REGS.pc[JSOpLength_GetLocal]) != JSOp::Pop) {
         cx->debugOnlyCheck(REGS.sp[-1]);
       }
 #endif
     }
     END_CASE(GetLocal)
 
@@ -3791,19 +3770,19 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         goto error;
       }
     }
     END_CASE(DefLet)
 
     CASE(DefFun) {
       /*
        * A top-level function defined in Global or Eval code (see ECMA-262
-       * Ed. 3), or else a SpiderMonkey extension: a named function statement in
-       * a compound statement (not at the top statement level of global code, or
-       * at the top level of a function body).
+       * Ed. 3), or else a SpiderMonkey extension: a named function statement
+       * in a compound statement (not at the top statement level of global
+       * code, or at the top level of a function body).
        */
       ReservedRooted<JSFunction*> fun(&rootFunction0,
                                       &REGS.sp[-1].toObject().as<JSFunction>());
       if (!DefFunOperation(cx, script, REGS.fp()->environmentChain(), fun)) {
         goto error;
       }
       REGS.sp--;
     }
@@ -4062,31 +4041,16 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       if (!InitElemOperation(cx, REGS.pc, obj, id, val)) {
         goto error;
       }
 
       REGS.sp -= 2;
     }
     END_CASE(InitElem)
 
-    CASE(InitPrivateElem) {
-      MOZ_ASSERT(REGS.stackDepth() >= 3);
-      HandleValue val = REGS.stackHandleAt(-1);
-      HandleValue id = REGS.stackHandleAt(-2);
-
-      ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-3].toObject());
-
-      if (!InitPrivateElemOperation(cx, REGS.pc, obj, id, val)) {
-        goto error;
-      }
-
-      REGS.sp -= 2;
-    }
-    END_CASE(InitElem)
-
     CASE(InitElemArray) {
       MOZ_ASSERT(REGS.stackDepth() >= 2);
       HandleValue val = REGS.stackHandleAt(-1);
 
       ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
 
       uint32_t index = GET_UINT32(REGS.pc);
       if (!InitArrayElemOperation(cx, REGS.pc, obj.as<ArrayObject>(), index,
@@ -4124,18 +4088,18 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       /* Pop [exception or hole, retsub pc-index]. */
       Value rval, lval;
       POP_COPY_TO(rval);
       POP_COPY_TO(lval);
       MOZ_ASSERT(lval.isBoolean());
       if (lval.toBoolean()) {
         /*
          * Exception was pending during finally, throw it *before* we adjust
-         * pc, because pc indexes into script->trynotes.  This turns out not to
-         * be necessary, but it seems clearer.  And it points out a FIXME:
+         * pc, because pc indexes into script->trynotes.  This turns out not
+         * to be necessary, but it seems clearer.  And it points out a FIXME:
          * 350509, due to Igor Bukanov.
          */
         ReservedRooted<Value> v(&rootValue0, rval);
         cx->setPendingExceptionAndCaptureStack(v);
         goto error;
       }
 
       MOZ_ASSERT(rval.toInt32() >= 0);
@@ -5246,17 +5210,16 @@ unsigned js::GetInitDataPropAttrs(JSOp o
   switch (op) {
     case JSOp::InitProp:
     case JSOp::InitElem:
       return JSPROP_ENUMERATE;
     case JSOp::InitLockedProp:
       return JSPROP_PERMANENT | JSPROP_READONLY;
     case JSOp::InitHiddenProp:
     case JSOp::InitHiddenElem:
-    case JSOp::InitPrivateElem:
       // Non-enumerable, but writable and configurable
       return 0;
     default:;
   }
   MOZ_CRASH("Unknown data initprop");
 }
 
 static bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc,
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -930,28 +930,16 @@
      *   Category: Objects
      *   Type: Defining properties
      *   Operands:
      *   Stack: obj, id, val => obj
      */ \
     MACRO(InitElem, init_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \
     MACRO(InitHiddenElem, init_hidden_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \
     /*
-     * Define a private field on `obj` with property key `id` and value `val`.
-     *
-     * `obj` must be an object,
-     * `id` must be a private name.
-     *
-     *   Category: Objects
-     *   Type: Defining properties
-     *   Operands:
-     *   Stack: obj, id, val => obj
-     */ \
-    MACRO(InitPrivateElem, init_private_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \
-    /*
      * Define an accessor property on `obj` with the given `getter`.
      * `nameIndex` gives the property name.
      *
      * `obj` must be an object and `getter` must be a function.
      *
      * `JSOp::InitHiddenPropGetter` is the same but defines a non-enumerable
      * property, for getters in classes.
      *
@@ -1046,27 +1034,16 @@
      *   Category: Objects
      *   Type: Accessing properties
      *   Operands:
      *   Stack: obj, key => obj[key]
      */ \
     MACRO(GetElem, get_elem, NULL, 1, 2, 1, JOF_BYTE|JOF_ELEM|JOF_TYPESET|JOF_IC) \
     MACRO(CallElem, call_elem, NULL, 1, 2, 1, JOF_BYTE|JOF_ELEM|JOF_TYPESET|JOF_IC) \
     /*
-     * Get the value of the private field `obj.#key`.
-     *
-     * Throws a TypeError if #key isn't on obj.
-     *
-     *   Category: Objects
-     *   Type: Accessing properties
-     *   Operands:
-     *   Stack: obj, key => obj[key]
-     */ \
-    MACRO(GetPrivateElem, get_private_elem, NULL, 1, 2, 1, JOF_BYTE|JOF_ELEM|JOF_TYPESET|JOF_IC) \
-    /*
      * Push the value of `obj.length`.
      *
      * `nameIndex` must be the index of the atom `"length"`. This then behaves
      * exactly like `JSOp::GetProp`.
      *
      *   Category: Objects
      *   Type: Accessing properties
      *   Operands: uint32_t nameIndex
@@ -1121,24 +1098,16 @@
      *
      *   Category: Objects
      *   Type: Accessing properties
      *   Operands:
      *   Stack: obj, key, val => val
      */ \
     MACRO(StrictSetElem, strict_set_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPSET|JOF_CHECKSTRICT|JOF_IC) \
     /*
-     * Like `JSOp::SetStrictElem`, but for private names. throw a TypeError if the private name doesnt' exist.
-     *   Category: Objects
-     *   Type: Accessing properties
-     *   Operands:
-     *   Stack: obj, key, val => val
-     */ \
-    MACRO(SetPrivateElem, set_private_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPSET|JOF_CHECKSTRICT|JOF_IC) \
-    /*
      * Delete a property from `obj`. Push true on success, false if the
      * property existed but could not be deleted. This implements `delete
      * obj.name` in non-strict code.
      *
      * Throws if `obj` is null or undefined. Can call proxy traps.
      *
      * Implements: [`delete obj.propname`][1] step 5 in non-strict code.
      *
@@ -1198,16 +1167,34 @@
      *
      *   Category: Objects
      *   Type: Accessing properties
      *   Operands:
      *   Stack: id, obj => (obj.hasOwnProperty(id))
      */ \
     MACRO(HasOwn, has_own, NULL, 1, 2, 1, JOF_BYTE|JOF_IC) \
     /*
+     * Push a bool representing the presence of private field id on obj.
+     * May throw, depending on the ThrowCondition.
+     *
+     * Two arguments:
+     *   - throwCondition: One of the ThrowConditions defined in
+     *     ThrowMsgKind.h. Determines why (or if) this op will throw.
+     *   - msgKind: One of the ThrowMsgKinds defined in ThrowMsgKind.h, which
+     *     maps to one of the messages in js.msg. Note: It's not possible to
+     *     pass arguments to the message at the moment.
+     *
+     *   Category: Control flow
+     *   Category: Objects
+     *   Type: Accessing properties
+     *   Operands: ThrowCondition throwCondition, ThrowMsgKind msgKind
+     *   Stack: obj, key => obj, key, (obj.hasOwnProperty(id))
+     */ \
+    MACRO(CheckPrivateField, check_private_field, NULL, 3, 2, 3, JOF_TWO_UINT8|JOF_CHECKSTRICT) \
+    /*
      * Push the SuperBase of the method `callee`. The SuperBase is
      * `callee.[[HomeObject]].[[GetPrototypeOf]]()`, the object where `super`
      * property lookups should begin.
      *
      * `callee` must be a function that has a HomeObject that's an object,
      * typically produced by `JSOp::Callee` or `JSOp::EnvCallee`.
      *
      * Implements: [GetSuperBase][1], except that instead of the environment,
@@ -3682,16 +3669,18 @@
 
 // clang-format on
 
 /*
  * In certain circumstances it may be useful to "pad out" the opcode space to
  * a power of two.  Use this macro to do so.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(MACRO) \
+  MACRO(238)                                   \
+  MACRO(239)                                   \
   MACRO(240)                                   \
   MACRO(241)                                   \
   MACRO(242)                                   \
   MACRO(243)                                   \
   MACRO(244)                                   \
   MACRO(245)                                   \
   MACRO(246)                                   \
   MACRO(247)                                   \
--- a/js/src/vm/ThrowMsgKind.cpp
+++ b/js/src/vm/ThrowMsgKind.cpp
@@ -13,12 +13,17 @@
 JSErrNum js::ThrowMsgKindToErrNum(ThrowMsgKind kind) {
   switch (kind) {
     case ThrowMsgKind::AssignToCall:
       return JSMSG_ASSIGN_TO_CALL;
     case ThrowMsgKind::IteratorNoThrow:
       return JSMSG_ITERATOR_NO_THROW;
     case ThrowMsgKind::CantDeleteSuper:
       return JSMSG_CANT_DELETE_SUPER;
+    case ThrowMsgKind::PrivateDoubleInit:
+      return JSMSG_PRIVATE_FIELD_DOUBLE;
+    case ThrowMsgKind::MissingPrivateOnGet:
+    case ThrowMsgKind::MissingPrivateOnSet:
+      return JSMSG_UNDECLARED_PRIVATE;
   }
 
   MOZ_CRASH("Unexpected message kind");
 }
--- a/js/src/vm/ThrowMsgKind.h
+++ b/js/src/vm/ThrowMsgKind.h
@@ -11,16 +11,23 @@
 
 #include "jsfriendapi.h"  // JSErrNum
 
 namespace js {
 
 enum class ThrowMsgKind : uint8_t {
   AssignToCall,
   IteratorNoThrow,
-  CantDeleteSuper
+  CantDeleteSuper,
+  // Private Fields:
+  PrivateDoubleInit,
+  MissingPrivateOnGet,
+  MissingPrivateOnSet,
 };
 
 JSErrNum ThrowMsgKindToErrNum(ThrowMsgKind kind);
 
+// Used for CheckPrivateField
+enum class ThrowCondition : uint8_t { ThrowHas, ThrowHasNot, NoThrow };
+
 }  // namespace js
 
 #endif /* vm_ThrowMsgKind_h */