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

--HG--
extra : rebase_source : 0081cf0155a2ca30cee859db9dd9bf2e3374b204
This commit is contained in:
Jeff Walden 2012-09-15 11:19:54 -07:00
parent 0cc102545f
commit c8cc474cd2
46 changed files with 1497 additions and 355 deletions

View File

@ -8551,7 +8551,7 @@ ResolveImpl(JSContext *cx, nsIXPConnectWrappedNative *wrapper, jsid id,
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 */
@ -8566,7 +8566,8 @@ static JSClass sHTMLDocumentAllClass = {
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 */
@ -8888,21 +8889,6 @@ GetDocumentAllHelper(JSContext *cx, JSObject *obj, JSObject **result)
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)
@ -8911,55 +8897,27 @@ nsHTMLDocumentSH::DocumentAllHelperGetProperty(JSContext *cx, JSHandleObject obj
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;
@ -9121,11 +9079,9 @@ nsHTMLDocumentSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
}
// 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");
@ -9144,16 +9100,9 @@ nsHTMLDocumentSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
// 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;

View File

@ -5,7 +5,10 @@
* 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"
@ -23,6 +26,7 @@ using namespace js;
using namespace js::ion;
using mozilla::DebugOnly;
using mozilla::Maybe;
namespace js {
namespace ion {
@ -164,12 +168,195 @@ CodeGenerator::visitDoubleToInt32(LDoubleToInt32 *lir)
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;
}
@ -2250,27 +2437,64 @@ CodeGenerator::visitCompareV(LCompareV *lir)
}
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;
}
@ -2288,13 +2512,13 @@ CodeGenerator::visitIsNullOrUndefined(LIsNullOrUndefined *lir)
}
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;
@ -2310,11 +2534,33 @@ CodeGenerator::visitIsNullOrUndefinedAndBranch(LIsNullOrUndefinedAndBranch *lir)
op = JSOP_EQ;
}
Register tag = masm.splitTagForTest(value);
masm.branchTestNull(Assembler::Equal, tag, ifTrue->lir()->label());
MOZ_ASSERT(lir->mir()->lhs()->type() != MIRType_Object ||
lir->mir()->operandMightEmulateUndefined(),
"Operands which can't emulate undefined should have been folded");
Assembler::Condition cond = masm.testUndefined(Assembler::Equal, tag);
emitBranch(cond, ifTrue, ifFalse);
OutOfLineTestObject *ool = NULL;
if (lir->mir()->operandMightEmulateUndefined()) {
ool = new OutOfLineTestObject();
if (!addOutOfLineCode(ool))
return false;
}
Register tag = masm.splitTagForTest(value);
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;
}
@ -2333,6 +2579,81 @@ CodeGenerator::visitIsNullOrUndefinedAndBranch(LIsNullOrUndefinedAndBranch *lir)
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)
{
@ -2417,19 +2738,69 @@ CodeGenerator::visitSetInitializedLength(LSetInitializedLength *lir)
}
bool
CodeGenerator::visitNotV(LNotV *lir)
CodeGenerator::visitNotO(LNotO *lir)
{
Label setFalse;
Label join;
masm.branchTestValueTruthy(ToValue(lir, LNotV::Input), &setFalse, ToFloatRegister(lir->tempFloat()));
MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
"This should be constant-folded if the object can't emulate undefined.");
// fallthrough to set true
masm.move32(Imm32(1), ToRegister(lir->getDef(0)));
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);
// true case rediercts to setFalse
masm.bind(&setFalse);
masm.move32(Imm32(0), ToRegister(lir->getDef(0)));
masm.bind(ifFalsy);
masm.move32(Imm32(1), output);
masm.bind(&join);
return true;
}
bool
CodeGenerator::visitNotV(LNotV *lir)
{
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;
Register output = ToRegister(lir->output());
masm.bind(ifFalsy);
masm.move32(Imm32(1), output);
masm.jump(&join);
masm.bind(ifTruthy);
masm.move32(Imm32(0), output);
// both branches meet here.
masm.bind(&join);

View File

@ -21,6 +21,7 @@
namespace js {
namespace ion {
class OutOfLineTestObject;
class OutOfLineNewArray;
class OutOfLineNewObject;
class CheckOverRecursedFailure;
@ -60,6 +61,8 @@ class CodeGenerator : public CodeGeneratorSpecific
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);
@ -106,6 +109,7 @@ class CodeGenerator : public CodeGeneratorSpecific
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);
@ -124,8 +128,10 @@ class CodeGenerator : public CodeGeneratorSpecific
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);
@ -225,10 +231,26 @@ class CodeGenerator : public CodeGeneratorSpecific
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__

View File

@ -2350,7 +2350,8 @@ IonBuilder::lookupSwitch(JSOp op, jssrcnote *sn)
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
@ -2388,7 +2389,8 @@ IonBuilder::lookupSwitch(JSOp op, jssrcnote *sn)
} 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
@ -2720,6 +2722,8 @@ IonBuilder::processCondSwitchBody(CFGState &state)
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);
@ -2732,12 +2736,11 @@ IonBuilder::jsop_andor(JSOp op)
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;
@ -2787,7 +2790,8 @@ IonBuilder::jsop_ifeq(JSOp op)
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:
//
@ -3012,7 +3016,7 @@ IonBuilder::jsop_binary(JSOp op, MDefinition *left, MDefinition *right)
TypeOracle::BinaryTypes types = oracle->binaryTypes(script(), pc);
current->add(ins);
ins->infer(cx, types);
ins->infer(types, cx);
current->push(ins);
if (ins->isEffectful())
@ -4202,7 +4206,7 @@ IonBuilder::jsop_compare(JSOp op)
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;
@ -5726,6 +5730,7 @@ IonBuilder::jsop_not()
MNot *ins = new MNot(value);
current->add(ins);
current->push(ins);
ins->infer(oracle->unaryTypes(script(), pc), cx);
return true;
}

View File

@ -108,51 +108,6 @@ MacroAssembler::PopRegsInMaskIgnore(RegisterSet set, RegisterSet ignore)
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,

View File

@ -272,8 +272,6 @@ class MacroAssembler : public MacroAssemblerSpecific
}
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.

View File

@ -859,20 +859,58 @@ class LTestDAndBranch : public LInstructionHelper<0, 1, 0>
}
};
// 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;
@ -881,8 +919,24 @@ class LTestVAndBranch : public LInstructionHelper<0, BOX_PIECES, 1>
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>
@ -1120,29 +1174,46 @@ class LCompareBAndBranch : public LInstructionHelper<0, BOX_PIECES + 1, 0>
}
};
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)
LIR_HEADER(IsNullOrLikeUndefinedAndBranch)
LIsNullOrUndefinedAndBranch(MBasicBlock *ifTrue, MBasicBlock *ifFalse)
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;
@ -1155,6 +1226,59 @@ class LIsNullOrUndefinedAndBranch : public LInstructionHelper<0, BOX_PIECES, 0>
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.
@ -1179,21 +1303,51 @@ class LNotD : public LInstructionHelper<1, 1, 0>
}
};
// 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

View File

@ -375,16 +375,3 @@ LMoveGroup::printOperands(FILE *fp)
fprintf(fp, ", ");
}
}
Label *
LTestVAndBranch::ifTrue()
{
return ifTrue_->lir()->label();
}
Label *
LTestVAndBranch::ifFalse()
{
return ifFalse_->lir()->label();
}

View File

@ -54,6 +54,7 @@
_(TestIAndBranch) \
_(TestDAndBranch) \
_(TestVAndBranch) \
_(TestOAndBranch) \
_(PolyInlineDispatch) \
_(Compare) \
_(CompareD) \
@ -63,8 +64,10 @@
_(CompareDAndBranch) \
_(CompareB) \
_(CompareBAndBranch) \
_(IsNullOrUndefined) \
_(IsNullOrUndefinedAndBranch) \
_(IsNullOrLikeUndefined) \
_(IsNullOrLikeUndefinedAndBranch)\
_(EmulatesUndefined) \
_(EmulatesUndefinedAndBranch) \
_(MinMaxI) \
_(MinMaxD) \
_(NegD) \
@ -77,6 +80,7 @@
_(MathFunctionD) \
_(NotI) \
_(NotD) \
_(NotO) \
_(NotV) \
_(AddI) \
_(SubI) \

View File

@ -367,11 +367,32 @@ LIRGenerator::visitTest(MTest *test)
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
@ -379,10 +400,6 @@ LIRGenerator::visitTest(MTest *test)
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());
@ -429,8 +446,27 @@ LIRGenerator::visitTest(MTest *test)
// 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);
}
@ -507,7 +543,7 @@ LIRGenerator::visitCompare(MCompare *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))
@ -536,8 +572,24 @@ LIRGenerator::visitCompare(MCompare *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);
}
@ -1391,14 +1443,15 @@ 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));
@ -1413,10 +1466,24 @@ LIRGenerator::visitNot(MNot *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);

View File

@ -189,6 +189,26 @@ 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)
{
@ -991,7 +1011,7 @@ MMul::canOverflow()
}
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,
@ -1065,11 +1085,16 @@ SafelyCoercesToDouble(JSContext *cx, types::StackTypeSet *types)
}
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());
@ -1124,14 +1149,12 @@ MCompare::infer(JSContext *cx, const TypeOracle::BinaryTypes &b)
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)) {
@ -1391,10 +1414,13 @@ MCompare::tryFold(bool *result)
*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;
@ -1526,24 +1552,36 @@ MCompare::foldsTo(bool useValueNumbers)
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;
}

View File

@ -893,7 +893,11 @@ 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);
@ -920,7 +924,15 @@ class MTest
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.
@ -1370,10 +1382,12 @@ class MCompare
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();
@ -1387,7 +1401,7 @@ class MCompare
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_;
}
@ -1398,6 +1412,12 @@ class MCompare
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)
@ -2186,7 +2206,7 @@ class MBinaryArithInstruction
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;
@ -3406,9 +3426,12 @@ 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();
@ -3416,8 +3439,16 @@ class MNot
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);
}

View File

@ -13,6 +13,7 @@
#include "vm/StringObject-inl.h"
#include "jsboolinlines.h"
#include "jsinterpinlines.h"
using namespace js;
@ -227,11 +228,11 @@ StringsEqual(JSContext *cx, HandleString lhs, HandleString rhs, JSBool *res)
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

View File

@ -432,7 +432,7 @@ bool GreaterThanOrEqual(JSContext *cx, HandleValue lhs, HandleValue rhs, JSBool
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);

View File

@ -209,6 +209,15 @@ class CodeGeneratorShared : public LInstructionVisitor
}
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);

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -40,7 +40,6 @@ CPPSRCS = \
testFuncCallback.cpp \
testFunctionProperties.cpp \
testGCOutOfMemory.cpp \
testOOM.cpp \
testGetPropertyDefault.cpp \
testHashTable.cpp \
testIndexToString.cpp \
@ -51,6 +50,8 @@ CPPSRCS = \
testLookup.cpp \
testLooselyEqual.cpp \
testNewObject.cpp \
testObjectEmulatingUndefined.cpp \
testOOM.cpp \
testOps.cpp \
testOriginPrincipals.cpp \
testParseJSON.cpp \

View File

@ -37,11 +37,23 @@ BEGIN_TEST(testLookup_bug522590)
}
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;
@ -50,8 +62,12 @@ document_resolve(JSContext *cx, JSHandleObject obj, JSHandleId id, unsigned flag
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;
}
@ -75,7 +91,7 @@ BEGIN_TEST(testLookup_bug570195)
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)

View File

@ -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)

View File

@ -3427,8 +3427,13 @@ JS_NewObject(JSContext *cx, JSClass *jsclasp, JSObject *protoArg, JSObject *pare
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());
@ -3655,8 +3660,7 @@ JS_HasPropertyById(JSContext *cx, JSObject *objArg, jsid idArg, JSBool *foundp)
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;
}
@ -3702,10 +3706,8 @@ JS_AlreadyHasOwnPropertyById(JSContext *cx, JSObject *objArg, jsid id_, JSBool *
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;
}

View File

@ -1838,7 +1838,6 @@ typedef JSBool
*
* 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,
@ -2761,8 +2760,6 @@ ToBoolean(const Value &v)
return v.toBoolean();
if (v.isInt32())
return v.toInt32() != 0;
if (v.isObject())
return true;
if (v.isNullOrUndefined())
return false;
if (v.isDouble()) {
@ -2770,7 +2767,7 @@ ToBoolean(const Value &v)
return !MOZ_DOUBLE_IS_NaN(d) && d != 0;
}
/* Slow path. Handle Strings. */
/* The slow path handles strings and objects. */
return js::ToBooleanSlow(v);
}
@ -4087,7 +4084,9 @@ struct JSClass {
#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. */
/*
@ -4230,7 +4229,6 @@ JS_IdToValue(JSContext *cx, jsid id, jsval *vp);
*/
#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

View File

@ -196,8 +196,11 @@ js_BooleanToString(JSContext *cx, JSBool b)
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

View File

@ -7,6 +7,11 @@
#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"
@ -26,6 +31,16 @@ BooleanGetPrimitiveValue(JSContext *cx, JSObject &obj, Value *vp)
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___ */

View File

@ -331,6 +331,12 @@ struct Class
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));

View File

@ -3591,6 +3591,8 @@ TypeObject::print()
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");
}
@ -5768,6 +5770,8 @@ JSObject::makeLazyType(JSContext *cx)
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

View File

@ -402,8 +402,11 @@ enum {
/* 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.
@ -907,6 +910,8 @@ struct TypeObject : gc::Cell
/* 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

View File

@ -51,6 +51,7 @@
#include "ion/Ion.h"
#include "jsatominlines.h"
#include "jsboolinlines.h"
#include "jsinferinlines.h"
#include "jsinterpinlines.h"
#include "jsobjinlines.h"
@ -638,12 +639,13 @@ js::LooselyEqual(JSContext *cx, const Value &lval, const Value &rval, bool *resu
}
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;
}

View File

@ -56,6 +56,7 @@
#include "jsarrayinlines.h"
#include "jsatominlines.h"
#include "jsboolinlines.h"
#include "jscntxtinlines.h"
#include "jsinterpinlines.h"
#include "jsobjinlines.h"
@ -517,7 +518,7 @@ 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;
@ -1008,7 +1009,7 @@ obj_keys(JSContext *cx, unsigned argc, Value *vp)
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();
@ -2476,13 +2477,8 @@ js_InferFlags(JSContext *cx, unsigned defaultFlags)
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;
}
@ -4336,8 +4332,6 @@ js_GetPropertyHelperInline(JSContext *cx, HandleObject obj, HandleObject receive
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;
@ -5025,7 +5019,11 @@ js::CheckAccess(JSContext *cx, JSObject *obj_, HandleId id, JSAccessMode mode,
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

View File

@ -698,6 +698,8 @@ 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;

View File

@ -74,7 +74,7 @@ typedef enum JSOp {
#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 */

View File

@ -83,7 +83,6 @@ pointer_match(const T *a, const T *b)
* - 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!
*/
/*
@ -1856,10 +1855,6 @@ ToXML(JSContext *cx, jsval v)
}
clasp = obj->getClass();
if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
JS_ASSERT(0);
}
if (clasp != &StringClass &&
clasp != &NumberClass &&
clasp != &BooleanClass) {
@ -1938,10 +1933,6 @@ ToXMLList(JSContext *cx, jsval v)
}
clasp = obj->getClass();
if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
JS_ASSERT(0);
}
if (clasp != &StringClass &&
clasp != &NumberClass &&
clasp != &BooleanClass) {
@ -7131,8 +7122,7 @@ XML(JSContext *cx, unsigned argc, Value *vp)
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;

View File

@ -439,9 +439,15 @@ mjit::Compiler::jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp f
/* 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();
@ -452,54 +458,9 @@ mjit::Compiler::jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp f
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))
@ -603,16 +564,7 @@ mjit::Compiler::jsop_not()
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));

View File

@ -27,17 +27,18 @@
#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"
@ -529,9 +530,11 @@ StubEqualityOp(VMFrame &f)
}
} 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;

View File

@ -3580,6 +3580,28 @@ GetMaxArgs(JSContext *cx, unsigned arg, jsval *vp)
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)
{
@ -3909,6 +3931,11 @@ static JSFunctionSpecWithHelp shell_functions[] = {
" 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"
@ -4167,11 +4194,10 @@ its_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
{
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;
}

View File

@ -50,7 +50,7 @@ else
status = summary + ' ' + inSection(4) + ' if (document.all !== undefined) ';
expect = false;
expect = true;
actual = false;
if (document.all !== undefined)
{
@ -104,7 +104,7 @@ else
reportCompare(expect, actual, status);
status = summary + ' ' + inSection(10) + ' if (document.all === undefined) ';
expect = true;
expect = false;
actual = false;
if (document.all === undefined)
{