bool checkRegisters(IRTrace* trace, const IRFactory& factory, const RegAllocInfo& regs) { assert(checkCfg(trace, factory)); auto blocks = rpoSortCfg(trace, factory); auto children = findDomChildren(blocks); forPreorderDoms(blocks.front(), children, RegState(), [&] (Block* block, RegState& state) { for (IRInstruction& inst : *block) { for (SSATmp* src : inst.srcs()) { auto const &info = regs[src]; if (!info.spilled() && (info.reg(0) == Transl::rVmSp || info.reg(0) == Transl::rVmFp)) { // hack - ignore rbx and rbp continue; } for (unsigned i = 0, n = info.numAllocatedRegs(); i < n; ++i) { assert(state.tmp(info, i) == src); } } for (SSATmp& dst : inst.dsts()) { auto const &info = regs[dst]; for (unsigned i = 0, n = info.numAllocatedRegs(); i < n; ++i) { state.tmp(info, i) = &dst; } } } }); return true; }
/* * 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; }
/* * 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). */ bool checkCfg(IRTrace* trace, const IRFactory& factory) { forEachTraceBlock(trace, checkBlock); // Check valid successor/predecessor edges. auto const blocks = rpoSortCfg(trace, factory); std::unordered_set<const Edge*> 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. }; if (auto *e = nextEdge(b)) checkEdge(e); if (auto *e = takenEdge(b)) checkEdge(e); } for (Block* b : blocks) { for (DEBUG_ONLY auto const &e : b->preds()) { assert(&e == takenEdge(e.from()) || &e == nextEdge(e.from())); assert(e.to() == b); } } checkCatchTraces(trace, factory); // visit dom tree in preorder, checking all tmps auto const children = findDomChildren(blocks); StateVector<SSATmp, bool> defined0(&factory, 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; }
bool checkTmpsSpanningCalls(IRTrace* trace, const IRFactory& irFactory) { auto const blocks = rpoSortCfg(trace, irFactory); auto const children = findDomChildren(blocks); // CallBuiltin is ok because it is not a php-level call. (It will // call a C++ helper and we can push/pop around it normally.) auto isCall = [&] (Opcode op) { return op == Call || op == CallArray; }; typedef StateVector<SSATmp,bool> State; bool isValid = true; forPreorderDoms( blocks.front(), children, State(&irFactory, false), [&] (Block* b, State& state) { for (auto& inst : *b) { for (auto& src : inst.srcs()) { if (src->isA(Type::FramePtr)) continue; if (src->isConst()) continue; if (!state[src]) { FTRACE(1, "checkTmpsSpanningCalls failed\n" " instruction: {}\n" " src: {}\n", inst.toString(), src->toString()); isValid = false; } } /* * Php calls kill all live temporaries. We can't keep them * alive across the call because we currently have no * callee-saved registers in our abi, and all translations * share the same spill slots. */ if (isCall(inst.op())) state.reset(); for (auto& d : inst.dsts()) { state[d] = true; } } } ); return isValid; }
bool checkTmpsSpanningCalls(const IRUnit& unit) { auto const blocks = rpoSortCfg(unit); auto const children = findDomChildren(unit, blocks); // CallBuiltin is ok because it is not a php-level call. (It will // call a C++ helper and we can push/pop around it normally.) auto isCall = [&] (Opcode op) { return op == Call || op == CallArray; }; typedef StateVector<SSATmp,bool> State; bool isValid = true; forPreorderDoms( blocks.front(), children, State(unit, false), [&] (Block* b, State& state) { for (auto& inst : *b) { for (auto& src : inst.srcs()) { /* * These SSATmp's are used only for stack analysis in the * simplifier and therefore may live across calls. In particular * these instructions are used to bridge the logical stack of the * caller when a callee is inlined so that analysis does not scan * into the callee stack when searching for a type of value in the * caller. */ if (inst.op() == ReDefSP && src->isA(Type::StkPtr)) continue; if (inst.op() == ReDefGeneratorSP && src->isA(Type::StkPtr)) { continue; } if (src->isA(Type::FramePtr)) continue; if (src->isConst()) continue; if (!state[src]) { auto msg = folly::format("checkTmpsSpanningCalls failed\n" " instruction: {}\n" " src: {}\n", inst.toString(), src->toString()).str(); std::cerr << msg; FTRACE(1, "{}", msg); isValid = false; } } /* * Php calls kill all live temporaries. We can't keep them * alive across the call because we currently have no * callee-saved registers in our abi, and all translations * share the same spill slots. */ if (isCall(inst.op())) state.reset(); for (auto& d : inst.dsts()) { state[d] = true; } } } ); return isValid; }