Bug 953164 - IonMonkey: Improve type information at branches, r=jandem

This commit is contained in:
Hannes Verschore 2014-02-12 10:57:34 +01:00
parent 06c5f9e5cc
commit 192f0c7a6a
11 changed files with 242 additions and 41 deletions

View File

@ -1647,42 +1647,31 @@ TryEliminateTypeBarrierFromTest(MTypeBarrier *barrier, bool filtersNull, bool fi
input = inputUnbox->input();
}
if (test->getOperand(0) == input && direction == TRUE_BRANCH) {
*eliminated = true;
if (inputUnbox)
inputUnbox->makeInfallible();
barrier->replaceAllUsesWith(barrier->input());
return;
}
MDefinition *subject = nullptr;
bool removeUndefined;
bool removeNull;
test->filtersUndefinedOrNull(direction == TRUE_BRANCH, &subject, &removeUndefined, &removeNull);
if (!test->getOperand(0)->isCompare())
// The Test doesn't filter undefined nor null.
if (!subject)
return;
MCompare *compare = test->getOperand(0)->toCompare();
MCompare::CompareType compareType = compare->compareType();
if (compareType != MCompare::Compare_Undefined && compareType != MCompare::Compare_Null)
return;
if (compare->getOperand(0) != input)
// Make sure the subject equals the input to the TypeBarrier.
if (subject != input)
return;
JSOp op = compare->jsop();
JS_ASSERT(op == JSOP_EQ || op == JSOP_STRICTEQ ||
op == JSOP_NE || op == JSOP_STRICTNE);
if ((direction == TRUE_BRANCH) != (op == JSOP_NE || op == JSOP_STRICTNE))
// When the TypeBarrier filters undefined, the test must at least also do,
// this, before the TypeBarrier can get removed.
if (!removeUndefined && filtersUndefined)
return;
// A test 'if (x.f != null)' or 'if (x.f != undefined)' filters both null
// and undefined. If strict equality is used, only the specified rhs is
// tested for.
if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) {
if (compareType == MCompare::Compare_Undefined && !filtersUndefined)
return;
if (compareType == MCompare::Compare_Null && !filtersNull)
return;
}
// When the TypeBarrier filters null, the test must at least also do,
// this, before the TypeBarrier can get removed.
if (!removeNull && filtersNull)
return;
// Eliminate the TypeBarrier. The possible TypeBarrier unboxing is kept,
// but made infallible.
*eliminated = true;
if (inputUnbox)
inputUnbox->makeInfallible();

View File

@ -197,18 +197,21 @@ GetJumpOffset(jsbytecode *pc)
}
IonBuilder::CFGState
IonBuilder::CFGState::If(jsbytecode *join, MBasicBlock *ifFalse)
IonBuilder::CFGState::If(jsbytecode *join, MTest *test)
{
CFGState state;
state.state = IF_TRUE;
state.stopAt = join;
state.branch.ifFalse = ifFalse;
state.branch.ifFalse = test->ifFalse();
state.branch.test = test;
return state;
}
IonBuilder::CFGState
IonBuilder::CFGState::IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MBasicBlock *ifFalse)
IonBuilder::CFGState::IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MTest *test)
{
MBasicBlock *ifFalse = test->ifFalse();
CFGState state;
// If the end of the false path is the same as the start of the
// false path, then the "else" block is empty and we can devolve
@ -221,6 +224,7 @@ IonBuilder::CFGState::IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MBasicBl
state.stopAt = trueEnd;
state.branch.falseEnd = falseEnd;
state.branch.ifFalse = ifFalse;
state.branch.test = test;
return state;
}
@ -231,6 +235,7 @@ IonBuilder::CFGState::AndOr(jsbytecode *join, MBasicBlock *joinStart)
state.state = AND_OR;
state.stopAt = join;
state.branch.ifFalse = joinStart;
state.branch.test = nullptr;
return state;
}
@ -1937,6 +1942,10 @@ IonBuilder::processIfElseTrueEnd(CFGState &state)
pc = state.branch.ifFalse->pc();
setCurrentAndSpecializePhis(state.branch.ifFalse);
graph().moveBlockToEnd(current);
if (state.branch.test)
filterTypesAtTest(state.branch.test);
return ControlStatus_Jumped;
}
@ -3047,6 +3056,59 @@ IonBuilder::tableSwitch(JSOp op, jssrcnote *sn)
return ControlStatus_Jumped;
}
bool
IonBuilder::filterTypesAtTest(MTest *test)
{
JS_ASSERT(test->ifTrue() == current || test->ifFalse() == current);
bool trueBranch = test->ifTrue() == current;
MDefinition *subject = nullptr;
bool removeUndefined;
bool removeNull;
test->filtersUndefinedOrNull(trueBranch, &subject, &removeUndefined, &removeNull);
// The test filters no undefined or null.
if (!subject)
return true;
// There is no TypeSet that can get filtered.
if (!subject->resultTypeSet())
return true;
// Only do this optimization if the typeset does contains null or undefined.
if ((removeUndefined && subject->resultTypeSet()->hasType(types::Type::UndefinedType())) ||
(removeNull && subject->resultTypeSet()->hasType(types::Type::NullType())))
{
return true;
}
// Find all values on the stack that correspond to the subject
// and replace it with a MIR with filtered TypeSet information.
// Create the replacement MIR lazily upon first occurence.
MDefinition *replace = nullptr;
for (uint32_t i = 0; i < current->stackDepth(); i++) {
if (current->getSlot(i) != subject)
continue;
// Create replacement MIR with filtered TypesSet.
if (!replace) {
types::TemporaryTypeSet *type =
subject->resultTypeSet()->filter(alloc_->lifoAlloc(), removeUndefined,
removeNull);
if (!type)
return false;
replace = ensureDefiniteTypeSet(subject, type);
}
current->setSlot(i, replace);
}
return true;
}
bool
IonBuilder::jsop_label()
{
@ -3465,7 +3527,7 @@ IonBuilder::jsop_ifeq(JSOp op)
// IF case, the IFEQ offset is the join point.
switch (SN_TYPE(sn)) {
case SRC_IF:
if (!cfgStack_.append(CFGState::If(falseStart, ifFalse)))
if (!cfgStack_.append(CFGState::If(falseStart, test)))
return false;
break;
@ -3484,7 +3546,7 @@ IonBuilder::jsop_ifeq(JSOp op)
JS_ASSERT(falseEnd > trueEnd);
JS_ASSERT(falseEnd >= falseStart);
if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, ifFalse)))
if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test)))
return false;
break;
}
@ -3497,6 +3559,9 @@ IonBuilder::jsop_ifeq(JSOp op)
// it's the next instruction.
setCurrentAndSpecializePhis(ifTrue);
// Filter the types in the true branch.
filterTypesAtTest(test);
return true;
}
@ -6305,6 +6370,26 @@ IonBuilder::ensureDefiniteType(MDefinition *def, JSValueType definiteType)
return replace;
}
MDefinition *
IonBuilder::ensureDefiniteTypeSet(MDefinition *def, types::TemporaryTypeSet *types)
{
// We cannot arbitrarily add a typeset to a definition. It can be shared
// in another path. So we always need to create a new MIR.
// Use ensureDefiniteType to do unboxing. If that happened the type can
// be added on the newly created unbox operation.
MDefinition *replace = ensureDefiniteType(def, types->getKnownTypeTag());
if (replace != def) {
replace->setResultTypeSet(types);
return replace;
}
// Create a NOP mir instruction to filter the typeset.
MFilterTypeSet *filter = MFilterTypeSet::New(alloc(), def, types);
current->add(filter);
return filter;
}
static size_t
NumFixedSlots(JSObject *object)
{

View File

@ -110,6 +110,7 @@ class IonBuilder : public MIRGenerator
MBasicBlock *ifFalse;
jsbytecode *falseEnd;
MBasicBlock *ifTrue; // Set when the end of the true path is reached.
MTest *test;
} branch;
struct {
// Common entry point.
@ -200,8 +201,8 @@ class IonBuilder : public MIRGenerator
}
}
static CFGState If(jsbytecode *join, MBasicBlock *ifFalse);
static CFGState IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MBasicBlock *ifFalse);
static CFGState If(jsbytecode *join, MTest *test);
static CFGState IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MTest *test);
static CFGState AndOr(jsbytecode *join, MBasicBlock *joinStart);
static CFGState TableSwitch(jsbytecode *exitpc, MTableSwitch *ins);
static CFGState CondSwitch(IonBuilder *builder, jsbytecode *exitpc, jsbytecode *defaultTarget);
@ -336,6 +337,9 @@ class IonBuilder : public MIRGenerator
MConstant *constant(const Value &v);
MConstant *constantInt(int32_t i);
// Filter the type information at tests
bool filterTypesAtTest(MTest *test);
// Add a guard which ensure that the set of type which goes through this
// generated code correspond to the observed types for the bytecode.
bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needBarrier);
@ -351,6 +355,9 @@ class IonBuilder : public MIRGenerator
// added to |current| in this case.
MDefinition *ensureDefiniteType(MDefinition* def, JSValueType definiteType);
// Creates a MDefinition based on the given def improved with type as TypeSet.
MDefinition *ensureDefiniteTypeSet(MDefinition* def, types::TemporaryTypeSet *types);
JSObject *getSingletonPrototype(JSFunction *target);
MDefinition *createThisScripted(MDefinition *callee);

View File

@ -2196,6 +2196,12 @@ LIRGenerator::visitStoreSlot(MStoreSlot *ins)
return true;
}
bool
LIRGenerator::visitFilterTypeSet(MFilterTypeSet *ins)
{
return redefine(ins, ins->input());
}
bool
LIRGenerator::visitTypeBarrier(MTypeBarrier *ins)
{

View File

@ -166,6 +166,7 @@ class LIRGenerator : public LIRGeneratorSpecific
bool visitInterruptCheck(MInterruptCheck *ins);
bool visitInterruptCheckPar(MInterruptCheckPar *ins);
bool visitStoreSlot(MStoreSlot *ins);
bool visitFilterTypeSet(MFilterTypeSet *ins);
bool visitTypeBarrier(MTypeBarrier *ins);
bool visitMonitorTypes(MMonitorTypes *ins);
bool visitPostWriteBarrier(MPostWriteBarrier *ins);

View File

@ -225,6 +225,12 @@ MaybeCallable(MDefinition *op)
return types->maybeCallable();
}
MTest *
MTest::New(TempAllocator &alloc, MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse)
{
return new(alloc) MTest(ins, ifTrue, ifFalse);
}
void
MTest::infer()
{
@ -245,6 +251,32 @@ MTest::foldsTo(TempAllocator &alloc, bool useValueNumbers)
return this;
}
void
MTest::filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined,
bool *filtersNull)
{
MDefinition *ins = getOperand(0);
if (ins->isCompare()) {
ins->toCompare()->filtersUndefinedOrNull(trueBranch, subject, filtersUndefined, filtersNull);
return;
}
if (!trueBranch && ins->isNot()) {
*subject = ins->getOperand(0);
*filtersUndefined = *filtersNull = true;
return;
}
if (trueBranch) {
*subject = ins;
*filtersUndefined = *filtersNull = true;
return;
}
*filtersUndefined = *filtersNull = false;
*subject = nullptr;
}
void
MDefinition::printOpcode(FILE *fp) const
{
@ -812,12 +844,6 @@ MFloor::trySpecializeFloat32(TempAllocator &alloc)
setPolicyType(MIRType_Float32);
}
MTest *
MTest::New(TempAllocator &alloc, MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse)
{
return new(alloc) MTest(ins, ifTrue, ifFalse);
}
MCompare *
MCompare::New(TempAllocator &alloc, MDefinition *left, MDefinition *right, JSOp op)
{
@ -2552,6 +2578,37 @@ MCompare::trySpecializeFloat32(TempAllocator &alloc)
}
}
void
MCompare::filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined,
bool *filtersNull)
{
*filtersNull = *filtersUndefined = false;
*subject = nullptr;
if (compareType() != Compare_Undefined && compareType() != Compare_Null)
return;
JS_ASSERT(jsop() == JSOP_STRICTNE || jsop() == JSOP_NE ||
jsop() == JSOP_STRICTEQ || jsop() == JSOP_EQ);
// JSOP_*NE only removes undefined/null from if/true branch
if (!trueBranch && (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE))
return;
// JSOP_*EQ only removes undefined/null from else/false branch
if (trueBranch && (jsop() == JSOP_STRICTEQ || jsop() == JSOP_EQ))
return;
if (jsop() == JSOP_STRICTEQ || jsop() == JSOP_STRICTNE) {
*filtersUndefined = compareType() == Compare_Undefined;
*filtersNull = compareType() == Compare_Null;
} else {
*filtersUndefined = *filtersNull = true;
}
*subject = lhs();
}
void
MNot::infer()
{

View File

@ -1321,6 +1321,8 @@ class MTest
}
void infer();
MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
void filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined,
bool *filtersNull);
void markOperandCantEmulateUndefined() {
operandMightEmulateUndefined_ = false;
@ -2278,6 +2280,8 @@ class MCompare
bool tryFold(bool *result);
bool evaluateConstantOperands(bool *result);
MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
void filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined,
bool *filtersNull);
void infer(BaselineInspector *inspector, jsbytecode *pc);
CompareType compareType() const {
@ -8736,6 +8740,37 @@ class MGuardThreadExclusive
}
};
class MFilterTypeSet
: public MUnaryInstruction
{
MFilterTypeSet(MDefinition *def, types::TemporaryTypeSet *types)
: MUnaryInstruction(def)
{
JS_ASSERT(!types->unknown());
MIRType type = MIRTypeFromValueType(types->getKnownTypeTag());
setResultType(type);
setResultTypeSet(types);
}
public:
INSTRUCTION_HEADER(FilterTypeSet)
static MFilterTypeSet *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types) {
return new(alloc) MFilterTypeSet(def, types);
}
bool congruentTo(MDefinition *def) const {
return false;
}
AliasSet getAliasSet() const {
return AliasSet::None();
}
virtual bool neverHoist() const {
return resultTypeSet()->empty();
}
};
// Given a value, guard that the value is in a particular TypeSet, then returns
// that value.
class MTypeBarrier

View File

@ -114,6 +114,7 @@ namespace jit {
_(LoadSlot) \
_(StoreSlot) \
_(FunctionEnvironment) \
_(FilterTypeSet) \
_(TypeBarrier) \
_(MonitorTypes) \
_(PostWriteBarrier) \

View File

@ -201,6 +201,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor
SAFE_OP(LoadSlot)
WRITE_GUARDED_OP(StoreSlot, slots)
SAFE_OP(FunctionEnvironment) // just a load of func env ptr
SAFE_OP(FilterTypeSet)
SAFE_OP(TypeBarrier) // causes a bailout if the type is not found: a-ok with us
SAFE_OP(MonitorTypes) // causes a bailout if the type is not found: a-ok with us
UNSAFE_OP(PostWriteBarrier)

View File

@ -492,6 +492,22 @@ TypeSet::clone(LifoAlloc *alloc) const
return res;
}
TemporaryTypeSet *
TypeSet::filter(LifoAlloc *alloc, bool filterUndefined, bool filterNull) const
{
TemporaryTypeSet *res = clone(alloc);
if (!res)
return nullptr;
if (filterUndefined)
res->flags = flags & ~TYPE_FLAG_UNDEFINED;
if (filterNull)
res->flags = flags & ~TYPE_FLAG_NULL;
return res;
}
/* static */ TemporaryTypeSet *
TypeSet::unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc)
{

View File

@ -579,6 +579,9 @@ class TypeSet
TemporaryTypeSet *clone(LifoAlloc *alloc) const;
bool clone(LifoAlloc *alloc, TemporaryTypeSet *result) const;
// Create a new TemporaryTypeSet where undefined and/or null has been filtered out.
TemporaryTypeSet *filter(LifoAlloc *alloc, bool filterUndefined, bool filterNull) const;
protected:
uint32_t baseObjectCount() const {
return (flags & TYPE_FLAG_OBJECT_COUNT_MASK) >> TYPE_FLAG_OBJECT_COUNT_SHIFT;