--- /dev/null
+// 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 <algorithm>
+#include <deque>
+#include <unordered_map>
+#include <unordered_set>
+
+using spv::Block;
+using spv::Id;
+using BlockSet = std::unordered_set<Id>;
+using IdToBool = std::unordered_map<Id, bool>;
+
+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<void(Block*)> callback) {
+ // Prerequisites for a merge block; must be completed prior to visiting the
+ // merge block.
+ std::unordered_map<Id, BlockSet> prereqs;
+ IdToBool visited; // Whether a block has already been visited.
+ std::deque<Block*> 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);
+ }
+ }
+ }
+}
#include "spirv.hpp"
-#include <vector>
+#include <cassert>
+#include <functional>
#include <iostream>
#include <memory>
-#include <assert.h>
+#include <vector>
namespace spv {
virtual ~Block()
{
}
-
+
Id getId() { return instructions.front()->getResultId(); }
Function& getParent() const { return parent; }
const std::vector<Block*> getSuccessors() const { return successors; }
void setUnreachable() { unreachable = true; }
bool isUnreachable() const { return unreachable; }
+ // Returns the block's merge instruction, if one exists (otherwise null).
+ const Instruction* getMergeInstruction() const {
+ if (instructions.size() < 2) return nullptr;
+ const Instruction* nextToLast = *(instructions.cend() - 2);
+ switch (nextToLast->getOpCode()) {
+ case OpSelectionMerge:
+ case OpLoopMerge:
+ return nextToLast;
+ default:
+ return nullptr;
+ }
+ return nullptr;
+ }
bool isTerminated() const
{
bool unreachable;
};
+// Traverses the control-flow graph rooted at root in an order suited for
+// readable code generation. Invokes callback at every node in the traversal
+// order.
+void inReadableOrder(Block* root, std::function<void(Block*)> callback);
+
//
// SPIR-V IR Function.
//
parameterInstructions[p]->dump(out);
// Blocks
- for (int b = 0; b < (int)blocks.size(); ++b)
- blocks[b]->dump(out);
+ inReadableOrder(blocks[0], [&out](const Block* b) { b->dump(out); });
Instruction end(0, 0, OpFunctionEnd);
end.dump(out);
}