// Place internal breakpoints to get out of the current function. This may place // multiple internal breakpoints, and it may place them more than one frame up. // Some instructions can cause PHP to be invoked without an explicit call. A set // which causes a destructor to run, a iteration init which causes an object's // next() method to run, a RetC which causes destructors to run, etc. This // recgonizes such cases and ensures we have internal breakpoints to cover the // destination(s) of such instructions. void CmdFlowControl::setupStepOuts() { // Existing step outs should be cleaned up before making new ones. assert(!hasStepOuts()); auto fp = g_context->getFP(); if (!fp) return; // No place to step out to! Offset returnOffset; bool fromVMEntry; while (!hasStepOuts()) { fp = g_context->getPrevVMState(fp, &returnOffset, nullptr, &fromVMEntry); // If we've run off the top of the stack, just return having setup no // step outs. This will cause cmds like Next and Out to just let the program // run, which is appropriate. if (!fp) break; Unit* returnUnit = fp->m_func->unit(); PC returnPC = returnUnit->at(returnOffset); TRACE(2, "CmdFlowControl::setupStepOuts: at '%s' offset %d opcode %s\n", fp->m_func->fullName()->data(), returnOffset, opcodeToName(*reinterpret_cast<const Op*>(returnPC))); // Don't step out to generated functions, keep looking. if (fp->m_func->line1() == 0) continue; if (fromVMEntry) { TRACE(2, "CmdFlowControl::setupStepOuts: VM entry\n"); // We only execute this for opcodes which invoke more PHP, and that does // not include switches. Thus, we'll have at most two destinations. assert(!isSwitch(*reinterpret_cast<const Op*>(returnPC)) && (numSuccs(reinterpret_cast<const Op*>(returnPC)) <= 2)); // Set an internal breakpoint after the instruction if it can fall thru. if (instrAllowsFallThru(*reinterpret_cast<const Op*>(returnPC))) { Offset nextOffset = returnOffset + instrLen((Op*)returnPC); TRACE(2, "CmdFlowControl: step out to '%s' offset %d (fall-thru)\n", fp->m_func->fullName()->data(), nextOffset); m_stepOut1 = StepDestination(returnUnit, nextOffset); } // Set an internal breakpoint at the target of a control flow instruction. // A good example of a control flow op that invokes PHP is IterNext. if (instrIsControlFlow(*reinterpret_cast<const Op*>(returnPC))) { Offset target = instrJumpTarget(reinterpret_cast<const Op*>(returnPC), 0); if (target != InvalidAbsoluteOffset) { Offset targetOffset = returnOffset + target; TRACE(2, "CmdFlowControl: step out to '%s' offset %d (jump target)\n", fp->m_func->fullName()->data(), targetOffset); m_stepOut2 = StepDestination(returnUnit, targetOffset); } } // If we have no place to step out to, then unwind another frame and try // again. The most common case that leads here is Ret*, which does not // fall-thru and has no encoded target. } else { TRACE(2, "CmdFlowControl: step out to '%s' offset %d\n", fp->m_func->fullName()->data(), returnOffset); m_stepOut1 = StepDestination(returnUnit, returnOffset); } } }
void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assertx(!m_complete); // Complete cmds should not be asked to do work. ActRec *fp = vmfp(); if (!fp) { // If we have no frame just wait for the next instruction to be interpreted. m_needsVMInterrupt = true; return; } PC pc = vmpc(); Unit* unit = fp->m_func->unit(); Offset offset = unit->offsetOf(pc); TRACE(2, "CmdNext: pc %p, opcode %s at '%s' offset %d\n", pc, opcodeToName(peek_op(pc)), fp->m_func->fullName()->data(), offset); int currentVMDepth = g_context->m_nesting; int currentStackDepth = proxy.getStackDepth(); TRACE(2, "CmdNext: original depth %d:%d, current depth %d:%d\n", m_vmDepth, m_stackDepth, currentVMDepth, currentStackDepth); // Where are we on the stack now vs. when we started? Breaking the answer down // into distinct variables helps the clarity of the algorithm below. bool deeper = false; bool originalDepth = false; if ((currentVMDepth == m_vmDepth) && (currentStackDepth == m_stackDepth)) { originalDepth = true; } else if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth > m_stackDepth))) { deeper = true; } m_needsVMInterrupt = false; // Will be set again below if still needed. // First consider if we've got internal breakpoints setup. These are used when // we can make an accurate prediction of where execution should flow, // eventually, and when we want to let the program run normally until we get // there. if (hasStepOuts() || hasStepResumable()) { TRACE(2, "CmdNext: checking internal breakpoint(s)\n"); if (atStepOutOffset(unit, offset)) { if (deeper) return; // Recursion TRACE(2, "CmdNext: hit step-out\n"); } else if (atStepResumableOffset(unit, offset)) { if (m_stepResumableId != getResumableId(fp)) return; TRACE(2, "CmdNext: hit step-cont\n"); // We're in the resumable we expect. This may be at a // different stack depth, though, especially if we've moved from // the original function to the resumable. Update the depth // accordingly. if (!originalDepth) { m_vmDepth = currentVMDepth; m_stackDepth = currentStackDepth; deeper = false; originalDepth = true; } } else if (interrupt.getInterruptType() == ExceptionHandler) { // Entering an exception handler may take us someplace we weren't // expecting. Adjust internal breakpoints accordingly. First case is easy. if (deeper) { TRACE(2, "CmdNext: exception handler, deeper\n"); return; } // For step-conts, we ignore handlers at the original level if we're not // in the original resumable. We don't care about exception handlers // in resumables being driven at the same level. if (hasStepResumable() && originalDepth && (m_stepResumableId != getResumableId(fp))) { TRACE(2, "CmdNext: exception handler, original depth, wrong cont\n"); return; } // Sometimes we have handlers in generated code, i.e., Continuation::next. // These just help propagate exceptions so ignore those. if (fp->m_func->line1() == 0) { TRACE(2, "CmdNext: exception handler, ignoring func with no source\n"); return; } if (fp->m_func->isBuiltin()) { TRACE(2, "CmdNext: exception handler, ignoring builtin functions\n"); return; } TRACE(2, "CmdNext: exception handler altering expected flow\n"); } else { // We have internal breakpoints setup, but we haven't hit one yet. Keep // running until we reach one. TRACE(2, "CmdNext: waiting to hit internal breakpoint...\n"); return; } // We've hit one internal breakpoint at a useful place, or decided we don't, // need them, so we can remove them all now. cleanupStepOuts(); cleanupStepResumable(); } if (interrupt.getInterruptType() == ExceptionHandler) { // If we're about to enter an exception handler we turn interrupts on to // ensure we stop when control reaches the handler. The normal logic below // will decide if we're done at that point or not. TRACE(2, "CmdNext: exception handler\n"); removeLocationFilter(); m_needsVMInterrupt = true; return; } if (m_skippingAwait) { m_skippingAwait = false; stepAfterAwait(); return; } if (deeper) { TRACE(2, "CmdNext: deeper, setup step out to get back to original line\n"); setupStepOuts(); // We can nuke the entire location filter here since we'll re-install it // when we get back to the old level. Keeping it installed may be more // efficient if we were on a large line, but there is a penalty for every // opcode executed while it's installed and that's bad if there's a lot of // code called from that line. removeLocationFilter(); return; } if (originalDepth && (m_loc == interrupt.getFileLine())) { TRACE(2, "CmdNext: not complete, still on same line\n"); stepCurrentLine(interrupt, fp, pc); return; } TRACE(2, "CmdNext: operation complete.\n"); m_complete = (decCount() == 0); if (!m_complete) { TRACE(2, "CmdNext: repeat count > 0, start fresh.\n"); onSetup(proxy, interrupt); } }
RegionDescPtr selectHotTrace(HotTransContext& ctx, TransIDSet& selectedSet, TransIDVec* selectedVec /* = nullptr */) { auto region = std::make_shared<RegionDesc>(); TransID tid = ctx.tid; TransID prevId = kInvalidTransID; selectedSet.clear(); if (selectedVec) selectedVec->clear(); TypedLocations 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, TypedLocations> blockPostConds; auto numBCInstrs = ctx.maxBCInstrs; FTRACE(1, "selectHotTrace: starting with maxBCInstrs = {}\n", numBCInstrs); while (!selectedSet.count(tid)) { auto rec = ctx.profData->transRec(tid); auto blockRegion = rec->region(); if (blockRegion == nullptr) break; // Break if region would be larger than the specified limit. if (blockRegion->instrSize() > numBCInstrs) { FTRACE(2, "selectHotTrace: breaking region at Translation {} because " "size would exceed of maximum translation limit\n", tid); 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) { auto const func = rec->func(); auto const bcOffset = rec->startBcOff(); 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 = rec->srcKey(); if (ctx.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(); // Add blockRegion's blocks and arcs to region. region->append(*blockRegion); numBCInstrs -= blockRegion->instrSize(); assertx(numBCInstrs >= 0); if (hasPredBlock) { region->addArc(predBlockId, newFirstBlockId); } selectedSet.insert(tid); if (selectedVec) selectedVec->push_back(tid); const auto lastSk = rec->lastSrcKey(); if (breaksRegion(lastSk)) { FTRACE(2, "selectHotTrace: breaking region because of last instruction " "in Translation {}: {}\n", tid, opcodeToName(lastSk.op())); break; } auto outArcs = ctx.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) { auto dstRec = ctx.profData->transRec(arc->dst()); auto possibleNext = dstRec->region()->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; }
bool InliningDecider::shouldInline(SrcKey callerSk, const Func* callee, const RegionDesc& region, uint32_t maxTotalCost) { auto sk = region.empty() ? SrcKey() : region.start(); assertx(callee); assertx(sk.func() == callee); // Tracing return lambdas. auto refuse = [&] (const char* why) { FTRACE(2, "shouldInline: rejecting callee region: {}", show(region)); return traceRefusal(m_topFunc, callee, why); }; auto accept = [&, this] (const char* kind) { FTRACE(2, "InliningDecider: inlining {}() <- {}()\t<reason: {}>\n", m_topFunc->fullName()->data(), callee->fullName()->data(), kind); return true; }; if (m_stackDepth + callee->maxStackCells() >= kStackCheckLeafPadding) { return refuse("inlining stack depth limit exceeded"); } // Even if the func contains NativeImpl we may have broken the trace before // we hit it. auto containsNativeImpl = [&] { for (auto block : region.blocks()) { if (!block->empty() && block->last().op() == OpNativeImpl) return true; } return false; }; // Try to inline CPP builtin functions. // The NativeImpl opcode may appear later in the function because of Asserts // generated in hhbbc if (callee->isCPPBuiltin() && containsNativeImpl()) { if (isInlinableCPPBuiltin(callee)) { return accept("inlinable CPP builtin"); } return refuse("non-inlinable CPP builtin"); } // If the function may use a VarEnv (which is stored in the ActRec) or may be // variadic, we restrict inlined callees to certain whitelisted instructions // which we know won't actually require these features. const bool needsCheckVVSafe = callee->attrs() & AttrMayUseVV; bool hasRet = false; // Iterate through the region, checking its suitability for inlining. for (auto const& block : region.blocks()) { sk = block->start(); for (auto i = 0, n = block->length(); i < n; ++i, sk.advance()) { auto op = sk.op(); // We don't allow inlined functions in the region. The client is // expected to disable inlining for the region it gives us to peek. if (sk.func() != callee) { return refuse("got region with inlined calls"); } // Restrict to VV-safe opcodes if necessary. if (needsCheckVVSafe && !isInliningVVSafe(op)) { return refuse(folly::format("{} may use dynamic environment", opcodeToName(op)).str().c_str()); } // Count the returns. if (isReturnish(op)) { hasRet = true; } // We can't inline FCallArray. XXX: Why? if (op == Op::FCallArray) { return refuse("can't inline FCallArray"); } } } if (!hasRet) { return refuse("region has no returns"); } // Refuse if the cost exceeds our thresholds. // We measure the cost of inlining each callstack and stop when it exceeds a // certain threshold. (Note that we do not measure the total cost of all the // inlined calls for a given caller---just the cost of each nested stack.) const int maxCost = maxTotalCost - m_cost; const int cost = computeTranslationCost(callerSk, region); if (cost > maxCost) { return refuse("too expensive"); } return accept("small region with return"); }
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; }
/* * Get location metadata for the inputs of `ni'. */ InputInfoVec getInputs(NormalizedInstruction& ni, FPInvOffset bcSPOff) { InputInfoVec inputs; if (isAlwaysNop(ni.op())) return inputs; always_assert_flog( instrInfo.count(ni.op()), "Invalid opcode in getInputsImpl: {}\n", opcodeToName(ni.op()) ); UNUSED auto const sk = ni.source; auto const& info = instrInfo[ni.op()]; auto const flags = info.in; auto stackOff = bcSPOff; if (flags & FStack) { stackOff -= ni.imm[0].u_IVA; // arguments consumed stackOff -= kNumActRecCells; // ActRec is torn down as well } if (flags & FuncdRef) inputs.needsRefCheck = true; if (flags & IgnoreInnerType) ni.ignoreInnerType = true; if (flags & Stack1) { SKTRACE(1, sk, "getInputs: Stack1 %d\n", stackOff.offset); inputs.emplace_back(Location::Stack { stackOff-- }); if (flags & DontGuardStack1) inputs.back().dontGuard = true; if (flags & Stack2) { SKTRACE(1, sk, "getInputs: Stack2 %d\n", stackOff.offset); inputs.emplace_back(Location::Stack { stackOff-- }); if (flags & Stack3) { SKTRACE(1, sk, "getInputs: Stack3 %d\n", stackOff.offset); inputs.emplace_back(Location::Stack { stackOff-- }); } } } if (flags & StackI) { inputs.emplace_back(Location::Stack { BCSPRelOffset{ni.imm[0].u_IVA}.to<FPInvOffset>(bcSPOff) }); } if (flags & StackN) { int numArgs = (ni.op() == Op::NewPackedArray || ni.op() == Op::NewVecArray || ni.op() == Op::ConcatN) ? ni.imm[0].u_IVA : ni.immVec.numStackValues(); SKTRACE(1, sk, "getInputs: StackN %d %d\n", stackOff.offset, numArgs); for (int i = 0; i < numArgs; i++) { inputs.emplace_back(Location::Stack { stackOff-- }); inputs.back().dontGuard = true; inputs.back().dontBreak = true; } } if (flags & BStackN) { int numArgs = ni.imm[0].u_IVA; SKTRACE(1, sk, "getInputs: BStackN %d %d\n", stackOff.offset, numArgs); for (int i = 0; i < numArgs; i++) { inputs.emplace_back(Location::Stack { stackOff-- }); } } if (flags & Local) { // (Almost) all instructions that take a Local have its index at their // first immediate. auto const loc = ni.imm[localImmIdx(ni.op())].u_IVA; SKTRACE(1, sk, "getInputs: local %d\n", loc); inputs.emplace_back(Location::Local { uint32_t(loc) }); } if (flags & AllLocals) ni.ignoreInnerType = true; if (flags & MKey) { auto mk = ni.imm[memberKeyImmIdx(ni.op())].u_KA; switch (mk.mcode) { case MEL: case MPL: inputs.emplace_back(Location::Local { uint32_t(mk.iva) }); break; case MEC: case MPC: inputs.emplace_back(Location::Stack { BCSPRelOffset{int32_t(mk.iva)}.to<FPInvOffset>(bcSPOff) }); break; case MW: case MEI: case MET: case MPT: case MQT: // The inputs vector is only used for deciding when to break the // tracelet, which can never happen for these cases. break; } } SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackOff.offset); TRACE(1, "%s\n", Trace::prettyNode("Inputs", inputs).c_str()); if ((flags & DontGuardAny) || dontGuardAnyInputs(ni.op())) { for (auto& info : inputs) info.dontGuard = true; } return inputs; }
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; }
void SialOpsParallel::log_statement(opcode_t type, int line){ SIP_LOG( std::cout<< "W " << sip_mpi_attr_.global_rank() << " : Line "<<line << ", type: " << opcodeToName(type)<<std::endl); }
/* * getInputs -- * Returns locations for this instruction's inputs. */ InputInfoVec getInputs(NormalizedInstruction& ni) { InputInfoVec inputs; auto UNUSED sk = ni.source; if (isAlwaysNop(ni.op())) return inputs; assertx(inputs.empty()); always_assert_flog( instrInfo.count(ni.op()), "Invalid opcode in getInputsImpl: {}\n", opcodeToName(ni.op()) ); const InstrInfo& info = instrInfo[ni.op()]; Operands input = info.in; BCSPOffset spOff{0}; if (input & FuncdRef) { inputs.needsRefCheck = true; } if (input & Iter) { inputs.emplace_back(Location(Location::Iter, ni.imm[0].u_IVA)); } if (input & FStack) { spOff += ni.imm[0].u_IVA; // arguments consumed spOff += kNumActRecCells; // ActRec is torn down as well } if (input & IgnoreInnerType) ni.ignoreInnerType = true; if (input & Stack1) { SKTRACE(1, sk, "getInputs: stack1 %d\n", spOff.offset); inputs.emplace_back(Location(spOff++)); if (input & DontGuardStack1) inputs.back().dontGuard = true; if (input & Stack2) { SKTRACE(1, sk, "getInputs: stack2 %d\n", spOff.offset); inputs.emplace_back(Location(spOff++)); if (input & Stack3) { SKTRACE(1, sk, "getInputs: stack3 %d\n", spOff.offset); inputs.emplace_back(Location(spOff++)); } } } if (input & StackI) { inputs.emplace_back(Location(BCSPOffset{ni.imm[0].u_IVA})); } if (input & StackN) { int numArgs = (ni.op() == Op::NewPackedArray || ni.op() == Op::ConcatN) ? ni.imm[0].u_IVA : ni.immVec.numStackValues(); SKTRACE(1, sk, "getInputs: stackN %d %d\n", spOff.offset, numArgs); for (int i = 0; i < numArgs; i++) { inputs.emplace_back(Location(spOff++)); inputs.back().dontGuard = true; inputs.back().dontBreak = true; } } if (input & BStackN) { int numArgs = ni.imm[0].u_IVA; SKTRACE(1, sk, "getInputs: BStackN %d %d\n", spOff.offset, numArgs); for (int i = 0; i < numArgs; i++) { inputs.emplace_back(Location(spOff++)); } } if (input & Local) { // (Almost) all instructions that take a Local have its index at // their first immediate. auto const loc = ni.imm[localImmIdx(ni.op())].u_IVA; SKTRACE(1, sk, "getInputs: local %d\n", loc); inputs.emplace_back(Location(Location::Local, loc)); } if (input & MKey) { auto mk = ni.imm[memberKeyImmIdx(ni.op())].u_KA; switch (mk.mcode) { case MEL: case MPL: inputs.emplace_back(Location(Location::Local, mk.iva)); break; case MEC: case MPC: inputs.emplace_back(Location(BCSPOffset{int32_t(mk.iva)})); break; case MW: case MEI: case MET: case MPT: case MQT: // The inputs vector is only used for deciding when to break the // tracelet, which can never happen for these cases. break; } } if (input & AllLocals) { ni.ignoreInnerType = true; } SKTRACE(1, sk, "stack args: virtual sfo now %d\n", spOff.offset); TRACE(1, "%s\n", Trace::prettyNode("Inputs", inputs).c_str()); if (inputs.size() && ((input & DontGuardAny) || dontGuardAnyInputs(ni.op()))) { for (int i = inputs.size(); i--; ) { inputs[i].dontGuard = true; } } if (input & This) { inputs.emplace_back(Location(Location::This)); } return inputs; }
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; }
bool InliningDecider::shouldInline(const Func* callee, const RegionDesc& region) { auto sk = region.empty() ? SrcKey() : region.start(); assertx(callee); assertx(sk.func() == callee); int cost = 0; // Tracing return lambdas. auto refuse = [&] (const char* why) { return traceRefusal(m_topFunc, callee, why); }; auto accept = [&, this] (const char* kind) { FTRACE(1, "InliningDecider: inlining {}() <- {}()\t<reason: {}>\n", m_topFunc->fullName()->data(), callee->fullName()->data(), kind); // Update our context. m_costStack.push_back(cost); m_cost += cost; m_callDepth += 1; m_stackDepth += callee->maxStackCells(); return true; }; // Check inlining depths. if (m_callDepth + 1 >= RuntimeOption::EvalHHIRInliningMaxDepth) { return refuse("inlining call depth limit exceeded"); } if (m_stackDepth + callee->maxStackCells() >= kStackCheckLeafPadding) { return refuse("inlining stack depth limit exceeded"); } // Even if the func contains NativeImpl we may have broken the trace before // we hit it. auto containsNativeImpl = [&] { for (auto block : region.blocks()) { if (!block->empty() && block->last().op() == OpNativeImpl) return true; } return false; }; // Try to inline CPP builtin functions. // The NativeImpl opcode may appear later in the function because of Asserts // generated in hhbbc if (callee->isCPPBuiltin() && containsNativeImpl()) { if (isInlinableCPPBuiltin(callee)) { return accept("inlinable CPP builtin"); } return refuse("non-inlinable CPP builtin"); } // If the function may use a VarEnv (which is stored in the ActRec) or may be // variadic, we restrict inlined callees to certain whitelisted instructions // which we know won't actually require these features. const bool needsCheckVVSafe = callee->attrs() & AttrMayUseVV; // We measure the cost of inlining each callstack and stop when it exceeds a // certain threshold. (Note that we do not measure the total cost of all the // inlined calls for a given caller---just the cost of each nested stack.) const int maxCost = RuntimeOption::EvalHHIRInliningMaxCost - m_cost; // We only inline callee regions that have exactly one return. // // NOTE: Currently, the tracelet selector uses the first Ret in the child's // region to determine when to stop inlining. However, the safety of this // behavior should not be considered guaranteed by InliningDecider; the // "right" way to decide when inlining ends is to inline all of `region'. int numRets = 0; // Iterate through the region, checking its suitability for inlining. for (auto const& block : region.blocks()) { sk = block->start(); for (auto i = 0, n = block->length(); i < n; ++i, sk.advance()) { auto op = sk.op(); // We don't allow inlined functions in the region. The client is // expected to disable inlining for the region it gives us to peek. if (sk.func() != callee) { return refuse("got region with inlined calls"); } // Restrict to VV-safe opcodes if necessary. if (needsCheckVVSafe && !isInliningVVSafe(op)) { return refuse(folly::format("{} may use dynamic environment", opcodeToName(op)).str().c_str()); } // Count the returns. if (isRet(op) || op == Op::NativeImpl) { if (++numRets > 1) { return refuse("region has too many returns"); } continue; } // We can't inline FCallArray. XXX: Why? if (op == Op::FCallArray) { return refuse("can't inline FCallArray"); } // Assert opcodes don't contribute to the inlining cost. if (op == Op::AssertRATL || op == Op::AssertRATStk) continue; cost += 1; // Add the size of immediate vectors to the cost. auto const pc = reinterpret_cast<const Op*>(sk.pc()); if (hasMVector(op)) { cost += getMVector(pc).size(); } else if (hasImmVector(op)) { cost += getImmVector(pc).size(); } // Refuse if the cost exceeds our thresholds. if (cost > maxCost) { return refuse("too expensive"); } } } if (numRets != 1) { return refuse("region has no returns"); } return accept("small region with single return"); }