mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 953164 - IonMonkey: Improve type information at branches, r=jandem
This commit is contained in:
parent
06c5f9e5cc
commit
192f0c7a6a
@ -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();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -114,6 +114,7 @@ namespace jit {
|
||||
_(LoadSlot) \
|
||||
_(StoreSlot) \
|
||||
_(FunctionEnvironment) \
|
||||
_(FilterTypeSet) \
|
||||
_(TypeBarrier) \
|
||||
_(MonitorTypes) \
|
||||
_(PostWriteBarrier) \
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user