/** * Create blocks for each entry point as well as ordinary control * flow boundaries. Calls are not treated as basic-block ends. */ void GraphBuilder::createBlocks() { PC bc = m_unit->entry(); m_graph->param_count = m_func->params().size(); m_graph->first_linear = createBlock(m_func->base()); // DV entry points m_graph->entries = new (m_arena) Block*[m_graph->param_count + 1]; int dv_index = 0; for (Range<Func::ParamInfoVec> p(m_func->params()); !p.empty(); ) { const Func::ParamInfo& param = p.popFront(); m_graph->entries[dv_index++] = !param.hasDefaultValue() ? 0 : createBlock(param.funcletOff()); } // main entry point assert(dv_index == m_graph->param_count); m_graph->entries[dv_index] = createBlock(m_func->base()); // ordinary basic block boundaries for (InstrRange i = funcInstrs(m_func); !i.empty(); ) { PC pc = i.popFront(); if (isCF(pc) && !i.empty()) createBlock(i.front()); if (isSwitch(*reinterpret_cast<const Op*>(pc))) { foreachSwitchTarget(reinterpret_cast<const Op*>(pc), [&](Offset& o) { createBlock(pc + o); }); } else { Offset target = instrJumpTarget((Op*)bc, pc - bc); if (target != InvalidAbsoluteOffset) createBlock(target); } } }
/** * Create blocks for each entry point as well as ordinary control * flow boundaries. Calls are not treated as basic-block ends. */ void GraphBuilder::createBlocks() { PC bc = m_unit->entry(); m_graph->param_count = m_func->params().size(); m_graph->first_linear = createBlock(m_func->base()); // DV entry points m_graph->entries = new (m_arena) Block*[m_graph->param_count + 1]; int dv_index = 0; for (auto& param : m_func->params()) { m_graph->entries[dv_index++] = !param.hasDefaultValue() ? 0 : createBlock(param.funcletOff); } // main entry point assert(dv_index == m_graph->param_count); m_graph->entries[dv_index] = createBlock(m_func->base()); // ordinary basic block boundaries for (InstrRange i = funcInstrs(m_func); !i.empty(); ) { PC pc = i.popFront(); if ((isCF(pc) || isTF(pc)) && !i.empty()) createBlock(i.front()); if (isSwitch(peek_op(pc))) { foreachSwitchTarget(pc, [&](Offset o) { createBlock(pc + o); }); } else { Offset target = instrJumpTarget(bc, pc - bc); if (target != InvalidAbsoluteOffset) createBlock(target); } } }
/** * Link ordinary blocks with ordinary edges and set their last instruction * and end offsets */ void GraphBuilder::linkBlocks() { PC bc = m_unit->entry(); Block* block = m_graph->first_linear; block->id = m_graph->block_count++; for (InstrRange i = funcInstrs(m_func); !i.empty(); ) { PC pc = i.popFront(); block->last = pc; if (isCF(pc)) { if (isSwitch(*reinterpret_cast<const Op*>(pc))) { int i = 0; foreachSwitchTarget((Op*)pc, [&](Offset& o) { succs(block)[i++] = at(pc + o); }); } else { Offset target = instrJumpTarget((Op*)bc, pc - bc); if (target != InvalidAbsoluteOffset) { assert(numSuccBlocks(block) > 0); succs(block)[numSuccBlocks(block) - 1] = at(target); } } } PC next_pc = !i.empty() ? i.front() : m_unit->at(m_func->past()); Block* next = at(next_pc); if (next) { block->next_linear = next; block->end = next_pc; if (!isTF(pc)) { assert(numSuccBlocks(block) > 0); succs(block)[0] = next; } block = next; block->id = m_graph->block_count++; } } block->end = m_unit->at(m_func->past()); }
/* * Region-selector that unintelligently takes a whole method at a * time. This is primarily intended for use for debugging and * development on the JIT. * * Adds no type annotations to the region beyond those known from the * context. It will list only the parameter types as guards. * * If the context is not a method entry point, returns nullptr to fall * back to the tracelet compiler. (This will happen for side-exits * from method regions, for example.) */ RegionDescPtr selectMethod(const RegionContext& context) { using namespace HPHP::Verifier; if (!isFuncEntry(context.func, context.bcOffset)) return nullptr; if (context.func->isPseudoMain()) return nullptr; FTRACE(1, "function entry for {}: using selectMethod\n", context.func->fullName()->data()); auto ret = std::make_shared<RegionDesc>(); Arena arena; GraphBuilder gb(arena, context.func); auto const graph = gb.build(); auto const unit = context.func->unit(); jit::hash_map<Block*,RegionDesc::BlockId> blockMap; /* * Spit out the blocks in a RPO, but skip DV-initializer blocks * (i.e. start with graph->first_linear. We don't handle those in * our method regions for now---they'll get handled by the tracelet * compiler and then may branch to the main entry point. */ sortRpo(graph); { auto spOffset = context.spOffset; for (Block* b = graph->first_linear; b != nullptr; b = b->next_rpo) { auto const start = unit->offsetOf(b->start); auto const length = numInstrs(b->start, b->end); SrcKey sk{context.func, start, context.resumed}; auto const rblock = ret->addBlock(sk, length, spOffset, 0); blockMap[b] = rblock->id(); // flag SP offset as unknown for all but the first block spOffset = FPInvOffset::invalid(); } } // Add all the ARCs. for (Block* b = graph->first_linear; b != nullptr; b = b->next_rpo) { auto const myId = blockMap[b]; auto const numSuccs = numSuccBlocks(b); for (auto i = uint32_t{0}; i < numSuccs; ++i) { auto const succIt = blockMap.find(b->succs[i]); if (succIt != end(blockMap)) { ret->addArc(myId, succIt->second); } } } // Compute stack depths for each block. for (Block* b = graph->first_linear; b != nullptr; b = b->next_rpo) { auto const myId = blockMap[b]; auto rblock = ret->block(myId); auto sp = rblock->initialSpOffset(); // Don't add unreachable blocks to the region. if (!sp.isValid()) { ret->deleteBlock(myId); continue; } for (InstrRange inst = blockInstrs(b); !inst.empty();) { auto const pc = inst.popFront(); auto const info = instrStackTransInfo(reinterpret_cast<const Op*>(pc)); switch (info.kind) { case StackTransInfo::Kind::InsertMid: ++sp; break; case StackTransInfo::Kind::PushPop: sp += info.numPushes - info.numPops; break; } } for (auto idx = uint32_t{0}; idx < numSuccBlocks(b); ++idx) { if (!b->succs[idx]) continue; auto const succ = ret->block(blockMap[b->succs[idx]]); if (succ->initialSpOffset().isValid()) { always_assert_flog( succ->initialSpOffset() == sp, "Stack depth mismatch in region method on {}\n" " srcblkoff={}, dstblkoff={}, src={}, target={}", context.func->fullName()->data(), context.func->unit()->offsetOf(b->start), context.func->unit()->offsetOf(b->succs[idx]->start), sp.offset, succ->initialSpOffset().offset ); continue; } succ->setInitialSpOffset(sp); FTRACE(2, "spOff for {} -> {}\n", context.func->unit()->offsetOf(b->succs[idx]->start), sp.offset ); } } /* * Fill the first block predictions with the live types. */ assertx(!ret->empty()); auto const startSK = ret->start(); for (auto& lt : context.liveTypes) { typedef RegionDesc::Location::Tag LTag; switch (lt.location.tag()) { case LTag::Stack: break; case LTag::Local: if (lt.location.localId() < context.func->numParams()) { // Only predict objectness, not the specific class type. auto const type = lt.type < TObj ? TObj : lt.type; ret->entry()->addPreCondition(startSK, {lt.location, type}); } break; } } return ret; }