Bug 1059364 - Don't emit ObjectGroupDispatch fallback path if we know it's never used. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 05 Mar 2015 15:47:13 +0100
changeset 232054 57ddae8223f9dc29dd6f2611c477f7221d3930dd
parent 232053 6e51757fe12e33791a58a2330e22bc16869a013d
child 232055 3e386e0dbdadad0fb0e14453e4b3f6db55fcc621
push id28368
push userkwierso@gmail.com
push dateThu, 05 Mar 2015 23:18:02 +0000
treeherdermozilla-central@1fe0dd36816b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1059364
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1059364 - Don't emit ObjectGroupDispatch fallback path if we know it's never used. r=bhackett
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/LIR-Common.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -749,22 +749,20 @@ CodeGenerator::visitFunctionDispatch(LFu
 
 void
 CodeGenerator::visitObjectGroupDispatch(LObjectGroupDispatch *lir)
 {
     MObjectGroupDispatch *mir = lir->mir();
     Register input = ToRegister(lir->input());
     Register temp = ToRegister(lir->temp());
 
-    // Hold the incoming ObjectGroup.
-
+    // Load the incoming ObjectGroup in temp.
     masm.loadPtr(Address(input, JSObject::offsetOfGroup()), temp);
 
     // Compare ObjectGroups.
-
     MacroAssembler::BranchGCPtr lastBranch;
     LBlock *lastBlock = nullptr;
     InlinePropertyTable *propTable = mir->propTable();
     for (size_t i = 0; i < mir->numCases(); i++) {
         JSFunction *func = mir->getCase(i);
         LBlock *target = skipTrivialBlocks(mir->getCaseBlock(i))->lir();
 
         DebugOnly<bool> found = false;
@@ -779,17 +777,32 @@ CodeGenerator::visitObjectGroupDispatch(
             lastBranch = MacroAssembler::BranchGCPtr(Assembler::Equal, temp, ImmGCPtr(group),
                                                      target->label());
             lastBlock = target;
             found = true;
         }
         MOZ_ASSERT(found);
     }
 
-    // Unknown function: jump to fallback block.
+    // Jump to fallback block if we have an unknown ObjectGroup. If there's no
+    // fallback block, we should have handled all cases.
+
+    if (!mir->hasFallback()) {
+        MOZ_ASSERT(lastBranch.isInitialized());
+#ifdef DEBUG
+        Label ok;
+        lastBranch.relink(&ok);
+        lastBranch.emit(masm);
+        masm.assumeUnreachable("Unexpected ObjectGroup");
+        masm.bind(&ok);
+#endif
+        if (!isNextBlock(lastBlock))
+            masm.jump(lastBlock->label());
+        return;
+    }
 
     LBlock *fallback = skipTrivialBlocks(mir->getFallback())->lir();
     if (!lastBranch.isInitialized()) {
         if (!isNextBlock(fallback))
             masm.jump(fallback->label());
         return;
     }
 
@@ -2671,19 +2684,17 @@ void
 CodeGenerator::visitPostWriteBarrierO(LPostWriteBarrierO *lir)
 {
     OutOfLineCallPostWriteBarrier *ool = new(alloc()) OutOfLineCallPostWriteBarrier(lir, lir->object());
     addOutOfLineCode(ool, lir->mir());
 
     Register temp = ToTempRegisterOrInvalid(lir->temp());
 
     if (lir->object()->isConstant()) {
-#ifdef DEBUG
         MOZ_ASSERT(!IsInsideNursery(&lir->object()->toConstant()->toObject()));
-#endif
     } else {
         masm.branchPtrInNurseryRange(Assembler::Equal, ToRegister(lir->object()), temp,
                                      ool->rejoin());
     }
 
     masm.branchPtrInNurseryRange(Assembler::Equal, ToRegister(lir->value()), temp, ool->entry());
 
     masm.bind(ool->rejoin());
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5349,22 +5349,16 @@ IonBuilder::inlineCalls(CallInfo &callIn
     // Note: this is an upperbound. Unreachable targets and uninlineable natives are also counted.
     uint32_t count = 1; // Possible fallback block.
     for (uint32_t i = 0; i < targets.length(); i++) {
         if (choiceSet[i])
             count++;
     }
     retPhi->reserveLength(count);
 
-    // During inlining the 'this' value is assigned a type set which is
-    // specialized to the groups which can generate that inlining target.
-    // After inlining the original type set is restored.
-    TemporaryTypeSet *cacheObjectTypeSet =
-        maybeCache ? maybeCache->object()->resultTypeSet() : nullptr;
-
     // Inline each of the inlineable targets.
     for (uint32_t i = 0; i < targets.length(); i++) {
         // Target must be inlineable.
         if (!choiceSet[i])
             continue;
 
         // Even though we made one round of inline decisions already, we may
         // be amending them below.
@@ -5402,22 +5396,26 @@ IonBuilder::inlineCalls(CallInfo &callIn
         // Create a new CallInfo to track modified state within the inline block.
         CallInfo inlineInfo(alloc(), callInfo.constructing());
         if (!inlineInfo.init(callInfo))
             return false;
         inlineInfo.popFormals(inlineBlock);
         inlineInfo.setFun(funcDef);
 
         if (maybeCache) {
+            // Assign the 'this' value a TypeSet specialized to the groups that
+            // can generate this inlining target.
             MOZ_ASSERT(callInfo.thisArg() == maybeCache->object());
-            TemporaryTypeSet *targetThisTypes =
-                maybeCache->propTable()->buildTypeSetForFunction(target);
-            if (!targetThisTypes)
+            TemporaryTypeSet *thisTypes = maybeCache->propTable()->buildTypeSetForFunction(target);
+            if (!thisTypes)
                 return false;
-            maybeCache->object()->setResultTypeSet(targetThisTypes);
+
+            MFilterTypeSet *filter = MFilterTypeSet::New(alloc(), inlineInfo.thisArg(), thisTypes);
+            inlineBlock->add(filter);
+            inlineInfo.setThis(filter);
         }
 
         // Inline the call into the inlineBlock.
         if (!setCurrentAndSpecializePhis(inlineBlock))
             return false;
         InliningStatus status = inlineSingleCall(inlineInfo, target);
         if (status == InliningStatus_Error)
             return false;
@@ -5442,37 +5440,78 @@ IonBuilder::inlineCalls(CallInfo &callIn
         MDefinition *retVal = inlineReturnBlock->peek(-1);
         retPhi->addInput(retVal);
         inlineReturnBlock->end(MGoto::New(alloc(), returnBlock));
         if (!returnBlock->addPredecessorWithoutPhis(inlineReturnBlock))
             return false;
     }
 
     // Patch the InlinePropertyTable to not dispatch to vetoed paths.
+    bool useFallback;
     if (maybeCache) {
-        maybeCache->object()->setResultTypeSet(cacheObjectTypeSet);
-
         InlinePropertyTable *propTable = maybeCache->propTable();
         propTable->trimTo(targets, choiceSet);
 
-        // If all paths were vetoed, output only a generic fallback path.
         if (propTable->numEntries() == 0) {
+            // If all paths were vetoed, output only a generic fallback path.
             MOZ_ASSERT(dispatch->numCases() == 0);
             maybeCache = nullptr;
-        }
+            useFallback = true;
+        } else {
+            // We need a fallback path if the ObjectGroup dispatch does not
+            // handle all incoming objects.
+            useFallback = false;
+            TemporaryTypeSet *objectTypes = maybeCache->object()->resultTypeSet();
+            for (uint32_t i = 0; i < objectTypes->getObjectCount(); i++) {
+                TypeSet::ObjectKey *obj = objectTypes->getObject(i);
+                if (!obj)
+                    continue;
+
+                if (!obj->isGroup()) {
+                    useFallback = true;
+                    break;
+                }
+
+                if (!propTable->hasObjectGroup(obj->group())) {
+                    useFallback = true;
+                    break;
+                }
+            }
+
+            if (!useFallback) {
+                // The object group dispatch handles all possible incoming
+                // objects, so the cache and barrier will not be reached and
+                // can be eliminated.
+                if (callInfo.fun()->isGetPropertyCache()) {
+                    MOZ_ASSERT(callInfo.fun() == maybeCache);
+                } else {
+                    MTypeBarrier *barrier = callInfo.fun()->toTypeBarrier();
+                    MOZ_ASSERT(!barrier->hasUses());
+                    MOZ_ASSERT(barrier->type() == MIRType_Object);
+                    MOZ_ASSERT(barrier->input()->isGetPropertyCache());
+                    MOZ_ASSERT(barrier->input()->toGetPropertyCache() == maybeCache);
+                    barrier->block()->discard(barrier);
+                }
+
+                MOZ_ASSERT(!maybeCache->hasUses());
+                maybeCache->block()->discard(maybeCache);
+            }
+        }
+    } else {
+        useFallback = dispatch->numCases() < targets.length();
     }
 
     // If necessary, generate a fallback path.
-    // MObjectGroupDispatch always uses a fallback path.
-    if (maybeCache || dispatch->numCases() < targets.length()) {
+    if (useFallback) {
         // Generate fallback blocks, and set |current| to the fallback return block.
         if (maybeCache) {
             MBasicBlock *fallbackTarget;
-            if (!inlineObjectGroupFallback(callInfo, dispatchBlock, (MObjectGroupDispatch *)dispatch,
-                                          maybeCache, &fallbackTarget))
+            if (!inlineObjectGroupFallback(callInfo, dispatchBlock,
+                                           dispatch->toObjectGroupDispatch(),
+                                           maybeCache, &fallbackTarget))
             {
                 return false;
             }
             dispatch->addFallback(fallbackTarget);
         } else {
             JSFunction *remaining = nullptr;
 
             // If there is only 1 remaining case, we can annotate the fallback call
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -2149,39 +2149,43 @@ class LFunctionDispatch : public LInstru
 
   public:
     LIR_HEADER(FunctionDispatch);
 
     explicit LFunctionDispatch(const LAllocation &in) {
         setOperand(0, in);
     }
 
-    MFunctionDispatch *mir() {
+    MFunctionDispatch *mir() const {
         return mir_->toFunctionDispatch();
     }
 };
 
 class LObjectGroupDispatch : public LInstructionHelper<0, 1, 1>
 {
     // Dispatch is performed based on an ObjectGroup -> block
     // map inferred by the MIR.
 
   public:
     LIR_HEADER(ObjectGroupDispatch);
 
+    const char *extraName() const {
+        return mir()->hasFallback() ? "HasFallback" : "NoFallback";
+    }
+
     LObjectGroupDispatch(const LAllocation &in, const LDefinition &temp) {
         setOperand(0, in);
         setTemp(0, temp);
     }
 
     const LDefinition *temp() {
         return getTemp(0);
     }
 
-    MObjectGroupDispatch *mir() {
+    MObjectGroupDispatch *mir() const {
         return mir_->toObjectGroupDispatch();
     }
 };
 
 // Compares two integral values of the same JS type, either integer or object.
 // For objects, both operands are in registers.
 class LCompare : public LInstructionHelper<1, 2, 0>
 {
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4184,16 +4184,26 @@ InlinePropertyTable::hasFunction(JSFunct
 {
     for (size_t i = 0; i < numEntries(); i++) {
         if (entries_[i]->func == func)
             return true;
     }
     return false;
 }
 
+bool
+InlinePropertyTable::hasObjectGroup(ObjectGroup *group) const
+{
+    for (size_t i = 0; i < numEntries(); i++) {
+        if (entries_[i]->group == group)
+            return true;
+    }
+    return false;
+}
+
 TemporaryTypeSet *
 InlinePropertyTable::buildTypeSetForFunction(JSFunction *func) const
 {
     LifoAlloc *alloc = GetJitContext()->temp->lifoAlloc();
     TemporaryTypeSet *types = alloc->new_<TemporaryTypeSet>();
     if (!types)
         return nullptr;
     for (size_t i = 0; i < numEntries(); i++) {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -9521,16 +9521,18 @@ class InlinePropertyTable : public TempO
     }
 
     JSFunction *getFunction(size_t i) const {
         MOZ_ASSERT(i < numEntries());
         return entries_[i]->func;
     }
 
     bool hasFunction(JSFunction *func) const;
+    bool hasObjectGroup(ObjectGroup *group) const;
+
     TemporaryTypeSet *buildTypeSetForFunction(JSFunction *func) const;
 
     // Remove targets that vetoed inlining from the InlinePropertyTable.
     void trimTo(const ObjectVector &targets, const BoolVector &choiceSet);
 
     // Ensure that the InlinePropertyTable's domain is a subset of |targets|.
     void trimToTargets(const ObjectVector &targets);
 };