Exemple #1
0
/**
 * Add to sets coveredNodes and coveredArcs the cfg arcs that are now
 * covered given the new region containing the translations in
 * selectedVec.
 */
static void markCovered(const TransCFG& cfg, const RegionDescPtr region,
                        const TransIDVec& selectedVec, const TransIDSet heads,
                        TransIDSet& coveredNodes,
                        TransCFG::ArcPtrSet& coveredArcs) {
  assert(selectedVec.size() > 0);
  TransID newHead = selectedVec[0];
  assert(!region->empty());
  assert(newHead == getTransId(region->entry()->id()));

  // Mark all region's nodes as covered.
  coveredNodes.insert(selectedVec.begin(), selectedVec.end());

  // Mark all incoming arcs into newHead from covered nodes as covered.
  for (auto arc : cfg.inArcs(newHead)) {
    TransID src = arc->src();
    if (coveredNodes.count(src)) {
      coveredArcs.insert(arc);
    }
  }

  // Mark all CFG arcs within the region as covered.
  region->forEachArc([&](RegionDesc::BlockId src, RegionDesc::BlockId dst) {
    if (!hasTransId(src) || !hasTransId(dst)) return;
    TransID srcTid = getTransId(src);
    TransID dstTid = getTransId(dst);
    assert(cfg.hasArc(srcTid, dstTid));
    bool foundArc = false;
    for (auto arc : cfg.outArcs(srcTid)) {
      if (arc->dst() == dstTid) {
        coveredArcs.insert(arc);
        foundArc = true;
      }
    }
    always_assert(foundArc);
  });

  // Mark all outgoing arcs from the region to a head node as covered.
  for (auto node : selectedVec) {
    for (auto arc : cfg.outArcs(node)) {
      if (heads.count(arc->dst())) {
        coveredArcs.insert(arc);
      }
    }
  }
}
Exemple #2
0
RegionDescPtr selectHotTrace(TransID triggerId,
                             const ProfData* profData,
                             TransCFG& cfg,
                             TransIDSet& selectedSet,
                             TransIDVec* selectedVec) {
  auto region = std::make_shared<RegionDesc>();
  TransID tid    = triggerId;
  TransID prevId = kInvalidTransID;
  selectedSet.clear();
  if (selectedVec) selectedVec->clear();

  PostConditions accumPostConds;
  // Maps BlockIds to the set of BC offsets for its successor blocks.
  // Used to prevent multiple successors with the same SrcKey for now.
  // This can go away once task #4157613 is done.
  hphp_hash_map<RegionDesc::BlockId, SrcKeySet> succSKSet;

  // Maps from BlockIds to accumulated post conditions for that block.
  // Used to determine if we can add branch-over edges by checking the
  // pre-conditions of the successor block.
  hphp_hash_map<RegionDesc::BlockId, PostConditions> blockPostConds;

  while (!selectedSet.count(tid)) {

    RegionDescPtr blockRegion = profData->transRegion(tid);
    if (blockRegion == nullptr) break;

    // If the debugger is attached, only allow single-block regions.
    if (prevId != kInvalidTransID && isDebuggerAttachedProcess()) {
      FTRACE(2, "selectHotTrace: breaking region at Translation {} "
             "because of debugger is attached\n", tid);
      break;
    }

    // Break if block is not the first and requires reffiness checks.
    // Task #2589970: fix translateRegion to support mid-region reffiness checks
    if (prevId != kInvalidTransID) {
      auto nRefDeps = blockRegion->blocks[0]->reffinessPreds().size();
      if (nRefDeps > 0) {
        FTRACE(2, "selectHotTrace: breaking region because of refDeps ({}) at "
               "Translation {}\n", nRefDeps, tid);
        break;
      }
    }

    // Break if block is not the first and it corresponds to the main
    // function body entry.  This is to prevent creating multiple
    // large regions containing the function body (starting at various
    // DV funclets).
    if (prevId != kInvalidTransID) {
      const Func* func = profData->transFunc(tid);
      Offset  bcOffset = profData->transStartBcOff(tid);
      if (func->base() == bcOffset) {
        FTRACE(2, "selectHotTrace: breaking region because reached the main "
               "function body entry at Translation {} (BC offset {})\n",
               tid, bcOffset);
        break;
      }
    }

    if (prevId != kInvalidTransID) {
      auto sk = profData->transSrcKey(tid);
      if (profData->optimized(sk)) {
        FTRACE(2, "selectHotTrace: breaking region because next sk already "
               "optimized, for Translation {}\n", tid);
        break;
      }
    }

    // Break trace if translation tid cannot follow the execution of
    // the entire translation prevId.  This can only happen if the
    // execution of prevId takes a side exit that leads to the
    // execution of tid.
    if (prevId != kInvalidTransID) {
      Op* lastInstr = profData->transLastInstr(prevId);
      const Unit* unit = profData->transFunc(prevId)->unit();
      OffsetSet succOffs = findSuccOffsets(lastInstr, unit);
      if (!succOffs.count(profData->transSrcKey(tid).offset())) {
        if (HPHP::Trace::moduleEnabled(HPHP::Trace::pgo, 2)) {
          FTRACE(2, "selectHotTrace: WARNING: Breaking region @: {}\n",
                 show(*region));
          FTRACE(2, "selectHotTrace: next translation selected: tid = {}\n{}\n",
                 tid, show(*blockRegion));
          FTRACE(2, "\nsuccOffs = {}\n", folly::join(", ", succOffs));
        }
        break;
      }
    }
    if (region->blocks.size() > 0) {
      auto& newBlock   = blockRegion->blocks.front();
      auto newBlockId  = newBlock->id();
      auto predBlockId = region->blocks.back().get()->id();
      if (!RuntimeOption::EvalHHIRBytecodeControlFlow) {
        region->addArc(predBlockId, newBlockId);
      } else {
      // With bytecode control-flow, we add all forward arcs in the TransCFG
      // that are induced by the blocks in the region, as a simple way
      // to expose control-flow for now.
      // This can go away once Task #4075822 is done.
        auto newBlockSrcKey = blockRegion->blocks.front().get()->start();
        if (succSKSet[predBlockId].count(newBlockSrcKey)) break;
        region->addArc(predBlockId, newBlockId);
        succSKSet[predBlockId].insert(newBlockSrcKey);
        assert(hasTransId(newBlockId));
        auto newTransId = getTransId(newBlockId);
        for (auto iOther = 0; iOther < region->blocks.size(); iOther++) {
          auto other = region->blocks[iOther];
          auto otherBlockId = other.get()->id();
          if (!hasTransId(otherBlockId)) continue;
          auto otherTransId = getTransId(otherBlockId);
          auto otherBlockSrcKey = other.get()->start();
          if (cfg.hasArc(otherTransId, newTransId) &&
              !other.get()->inlinedCallee() &&
              // Task #4157613 will allow the following check to go away
              !succSKSet[otherBlockId].count(newBlockSrcKey) &&
              preCondsAreSatisfied(newBlock, blockPostConds[otherBlockId])) {
            region->addArc(otherBlockId, newBlockId);
            succSKSet[otherBlockId].insert(newBlockSrcKey);
          }
          // When Eval.JitLoops is set, insert back-edges in the
          // region if they exist in the TransCFG.
          if (RuntimeOption::EvalJitLoops &&
              cfg.hasArc(newTransId, otherTransId) &&
              // Task #4157613 will allow the following check to go away
              !succSKSet[newBlockId].count(otherBlockSrcKey)) {
            region->addArc(newBlockId, otherBlockId);
            succSKSet[newBlockId].insert(otherBlockSrcKey);
          }
        }
      }
    }
    region->blocks.insert(region->blocks.end(), blockRegion->blocks.begin(),
                          blockRegion->blocks.end());
    region->arcs.insert(region->arcs.end(), blockRegion->arcs.begin(),
                        blockRegion->arcs.end());
    if (cfg.outArcs(tid).size() > 1) {
      region->setSideExitingBlock(blockRegion->blocks.front()->id());
    }
    selectedSet.insert(tid);
    if (selectedVec) selectedVec->push_back(tid);

    Op lastOp = *(profData->transLastInstr(tid));
    if (breaksRegion(lastOp)) {
      FTRACE(2, "selectHotTrace: breaking region because of last instruction "
             "in Translation {}: {}\n", tid, opcodeToName(lastOp));
      break;
    }

    auto outArcs = cfg.outArcs(tid);
    if (outArcs.size() == 0) {
      FTRACE(2, "selectHotTrace: breaking region because there's no successor "
             "for Translation {}\n", tid);
      break;
    }

    auto lastNewBlock = blockRegion->blocks.back();
    discardPoppedTypes(accumPostConds,
                       blockRegion->blocks[0]->initialSpOffset());
    mergePostConds(accumPostConds, lastNewBlock->postConds());
    blockPostConds[lastNewBlock->id()] = accumPostConds;

    TransCFG::ArcPtrVec possibleOutArcs;
    for (auto arc : outArcs) {
      RegionDesc::BlockPtr possibleNext =
        profData->transRegion(arc->dst())->blocks[0];
      if (preCondsAreSatisfied(possibleNext, accumPostConds)) {
        possibleOutArcs.emplace_back(arc);
      }
    }

    if (possibleOutArcs.size() == 0) {
      FTRACE(2, "selectHotTrace: breaking region because postcondition check "
             "pruned all successors of Translation {}\n", tid);
      break;
    }

    auto maxWeight = std::numeric_limits<int64_t>::min();
    TransCFG::Arc* maxArc = nullptr;
    for (auto arc : possibleOutArcs) {
      if (arc->weight() >= maxWeight) {
        maxWeight = arc->weight();
        maxArc = arc;
      }
    }
    assert(maxArc != nullptr);
    prevId = tid;
    tid = maxArc->dst();
  }

  return region;
}
Exemple #3
0
RegionDescPtr selectHotTrace(TransID triggerId,
                             const ProfData* profData,
                             TransCFG& cfg,
                             TransIDSet& selectedSet,
                             TransIDVec* selectedVec) {
  auto region = std::make_shared<RegionDesc>();
  TransID tid    = triggerId;
  TransID prevId = kInvalidTransID;
  selectedSet.clear();
  if (selectedVec) selectedVec->clear();

  PostConditions accumPostConds;
  // Maps BlockIds to the set of BC offsets for its successor blocks.
  // Used to prevent multiple successors with the same SrcKey for now.
  // This can go away once task #4157613 is done.
  hphp_hash_map<RegionDesc::BlockId, SrcKeySet> succSKSet;

  // Maps from BlockIds to accumulated post conditions for that block.
  // Used to determine if we can add branch-over edges by checking the
  // pre-conditions of the successor block.
  hphp_hash_map<RegionDesc::BlockId, PostConditions> blockPostConds;

  while (!selectedSet.count(tid)) {

    RegionDescPtr blockRegion = profData->transRegion(tid);
    if (blockRegion == nullptr) break;

    // If the debugger is attached, only allow single-block regions.
    if (prevId != kInvalidTransID && isDebuggerAttachedProcess()) {
      FTRACE(2, "selectHotTrace: breaking region at Translation {} "
             "because of debugger is attached\n", tid);
      break;
    }

    // Break if block is not the first and it corresponds to the main
    // function body entry.  This is to prevent creating multiple
    // large regions containing the function body (starting at various
    // DV funclets).
    if (prevId != kInvalidTransID) {
      const Func* func = profData->transFunc(tid);
      Offset  bcOffset = profData->transStartBcOff(tid);
      if (func->base() == bcOffset) {
        FTRACE(2, "selectHotTrace: breaking region because reached the main "
               "function body entry at Translation {} (BC offset {})\n",
               tid, bcOffset);
        break;
      }
    }

    if (prevId != kInvalidTransID) {
      auto sk = profData->transSrcKey(tid);
      if (profData->optimized(sk)) {
        FTRACE(2, "selectHotTrace: breaking region because next sk already "
               "optimized, for Translation {}\n", tid);
        break;
      }
    }

    bool hasPredBlock = !region->empty();
    RegionDesc::BlockId predBlockId = (hasPredBlock ?
                                       region->blocks().back().get()->id() : 0);
    auto const& newFirstBlock = blockRegion->entry();
    auto newFirstBlockId = newFirstBlock->id();
    auto newFirstBlockSk = newFirstBlock->start();
    auto newLastBlockId  = blockRegion->blocks().back()->id();

    // Make sure we don't end up with multiple successors for the same
    // SrcKey. Task #4157613 will allow the following check to go away.
    // This needs to be done before we insert blockRegion into region,
    // to avoid creating unreachable blocks.
    if (RuntimeOption::EvalHHIRBytecodeControlFlow && hasPredBlock &&
        succSKSet[predBlockId].count(newFirstBlockSk)) {
      break;
    }

    // Add blockRegion's blocks and arcs to region.
    region->append(*blockRegion);

    if (hasPredBlock) {
      if (RuntimeOption::EvalHHIRBytecodeControlFlow) {
        // This is checked above.
        assert(succSKSet[predBlockId].count(newFirstBlockSk) == 0);
        succSKSet[predBlockId].insert(newFirstBlockSk);
      }
      region->addArc(predBlockId, newFirstBlockId);
    }

    // With bytecode control-flow, we add all forward arcs in the TransCFG
    // that are induced by the blocks in the region, as a simple way
    // to expose control-flow for now.
    // This can go away once Task #4075822 is done.
    if (RuntimeOption::EvalHHIRBytecodeControlFlow) {
      assert(hasTransId(newFirstBlockId));
      auto newTransId = getTransId(newFirstBlockId);
      auto& blocks = region->blocks();
      for (auto iOther = 0; iOther < blocks.size(); iOther++) {
        auto other = blocks[iOther];
        auto otherFirstBlockId = other.get()->id();
        if (!hasTransId(otherFirstBlockId)) continue;
        auto otherTransId = getTransId(otherFirstBlockId);
        auto otherFirstBlockSk = other.get()->start();
        auto otherRegion = profData->transRegion(otherTransId);
        auto otherLastBlockId = otherRegion->blocks().back()->id();
        // When loops are off, stop once we hit the newTransId we just inserted.
        if (!RuntimeOption::EvalJitLoops && otherTransId == newTransId) break;
        if (cfg.hasArc(otherTransId, newTransId) &&
            // Task #4157613 will allow the following check to go away
            !succSKSet[otherLastBlockId].count(newFirstBlockSk) &&
            preCondsAreSatisfied(newFirstBlock,
                                 blockPostConds[otherLastBlockId])) {
          region->addArc(otherLastBlockId, newFirstBlockId);
          succSKSet[otherLastBlockId].insert(newFirstBlockSk);
        }
        // When Eval.JitLoops is set, insert back-edges in the
        // region if they exist in the TransCFG.
        if (RuntimeOption::EvalJitLoops &&
            cfg.hasArc(newTransId, otherTransId) &&
            // Task #4157613 will allow the following check to go away
            !succSKSet[newLastBlockId].count(otherFirstBlockSk)) {
          region->addArc(newLastBlockId, otherFirstBlockId);
          succSKSet[newLastBlockId].insert(otherFirstBlockSk);
        }
      }
    }

    if (cfg.outArcs(tid).size() > 1) {
      region->setSideExitingBlock(blockRegion->entry()->id());
    }
    selectedSet.insert(tid);
    if (selectedVec) selectedVec->push_back(tid);

    Op lastOp = *(profData->transLastInstr(tid));
    if (breaksRegion(lastOp)) {
      FTRACE(2, "selectHotTrace: breaking region because of last instruction "
             "in Translation {}: {}\n", tid, opcodeToName(lastOp));
      break;
    }

    auto outArcs = cfg.outArcs(tid);
    if (outArcs.size() == 0) {
      FTRACE(2, "selectHotTrace: breaking region because there's no successor "
             "for Translation {}\n", tid);
      break;
    }

    auto newLastBlock = blockRegion->blocks().back();
    discardPoppedTypes(accumPostConds,
                       blockRegion->entry()->initialSpOffset());
    mergePostConds(accumPostConds, newLastBlock->postConds());
    blockPostConds[newLastBlock->id()] = accumPostConds;

    TransCFG::ArcPtrVec possibleOutArcs;
    for (auto arc : outArcs) {
      RegionDesc::BlockPtr possibleNext =
        profData->transRegion(arc->dst())->entry();
      if (preCondsAreSatisfied(possibleNext, accumPostConds)) {
        possibleOutArcs.emplace_back(arc);
      }
    }

    if (possibleOutArcs.size() == 0) {
      FTRACE(2, "selectHotTrace: breaking region because postcondition check "
             "pruned all successors of Translation {}\n", tid);
      break;
    }

    auto maxWeight = std::numeric_limits<int64_t>::min();
    TransCFG::Arc* maxArc = nullptr;
    for (auto arc : possibleOutArcs) {
      if (arc->weight() >= maxWeight) {
        maxWeight = arc->weight();
        maxArc = arc;
      }
    }
    assert(maxArc != nullptr);
    prevId = tid;
    tid = maxArc->dst();
  }

  return region;
}
Exemple #4
0
RegionDescPtr selectHotTrace(TransID triggerId,
                             const ProfData* profData,
                             TransCFG& cfg,
                             TransIDSet& selectedSet,
                             TransIDVec* selectedVec) {
  auto region = std::make_shared<RegionDesc>();
  TransID tid    = triggerId;
  TransID prevId = kInvalidTransID;
  selectedSet.clear();
  if (selectedVec) selectedVec->clear();

  PostConditions accumPostConds;

  // Maps from BlockIds to accumulated post conditions for that block.
  // Used to determine if we can add branch-over edges by checking the
  // pre-conditions of the successor block.
  hphp_hash_map<RegionDesc::BlockId, PostConditions> blockPostConds;

  uint32_t numBCInstrs = 0;

  while (!selectedSet.count(tid)) {

    RegionDescPtr blockRegion = profData->transRegion(tid);
    if (blockRegion == nullptr) break;

    // Break if region would be larger than the specified limit.
    auto newInstrSize = numBCInstrs + blockRegion->instrSize();
    if (newInstrSize > RuntimeOption::EvalJitMaxRegionInstrs) {
      FTRACE(2, "selectHotTrace: breaking region at Translation {} because "
             "size ({}) would exceed of maximum translation limit\n",
             tid, newInstrSize);
      break;
    }

    // If the debugger is attached, only allow single-block regions.
    if (prevId != kInvalidTransID && isDebuggerAttachedProcess()) {
      FTRACE(2, "selectHotTrace: breaking region at Translation {} "
             "because of debugger is attached\n", tid);
      break;
    }

    // Break if block is not the first and it corresponds to the main
    // function body entry.  This is to prevent creating multiple
    // large regions containing the function body (starting at various
    // DV funclets).
    if (prevId != kInvalidTransID) {
      const Func* func = profData->transFunc(tid);
      Offset  bcOffset = profData->transStartBcOff(tid);
      if (func->base() == bcOffset) {
        FTRACE(2, "selectHotTrace: breaking region because reached the main "
               "function body entry at Translation {} (BC offset {})\n",
               tid, bcOffset);
        break;
      }
    }

    if (prevId != kInvalidTransID) {
      auto sk = profData->transSrcKey(tid);
      if (profData->optimized(sk)) {
        FTRACE(2, "selectHotTrace: breaking region because next sk already "
               "optimized, for Translation {}\n", tid);
        break;
      }
    }

    bool hasPredBlock = !region->empty();
    RegionDesc::BlockId predBlockId = (hasPredBlock ?
                                       region->blocks().back().get()->id() : 0);
    auto const& newFirstBlock = blockRegion->entry();
    auto newFirstBlockId = newFirstBlock->id();
    auto newLastBlockId  = blockRegion->blocks().back()->id();

    // Add blockRegion's blocks and arcs to region.
    region->append(*blockRegion);
    numBCInstrs += blockRegion->instrSize();

    if (hasPredBlock) {
      region->addArc(predBlockId, newFirstBlockId);
    }

    // When Eval.JitLoops is set, insert back-edges in the region if
    // they exist in the TransCFG.
    if (RuntimeOption::EvalJitLoops) {
      assertx(hasTransId(newFirstBlockId));
      auto newTransId = getTransId(newFirstBlockId);
      // Don't add the arc if the last opcode in the source block ends
      // the region.
      if (!breaksRegion(*profData->transLastInstr(newTransId))) {
        auto& blocks = region->blocks();
        for (auto iOther = 0; iOther < blocks.size(); iOther++) {
          auto other = blocks[iOther];
          auto otherFirstBlockId = other.get()->id();
          if (!hasTransId(otherFirstBlockId)) continue;
          auto otherTransId = getTransId(otherFirstBlockId);
          if (cfg.hasArc(newTransId, otherTransId)) {
            region->addArc(newLastBlockId, otherFirstBlockId);
          }
        }
      }
    }

    if (cfg.outArcs(tid).size() > 1) {
      region->setSideExitingBlock(blockRegion->entry()->id());
    }
    selectedSet.insert(tid);
    if (selectedVec) selectedVec->push_back(tid);

    Op lastOp = *(profData->transLastInstr(tid));
    if (breaksRegion(lastOp)) {
      FTRACE(2, "selectHotTrace: breaking region because of last instruction "
             "in Translation {}: {}\n", tid, opcodeToName(lastOp));
      break;
    }

    auto outArcs = cfg.outArcs(tid);
    if (outArcs.size() == 0) {
      FTRACE(2, "selectHotTrace: breaking region because there's no successor "
             "for Translation {}\n", tid);
      break;
    }

    auto newLastBlock = blockRegion->blocks().back();
    discardPoppedTypes(accumPostConds,
                       blockRegion->entry()->initialSpOffset());
    mergePostConds(accumPostConds, newLastBlock->postConds());
    blockPostConds[newLastBlock->id()] = accumPostConds;

    TransCFG::ArcPtrVec possibleOutArcs;
    for (auto arc : outArcs) {
      RegionDesc::BlockPtr possibleNext =
        profData->transRegion(arc->dst())->entry();
      if (preCondsAreSatisfied(possibleNext, accumPostConds)) {
        possibleOutArcs.emplace_back(arc);
      }
    }

    if (possibleOutArcs.size() == 0) {
      FTRACE(2, "selectHotTrace: breaking region because postcondition check "
             "pruned all successors of Translation {}\n", tid);
      break;
    }

    auto maxWeight = std::numeric_limits<int64_t>::min();
    TransCFG::Arc* maxArc = nullptr;
    for (auto arc : possibleOutArcs) {
      if (arc->weight() >= maxWeight) {
        maxWeight = arc->weight();
        maxArc = arc;
      }
    }
    assertx(maxArc != nullptr);
    prevId = tid;
    tid = maxArc->dst();
  }

  FTRACE(3, "selectHotTrace: before chainRetransBlocks:\n{}\n", show(*region));
  region->chainRetransBlocks();
  FTRACE(3, "selectHotTrace: after chainRetransBlocks:\n{}\n", show(*region));

  return region;
}
/**
 * Chain the retranslation blocks.  This method enforces that, for
 * each region block, all its successor have distinct SrcKeys.
 */
void RegionDesc::chainRetransBlocks() {

  jit::vector<Chain> chains;
  BlockToChainMap block2chain;

  // 1. Initially assign each region block to its own chain.
  for (auto b : blocks()) {
    auto bid = b->id();
    auto cid = chains.size();
    chains.push_back({cid, {bid}});
    block2chain[bid] = cid;
  }

  // 2. For each block, if it has 2 successors with the same SrcKey,
  //    then merge the successors' chains into one.
  for (auto b : blocks()) {
    auto bid = b->id();
    const auto& succSet = succs(bid);
    for (auto it1 = succSet.begin(); it1 != succSet.end(); it1++) {
      auto bid1 = *it1;
      auto cid1 = block2chain[bid1];
      for (auto it2 = it1 + 1; it2 != succSet.end(); it2++) {
        auto bid2 = *it2;
        auto cid2 = block2chain[bid2];
        if (data(bid1).block->start() == data(bid2).block->start()) {
          mergeChains(chains[cid1], chains[cid2], block2chain);
        }
      }
    }
  }

  // 3. Sort each chain.  In general, we want to sort each chain in
  //    decreasing order of profile weights.  However, note that this
  //    transformation can turn acyclic graphs into cyclic ones (see
  //    example below).  Therefore, if JitLoops are disabled, we
  //    instead sort each chain following the original block order,
  //    which prevents loops from being generated if the region was
  //    originally acyclic.
  //
  //    Here's an example showing how an acyclic CFG can become cyclic
  //    by chaining its retranslation blocks:
  //
  //      - Region before chaining retranslation blocks, where B2' and B2"
  //        are retranslations starting at the same SrcKey:
  //          B1  -> B2'
  //          B1  -> B2"
  //          B2' -> B3
  //          B3  -> B2"
  //
  //      - Region after sorting the chain as B2" -R-> B2':
  //          B1  ->   B2"
  //          B2" -R-> B2'
  //          B2' ->   B3
  //          B3  ->   B2"
  //        Note the cycle: B2" -R-> B2' -> B3 -> B2".
  //
  auto profData = mcg->tx().profData();

  auto weight = [&](RegionDesc::BlockId bid) {
    return hasTransId(bid) ? profData->absTransCounter(getTransId(bid)) : 0;
  };

  auto sortGeneral = [&](RegionDesc::BlockId bid1, RegionDesc::BlockId bid2) {
    return weight(bid1) > weight(bid2);
  };

  using SortFun = std::function<bool(RegionDesc::BlockId, RegionDesc::BlockId)>;
  SortFun sortFunc = sortGeneral;

  hphp_hash_map<RegionDesc::BlockId, uint32_t> origBlockOrder;
  if (!RuntimeOption::EvalJitLoops) {
    for (uint32_t i = 0; i < m_blocks.size(); i++) {
      origBlockOrder[m_blocks[i]->id()] = i;
    }
    auto sortAcyclic = [&](RegionDesc::BlockId bid1, RegionDesc::BlockId bid2) {
      return origBlockOrder[bid1] < origBlockOrder[bid2];
    };
    sortFunc = sortAcyclic;
  }

  TRACE(1, "chainRetransBlocks: computed chains:\n");
  for (auto& c : chains) {
    std::sort(c.blocks.begin(), c.blocks.end(), sortFunc);

    if (Trace::moduleEnabled(Trace::region, 1) && c.blocks.size() > 0) {
      FTRACE(1, "  -> {} (w={})", c.blocks[0], weight(c.blocks[0]));
      for (size_t i = 1; i < c.blocks.size(); i++) {
        FTRACE(1, ", {} (w={})", c.blocks[i], weight(c.blocks[i]));
      }
      FTRACE(1, "\n");
    }
  }

  // 4. Set the nextRetrans blocks according to the computed chains.
  for (auto& c : chains) {
    if (c.blocks.size() == 0) continue;
    for (size_t i = 0; i < c.blocks.size() - 1; i++) {
      setNextRetrans(c.blocks[i], c.blocks[i + 1]);
    }
  }

  // 5. For each block with multiple successors in the same chain,
  //    only keep the successor that first appears in the chain.
  for (auto b : blocks()) {
    auto& succSet = data(b->id()).succs;
    for (auto s : succSet) {
      auto& c = chains[block2chain[s]];
      auto selectedSucc = findFirstInSet(c, succSet);
      for (auto other : c.blocks) {
        if (other == selectedSucc) continue;
        succSet.erase(other);
      }
    }
  }

  // 6. Reorder the blocks in the region in topological order (if
  //    region is acyclic), since the previous steps may break it.
  sortBlocks();
}