Bug 792108 - Implement JSCLASS_EMULATES_UNDEFINED to allow objects of a given class to act like the value |undefined| in certain contexts. Also add a TI flag for such objects, permitting us to assume that no objects use the flag until one is observed, also speeding up object-is-truthy tests when no falsy object is observed. r=jandem, r=bz
☠☠ backed out by 34d54961cd14 ☠ ☠
authorJeff Walden <jwalden@mit.edu>
Sat, 15 Sep 2012 11:19:54 -0700
changeset 125411 bc98fdc051f5c2f85f2a1951e625323266ad0777
parent 125410 6304fff5886683f751843ad95a614b48989f1070
child 125412 9c21138d9e9ab3616a850948f7e3334886bd6dff
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, bz
bugs792108
milestone20.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 792108 - Implement JSCLASS_EMULATES_UNDEFINED to allow objects of a given class to act like the value |undefined| in certain contexts. Also add a TI flag for such objects, permitting us to assume that no objects use the flag until one is observed, also speeding up object-is-truthy tests when no falsy object is observed. r=jandem, r=bz
dom/base/nsDOMClassInfo.cpp
js/src/ion/CodeGenerator.cpp
js/src/ion/CodeGenerator.h
js/src/ion/IonBuilder.cpp
js/src/ion/IonMacroAssembler.cpp
js/src/ion/IonMacroAssembler.h
js/src/ion/LIR-Common.h
js/src/ion/LIR.cpp
js/src/ion/LOpcodes.h
js/src/ion/Lowering.cpp
js/src/ion/MIR.cpp
js/src/ion/MIR.h
js/src/ion/VMFunctions.cpp
js/src/ion/VMFunctions.h
js/src/ion/shared/CodeGenerator-shared.h
js/src/jit-test/tests/basic/emulates-undefined.js
js/src/jit-test/tests/truthiness/equal-null.js
js/src/jit-test/tests/truthiness/equal-undefined.js
js/src/jit-test/tests/truthiness/if-equal-null.js
js/src/jit-test/tests/truthiness/if-equal-undefined.js
js/src/jit-test/tests/truthiness/if-not-equal-null.js
js/src/jit-test/tests/truthiness/if-not-equal-undefined.js
js/src/jit-test/tests/truthiness/if.js
js/src/jit-test/tests/truthiness/not-equal-null.js
js/src/jit-test/tests/truthiness/not-equal-undefined.js
js/src/jit-test/tests/truthiness/not.js
js/src/jit-test/tests/truthiness/typeof.js
js/src/jsapi-tests/Makefile.in
js/src/jsapi-tests/testLookup.cpp
js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsbool.cpp
js/src/jsboolinlines.h
js/src/jsclass.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsobjinlines.h
js/src/jsopcode.h
js/src/jsxml.cpp
js/src/methodjit/FastOps.cpp
js/src/methodjit/StubCalls.cpp
js/src/shell/js.cpp
js/src/tests/js1_5/Regress/regress-246964.js
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -8546,32 +8546,33 @@ ResolveImpl(JSContext *cx, nsIXPConnectW
 
   return doc->ResolveName(depStr, nullptr, result, aCache);
 }
 
 
 static JSClass sHTMLDocumentAllClass = {
   "HTML document.all class",
   JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE |
-  JSCLASS_HAS_RESERVED_SLOTS(1),
+  JSCLASS_EMULATES_UNDEFINED | JSCLASS_HAS_RESERVED_SLOTS(1),
   JS_PropertyStub,                                         /* addProperty */
   JS_PropertyStub,                                         /* delProperty */
   nsHTMLDocumentSH::DocumentAllGetProperty,                /* getProperty */
   JS_StrictPropertyStub,                                   /* setProperty */
   JS_EnumerateStub,
   (JSResolveOp)nsHTMLDocumentSH::DocumentAllNewResolve,
   JS_ConvertStub,
   nsHTMLDocumentSH::ReleaseDocument,
   nullptr,                                                  /* checkAccess */
   nsHTMLDocumentSH::CallToGetPropMapper
 };
 
 
 static JSClass sHTMLDocumentAllHelperClass = {
-  "HTML document.all helper class", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,
+  "HTML document.all helper class",
+  JSCLASS_NEW_RESOLVE,
   JS_PropertyStub,                                         /* addProperty */
   JS_PropertyStub,                                         /* delProperty */
   nsHTMLDocumentSH::DocumentAllHelperGetProperty,          /* getProperty */
   JS_StrictPropertyStub,                                   /* setProperty */
   JS_EnumerateStub,
   (JSResolveOp)nsHTMLDocumentSH::DocumentAllHelperNewResolve,
   JS_ConvertStub
 };
@@ -8883,88 +8884,45 @@ GetDocumentAllHelper(JSContext *cx, JSOb
       return false;
     }
   }
 
   *result = obj;
   return true;
 }
 
-static inline void *
-FlagsToPrivate(uint32_t flags)
-{
-  MOZ_ASSERT((flags & (1 << 31)) == 0);
-  return reinterpret_cast<void*>(static_cast<uintptr_t>(flags << 1));
-}
-
-static inline uint32_t
-PrivateToFlags(void *priv)
-{
-  uintptr_t intPriv = reinterpret_cast<uintptr_t>(priv);
-  MOZ_ASSERT(intPriv <= UINT32_MAX && (intPriv & 1) == 0);
-  return static_cast<uint32_t>(intPriv >> 1);
-}
-
 JSBool
 nsHTMLDocumentSH::DocumentAllHelperGetProperty(JSContext *cx, JSHandleObject obj,
                                                JSHandleId id, JSMutableHandleValue vp)
 {
   if (nsDOMClassInfo::sAll_id != id) {
     return JS_TRUE;
   }
 
-  JSObject *helper;
-  if (!GetDocumentAllHelper(cx, obj, &helper)) {
-    return JS_FALSE;
-  }
-
-  if (!helper) {
-    NS_ERROR("Uh, how'd we get here?");
-
-    // Let scripts continue, if we somehow did get here...
-
-    return JS_TRUE;
-  }
-
-  uint32_t flags = PrivateToFlags(::JS_GetPrivate(helper));
-
-  if (flags & JSRESOLVE_DETECTING || !(flags & JSRESOLVE_QUALIFIED)) {
-    // document.all is either being detected, e.g. if (document.all),
-    // or it was not being resolved with a qualified name. Claim that
-    // document.all is undefined.
-
-    vp.setUndefined();
-  } else {
-    // document.all is not being detected, and it resolved with a
-    // qualified name. Expose the document.all collection.
-
-    if (!vp.isObjectOrNull()) { 
-      // First time through, create the collection, and set the
-      // document as its private nsISupports data.
-      nsresult rv;
-      nsCOMPtr<nsIHTMLDocument> doc = do_QueryWrapper(cx, obj, &rv);
-      if (NS_FAILED(rv)) {
-        xpc::Throw(cx, rv);
-
-        return JS_FALSE;
-      }
-
-      JSObject *all = ::JS_NewObject(cx, &sHTMLDocumentAllClass, nullptr,
-                                     ::JS_GetGlobalForObject(cx, obj));
-      if (!all) {
-        return JS_FALSE;
-      }
-
-      // Let the JSObject take over ownership of doc.
-      ::JS_SetPrivate(all, doc);
-
-      doc.forget();
-
-      vp.setObject(*all);
-    }
+  if (!vp.isObjectOrNull()) {
+    // First time through, create the collection, and set the
+    // document as its private nsISupports data.
+    nsresult rv;
+    nsCOMPtr<nsIHTMLDocument> doc = do_QueryWrapper(cx, obj, &rv);
+    if (NS_FAILED(rv)) {
+      xpc::Throw(cx, rv);
+      return JS_FALSE;
+    }
+
+    js::Rooted<JSObject*> all(cx);
+    all = ::JS_NewObject(cx, &sHTMLDocumentAllClass, nullptr,
+                         ::JS_GetGlobalForObject(cx, obj));
+    if (!all) {
+      return JS_FALSE;
+    }
+
+    // Let the JSObject take over ownership of doc.
+    ::JS_SetPrivate(all, doc.forget().get());
+
+    vp.setObject(*all);
   }
 
   return JS_TRUE;
 }
 
 JSBool
 nsHTMLDocumentSH::DocumentAllHelperNewResolve(JSContext *cx, JSHandleObject obj,
                                               JSHandleId id, unsigned flags,
@@ -9116,21 +9074,19 @@ nsHTMLDocumentSH::NewResolve(nsIXPConnec
               return NS_ERROR_UNEXPECTED;
             }
           } while (tmpProto != helper);
 
           ::JS_SetPrototype(cx, tmp, proto);
         }
 
         // If we don't already have a helper, and we're resolving
-        // document.all qualified, and we're *not* detecting
-        // document.all, e.g. if (document.all), and "all" isn't
-        // already defined on our prototype, create a helper.
-        if (!helper && flags & JSRESOLVE_QUALIFIED &&
-            !(flags & JSRESOLVE_DETECTING) && !hasAll) {
+        // document.all qualified, and "all" isn't already defined
+        // on our prototype, create a helper.
+        if (!helper && (flags & JSRESOLVE_QUALIFIED) && !hasAll) {
           // Print a warning so developers can stop using document.all
           PrintWarningOnConsole(cx, "DocumentAllUsed");
 
           if (!::JS_GetPrototype(cx, obj, &proto)) {
             return NS_ERROR_UNEXPECTED;
           }
           helper = ::JS_NewObject(cx, &sHTMLDocumentAllHelperClass,
                                   proto,
@@ -9139,26 +9095,19 @@ nsHTMLDocumentSH::NewResolve(nsIXPConnec
           if (!helper) {
             return NS_ERROR_OUT_OF_MEMORY;
           }
 
           // Insert the helper into our prototype chain. helper's prototype
           // is already obj's current prototype.
           if (!::JS_SetPrototype(cx, obj, helper)) {
             xpc::Throw(cx, NS_ERROR_UNEXPECTED);
-
             return NS_ERROR_UNEXPECTED;
           }
         }
-
-        // If we have (or just created) a helper, pass the resolve flags
-        // to the helper as its private data.
-        if (helper) {
-          ::JS_SetPrivate(helper, FlagsToPrivate(flags));
-        }
       }
 
       return NS_OK;
     }
   }
 
   return nsDocumentSH::NewResolve(wrapper, cx, obj, id, flags, objp, _retval);
 }
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -1,16 +1,19 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=4 sw=4 et tw=99:
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/Util.h"
 
 #include "CodeGenerator.h"
 #include "IonLinker.h"
 #include "IonSpewer.h"
 #include "MIRGenerator.h"
 #include "shared/CodeGenerator-shared-inl.h"
 #include "jsnum.h"
 #include "jsmath.h"
@@ -18,16 +21,17 @@
 #include "ExecutionModeInlines.h"
 
 #include "vm/StringObject-inl.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::DebugOnly;
+using mozilla::Maybe;
 
 namespace js {
 namespace ion {
 
 StringObject *
 MNewStringObject::templateObj() const {
     return &templateObj_->asString();
 }
@@ -159,22 +163,205 @@ CodeGenerator::visitDoubleToInt32(LDoubl
     FloatRegister input = ToFloatRegister(lir->input());
     Register output = ToRegister(lir->output());
     emitDoubleToInt32(input, output, &fail, lir->mir()->canBeNegativeZero());
     if (!bailoutFrom(&fail, lir->snapshot()))
         return false;
     return true;
 }
 
+void
+CodeGenerator::emitOOLTestObject(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch)
+{
+    saveVolatile(scratch);
+    masm.setupUnalignedABICall(1, scratch);
+    masm.passABIArg(objreg);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ObjectEmulatesUndefined));
+    masm.storeCallResult(scratch);
+    restoreVolatile(scratch);
+
+    masm.branchTest32(Assembler::NonZero, scratch, scratch, ifFalsy);
+    masm.jump(ifTruthy);
+}
+
+// Base out-of-line code generator for all tests of the truthiness of an
+// object, where the object might not be truthy.  (Recall that per spec all
+// objects are truthy, but we implement the JSCLASS_EMULATES_UNDEFINED class
+// flag to permit objects to look like |undefined| in certain contexts,
+// including in object truthiness testing.)  We check truthiness inline except
+// when we're testing it on a proxy (or if TI guarantees us that the specified
+// object will never emulate |undefined|), in which case out-of-line code will
+// call EmulatesUndefined for a conclusive answer.
+class OutOfLineTestObject : public OutOfLineCodeBase<CodeGenerator>
+{
+    Register objreg_;
+    Register scratch_;
+
+    Label *ifTruthy_;
+    Label *ifFalsy_;
+
+#ifdef DEBUG
+    bool initialized() { return ifTruthy_ != NULL; }
+#endif
+
+  public:
+    OutOfLineTestObject()
+#ifdef DEBUG
+      : ifTruthy_(NULL), ifFalsy_(NULL)
+#endif
+    { }
+
+    bool accept(CodeGenerator *codegen) MOZ_FINAL MOZ_OVERRIDE {
+        MOZ_ASSERT(initialized());
+        codegen->emitOOLTestObject(objreg_, ifTruthy_, ifFalsy_, scratch_);
+        return true;
+    }
+
+    // Specify the register where the object to be tested is found, labels to
+    // jump to if the object is truthy or falsy, and a scratch register for
+    // use in the out-of-line path.
+    void setInputAndTargets(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch) {
+        MOZ_ASSERT(!initialized());
+        MOZ_ASSERT(ifTruthy);
+        objreg_ = objreg;
+        scratch_ = scratch;
+        ifTruthy_ = ifTruthy;
+        ifFalsy_ = ifFalsy;
+    }
+};
+
+// A subclass of OutOfLineTestObject containing two extra labels, for use when
+// the ifTruthy/ifFalsy labels are needed in inline code as well as out-of-line
+// code.  The user should bind these labels in inline code, and specify them as
+// targets via setInputAndTargets, as appropriate.
+class OutOfLineTestObjectWithLabels : public OutOfLineTestObject
+{
+    Label label1_;
+    Label label2_;
+
+  public:
+    OutOfLineTestObjectWithLabels() { }
+
+    Label *label1() { return &label1_; }
+    Label *label2() { return &label2_; }
+};
+
+void
+CodeGenerator::testObjectTruthy(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch,
+                                OutOfLineTestObject *ool)
+{
+    ool->setInputAndTargets(objreg, ifTruthy, ifFalsy, scratch);
+
+    // Perform a fast-path check of the object's class flags if the object's
+    // not a proxy.  Let out-of-line code handle the slow cases that require
+    // saving registers, making a function call, and restoring registers.
+    //
+    // The branches to out-of-line code here implement a conservative version
+    // of the JSObject::isWrapper test performed in EmulatesUndefined.  If none
+    // of the branches are taken, we can check class flags directly.
+    masm.loadObjClass(objreg, scratch);
+
+    Label *outOfLineTest = ool->entry();
+    masm.branchPtr(Assembler::Equal, scratch, ImmWord(&ObjectProxyClass), outOfLineTest);
+    masm.branchPtr(Assembler::Equal, scratch, ImmWord(&OuterWindowProxyClass), outOfLineTest);
+    masm.branchPtr(Assembler::Equal, scratch, ImmWord(&FunctionProxyClass), outOfLineTest);
+
+    masm.branchTest32(Assembler::Zero, Address(scratch, Class::offsetOfFlags()),
+                      Imm32(JSCLASS_EMULATES_UNDEFINED), ifTruthy);
+    masm.jump(ifFalsy);
+}
+
+void
+CodeGenerator::testValueTruthy(const ValueOperand &value,
+                               const LDefinition *scratch1, const LDefinition *scratch2,
+                               FloatRegister fr,
+                               Label *ifTruthy, Label *ifFalsy,
+                               OutOfLineTestObject *ool)
+{
+    Register tag = masm.splitTagForTest(value);
+    Assembler::Condition cond;
+
+    // Eventually we will want some sort of type filter here. For now, just
+    // emit all easy cases. For speed we use the cached tag for all comparison,
+    // except for doubles, which we test last (as the operation can clobber the
+    // tag, which may be in ScratchReg).
+    masm.branchTestUndefined(Assembler::Equal, tag, ifFalsy);
+    masm.branchTestNull(Assembler::Equal, tag, ifFalsy);
+
+    Label notBoolean;
+    masm.branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
+    masm.branchTestBooleanTruthy(false, value, ifFalsy);
+    masm.jump(ifTruthy);
+    masm.bind(&notBoolean);
+
+    Label notInt32;
+    masm.branchTestInt32(Assembler::NotEqual, tag, &notInt32);
+    cond = masm.testInt32Truthy(false, value);
+    masm.j(cond, ifFalsy);
+    masm.jump(ifTruthy);
+    masm.bind(&notInt32);
+
+    if (ool) {
+        Label notObject;
+
+        masm.branchTestObject(Assembler::NotEqual, tag, &notObject);
+
+        Register objreg = masm.extractObject(value, ToRegister(scratch1));
+        testObjectTruthy(objreg, ifTruthy, ifFalsy, ToRegister(scratch2), ool);
+
+        masm.bind(&notObject);
+    } else {
+        masm.branchTestObject(Assembler::Equal, tag, ifTruthy);
+    }
+
+    // Test if a string is non-empty.
+    Label notString;
+    masm.branchTestString(Assembler::NotEqual, tag, &notString);
+    cond = masm.testStringTruthy(false, value);
+    masm.j(cond, ifFalsy);
+    masm.jump(ifTruthy);
+    masm.bind(&notString);
+
+    // If we reach here the value is a double.
+    masm.unboxDouble(value, fr);
+    cond = masm.testDoubleTruthy(false, fr);
+    masm.j(cond, ifFalsy);
+    masm.jump(ifTruthy);
+}
+
+bool
+CodeGenerator::visitTestOAndBranch(LTestOAndBranch *lir)
+{
+    MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
+               "Objects which can't emulate undefined should have been constant-folded");
+
+    OutOfLineTestObject *ool = new OutOfLineTestObject();
+    if (!addOutOfLineCode(ool))
+        return false;
+
+    testObjectTruthy(ToRegister(lir->input()), lir->ifTruthy(), lir->ifFalsy(),
+                     ToRegister(lir->temp()), ool);
+    return true;
+
+}
+
 bool
 CodeGenerator::visitTestVAndBranch(LTestVAndBranch *lir)
 {
-    const ValueOperand value = ToValue(lir, LTestVAndBranch::Input);
-    masm.branchTestValueTruthy(value, lir->ifTrue(), ToFloatRegister(lir->tempFloat()));
-    masm.jump(lir->ifFalse());
+    OutOfLineTestObject *ool = NULL;
+    if (lir->mir()->operandMightEmulateUndefined()) {
+        ool = new OutOfLineTestObject();
+        if (!addOutOfLineCode(ool))
+            return false;
+    }
+
+    testValueTruthy(ToValue(lir, LTestVAndBranch::Input),
+                    lir->temp1(), lir->temp2(),
+                    ToFloatRegister(lir->tempFloat()),
+                    lir->ifTruthy(), lir->ifFalsy(), ool);
     return true;
 }
 
 bool
 CodeGenerator::visitPolyInlineDispatch(LPolyInlineDispatch *lir)
 {
     MPolyInlineDispatch *mir = lir->mir();
     Register inputReg = ToRegister(lir->input());
@@ -2245,37 +2432,74 @@ CodeGenerator::visitCompareV(LCompareV *
 
       default:
         JS_NOT_REACHED("Unexpected compare op");
         return false;
     }
 }
 
 bool
-CodeGenerator::visitIsNullOrUndefined(LIsNullOrUndefined *lir)
+CodeGenerator::visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir)
 {
     JSOp op = lir->mir()->jsop();
     MIRType specialization = lir->mir()->specialization();
     JS_ASSERT(IsNullOrUndefined(specialization));
 
-    const ValueOperand value = ToValue(lir, LIsNullOrUndefined::Value);
+    const ValueOperand value = ToValue(lir, LIsNullOrLikeUndefined::Value);
     Register output = ToRegister(lir->output());
 
     if (op == JSOP_EQ || op == JSOP_NE) {
+        MOZ_ASSERT(lir->mir()->lhs()->type() != MIRType_Object ||
+                   lir->mir()->operandMightEmulateUndefined(),
+                   "Operands which can't emulate undefined should have been folded");
+
+        OutOfLineTestObjectWithLabels *ool = NULL;
+        Maybe<Label> label1, label2;
+        Label *nullOrLikeUndefined;
+        Label *notNullOrLikeUndefined;
+        if (lir->mir()->operandMightEmulateUndefined()) {
+            ool = new OutOfLineTestObjectWithLabels();
+            if (!addOutOfLineCode(ool))
+                return false;
+            nullOrLikeUndefined = ool->label1();
+            notNullOrLikeUndefined = ool->label2();
+        } else {
+            label1.construct();
+            label2.construct();
+            nullOrLikeUndefined = label1.addr();
+            notNullOrLikeUndefined = label2.addr();
+        }
+
         Register tag = masm.splitTagForTest(value);
 
-        Label nullOrUndefined, done;
-        masm.branchTestNull(Assembler::Equal, tag, &nullOrUndefined);
-        masm.branchTestUndefined(Assembler::Equal, tag, &nullOrUndefined);
-
+        masm.branchTestNull(Assembler::Equal, tag, nullOrLikeUndefined);
+        masm.branchTestUndefined(Assembler::Equal, tag, nullOrLikeUndefined);
+
+        if (ool) {
+            // Check whether it's a truthy object or a falsy object that emulates
+            // undefined.
+            masm.branchTestObject(Assembler::NotEqual, tag, notNullOrLikeUndefined);
+
+            Register objreg = masm.extractObject(value, ToRegister(lir->temp0()));
+            testObjectTruthy(objreg, notNullOrLikeUndefined, nullOrLikeUndefined,
+                             ToRegister(lir->temp1()), ool);
+        }
+
+        Label done;
+
+        // It's not null or undefined, and if it's an object it doesn't
+        // emulate undefined, so it's not like undefined.
+        masm.bind(notNullOrLikeUndefined);
         masm.move32(Imm32(op == JSOP_NE), output);
         masm.jump(&done);
 
-        masm.bind(&nullOrUndefined);
+        masm.bind(nullOrLikeUndefined);
         masm.move32(Imm32(op == JSOP_EQ), output);
+
+        // Both branches meet here.
         masm.bind(&done);
         return true;
     }
 
     JS_ASSERT(op == JSOP_STRICTEQ || op == JSOP_STRICTNE);
 
     Assembler::Condition cond = JSOpToCondition(op);
     if (specialization == MIRType_Null)
@@ -2283,43 +2507,65 @@ CodeGenerator::visitIsNullOrUndefined(LI
     else
         cond = masm.testUndefined(cond, value);
 
     emitSet(cond, output);
     return true;
 }
 
 bool
-CodeGenerator::visitIsNullOrUndefinedAndBranch(LIsNullOrUndefinedAndBranch *lir)
+CodeGenerator::visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBranch *lir)
 {
     JSOp op = lir->mir()->jsop();
     MIRType specialization = lir->mir()->specialization();
     JS_ASSERT(IsNullOrUndefined(specialization));
 
-    const ValueOperand value = ToValue(lir, LIsNullOrUndefinedAndBranch::Value);
+    const ValueOperand value = ToValue(lir, LIsNullOrLikeUndefinedAndBranch::Value);
 
     if (op == JSOP_EQ || op == JSOP_NE) {
         MBasicBlock *ifTrue;
         MBasicBlock *ifFalse;
 
         if (op == JSOP_EQ) {
             ifTrue = lir->ifTrue();
             ifFalse = lir->ifFalse();
         } else {
             // Swap branches.
             ifTrue = lir->ifFalse();
             ifFalse = lir->ifTrue();
             op = JSOP_EQ;
         }
 
+        MOZ_ASSERT(lir->mir()->lhs()->type() != MIRType_Object ||
+                   lir->mir()->operandMightEmulateUndefined(),
+                   "Operands which can't emulate undefined should have been folded");
+
+        OutOfLineTestObject *ool = NULL;
+        if (lir->mir()->operandMightEmulateUndefined()) {
+            ool = new OutOfLineTestObject();
+            if (!addOutOfLineCode(ool))
+                return false;
+        }
+
         Register tag = masm.splitTagForTest(value);
-        masm.branchTestNull(Assembler::Equal, tag, ifTrue->lir()->label());
-
-        Assembler::Condition cond = masm.testUndefined(Assembler::Equal, tag);
-        emitBranch(cond, ifTrue, ifFalse);
+        Label *ifTrueLabel = ifTrue->lir()->label();
+        Label *ifFalseLabel = ifFalse->lir()->label();
+
+        masm.branchTestNull(Assembler::Equal, tag, ifTrueLabel);
+        masm.branchTestUndefined(Assembler::Equal, tag, ifTrueLabel);
+
+        if (ool) {
+            masm.branchTestObject(Assembler::NotEqual, tag, ifFalseLabel);
+
+            // Objects that emulate undefined are loosely equal to null/undefined.
+            Register objreg = masm.extractObject(value, ToRegister(lir->temp0()));
+            testObjectTruthy(objreg, ifFalseLabel, ifTrueLabel, ToRegister(lir->temp1()), ool);
+        } else {
+            masm.jump(ifFalseLabel);
+        }
         return true;
     }
 
     JS_ASSERT(op == JSOP_STRICTEQ || op == JSOP_STRICTNE);
 
     Assembler::Condition cond = JSOpToCondition(op);
     if (specialization == MIRType_Null)
         cond = masm.testNull(cond, value);
@@ -2329,16 +2575,91 @@ CodeGenerator::visitIsNullOrUndefinedAnd
     emitBranch(cond, lir->ifTrue(), lir->ifFalse());
     return true;
 }
 
 typedef JSString *(*ConcatStringsFn)(JSContext *, HandleString, HandleString);
 static const VMFunction ConcatStringsInfo = FunctionInfo<ConcatStringsFn>(js_ConcatStrings);
 
 bool
+CodeGenerator::visitEmulatesUndefined(LEmulatesUndefined *lir)
+{
+    MOZ_ASSERT(IsNullOrUndefined(lir->mir()->specialization()));
+    MOZ_ASSERT(lir->mir()->lhs()->type() == MIRType_Object);
+    MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
+               "If the object couldn't emulate undefined, this should have been folded.");
+
+    JSOp op = lir->mir()->jsop();
+    MOZ_ASSERT(op == JSOP_EQ || op == JSOP_NE, "Strict equality should have been folded");
+
+    OutOfLineTestObjectWithLabels *ool = new OutOfLineTestObjectWithLabels();
+    if (!addOutOfLineCode(ool))
+        return false;
+
+    Label *emulatesUndefined = ool->label1();
+    Label *doesntEmulateUndefined = ool->label2();
+
+    Register objreg = ToRegister(lir->input());
+    Register output = ToRegister(lir->output());
+    testObjectTruthy(objreg, doesntEmulateUndefined, emulatesUndefined, output, ool);
+
+    Label done;
+
+    masm.bind(doesntEmulateUndefined);
+    masm.move32(Imm32(op == JSOP_NE), output);
+    masm.jump(&done);
+
+    masm.bind(emulatesUndefined);
+    masm.move32(Imm32(op == JSOP_EQ), output);
+    masm.bind(&done);
+    return true;
+}
+
+bool
+CodeGenerator::visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir)
+{
+    MOZ_ASSERT(IsNullOrUndefined(lir->mir()->specialization()));
+    MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
+               "Operands which can't emulate undefined should have been folded");
+
+    JSOp op = lir->mir()->jsop();
+    MOZ_ASSERT(op == JSOP_EQ || op == JSOP_NE, "Strict equality should have been folded");
+
+    OutOfLineTestObject *ool = new OutOfLineTestObject();
+    if (!addOutOfLineCode(ool))
+        return false;
+
+    Label *equal;
+    Label *unequal;
+
+    {
+        MBasicBlock *ifTrue;
+        MBasicBlock *ifFalse;
+
+        if (op == JSOP_EQ) {
+            ifTrue = lir->ifTrue();
+            ifFalse = lir->ifFalse();
+        } else {
+            // Swap branches.
+            ifTrue = lir->ifFalse();
+            ifFalse = lir->ifTrue();
+            op = JSOP_EQ;
+        }
+
+        equal = ifTrue->lir()->label();
+        unequal = ifFalse->lir()->label();
+    }
+
+    Register objreg = ToRegister(lir->input());
+
+    testObjectTruthy(objreg, unequal, equal, ToRegister(lir->temp()), ool);
+    return true;
+}
+
+bool
 CodeGenerator::visitConcat(LConcat *lir)
 {
     pushArg(ToRegister(lir->rhs()));
     pushArg(ToRegister(lir->lhs()));
     if (!callVM(ConcatStringsInfo, lir))
         return false;
     return true;
 }
@@ -2412,29 +2733,79 @@ CodeGenerator::visitSetInitializedLength
     masm.bumpKey(&index, 1);
     masm.storeKey(index, initLength);
     // Restore register value if it is used/captured after.
     masm.bumpKey(&index, -1);
     return true;
 }
 
 bool
+CodeGenerator::visitNotO(LNotO *lir)
+{
+    MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
+               "This should be constant-folded if the object can't emulate undefined.");
+
+    OutOfLineTestObjectWithLabels *ool = new OutOfLineTestObjectWithLabels();
+    if (!addOutOfLineCode(ool))
+        return false;
+
+    Label *ifTruthy = ool->label1();
+    Label *ifFalsy = ool->label2();
+
+    Register objreg = ToRegister(lir->input());
+    Register output = ToRegister(lir->output());
+    testObjectTruthy(objreg, ifTruthy, ifFalsy, output, ool);
+
+    Label join;
+
+    masm.bind(ifTruthy);
+    masm.move32(Imm32(0), output);
+    masm.jump(&join);
+
+    masm.bind(ifFalsy);
+    masm.move32(Imm32(1), output);
+
+    masm.bind(&join);
+    return true;
+}
+
+bool
 CodeGenerator::visitNotV(LNotV *lir)
 {
-    Label setFalse;
+    Maybe<Label> ifTruthyLabel, ifFalsyLabel;
+    Label *ifTruthy;
+    Label *ifFalsy;
+
+    OutOfLineTestObjectWithLabels *ool = NULL;
+    if (lir->mir()->operandMightEmulateUndefined()) {
+        ool = new OutOfLineTestObjectWithLabels();
+        if (!addOutOfLineCode(ool))
+            return false;
+        ifTruthy = ool->label1();
+        ifFalsy = ool->label2();
+    } else {
+        ifTruthyLabel.construct();
+        ifFalsyLabel.construct();
+        ifTruthy = ifTruthyLabel.addr();
+        ifFalsy = ifFalsyLabel.addr();
+    }
+
+    testValueTruthy(ToValue(lir, LNotV::Input), lir->temp1(), lir->temp2(),
+                    ToFloatRegister(lir->tempFloat()),
+                    ifTruthy, ifFalsy, ool);
+
     Label join;
-    masm.branchTestValueTruthy(ToValue(lir, LNotV::Input), &setFalse, ToFloatRegister(lir->tempFloat()));
-
-    // fallthrough to set true
-    masm.move32(Imm32(1), ToRegister(lir->getDef(0)));
+    Register output = ToRegister(lir->output());
+
+    masm.bind(ifFalsy);
+    masm.move32(Imm32(1), output);
     masm.jump(&join);
 
-    // true case rediercts to setFalse
-    masm.bind(&setFalse);
-    masm.move32(Imm32(0), ToRegister(lir->getDef(0)));
+    masm.bind(ifTruthy);
+    masm.move32(Imm32(0), output);
 
     // both branches meet here.
     masm.bind(&join);
     return true;
 }
 
 bool
 CodeGenerator::visitBoundsCheck(LBoundsCheck *lir)
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -16,16 +16,17 @@
 # include "arm/CodeGenerator-arm.h"
 #else
 #error "CPU Not Supported"
 #endif
 
 namespace js {
 namespace ion {
 
+class OutOfLineTestObject;
 class OutOfLineNewArray;
 class OutOfLineNewObject;
 class CheckOverRecursedFailure;
 class OutOfLineUnboxDouble;
 class OutOfLineCache;
 class OutOfLineStoreElementHole;
 class OutOfLineTypeOfV;
 class OutOfLineLoadTypedArray;
@@ -55,16 +56,18 @@ class CodeGenerator : public CodeGenerat
     bool visitDefVar(LDefVar *lir);
     bool visitOsrEntry(LOsrEntry *lir);
     bool visitOsrScopeChain(LOsrScopeChain *lir);
     bool visitStackArgT(LStackArgT *lir);
     bool visitStackArgV(LStackArgV *lir);
     bool visitValueToInt32(LValueToInt32 *lir);
     bool visitValueToDouble(LValueToDouble *lir);
     bool visitInt32ToDouble(LInt32ToDouble *lir);
+    void emitOOLTestObject(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch);
+    bool visitTestOAndBranch(LTestOAndBranch *lir);
     bool visitTestVAndBranch(LTestVAndBranch *lir);
     bool visitPolyInlineDispatch(LPolyInlineDispatch *lir);
     bool visitIntToString(LIntToString *lir);
     bool visitInteger(LInteger *lir);
     bool visitRegExp(LRegExp *lir);
     bool visitRegExpTest(LRegExpTest *lir);
     bool visitLambda(LLambda *lir);
     bool visitLambdaForSingleton(LLambdaForSingleton *lir);
@@ -101,16 +104,17 @@ class CodeGenerator : public CodeGenerat
     bool visitCreateThisWithTemplate(LCreateThisWithTemplate *lir);
     bool visitReturnFromCtor(LReturnFromCtor *lir);
     bool visitArrayLength(LArrayLength *lir);
     bool visitTypedArrayLength(LTypedArrayLength *lir);
     bool visitTypedArrayElements(LTypedArrayElements *lir);
     bool visitStringLength(LStringLength *lir);
     bool visitInitializedLength(LInitializedLength *lir);
     bool visitSetInitializedLength(LSetInitializedLength *lir);
+    bool visitNotO(LNotO *ins);
     bool visitNotV(LNotV *ins);
     bool visitBoundsCheck(LBoundsCheck *lir);
     bool visitBoundsCheckRange(LBoundsCheckRange *lir);
     bool visitBoundsCheckLower(LBoundsCheckLower *lir);
     bool visitLoadFixedSlotV(LLoadFixedSlotV *ins);
     bool visitLoadFixedSlotT(LLoadFixedSlotT *ins);
     bool visitStoreFixedSlotV(LStoreFixedSlotV *ins);
     bool visitStoreFixedSlotT(LStoreFixedSlotT *ins);
@@ -119,18 +123,20 @@ class CodeGenerator : public CodeGenerat
     bool visitPowD(LPowD *lir);
     bool visitRandom(LRandom *lir);
     bool visitMathFunctionD(LMathFunctionD *ins);
     bool visitModD(LModD *ins);
     bool visitMinMaxI(LMinMaxI *lir);
     bool visitBinaryV(LBinaryV *lir);
     bool visitCompareS(LCompareS *lir);
     bool visitCompareV(LCompareV *lir);
-    bool visitIsNullOrUndefined(LIsNullOrUndefined *lir);
-    bool visitIsNullOrUndefinedAndBranch(LIsNullOrUndefinedAndBranch *lir);
+    bool visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir);
+    bool visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBranch *lir);
+    bool visitEmulatesUndefined(LEmulatesUndefined *lir);
+    bool visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir);
     bool visitConcat(LConcat *lir);
     bool visitCharCodeAt(LCharCodeAt *lir);
     bool visitFromCharCode(LFromCharCode *lir);
     bool visitFunctionEnvironment(LFunctionEnvironment *lir);
     bool visitCallGetProperty(LCallGetProperty *lir);
     bool visitCallGetElement(LCallGetElement *lir);
     bool visitCallSetElement(LCallSetElement *lir);
     bool visitThrow(LThrow *lir);
@@ -220,15 +226,31 @@ class CodeGenerator : public CodeGenerat
   private:
     bool visitCache(LInstruction *load);
     bool visitCallSetProperty(LInstruction *ins);
 
     ConstantOrRegister getSetPropertyValue(LInstruction *ins);
     bool generateBranchV(const ValueOperand &value, Label *ifTrue, Label *ifFalse, FloatRegister fr);
 
     IonScriptCounts *maybeCreateScriptCounts();
+
+    // Test whether value is truthy or not and jump to the corresponding label.
+    // If the value can be an object that emulates |undefined|, |ool| must be
+    // non-null; otherwise it may be null (and the scratch definitions should
+    // be bogus), in which case an object encountered here will always be
+    // truthy.
+    void testValueTruthy(const ValueOperand &value,
+                         const LDefinition *scratch1, const LDefinition *scratch2,
+                         FloatRegister fr,
+                         Label *ifTruthy, Label *ifFalsy,
+                         OutOfLineTestObject *ool);
+
+    // Like testValueTruthy but takes an object, and |ool| must be non-null.
+    // (If it's known that an object can never emulate |undefined| it shouldn't
+    // be tested in the first place.)
+    void testObjectTruthy(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch,
+                          OutOfLineTestObject *ool);
 };
 
 } // namespace ion
 } // namespace js
 
 #endif // jsion_codegen_h__
-
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -2345,17 +2345,18 @@ IonBuilder::lookupSwitch(JSOp op, jssrcn
 
         // Go back and fill in the MTest for the previous case block, or add the MGoto
         // to the current block
         if (i == 0) {
             // prevCond is definitely NULL, end 'current' with MGoto to this case.
             current->end(MGoto::New(cond));
         } else {
             // End previous conditional block with an MTest.
-            prevCond->end(MTest::New(prevCmpIns, prevBody, cond));
+            MTest *test = MTest::New(prevCmpIns, prevBody, cond);
+            prevCond->end(test);
 
             // If the previous cond shared its body with a prior cond, then
             // add the previous cond as a predecessor to its body (since it's
             // now finished).
             if (prevShared)
                 prevBody->addPredecessor(prevCond);
         }
 
@@ -2383,17 +2384,18 @@ IonBuilder::lookupSwitch(JSOp op, jssrcn
     // Add edge from last conditional block to the default block
     if (defaultBody == prevBody) {
         // Last conditional block goes to default body on both comparison
         // success and comparison failure.
         prevCond->end(MGoto::New(defaultBody));
     } else {
         // Last conditional block has body that is distinct from
         // the default block.
-        prevCond->end(MTest::New(prevCmpIns, prevBody, defaultBody));
+        MTest *test = MTest::New(prevCmpIns, prevBody, defaultBody);
+        prevCond->end(test);
 
         // Add the cond as a predecessor as a default, but only if
         // the default is shared with another block, because otherwise
         // the default block would have been constructed with the final
         // cond as its predecessor anyway.
         if (defaultShared)
             defaultBody->addPredecessor(prevCond);
     }
@@ -2715,34 +2717,35 @@ IonBuilder::processCondSwitchBody(CFGSta
     else
         state.stopAt = state.condswitch.exitpc;
     return ControlStatus_Jumped;
 }
 
 bool
 IonBuilder::jsop_andor(JSOp op)
 {
+    JS_ASSERT(op == JSOP_AND || op == JSOP_OR);
+
     jsbytecode *rhsStart = pc + js_CodeSpec[op].length;
     jsbytecode *joinStart = pc + GetJumpOffset(pc);
     JS_ASSERT(joinStart > pc);
 
     // We have to leave the LHS on the stack.
     MDefinition *lhs = current->peek(-1);
 
     MBasicBlock *evalRhs = newBlock(current, rhsStart);
     MBasicBlock *join = newBlock(current, joinStart);
     if (!evalRhs || !join)
         return false;
 
-    if (op == JSOP_AND) {
-        current->end(MTest::New(lhs, evalRhs, join));
-    } else {
-        JS_ASSERT(op == JSOP_OR);
-        current->end(MTest::New(lhs, join, evalRhs));
-    }
+    MTest *test = (op == JSOP_AND)
+                  ? MTest::New(lhs, evalRhs, join)
+                  : MTest::New(lhs, join, evalRhs);
+    test->infer(oracle->unaryTypes(script(), pc), cx);
+    current->end(test);
 
     if (!cfgStack_.append(CFGState::AndOr(joinStart, join)))
         return false;
 
     current = evalRhs;
     return true;
 }
 
@@ -2782,17 +2785,18 @@ IonBuilder::jsop_ifeq(JSOp op)
     MDefinition *ins = current->pop();
 
     // Create true and false branches.
     MBasicBlock *ifTrue = newBlock(current, trueStart);
     MBasicBlock *ifFalse = newBlock(current, falseStart);
     if (!ifTrue || !ifFalse)
         return false;
 
-    current->end(MTest::New(ins, ifTrue, ifFalse));
+    MTest *test = MTest::New(ins, ifTrue, ifFalse);
+    current->end(test);
 
     // The bytecode for if/ternary gets emitted either like this:
     //
     //    IFEQ X  ; src note (IF_ELSE, COND) points to the GOTO
     //    ...
     //    GOTO Z
     // X: ...     ; else/else if
     //    ...
@@ -3007,17 +3011,17 @@ IonBuilder::jsop_binary(JSOp op, MDefini
 
       default:
         JS_NOT_REACHED("unexpected binary opcode");
         return false;
     }
 
     TypeOracle::BinaryTypes types = oracle->binaryTypes(script(), pc);
     current->add(ins);
-    ins->infer(cx, types);
+    ins->infer(types, cx);
     current->push(ins);
 
     if (ins->isEffectful())
         return resumeAfter(ins);
     return maybeInsertResume();
 }
 
 bool
@@ -4197,17 +4201,17 @@ IonBuilder::jsop_compare(JSOp op)
     MDefinition *right = current->pop();
     MDefinition *left = current->pop();
 
     MCompare *ins = MCompare::New(left, right, op);
     current->add(ins);
     current->push(ins);
 
     TypeOracle::BinaryTypes b = oracle->binaryTypes(script(), pc);
-    ins->infer(cx, b);
+    ins->infer(b, cx);
 
     if (ins->isEffectful() && !resumeAfter(ins))
         return false;
     return true;
 }
 
 JSObject *
 IonBuilder::getNewArrayTemplateObject(uint32_t count)
@@ -5721,16 +5725,17 @@ GetDefiniteSlot(JSContext *cx, types::St
 bool
 IonBuilder::jsop_not()
 {
     MDefinition *value = current->pop();
 
     MNot *ins = new MNot(value);
     current->add(ins);
     current->push(ins);
+    ins->infer(oracle->unaryTypes(script(), pc), cx);
     return true;
 }
 
 
 inline bool
 IonBuilder::TestCommonPropFunc(JSContext *cx, types::StackTypeSet *types, HandleId id,
                                JSFunction **funcp, bool isGetter, bool *isDOM,
                                MDefinition **guardOut)
--- a/js/src/ion/IonMacroAssembler.cpp
+++ b/js/src/ion/IonMacroAssembler.cpp
@@ -103,61 +103,16 @@ MacroAssembler::PopRegsInMaskIgnore(Regi
         diff -= sizeof(double);
         if (!ignore.has(*iter))
             loadDouble(Address(StackPointer, diff), *iter);
     }
 
     freeStack(reserved);
 }
 
-void
-MacroAssembler::branchTestValueTruthy(const ValueOperand &value, Label *ifTrue, FloatRegister fr)
-{
-    Register tag = splitTagForTest(value);
-    Label ifFalse;
-    Assembler::Condition cond;
-
-    // Eventually we will want some sort of type filter here. For now, just
-    // emit all easy cases. For speed we use the cached tag for all comparison,
-    // except for doubles, which we test last (as the operation can clobber the
-    // tag, which may be in ScratchReg).
-    branchTestUndefined(Assembler::Equal, tag, &ifFalse);
-
-    branchTestNull(Assembler::Equal, tag, &ifFalse);
-    branchTestObject(Assembler::Equal, tag, ifTrue);
-
-    Label notBoolean;
-    branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
-    branchTestBooleanTruthy(false, value, &ifFalse);
-    jump(ifTrue);
-    bind(&notBoolean);
-
-    Label notInt32;
-    branchTestInt32(Assembler::NotEqual, tag, &notInt32);
-    cond = testInt32Truthy(false, value);
-    j(cond, &ifFalse);
-    jump(ifTrue);
-    bind(&notInt32);
-
-    // Test if a string is non-empty.
-    Label notString;
-    branchTestString(Assembler::NotEqual, tag, &notString);
-    cond = testStringTruthy(false, value);
-    j(cond, &ifFalse);
-    jump(ifTrue);
-    bind(&notString);
-
-    // If we reach here the value is a double.
-    unboxDouble(value, fr);
-    cond = testDoubleTruthy(false, fr);
-    j(cond, &ifFalse);
-    jump(ifTrue);
-    bind(&ifFalse);
-}
-
 template<typename T>
 void
 MacroAssembler::loadFromTypedArray(int arrayType, const T &src, AnyRegister dest, Register temp,
                                    Label *fail)
 {
     switch (arrayType) {
       case TypedArray::TYPE_INT8:
         load8SignExtend(src, dest.gpr());
--- a/js/src/ion/IonMacroAssembler.h
+++ b/js/src/ion/IonMacroAssembler.h
@@ -267,18 +267,16 @@ class MacroAssembler : public MacroAssem
     void PopRegsInMask(RegisterSet set) {
         PopRegsInMaskIgnore(set, RegisterSet());
     }
     void PopRegsInMask(GeneralRegisterSet set) {
         PopRegsInMask(RegisterSet(set, FloatRegisterSet()));
     }
     void PopRegsInMaskIgnore(RegisterSet set, RegisterSet ignore);
 
-    void branchTestValueTruthy(const ValueOperand &value, Label *ifTrue, FloatRegister fr);
-
     void branchIfFunctionHasNoScript(Register fun, Label *label) {
         // 16-bit loads are slow and unaligned 32-bit loads may be too so
         // perform an aligned 32-bit load and adjust the bitmask accordingly.
         JS_STATIC_ASSERT(offsetof(JSFunction, nargs) % sizeof(uint32_t) == 0);
         JS_STATIC_ASSERT(offsetof(JSFunction, flags) == offsetof(JSFunction, nargs) + 2);
         JS_STATIC_ASSERT(IS_LITTLE_ENDIAN);
         Address address(fun, offsetof(JSFunction, nargs));
         uint32_t bit = JSFunction::INTERPRETED << 16;
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -854,40 +854,94 @@ class LTestDAndBranch : public LInstruct
     MBasicBlock *ifTrue() const {
         return ifTrue_;
     }
     MBasicBlock *ifFalse() const {
         return ifFalse_;
     }
 };
 
-// Takes in a boxed value and tests it for truthiness.
-class LTestVAndBranch : public LInstructionHelper<0, BOX_PIECES, 1>
+// Takes an object and tests it for truthiness.  An object is falsy iff it
+// emulates |undefined|; see js::EmulatesUndefined.
+class LTestOAndBranch : public LInstructionHelper<0, 1, 1>
 {
-    MBasicBlock *ifTrue_;
-    MBasicBlock *ifFalse_;
+    MBasicBlock *ifTruthy_;
+    MBasicBlock *ifFalsy_;
+
+  public:
+    LIR_HEADER(TestOAndBranch)
+
+    LTestOAndBranch(const LAllocation &input, MBasicBlock *ifTruthy, MBasicBlock *ifFalsy,
+                    const LDefinition &temp)
+      : ifTruthy_(ifTruthy),
+        ifFalsy_(ifFalsy)
+    {
+        setOperand(0, input);
+        setTemp(0, temp);
+    }
+
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
+
+    Label *ifTruthy() {
+        return ifTruthy_->lir()->label();
+    }
+    Label *ifFalsy() {
+        return ifFalsy_->lir()->label();
+    }
+
+    MTest *mir() {
+        return mir_->toTest();
+    }
+};
+
+// Takes in a boxed value and tests it for truthiness.
+class LTestVAndBranch : public LInstructionHelper<0, BOX_PIECES, 3>
+{
+    MBasicBlock *ifTruthy_;
+    MBasicBlock *ifFalsy_;
 
   public:
     LIR_HEADER(TestVAndBranch)
 
-    LTestVAndBranch(MBasicBlock *ifTrue, MBasicBlock *ifFalse, const LDefinition &temp)
-      : ifTrue_(ifTrue),
-        ifFalse_(ifFalse)
+    LTestVAndBranch(MBasicBlock *ifTruthy, MBasicBlock *ifFalsy, const LDefinition &temp0,
+                    const LDefinition &temp1, const LDefinition &temp2)
+      : ifTruthy_(ifTruthy),
+        ifFalsy_(ifFalsy)
     {
-        setTemp(0, temp);
+        setTemp(0, temp0);
+        setTemp(1, temp1);
+        setTemp(2, temp2);
     }
 
     static const size_t Input = 0;
 
     const LAllocation *tempFloat() {
         return getTemp(0)->output();
     }
 
-    Label *ifTrue();
-    Label *ifFalse();
+    const LDefinition *temp1() {
+        return getTemp(1);
+    }
+
+    const LDefinition *temp2() {
+        return getTemp(2);
+    }
+
+    Label *ifTruthy() {
+        return ifTruthy_->lir()->label();
+    }
+    Label *ifFalsy() {
+        return ifFalsy_->lir()->label();
+    }
+
+    MTest *mir() {
+        return mir_->toTest();
+    }
 };
 
 class LPolyInlineDispatch : public LInstructionHelper<0, 1, 1>
 {
   // Accesses function/block table from MIR instruction.
   public:
     LIR_HEADER(PolyInlineDispatch)
 
@@ -1115,51 +1169,121 @@ class LCompareBAndBranch : public LInstr
     MBasicBlock *ifFalse() const {
         return ifFalse_;
     }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
-class LIsNullOrUndefined : public LInstructionHelper<1, BOX_PIECES, 0>
+class LIsNullOrLikeUndefined : public LInstructionHelper<1, BOX_PIECES, 2>
 {
   public:
-    LIR_HEADER(IsNullOrUndefined)
+    LIR_HEADER(IsNullOrLikeUndefined)
+
+    LIsNullOrLikeUndefined(const LDefinition &temp0, const LDefinition &temp1)
+    {
+        setTemp(0, temp0);
+        setTemp(1, temp1);
+    }
 
     static const size_t Value = 0;
 
     MCompare *mir() {
         return mir_->toCompare();
     }
+
+    const LDefinition *temp0() {
+        return getTemp(0);
+    }
+
+    const LDefinition *temp1() {
+        return getTemp(1);
+    }
 };
 
-class LIsNullOrUndefinedAndBranch : public LInstructionHelper<0, BOX_PIECES, 0>
+class LIsNullOrLikeUndefinedAndBranch : public LInstructionHelper<0, BOX_PIECES, 2>
 {
     MBasicBlock *ifTrue_;
     MBasicBlock *ifFalse_;
 
   public:
-    LIR_HEADER(IsNullOrUndefinedAndBranch)
-
-    LIsNullOrUndefinedAndBranch(MBasicBlock *ifTrue, MBasicBlock *ifFalse)
+    LIR_HEADER(IsNullOrLikeUndefinedAndBranch)
+
+    LIsNullOrLikeUndefinedAndBranch(MBasicBlock *ifTrue, MBasicBlock *ifFalse, const LDefinition &temp0, const LDefinition &temp1)
       : ifTrue_(ifTrue), ifFalse_(ifFalse)
-    { }
+    {
+        setTemp(0, temp0);
+        setTemp(1, temp1);
+    }
 
     static const size_t Value = 0;
 
     MBasicBlock *ifTrue() const {
         return ifTrue_;
     }
     MBasicBlock *ifFalse() const {
         return ifFalse_;
     }
     MCompare *mir() {
         return mir_->toCompare();
     }
+    const LDefinition *temp0() {
+        return getTemp(0);
+    }
+    const LDefinition *temp1() {
+        return getTemp(1);
+    }
+};
+
+// Takes an object and tests whether it emulates |undefined|, as determined by
+// the JSCLASS_EMULATES_UNDEFINED class flag on unwrapped objects.  See also
+// js::EmulatesUndefined.
+class LEmulatesUndefined : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(EmulatesUndefined)
+
+    LEmulatesUndefined(const LAllocation &input)
+    {
+        setOperand(0, input);
+    }
+
+    MCompare *mir() {
+        return mir_->toCompare();
+    }
+};
+
+class LEmulatesUndefinedAndBranch : public LInstructionHelper<0, 1, 1>
+{
+    MBasicBlock *ifTrue_;
+    MBasicBlock *ifFalse_;
+
+  public:
+    LIR_HEADER(EmulatesUndefinedAndBranch)
+
+    LEmulatesUndefinedAndBranch(const LAllocation &input, MBasicBlock *ifTrue, MBasicBlock *ifFalse, const LDefinition &temp)
+      : ifTrue_(ifTrue), ifFalse_(ifFalse)
+    {
+        setOperand(0, input);
+        setTemp(0, temp);
+    }
+
+    MBasicBlock *ifTrue() const {
+        return ifTrue_;
+    }
+    MBasicBlock *ifFalse() const {
+        return ifFalse_;
+    }
+    MCompare *mir() {
+        return mir_->toCompare();
+    }
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
 };
 
 // Not operation on an integer.
 class LNotI : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(NotI)
 
@@ -1174,31 +1298,61 @@ class LNotD : public LInstructionHelper<
   public:
     LIR_HEADER(NotD)
 
     LNotD(const LAllocation &input) {
         setOperand(0, input);
     }
 };
 
+// Boolean complement operation on an object.
+class LNotO : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(NotO)
+
+    LNotO(const LAllocation &input)
+    {
+        setOperand(0, input);
+    }
+
+    MNot *mir() {
+        return mir_->toNot();
+    }
+};
+
 // Boolean complement operation on a value.
-class LNotV : public LInstructionHelper<1, BOX_PIECES, 1>
+class LNotV : public LInstructionHelper<1, BOX_PIECES, 3>
 {
   public:
     LIR_HEADER(NotV)
 
     static const size_t Input = 0;
-    LNotV(const LDefinition &temp)
+    LNotV(const LDefinition &temp0, const LDefinition &temp1, const LDefinition &temp2)
     {
-        setTemp(0, temp);
+        setTemp(0, temp0);
+        setTemp(1, temp1);
+        setTemp(2, temp2);
     }
 
     const LAllocation *tempFloat() {
         return getTemp(0)->output();
     }
+
+    const LDefinition *temp1() {
+        return getTemp(1);
+    }
+
+    const LDefinition *temp2() {
+        return getTemp(2);
+    }
+
+    MNot *mir() {
+        return mir_->toNot();
+    }
 };
 
 // Bitwise not operation, takes a 32-bit integer as input and returning
 // a 32-bit integer result as an output.
 class LBitNotI : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(BitNotI)
--- a/js/src/ion/LIR.cpp
+++ b/js/src/ion/LIR.cpp
@@ -370,21 +370,8 @@ LMoveGroup::printOperands(FILE *fp)
         const LMove &move = getMove(i);
         // Use two printfs, as LAllocation::toString is not reentrant.
         fprintf(fp, "[%s", move.from()->toString());
         fprintf(fp, " -> %s]", move.to()->toString());
         if (i != numMoves() - 1)
             fprintf(fp, ", ");
     }
 }
-
-Label *
-LTestVAndBranch::ifTrue()
-{
-    return ifTrue_->lir()->label();
-}
-
-Label *
-LTestVAndBranch::ifFalse()
-{
-    return ifFalse_->lir()->label();
-}
-
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -49,39 +49,43 @@
     _(ShiftI)                       \
     _(UrshD)                        \
     _(Return)                       \
     _(Throw)                        \
     _(Phi)                          \
     _(TestIAndBranch)               \
     _(TestDAndBranch)               \
     _(TestVAndBranch)               \
+    _(TestOAndBranch)               \
     _(PolyInlineDispatch)           \
     _(Compare)                      \
     _(CompareD)                     \
     _(CompareS)                     \
     _(CompareV)                     \
     _(CompareAndBranch)             \
     _(CompareDAndBranch)            \
     _(CompareB)                     \
     _(CompareBAndBranch)            \
-    _(IsNullOrUndefined)            \
-    _(IsNullOrUndefinedAndBranch)   \
+    _(IsNullOrLikeUndefined)        \
+    _(IsNullOrLikeUndefinedAndBranch)\
+    _(EmulatesUndefined)            \
+    _(EmulatesUndefinedAndBranch)   \
     _(MinMaxI)                      \
     _(MinMaxD)                      \
     _(NegD)                         \
     _(AbsI)                         \
     _(AbsD)                         \
     _(SqrtD)                        \
     _(PowI)                         \
     _(PowD)                         \
     _(Random)                       \
     _(MathFunctionD)                \
     _(NotI)                         \
     _(NotD)                         \
+    _(NotO)                         \
     _(NotV)                         \
     _(AddI)                         \
     _(SubI)                         \
     _(MulI)                         \
     _(MathD)                        \
     _(ModD)                         \
     _(BinaryV)                      \
     _(Concat)                       \
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -362,32 +362,49 @@ ReorderComparison(JSOp op, MDefinition *
 
 bool
 LIRGenerator::visitTest(MTest *test)
 {
     MDefinition *opd = test->getOperand(0);
     MBasicBlock *ifTrue = test->ifTrue();
     MBasicBlock *ifFalse = test->ifFalse();
 
+    // String is converted to length of string in the type analysis phase (see
+    // TestPolicy).
+    JS_ASSERT(opd->type() != MIRType_String);
+
     if (opd->type() == MIRType_Value) {
-        LTestVAndBranch *lir = new LTestVAndBranch(ifTrue, ifFalse, tempFloat());
+        LDefinition temp0, temp1;
+        if (test->operandMightEmulateUndefined()) {
+            temp0 = temp();
+            temp1 = temp();
+        } else {
+            temp0 = LDefinition::BogusTemp();
+            temp1 = LDefinition::BogusTemp();
+        }
+        LTestVAndBranch *lir = new LTestVAndBranch(ifTrue, ifFalse, tempFloat(), temp0, temp1);
         if (!useBox(lir, LTestVAndBranch::Input, opd))
             return false;
-        return add(lir);
+        return add(lir, test);
+    }
+
+    if (opd->type() == MIRType_Object) {
+        // If the object might emulate undefined, we have to test for that.
+        if (test->operandMightEmulateUndefined())
+            return add(new LTestOAndBranch(useRegister(opd), ifTrue, ifFalse, temp()), test);
+
+        // Otherwise we know it's truthy.
+        return add(new LGoto(ifTrue));
     }
 
     // These must be explicitly sniffed out since they are constants and have
     // no payload.
     if (opd->type() == MIRType_Undefined || opd->type() == MIRType_Null)
         return add(new LGoto(ifFalse));
 
-    // Objects are easy, too.
-    if (opd->type() == MIRType_Object)
-        return add(new LGoto(ifTrue));
-
     // Constant Double operand.
     if (opd->type() == MIRType_Double && opd->isConstant()) {
         bool result = ToBoolean(opd->toConstant()->value());
         return add(new LGoto(result ? ifTrue : ifFalse));
     }
 
     // Constant Int32 operand.
     if (opd->type() == MIRType_Int32 && opd->isConstant()) {
@@ -424,18 +441,37 @@ LIRGenerator::visitTest(MTest *test)
         if (comp->specialization() == MIRType_Double) {
             return add(new LCompareDAndBranch(useRegister(left), useRegister(right), ifTrue,
                                               ifFalse), comp);
         }
 
         // The second operand has known null/undefined type, so just test the
         // first operand.
         if (IsNullOrUndefined(comp->specialization())) {
-            LIsNullOrUndefinedAndBranch *lir = new LIsNullOrUndefinedAndBranch(ifTrue, ifFalse);
-            if (!useBox(lir, LIsNullOrUndefinedAndBranch::Value, left))
+            if (left->type() == MIRType_Object) {
+                MOZ_ASSERT(comp->operandMightEmulateUndefined(),
+                           "MCompare::tryFold should handle the never-emulates-undefined case");
+
+                LEmulatesUndefinedAndBranch *lir =
+                    new LEmulatesUndefinedAndBranch(useRegister(left), ifTrue, ifFalse, temp());
+                return add(lir, comp);
+            }
+
+            LDefinition temp0, temp1;
+            if (comp->operandMightEmulateUndefined()) {
+                temp0 = temp();
+                temp1 = temp();
+            } else {
+                temp0 = LDefinition::BogusTemp();
+                temp1 = LDefinition::BogusTemp();
+            }
+
+            LIsNullOrLikeUndefinedAndBranch *lir =
+                new LIsNullOrLikeUndefinedAndBranch(ifTrue, ifFalse, temp0, temp1);
+            if (!useBox(lir, LIsNullOrLikeUndefinedAndBranch::Value, left))
                 return false;
             return add(lir, comp);
         }
 
         if (comp->specialization() == MIRType_Boolean) {
             JS_ASSERT(left->type() == MIRType_Value);
             JS_ASSERT(right->type() == MIRType_Boolean);
 
@@ -502,17 +538,17 @@ LIRGenerator::visitCompare(MCompare *com
         if (comp->specialization() == MIRType_String) {
             LCompareS *lir = new LCompareS(useRegister(left), useRegister(right), temp());
             if (!define(lir, comp))
                 return false;
             return assignSafepoint(lir, comp);
         }
 
         // Sniff out if the output of this compare is used only for a branching.
-        // If it is, then we willl emit an LCompare*AndBranch instruction in place
+        // If it is, then we will emit an LCompare*AndBranch instruction in place
         // of this compare and any test that uses this compare. Thus, we can
         // ignore this Compare.
         if (CanEmitCompareAtUses(comp))
             return emitAtUses(comp);
 
         if (comp->specialization() == MIRType_Int32 || comp->specialization() == MIRType_Object) {
             JSOp op = ReorderComparison(comp->jsop(), &left, &right);
             LAllocation rhs = comp->specialization() == MIRType_Object
@@ -531,18 +567,34 @@ LIRGenerator::visitCompare(MCompare *com
             LCompareB *lir = new LCompareB(useRegisterOrConstant(right));
             if (!useBox(lir, LCompareB::Lhs, left))
                 return false;
             return define(lir, comp);
         }
 
         JS_ASSERT(IsNullOrUndefined(comp->specialization()));
 
-        LIsNullOrUndefined *lir = new LIsNullOrUndefined();
-        if (!useBox(lir, LIsNullOrUndefined::Value, comp->getOperand(0)))
+        if (left->type() == MIRType_Object) {
+            MOZ_ASSERT(comp->operandMightEmulateUndefined(),
+                       "MCompare::tryFold should have folded this away");
+
+            return define(new LEmulatesUndefined(useRegister(left)), comp);
+        }
+
+        LDefinition temp0, temp1;
+        if (comp->operandMightEmulateUndefined()) {
+            temp0 = temp();
+            temp1 = temp();
+        } else {
+            temp0 = LDefinition::BogusTemp();
+            temp1 = LDefinition::BogusTemp();
+        }
+
+        LIsNullOrLikeUndefined *lir = new LIsNullOrLikeUndefined(temp0, temp1);
+        if (!useBox(lir, LIsNullOrLikeUndefined::Value, left))
             return false;
         return define(lir, comp);
     }
 
     LCompareV *lir = new LCompareV();
     if (!useBoxAtStart(lir, LCompareV::LhsInput, left))
         return false;
     if (!useBoxAtStart(lir, LCompareV::RhsInput, right))
@@ -1386,42 +1438,57 @@ LIRGenerator::visitSetInitializedLength(
                                          useRegisterOrConstant(ins->index())), ins);
 }
 
 bool
 LIRGenerator::visitNot(MNot *ins)
 {
     MDefinition *op = ins->operand();
 
-    // String is converted to length of string in the IonBuilder phase
+    // String is converted to length of string in the type analysis phase (see
+    // TestPolicy).
     JS_ASSERT(op->type() != MIRType_String);
 
     // - boolean: x xor 1
     // - int32: LCompare(x, 0)
     // - double: LCompare(x, 0)
     // - null or undefined: true
-    // - object: false
+    // - object: false if it never emulates undefined, else LNotO(x)
     switch (op->type()) {
       case MIRType_Boolean: {
         MConstant *cons = MConstant::New(Int32Value(1));
         ins->block()->insertBefore(ins, cons);
         return lowerForALU(new LBitOpI(JSOP_BITXOR), ins, op, cons);
       }
       case MIRType_Int32: {
         return define(new LNotI(useRegisterAtStart(op)), ins);
       }
       case MIRType_Double:
         return define(new LNotD(useRegister(op)), ins);
       case MIRType_Undefined:
       case MIRType_Null:
         return define(new LInteger(1), ins);
-      case MIRType_Object:
-        return define(new LInteger(0), ins);
+      case MIRType_Object: {
+        // Objects that don't emulate undefined can be constant-folded.
+        if (!ins->operandMightEmulateUndefined())
+            return define(new LInteger(0), ins);
+        // All others require further work.
+        return define(new LNotO(useRegister(op)), ins);
+      }
       case MIRType_Value: {
-          LNotV *lir = new LNotV(tempFloat());
+        LDefinition temp0, temp1;
+        if (ins->operandMightEmulateUndefined()) {
+            temp0 = temp();
+            temp1 = temp();
+        } else {
+            temp0 = LDefinition::BogusTemp();
+            temp1 = LDefinition::BogusTemp();
+        }
+
+        LNotV *lir = new LNotV(tempFloat(), temp0, temp1);
         if (!useBox(lir, LNotV::Input, op))
             return false;
         return define(lir, ins);
       }
 
       default:
         JS_ASSERT(!"Unexpected MIRType.");
         return false;
--- a/js/src/ion/MIR.cpp
+++ b/js/src/ion/MIR.cpp
@@ -184,16 +184,36 @@ MDefinition::analyzeEdgeCasesBackward()
     return;
 }
 void
 MDefinition::analyzeTruncateBackward()
 {
     return;
 }
 
+static bool
+MaybeEmulatesUndefined(types::StackTypeSet *types, JSContext *cx)
+{
+    if (!types->maybeObject())
+        return false;
+    return types->hasObjectFlags(cx, types::OBJECT_FLAG_EMULATES_UNDEFINED);
+}
+
+void
+MTest::infer(const TypeOracle::UnaryTypes &u, JSContext *cx)
+{
+    if (!u.inTypes)
+        return;
+
+    JS_ASSERT(operandMightEmulateUndefined());
+
+    if (!MaybeEmulatesUndefined(u.inTypes, cx))
+        markOperandCantEmulateUndefined();
+}
+
 MDefinition *
 MTest::foldsTo(bool useValueNumbers)
 {
     MDefinition *op = getOperand(0);
 
     if (op->isNot())
         return MTest::New(op->toNot()->operand(), ifFalse(), ifTrue());
 
@@ -986,17 +1006,17 @@ bool
 MMul::canOverflow()
 {
     if (implicitTruncate_)
         return false;
     return !range() || !range()->isFinite();
 }
 
 void
-MBinaryArithInstruction::infer(JSContext *cx, const TypeOracle::BinaryTypes &b)
+MBinaryArithInstruction::infer(const TypeOracle::BinaryTypes &b, JSContext *cx)
 {
     // Retrieve type information of lhs and rhs
     // Rhs is defaulted to int32 first,
     // because in some cases there is no rhs type information
     MIRType lhs = MIRTypeFromValueType(b.lhsTypes->getKnownTypeTag());
     MIRType rhs = MIRType_Int32;
 
     // Test if types coerces to doubles
@@ -1060,21 +1080,26 @@ SafelyCoercesToDouble(JSContext *cx, typ
 
     if ((flags & converts) == flags)
         return true;
 
     return false;
 }
 
 void
-MCompare::infer(JSContext *cx, const TypeOracle::BinaryTypes &b)
+MCompare::infer(const TypeOracle::BinaryTypes &b, JSContext *cx)
 {
     if (!b.lhsTypes || !b.rhsTypes)
         return;
 
+    JS_ASSERT(operandMightEmulateUndefined());
+
+    if (!MaybeEmulatesUndefined(b.lhsTypes, cx) && !MaybeEmulatesUndefined(b.rhsTypes, cx))
+        markNoOperandEmulatesUndefined();
+
     MIRType lhs = MIRTypeFromValueType(b.lhsTypes->getKnownTypeTag());
     MIRType rhs = MIRTypeFromValueType(b.rhsTypes->getKnownTypeTag());
 
     // Strict integer or boolean comparisons may be treated as Int32.
     if ((lhs == MIRType_Int32 && rhs == MIRType_Int32) ||
         (lhs == MIRType_Boolean && rhs == MIRType_Boolean))
     {
         specialization_ = MIRType_Int32;
@@ -1119,24 +1144,22 @@ MCompare::infer(JSContext *cx, const Typ
         }
 
         if (lhs == MIRType_String && rhs == MIRType_String) {
             // We don't yet want to optimize relational string compares.
             specialization_ = MIRType_String;
             return;
         }
 
+        // Swap null/undefined lhs to rhs so we can test for it only on lhs.
         if (IsNullOrUndefined(lhs)) {
-            // Lowering expects the rhs to be null/undefined, so we have to
-            // swap the operands. This is necessary since we may not know which
-            // operand was null/undefined during lowering (both operands may have
-            // MIRType_Value).
-            specialization_ = lhs;
+            MIRType tmp = lhs;
+            lhs = rhs;
+            rhs = tmp;
             swapOperands();
-            return;
         }
 
         if (IsNullOrUndefined(rhs)) {
             specialization_ = rhs;
             return;
         }
     }
 
@@ -1386,20 +1409,23 @@ MCompare::tryFold(bool *result)
                 // Both sides have the same type, null or undefined.
                 *result = (op == JSOP_EQ || op == JSOP_STRICTEQ);
             } else {
                 // One side is null, the other side is undefined. The result is only
                 // true for loose equality.
                 *result = (op == JSOP_EQ || op == JSOP_STRICTNE);
             }
             return true;
+          case MIRType_Object:
+            if ((op == JSOP_EQ || op == JSOP_NE) && operandMightEmulateUndefined())
+                return false;
+            /* FALL THROUGH */
           case MIRType_Int32:
           case MIRType_Double:
           case MIRType_String:
-          case MIRType_Object:
           case MIRType_Boolean:
             *result = (op == JSOP_NE || op == JSOP_STRICTNE);
             return true;
           default:
             JS_NOT_REACHED("Unexpected type");
             return false;
         }
     }
@@ -1521,34 +1547,46 @@ MCompare::foldsTo(bool useValueNumbers)
     if (tryFold(&result))
         return MConstant::New(BooleanValue(result));
     else if (evaluateConstantOperands(&result))
         return MConstant::New(BooleanValue(result));
 
     return this;
 }
 
+void
+MNot::infer(const TypeOracle::UnaryTypes &u, JSContext *cx)
+{
+    if (!u.inTypes)
+        return;
+
+    JS_ASSERT(operandMightEmulateUndefined());
+
+    if (!MaybeEmulatesUndefined(u.inTypes, cx))
+        markOperandCantEmulateUndefined();
+}
+
 MDefinition *
 MNot::foldsTo(bool useValueNumbers)
 {
     // Fold if the input is constant
     if (operand()->isConstant()) {
        const Value &v = operand()->toConstant()->value();
-        // ValueToBoolean can cause no side-effects, so this is safe.
+        // ToBoolean can cause no side effects, so this is safe.
         return MConstant::New(BooleanValue(!ToBoolean(v)));
     }
 
-    // NOT of an object is always false
-    if (operand()->type() == MIRType_Object)
-        return MConstant::New(BooleanValue(false));
-
     // NOT of an undefined or null value is always true
     if (operand()->type() == MIRType_Undefined || operand()->type() == MIRType_Null)
         return MConstant::New(BooleanValue(true));
 
+    // NOT of an object that can't emulate undefined is always false.
+    if (operand()->type() == MIRType_Object && !operandMightEmulateUndefined())
+        return MConstant::New(BooleanValue(false));
+
     return this;
 }
 
 bool
 MBoundsCheckLower::fallible()
 {
     return !range() || range()->lower() < minimum_;
 }
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -888,17 +888,21 @@ NegateBranchDirection(BranchDirection di
 }
 
 // Tests if the input instruction evaluates to true or false, and jumps to the
 // start of a corresponding basic block.
 class MTest
   : public MAryControlInstruction<1, 2>,
     public TestPolicy
 {
-    MTest(MDefinition *ins, MBasicBlock *if_true, MBasicBlock *if_false) {
+    bool operandMightEmulateUndefined_;
+
+    MTest(MDefinition *ins, MBasicBlock *if_true, MBasicBlock *if_false)
+      : operandMightEmulateUndefined_(true)
+    {
         initOperand(0, ins);
         setSuccessor(0, if_true);
         setSuccessor(1, if_false);
     }
 
   public:
     INSTRUCTION_HEADER(Test)
     static MTest *New(MDefinition *ins,
@@ -915,17 +919,25 @@ class MTest
     }
     TypePolicy *typePolicy() {
         return this;
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    void infer(const TypeOracle::UnaryTypes &u, JSContext *cx);
     MDefinition *foldsTo(bool useValueNumbers);
+
+    void markOperandCantEmulateUndefined() {
+        operandMightEmulateUndefined_ = false;
+    }
+    bool operandMightEmulateUndefined() const {
+        return operandMightEmulateUndefined_;
+    }
 };
 
 // Returns from this function to the previous caller.
 class MReturn
   : public MAryControlInstruction<1, 0>,
     public BoxInputsPolicy
 {
     MReturn(MDefinition *ins) {
@@ -1365,44 +1377,52 @@ class MTernaryInstruction : public MAryI
     }
 };
 
 class MCompare
   : public MBinaryInstruction,
     public ComparePolicy
 {
     JSOp jsop_;
+    bool operandMightEmulateUndefined_;
 
     MCompare(MDefinition *left, MDefinition *right, JSOp jsop)
       : MBinaryInstruction(left, right),
-        jsop_(jsop)
+        jsop_(jsop),
+        operandMightEmulateUndefined_(true)
     {
         setResultType(MIRType_Boolean);
         setMovable();
     }
 
   public:
     INSTRUCTION_HEADER(Compare)
     static MCompare *New(MDefinition *left, MDefinition *right, JSOp op);
 
     bool tryFold(bool *result);
     bool evaluateConstantOperands(bool *result);
     MDefinition *foldsTo(bool useValueNumbers);
 
-    void infer(JSContext *cx, const TypeOracle::BinaryTypes &b);
+    void infer(const TypeOracle::BinaryTypes &b, JSContext *cx);
     MIRType specialization() const {
         return specialization_;
     }
 
     JSOp jsop() const {
         return jsop_;
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    void markNoOperandEmulatesUndefined() {
+        operandMightEmulateUndefined_ = false;
+    }
+    bool operandMightEmulateUndefined() const {
+        return operandMightEmulateUndefined_;
+    }
     AliasSet getAliasSet() const {
         // Strict equality is never effectful.
         if (jsop_ == JSOP_STRICTEQ || jsop_ == JSOP_STRICTNE)
             return AliasSet::None();
         if (specialization_ == MIRType_None)
             return AliasSet::Store(AliasSet::Any);
         JS_ASSERT(specialization_ <= MIRType_Object);
         return AliasSet::None();
@@ -2181,17 +2201,17 @@ class MBinaryArithInstruction
     MIRType specialization() const {
         return specialization_;
     }
 
     MDefinition *foldsTo(bool useValueNumbers);
 
     virtual double getIdentity() = 0;
 
-    void infer(JSContext *cx, const TypeOracle::BinaryTypes &b);
+    void infer(const TypeOracle::BinaryTypes &b, JSContext *cx);
 
     void setInt32() {
         specialization_ = MIRType_Int32;
         setResultType(MIRType_Int32);
     }
 
     bool congruentTo(MDefinition *const &ins) const {
         return MBinaryInstruction::congruentTo(ins);
@@ -3401,28 +3421,39 @@ class MTypedArrayElements
     }
 };
 
 // Perform !-operation
 class MNot
   : public MUnaryInstruction,
     public TestPolicy
 {
+    bool operandMightEmulateUndefined_;
+
   public:
-    MNot(MDefinition *elements)
-      : MUnaryInstruction(elements)
+    MNot(MDefinition *input)
+      : MUnaryInstruction(input),
+        operandMightEmulateUndefined_(true)
     {
         setResultType(MIRType_Boolean);
         setMovable();
     }
 
     INSTRUCTION_HEADER(Not)
 
+    void infer(const TypeOracle::UnaryTypes &u, JSContext *cx);
     MDefinition *foldsTo(bool useValueNumbers);
 
+    void markOperandCantEmulateUndefined() {
+        operandMightEmulateUndefined_ = false;
+    }
+    bool operandMightEmulateUndefined() const {
+        return operandMightEmulateUndefined_;
+    }
+
     MDefinition *operand() const {
         return getOperand(0);
     }
 
     virtual AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     TypePolicy *typePolicy() {
--- a/js/src/ion/VMFunctions.cpp
+++ b/js/src/ion/VMFunctions.cpp
@@ -8,16 +8,17 @@
 #include "Ion.h"
 #include "IonCompartment.h"
 #include "jsinterp.h"
 #include "ion/IonFrames.h"
 #include "ion/IonFrames-inl.h" // for GetTopIonJSScript
 
 #include "vm/StringObject-inl.h"
 
+#include "jsboolinlines.h"
 #include "jsinterpinlines.h"
 
 using namespace js;
 using namespace js::ion;
 
 namespace js {
 namespace ion {
 
@@ -222,21 +223,21 @@ StringsEqual(JSContext *cx, HandleString
         return false;
     *res = (equal == Equal);
     return true;
 }
 
 template bool StringsEqual<true>(JSContext *cx, HandleString lhs, HandleString rhs, JSBool *res);
 template bool StringsEqual<false>(JSContext *cx, HandleString lhs, HandleString rhs, JSBool *res);
 
-bool
-ValueToBooleanComplement(JSContext *cx, const Value &input, JSBool *output)
+JSBool
+ObjectEmulatesUndefined(RawObject obj)
 {
-    *output = !ToBoolean(input);
-    return true;
+    AutoAssertNoGC nogc;
+    return EmulatesUndefined(obj);
 }
 
 bool
 IteratorMore(JSContext *cx, HandleObject obj, JSBool *res)
 {
     RootedValue tmp(cx);
     if (!js_IteratorMore(cx, obj, &tmp))
         return false;
--- a/js/src/ion/VMFunctions.h
+++ b/js/src/ion/VMFunctions.h
@@ -427,17 +427,17 @@ bool StrictlyEqual(JSContext *cx, Handle
 bool LessThan(JSContext *cx, HandleValue lhs, HandleValue rhs, JSBool *res);
 bool LessThanOrEqual(JSContext *cx, HandleValue lhs, HandleValue rhs, JSBool *res);
 bool GreaterThan(JSContext *cx, HandleValue lhs, HandleValue rhs, JSBool *res);
 bool GreaterThanOrEqual(JSContext *cx, HandleValue lhs, HandleValue rhs, JSBool *res);
 
 template<bool Equal>
 bool StringsEqual(JSContext *cx, HandleString left, HandleString right, JSBool *res);
 
-bool ValueToBooleanComplement(JSContext *cx, const Value &input, JSBool *output);
+JSBool ObjectEmulatesUndefined(RawObject obj);
 
 bool IteratorMore(JSContext *cx, HandleObject obj, JSBool *res);
 
 // Allocation functions for JSOP_NEWARRAY and JSOP_NEWOBJECT
 JSObject *NewInitArray(JSContext *cx, uint32_t count, types::TypeObject *type);
 JSObject *NewInitObject(JSContext *cx, HandleObject templateObject);
 
 bool ArrayPopDense(JSContext *cx, HandleObject obj, MutableHandleValue rval);
--- a/js/src/ion/shared/CodeGenerator-shared.h
+++ b/js/src/ion/shared/CodeGenerator-shared.h
@@ -204,16 +204,25 @@ class CodeGeneratorShared : public LInst
     void emitPreBarrier(Register base, const LAllocation *index, MIRType type);
     void emitPreBarrier(Address address, MIRType type);
 
     inline bool isNextBlock(LBlock *block) {
         return (current->mir()->id() + 1 == block->mir()->id());
     }
 
   public:
+    // Save and restore all volatile registers to/from the stack, excluding the
+    // specified register(s), before a function call made using callWithABI and
+    // after storing the function call's return value to an output register.
+    // (The only registers that don't need to be saved/restored are 1) the
+    // temporary register used to store the return value of the function call,
+    // if there is one [otherwise that stored value would be overwritten]; and
+    // 2) temporary registers whose values aren't needed in the rest of the LIR
+    // instruction [this is purely an optimization].  All other volatiles must
+    // be saved and restored in case future LIR instructions need those values.)
     void saveVolatile(Register output) {
         RegisterSet regs = RegisterSet::Volatile();
         regs.maybeTake(output);
         masm.PushRegsInMask(regs);
     }
     void restoreVolatile(Register output) {
         RegisterSet regs = RegisterSet::Volatile();
         regs.maybeTake(output);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/emulates-undefined.js
@@ -0,0 +1,18 @@
+function test() {
+    var values = [undefined, null, Math, objectEmulatingUndefined()];
+    var expected = [true, true, false, true];
+
+    for (var i=0; i<100; i++) {
+        var idx = i % values.length;
+        if (values[idx] == undefined)
+            assertEq(expected[idx], true);
+        else
+            assertEq(expected[idx], false);
+
+        if (null != values[idx])
+            assertEq(expected[idx], false);
+        else
+            assertEq(expected[idx], true);
+    }
+}
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/equal-null.js
@@ -0,0 +1,37 @@
+function f(v, value)
+{
+  var b = v == null;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, false);
+f({}, false);
+f(null, true);
+f(null, true);
+f(undefined, true);
+f(undefined, true);
+f(objectEmulatingUndefined(), true);
+f(objectEmulatingUndefined(), true);
+f(Object.prototype, false);
+f(Object.prototype, false);
+
+function g(v, value)
+{
+  var b = v == null;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, false);
+g({}, false);
+
+function h(v, value)
+{
+  var b = v == null;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), true);
+h(objectEmulatingUndefined(), true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/equal-undefined.js
@@ -0,0 +1,37 @@
+function f(v, value)
+{
+  var b = v == undefined;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, false);
+f({}, false);
+f(null, true);
+f(null, true);
+f(undefined, true);
+f(undefined, true);
+f(objectEmulatingUndefined(), true);
+f(objectEmulatingUndefined(), true);
+f(Object.prototype, false);
+f(Object.prototype, false);
+
+function g(v, value)
+{
+  var b = v == undefined;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, false);
+g({}, false);
+
+function h(v, value)
+{
+  var b = v == undefined;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), true);
+h(objectEmulatingUndefined(), true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/if-equal-null.js
@@ -0,0 +1,46 @@
+var counterF = 0;
+
+function f(v, value)
+{
+  if (v == null)
+    counterF++;
+  assertEq(counterF, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, 0);
+f({}, 0);
+f(null, 1);
+f(null, 2);
+f(undefined, 3);
+f(undefined, 4);
+f(objectEmulatingUndefined(), 5);
+f(objectEmulatingUndefined(), 6);
+f(Object.prototype, 6);
+f(Object.prototype, 6);
+
+var counterG = 0;
+
+function g(v, value)
+{
+  if (v == null)
+    counterG++;
+  assertEq(counterG, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, 0);
+g({}, 0);
+
+var counterH = 0;
+
+function h(v, value)
+{
+  if (v == null)
+    counterH++;
+  assertEq(counterH, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), 1);
+h(objectEmulatingUndefined(), 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/if-equal-undefined.js
@@ -0,0 +1,46 @@
+var counterF = 0;
+
+function f(v, value)
+{
+  if (v == undefined)
+    counterF++;
+  assertEq(counterF, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, 0);
+f({}, 0);
+f(null, 1);
+f(null, 2);
+f(undefined, 3);
+f(undefined, 4);
+f(objectEmulatingUndefined(), 5);
+f(objectEmulatingUndefined(), 6);
+f(Object.prototype, 6);
+f(Object.prototype, 6);
+
+var counterG = 0;
+
+function g(v, value)
+{
+  if (v == undefined)
+    counterG++;
+  assertEq(counterG, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, 0);
+g({}, 0);
+
+var counterH = 0;
+
+function h(v, value)
+{
+  if (v == undefined)
+    counterH++;
+  assertEq(counterH, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), 1);
+h(objectEmulatingUndefined(), 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/if-not-equal-null.js
@@ -0,0 +1,46 @@
+var counterF = 0;
+
+function f(v, value)
+{
+  if (v != null)
+    counterF++;
+  assertEq(counterF, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, 1);
+f({}, 2);
+f(null, 2);
+f(null, 2);
+f(undefined, 2);
+f(undefined, 2);
+f(objectEmulatingUndefined(), 2);
+f(objectEmulatingUndefined(), 2);
+f(Object.prototype, 3);
+f(Object.prototype, 4);
+
+var counterG = 0;
+
+function g(v, value)
+{
+  if (v != null)
+    counterG++;
+  assertEq(counterG, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, 1);
+g({}, 2);
+
+var counterH = 0;
+
+function h(v, value)
+{
+  if (v != null)
+    counterH++;
+  assertEq(counterH, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), 0);
+h(objectEmulatingUndefined(), 0);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/if-not-equal-undefined.js
@@ -0,0 +1,46 @@
+var counterF = 0;
+
+function f(v, value)
+{
+  if (v != undefined)
+    counterF++;
+  assertEq(counterF, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, 1);
+f({}, 2);
+f(null, 2);
+f(null, 2);
+f(undefined, 2);
+f(undefined, 2);
+f(objectEmulatingUndefined(), 2);
+f(objectEmulatingUndefined(), 2);
+f(Object.prototype, 3);
+f(Object.prototype, 4);
+
+var counterG = 0;
+
+function g(v, value)
+{
+  if (v != undefined)
+    counterG++;
+  assertEq(counterG, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, 1);
+g({}, 2);
+
+var counterH = 0;
+
+function h(v, value)
+{
+  if (v != undefined)
+    counterH++;
+  assertEq(counterH, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), 0);
+h(objectEmulatingUndefined(), 0);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/if.js
@@ -0,0 +1,24 @@
+function t1(v)
+{
+  if (v)
+    return 1;
+  return 0;
+}
+
+assertEq(t1(objectEmulatingUndefined()), 0);
+assertEq(t1(objectEmulatingUndefined()), 0);
+assertEq(t1(objectEmulatingUndefined()), 0);
+
+function t2(v)
+{
+  if (v)
+    return 1;
+  return 0;
+}
+
+assertEq(t2(17), 1);
+assertEq(t2(0), 0);
+assertEq(t2(-0), 0);
+assertEq(t2(objectEmulatingUndefined()), 0);
+assertEq(t2(objectEmulatingUndefined()), 0);
+assertEq(t2(objectEmulatingUndefined()), 0);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/not-equal-null.js
@@ -0,0 +1,37 @@
+function f(v, value)
+{
+  var b = v != null;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, true);
+f({}, true);
+f(null, false);
+f(null, false);
+f(undefined, false);
+f(undefined, false);
+f(objectEmulatingUndefined(), false);
+f(objectEmulatingUndefined(), false);
+f(Object.prototype, true);
+f(Object.prototype, true);
+
+function g(v, value)
+{
+  var b = v != null;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, true);
+g({}, true);
+
+function h(v, value)
+{
+  var b = v != null;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), false);
+h(objectEmulatingUndefined(), false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/not-equal-undefined.js
@@ -0,0 +1,37 @@
+function f(v, value)
+{
+  var b = v != undefined;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+f({}, true);
+f({}, true);
+f(null, false);
+f(null, false);
+f(undefined, false);
+f(undefined, false);
+f(objectEmulatingUndefined(), false);
+f(objectEmulatingUndefined(), false);
+f(Object.prototype, true);
+f(Object.prototype, true);
+
+function g(v, value)
+{
+  var b = v != undefined;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+g({}, true);
+g({}, true);
+
+function h(v, value)
+{
+  var b = v != undefined;
+  assertEq(b, value,
+           "failed: " + v + " " + value);
+}
+
+h(objectEmulatingUndefined(), false);
+h(objectEmulatingUndefined(), false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/not.js
@@ -0,0 +1,24 @@
+function t1(v)
+{
+  if (!v)
+    return 1;
+  return 0;
+}
+
+assertEq(t1(objectEmulatingUndefined()), 1);
+assertEq(t1(objectEmulatingUndefined()), 1);
+assertEq(t1(objectEmulatingUndefined()), 1);
+
+function t2(v)
+{
+  if (!v)
+    return 1;
+  return 0;
+}
+
+assertEq(t2(17), 0);
+assertEq(t2(0), 1);
+assertEq(t2(-0), 1);
+assertEq(t2(objectEmulatingUndefined()), 1);
+assertEq(t2(objectEmulatingUndefined()), 1);
+assertEq(t2(objectEmulatingUndefined()), 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/truthiness/typeof.js
@@ -0,0 +1,23 @@
+function t1(v)
+{
+  return typeof v;
+}
+
+assertEq(t1(objectEmulatingUndefined()), "undefined");
+assertEq(t1(objectEmulatingUndefined()), "undefined");
+assertEq(t1(objectEmulatingUndefined()), "undefined");
+
+function t2(v)
+{
+  return typeof v;
+}
+
+assertEq(t2(17), "number");
+assertEq(t2(0), "number");
+assertEq(t2(-0), "number");
+assertEq(t2(function(){}), "function");
+assertEq(t2({}), "object");
+assertEq(t2(null), "object");
+assertEq(t2(objectEmulatingUndefined()), "undefined");
+assertEq(t2(objectEmulatingUndefined()), "undefined");
+assertEq(t2(objectEmulatingUndefined()), "undefined");
--- a/js/src/jsapi-tests/Makefile.in
+++ b/js/src/jsapi-tests/Makefile.in
@@ -35,27 +35,28 @@ CPPSRCS = \
   testEnclosingFunction.cpp \
   testErrorCopying.cpp \
   testExtendedEq.cpp \
   testExternalStrings.cpp \
   testFindSCCs.cpp \
   testFuncCallback.cpp \
   testFunctionProperties.cpp \
   testGCOutOfMemory.cpp \
-  testOOM.cpp \
   testGetPropertyDefault.cpp \
   testHashTable.cpp \
   testIndexToString.cpp \
   testIntString.cpp \
   testIntTypesABI.cpp \
   testIntern.cpp \
   testJSEvaluateScript.cpp \
   testLookup.cpp \
   testLooselyEqual.cpp \
   testNewObject.cpp \
+  testObjectEmulatingUndefined.cpp \
+  testOOM.cpp \
   testOps.cpp \
   testOriginPrincipals.cpp \
   testParseJSON.cpp \
   testProfileStrings.cpp \
   testPropCache.cpp \
   testRegExp.cpp \
   testResolveRecursion.cpp \
   testSameValue.cpp \
--- a/js/src/jsapi-tests/testLookup.cpp
+++ b/js/src/jsapi-tests/testLookup.cpp
@@ -32,31 +32,47 @@ BEGIN_TEST(testLookup_bug522590)
     JSObject *funobj = &r.toObject();
     CHECK(funobj->isFunction());
     CHECK(!js::IsInternalFunctionObject(funobj));
 
     return true;
 }
 END_TEST(testLookup_bug522590)
 
+static JSClass DocumentAllClass = {
+    "DocumentAll",
+    JSCLASS_EMULATES_UNDEFINED,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub
+};
+
 JSBool
 document_resolve(JSContext *cx, JSHandleObject obj, JSHandleId id, unsigned flags,
                  JSMutableHandleObject objp)
 {
-    // If id is "all", and we're not detecting, resolve document.all=true.
+    // If id is "all", resolve document.all=true.
     js::RootedValue v(cx);
     if (!JS_IdToValue(cx, id, v.address()))
         return false;
     if (JSVAL_IS_STRING(v)) {
         JSString *str = JSVAL_TO_STRING(v);
         JSFlatString *flatStr = JS_FlattenString(cx, str);
         if (!flatStr)
             return false;
-        if (JS_FlatStringEqualsAscii(flatStr, "all") && !(flags & JSRESOLVE_DETECTING)) {
-            JSBool ok = JS_DefinePropertyById(cx, obj, id, JSVAL_TRUE, NULL, NULL, 0);
+        if (JS_FlatStringEqualsAscii(flatStr, "all")) {
+            js::Rooted<JSObject*> docAll(cx, JS_NewObject(cx, &DocumentAllClass, NULL, NULL));
+            if (!docAll)
+                return false;
+            js::Rooted<JS::Value> allValue(cx, ObjectValue(*docAll));
+            JSBool ok = JS_DefinePropertyById(cx, obj, id, allValue, NULL, NULL, 0);
             objp.set(ok ? obj.get() : NULL);
             return ok;
         }
     }
     objp.set(NULL);
     return true;
 }
 
@@ -70,12 +86,12 @@ BEGIN_TEST(testLookup_bug570195)
 {
     js::RootedObject obj(cx, JS_NewObject(cx, &document_class, NULL, NULL));
     CHECK(obj);
     CHECK(JS_DefineProperty(cx, global, "document", OBJECT_TO_JSVAL(obj), NULL, NULL, 0));
     js::RootedValue v(cx);
     EVAL("document.all ? true : false", v.address());
     CHECK_SAME(v, JSVAL_FALSE);
     EVAL("document.hasOwnProperty('all')", v.address());
-    CHECK_SAME(v, JSVAL_FALSE);
+    CHECK_SAME(v, JSVAL_TRUE);
     return true;
 }
 END_TEST(testLookup_bug570195)
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "tests.h"
+
+static JSClass ObjectEmulatingUndefinedClass = {
+    "ObjectEmulatingUndefined",
+    JSCLASS_EMULATES_UNDEFINED,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub
+};
+
+static JSBool
+ObjectEmulatingUndefinedConstructor(JSContext *cx, unsigned argc, jsval *vp)
+{
+    JSObject *obj = JS_NewObjectForConstructor(cx, &ObjectEmulatingUndefinedClass, vp);
+    if (!obj)
+        return false;
+    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
+    return true;
+}
+
+BEGIN_TEST(testObjectEmulatingUndefined_truthy)
+{
+    CHECK(JS_InitClass(cx, global, NULL, &ObjectEmulatingUndefinedClass,
+                       ObjectEmulatingUndefinedConstructor, 0, NULL, NULL, NULL, NULL));
+
+    jsval result;
+
+    EVAL("if (new ObjectEmulatingUndefined()) true; else false;", &result);
+    CHECK_SAME(result, JSVAL_FALSE);
+
+    EVAL("if (!new ObjectEmulatingUndefined()) true; else false;", &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    EVAL("var obj = new ObjectEmulatingUndefined(); \n"
+         "var res = []; \n"
+         "for (var i = 0; i < 50; i++) \n"
+         "  res.push(Boolean(obj)); \n"
+         "res.every(function(v) { return v === false; });",
+         &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    return true;
+}
+END_TEST(testObjectEmulatingUndefined_truthy)
+
+BEGIN_TEST(testObjectEmulatingUndefined_equal)
+{
+    CHECK(JS_InitClass(cx, global, NULL, &ObjectEmulatingUndefinedClass,
+                       ObjectEmulatingUndefinedConstructor, 0, NULL, NULL, NULL, NULL));
+
+    jsval result;
+
+    EVAL("if (new ObjectEmulatingUndefined() == undefined) true; else false;", &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    EVAL("if (new ObjectEmulatingUndefined() == null) true; else false;", &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    EVAL("if (new ObjectEmulatingUndefined() != undefined) true; else false;", &result);
+    CHECK_SAME(result, JSVAL_FALSE);
+
+    EVAL("if (new ObjectEmulatingUndefined() != null) true; else false;", &result);
+    CHECK_SAME(result, JSVAL_FALSE);
+
+    EVAL("var obj = new ObjectEmulatingUndefined(); \n"
+         "var res = []; \n"
+         "for (var i = 0; i < 50; i++) \n"
+         "  res.push(obj == undefined); \n"
+         "res.every(function(v) { return v === true; });",
+         &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    EVAL("var obj = new ObjectEmulatingUndefined(); \n"
+         "var res = []; \n"
+         "for (var i = 0; i < 50; i++) \n"
+         "  res.push(obj == null); \n"
+         "res.every(function(v) { return v === true; });",
+         &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    EVAL("var obj = new ObjectEmulatingUndefined(); \n"
+         "var res = []; \n"
+         "for (var i = 0; i < 50; i++) \n"
+         "  res.push(obj != undefined); \n"
+         "res.every(function(v) { return v === false; });",
+         &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    EVAL("var obj = new ObjectEmulatingUndefined(); \n"
+         "var res = []; \n"
+         "for (var i = 0; i < 50; i++) \n"
+         "  res.push(obj != null); \n"
+         "res.every(function(v) { return v === false; });",
+         &result);
+    CHECK_SAME(result, JSVAL_TRUE);
+
+    return true;
+}
+END_TEST(testObjectEmulatingUndefined_equal)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3422,18 +3422,23 @@ JS_NewObject(JSContext *cx, JSClass *jsc
         clasp = &ObjectClass;    /* default class is Object */
 
     JS_ASSERT(clasp != &FunctionClass);
     JS_ASSERT(!(clasp->flags & JSCLASS_IS_GLOBAL));
 
     JSObject *obj = NewObjectWithClassProto(cx, clasp, proto, parent);
     AutoAssertNoGC nogc;
     if (obj) {
+        TypeObjectFlags flags = 0;
         if (clasp->ext.equality)
-            MarkTypeObjectFlags(cx, obj, OBJECT_FLAG_SPECIAL_EQUALITY);
+            flags |= OBJECT_FLAG_SPECIAL_EQUALITY;
+        if (clasp->emulatesUndefined())
+            flags |= OBJECT_FLAG_EMULATES_UNDEFINED;
+        if (flags)
+            MarkTypeObjectFlags(cx, obj, flags);
     }
 
     JS_ASSERT_IF(obj, obj->getParent());
     return obj;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewObjectWithGivenProto(JSContext *cx, JSClass *jsclasp, JSObject *protoArg, JSObject *parentArg)
@@ -3650,18 +3655,17 @@ JS_LookupPropertyWithFlags(JSContext *cx
 
 JS_PUBLIC_API(JSBool)
 JS_HasPropertyById(JSContext *cx, JSObject *objArg, jsid idArg, JSBool *foundp)
 {
     RootedObject obj(cx, objArg);
     RootedId id(cx, idArg);
     RootedObject obj2(cx);
     RootedShape prop(cx);
-    JSBool ok = LookupPropertyById(cx, obj, id, JSRESOLVE_QUALIFIED | JSRESOLVE_DETECTING,
-                                   &obj2, &prop);
+    JSBool ok = LookupPropertyById(cx, obj, id, JSRESOLVE_QUALIFIED, &obj2, &prop);
     *foundp = (prop != NULL);
     return ok;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_HasElement(JSContext *cx, JSObject *objArg, uint32_t index, JSBool *foundp)
 {
     RootedObject obj(cx, objArg);
@@ -3697,20 +3701,18 @@ JS_AlreadyHasOwnPropertyById(JSContext *
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, id);
 
     if (!obj->isNative()) {
         RootedObject obj2(cx);
         RootedShape prop(cx);
 
-        if (!LookupPropertyById(cx, obj, id, JSRESOLVE_QUALIFIED | JSRESOLVE_DETECTING,
-                                &obj2, &prop)) {
+        if (!LookupPropertyById(cx, obj, id, JSRESOLVE_QUALIFIED, &obj2, &prop))
             return JS_FALSE;
-        }
         *foundp = (obj == obj2);
         return JS_TRUE;
     }
 
     *foundp = obj->nativeContains(cx, id);
     return JS_TRUE;
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1833,17 +1833,16 @@ typedef JSBool
 typedef JSBool
 (* JSResolveOp)(JSContext *cx, JSHandleObject obj, JSHandleId id);
 
 /*
  * Like JSResolveOp, but flags provide contextual information as follows:
  *
  *  JSRESOLVE_QUALIFIED   a qualified property id: obj.id or obj[id], not id
  *  JSRESOLVE_ASSIGNING   obj[id] is on the left-hand side of an assignment
- *  JSRESOLVE_DETECTING   'if (o.p)...' or similar detection opcode sequence
  *
  * The *objp out parameter, on success, should be null to indicate that id
  * was not resolved; and non-null, referring to obj or one of its prototypes,
  * if id was resolved.  The hook may assume *objp is null on entry.
  *
  * This hook instead of JSResolveOp is called via the JSClass.resolve member
  * if JSCLASS_NEW_RESOLVE is set in JSClass.flags.
  */
@@ -2756,26 +2755,24 @@ ToNumber(JSContext *cx, const Value &v, 
 
 JS_ALWAYS_INLINE bool
 ToBoolean(const Value &v)
 {
     if (v.isBoolean())
         return v.toBoolean();
     if (v.isInt32())
         return v.toInt32() != 0;
-    if (v.isObject())
-        return true;
     if (v.isNullOrUndefined())
         return false;
     if (v.isDouble()) {
         double d = v.toDouble();
         return !MOZ_DOUBLE_IS_NaN(d) && d != 0;
     }
 
-    /* Slow path. Handle Strings. */
+    /* The slow path handles strings and objects. */
     return js::ToBooleanSlow(v);
 }
 
 } /* namespace JS */
 
 extern JS_PUBLIC_API(JSBool)
 JS_DoubleIsInt32(double d, int32_t *ip);
 
@@ -4082,17 +4079,19 @@ struct JSClass {
 
 #define JSCLASS_HAS_PRIVATE             (1<<0)  /* objects have private slot */
 #define JSCLASS_NEW_ENUMERATE           (1<<1)  /* has JSNewEnumerateOp hook */
 #define JSCLASS_NEW_RESOLVE             (1<<2)  /* has JSNewResolveOp hook */
 #define JSCLASS_PRIVATE_IS_NSISUPPORTS  (1<<3)  /* private is (nsISupports *) */
 #define JSCLASS_IS_DOMJSCLASS           (1<<4)  /* objects are DOM */
 #define JSCLASS_IMPLEMENTS_BARRIERS     (1<<5)  /* Correctly implements GC read
                                                    and write barriers */
-#define JSCLASS_DOCUMENT_OBSERVER       (1<<6)  /* DOM document observer */
+#define JSCLASS_EMULATES_UNDEFINED      (1<<6)  /* objects of this class act
+                                                   like the value undefined,
+                                                   in some contexts */
 #define JSCLASS_USERBIT1                (1<<7)  /* Reserved for embeddings. */
 
 /*
  * To reserve slots fetched and stored via JS_Get/SetReservedSlot, bitwise-or
  * JSCLASS_HAS_RESERVED_SLOTS(n) into the initializer for JSClass.flags, where
  * n is a constant in [1, 255].  Reserved slots are indexed from 0 to n-1.
  */
 #define JSCLASS_RESERVED_SLOTS_SHIFT    8       /* room for 8 flags below */
@@ -4225,17 +4224,16 @@ JS_ValueToId(JSContext *cx, jsval v, jsi
 extern JS_PUBLIC_API(JSBool)
 JS_IdToValue(JSContext *cx, jsid id, jsval *vp);
 
 /*
  * JSNewResolveOp flag bits.
  */
 #define JSRESOLVE_QUALIFIED     0x01    /* resolve a qualified property id */
 #define JSRESOLVE_ASSIGNING     0x02    /* resolve on the left of assignment */
-#define JSRESOLVE_DETECTING     0x04    /* 'if (o.p)...' or '(o.p) ?...:...' */
 
 /*
  * Invoke the [[DefaultValue]] hook (see ES5 8.6.2) with the provided hint on
  * the specified object, computing a primitive default value for the object.
  * The hint must be JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID (no hint).  On
  * success the resulting value is stored in *vp.
  */
 extern JS_PUBLIC_API(JSBool)
--- a/js/src/jsbool.cpp
+++ b/js/src/jsbool.cpp
@@ -191,18 +191,21 @@ JSString *
 js_BooleanToString(JSContext *cx, JSBool b)
 {
     return b ? cx->runtime->atomState.true_ : cx->runtime->atomState.false_;
 }
 
 JS_PUBLIC_API(bool)
 js::ToBooleanSlow(const Value &v)
 {
-    JS_ASSERT(v.isString());
-    return v.toString()->length() != 0;
+    if (v.isString())
+        return v.toString()->length() != 0;
+
+    JS_ASSERT(v.isObject());
+    return !EmulatesUndefined(&v.toObject());
 }
 
 bool
 js::BooleanGetPrimitiveValueSlow(JSContext *cx, JSObject &obj, Value *vp)
 {
     InvokeArgsGuard ag;
     if (!cx->stack.pushInvokeArgs(cx, 0, &ag))
         return false;
--- a/js/src/jsboolinlines.h
+++ b/js/src/jsboolinlines.h
@@ -2,16 +2,21 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jsboolinlines_h___
 #define jsboolinlines_h___
 
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+
+#include "gc/Root.h"
+
 #include "jsobjinlines.h"
 
 #include "vm/BooleanObject-inl.h"
 
 namespace js {
 
 bool BooleanGetPrimitiveValueSlow(JSContext *, JSObject &, Value *);
 
@@ -21,11 +26,21 @@ BooleanGetPrimitiveValue(JSContext *cx, 
     if (obj.isBoolean()) {
         *vp = BooleanValue(obj.asBoolean().unbox());
         return true;
     }
 
     return BooleanGetPrimitiveValueSlow(cx, obj, vp);
 }
 
+inline bool
+EmulatesUndefined(RawObject obj)
+{
+    AutoAssertNoGC nogc;
+    RawObject actual = MOZ_LIKELY(!obj->isWrapper()) ? obj : UnwrapObject(obj);
+    bool emulatesUndefined = actual->getClass()->emulatesUndefined();
+    MOZ_ASSERT_IF(emulatesUndefined, obj->type()->flags & types::OBJECT_FLAG_EMULATES_UNDEFINED);
+    return emulatesUndefined;
+}
+
 } /* namespace js */
 
 #endif /* jsboolinlines_h___ */
--- a/js/src/jsclass.h
+++ b/js/src/jsclass.h
@@ -326,16 +326,22 @@ struct Class
 
     bool isNative() const {
         return !(flags & NON_NATIVE);
     }
 
     bool hasPrivate() const {
         return !!(flags & JSCLASS_HAS_PRIVATE);
     }
+
+    bool emulatesUndefined() const {
+        return flags & JSCLASS_EMULATES_UNDEFINED;
+    }
+
+    static size_t offsetOfFlags() { return offsetof(Class, flags); }
 };
 
 JS_STATIC_ASSERT(offsetof(JSClass, name) == offsetof(Class, name));
 JS_STATIC_ASSERT(offsetof(JSClass, flags) == offsetof(Class, flags));
 JS_STATIC_ASSERT(offsetof(JSClass, addProperty) == offsetof(Class, addProperty));
 JS_STATIC_ASSERT(offsetof(JSClass, delProperty) == offsetof(Class, delProperty));
 JS_STATIC_ASSERT(offsetof(JSClass, getProperty) == offsetof(Class, getProperty));
 JS_STATIC_ASSERT(offsetof(JSClass, setProperty) == offsetof(Class, setProperty));
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3586,16 +3586,18 @@ TypeObject::print()
         if (!hasAnyFlags(OBJECT_FLAG_NON_DENSE_ARRAY))
             printf(" dense");
         if (!hasAnyFlags(OBJECT_FLAG_NON_TYPED_ARRAY))
             printf(" typed");
         if (hasAnyFlags(OBJECT_FLAG_UNINLINEABLE))
             printf(" uninlineable");
         if (hasAnyFlags(OBJECT_FLAG_SPECIAL_EQUALITY))
             printf(" specialEquality");
+        if (hasAnyFlags(OBJECT_FLAG_EMULATES_UNDEFINED))
+            printf(" emulatesUndefined");
         if (hasAnyFlags(OBJECT_FLAG_ITERATED))
             printf(" iterated");
     }
 
     unsigned count = getPropertyCount();
 
     if (count == 0) {
         printf(" {}\n");
@@ -5763,16 +5765,18 @@ JSObject::makeLazyType(JSContext *cx)
      * ops. Just mark the type as totally unknown.
      */
     if (self->isXML() && !type->unknownProperties())
         type->markUnknown(cx);
 #endif
 
     if (self->getClass()->ext.equality)
         type->flags |= OBJECT_FLAG_SPECIAL_EQUALITY;
+    if (self->getClass()->emulatesUndefined())
+        type->flags |= OBJECT_FLAG_EMULATES_UNDEFINED;
 
     /*
      * Adjust flags for objects which will have the wrong flags set by just
      * looking at the class prototype key.
      */
 
     if (self->isSlowArray())
         type->flags |= OBJECT_FLAG_NON_DENSE_ARRAY | OBJECT_FLAG_NON_PACKED_ARRAY;
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -397,18 +397,21 @@ enum {
     OBJECT_FLAG_SPECIAL_EQUALITY      = 0x00200000,
 
     /* Whether any objects have been iterated over. */
     OBJECT_FLAG_ITERATED              = 0x00400000,
 
     /* For a global object, whether flags were set on the RegExpStatics. */
     OBJECT_FLAG_REGEXP_FLAGS_SET      = 0x00800000,
 
+    /* Whether any objects emulate undefined; see EmulatesUndefined. */
+    OBJECT_FLAG_EMULATES_UNDEFINED    = 0x01000000,
+
     /* Flags which indicate dynamic properties of represented objects. */
-    OBJECT_FLAG_DYNAMIC_MASK          = 0x00ff0000,
+    OBJECT_FLAG_DYNAMIC_MASK          = 0x01ff0000,
 
     /*
      * Whether all properties of this object are considered unknown.
      * If set, all flags in DYNAMIC_MASK will also be set.
      */
     OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x80000000,
 
     /* Mask for objects created with unknown properties. */
@@ -902,16 +905,18 @@ struct TypeObject : gc::Cell
      * object whose type has not been constructed yet.
      */
     static const size_t LAZY_SINGLETON = 1;
     bool lazy() const { return singleton == (RawObject) LAZY_SINGLETON; }
 
     /* Flags for this object. */
     TypeObjectFlags flags;
 
+    static inline size_t offsetOfFlags() { return offsetof(TypeObject, flags); }
+
     /*
      * Estimate of the contribution of this object to the type sets it appears in.
      * This is the sum of the sizes of those sets at the point when the object
      * was added.
      *
      * When the contribution exceeds the CONTRIBUTION_LIMIT, any type sets the
      * object is added to are instead marked as unknown. If we get to this point
      * we are probably not adding types which will let us do meaningful optimization
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -46,16 +46,17 @@
 
 #ifdef JS_METHODJIT
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Logging.h"
 #endif
 #include "ion/Ion.h"
 
 #include "jsatominlines.h"
+#include "jsboolinlines.h"
 #include "jsinferinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsopcodeinlines.h"
 #include "jsprobes.h"
 #include "jspropertycacheinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
@@ -633,22 +634,23 @@ js::LooselyEqual(JSContext *cx, const Va
             return true;
         }
 
         *result = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
         return true;
     }
 
     if (lval.isNullOrUndefined()) {
-        *result = rval.isNullOrUndefined();
+        *result = rval.isNullOrUndefined() ||
+                  (rval.isObject() && EmulatesUndefined(&rval.toObject()));
         return true;
     }
 
     if (rval.isNullOrUndefined()) {
-        *result = false;
+        *result = (lval.isObject() && EmulatesUndefined(&lval.toObject()));
         return true;
     }
 
     RootedValue lvalue(cx, lval);
     RootedValue rvalue(cx, rval);
 
     if (!ToPrimitive(cx, lvalue.address()))
         return false;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -51,16 +51,17 @@
 #include "frontend/Parser.h"
 #include "gc/Marking.h"
 #include "js/MemoryMetrics.h"
 #include "vm/StringBuffer.h"
 #include "vm/Xdr.h"
 
 #include "jsarrayinlines.h"
 #include "jsatominlines.h"
+#include "jsboolinlines.h"
 #include "jscntxtinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/BooleanObject-inl.h"
 #include "vm/NumberObject-inl.h"
@@ -512,17 +513,17 @@ js_HasOwnPropertyHelper(JSContext *cx, L
     rval.setBoolean(!!prop);
     return true;
 }
 
 JSBool
 js_HasOwnProperty(JSContext *cx, LookupGenericOp lookup, HandleObject obj, HandleId id,
                   MutableHandleObject objp, MutableHandleShape propp)
 {
-    JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_DETECTING);
+    JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED);
     if (lookup) {
         if (!lookup(cx, obj, id, objp, propp))
             return false;
     } else {
         if (!baseops::LookupProperty(cx, obj, id, objp, propp))
             return false;
     }
     if (!propp)
@@ -1003,17 +1004,17 @@ obj_keys(JSContext *cx, unsigned argc, V
     vp->setObject(*aobj);
 
     return true;
 }
 
 static bool
 HasProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, bool *foundp)
 {
-    if (!JSObject::hasProperty(cx, obj, id, foundp, JSRESOLVE_QUALIFIED | JSRESOLVE_DETECTING))
+    if (!JSObject::hasProperty(cx, obj, id, foundp, JSRESOLVE_QUALIFIED))
         return false;
     if (!*foundp) {
         vp.setUndefined();
         return true;
     }
 
     /*
      * We must go through the method read barrier in case id is 'get' or 'set'.
@@ -2471,23 +2472,18 @@ js_InferFlags(JSContext *cx, unsigned de
     if (!script)
         return defaultFlags;
 
     const JSCodeSpec *cs = &js_CodeSpec[*pc];
     uint32_t format = cs->format;
     unsigned flags = 0;
     if (JOF_MODE(format) != JOF_NAME)
         flags |= JSRESOLVE_QUALIFIED;
-    if (format & JOF_SET) {
+    if (format & JOF_SET)
         flags |= JSRESOLVE_ASSIGNING;
-    } else if (cs->length >= 0) {
-        pc += cs->length;
-        if (pc < script->code + script->length && Detecting(cx, script, pc))
-            flags |= JSRESOLVE_DETECTING;
-    }
     return flags;
 }
 
 /* static */ JSBool
 JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj,
                                HandleId id, MutableHandleValue vp, JSBool strict)
 {
     if (JS_UNLIKELY(obj->watched())) {
@@ -4331,18 +4327,16 @@ js_GetPropertyHelperInline(JSContext *cx
             if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic))
                 return JS_TRUE;
 
             /* Do not warn about tests like (obj[prop] == undefined). */
             if (cx->resolveFlags == RESOLVE_INFER) {
                 pc += js_CodeSpec[op].length;
                 if (Detecting(cx, script, pc))
                     return JS_TRUE;
-            } else if (cx->resolveFlags & JSRESOLVE_DETECTING) {
-                return JS_TRUE;
             }
 
             unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT;
             cx->stack.currentScript()->warnedAboutUndefinedProp = true;
 
             /* Ok, bad undefined property reference: whine about it. */
             RootedValue val(cx, IdToValue(id));
             if (!js_ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP,
@@ -5020,17 +5014,21 @@ js::CheckAccess(JSContext *cx, JSObject 
     if (!check)
         check = cx->runtime->securityCallbacks->checkObjectAccess;
     return !check || check(cx, pobj, id, mode, vp);
 }
 
 JSType
 baseops::TypeOf(JSContext *cx, HandleObject obj)
 {
-    return obj->isCallable() ? JSTYPE_FUNCTION : JSTYPE_OBJECT;
+    if (EmulatesUndefined(obj))
+        return JSTYPE_VOID;
+    if (obj->isCallable())
+        return JSTYPE_FUNCTION;
+    return JSTYPE_OBJECT;
 }
 
 bool
 js::IsDelegate(JSContext *cx, HandleObject obj, const js::Value &v, bool *result)
 {
     if (v.isPrimitive()) {
         *result = false;
         return true;
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -693,16 +693,18 @@ JSObject::clearType(JSContext *cx, js::H
 }
 
 inline void
 JSObject::setType(js::types::TypeObject *newType)
 {
     JS_ASSERT(newType);
     JS_ASSERT_IF(hasSpecialEquality(),
                  newType->hasAnyFlags(js::types::OBJECT_FLAG_SPECIAL_EQUALITY));
+    JS_ASSERT_IF(getClass()->emulatesUndefined(),
+                 newType->hasAnyFlags(js::types::OBJECT_FLAG_EMULATES_UNDEFINED));
     JS_ASSERT(!hasSingletonType());
     JS_ASSERT(compartment() == newType->compartment());
     type_ = newType;
 }
 
 inline js::TaggedProto
 JSObject::getTaggedProto() const
 {
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -69,17 +69,17 @@ typedef enum JSOp {
 #define JOF_SET           (1U<<8) /* set (i.e., assignment) operation */
 #define JOF_DEL           (1U<<9) /* delete operation */
 #define JOF_DEC          (1U<<10) /* decrement (--, not ++) opcode */
 #define JOF_INC          (2U<<10) /* increment (++, not --) opcode */
 #define JOF_INCDEC       (3U<<10) /* increment or decrement opcode */
 #define JOF_POST         (1U<<12) /* postorder increment or decrement */
 #define JOF_ASSIGNING     JOF_SET /* hint for Class.resolve, used for ops
                                      that do simplex assignment */
-#define JOF_DETECTING    (1U<<14) /* object detection for JSNewResolveOp */
+#define JOF_DETECTING    (1U<<14) /* object detection for warning-quelling */
 #define JOF_BACKPATCH    (1U<<15) /* backpatch placeholder during codegen */
 #define JOF_LEFTASSOC    (1U<<16) /* left-associative operator */
 /* (1U<<17) is unused */
 /* (1U<<18) is unused */
 #define JOF_PARENHEAD    (1U<<20) /* opcode consumes value of expression in
                                      parenthesized statement head */
 #define JOF_INVOKE       (1U<<21) /* JSOP_CALL, JSOP_NEW, JSOP_EVAL */
 #define JOF_TMPSLOT      (1U<<22) /* interpreter uses extra temporary slot
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -78,17 +78,16 @@ pointer_match(const T *a, const T *b)
  * NOTES
  * - in the js shell, you must use the -x command line option, or call
  *   options('xml') before compiling anything that uses XML literals
  *
  * TODO
  * - XXXbe patrol
  * - Fuse objects and their JSXML* private data into single GC-things
  * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
- * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
  */
 
 /*
  * Random utilities and global functions.
  */
 const char js_AttributeName_str[] = "AttributeName";
 const char js_localName_str[]     = "localName";
 const char js_xml_parent_str[]    = "parent";
@@ -1851,20 +1850,16 @@ ToXML(JSContext *cx, jsval v)
                     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
                     return js_GetXMLObject(cx, xml);
                 }
             }
             return obj;
         }
 
         clasp = obj->getClass();
-        if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
-            JS_ASSERT(0);
-        }
-
         if (clasp != &StringClass &&
             clasp != &NumberClass &&
             clasp != &BooleanClass) {
             goto bad;
         }
     }
 
     str = ToString(cx, v);
@@ -1933,20 +1928,16 @@ ToXMLList(JSContext *cx, jsval v)
                 if (!Append(cx, list, xml))
                     return NULL;
                 return listobj;
             }
             return obj;
         }
 
         clasp = obj->getClass();
-        if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
-            JS_ASSERT(0);
-        }
-
         if (clasp != &StringClass &&
             clasp != &NumberClass &&
             clasp != &BooleanClass) {
             goto bad;
         }
     }
 
     str = ToString(cx, v);
@@ -7126,18 +7117,17 @@ XML(JSContext *cx, unsigned argc, Value 
     xobj = ToXML(cx, v);
     if (!xobj)
         return JS_FALSE;
     xml = (JSXML *) xobj->getPrivate();
 
     if (IsConstructing(vp) && !JSVAL_IS_PRIMITIVE(v)) {
         vobj = JSVAL_TO_OBJECT(v);
         clasp = vobj->getClass();
-        if (clasp == &XMLClass ||
-            (clasp->flags & JSCLASS_DOCUMENT_OBSERVER)) {
+        if (clasp == &XMLClass) {
             copy = DeepCopy(cx, xml, NULL, 0);
             if (!copy)
                 return JS_FALSE;
             vp->setObject(*copy->object);
             return JS_TRUE;
         }
     }
 
--- a/js/src/methodjit/FastOps.cpp
+++ b/js/src/methodjit/FastOps.cpp
@@ -434,77 +434,38 @@ mjit::Compiler::jsop_equality(JSOp op, B
     /* The compiler should have handled constant folding. */
     JS_ASSERT(!(rhs->isConstant() && lhs->isConstant()));
 
     bool lhsTest;
     if ((lhsTest = CheckNullOrUndefined(lhs)) || CheckNullOrUndefined(rhs)) {
         /* What's the other mask? */
         FrameEntry *test = lhsTest ? rhs : lhs;
 
-        if (test->isType(JSVAL_TYPE_NULL) || test->isType(JSVAL_TYPE_UNDEFINED)) {
+        // Use a stub when comparing to object to address EmulatesUndefined.
+        if (test->isType(JSVAL_TYPE_NULL) ||
+            test->isType(JSVAL_TYPE_UNDEFINED) ||
+            test->isType(JSVAL_TYPE_OBJECT))
+        {
             return emitStubCmpOp(stub, target, fused);
-        } else if (test->isTypeKnown()) {
+        }
+
+        if (test->isTypeKnown()) {
             /* The test will not succeed, constant fold the compare. */
             bool result = GetCompareCondition(op, fused) == Assembler::NotEqual;
             frame.pop();
             frame.pop();
             if (target)
                 return constantFoldBranch(target, result);
             frame.push(BooleanValue(result));
             return true;
         }
 
-        /* The other side must be null or undefined. */
-        RegisterID reg = frame.ownRegForType(test);
-        frame.pop();
-        frame.pop();
-
-        /*
-         * :FIXME: Easier test for undefined || null?
-         * Maybe put them next to each other, subtract, do a single compare?
-         */
-
-        if (target) {
-            frame.syncAndKillEverything();
-            frame.freeReg(reg);
-
-            Jump sj = stubcc.masm.branchTest32(GetStubCompareCondition(fused),
-                                               Registers::ReturnReg, Registers::ReturnReg);
-
-            if ((op == JSOP_EQ && fused == JSOP_IFNE) ||
-                (op == JSOP_NE && fused == JSOP_IFEQ)) {
-                Jump b1 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED));
-                Jump b2 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_NULL));
-                Jump j1 = masm.jump();
-                b1.linkTo(masm.label(), &masm);
-                b2.linkTo(masm.label(), &masm);
-                Jump j2 = masm.jump();
-                if (!jumpAndRun(j2, target, &sj))
-                    return false;
-                j1.linkTo(masm.label(), &masm);
-            } else {
-                Jump j = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED));
-                Jump j2 = masm.branchPtr(Assembler::NotEqual, reg, ImmType(JSVAL_TYPE_NULL));
-                if (!jumpAndRun(j2, target, &sj))
-                    return false;
-                j.linkTo(masm.label(), &masm);
-            }
-        } else {
-            Jump j = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED));
-            Jump j2 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_NULL));
-            masm.move(Imm32(op == JSOP_NE), reg);
-            Jump j3 = masm.jump();
-            j2.linkTo(masm.label(), &masm);
-            j.linkTo(masm.label(), &masm);
-            masm.move(Imm32(op == JSOP_EQ), reg);
-            j3.linkTo(masm.label(), &masm);
-            frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
-        }
-        return true;
-    }
+        // If the type of the other side is unknown, use a stub for simplicity.
+        return emitStubCmpOp(stub, target, fused);
+   }
 
     if (cx->typeInferenceEnabled() &&
         lhs->isType(JSVAL_TYPE_OBJECT) && rhs->isType(JSVAL_TYPE_OBJECT))
     {
         CompileStatus status = jsop_equality_obj_obj(op, target, fused);
         if (status == Compile_Okay) return true;
         else if (status == Compile_Error) return false;
     }
@@ -598,26 +559,17 @@ mjit::Compiler::jsop_not()
 
             masm.xor32(Imm32(1), reg);
 
             frame.pop();
             frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
             break;
           }
 
-          case JSVAL_TYPE_OBJECT:
-          {
-            RegisterID reg = frame.allocReg();
-            masm.move(Imm32(0), reg);
-
-            frame.pop();
-            frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
-            break;
-          }
-
+          case JSVAL_TYPE_OBJECT: // EmulatesUndefined makes this non-trivial.
           default:
           {
             prepareStubCall(Uses(1));
             INLINE_STUBCALL_USES(stubs::ValueToBoolean, REJOIN_NONE, Uses(1));
 
             RegisterID reg = Registers::ReturnReg;
             frame.takeReg(reg);
             masm.xor32(Imm32(1), reg);
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -22,27 +22,28 @@
 #include "gc/Marking.h"
 #include "vm/Debugger.h"
 #include "vm/NumericConversions.h"
 #include "vm/String.h"
 #include "methodjit/Compiler.h"
 #include "methodjit/StubCalls.h"
 #include "methodjit/Retcon.h"
 
+#include "jsatominlines.h"
+#include "jsboolinlines.h"
+#include "jscntxtinlines.h"
+#include "jsfuninlines.h"
 #include "jsinterpinlines.h"
-#include "jsscopeinlines.h"
-#include "jsscriptinlines.h"
 #include "jsnuminlines.h"
 #include "jsobjinlines.h"
-#include "jscntxtinlines.h"
-#include "jsatominlines.h"
-#include "StubCalls-inl.h"
-#include "jsfuninlines.h"
+#include "jsscopeinlines.h"
+#include "jsscriptinlines.h"
 #include "jstypedarray.h"
 
+#include "StubCalls-inl.h"
 #include "vm/RegExpObject-inl.h"
 #include "vm/String-inl.h"
 
 #ifdef JS_ION
 #include "ion/Ion.h"
 #endif
 
 #ifdef XP_WIN
@@ -524,19 +525,21 @@ StubEqualityOp(VMFrame &f)
             }
         } else if (lval.isNullOrUndefined()) {
             cond = EQ;
         } else {
             cond = (lval.payloadAsRawUint32() == rval.payloadAsRawUint32()) == EQ;
         }
     } else {
         if (lval.isNullOrUndefined()) {
-            cond = rval.isNullOrUndefined() == EQ;
+            cond = (rval.isNullOrUndefined() ||
+                    (rval.isObject() && EmulatesUndefined(&rval.toObject()))) ==
+                    EQ;
         } else if (rval.isNullOrUndefined()) {
-            cond = !EQ;
+            cond = (lval.isObject() && EmulatesUndefined(&lval.toObject())) == EQ;
         } else {
             if (!ToPrimitive(cx, &lval))
                 return false;
             if (!ToPrimitive(cx, &rval))
                 return false;
 
             /*
              * The string==string case is repeated because ToPrimitive can
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3576,16 +3576,38 @@ RelaxRootChecks(JSContext *cx, unsigned 
 static JSBool
 GetMaxArgs(JSContext *cx, unsigned arg, jsval *vp)
 {
     JS_SET_RVAL(cx, vp, INT_TO_JSVAL(StackSpace::ARGS_LENGTH_MAX));
     return true;
 }
 
 static JSBool
+ObjectEmulatingUndefined(JSContext *cx, unsigned argc, jsval *vp)
+{
+    static JSClass cls = {
+        "ObjectEmulatingUndefined",
+        JSCLASS_EMULATES_UNDEFINED,
+        JS_PropertyStub,
+        JS_PropertyStub,
+        JS_PropertyStub,
+        JS_StrictPropertyStub,
+        JS_EnumerateStub,
+        JS_ResolveStub,
+        JS_ConvertStub
+    };
+
+    RootedObject obj(cx, JS_NewObject(cx, &cls, NULL, NULL));
+    if (!obj)
+        return false;
+    JS_SET_RVAL(cx, vp, ObjectValue(*obj));
+    return true;
+}
+
+static JSBool
 GetSelfHostedValue(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (argc != 1 || !args[0].isString()) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS,
                              "getSelfHostedValue");
         return false;
@@ -3904,16 +3926,21 @@ static JSFunctionSpecWithHelp shell_func
 "  Return the maximum number of supported args for a call."),
 
     JS_FN_HELP("relaxRootChecks", RelaxRootChecks, 0, 0,
 "relaxRootChecks()",
 "  Tone down the frequency with which the dynamic rooting analysis checks for\n"
 "  rooting hazards. This is helpful to reduce the time taken when interpreting\n"
 "  heavily numeric code."),
 
+    JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0,
+"objectEmulatingUndefined()",
+"  Return a new object obj for which typeof obj === \"undefined\", obj == null\n"
+"  and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"),
+
     JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
 "getSelfHostedValue()",
 "  Get a self-hosted value by its name. Note that these values don't get \n"
 "  cached, so repeatedly getting the same value creates multiple distinct clones."),
 
     JS_FS_HELP_END
 };
 #ifdef MOZ_PROFILING
@@ -4162,21 +4189,20 @@ its_enumerate(JSContext *cx, HandleObjec
 }
 
 static JSBool
 its_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
             MutableHandleObject objp)
 {
     if (its_noisy) {
         IdStringifier idString(cx, id);
-        fprintf(gOutFile, "resolving its property %s, flags {%s,%s,%s}\n",
+        fprintf(gOutFile, "resolving its property %s, flags {%s,%s}\n",
                idString.getBytes(),
                (flags & JSRESOLVE_QUALIFIED) ? "qualified" : "",
-               (flags & JSRESOLVE_ASSIGNING) ? "assigning" : "",
-               (flags & JSRESOLVE_DETECTING) ? "detecting" : "");
+               (flags & JSRESOLVE_ASSIGNING) ? "assigning" : "");
     }
     return true;
 }
 
 static JSBool
 its_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp)
 {
     if (its_noisy)
--- a/js/src/tests/js1_5/Regress/regress-246964.js
+++ b/js/src/tests/js1_5/Regress/regress-246964.js
@@ -45,17 +45,17 @@ else
   if (document.all != undefined)
   {
     actual = true;
   }
   reportCompare(expect, actual, status);
 
 
   status = summary + ' ' + inSection(4) + ' if (document.all !== undefined) ';
-  expect = false;
+  expect = true;
   actual = false;
   if (document.all !== undefined)
   {
     actual = true;
   }
   reportCompare(expect, actual, status);
 
   status = summary + ' ' + inSection(5) + ' if (document.all != null) ' ;
@@ -99,17 +99,17 @@ else
   actual = false;
   if (document.all == undefined)
   {
     actual = true;
   }
   reportCompare(expect, actual, status);
 
   status = summary + ' ' + inSection(10) + ' if (document.all === undefined) ';
-  expect = true;
+  expect = false;
   actual = false;
   if (document.all === undefined)
   {
     actual = true;
   }
   reportCompare(expect, actual, status);
 
   status = summary + ' ' + inSection(11) +