/* * Algorithm for reflow: * 1. for each block in reverse postorder: * 2. compute dest types of each instruction in forwards order * 3. if the block ends with a jmp that passes types to a label, * and the jmp is a loop edge, * and any passed types cause the target label's type to widen, * then set again=true * 4. if again==true, goto step 1 */ void reflowTypes(IRUnit& unit) { auto blocklist = rpoSortCfgWithIds(unit); auto isBackEdge = [&](Block* from, Block* to) { return blocklist.ids[from] > blocklist.ids[to]; }; for (bool again = true; again;) { again = false; for (auto* block : blocklist.blocks) { FTRACE(5, "reflowTypes: visiting block {}\n", block->id()); for (auto& inst : *block) retypeDests(&inst, &unit); auto& jmp = block->back(); auto n = jmp.numSrcs(); if (!again && jmp.is(Jmp) && n > 0 && isBackEdge(block, jmp.taken())) { // if we pass a widening type to a label, loop again. auto srcs = jmp.srcs(); auto dsts = jmp.taken()->front().dsts(); for (unsigned i = 0; i < n; ++i) { if (srcs[i]->type() <= dsts[i].type()) continue; again = true; break; } } } } }
/* * Build the CFG, then the dominator tree, then use it to validate SSA. * 1. Each src must be defined by some other instruction, and each dst must * be defined by the current instruction. * 2. Each src must be defined earlier in the same block or in a dominator. * 3. Each dst must not be previously defined. * 4. Treat tmps defined by DefConst as always defined. * 5. Each predecessor of a reachable block must be reachable (deleted * blocks must not have out-edges to reachable blocks). * 6. The entry block must not have any predecessors. */ bool checkCfg(const IRUnit& unit) { auto const blocksIds = rpoSortCfgWithIds(unit); auto const& blocks = blocksIds.blocks; jit::hash_set<const Edge*> edges; // Entry block can't have predecessors. assert(unit.entry()->numPreds() == 0); // Check valid successor/predecessor edges. for (Block* b : blocks) { auto checkEdge = [&] (const Edge* e) { assert(e->from() == b); edges.insert(e); for (auto& p : e->to()->preds()) if (&p == e) return; assert(false); // did not find edge. }; checkBlock(b); if (auto *e = b->nextEdge()) checkEdge(e); if (auto *e = b->takenEdge()) checkEdge(e); } for (Block* b : blocks) { for (DEBUG_ONLY auto const &e : b->preds()) { assert(&e == e.inst()->takenEdge() || &e == e.inst()->nextEdge()); assert(e.to() == b); } } // visit dom tree in preorder, checking all tmps auto const children = findDomChildren(unit, blocksIds); StateVector<SSATmp, bool> defined0(unit, false); forPreorderDoms(blocks.front(), children, defined0, [] (Block* block, StateVector<SSATmp, bool>& defined) { for (IRInstruction& inst : *block) { for (DEBUG_ONLY SSATmp* src : inst.srcs()) { assert(src->inst() != &inst); assert_log(src->inst()->op() == DefConst || defined[src], [&]{ return folly::format( "src '{}' in '{}' came from '{}', which is not a " "DefConst and is not defined at this use site", src->toString(), inst.toString(), src->inst()->toString()).str(); }); } for (SSATmp& dst : inst.dsts()) { assert(dst.inst() == &inst && inst.op() != DefConst); assert(!defined[dst]); defined[dst] = true; } } }); return true; }
void gvn(IRUnit& unit) { auto rpoBlocksWithIds = rpoSortCfgWithIds(unit); auto& rpoBlocks = rpoBlocksWithIds.blocks; auto dominators = findDominators(unit, rpoBlocksWithIds); ValueNumberTable vnTable(unit, ValueNumberMetadata { nullptr, nullptr }); // This is an implementation of the RPO version of the global value numbering // algorithm presented in the 1996 paper "SCC-based Value Numbering" by // Cooper and Simpson. runAnalysis(unit, rpoBlocks, vnTable); replaceRedundantComputations(unit, dominators, rpoBlocks, vnTable); }
/* * Build the CFG, then the dominator tree, then use it to validate SSA. * 1. Each src must be defined by some other instruction, and each dst must * be defined by the current instruction. * 2. Each src must be defined earlier in the same block or in a dominator. * 3. Each dst must not be previously defined. * 4. Treat tmps defined by DefConst as always defined. * 5. Each predecessor of a reachable block must be reachable (deleted * blocks must not have out-edges to reachable blocks). * 6. The entry block must not have any predecessors. * 7. The entry block starts with a DefFP instruction. */ bool checkCfg(const IRUnit& unit) { auto const blocksIds = rpoSortCfgWithIds(unit); auto const& blocks = blocksIds.blocks; jit::hash_set<const Edge*> edges; // Entry block can't have predecessors. always_assert(unit.entry()->numPreds() == 0); // Entry block starts with DefFP always_assert(!unit.entry()->empty() && unit.entry()->begin()->op() == DefFP); // Check valid successor/predecessor edges. for (Block* b : blocks) { auto checkEdge = [&] (const Edge* e) { always_assert(e->from() == b); edges.insert(e); for (auto& p : e->to()->preds()) if (&p == e) return; always_assert(false); // did not find edge. }; checkBlock(b); if (auto *e = b->nextEdge()) checkEdge(e); if (auto *e = b->takenEdge()) checkEdge(e); } for (Block* b : blocks) { for (auto const &e : b->preds()) { always_assert(&e == e.inst()->takenEdge() || &e == e.inst()->nextEdge()); always_assert(e.to() == b); } } // Visit every instruction and make sure their sources are defined in a block // that dominates the block containing the instruction. auto const idoms = findDominators(unit, blocksIds); forEachInst(blocks, [&] (const IRInstruction* inst) { for (auto src : inst->srcs()) { if (src->inst()->is(DefConst)) continue; auto const dom = findDefiningBlock(src); always_assert_flog( dom && dominates(dom, inst->block(), idoms), "src '{}' in '{}' came from '{}', which is not a " "DefConst and is not defined at this use site", src->toString(), inst->toString(), src->inst()->toString() ); } }); return true; }
/* * reoptimize() runs a trace through a second pass of IRBuilder * optimizations, like this: * * reset state. * move all blocks to a temporary list. * compute immediate dominators. * for each block in trace order: * if we have a snapshot state for this block: * clear cse entries that don't dominate this block. * use snapshot state. * move all instructions to a temporary list. * for each instruction: * optimizeWork - do CSE and simplify again * if not simplified: * append existing instruction and update state. * else: * if the instruction has a result, insert a mov from the * simplified tmp to the original tmp and discard the instruction. * if the last conditional branch was turned into a jump, remove the * fall-through edge to the next block. */ void IRBuilder::reoptimize() { Timer _t("optimize_reoptimize"); FTRACE(5, "ReOptimize:vvvvvvvvvvvvvvvvvvvv\n"); SCOPE_EXIT { FTRACE(5, "ReOptimize:^^^^^^^^^^^^^^^^^^^^\n"); }; always_assert(m_savedBlocks.empty()); always_assert(!m_curWhere); always_assert(m_state.inlineDepth() == 0); m_state.setEnableCse(RuntimeOption::EvalHHIRCse); m_enableSimplification = RuntimeOption::EvalHHIRSimplification; if (!m_state.enableCse() && !m_enableSimplification) return; setConstrainGuards(false); auto blocksIds = rpoSortCfgWithIds(m_unit); auto const idoms = findDominators(m_unit, blocksIds); m_state.clear(); for (auto* block : blocksIds.blocks) { FTRACE(5, "Block: {}\n", block->id()); m_state.startBlock(block); m_curBlock = block; auto nextBlock = block->next(); auto backMarker = block->back().marker(); auto instructions = block->moveInstrs(); assert(block->empty()); while (!instructions.empty()) { auto* inst = &instructions.front(); instructions.pop_front(); // merging state looks at the current marker, and optimizeWork // below may create new instructions. Use the marker from this // instruction. assert(inst->marker().valid()); setMarker(inst->marker()); auto const tmp = optimizeWork(inst, idoms); // Can generate new instrs! if (!tmp) { // Could not optimize; keep the old instruction appendInstruction(inst); continue; } SSATmp* dst = inst->dst(); if (dst != tmp) { // The result of optimization has a different destination than the inst. // Generate a mov(tmp->dst) to get result into dst. If we get here then // assume the last instruction in the block isn't a guard. If it was, // we would have to insert the mov on the fall-through edge. assert(block->empty() || !block->back().isBlockEnd()); appendInstruction(m_unit.mov(dst, tmp, inst->marker())); } if (inst->isBlockEnd()) { // We're not re-adding the block-end instruction. Unset its edges. inst->setTaken(nullptr); inst->setNext(nullptr); } } if (block->empty() || !block->back().isBlockEnd()) { // Our block-end instruction was eliminated (most likely a Jmp* converted // to a nop). Replace it with a jump to the next block. appendInstruction(m_unit.gen(Jmp, backMarker, nextBlock)); } m_state.finishBlock(block); } }