mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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 : a76167661fffc26adf0e631d0b87c842b5fc0ed5
This commit is contained in:
parent
b1a01dc7d1
commit
118086b8be
@ -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;
|
||||
|
@ -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, ¬Boolean);
|
||||
masm.branchTestBooleanTruthy(false, value, ifFalsy);
|
||||
masm.jump(ifTruthy);
|
||||
masm.bind(¬Boolean);
|
||||
|
||||
Label notInt32;
|
||||
masm.branchTestInt32(Assembler::NotEqual, tag, ¬Int32);
|
||||
cond = masm.testInt32Truthy(false, value);
|
||||
masm.j(cond, ifFalsy);
|
||||
masm.jump(ifTruthy);
|
||||
masm.bind(¬Int32);
|
||||
|
||||
if (ool) {
|
||||
Label notObject;
|
||||
|
||||
masm.branchTestObject(Assembler::NotEqual, tag, ¬Object);
|
||||
|
||||
Register objreg = masm.extractObject(value, ToRegister(scratch1));
|
||||
testObjectTruthy(objreg, ifTruthy, ifFalsy, ToRegister(scratch2), ool);
|
||||
|
||||
masm.bind(¬Object);
|
||||
} else {
|
||||
masm.branchTestObject(Assembler::Equal, tag, ifTruthy);
|
||||
}
|
||||
|
||||
// Test if a string is non-empty.
|
||||
Label notString;
|
||||
masm.branchTestString(Assembler::NotEqual, tag, ¬String);
|
||||
cond = masm.testStringTruthy(false, value);
|
||||
masm.j(cond, ifFalsy);
|
||||
masm.jump(ifTruthy);
|
||||
masm.bind(¬String);
|
||||
|
||||
// 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);
|
||||
|
@ -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__
|
||||
|
||||
|
@ -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,12 @@ 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);
|
||||
TypeOracle::UnaryTypes types = oracle->unaryTypes(script(), pc);
|
||||
test->infer(types, cx);
|
||||
current->end(test);
|
||||
|
||||
if (!cfgStack_.append(CFGState::AndOr(joinStart, join)))
|
||||
return false;
|
||||
@ -2787,7 +2791,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:
|
||||
//
|
||||
@ -2959,7 +2964,8 @@ IonBuilder::jsop_bitop(JSOp op)
|
||||
}
|
||||
|
||||
current->add(ins);
|
||||
ins->infer(oracle->binaryTypes(script(), pc));
|
||||
TypeOracle::BinaryTypes types = oracle->binaryTypes(script(), pc);
|
||||
ins->infer(types);
|
||||
|
||||
current->push(ins);
|
||||
if (ins->isEffectful() && !resumeAfter(ins))
|
||||
@ -3012,7 +3018,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())
|
||||
@ -4206,7 +4212,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;
|
||||
@ -5730,6 +5736,8 @@ IonBuilder::jsop_not()
|
||||
MNot *ins = new MNot(value);
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
TypeOracle::UnaryTypes types = oracle->unaryTypes(script(), pc);
|
||||
ins->infer(types, cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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, ¬Boolean);
|
||||
branchTestBooleanTruthy(false, value, &ifFalse);
|
||||
jump(ifTrue);
|
||||
bind(¬Boolean);
|
||||
|
||||
Label notInt32;
|
||||
branchTestInt32(Assembler::NotEqual, tag, ¬Int32);
|
||||
cond = testInt32Truthy(false, value);
|
||||
j(cond, &ifFalse);
|
||||
jump(ifTrue);
|
||||
bind(¬Int32);
|
||||
|
||||
// Test if a string is non-empty.
|
||||
Label notString;
|
||||
branchTestString(Assembler::NotEqual, tag, ¬String);
|
||||
cond = testStringTruthy(false, value);
|
||||
j(cond, &ifFalse);
|
||||
jump(ifTrue);
|
||||
bind(¬String);
|
||||
|
||||
// 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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -378,16 +378,3 @@ LMoveGroup::printOperands(FILE *fp)
|
||||
fprintf(fp, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
Label *
|
||||
LTestVAndBranch::ifTrue()
|
||||
{
|
||||
return ifTrue_->lir()->label();
|
||||
}
|
||||
|
||||
Label *
|
||||
LTestVAndBranch::ifFalse()
|
||||
{
|
||||
return ifFalse_->lir()->label();
|
||||
}
|
||||
|
||||
|
@ -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) \
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
18
js/src/jit-test/tests/basic/emulates-undefined.js
Normal file
18
js/src/jit-test/tests/basic/emulates-undefined.js
Normal 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();
|
37
js/src/jit-test/tests/truthiness/equal-null.js
Normal file
37
js/src/jit-test/tests/truthiness/equal-null.js
Normal 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);
|
37
js/src/jit-test/tests/truthiness/equal-undefined.js
Normal file
37
js/src/jit-test/tests/truthiness/equal-undefined.js
Normal 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);
|
46
js/src/jit-test/tests/truthiness/if-equal-null.js
Normal file
46
js/src/jit-test/tests/truthiness/if-equal-null.js
Normal 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);
|
46
js/src/jit-test/tests/truthiness/if-equal-undefined.js
Normal file
46
js/src/jit-test/tests/truthiness/if-equal-undefined.js
Normal 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);
|
46
js/src/jit-test/tests/truthiness/if-not-equal-null.js
Normal file
46
js/src/jit-test/tests/truthiness/if-not-equal-null.js
Normal 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);
|
46
js/src/jit-test/tests/truthiness/if-not-equal-undefined.js
Normal file
46
js/src/jit-test/tests/truthiness/if-not-equal-undefined.js
Normal 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);
|
24
js/src/jit-test/tests/truthiness/if.js
Normal file
24
js/src/jit-test/tests/truthiness/if.js
Normal 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);
|
37
js/src/jit-test/tests/truthiness/not-equal-null.js
Normal file
37
js/src/jit-test/tests/truthiness/not-equal-null.js
Normal 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);
|
37
js/src/jit-test/tests/truthiness/not-equal-undefined.js
Normal file
37
js/src/jit-test/tests/truthiness/not-equal-undefined.js
Normal 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);
|
24
js/src/jit-test/tests/truthiness/not.js
Normal file
24
js/src/jit-test/tests/truthiness/not.js
Normal 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);
|
23
js/src/jit-test/tests/truthiness/typeof.js
Normal file
23
js/src/jit-test/tests/truthiness/typeof.js
Normal 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");
|
@ -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 \
|
||||
|
@ -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)
|
||||
|
107
js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
Normal file
107
js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
Normal 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)
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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___ */
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
@ -644,33 +596,22 @@ mjit::Compiler::jsop_not()
|
||||
Jump jmpNotBool = masm.testBoolean(Assembler::NotEqual, type);
|
||||
masm.xor32(Imm32(1), data);
|
||||
|
||||
|
||||
/* OOL path is for int + object. */
|
||||
/* OOL path is for int. */
|
||||
Label lblMaybeInt32 = stubcc.masm.label();
|
||||
|
||||
Jump jmpNotInt32 = stubcc.masm.testInt32(Assembler::NotEqual, type);
|
||||
stubcc.masm.set32(Assembler::Equal, data, Imm32(0), data);
|
||||
Jump jmpInt32Exit = stubcc.masm.jump();
|
||||
|
||||
Label lblMaybeObject = stubcc.masm.label();
|
||||
Jump jmpNotObject = stubcc.masm.testPrimitive(Assembler::Equal, type);
|
||||
stubcc.masm.move(Imm32(0), data);
|
||||
Jump jmpObjectExit = stubcc.masm.jump();
|
||||
|
||||
|
||||
/* Rejoin location. */
|
||||
Label lblRejoin = masm.label();
|
||||
|
||||
/* Patch up jumps. */
|
||||
stubcc.linkExitDirect(jmpNotBool, lblMaybeInt32);
|
||||
|
||||
jmpNotInt32.linkTo(lblMaybeObject, &stubcc.masm);
|
||||
jmpNotInt32.linkTo(syncTarget, &stubcc.masm);
|
||||
stubcc.crossJump(jmpInt32Exit, lblRejoin);
|
||||
|
||||
jmpNotObject.linkTo(syncTarget, &stubcc.masm);
|
||||
stubcc.crossJump(jmpObjectExit, lblRejoin);
|
||||
|
||||
|
||||
/* Leave. */
|
||||
stubcc.leave();
|
||||
OOL_STUBCALL(stubs::Not, REJOIN_FALLTHROUGH);
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
@ -4168,11 +4195,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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user