Bug 1113014 - Don't emit a branch in LoadDOMPrivate if we know the object kind (native or proxy) statically. r=bz
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 12 Jan 2018 12:47:19 +0100
changeset 453312 4ef2425934f236201df33293a34c2400d5c5f19d
parent 453311 e4d4a77b4c61a1be32c503ae13b4d873bbd22c1a
child 453313 462b35675a119a778f35aed836aa88b606f0babd
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1113014
milestone59.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 1113014 - Don't emit a branch in LoadDOMPrivate if we know the object kind (native or proxy) statically. r=bz
js/public/Proxy.h
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -380,16 +380,20 @@ namespace detail {
 // store their dynamic slots pointer.
 
 struct ProxyReservedSlots
 {
     Value slots[1];
 
     static inline int offsetOfPrivateSlot();
 
+    static inline int offsetOfSlot(size_t slot) {
+        return offsetof(ProxyReservedSlots, slots[0]) + slot * sizeof(Value);
+    }
+
     void init(size_t nreserved) {
         for (size_t i = 0; i < nreserved; i++)
             slots[i] = JS::UndefinedValue();
     }
 
     ProxyReservedSlots(const ProxyReservedSlots&) = delete;
     void operator=(const ProxyReservedSlots&) = delete;
 };
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4073,38 +4073,57 @@ CodeGenerator::visitCallNative(LCallNati
     // is no need for leaveFakeExitFrame.
 
     // Move the StackPointer back to its original location, unwinding the native exit frame.
     masm.adjustStack(NativeExitFrameLayout::Size() - unusedStack);
     MOZ_ASSERT(masm.framePushed() == initialStack);
 }
 
 static void
-LoadDOMPrivate(MacroAssembler& masm, Register obj, Register priv)
+LoadDOMPrivate(MacroAssembler& masm, Register obj, Register priv, DOMObjectKind kind)
 {
     // Load the value in DOM_OBJECT_SLOT for a native or proxy DOM object. This
     // will be in the first slot but may be fixed or non-fixed.
     MOZ_ASSERT(obj != priv);
 
-    // Check shape->numFixedSlots != 0.
-    masm.loadPtr(Address(obj, ShapedObject::offsetOfShape()), priv);
-
-    Label hasFixedSlots, done;
-    masm.branchTest32(Assembler::NonZero,
-                      Address(priv, Shape::offsetOfSlotInfo()),
-                      Imm32(Shape::fixedSlotsMask()),
-                      &hasFixedSlots);
-
-    masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), priv);
-    masm.loadPrivate(Address(priv, 0), priv);
-
-    masm.jump(&done);
-    masm.bind(&hasFixedSlots);
-
-    masm.loadPrivate(Address(obj, NativeObject::getFixedSlotOffset(0)), priv);
+    // Check if it's a proxy.
+    Label isProxy, done;
+    if (kind == DOMObjectKind::Unknown)
+        masm.branchTestObjectIsProxy(true, obj, priv, &isProxy);
+
+    if (kind != DOMObjectKind::Proxy) {
+#ifdef DEBUG
+        // If it's a native object, the value must be in a fixed slot.
+        Label hasFixedSlots;
+        masm.loadPtr(Address(obj, ShapedObject::offsetOfShape()), priv);
+        masm.branchTest32(Assembler::NonZero,
+                          Address(priv, Shape::offsetOfSlotInfo()),
+                          Imm32(Shape::fixedSlotsMask()),
+                          &hasFixedSlots);
+        masm.assumeUnreachable("Expected a fixed slot");
+        masm.bind(&hasFixedSlots);
+#endif
+        masm.loadPrivate(Address(obj, NativeObject::getFixedSlotOffset(0)), priv);
+        if (kind == DOMObjectKind::Unknown)
+            masm.jump(&done);
+    }
+
+    if (kind != DOMObjectKind::Native) {
+        masm.bind(&isProxy);
+#ifdef DEBUG
+        // Sanity check: it must be a DOM proxy.
+        Label isDOMProxy;
+        masm.branchTestProxyHandlerFamily(Assembler::Equal, obj, priv,
+                                          GetDOMProxyHandlerFamily(), &isDOMProxy);
+        masm.assumeUnreachable("Expected a DOM proxy");
+        masm.bind(&isDOMProxy);
+#endif
+        masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), priv);
+        masm.loadPrivate(Address(priv, detail::ProxyReservedSlots::offsetOfSlot(0)), priv);
+    }
 
     masm.bind(&done);
 }
 
 void
 CodeGenerator::visitCallDOMNative(LCallDOMNative* call)
 {
     WrappedFunction* target = call->getSingleTarget();
@@ -4147,17 +4166,17 @@ CodeGenerator::visitCallDOMNative(LCallD
     // Now compute the argv value.  Since StackPointer is pointing to &vp[0] and
     // argv is &vp[2] we just need to add 2*sizeof(Value) to the current
     // StackPointer.
     JS_STATIC_ASSERT(JSJitMethodCallArgsTraits::offsetOfArgv == 0);
     JS_STATIC_ASSERT(JSJitMethodCallArgsTraits::offsetOfArgc ==
                      IonDOMMethodExitFrameLayoutTraits::offsetOfArgcFromArgv);
     masm.computeEffectiveAddress(Address(masm.getStackPointer(), 2 * sizeof(Value)), argArgs);
 
-    LoadDOMPrivate(masm, obj, argPrivate);
+    LoadDOMPrivate(masm, obj, argPrivate, static_cast<MCallDOMNative*>(call->mir())->objectKind());
 
     // Push argc from the call instruction into what will become the IonExitFrame
     masm.Push(Imm32(call->numActualArgs()));
 
     // Push our argv onto the stack
     masm.Push(argArgs);
     // And store our JSJitMethodCallArgs* in argArgs.
     masm.moveStackPtrTo(argArgs);
@@ -11629,17 +11648,17 @@ CodeGenerator::visitGetDOMProperty(LGetD
     masm.Push(UndefinedValue());
     // We pass the pointer to our out param as an instance of
     // JSJitGetterCallArgs, since on the binary level it's the same thing.
     JS_STATIC_ASSERT(sizeof(JSJitGetterCallArgs) == sizeof(Value*));
     masm.moveStackPtrTo(ValueReg);
 
     masm.Push(ObjectReg);
 
-    LoadDOMPrivate(masm, ObjectReg, PrivateReg);
+    LoadDOMPrivate(masm, ObjectReg, PrivateReg, ins->mir()->objectKind());
 
     // Rooting will happen at GC time.
     masm.moveStackPtrTo(ObjectReg);
 
     uint32_t safepointOffset = masm.buildFakeExitFrame(JSContextReg);
     masm.loadJSContext(JSContextReg);
     masm.enterFakeExitFrame(JSContextReg, JSContextReg, ExitFrameType::IonDOMGetter);
 
@@ -11728,17 +11747,17 @@ CodeGenerator::visitSetDOMProperty(LSetD
     masm.Push(argVal);
     // We pass the pointer to our out param as an instance of
     // JSJitGetterCallArgs, since on the binary level it's the same thing.
     JS_STATIC_ASSERT(sizeof(JSJitSetterCallArgs) == sizeof(Value*));
     masm.moveStackPtrTo(ValueReg);
 
     masm.Push(ObjectReg);
 
-    LoadDOMPrivate(masm, ObjectReg, PrivateReg);
+    LoadDOMPrivate(masm, ObjectReg, PrivateReg, ins->mir()->objectKind());
 
     // Rooting will happen at GC time.
     masm.moveStackPtrTo(ObjectReg);
 
     uint32_t safepointOffset = masm.buildFakeExitFrame(JSContextReg);
     masm.loadJSContext(JSContextReg);
     masm.enterFakeExitFrame(JSContextReg, JSContextReg, ExitFrameType::IonDOMSetter);
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5521,32 +5521,33 @@ IonBuilder::makeCallHelper(JSFunction* t
     uint32_t targetArgs = callInfo.argc();
 
     // Collect number of missing arguments provided that the target is
     // scripted. Native functions are passed an explicit 'argc' parameter.
     if (target && !target->isNative())
         targetArgs = Max<uint32_t>(target->nargs(), callInfo.argc());
 
     bool isDOMCall = false;
+    DOMObjectKind objKind = DOMObjectKind::Unknown;
     if (target && !callInfo.constructing()) {
         // We know we have a single call target.  Check whether the "this" types
         // are DOM types and our function a DOM function, and if so flag the
         // MCall accordingly.
         TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
         if (thisTypes &&
             thisTypes->getKnownMIRType() == MIRType::Object &&
-            thisTypes->isDOMClass(constraints()))
+            thisTypes->isDOMClass(constraints(), &objKind))
         {
             MOZ_TRY_VAR(isDOMCall, testShouldDOMCall(thisTypes, target, JSJitInfo::Method));
         }
     }
 
     MCall* call = MCall::New(alloc(), target, targetArgs + 1 + callInfo.constructing(),
                              callInfo.argc(), callInfo.constructing(),
-                             callInfo.ignoresReturnValue(), isDOMCall);
+                             callInfo.ignoresReturnValue(), isDOMCall, objKind);
     if (!call)
         return abort(AbortReason::Alloc);
 
     if (callInfo.constructing())
         call->addArg(targetArgs + 1, callInfo.getNewTarget());
 
     // Explicitly pad any missing arguments with |undefined|.
     // This permits skipping the argumentsRectifier.
@@ -11019,28 +11020,30 @@ IonBuilder::getPropTryCommonGetter(bool*
                 return Ok();
             }
         } else {
             // The Baseline IC didn't have any information we can use.
             return Ok();
         }
     }
 
-    bool isDOM = objTypes && objTypes->isDOMClass(constraints());
+    DOMObjectKind objKind = DOMObjectKind::Unknown;
+    bool isDOM = objTypes && objTypes->isDOMClass(constraints(), &objKind);
     if (isDOM)
         MOZ_TRY_VAR(isDOM, testShouldDOMCall(objTypes, commonGetter, JSJitInfo::Getter));
 
     if (isDOM) {
         const JSJitInfo* jitinfo = commonGetter->jitInfo();
         // We don't support MGetDOMProperty/MGetDOMMember on things that might
         // be proxies when the value might be in a slot, because the
         // CodeGenerator code for LGetDOMProperty/LGetDOMMember doesn't handle
         // that case correctly.
-        if ((!jitinfo->isAlwaysInSlot && !jitinfo->isLazilyCachedInSlot) ||
-            !objTypes->maybeProxy(constraints())) {
+        if (objKind == DOMObjectKind::Native ||
+            (!jitinfo->isAlwaysInSlot && !jitinfo->isLazilyCachedInSlot))
+        {
             MInstruction* get;
             if (jitinfo->isAlwaysInSlot) {
                 // If our object is a singleton and we know the property is
                 // constant (which is true if and only if the get doesn't alias
                 // anything), we can just read the slot here and use that
                 // constant.
                 JSObject* singleton = objTypes->maybeSingleton();
                 if (singleton && jitinfo->aliasSet() == JSJitInfo::AliasNone) {
@@ -11050,17 +11053,17 @@ IonBuilder::getPropTryCommonGetter(bool*
                     return Ok();
                 }
 
                 // We can't use MLoadFixedSlot here because it might not have
                 // the right aliasing behavior; we want to alias DOM setters as
                 // needed.
                 get = MGetDOMMember::New(alloc(), jitinfo, obj, guard, globalGuard);
             } else {
-                get = MGetDOMProperty::New(alloc(), jitinfo, obj, guard, globalGuard);
+                get = MGetDOMProperty::New(alloc(), jitinfo, objKind, obj, guard, globalGuard);
             }
             if (!get)
                 return abort(AbortReason::Alloc);
             current->add(get);
             current->push(get);
 
             if (get->isEffectful())
                 MOZ_TRY(resumeAfter(get));
@@ -11722,27 +11725,29 @@ IonBuilder::setPropTryCommonSetter(bool*
 
 AbortReasonOr<Ok>
 IonBuilder::setPropTryCommonDOMSetter(bool* emitted, MDefinition* obj,
                                       MDefinition* value, JSFunction* setter,
                                       TemporaryTypeSet* objTypes)
 {
     MOZ_ASSERT(*emitted == false);
 
-    if (!objTypes || !objTypes->isDOMClass(constraints()))
+    DOMObjectKind objKind = DOMObjectKind::Unknown;
+    if (!objTypes || !objTypes->isDOMClass(constraints(), &objKind))
         return Ok();
 
     bool isDOM = false;
     MOZ_TRY_VAR(isDOM, testShouldDOMCall(objTypes, setter, JSJitInfo::Setter));
     if (!isDOM)
         return Ok();
 
     // Emit SetDOMProperty.
     MOZ_ASSERT(setter->jitInfo()->type() == JSJitInfo::Setter);
-    MSetDOMProperty* set = MSetDOMProperty::New(alloc(), setter->jitInfo()->setter, obj, value);
+    MSetDOMProperty* set = MSetDOMProperty::New(alloc(), setter->jitInfo()->setter, objKind,
+                                                obj, value);
 
     current->add(set);
     current->push(value);
 
     MOZ_TRY(resumeAfter(set));
 
     *emitted = true;
     return Ok();
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2031,24 +2031,24 @@ WrappedFunction::WrappedFunction(JSFunct
     isNative_(fun->isNative()),
     isConstructor_(fun->isConstructor()),
     isClassConstructor_(fun->isClassConstructor()),
     isSelfHostedBuiltin_(fun->isSelfHostedBuiltin())
 {}
 
 MCall*
 MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs,
-           bool construct, bool ignoresReturnValue, bool isDOMCall)
+           bool construct, bool ignoresReturnValue, bool isDOMCall, DOMObjectKind objectKind)
 {
     WrappedFunction* wrappedTarget = target ? new(alloc) WrappedFunction(target) : nullptr;
     MOZ_ASSERT(maxArgc >= numActualArgs);
     MCall* ins;
     if (isDOMCall) {
         MOZ_ASSERT(!construct);
-        ins = new(alloc) MCallDOMNative(wrappedTarget, numActualArgs);
+        ins = new(alloc) MCallDOMNative(wrappedTarget, numActualArgs, objectKind);
     } else {
         ins = new(alloc) MCall(wrappedTarget, numActualArgs, construct, ignoresReturnValue);
     }
     if (!ins->init(alloc, maxArgc + NumNonArgumentOperands))
         return nullptr;
     return ins;
 }
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4217,17 +4217,18 @@ class MCall
         needsArgCheck_(true)
     {
         setResultType(MIRType::Value);
     }
 
   public:
     INSTRUCTION_HEADER(Call)
     static MCall* New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs,
-                      bool construct, bool ignoresReturnValue, bool isDOMCall);
+                      bool construct, bool ignoresReturnValue, bool isDOMCall,
+                      DOMObjectKind objectKind);
 
     void initFunction(MDefinition* func) {
         initOperand(FunctionOperandIndex, func);
     }
 
     bool needsArgCheck() const {
         return needsArgCheck_;
     }
@@ -4307,38 +4308,46 @@ class MCall
 };
 
 class MCallDOMNative : public MCall
 {
     // A helper class for MCalls for DOM natives.  Note that this is NOT
     // actually a separate MIR op from MCall, because all sorts of places use
     // isCall() to check for calls and all we really want is to overload a few
     // virtual things from MCall.
-  protected:
-    MCallDOMNative(WrappedFunction* target, uint32_t numActualArgs)
-        : MCall(target, numActualArgs, false, false)
+
+    DOMObjectKind objectKind_;
+
+  protected:
+    MCallDOMNative(WrappedFunction* target, uint32_t numActualArgs, DOMObjectKind objectKind)
+      : MCall(target, numActualArgs, false, false),
+        objectKind_(objectKind)
     {
         MOZ_ASSERT(getJitInfo()->type() != JSJitInfo::InlinableNative);
 
         // If our jitinfo is not marked eliminatable, that means that our C++
         // implementation is fallible or that it never wants to be eliminated or
         // that we have no hope of ever doing the sort of argument analysis that
         // would allow us to detemine that we're side-effect-free.  In the
         // latter case we wouldn't get DCEd no matter what, but for the former
         // two cases we have to explicitly say that we can't be DCEd.
         if (!getJitInfo()->isEliminatable)
             setGuard();
     }
 
     friend MCall* MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc,
                              size_t numActualArgs, bool construct, bool ignoresReturnValue,
-                             bool isDOMCall);
+                             bool isDOMCall, DOMObjectKind objectKind);
 
     const JSJitInfo* getJitInfo() const;
   public:
+    DOMObjectKind objectKind() const {
+        return objectKind_;
+    }
+
     virtual AliasSet getAliasSet() const override;
 
     virtual bool congruentTo(const MDefinition* ins) const override;
 
     virtual bool isCallDOMNative() const override {
         return true;
     }
 
@@ -12236,44 +12245,50 @@ class MCallInitElementArray
     }
 };
 
 class MSetDOMProperty
   : public MBinaryInstruction,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
 {
     const JSJitSetterOp func_;
-
-    MSetDOMProperty(const JSJitSetterOp func, MDefinition* obj, MDefinition* val)
+    DOMObjectKind objectKind_;
+
+    MSetDOMProperty(const JSJitSetterOp func, DOMObjectKind objectKind, MDefinition* obj,
+                    MDefinition* val)
       : MBinaryInstruction(classOpcode, obj, val),
-        func_(func)
+        func_(func),
+        objectKind_(objectKind)
     { }
 
   public:
     INSTRUCTION_HEADER(SetDOMProperty)
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, object), (1, value))
 
     JSJitSetterOp fun() const {
         return func_;
     }
+    DOMObjectKind objectKind() const {
+        return objectKind_;
+    }
 
     bool possiblyCalls() const override {
         return true;
     }
 };
 
-class MGetDOMProperty
+class MGetDOMPropertyBase
   : public MVariadicInstruction,
     public ObjectPolicy<0>::Data
 {
     const JSJitInfo* info_;
 
   protected:
-    MGetDOMProperty(Opcode op, const JSJitInfo* jitinfo)
+    MGetDOMPropertyBase(Opcode op, const JSJitInfo* jitinfo)
       : MVariadicInstruction(op),
         info_(jitinfo)
     {
         MOZ_ASSERT(jitinfo);
         MOZ_ASSERT(jitinfo->type() == JSJitInfo::Getter);
 
         // We are movable iff the jitinfo says we can be.
         if (isDomMovable()) {
@@ -12315,28 +12330,18 @@ class MGetDOMProperty
         // And the same for the global guard, if we have one.
         if (globalGuard)
             initOperand(operandIndex, globalGuard);
 
         return true;
     }
 
   public:
-    INSTRUCTION_HEADER(GetDOMProperty)
     NAMED_OPERANDS((0, object))
 
-    static MGetDOMProperty* New(TempAllocator& alloc, const JSJitInfo* info, MDefinition* obj,
-                                MDefinition* guard, MDefinition* globalGuard)
-    {
-        auto* res = new(alloc) MGetDOMProperty(classOpcode, info);
-        if (!res || !res->init(alloc, obj, guard, globalGuard))
-            return nullptr;
-        return res;
-    }
-
     JSJitGetterOp fun() const {
         return info_->getter;
     }
     bool isInfallible() const {
         return info_->isInfallible;
     }
     bool isDomMovable() const {
         return info_->isMovable;
@@ -12346,24 +12351,18 @@ class MGetDOMProperty
     }
     size_t domMemberSlotIndex() const {
         MOZ_ASSERT(info_->isAlwaysInSlot || info_->isLazilyCachedInSlot);
         return info_->slotIndex;
     }
     bool valueMayBeInSlot() const {
         return info_->isLazilyCachedInSlot;
     }
-    bool congruentTo(const MDefinition* ins) const override {
-        if (!ins->isGetDOMProperty())
-            return false;
-
-        return congruentTo(ins->toGetDOMProperty());
-    }
-
-    bool congruentTo(const MGetDOMProperty* ins) const {
+
+    bool baseCongruentTo(const MGetDOMPropertyBase* ins) const {
         if (!isDomMovable())
             return false;
 
         // Checking the jitinfo is the same as checking the constant function
         if (!(info() == ins->info()))
             return false;
 
         return congruentIfOperandsEqual(ins);
@@ -12373,28 +12372,61 @@ class MGetDOMProperty
         JSJitInfo::AliasSet aliasSet = domAliasSet();
         if (aliasSet == JSJitInfo::AliasNone)
             return AliasSet::None();
         if (aliasSet == JSJitInfo::AliasDOMSets)
             return AliasSet::Load(AliasSet::DOMProperty);
         MOZ_ASSERT(aliasSet == JSJitInfo::AliasEverything);
         return AliasSet::Store(AliasSet::Any);
     }
+};
+
+class MGetDOMProperty
+  : public MGetDOMPropertyBase
+{
+    DOMObjectKind objectKind_;
+
+  protected:
+    MGetDOMProperty(const JSJitInfo* jitinfo, DOMObjectKind objectKind)
+      : MGetDOMPropertyBase(classOpcode, jitinfo),
+        objectKind_(objectKind)
+    {}
+
+  public:
+    INSTRUCTION_HEADER(GetDOMProperty)
+
+    static MGetDOMProperty* New(TempAllocator& alloc, const JSJitInfo* info, DOMObjectKind objectKind,
+                                MDefinition* obj, MDefinition* guard, MDefinition* globalGuard)
+    {
+        auto* res = new(alloc) MGetDOMProperty(info, objectKind);
+        if (!res || !res->init(alloc, obj, guard, globalGuard))
+            return nullptr;
+        return res;
+    }
+
+    DOMObjectKind objectKind() const {
+        return objectKind_;
+    }
+
+    bool congruentTo(const MDefinition* ins) const override {
+        if (!ins->isGetDOMProperty())
+            return false;
+
+        return baseCongruentTo(ins->toGetDOMProperty());
+    }
 
     bool possiblyCalls() const override {
         return true;
     }
 };
 
-class MGetDOMMember : public MGetDOMProperty
-{
-    // We inherit everything from MGetDOMProperty except our
-    // possiblyCalls value and the congruentTo behavior.
+class MGetDOMMember : public MGetDOMPropertyBase
+{
     explicit MGetDOMMember(const JSJitInfo* jitinfo)
-      : MGetDOMProperty(classOpcode, jitinfo)
+      : MGetDOMPropertyBase(classOpcode, jitinfo)
     {
         setResultType(MIRTypeFromValueType(jitinfo->returnType()));
     }
 
   public:
     INSTRUCTION_HEADER(GetDOMMember)
 
     static MGetDOMMember* New(TempAllocator& alloc, const JSJitInfo* info, MDefinition* obj,
@@ -12409,17 +12441,17 @@ class MGetDOMMember : public MGetDOMProp
     bool possiblyCalls() const override {
         return false;
     }
 
     bool congruentTo(const MDefinition* ins) const override {
         if (!ins->isGetDOMMember())
             return false;
 
-        return MGetDOMProperty::congruentTo(ins->toGetDOMMember());
+        return baseCongruentTo(ins->toGetDOMMember());
     }
 };
 
 class MStringLength
   : public MUnaryInstruction,
     public StringPolicy<0>::Data
 {
     explicit MStringLength(MDefinition* string)
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2355,28 +2355,40 @@ TemporaryTypeSet::getTypedArrayType(Comp
         if (sharedness)
             getTypedArraySharedness(constraints, sharedness);
         return (Scalar::Type) (clasp - &TypedArrayObject::classes[0]);
     }
     return Scalar::MaxTypedArrayViewType;
 }
 
 bool
-TemporaryTypeSet::isDOMClass(CompilerConstraintList* constraints)
+TemporaryTypeSet::isDOMClass(CompilerConstraintList* constraints, DOMObjectKind* kind)
 {
     if (unknownObject())
         return false;
 
+    *kind = DOMObjectKind::Unknown;
+    bool isFirst = true;
+
     unsigned count = getObjectCount();
     for (unsigned i = 0; i < count; i++) {
         const Class* clasp = getObjectClass(i);
         if (!clasp)
             continue;
         if (!clasp->isDOMClass() || !getObject(i)->hasStableClassAndProto(constraints))
             return false;
+
+        DOMObjectKind thisKind = clasp->isProxy() ? DOMObjectKind::Proxy : DOMObjectKind::Native;
+        if (isFirst) {
+            *kind = thisKind;
+            isFirst = false;
+            continue;
+        }
+        if (*kind != thisKind)
+            *kind = DOMObjectKind::Unknown;
     }
 
     return count > 0;
 }
 
 bool
 TemporaryTypeSet::maybeCallable(CompilerConstraintList* constraints)
 {
@@ -2396,37 +2408,16 @@ TemporaryTypeSet::maybeCallable(Compiler
         if (!getObject(i)->hasStableClassAndProto(constraints))
             return true;
     }
 
     return false;
 }
 
 bool
-TemporaryTypeSet::maybeProxy(CompilerConstraintList* constraints)
-{
-    if (!maybeObject())
-        return false;
-
-    if (unknownObject())
-        return true;
-
-    unsigned count = getObjectCount();
-    for (unsigned i = 0; i < count; i++) {
-        const Class* clasp = getObjectClass(i);
-        if (!clasp)
-            continue;
-        if (clasp->isProxy() || !getObject(i)->hasStableClassAndProto(constraints))
-            return true;
-    }
-
-    return false;
-}
-
-bool
 TemporaryTypeSet::maybeEmulatesUndefined(CompilerConstraintList* constraints)
 {
     if (!maybeObject())
         return false;
 
     if (unknownObject())
         return true;
 
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -733,16 +733,18 @@ class HeapTypeSet : public ConstraintTyp
 
     // Mark this type set as being non-constant.
     inline void setNonConstantProperty(JSContext* cx);
 };
 
 CompilerConstraintList*
 NewCompilerConstraintList(jit::TempAllocator& alloc);
 
+enum class DOMObjectKind : uint8_t { Proxy, Native, Unknown };
+
 class TemporaryTypeSet : public TypeSet
 {
   public:
     TemporaryTypeSet() {}
     TemporaryTypeSet(LifoAlloc* alloc, Type type);
 
     TemporaryTypeSet(uint32_t flags, ObjectKey** objectSet) {
         this->flags = flags;
@@ -823,24 +825,21 @@ class TemporaryTypeSet : public TypeSet
     /* Get the typed array type of all objects in this set, or Scalar::MaxTypedArrayViewType.
      * If there is such a common type and sharedness is not nullptr then
      * *sharedness is set to what we know about the sharedness of the memory.
      */
     Scalar::Type getTypedArrayType(CompilerConstraintList* constraints,
                                    TypedArraySharedness* sharedness = nullptr);
 
     /* Whether all objects have JSCLASS_IS_DOMJSCLASS set. */
-    bool isDOMClass(CompilerConstraintList* constraints);
+    bool isDOMClass(CompilerConstraintList* constraints, DOMObjectKind* kind);
 
     /* Whether clasp->isCallable() is true for one or more objects in this set. */
     bool maybeCallable(CompilerConstraintList* constraints);
 
-    /* Whether clasp->isProxy() might be true for one or more objects in this set. */
-    bool maybeProxy(CompilerConstraintList* constraints);
-
     /* Whether clasp->emulatesUndefined() is true for one or more objects in this set. */
     bool maybeEmulatesUndefined(CompilerConstraintList* constraints);
 
     /* Get the single value which can appear in this type set, otherwise nullptr. */
     JSObject* maybeSingleton();
     ObjectKey* maybeSingleObject();
 
     /* Whether any objects in the type set needs a barrier on id. */