/** * 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); } } } }
/** * 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 TransIDVec selectedVec, const TransIDSet heads, TransIDSet& coveredNodes, TransCFG::ArcPtrSet& coveredArcs) { assert(selectedVec.size() > 0); TransID newHead = selectedVec[0]; // 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 arcs between consecutive region nodes as covered. for (size_t i = 0; i < selectedVec.size() - 1; i++) { TransID node = selectedVec[i]; TransID next = selectedVec[i + 1]; bool foundArc = false; for (auto arc : cfg.outArcs(node)) { if (arc->dst() == next) { 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); } } } }
/** * Returns the sum of the weights of the arcs going from srcs to dst. */ static int64_t interRegionWeight(const TransIDVec& srcs, TransID dst, const TransCFG& cfg) { int64_t totalWeight = 0; for (auto src : srcs) { for (auto arc : cfg.outArcs(src)) { if (arc->dst() == dst) { totalWeight += arc->weight(); } } } return totalWeight; }
/** * Sorts the regions vector in a linear order to be used for * translation. The goal is to obtain an order that improves locality * when the function is executed. Each region is translated separately. */ static void sortRegions(RegionVec& regions, const Func* func, const TransCFG& cfg, const ProfData* profData, const TransIDToRegionMap& headToRegion, const RegionToTransIDsMap& regionToTransIds) { RegionVec sorted; RegionSet selected; if (regions.empty()) return; // First, pick the region starting at the lowest bytecode offset. // This will normally correspond to the main function entry (for // normal, regular bytecode), but it may not be for irregular // functions written in hhas (like array_map and array_filter). If // there multiple regions starting at the lowest bytecode offset, // pick the one with the largest profile weight. RegionDescPtr entryRegion = nullptr; int64_t maxEntryWeight = -1; Offset lowestOffset = kInvalidOffset; for (const auto& pair : regionToTransIds) { auto r = pair.first; auto& tids = pair.second; TransID firstTid = tids[0]; Offset firstOffset = profData->transSrcKey(firstTid).offset(); int64_t weight = cfg.weight(firstTid); if (lowestOffset == kInvalidOffset || firstOffset < lowestOffset || (firstOffset == lowestOffset && weight > maxEntryWeight)) { entryRegion = r; maxEntryWeight = weight; lowestOffset = firstOffset; } } assert(entryRegion); sorted.push_back(entryRegion); selected.insert(entryRegion); RegionDescPtr region = entryRegion; // Select the remaining regions, iteratively picking the most likely // region to execute next. for (auto i = 1; i < regions.size(); i++) { int64_t maxWeight = -1; int64_t maxHeadWeight = -1; RegionDescPtr bestNext = nullptr; auto regionTransIds = getRegionTransIDVec(regionToTransIds, region); for (auto next : regions) { if (selected.count(next)) continue; auto nextTransIds = getRegionTransIDVec(regionToTransIds, next); int64_t weight = interRegionWeight(regionTransIds, nextTransIds[0], cfg); int64_t headWeight = cfg.weight(nextTransIds[0]); if ((weight > maxWeight) || (weight == maxWeight && headWeight > maxHeadWeight)) { maxWeight = weight; maxHeadWeight = headWeight; bestNext = next; } } assert(bestNext); sorted.push_back(bestNext); selected.insert(bestNext); region = bestNext; } assert(sorted.size() == regions.size()); regions = sorted; if (debug && Trace::moduleEnabled(HPHP::Trace::pgo, 5)) { for (size_t i = 0; i < regions.size(); i++) { auto r = regions[i]; auto tids = getRegionTransIDVec(regionToTransIds, r); std::string transIds = folly::join(", ", tids); FTRACE(6, "sortRegions: region[{}]: {}\n", i, transIds); } } }
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; }
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; }
/** * Sorts the regions vector in a linear order to be used for * translation. The goal is to obtain an order that improves locality * when the function is executed. */ static void sortRegion(RegionVec& regions, const Func* func, const TransCFG& cfg, const ProfData* profData, const TransIDToRegionMap& headToRegion, const RegionToTransIDsMap& regionToTransIds) { RegionVec sorted; RegionSet selected; // First, pick the region for the function body entry. There may be // multiple translations of the function body, so pick the one with // largest profile weight. RegionDescPtr entryRegion = nullptr; int64_t maxEntryWeight = -1; for (const auto& pair : regionToTransIds) { auto r = pair.first; auto& tids = pair.second; for (auto tid : tids) { if (profData->transSrcKey(tid).offset() == func->base()) { int64_t weight = cfg.weight(tid); if (weight > maxEntryWeight) { entryRegion = r; maxEntryWeight = weight; } } } } assert(entryRegion); sorted.push_back(entryRegion); selected.insert(entryRegion); RegionDescPtr region = entryRegion; // Select the remaining regions, iteratively picking the most likely // region to execute next. for (auto i = 1; i < regions.size(); i++) { int64_t maxWeight = -1; int64_t maxHeadWeight = -1; RegionDescPtr bestNext = nullptr; auto regionTransIds = getRegionTransIDVec(regionToTransIds, region); for (auto next : regions) { if (setContains(selected, next)) continue; auto nextTransIds = getRegionTransIDVec(regionToTransIds, next); int64_t weight = interRegionWeight(regionTransIds, nextTransIds[0], cfg); int64_t headWeight = cfg.weight(nextTransIds[0]); if ((weight > maxWeight) || (weight == maxWeight && headWeight > maxHeadWeight)) { maxWeight = weight; maxHeadWeight = headWeight; bestNext = next; } } assert(bestNext); sorted.push_back(bestNext); selected.insert(bestNext); region = bestNext; } assert(sorted.size() == regions.size()); regions = sorted; if (debug && Trace::moduleEnabled(HPHP::Trace::pgo, 5)) { for (size_t i = 0; i < regions.size(); i++) { auto r = regions[i]; auto tids = getRegionTransIDVec(regionToTransIds, r); std::string transIds = folly::join(", ", tids); FTRACE(6, "sortRegion: region[{}]: {}\n", i, transIds); } } }
RegionDescPtr selectHotTrace(TransID triggerId, const ProfData* profData, TransCFG& cfg, TransIDSet& selectedSet, TransIDVec* selectedVec) { auto region = std::make_shared<RegionDesc>(); TransID tid = triggerId; TransID prevId = InvalidID; selectedSet.clear(); if (selectedVec) selectedVec->clear(); PostConditions accumPostConds; 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 != InvalidID && 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 != InvalidID) { 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 != InvalidID) { 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 != InvalidID) { 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 != InvalidID) { 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", JIT::show(*region)); FTRACE(2, "selectHotTrace: next translation selected: tid = {}\n{}\n", tid, JIT::show(*blockRegion)); FTRACE(2, "\nsuccOffs = {}\n", folly::join(", ", succOffs)); } break; } } region->blocks.insert(region->blocks.end(), blockRegion->blocks.begin(), blockRegion->blocks.end()); 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()); 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; }
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; }
RegionDescPtr selectHotTrace(TransID triggerId, const ProfData* profData, TransCFG& cfg, TransIDSet& selectedSet) { JIT::RegionDescPtr region = smart::make_unique<JIT::RegionDesc>(); TransID tid = triggerId; TransID prevId = InvalidID; selectedSet.clear(); while (!setContains(selectedSet, tid)) { RegionDesc::BlockPtr block = profData->transBlock(tid); if (block == nullptr) break; // If the debugger is attached, only allow single-block regions. if (prevId != InvalidID && isDebuggerAttachedProcess()) { FTRACE(5, "selectHotRegion: 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 != InvalidID) { auto nRefDeps = block->reffinessPreds().size(); if (nRefDeps > 0) { FTRACE(5, "selectHotRegion: breaking region because of refDeps ({}) at " "Translation {}\n", nRefDeps, tid); break; } } // Break trace if translation tid cannot follow the execution of // the entire translation prevTd. This can only happen if the // execution of prevId takes a side exit that leads to the // execution of tid. if (prevId != InvalidID) { Op* lastInstr = profData->transLastInstr(prevId); const Unit* unit = profData->transFunc(prevId)->unit(); OffsetSet succOffs = findSuccOffsets(lastInstr, unit); if (!setContains(succOffs, profData->transSrcKey(tid).offset())) { if (HPHP::Trace::moduleEnabled(HPHP::Trace::pgo, 5)) { FTRACE(5, "selectHotTrace: WARNING: Breaking region @: {}\n", JIT::show(*region)); FTRACE(5, "selectHotTrace: next translation selected: tid = {}\n{}\n", tid, JIT::show(*block)); std::string succStr("succOffs = "); for (auto succ : succOffs) { succStr += lexical_cast<std::string>(succ); } FTRACE(5, "\n{}\n", succStr); } break; } } region->blocks.emplace_back(block); selectedSet.insert(tid); Op lastOp = *(profData->transLastInstr(tid)); if (breaksRegion(lastOp)) { FTRACE(5, "selectHotTrace: breaking region because of last instruction " "in Translation {}: {}\n", tid, opcodeToName(lastOp)); break; } auto outArcs = cfg.outArcs(tid); if (outArcs.size() == 0) { FTRACE(5, "selectHotTrace: breaking region because there's no successor " "for Translation {}\n", tid); break; } auto maxWeight = std::numeric_limits<int64_t>::min(); TransCFG::Arc* maxArc = nullptr; for (auto arc : outArcs) { if (arc->weight() >= maxWeight) { maxWeight = arc->weight(); maxArc = arc; } } assert(maxArc != nullptr); prevId = tid; tid = maxArc->dst(); } return region; }