// The SPIR-V spec requires code blocks to appear in an order satisfying the // dominator-tree direction (ie, dominator before the dominated). This is, // actually, easy to achieve: any pre-order CFG traversal algorithm will do it. // Because such algorithms visit a block only after traversing some path to it // from the root, they necessarily visit the block's idom first. // // But not every graph-traversal algorithm outputs blocks in an order that // appears logical to human readers. The problem is that unrelated branches may // be interspersed with each other, and merge blocks may come before some of the // branches being merged. // // A good, human-readable order of blocks may be achieved by performing // depth-first search but delaying merge nodes until after all their branches // have been visited. This is implemented below by the inReadableOrder() // function. #include "spvIR.h" #include #include #include #include using spv::Block; using spv::Id; using BlockSet = std::unordered_set; using IdToBool = std::unordered_map; namespace { // True if any of prerequisites have not yet been visited. bool delay(const BlockSet& prereqs, const IdToBool& visited) { return std::any_of(prereqs.begin(), prereqs.end(), [&visited](Id b) { return !visited.count(b); }); } } void spv::inReadableOrder(Block* root, std::function callback) { // Prerequisites for a merge block; must be completed prior to visiting the // merge block. std::unordered_map prereqs; IdToBool visited; // Whether a block has already been visited. std::deque worklist; // DFS worklist worklist.push_back(root); while (!worklist.empty()) { Block* current = worklist.front(); worklist.pop_front(); // Nodes may be pushed repeadetly (before they're first visited) if they // have multiple predecessors. Skip the already-visited ones. if (visited[current->getId()]) continue; callback(current); visited[current->getId()] = true; if (auto merge = current->getMergeInstruction()) { auto& mergePrereqs = prereqs[merge->getIdOperand(0)]; // Delay visiting merge blocks until all branches are visited. for (const auto succ : current->getSuccessors()) mergePrereqs.insert(succ->getId()); } for (auto succ : current->getSuccessors()) { if (!visited[succ->getId()] && !delay(prereqs[succ->getId()], visited)) { worklist.push_back(succ); } } } }