Bug 1385215 part 2 - Inline Object.prototype.toString in Ion. r=evilpie
authorJan de Mooij <jdemooij@mozilla.com>
Mon, 31 Jul 2017 14:12:49 +0200
changeset 423176 aa3fa3b4af7229fb3dcf1044bcfd1fa283c7caee
parent 423175 433a0c6cef710274dc82d1f2b30cb378d53b3b6e
child 423177 fca89bbfc8970ce6a336113d7b5fc4f28d1d029e
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie
bugs1385215
milestone56.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 1385215 part 2 - Inline Object.prototype.toString in Ion. r=evilpie
js/src/builtin/Object.cpp
js/src/builtin/Object.h
js/src/jit-test/tests/ion/object-prototype-tostring.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/InlinableNatives.h
js/src/jit/IonBuilder.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -483,16 +483,64 @@ GetBuiltinTagSlow(JSContext* cx, HandleO
                 return true;
             }
         }
         builtinTag.set(nullptr);
         return true;
     }
 }
 
+static MOZ_ALWAYS_INLINE JSString*
+GetBuiltinTagFast(JSObject* obj, const Class* clasp, JSContext* cx)
+{
+    MOZ_ASSERT(clasp == obj->getClass());
+    MOZ_ASSERT(!clasp->isProxy());
+
+    // Optimize the non-proxy case to bypass GetBuiltinClass.
+    if (clasp == &PlainObject::class_ || clasp == &UnboxedPlainObject::class_) {
+        // This is not handled by GetBuiltinTagSlow, but this case is by far
+        // the most common so we optimize it here.
+        return cx->names().objectObject;
+    }
+
+    if (clasp == &ArrayObject::class_ || clasp == &UnboxedArrayObject::class_)
+        return cx->names().objectArray;
+
+    if (clasp == &JSFunction::class_)
+        return cx->names().objectFunction;
+
+    if (clasp == &StringObject::class_)
+        return cx->names().objectString;
+
+    if (clasp == &NumberObject::class_)
+        return cx->names().objectNumber;
+
+    if (clasp == &BooleanObject::class_)
+        return cx->names().objectBoolean;
+
+    if (clasp == &DateObject::class_)
+        return cx->names().objectDate;
+
+    if (clasp == &RegExpObject::class_)
+        return cx->names().objectRegExp;
+
+    if (obj->is<ArgumentsObject>())
+        return cx->names().objectArguments;
+
+    if (obj->is<ErrorObject>())
+        return cx->names().objectError;
+
+    if (obj->isCallable() && !obj->getClass()->isDOMClass()) {
+        // Non-standard: Prevent <object> from showing up as Function.
+        return cx->names().objectFunction;
+    }
+
+    return nullptr;
+}
+
 // ES6 19.1.3.6
 bool
 js::obj_toString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     if (args.thisv().isUndefined()) {
@@ -512,53 +560,17 @@ js::obj_toString(JSContext* cx, unsigned
         return false;
 
     RootedString builtinTag(cx);
     const Class* clasp = obj->getClass();
     if (MOZ_UNLIKELY(clasp->isProxy())) {
         if (!GetBuiltinTagSlow(cx, obj, &builtinTag))
             return false;
     } else {
-        // Optimize the non-proxy case to bypass GetBuiltinClass.
-        if (clasp == &PlainObject::class_ || clasp == &UnboxedPlainObject::class_) {
-            // This is not handled by GetBuiltinTagSlow, but this case is by far
-            // the most common so we optimize it here.
-            builtinTag = cx->names().objectObject;
-
-        } else if (clasp == &ArrayObject::class_ || clasp == &UnboxedArrayObject::class_) {
-            builtinTag = cx->names().objectArray;
-
-        } else if (clasp == &JSFunction::class_) {
-            builtinTag = cx->names().objectFunction;
-
-        } else if (clasp == &StringObject::class_) {
-            builtinTag = cx->names().objectString;
-
-        } else if (clasp == &NumberObject::class_) {
-            builtinTag = cx->names().objectNumber;
-
-        } else if (clasp == &BooleanObject::class_) {
-            builtinTag = cx->names().objectBoolean;
-
-        } else if (clasp == &DateObject::class_) {
-            builtinTag = cx->names().objectDate;
-
-        } else if (clasp == &RegExpObject::class_) {
-            builtinTag = cx->names().objectRegExp;
-
-        } else if (obj->is<ArgumentsObject>()) {
-            builtinTag = cx->names().objectArguments;
-
-        } else if (obj->is<ErrorObject>()) {
-            builtinTag = cx->names().objectError;
-
-        } else if (obj->isCallable() && !obj->getClass()->isDOMClass()) {
-            // Non-standard: Prevent <object> from showing up as Function.
-            builtinTag = cx->names().objectFunction;
-        }
+        builtinTag = GetBuiltinTagFast(obj, clasp, cx);
 #ifdef DEBUG
         // Assert this fast path is correct and matches BuiltinTagSlow. The
         // only exception is the PlainObject case: we special-case it here
         // because it's so common, but BuiltinTagSlow doesn't handle this.
         RootedString builtinTagSlow(cx);
         if (!GetBuiltinTagSlow(cx, obj, &builtinTagSlow))
             return false;
         if (clasp == &PlainObject::class_ || clasp == &UnboxedPlainObject::class_)
@@ -605,16 +617,36 @@ js::obj_toString(JSContext* cx, unsigned
     JSString* str = sb.finishAtom();
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
+JSString*
+js::ObjectClassToString(JSContext* cx, HandleObject obj)
+{
+    const Class* clasp = obj->getClass();
+
+    if (JSString* tag = GetBuiltinTagFast(obj, clasp, cx))
+        return tag;
+
+    const char* className = clasp->name;
+    StringBuffer sb(cx);
+    if (!sb.append("[object ") ||
+        !sb.append(className, strlen(className)) ||
+        !sb.append(']'))
+    {
+        return nullptr;
+    }
+
+    return sb.finishAtom();
+}
+
 static bool
 obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() < 2) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                                   "Object.setPrototypeOf", "1", "");
@@ -1504,17 +1536,17 @@ ProtoSetter(JSContext* cx, unsigned argc
     args.rval().setUndefined();
     return true;
 }
 
 static const JSFunctionSpec object_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,             obj_toSource,                0,0),
 #endif
-    JS_FN(js_toString_str,             obj_toString,                0,0),
+    JS_INLINABLE_FN(js_toString_str,   obj_toString,                0,0, ObjectToString),
     JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0),
     JS_SELF_HOSTED_FN(js_valueOf_str,  "Object_valueOf",            0,0),
 #if JS_HAS_OBJ_WATCHPOINT
     JS_FN(js_watch_str,                obj_watch,                   2,0),
     JS_FN(js_unwatch_str,              obj_unwatch,                 1,0),
 #endif
     JS_SELF_HOSTED_FN(js_hasOwnProperty_str, "Object_hasOwnProperty", 1,0),
     JS_FN(js_isPrototypeOf_str,        obj_isPrototypeOf,           1,0),
--- a/js/src/builtin/Object.h
+++ b/js/src/builtin/Object.h
@@ -50,16 +50,19 @@ obj_getPrototypeOf(JSContext* cx, unsign
 
 
 MOZ_MUST_USE bool
 obj_isExtensible(JSContext* cx, unsigned argc, JS::Value* vp);
 
 MOZ_MUST_USE bool
 obj_toString(JSContext* cx, unsigned argc, JS::Value* vp);
 
+JSString*
+ObjectClassToString(JSContext* cx, HandleObject obj);
+
 // Exposed so SelfHosting.cpp can use it in the OwnPropertyKeys intrinsic
 MOZ_MUST_USE bool
 GetOwnPropertyKeys(JSContext* cx, const JS::CallArgs& args, unsigned flags);
 
 /*
  * Like IdToValue, but convert int jsids to strings. This is used when
  * exposing a jsid to script for Object.getOwnProperty{Names,Symbols}
  * or scriptable proxy traps.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/object-prototype-tostring.js
@@ -0,0 +1,45 @@
+var toString = Object.prototype.toString;
+var iter = 500;
+
+function testConstant() {
+    for (var i = 0; i < iter; i++) {
+        assertEq(({}).toString(), "[object Object]");
+        assertEq(toString.call([]), "[object Array]");
+        assertEq(toString.call(Math.abs), "[object Function]");
+    }
+}
+testConstant();
+
+function testOwnToStringTag() {
+    var stringify = o => toString.call(o);
+    var o = {};
+    for (var i = 0; i < iter; i++)
+        assertEq(stringify(o), "[object Object]");
+    o[Symbol.toStringTag] = "foo";
+    for (var i = 0; i < iter; i++)
+        assertEq(stringify(o), "[object foo]");
+}
+testOwnToStringTag();
+
+function testDynamic() {
+    var arr = [{}, [], new Date, /a/];
+    var expected = ["[object Object]", "[object Array]", "[object Date]", "[object RegExp]"];
+    for (var i = 0; i < iter; i++) {
+        for (var j = 0; j < arr.length; j++)
+            assertEq(toString.call(arr[j]), expected[j]);
+    }
+}
+testDynamic();
+
+function testToStringTagProto() {
+    var c = 0;
+    Object.defineProperty(Date.prototype, Symbol.toStringTag, {get() { c++; return "evil"; }});
+    var arr = [{}, [], new Date, /a/];
+    var expected = ["[object Object]", "[object Array]", "[object evil]", "[object RegExp]"];
+    for (var i = 0; i < iter; i++) {
+        for (var j = 0; j < arr.length; j++)
+            assertEq(toString.call(arr[j]), expected[j]);
+    }
+    assertEq(c, iter);
+}
+testToStringTagProto();
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -12126,16 +12126,27 @@ CodeGenerator::visitHasClass(LHasClass* 
 {
     Register lhs = ToRegister(ins->lhs());
     Register output = ToRegister(ins->output());
 
     masm.loadObjClass(lhs, output);
     masm.cmpPtrSet(Assembler::Equal, output, ImmPtr(ins->mir()->getClass()), output);
 }
 
+typedef JSString* (*ObjectClassToStringFn)(JSContext*, HandleObject);
+static const VMFunction ObjectClassToStringInfo =
+    FunctionInfo<ObjectClassToStringFn>(js::ObjectClassToString, "ObjectClassToString");
+
+void
+CodeGenerator::visitObjectClassToString(LObjectClassToString* lir)
+{
+    pushArg(ToRegister(lir->object()));
+    callVM(ObjectClassToStringInfo, lir);
+}
+
 void
 CodeGenerator::visitWasmParameter(LWasmParameter* lir)
 {
 }
 
 void
 CodeGenerator::visitWasmParameterI64(LWasmParameterI64* lir)
 {
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -389,16 +389,17 @@ class CodeGenerator final : public CodeG
     void visitIsConstructor(LIsConstructor* lir);
     void visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool);
     void visitIsArrayO(LIsArrayO* lir);
     void visitIsArrayV(LIsArrayV* lir);
     void visitIsTypedArray(LIsTypedArray* lir);
     void visitIsObject(LIsObject* lir);
     void visitIsObjectAndBranch(LIsObjectAndBranch* lir);
     void visitHasClass(LHasClass* lir);
+    void visitObjectClassToString(LObjectClassToString* lir);
     void visitWasmParameter(LWasmParameter* lir);
     void visitWasmParameterI64(LWasmParameterI64* lir);
     void visitWasmReturn(LWasmReturn* ret);
     void visitWasmReturnI64(LWasmReturnI64* ret);
     void visitWasmReturnVoid(LWasmReturnVoid* ret);
     void visitLexicalCheck(LLexicalCheck* ins);
     void visitThrowRuntimeLexicalError(LThrowRuntimeLexicalError* ins);
     void visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins);
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -83,16 +83,17 @@
     _(StringFromCharCode)           \
     _(StringFromCodePoint)          \
     _(StringCharAt)                 \
                                     \
     _(IntrinsicStringReplaceString) \
     _(IntrinsicStringSplitString)   \
                                     \
     _(ObjectCreate)                 \
+    _(ObjectToString)               \
                                     \
     _(SimdInt32x4)                  \
     _(SimdUint32x4)                 \
     _(SimdInt16x8)                  \
     _(SimdUint16x8)                 \
     _(SimdInt8x16)                  \
     _(SimdUint8x16)                 \
     _(SimdFloat32x4)                \
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -678,16 +678,17 @@ class IonBuilder
     InliningResult inlineRegExpTester(CallInfo& callInfo);
     InliningResult inlineIsRegExpObject(CallInfo& callInfo);
     InliningResult inlineRegExpPrototypeOptimizable(CallInfo& callInfo);
     InliningResult inlineRegExpInstanceOptimizable(CallInfo& callInfo);
     InliningResult inlineGetFirstDollarIndex(CallInfo& callInfo);
 
     // Object natives and intrinsics.
     InliningResult inlineObjectCreate(CallInfo& callInfo);
+    InliningResult inlineObjectToString(CallInfo& callInfo);
     InliningResult inlineDefineDataProperty(CallInfo& callInfo);
 
     // Atomics natives.
     InliningResult inlineAtomicsCompareExchange(CallInfo& callInfo);
     InliningResult inlineAtomicsExchange(CallInfo& callInfo);
     InliningResult inlineAtomicsLoad(CallInfo& callInfo);
     InliningResult inlineAtomicsStore(CallInfo& callInfo);
     InliningResult inlineAtomicsBinop(CallInfo& callInfo, InlinableNative target);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4386,16 +4386,26 @@ void
 LIRGenerator::visitHasClass(MHasClass* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
     MOZ_ASSERT(ins->type() == MIRType::Boolean);
     define(new(alloc()) LHasClass(useRegister(ins->object())), ins);
 }
 
 void
+LIRGenerator::visitObjectClassToString(MObjectClassToString* ins)
+{
+    MOZ_ASSERT(ins->object()->type() == MIRType::Object);
+    MOZ_ASSERT(ins->type() == MIRType::String);
+    auto lir = new(alloc()) LObjectClassToString(useRegisterAtStart(ins->object()));
+    defineReturn(lir, ins);
+    assignSafepoint(lir, ins);
+}
+
+void
 LIRGenerator::visitWasmAddOffset(MWasmAddOffset* ins)
 {
     MOZ_ASSERT(ins->base()->type() == MIRType::Int32);
     MOZ_ASSERT(ins->type() == MIRType::Int32);
     define(new(alloc()) LWasmAddOffset(useRegisterAtStart(ins->base())), ins);
 }
 
 void
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -293,16 +293,17 @@ class LIRGenerator : public LIRGenerator
     void visitInstanceOf(MInstanceOf* ins);
     void visitCallInstanceOf(MCallInstanceOf* ins);
     void visitIsCallable(MIsCallable* ins);
     void visitIsConstructor(MIsConstructor* ins);
     void visitIsArray(MIsArray* ins);
     void visitIsTypedArray(MIsTypedArray* ins);
     void visitIsObject(MIsObject* ins);
     void visitHasClass(MHasClass* ins);
+    void visitObjectClassToString(MObjectClassToString* ins);
     void visitWasmAddOffset(MWasmAddOffset* ins);
     void visitWasmLoadTls(MWasmLoadTls* ins);
     void visitWasmBoundsCheck(MWasmBoundsCheck* ins);
     void visitWasmLoadGlobalVar(MWasmLoadGlobalVar* ins);
     void visitWasmStoreGlobalVar(MWasmStoreGlobalVar* ins);
     void visitWasmParameter(MWasmParameter* ins);
     void visitWasmReturn(MWasmReturn* ins);
     void visitWasmReturnVoid(MWasmReturnVoid* ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -231,16 +231,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::IntrinsicStringSplitString:
         return inlineStringSplitString(callInfo);
       case InlinableNative::IntrinsicNewStringIterator:
         return inlineNewIterator(callInfo, MNewIterator::StringIterator);
 
       // Object natives.
       case InlinableNative::ObjectCreate:
         return inlineObjectCreate(callInfo);
+      case InlinableNative::ObjectToString:
+        return inlineObjectToString(callInfo);
 
       // SIMD natives.
       case InlinableNative::SimdInt32x4:
         return inlineSimd(callInfo, target, SimdType::Int32x4);
       case InlinableNative::SimdUint32x4:
         return inlineSimd(callInfo, target, SimdType::Uint32x4);
       case InlinableNative::SimdInt16x8:
         return inlineSimd(callInfo, target, SimdType::Int16x8);
@@ -2291,16 +2293,73 @@ IonBuilder::inlineObjectCreate(CallInfo&
     bool emitted = false;
     MOZ_TRY(newObjectTryTemplateObject(&emitted, templateObject));
 
     MOZ_ASSERT(emitted);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningResult
+IonBuilder::inlineObjectToString(CallInfo& callInfo)
+{
+    if (callInfo.constructing() || callInfo.argc() != 0) {
+        trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
+        return InliningStatus_NotInlined;
+    }
+
+    if (getInlineReturnType() != MIRType::String)
+        return InliningStatus_NotInlined;
+
+    MDefinition* arg = callInfo.thisArg();
+    if (arg->type() != MIRType::Object)
+        return InliningStatus_NotInlined;
+
+    TemporaryTypeSet* types = arg->resultTypeSet();
+    if (!types || types->unknownObject())
+        return InliningStatus_NotInlined;
+
+    // Don't optimize if this might be a proxy.
+    using ForAllResult = TemporaryTypeSet::ForAllResult;
+    if (types->forAllClasses(constraints(), IsProxyClass) != ForAllResult::ALL_FALSE)
+        return InliningStatus_NotInlined;
+
+    // Make sure there's no Symbol.toStringTag property.
+    jsid toStringTag = SYMBOL_TO_JSID(compartment->runtime()->wellKnownSymbols().toStringTag);
+    bool res;
+    MOZ_TRY_VAR(res, testNotDefinedProperty(arg, toStringTag));
+    if (!res)
+        return InliningStatus_NotInlined;
+
+    // At this point we know we're going to inline this.
+    callInfo.setImplicitlyUsedUnchecked();
+
+    // Try to constant fold some common cases.
+    if (const Class* knownClass = types->getKnownClass(constraints())) {
+        if (knownClass == &PlainObject::class_ || knownClass == &UnboxedPlainObject::class_) {
+            pushConstant(StringValue(names().objectObject));
+            return InliningStatus_Inlined;
+        }
+        if (IsArrayClass(knownClass)) {
+            pushConstant(StringValue(names().objectArray));
+            return InliningStatus_Inlined;
+        }
+        if (knownClass == &JSFunction::class_) {
+            pushConstant(StringValue(names().objectFunction));
+            return InliningStatus_Inlined;
+        }
+    }
+
+    MObjectClassToString* toString = MObjectClassToString::New(alloc(), arg);
+    current->add(toString);
+    current->push(toString);
+
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningResult
 IonBuilder::inlineHasClass(CallInfo& callInfo,
                            const Class* clasp1, const Class* clasp2,
                            const Class* clasp3, const Class* clasp4)
 {
     if (callInfo.constructing() || callInfo.argc() != 1) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
         return InliningStatus_NotInlined;
     }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -13500,16 +13500,40 @@ class MIsTypedArray
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, value))
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
+class MObjectClassToString
+  : public MUnaryInstruction,
+    public SingleObjectPolicy::Data
+{
+    explicit MObjectClassToString(MDefinition* obj)
+      : MUnaryInstruction(obj)
+    {
+        setMovable();
+        setResultType(MIRType::String);
+    }
+
+  public:
+    INSTRUCTION_HEADER(ObjectClassToString)
+    TRIVIAL_NEW_WRAPPERS
+    NAMED_OPERANDS((0, object))
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+    bool congruentTo(const MDefinition* ins) const override {
+        return congruentIfOperandsEqual(ins);
+    }
+};
+
 class MCheckReturn
   : public MBinaryInstruction,
     public BoxInputsPolicy::Data
 {
     explicit MCheckReturn(MDefinition* retVal, MDefinition* thisVal)
       : MBinaryInstruction(retVal, thisVal)
     {
         setGuard();
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -277,16 +277,17 @@ namespace jit {
     _(GetDOMMember)                                                         \
     _(SetDOMProperty)                                                       \
     _(IsConstructor)                                                        \
     _(IsCallable)                                                           \
     _(IsArray)                                                              \
     _(IsTypedArray)                                                         \
     _(IsObject)                                                             \
     _(HasClass)                                                             \
+    _(ObjectClassToString)                                                  \
     _(CopySign)                                                             \
     _(Rotate)                                                               \
     _(NewDerivedTypedObject)                                                \
     _(RecompileCheck)                                                       \
     _(UnknownValue)                                                         \
     _(LexicalCheck)                                                         \
     _(ThrowRuntimeLexicalError)                                             \
     _(GlobalNameConflictsCheck)                                             \
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -8092,16 +8092,32 @@ class LHasClass : public LInstructionHel
     const LAllocation* lhs() {
         return getOperand(0);
     }
     MHasClass* mir() const {
         return mir_->toHasClass();
     }
 };
 
+class LObjectClassToString : public LCallInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(ObjectClassToString);
+
+    explicit LObjectClassToString(const LAllocation& lhs) {
+        setOperand(0, lhs);
+    }
+    const LAllocation* object() {
+        return getOperand(0);
+    }
+    MObjectClassToString* mir() const {
+        return mir_->toObjectClassToString();
+    }
+};
+
 template<size_t Defs, size_t Ops>
 class LWasmSelectBase : public LInstructionHelper<Defs, Ops, 0>
 {
     typedef LInstructionHelper<Defs, Ops, 0> Base;
   public:
 
     MWasmSelect* mir() const {
         return Base::mir_->toWasmSelect();
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -391,16 +391,17 @@
     _(IsCallable)                   \
     _(IsConstructor)                \
     _(IsArrayO)                     \
     _(IsArrayV)                     \
     _(IsTypedArray)                 \
     _(IsObject)                     \
     _(IsObjectAndBranch)            \
     _(HasClass)                     \
+    _(ObjectClassToString)          \
     _(RecompileCheck)               \
     _(MemoryBarrier)                \
     _(AssertRangeI)                 \
     _(AssertRangeD)                 \
     _(AssertRangeF)                 \
     _(AssertRangeV)                 \
     _(AssertResultV)                \
     _(AssertResultT)                \