[ir] Add Module::DependencyOrderedFunctions()
This returns the list of functions in the module in dependency order,
which is needed by multiple backends in order to generate valid code.
Change-Id: I4c0d9a8ee439ed0272504d3f848cf80738455244
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/187745
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/control_instruction.h b/src/tint/lang/core/ir/control_instruction.h
index 0f98763..60f29e2 100644
--- a/src/tint/lang/core/ir/control_instruction.h
+++ b/src/tint/lang/core/ir/control_instruction.h
@@ -54,6 +54,10 @@
/// @param cb the function to call once for each block
virtual void ForeachBlock(const std::function<void(ir::Block*)>& cb) = 0;
+ /// Calls @p cb for each block owned by this control instruction
+ /// @param cb the function to call once for each block
+ virtual void ForeachBlock(const std::function<void(const ir::Block*)>& cb) const = 0;
+
/// @return All the exits for the flow control instruction
const Hashset<Exit*, 2>& Exits() const { return exits_; }
diff --git a/src/tint/lang/core/ir/if.cc b/src/tint/lang/core/ir/if.cc
index be25a7c..c7a2aad 100644
--- a/src/tint/lang/core/ir/if.cc
+++ b/src/tint/lang/core/ir/if.cc
@@ -63,6 +63,15 @@
}
}
+void If::ForeachBlock(const std::function<void(const ir::Block*)>& cb) const {
+ if (true_) {
+ cb(true_);
+ }
+ if (false_) {
+ cb(false_);
+ }
+}
+
If* If::Clone(CloneContext& ctx) {
auto* cond = ctx.Remap(Condition());
auto* new_true = ctx.ir.blocks.Create<ir::Block>();
diff --git a/src/tint/lang/core/ir/if.h b/src/tint/lang/core/ir/if.h
index 7dfe6d2..3a6e0d7 100644
--- a/src/tint/lang/core/ir/if.h
+++ b/src/tint/lang/core/ir/if.h
@@ -77,6 +77,9 @@
/// @copydoc ControlInstruction::ForeachBlock
void ForeachBlock(const std::function<void(ir::Block*)>& cb) override;
+ /// @copydoc ControlInstruction::ForeachBlock
+ void ForeachBlock(const std::function<void(const ir::Block*)>& cb) const override;
+
/// @returns the if condition
Value* Condition() { return operands_[kConditionOperandOffset]; }
diff --git a/src/tint/lang/core/ir/loop.cc b/src/tint/lang/core/ir/loop.cc
index a2c8e83..fca440f 100644
--- a/src/tint/lang/core/ir/loop.cc
+++ b/src/tint/lang/core/ir/loop.cc
@@ -87,6 +87,18 @@
}
}
+void Loop::ForeachBlock(const std::function<void(const ir::Block*)>& cb) const {
+ if (initializer_) {
+ cb(initializer_);
+ }
+ if (body_) {
+ cb(body_);
+ }
+ if (continuing_) {
+ cb(continuing_);
+ }
+}
+
bool Loop::HasInitializer() const {
return initializer_->Terminator() != nullptr;
}
diff --git a/src/tint/lang/core/ir/loop.h b/src/tint/lang/core/ir/loop.h
index c87760e..edfca17 100644
--- a/src/tint/lang/core/ir/loop.h
+++ b/src/tint/lang/core/ir/loop.h
@@ -88,6 +88,9 @@
/// @copydoc ControlInstruction::ForeachBlock
void ForeachBlock(const std::function<void(ir::Block*)>& cb) override;
+ /// @copydoc ControlInstruction::ForeachBlock
+ void ForeachBlock(const std::function<void(const ir::Block*)>& cb) const override;
+
/// @returns the switch initializer block
ir::Block* Initializer() { return initializer_; }
diff --git a/src/tint/lang/core/ir/module.cc b/src/tint/lang/core/ir/module.cc
index cbc747b..a1be474 100644
--- a/src/tint/lang/core/ir/module.cc
+++ b/src/tint/lang/core/ir/module.cc
@@ -28,11 +28,76 @@
#include "src/tint/lang/core/ir/module.h"
#include <limits>
+#include <utility>
+#include "src/tint/lang/core/ir/control_instruction.h"
+#include "src/tint/lang/core/ir/user_call.h"
+#include "src/tint/utils/containers/unique_vector.h"
#include "src/tint/utils/ice/ice.h"
namespace tint::core::ir {
+namespace {
+
+/// Helper to non-recursively sort a module's function in dependency order.
+struct FunctionSorter {
+ /// The dependency-ordered list of functions.
+ Vector<const Function*, 16> ordered_functions{};
+
+ /// The functions that have been visited and checked for dependencies.
+ Hashset<const Function*, 16> visited{};
+ /// A stack of functions that need to processed and eventually added to the ordered list.
+ Vector<const Function*, 16> function_stack{};
+
+ /// Visit a function and check for dependencies, and eventually add it to the ordered list.
+ /// @param func the function to visit
+ void Visit(const Function* func) {
+ function_stack.Push(func);
+ while (!function_stack.IsEmpty()) {
+ // Visit the next function on the stack, if it hasn't already been visited.
+ auto* current_function = function_stack.Back();
+ if (visited.Add(current_function)) {
+ // Check for dependencies inside the function, adding them to the queue if they have
+ // not already been visited.
+ Visit(current_function->Block());
+ } else {
+ // We previously visited the function, so just discard it.
+ function_stack.Pop();
+ }
+
+ // If the function at the top of the stack has been visited, we know that it has no
+ // unvisited dependencies. We can now add it to the ordered list, and walk back down the
+ // stack until we find the next unvisited function.
+ while (!function_stack.IsEmpty() && visited.Contains(function_stack.Back())) {
+ ordered_functions.Push(function_stack.Pop());
+ }
+ }
+ }
+
+ /// Visit a function body block and look for dependencies.
+ /// @param block the function body to visit
+ void Visit(const Block* block) {
+ Vector<const Block*, 64> block_stack;
+ block_stack.Push(block);
+ while (!block_stack.IsEmpty()) {
+ auto* current_block = block_stack.Pop();
+ for (auto* inst : *current_block) {
+ if (auto* control = inst->As<ControlInstruction>()) {
+ // Enqueue child blocks.
+ control->ForeachBlock([&](const Block* b) { block_stack.Push(b); });
+ } else if (auto* call = inst->As<UserCall>()) {
+ // Enqueue the function that is being called.
+ if (!visited.Contains(call->Target())) {
+ function_stack.Push(call->Target());
+ }
+ }
+ }
+ }
+ }
+};
+
+} // namespace
+
Module::Module() : root_block(blocks.Create<ir::Block>()) {}
Module::Module(Module&&) = default;
@@ -71,4 +136,12 @@
value_to_name_.Remove(value);
}
+Vector<const Function*, 16> Module::DependencyOrderedFunctions() const {
+ FunctionSorter sorter;
+ for (auto& func : functions) {
+ sorter.Visit(func);
+ }
+ return std::move(sorter.ordered_functions);
+}
+
} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index f4a93a1..ebbd0c0 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -129,6 +129,9 @@
return {allocators.values.Objects()};
}
+ /// @returns the functions in the module, in dependency order
+ Vector<const Function*, 16> DependencyOrderedFunctions() const;
+
/// The block allocator
BlockAllocator<Block> blocks;
@@ -144,7 +147,7 @@
BlockAllocator<Value> values;
} allocators;
- /// List of functions in the program
+ /// List of functions in the module.
Vector<ConstPropagatingPtr<Function>, 8> functions;
/// The block containing module level declarations, if any exist.
diff --git a/src/tint/lang/core/ir/module_test.cc b/src/tint/lang/core/ir/module_test.cc
index 6cfa913..b04ae70 100644
--- a/src/tint/lang/core/ir/module_test.cc
+++ b/src/tint/lang/core/ir/module_test.cc
@@ -26,9 +26,13 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/ir/module.h"
+
+#include "gmock/gmock.h"
#include "src/tint/lang/core/ir/ir_helper_test.h"
#include "src/tint/lang/core/ir/var.h"
+using ::testing::ElementsAre;
+
namespace tint::core::ir {
namespace {
@@ -55,5 +59,42 @@
EXPECT_EQ(mod.NameOf(v).Name(), "b");
}
+TEST_F(IR_ModuleTest, DependencyOrderedFunctions) {
+ auto* fa = b.Function("a", ty.void_());
+ auto* fb = b.Function("b", ty.void_());
+ auto* fc = b.Function("c", ty.void_());
+ auto* fd = b.Function("d", ty.void_());
+ b.Append(fa->Block(), [&] { //
+ auto* ifelse = b.If(true);
+ b.Append(ifelse->True(), [&] {
+ b.Call(fc);
+ b.ExitIf(ifelse);
+ });
+ b.Append(ifelse->False(), [&] {
+ b.Call(fb);
+ b.ExitIf(ifelse);
+ });
+ b.Return(fa);
+ });
+ b.Append(fb->Block(), [&] { //
+ b.Call(fc);
+ b.Call(fd);
+ b.Return(fb);
+ });
+ b.Append(fc->Block(), [&] { //
+ b.Call(fd);
+ b.Return(fc);
+ });
+ b.Append(fd->Block(), [&] { //
+ b.Return(fd);
+ });
+ mod.functions.Clear();
+ mod.functions.Push(fa);
+ mod.functions.Push(fd);
+ mod.functions.Push(fb);
+ mod.functions.Push(fc);
+ EXPECT_THAT(mod.DependencyOrderedFunctions(), ElementsAre(fd, fc, fb, fa));
+}
+
} // namespace
} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/switch.cc b/src/tint/lang/core/ir/switch.cc
index a124daa..5ba9abf 100644
--- a/src/tint/lang/core/ir/switch.cc
+++ b/src/tint/lang/core/ir/switch.cc
@@ -53,6 +53,12 @@
}
}
+void Switch::ForeachBlock(const std::function<void(const ir::Block*)>& cb) const {
+ for (auto& c : cases_) {
+ cb(c.block);
+ }
+}
+
Switch* Switch::Clone(CloneContext& ctx) {
auto* cond = ctx.Remap(Condition());
auto* new_switch = ctx.ir.allocators.instructions.Create<Switch>(cond);
diff --git a/src/tint/lang/core/ir/switch.h b/src/tint/lang/core/ir/switch.h
index d0783b8..c1b5f3a 100644
--- a/src/tint/lang/core/ir/switch.h
+++ b/src/tint/lang/core/ir/switch.h
@@ -95,6 +95,9 @@
/// @copydoc ControlInstruction::ForeachBlock
void ForeachBlock(const std::function<void(ir::Block*)>& cb) override;
+ /// @copydoc ControlInstruction::ForeachBlock
+ void ForeachBlock(const std::function<void(const ir::Block*)>& cb) const override;
+
/// @returns the switch cases
Vector<Case, 4>& Cases() { return cases_; }