// 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); } } }
TEST(CFG, InsertPreHeaders_MultiBackEdge) { IRUnit unit{test_context}; auto const dummy = BCMarker::Dummy(); // Multiple back-edges to the same block. /* digraph G { B0 -> B1; B0 -> B2 B1 -> B3 B2 -> B3 B3 -> B3; B3 -> B4 B4 -> B3 } */ auto b0 = unit.entry(); auto b1 = unit.defBlock(); auto b2 = unit.defBlock(); auto b3 = unit.defBlock(); auto b4 = unit.defBlock(); auto val1 = unit.gen(Conjure, dummy, TBool); auto val2 = unit.gen(Conjure, dummy, TBool); b0->push_back(unit.gen(JmpZero, dummy, b1, val1->dst())); b0->back().setNext(b2); b1->push_back(unit.gen(Jmp, dummy, b3)); b2->push_back(unit.gen(Jmp, dummy, b3)); b3->push_back(unit.gen(JmpNZero, dummy, b3, val2->dst())); b3->back().setNext(b4); b4->push_back(unit.gen(Jmp, dummy, b3)); auto oldSize = unit.numBlocks(); auto res = insertLoopPreHeaders(unit); auto newSize = unit.numBlocks(); EXPECT_TRUE(res); EXPECT_EQ(newSize, oldSize + 1); // b5 is the new pre-header. auto b5 = b1->taken(); EXPECT_NE(b3, b4); EXPECT_EQ(b5->numSuccs(), 1); EXPECT_EQ(b2->taken(), b5); EXPECT_EQ(b5->taken(), b3); EXPECT_EQ(b3->taken(), b3); EXPECT_EQ(b4->taken(), b3); }
void insertIncRefs(PrcEnv& env) { auto antQ = dataflow_worklist<uint32_t, std::less<uint32_t>>(env.rpoBlocks.size()); auto avlQ = dataflow_worklist<uint32_t, std::greater<uint32_t>>(env.rpoBlocks.size()); env.states.resize(env.unit.numBlocks()); for (uint32_t i = 0; i < env.rpoBlocks.size(); i++) { auto blk = env.rpoBlocks[i]; auto& state = env.states[blk->id()]; state.rpoId = i; if (blk->numSuccs()) state.antOut.set(); if (blk->numPreds()) state.avlIn.set(); antQ.push(i); avlQ.push(i); } auto id = 0; for (auto& v : env.insertMap) { for (auto const tmp : v) { auto const blk = tmp->inst()->block(); auto& state = env.states[blk->id()]; if (!state.local.test(id)) { state.local.set(id); continue; } } id++; } using Bits = PrcState::Bits; // compute anticipated do { auto const blk = env.rpoBlocks[antQ.pop()]; auto& state = env.states[blk->id()]; state.antIn = state.antOut | state.local; state.pantIn = state.pantOut | state.local; blk->forEachPred( [&] (Block* b) { auto& s = env.states[b->id()]; auto const antOut = s.antOut & state.antIn; auto const pantOut = s.pantOut | state.pantIn; if (antOut != s.antOut || pantOut != s.pantOut) { s.antOut = antOut; s.pantOut = pantOut; antQ.push(s.rpoId); } } ); } while (!antQ.empty()); // compute available do { auto const blk = env.rpoBlocks[avlQ.pop()]; auto& state = env.states[blk->id()]; state.avlOut = state.avlIn | state.local; blk->forEachSucc( [&] (Block* b) { auto& s = env.states[b->id()]; auto const avlIn = s.avlIn & state.avlOut; if (avlIn != s.avlIn) { s.avlIn = avlIn; avlQ.push(s.rpoId); } }); } while (!avlQ.empty()); for (auto blk : env.rpoBlocks) { auto& state = env.states[blk->id()]; FTRACE(4, "InsertIncDecs: Blk(B{}) <- {}\n" "{}" " ->{}\n", blk->id(), [&] { std::string ret; blk->forEachPred([&] (Block* pred) { folly::format(&ret, " B{}", pred->id()); }); return ret; }(), show(state), [&] { std::string ret; blk->forEachSucc([&] (Block* succ) { folly::format(&ret, " B{}", succ->id()); }); return ret; }()); auto inc = state.local; for (auto inc_id = 0; inc.any(); inc >>= 1, inc_id++) { if (inc.test(0)) { auto const& tmps = env.insertMap[inc_id]; auto insert = [&] (IRInstruction* inst) { FTRACE(3, "Inserting IncRef into B{}\n", blk->id()); auto const iter = std::next(blk->iteratorTo(inst)); blk->insert(iter, env.unit.gen(IncRef, inst->bcctx(), tmps[0])); }; SSATmp* last = nullptr; // Insert an IncRef after every candidate in this block except // the last one (since we know for all but the last that its // successor is anticipated). Note that entries in tmps from // the same block are guaranteed to be in program order. for (auto const tmp : tmps) { if (tmp->inst()->block() != blk) continue; if (last) insert(last->inst()); last = tmp; } // If it's partially anticipated out, insert an inc after the // last one too. always_assert(last); if (state.pantOut.test(inc_id)) insert(last->inst()); } } auto dec = state.avlIn & ~state.pantIn; if (dec.any()) { blk->forEachPred( [&] (Block* pred) { auto& pstate = env.states[pred->id()]; dec &= pstate.pantOut; }); for (auto dec_id = 0; dec.any(); dec >>= 1, dec_id++) { if (dec.test(0)) { FTRACE(3, "Inserting DecRef into B{}\n", blk->id()); auto const tmp = env.insertMap[dec_id][0]; blk->prepend(env.unit.gen(DecRef, tmp->inst()->bcctx(), DecRefData{}, tmp)); } } } }