From df60b9b729fba56cc2b73b27e878862861916427 Mon Sep 17 00:00:00 2001 From: Philip Salzmann Date: Thu, 5 Dec 2024 16:04:17 +0100 Subject: [PATCH 1/3] Set up graph_query testing infrastructure for task graph --- include/recorders.h | 43 +++++------ src/print_graph.cc | 78 ++++++++++---------- src/recorders.cc | 12 +--- src/task_manager.cc | 3 +- test/graph_test_utils.h | 2 + test/print_graph_tests.cc | 21 +++--- test/task_graph_test_utils.h | 136 +++++++++++++++++++++++++++++++++++ test/test_utils.h | 3 + 8 files changed, 215 insertions(+), 83 deletions(-) create mode 100644 test/task_graph_test_utils.h diff --git a/include/recorders.h b/include/recorders.h index 2b2fd0ca6..ab3827b71 100644 --- a/include/recorders.h +++ b/include/recorders.h @@ -50,19 +50,24 @@ using reduction_list = std::vector; template struct dependency_record { - const IdType node; - const dependency_kind kind; - const dependency_origin origin; + IdType predecessor; + IdType successor; + dependency_kind kind; + dependency_origin origin; + + dependency_record(const IdType predecessor, const IdType successor, const dependency_kind kind, const dependency_origin origin) + : predecessor(predecessor), successor(successor), kind(kind), origin(origin) {} }; // Task recording -using task_dependency_list = std::vector>; +using task_dependency_record = dependency_record; +// TODO: Switch to hierarchy like for CDAG/IDAG struct task_record { task_record(const task& tsk, const buffer_name_map& get_buffer_debug_name); - task_id tid; + task_id id; std::string debug_name; collective_group_id cgid; task_type type; @@ -70,38 +75,26 @@ struct task_record { reduction_list reductions; access_list accesses; detail::side_effect_map side_effect_map; - task_dependency_list dependencies; }; class task_recorder { public: - void record(task_record&& record) { m_recorded_tasks.push_back(std::move(record)); } + void record(std::unique_ptr record) { m_recorded_tasks.push_back(std::move(record)); } - const std::vector& get_tasks() const { return m_recorded_tasks; } + void record_dependency(const task_dependency_record& dependency) { m_recorded_dependencies.push_back(dependency); } - const task_record& get_task(const task_id tid) const { - const auto it = std::find_if(m_recorded_tasks.begin(), m_recorded_tasks.end(), [tid](const task_record& rec) { return rec.tid == tid; }); - assert(it != m_recorded_tasks.end()); - return *it; - } + const std::vector>& get_graph_nodes() const { return m_recorded_tasks; } + + const std::vector& get_dependencies() const { return m_recorded_dependencies; } private: - std::vector m_recorded_tasks; + std::vector> m_recorded_tasks; + std::vector m_recorded_dependencies; }; // Command recording -using command_dependency_list = std::vector>; - -struct command_dependency_record { - command_id predecessor; - command_id successor; - dependency_kind kind; - dependency_origin origin; - - command_dependency_record(const command_id predecessor, const command_id successor, const dependency_kind kind, const dependency_origin origin) - : predecessor(predecessor), successor(successor), kind(kind), origin(origin) {} -}; +using command_dependency_record = dependency_record; struct command_record : matchbox::acceptor { diff --git a/src/print_graph.cc b/src/print_graph.cc index e00237bde..f4bbdfec7 100644 --- a/src/print_graph.cc +++ b/src/print_graph.cc @@ -85,9 +85,42 @@ void format_requirements(std::string& label, const reduction_list& reductions, c } } +template +void print_dependencies( + const std::vector>& dependencies, std::string& dot, + const std::function id_transform = [](IdType id) { return std::to_string(id); }) { + // Sort and deduplicate edges + struct dependency_edge { + IdType predecessor; + IdType successor; + }; + struct dependency_edge_order { + bool operator()(const dependency_edge& lhs, const dependency_edge& rhs) const { + if(lhs.predecessor < rhs.predecessor) return true; + if(lhs.predecessor > rhs.predecessor) return false; + return lhs.successor < rhs.successor; + } + }; + struct dependency_kind_order { + bool operator()(const std::pair& lhs, const std::pair& rhs) const { + return (lhs.first == dependency_kind::true_dep && rhs.first != dependency_kind::true_dep); + } + }; + std::map, dependency_kind_order>, dependency_edge_order> + dependencies_by_edge; // ordered and unique + for(const auto& dep : dependencies) { + dependencies_by_edge[{dep.predecessor, dep.successor}].insert(std::pair{dep.kind, dep.origin}); + } + for(const auto& [edge, meta] : dependencies_by_edge) { + // If there's at most two edges, take the first one (likely a true dependency followed by an anti-dependency). If there's more, bail (don't style). + const auto style = meta.size() <= 2 ? dependency_style(meta.begin()->first, meta.begin()->second) : std::string{}; + fmt::format_to(std::back_inserter(dot), "{}->{}[{}];", id_transform(edge.predecessor), id_transform(edge.successor), style); + } +} + std::string get_task_label(const task_record& tsk) { std::string label; - fmt::format_to(std::back_inserter(label), "T{}", tsk.tid); + fmt::format_to(std::back_inserter(label), "T{}", tsk.id); if(!tsk.debug_name.empty()) { fmt::format_to(std::back_inserter(label), " \"{}\"", utils::escape_for_dot_label(tsk.debug_name)); } fmt::format_to(std::back_inserter(label), "
{}", task_type_string(tsk.type)); @@ -107,16 +140,15 @@ std::string make_graph_preamble(const std::string& title) { return fmt::format(" std::string print_task_graph(const task_recorder& recorder, const std::string& title) { std::string dot = make_graph_preamble(title); - CELERITY_DEBUG("print_task_graph, {} entries", recorder.get_tasks().size()); + CELERITY_DEBUG("print_task_graph, {} entries", recorder.get_graph_nodes().size()); - for(const auto& tsk : recorder.get_tasks()) { - const char* shape = tsk.type == task_type::epoch || tsk.type == task_type::horizon ? "ellipse" : "box style=rounded"; - fmt::format_to(std::back_inserter(dot), "{}[shape={} label=<{}>];", tsk.tid, shape, get_task_label(tsk)); - for(auto d : tsk.dependencies) { - fmt::format_to(std::back_inserter(dot), "{}->{}[{}];", d.node, tsk.tid, dependency_style(d.kind, d.origin)); - } + for(const auto& tsk : recorder.get_graph_nodes()) { + const char* shape = tsk->type == task_type::epoch || tsk->type == task_type::horizon ? "ellipse" : "box style=rounded"; + fmt::format_to(std::back_inserter(dot), "{}[shape={} label=<{}>];", tsk->id, shape, get_task_label(*tsk)); } + print_dependencies(recorder.get_dependencies(), dot); + dot += "}"; return dot; } @@ -135,7 +167,7 @@ std::string print_command_graph(const node_id local_nid, const command_recorder& std::string main_dot; std::map task_subgraph_dot; // this map must be ordered! - const auto local_to_global_id = [local_nid](uint64_t id) { + const auto local_to_global_id = [local_nid](auto id) -> std::string { // IDs in the DOT language may not start with a digit (unless the whole thing is a numeral) return fmt::format("id_{}_{}", local_nid, id); }; @@ -241,33 +273,7 @@ std::string print_command_graph(const node_id local_nid, const command_recorder& }); }; - // Sort and deduplicate edges - struct dependency_edge { - command_id predecessor; - command_id successor; - }; - struct dependency_edge_order { - bool operator()(const dependency_edge& lhs, const dependency_edge& rhs) const { - if(lhs.predecessor < rhs.predecessor) return true; - if(lhs.predecessor > rhs.predecessor) return false; - return lhs.successor < rhs.successor; - } - }; - struct dependency_kind_order { - bool operator()(const std::pair& lhs, const std::pair& rhs) const { - return (lhs.first == dependency_kind::true_dep && rhs.first != dependency_kind::true_dep); - } - }; - std::map, dependency_kind_order>, dependency_edge_order> - dependencies_by_edge; // ordered and unique - for(const auto& dep : recorder.get_dependencies()) { - dependencies_by_edge[{dep.predecessor, dep.successor}].insert(std::pair{dep.kind, dep.origin}); - } - for(const auto& [edge, meta] : dependencies_by_edge) { - // If there's at most two edges, take the first one (likely a true dependency followed by an anti-dependency). If there's more, bail (don't style). - const auto style = meta.size() <= 2 ? dependency_style(meta.begin()->first, meta.begin()->second) : std::string{}; - fmt::format_to(std::back_inserter(main_dot), "{}->{}[{}];", local_to_global_id(edge.predecessor), local_to_global_id(edge.successor), style); - } + print_dependencies(recorder.get_dependencies(), main_dot, local_to_global_id); std::string result_dot = make_graph_preamble(title); for(auto& [_, sg_dot] : task_subgraph_dot) { diff --git a/src/recorders.cc b/src/recorders.cc index 8aaf852bf..356246b05 100644 --- a/src/recorders.cc +++ b/src/recorders.cc @@ -39,18 +39,10 @@ reduction_list build_reduction_list(const task& tsk, const buffer_name_map& get_ return ret; } -task_dependency_list build_task_dependency_list(const task& tsk) { - task_dependency_list ret; - for(const auto& dep : tsk.get_dependencies()) { - ret.push_back({dep.node->get_id(), dep.kind, dep.origin}); - } - return ret; -} - task_record::task_record(const task& tsk, const buffer_name_map& get_buffer_debug_name) - : tid(tsk.get_id()), debug_name(tsk.get_debug_name()), cgid(tsk.get_collective_group_id()), type(tsk.get_type()), geometry(tsk.get_geometry()), + : id(tsk.get_id()), debug_name(tsk.get_debug_name()), cgid(tsk.get_collective_group_id()), type(tsk.get_type()), geometry(tsk.get_geometry()), reductions(build_reduction_list(tsk, get_buffer_debug_name)), accesses(build_access_list(tsk, get_buffer_debug_name)), - side_effect_map(tsk.get_side_effect_map()), dependencies(build_task_dependency_list(tsk)) {} + side_effect_map(tsk.get_side_effect_map()) {} // Commands diff --git a/src/task_manager.cc b/src/task_manager.cc index 5cf4e5dc0..6346465b1 100644 --- a/src/task_manager.cc +++ b/src/task_manager.cc @@ -189,7 +189,7 @@ namespace detail { void task_manager::invoke_callbacks(const task* tsk) const { if(m_delegate != nullptr) { m_delegate->task_created(tsk); } if(m_task_recorder != nullptr) { - m_task_recorder->record(task_record(*tsk, [this](const buffer_id bid) { return m_buffers.at(bid).debug_name; })); + m_task_recorder->record(std::make_unique(*tsk, [this](const buffer_id bid) { return m_buffers.at(bid).debug_name; })); } } @@ -198,6 +198,7 @@ namespace detail { depender.add_dependency({&dependee, kind, origin}); m_execution_front.erase(&dependee); m_max_pseudo_critical_path_length = std::max(m_max_pseudo_critical_path_length, depender.get_pseudo_critical_path_length()); + if(m_task_recorder != nullptr) { m_task_recorder->record_dependency({dependee.get_id(), depender.get_id(), kind, origin}); } } bool task_manager::need_new_horizon() const { diff --git a/test/graph_test_utils.h b/test/graph_test_utils.h index 2175214f0..58ba28303 100644 --- a/test/graph_test_utils.h +++ b/test/graph_test_utils.h @@ -11,12 +11,14 @@ using namespace celerity::detail; namespace celerity::test_utils { +class tdag_test_context; class cdag_test_context; class idag_test_context; class scheduler_test_context; template class task_builder { + friend class tdag_test_context; friend class cdag_test_context; friend class idag_test_context; friend class scheduler_test_context; diff --git a/test/print_graph_tests.cc b/test/print_graph_tests.cc index a3603a688..9d6aecb7d 100644 --- a/test/print_graph_tests.cc +++ b/test/print_graph_tests.cc @@ -37,11 +37,10 @@ TEST_CASE("task-graph printing is unchanged", "[print_graph][task-graph]") { // replace the `expected` value with the new dot graph. const std::string expected = "digraph G{label=;pad=0.2;0[shape=ellipse label=epoch>];1[shape=box style=rounded label=device-compute " - "[0,0,0] + [64,1,1]
discard_write B1 {[0,0,0] - [1,1,1]}>];0->1[color=orchid];2[shape=box style=rounded label=device-compute " - "[0,0,0] + [64,1,1]
discard_write B0 {[0,0,0] - [64,1,1]}>];0->2[color=orchid];3[shape=box style=rounded " - "label=device-compute [0,0,0] + [64,1,1]
(R1) read_write B1 {[0,0,0] - [1,1,1]}
read B0 {[0,0,0] - " - "[64,1,1]}>];1->3[];2->3[];4[shape=box style=rounded label=device-compute [0,0,0] + [64,1,1]
read B1 {[0,0,0] - " - "[1,1,1]}>];3->4[];}"; + "[0,0,0] + [64,1,1]
discard_write B1 {[0,0,0] - [1,1,1]}>];2[shape=box style=rounded label=device-compute [0,0,0] + " + "[64,1,1]
discard_write B0 {[0,0,0] - [64,1,1]}>];3[shape=box style=rounded label=device-compute [0,0,0] + [64,1,1]
(R1) " + "read_write B1 {[0,0,0] - [1,1,1]}
read B0 {[0,0,0] - [64,1,1]}>];4[shape=box style=rounded label=device-compute " + "[0,0,0] + [64,1,1]
read B1 {[0,0,0] - [1,1,1]}>];0->1[color=orchid];0->2[color=orchid];1->3[];2->3[];3->4[];}"; const auto dot = print_task_graph(tt.trec); CHECK(dot == expected); @@ -316,12 +315,12 @@ TEST_CASE_METHOD(test_utils::runtime_fixture, "full graph is printed if CELERITY SECTION("task graph") { const auto* expected = "digraph G{label=;pad=0.2;0[shape=ellipse label=epoch>];1[shape=box style=rounded label=host-compute " - "[0,0,0] + [16,1,1]
read_write B0 {[0,0,0] - [16,1,1]}>];0->1[];2[shape=ellipse " - "label=horizon>];1->2[color=orange];3[shape=box style=rounded label=host-compute [0,0,0] + " - "[16,1,1]
read_write B0 {[0,0,0] - [16,1,1]}>];1->3[];4[shape=ellipse " - "label=horizon>];3->4[color=orange];2->4[color=orange];5[shape=box style=rounded label=host-compute [0,0,0] + " - "[16,1,1]
read_write B0 {[0,0,0] - [16,1,1]}>];3->5[];6[shape=ellipse " - "label=horizon>];5->6[color=orange];4->6[color=orange];7[shape=ellipse label=epoch>];6->7[color=orange];}"; + "[0,0,0] + [16,1,1]
read_write B0 {[0,0,0] - [16,1,1]}>];2[shape=ellipse label=horizon>];3[shape=box style=rounded " + "label=host-compute [0,0,0] + [16,1,1]
read_write B0 {[0,0,0] - [16,1,1]}>];4[shape=ellipse " + "label=horizon>];5[shape=box style=rounded label=host-compute [0,0,0] + [16,1,1]
read_write B0 {[0,0,0] - " + "[16,1,1]}>];6[shape=ellipse label=horizon>];7[shape=ellipse " + "label=epoch>];0->1[];1->2[color=orange];1->3[];2->4[color=orange];3->4[color=orange];3->5[];4->6[color=orange];5->6[color=orange];6->7[color=orange];}"; const auto dot = runtime_testspy::print_task_graph(celerity::detail::runtime::get_instance()); CHECK(dot == expected); diff --git a/test/task_graph_test_utils.h b/test/task_graph_test_utils.h new file mode 100644 index 000000000..69662c3b7 --- /dev/null +++ b/test/task_graph_test_utils.h @@ -0,0 +1,136 @@ +#pragma once + +#include "graph_test_utils.h" + + +namespace celerity::test_utils { + +template +struct task_matcher { + static bool matches(const Record& tsk, const task_id tid) { return tsk.id == tid; } + + static std::string print_filter(const task_id tid) { return fmt::format("\"T{}\"", tid); } +}; + +template +using task_query = graph_query; + +// TODO: Can we make this the base class of cdag / idag test contexts? +class tdag_test_context final : private task_manager::delegate { + friend class task_builder; + + public: + struct policy_set { + task_manager::policy_set tm; + }; + + tdag_test_context(const size_t num_collective_nodes, const policy_set& policy = {}) + : m_tm(num_collective_nodes, m_tdag, &m_task_recorder, static_cast(this), policy.tm) { + m_initial_epoch_tid = m_tm.generate_epoch_task(epoch_action::init); + } + + ~tdag_test_context() { maybe_print_graphs(); } + + tdag_test_context(const tdag_test_context&) = delete; + tdag_test_context(tdag_test_context&&) = delete; + tdag_test_context& operator=(const tdag_test_context&) = delete; + tdag_test_context& operator=(tdag_test_context&&) = delete; + + void task_created(const task* tsk) override {} + + template + test_utils::mock_buffer create_buffer(range size, bool mark_as_host_initialized = false) { + const buffer_id bid = m_next_buffer_id++; + const auto buf = test_utils::mock_buffer(bid, size); + m_tm.notify_buffer_created(bid, range_cast<3>(size), mark_as_host_initialized); + return buf; + } + + test_utils::mock_host_object create_host_object(const bool owns_instance = true) { + const host_object_id hoid = m_next_host_object_id++; + m_tm.notify_host_object_created(hoid); + return test_utils::mock_host_object(hoid); + } + + template + auto device_compute(const range& global_size, const id& global_offset = {}) { + return task_builder(*this).template device_compute(global_size, global_offset); + } + + template + auto device_compute(const nd_range& execution_range) { + return task_builder(*this).template device_compute(execution_range); + } + + template + auto host_task(const range& global_size) { + return task_builder(*this).host_task(global_size); + } + + auto master_node_host_task() { return task_builder(*this).master_node_host_task(); } + + auto collective_host_task(experimental::collective_group group = detail::default_collective_group) { + return task_builder(*this).collective_host_task(group); + } + + task_id fence(test_utils::mock_host_object ho) { + host_object_effect effect{ho.get_id(), experimental::side_effect_order::sequential}; + return m_tm.generate_fence_task(effect, nullptr); + } + + template + task_id fence(test_utils::mock_buffer buf, subrange sr) { + buffer_access access{buf.get_id(), access_mode::read, + std::make_unique>>(celerity::access::fixed(sr), buf.get_range())}; + return m_tm.generate_fence_task(std::move(access), nullptr); + } + + template + task_id fence(test_utils::mock_buffer buf) { + return fence(buf, {{}, buf.get_range()}); + } + + task_id epoch(epoch_action action) { return m_tm.generate_epoch_task(action); } + + // TODO: This is of limited usefulness until we convert task records into a hierarchy + template + task_query query_tasks(Filters... filters) { + return task_query(m_task_recorder).template select_all(std::forward(filters)...); + } + + void set_horizon_step(const int step) { m_tm.set_horizon_step(step); } + + task_graph& get_task_graph() { return m_tdag; } + + task_manager& get_task_manager() { return m_tm; } + + const task_recorder& get_task_recorder() const { return m_task_recorder; } + + task_id get_initial_epoch_task() const { return m_initial_epoch_tid; } + + [[nodiscard]] std::string print_task_graph() { return detail::print_task_graph(m_task_recorder, make_test_graph_title("Task Graph")); } + + private: + buffer_id m_next_buffer_id = 0; + host_object_id m_next_host_object_id = 0; + reduction_id m_next_reduction_id = 1; // Start from 1 as rid 0 designates "no reduction" in push commands + task_graph m_tdag; + task_manager m_tm; + task_recorder m_task_recorder; + task_id m_initial_epoch_tid = 0; + + reduction_info create_reduction(const buffer_id bid, const bool include_current_buffer_value) { + return reduction_info{m_next_reduction_id++, bid, include_current_buffer_value}; + } + + template + task_id submit_command_group(CGF cgf) { + return m_tm.generate_command_group_task(invoke_command_group_function(cgf)); + } + + void maybe_print_graphs() { + if(test_utils::g_print_graphs) { fmt::print("\n{}\n", print_task_graph()); } + } +}; + +} // namespace celerity::test_utils diff --git a/test/test_utils.h b/test/test_utils.h index 18a012e68..cb024d249 100644 --- a/test/test_utils.h +++ b/test/test_utils.h @@ -245,6 +245,7 @@ namespace test_utils { private: friend class mock_buffer_factory; + friend class tdag_test_context; friend class cdag_test_context; friend class idag_test_context; friend class scheduler_test_context; @@ -263,6 +264,7 @@ namespace test_utils { private: friend class mock_host_object_factory; + friend class tdag_test_context; friend class cdag_test_context; friend class idag_test_context; friend class scheduler_test_context; @@ -449,6 +451,7 @@ namespace test_utils { std::string make_test_graph_title(const std::string& type, size_t num_nodes, detail::node_id local_nid); std::string make_test_graph_title(const std::string& type, size_t num_nodes, detail::node_id local_nid, size_t num_devices_per_node); + // DEPRECATED: Use tdag_test_context in task_graph_test_utils.h instead struct task_test_context { detail::task_graph tdag; detail::task_recorder trec; From 2230e24217707de7261aeaf569c6c130b57c8c2e Mon Sep 17 00:00:00 2001 From: Philip Salzmann Date: Mon, 23 Dec 2024 14:16:53 +0100 Subject: [PATCH 2/3] Convert all task graph tests to builder pattern + query --- test/accessor_tests.cc | 38 +- test/debug_naming_tests.cc | 31 +- test/graph_test_utils.h | 19 +- test/print_graph_tests.cc | 35 +- test/task_graph_tests.cc | 725 +++++++++++++++---------------------- test/test_utils.cc | 4 - test/test_utils.h | 103 ------ 7 files changed, 351 insertions(+), 604 deletions(-) diff --git a/test/accessor_tests.cc b/test/accessor_tests.cc index ea738bc0c..0a138117e 100644 --- a/test/accessor_tests.cc +++ b/test/accessor_tests.cc @@ -6,6 +6,7 @@ #include +#include "task_graph_test_utils.h" #include "test_utils.h" namespace celerity { @@ -230,34 +231,15 @@ namespace detail { } TEST_CASE("conflicts between producer-accessors and reductions are reported", "[task-manager]") { - test_utils::task_test_context tt; - - auto buf_0 = tt.mbf.create_buffer(range<1>{1}); - - CHECK_THROWS(test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - test_utils::add_reduction(cgh, tt.mrf, buf_0, false); - test_utils::add_reduction(cgh, tt.mrf, buf_0, false); - })); - - CHECK_THROWS(test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - test_utils::add_reduction(cgh, tt.mrf, buf_0, false); - buf_0.get_access(cgh, fixed<1>({0, 1})); - })); - - CHECK_THROWS(test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - test_utils::add_reduction(cgh, tt.mrf, buf_0, false); - buf_0.get_access(cgh, fixed<1>({0, 1})); - })); - - CHECK_THROWS(test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - test_utils::add_reduction(cgh, tt.mrf, buf_0, false); - buf_0.get_access(cgh, fixed<1>({0, 1})); - })); - - CHECK_THROWS(test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - test_utils::add_reduction(cgh, tt.mrf, buf_0, false); - buf_0.get_access(cgh, fixed<1>({0, 1})); - })); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + + auto buf_0 = tctx.create_buffer(range<1>{1}); + + CHECK_THROWS(tctx.device_compute(range<1>{ones}).reduce(buf_0, false).reduce(buf_0, false).submit()); + CHECK_THROWS(tctx.device_compute(range<1>{ones}).reduce(buf_0, false).read(buf_0, all{}).submit()); + CHECK_THROWS(tctx.device_compute(range<1>{ones}).reduce(buf_0, false).write(buf_0, all{}).submit()); + CHECK_THROWS(tctx.device_compute(range<1>{ones}).reduce(buf_0, false).read_write(buf_0, all{}).submit()); + CHECK_THROWS(tctx.device_compute(range<1>{ones}).reduce(buf_0, false).discard_write(buf_0, all{}).submit()); } template diff --git a/test/debug_naming_tests.cc b/test/debug_naming_tests.cc index d5a053e66..3433e1afd 100644 --- a/test/debug_naming_tests.cc +++ b/test/debug_naming_tests.cc @@ -7,6 +7,7 @@ #include +#include "task_graph_test_utils.h" #include "test_utils.h" using namespace celerity; @@ -15,34 +16,30 @@ using namespace celerity::detail; TEST_CASE("debug names can be set and retrieved from tasks", "[debug]") { const std::string task_name = "sample task"; - auto tt = test_utils::task_test_context{}; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); SECTION("Host Task") { - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { celerity::debug::set_task_name(cgh, task_name); }); + const auto tid_a = tctx.master_node_host_task().name(task_name).submit(); + const auto tid_b = tctx.master_node_host_task().submit(); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) {}); - - CHECK(test_utils::get_task(tt.tdag, tid_a)->get_debug_name() == task_name); - CHECK(test_utils::get_task(tt.tdag, tid_b)->get_debug_name().empty()); + CHECK(test_utils::get_task(tctx.get_task_graph(), tid_a)->get_debug_name() == task_name); + CHECK(test_utils::get_task(tctx.get_task_graph(), tid_b)->get_debug_name().empty()); } SECTION("Compute Task") { - const auto tid_a = test_utils::add_compute_task(tt.tm, [&](handler& cgh) { celerity::debug::set_task_name(cgh, task_name); }); - - const auto tid_b = test_utils::add_compute_task(tt.tm, [&](handler& cgh) {}); + const auto tid_a = tctx.device_compute(range<1>(ones)).name(task_name).submit(); + const auto tid_b = tctx.device_compute(range<1>(ones)).submit(); - CHECK(test_utils::get_task(tt.tdag, tid_a)->get_debug_name() == task_name); - CHECK_THAT(test_utils::get_task(tt.tdag, tid_b)->get_debug_name(), Catch::Matchers::ContainsSubstring("compute_task_unnamed")); + CHECK(test_utils::get_task(tctx.get_task_graph(), tid_a)->get_debug_name() == task_name); + CHECK_THAT(test_utils::get_task(tctx.get_task_graph(), tid_b)->get_debug_name(), Catch::Matchers::ContainsSubstring("compute_task_unnamed")); } SECTION("ND Range Task") { - const auto tid_a = - test_utils::add_nd_range_compute_task(tt.tm, [&](handler& cgh) { celerity::debug::set_task_name(cgh, task_name); }); - - const auto tid_b = test_utils::add_compute_task(tt.tm, [&](handler& cgh) {}); + const auto tid_a = tctx.device_compute(nd_range<1>{range<1>{1}, range<1>{1}}).name(task_name).submit(); + const auto tid_b = tctx.device_compute(nd_range<1>{range<1>{1}, range<1>{1}}).submit(); - CHECK(test_utils::get_task(tt.tdag, tid_a)->get_debug_name() == task_name); - CHECK_THAT(test_utils::get_task(tt.tdag, tid_b)->get_debug_name(), Catch::Matchers::ContainsSubstring("nd_range_task_unnamed")); + CHECK(test_utils::get_task(tctx.get_task_graph(), tid_a)->get_debug_name() == task_name); + CHECK_THAT(test_utils::get_task(tctx.get_task_graph(), tid_b)->get_debug_name(), Catch::Matchers::ContainsSubstring("nd_range_task_unnamed")); } } diff --git a/test/graph_test_utils.h b/test/graph_test_utils.h index 58ba28303..16f257f54 100644 --- a/test/graph_test_utils.h +++ b/test/graph_test_utils.h @@ -28,7 +28,7 @@ class task_builder { class step { public: step(TestContext& tctx, action command, std::vector requirements = {}) - : m_tctx(tctx), m_command(std::move(command)), m_requirements(std::move(requirements)), m_uncaught_exceptions_before(std::uncaught_exceptions()) {} + : m_tctx(&tctx), m_command(std::move(command)), m_requirements(std::move(requirements)), m_uncaught_exceptions_before(std::uncaught_exceptions()) {} ~step() noexcept(false) { // NOLINT(bugprone-exception-escape) if(std::uncaught_exceptions() == m_uncaught_exceptions_before && (m_command || !m_requirements.empty())) { @@ -37,13 +37,13 @@ class task_builder { } step(const step&) = delete; - step(step&&) = delete; + step(step&&) = default; step& operator=(const step&) = delete; - step& operator=(step&&) = delete; + step& operator=(step&&) = default; task_id submit() { assert(m_command); - const auto tid = m_tctx.submit_command_group([this](handler& cgh) { + const auto tid = m_tctx->submit_command_group([this](handler& cgh) { for(auto& a : m_requirements) { a(cgh); } @@ -78,10 +78,15 @@ class task_builder { return chain([&buf, rmfn](handler& cgh) { buf.template get_access(cgh, rmfn); }); } + template + step discard_read_write(BufferT& buf, RangeMapper rmfn) { + return chain([&buf, rmfn](handler& cgh) { buf.template get_access(cgh, rmfn); }); + } + template inline step reduce(BufferT& buf, const bool include_current_buffer_value) { return chain([this, &buf, include_current_buffer_value]( - handler& cgh) { add_reduction(cgh, m_tctx.create_reduction(buf.get_id(), include_current_buffer_value)); }); + handler& cgh) { add_reduction(cgh, m_tctx->create_reduction(buf.get_id(), include_current_buffer_value)); }); } template @@ -107,7 +112,7 @@ class task_builder { } private: - TestContext& m_tctx; + TestContext* m_tctx; action m_command; std::vector m_requirements; int m_uncaught_exceptions_before; @@ -121,7 +126,7 @@ class task_builder { auto command = std::move(m_command); m_requirements = {}; m_command = {}; - return StepT{m_tctx, std::move(command), std::move(requirements)}; + return StepT{*m_tctx, std::move(command), std::move(requirements)}; } }; diff --git a/test/print_graph_tests.cc b/test/print_graph_tests.cc index 9d6aecb7d..d7ecea67b 100644 --- a/test/print_graph_tests.cc +++ b/test/print_graph_tests.cc @@ -5,6 +5,7 @@ #include "command_graph_generator_test_utils.h" #include "instruction_graph_test_utils.h" +#include "task_graph_test_utils.h" #include "test_utils.h" using namespace celerity; @@ -14,24 +15,18 @@ using namespace celerity::test_utils; namespace acc = celerity::access; TEST_CASE("task-graph printing is unchanged", "[print_graph][task-graph]") { - auto tt = test_utils::task_test_context{}; + tdag_test_context tctx(1 /* num_collective_nodes */); - auto range = celerity::range<1>(64); - auto buf_0 = tt.mbf.create_buffer(range); - auto buf_1 = tt.mbf.create_buffer(celerity::range<1>(1)); + const auto range = celerity::range<1>(64); + auto buf_0 = tctx.create_buffer(range); + auto buf_1 = tctx.create_buffer(celerity::range<1>(1)); // graph copied from graph_gen_reduction_tests "command_graph_generator generates reduction command trees" - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_1.get_access(cgh, acc::one_to_one{}); }, range); - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_0.get_access(cgh, acc::one_to_one{}); }, range); - test_utils::add_compute_task( - tt.tm, - [&](handler& cgh) { - buf_0.get_access(cgh, acc::one_to_one{}); - test_utils::add_reduction(cgh, tt.mrf, buf_1, true /* include_current_buffer_value */); - }, - range); - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_1.get_access(cgh, acc::fixed<1>({0, 1})); }, range); + tctx.device_compute(range).discard_write(buf_1, acc::one_to_one{}).submit(); + tctx.device_compute(range).discard_write(buf_0, acc::one_to_one{}).submit(); + tctx.device_compute(range).read(buf_0, acc::one_to_one{}).reduce(buf_1, true /* include_current_buffer_value */).submit(); + tctx.device_compute(range).read(buf_1, acc::fixed<1>({0, 1})).submit(); // Smoke test: It is valid for the dot output to change with updates to graph generation. If this test fails, verify that the printed graph is sane and // replace the `expected` value with the new dot graph. @@ -42,7 +37,7 @@ TEST_CASE("task-graph printing is unchanged", "[print_graph][task-graph]") { "read_write B1 {[0,0,0] - [1,1,1]}
read B0 {[0,0,0] - [64,1,1]}>];4[shape=box style=rounded label=device-compute " "[0,0,0] + [64,1,1]
read B1 {[0,0,0] - [1,1,1]}>];0->1[color=orchid];0->2[color=orchid];1->3[];2->3[];3->4[];}"; - const auto dot = print_task_graph(tt.trec); + const auto dot = print_task_graph(tctx.get_task_recorder()); CHECK(dot == expected); if(dot != expected) { fmt::print("\n{}:\n\ngot:\n\n{}\n\nexpected:\n\n{}\n\n", Catch::getResultCapture().getCurrentTestName(), dot, expected); } } @@ -358,13 +353,13 @@ template class name_class {}; TEST_CASE("task-graph names are escaped", "[print_graph][task-graph][task-name]") { - auto tt = test_utils::task_test_context{}; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); - auto range = celerity::range<1>(64); - auto buf = tt.mbf.create_buffer(range); + const auto range = celerity::range<1>(64); + auto buf = tctx.create_buffer(range); - test_utils::add_compute_task>(tt.tm, [&](handler& cgh) { buf.get_access(cgh, acc::one_to_one{}); }, range); + tctx.device_compute>(range).discard_write(buf, acc::one_to_one{}).submit(); const auto* escaped_name = "\"name_class<...>\""; - REQUIRE_THAT(print_task_graph(tt.trec), Catch::Matchers::ContainsSubstring(escaped_name)); + REQUIRE_THAT(print_task_graph(tctx.get_task_recorder()), Catch::Matchers::ContainsSubstring(escaped_name)); } diff --git a/test/task_graph_tests.cc b/test/task_graph_tests.cc index 738811346..7cc58717e 100644 --- a/test/task_graph_tests.cc +++ b/test/task_graph_tests.cc @@ -1,16 +1,16 @@ #include "cgf.h" #include "task.h" +#include "task_graph_test_utils.h" #include "task_manager.h" +#include "test_utils.h" #include "types.h" -#include -#include -#include - #include #include -#include "test_utils.h" +#include +#include +#include namespace celerity { @@ -20,6 +20,22 @@ namespace detail { using celerity::access::fixed; using celerity::access::one_to_one; + /// Returns true if all recorded dependencies between the given tasks match the given kind and origin. + bool all_dependencies_match(const task_recorder& recorder, const task_id predecessor, const task_id successor, const dependency_kind kind, + const dependency_origin origin = dependency_origin::dataflow) { + const auto& deps = recorder.get_dependencies(); + return std::all_of(deps.begin(), deps.end(), + [&](const auto& dep) { return (dep.predecessor != predecessor || dep.successor != successor) || (dep.kind == kind && dep.origin == origin); }); + } + + /// Returns true if at least one recorded dependency between the given tasks matches the given kind and origin. + bool some_dependencies_match(const task_recorder& recorder, const task_id predecessor, const task_id successor, const dependency_kind kind, + const dependency_origin origin = dependency_origin::dataflow) { + const auto& deps = recorder.get_dependencies(); + return std::any_of(deps.begin(), deps.end(), + [&](const auto& dep) { return dep.predecessor == predecessor && dep.successor == successor && dep.kind == kind && dep.origin == origin; }); + } + TEST_CASE("task_manager calls into delegate on task creation", "[task_manager]") { struct counter_delegate final : public task_manager::delegate { size_t counter = 0; @@ -40,18 +56,14 @@ namespace detail { } TEST_CASE("task_manager correctly records compute task information", "[task_manager][task][device_compute_task]") { - test_utils::task_test_context tt; - auto buf_a = tt.mbf.create_buffer(range<2>(64, 152), true /* host_initialized */); - auto buf_b = tt.mbf.create_buffer(range<3>(7, 21, 99)); - const auto tid = test_utils::add_compute_task( - tt.tm, - [&](handler& cgh) { - buf_a.get_access(cgh, one_to_one{}); - buf_b.get_access(cgh, fixed{subrange<3>{{}, {5, 18, 74}}}); - }, - range<2>{32, 128}, id<2>{32, 24}); - - const auto tsk = test_utils::get_task(tt.tdag, tid); + test_utils::tdag_test_context tctx(1 /* num collective nodes */); + + auto buf_a = tctx.create_buffer(range<2>(64, 152), true /* host_initialized */); + auto buf_b = tctx.create_buffer(range<3>(7, 21, 99)); + const auto tid = + tctx.device_compute(range<2>(32, 128), id<2>(32, 24)).read(buf_a, one_to_one{}).discard_write(buf_b, fixed{subrange<3>{{}, {5, 18, 74}}}).submit(); + + const auto tsk = test_utils::get_task(tctx.get_task_graph(), tid); CHECK(tsk->get_type() == task_type::device_compute); CHECK(tsk->get_dimensions() == 2); CHECK(tsk->get_global_size() == range<3>{32, 128, 1}); @@ -60,10 +72,10 @@ namespace detail { auto& bam = tsk->get_buffer_access_map(); const auto bufs = bam.get_accessed_buffers(); CHECK(bufs.size() == 2); - CHECK(std::find(bufs.cbegin(), bufs.cend(), buf_a.get_id()) != bufs.cend()); - CHECK(std::find(bufs.cbegin(), bufs.cend(), buf_b.get_id()) != bufs.cend()); + CHECK(bufs.contains(buf_a.get_id())); + CHECK(bufs.contains(buf_b.get_id())); CHECK(bam.get_nth_access(0) == std::pair{buf_a.get_id(), access_mode::read}); - CHECK(bam.get_nth_access(1) == std::pair{buf_b.get_id(), access_mode::discard_read_write}); + CHECK(bam.get_nth_access(1) == std::pair{buf_b.get_id(), access_mode::discard_write}); const auto reqs_a = bam.compute_consumed_region(buf_a.get_id(), subrange{tsk->get_global_offset(), tsk->get_global_size()}); CHECK(reqs_a == box(subrange<3>({32, 24, 0}, {32, 128, 1}))); const auto reqs_b = bam.compute_produced_region(buf_b.get_id(), subrange{tsk->get_global_offset(), tsk->get_global_size()}); @@ -85,115 +97,39 @@ namespace detail { CHECK(req == box<3>()); } - TEST_CASE("task_manager does not create multiple dependencies between the same tasks", "[task_manager][task-graph]") { - auto tt = test_utils::task_test_context{}; - auto buf_a = tt.mbf.create_buffer(range<1>(128)); - auto buf_b = tt.mbf.create_buffer(range<1>(128)); - - SECTION("true dependencies") { - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a)); - - const auto its = test_utils::get_task(tt.tdag, tid_a)->get_dependents(); - REQUIRE(std::distance(its.begin(), its.end()) == 1); - } - - SECTION("anti-dependencies") { - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a, dependency_kind::anti_dep)); - - const auto its = test_utils::get_task(tt.tdag, tid_a)->get_dependents(); - REQUIRE(std::distance(its.begin(), its.end()) == 1); - } - - // Here we also check that true dependencies always take precedence - SECTION("true and anti-dependencies combined") { - SECTION("if true is declared first") { - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a)); - CHECK_FALSE(test_utils::has_dependency(tt.tdag, tid_b, tid_a, dependency_kind::anti_dep)); - - const auto its = test_utils::get_task(tt.tdag, tid_a)->get_dependents(); - REQUIRE(std::distance(its.begin(), its.end()) == 1); - } + TEST_CASE("task_manager respects range mapper results for finding dependencies", "[task_manager][task-graph]") { + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf = tctx.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - SECTION("if anti is declared first") { - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 128})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a)); - CHECK_FALSE(test_utils::has_dependency(tt.tdag, tid_b, tid_a, dependency_kind::anti_dep)); - - const auto its = test_utils::get_task(tt.tdag, tid_a)->get_dependents(); - REQUIRE(std::distance(its.begin(), its.end()) == 1); - } - } - } + const auto tid_a = tctx.device_compute(range<1>(ones)).discard_write(buf, fixed<1>{{0, 64}}).submit(); + const auto tid_b = tctx.device_compute(range<1>(ones)).read(buf, fixed<1>{{0, 128}}).submit(); + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_b))); + CHECK(tctx.query_tasks(tctx.get_initial_epoch_task()).successors().contains(tctx.query_tasks(tid_b))); // for read of the host-initialized part - TEST_CASE("task_manager respects range mapper results for finding dependencies", "[task_manager][task-graph]") { - auto tt = test_utils::task_test_context{}; - auto buf = tt.mbf.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - - const auto tid_a = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{0, 64}}); }); - const auto tid_b = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{0, 128}}); }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a)); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tt.initial_epoch_task)); // for read of the host-initialized part - - const auto tid_c = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{64, 128}}); }); - CHECK_FALSE(test_utils::has_dependency(tt.tdag, tid_c, tid_a)); - CHECK(test_utils::has_dependency(tt.tdag, tid_c, tt.initial_epoch_task)); // for read of the host-initialized part + const auto tid_c = tctx.device_compute(range<1>(ones)).read(buf, fixed<1>{{64, 128}}).submit(); + CHECK(tctx.query_tasks(tid_a).is_concurrent_with(tctx.query_tasks(tid_c))); + CHECK(tctx.query_tasks(tctx.get_initial_epoch_task()).successors().contains(tctx.query_tasks(tid_c))); // for read of the host-initialized part } TEST_CASE("task_manager correctly generates anti-dependencies", "[task_manager][task-graph]") { - auto tt = test_utils::task_test_context{}; - auto buf = tt.mbf.create_buffer(range<1>(128)); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf = tctx.create_buffer(range<1>(128)); // Write to the full buffer - const auto tid_a = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{0, 128}}); }); + const auto tid_a = tctx.device_compute(range<1>(ones)).discard_write(buf, all{}).submit(); // Read the first half of the buffer - const auto tid_b = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{0, 64}}); }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a)); + const auto tid_b = tctx.device_compute(range<1>(ones)).read(buf, fixed<1>{{0, 64}}).submit(); + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_b))); // Overwrite the second half - no anti-dependency onto task_b should exist (but onto task_a) - const auto tid_c = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{64, 64}}); }); - REQUIRE(test_utils::has_dependency(tt.tdag, tid_c, tid_a, dependency_kind::anti_dep)); - REQUIRE_FALSE(test_utils::has_dependency(tt.tdag, tid_c, tid_b, dependency_kind::anti_dep)); + const auto tid_c = tctx.device_compute(range<1>(ones)).discard_write(buf, fixed<1>{{64, 64}}).submit(); + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_c))); + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_a, tid_c, dependency_kind::anti_dep)); + CHECK(tctx.query_tasks(tid_b).is_concurrent_with(tctx.query_tasks(tid_c))); // Overwrite the first half - now only an anti-dependency onto task_b should exist - const auto tid_d = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{0, 64}}); }); - REQUIRE_FALSE(test_utils::has_dependency(tt.tdag, tid_d, tid_a, dependency_kind::anti_dep)); - REQUIRE(test_utils::has_dependency(tt.tdag, tid_d, tid_b, dependency_kind::anti_dep)); + const auto tid_d = tctx.device_compute(range<1>(ones)).discard_write(buf, fixed<1>{{0, 64}}).submit(); + CHECK(tctx.query_tasks(tid_b).successors().contains(tctx.query_tasks(tid_d))); + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_b, tid_d, dependency_kind::anti_dep)); + CHECK(tctx.query_tasks(tid_c).is_concurrent_with(tctx.query_tasks(tid_d))); } TEST_CASE("task_manager correctly handles host-initialized buffers", "[task_manager][task-graph]") { @@ -201,41 +137,35 @@ namespace detail { task_manager::policy_set tm_policy; tm_policy.uninitialized_read_error = error_policy::ignore; - auto tt = test_utils::task_test_context(tm_policy); - auto host_init_buf = tt.mbf.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - auto non_host_init_buf = tt.mbf.create_buffer(range<1>(128), false /* mark_as_host_initialized */); - auto artificial_dependency_buf = tt.mbf.create_buffer(range<1>(1), false /* mark_as_host_initialized */); - - const auto tid_a = test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - host_init_buf.get_access(cgh, fixed<1>{{0, 128}}); - artificial_dependency_buf.get_access(cgh, all{}); - }); - CHECK(test_utils::has_dependency(tt.tdag, tid_a, tt.initial_epoch_task)); - - const auto tid_b = test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - non_host_init_buf.get_access(cgh, fixed<1>{{0, 128}}); - // introduce an arbitrary true-dependency to avoid the fallback epoch dependency that is generated for tasks without other true-dependencies - artificial_dependency_buf.get_access(cgh, all{}); - }); - CHECK_FALSE(test_utils::has_dependency(tt.tdag, tid_b, tt.initial_epoch_task)); - - const auto tid_c = test_utils::add_compute_task( - tt.tm, [&](handler& cgh) { host_init_buf.get_access(cgh, fixed<1>{{0, 128}}); }); - CHECK(test_utils::has_dependency(tt.tdag, tid_c, tid_a, dependency_kind::anti_dep)); - const auto tid_d = test_utils::add_compute_task( - tt.tm, [&](handler& cgh) { non_host_init_buf.get_access(cgh, fixed<1>{{0, 128}}); }); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */, {tm_policy}); + + auto host_init_buf = tctx.create_buffer(range<1>(128), true /* mark_as_host_initialized */); + auto non_host_init_buf = tctx.create_buffer(range<1>(128), false /* mark_as_host_initialized */); + auto artificial_dependency_buf = tctx.create_buffer(range<1>(1), false /* mark_as_host_initialized */); + + const auto tid_a = tctx.device_compute(range<1>(ones)).read(host_init_buf, all{}).discard_write(artificial_dependency_buf, all{}).submit(); + CHECK(tctx.query_tasks(tctx.get_initial_epoch_task()).successors().contains(tctx.query_tasks(tid_a))); + + // introduce an arbitrary true-dependency to avoid the fallback epoch dependency that is generated for tasks without other true-dependencies + const auto tid_b = tctx.device_compute(range<1>(ones)).read(non_host_init_buf, all{}).read(artificial_dependency_buf, all{}).submit(); + CHECK_FALSE(tctx.query_tasks(tctx.get_initial_epoch_task()).successors().contains(tctx.query_tasks(tid_b))); + + const auto tid_c = tctx.device_compute(range<1>(ones)).discard_write(host_init_buf, all{}).submit(); + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_c))); + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_a, tid_c, dependency_kind::anti_dep)); + const auto tid_d = tctx.device_compute(range<1>(ones)).discard_write(non_host_init_buf, all{}).submit(); // Since task b is essentially reading uninitialized garbage, it doesn't make a difference if we write into it concurrently - CHECK_FALSE(test_utils::has_dependency(tt.tdag, tid_d, tid_b, dependency_kind::anti_dep)); + CHECK(tctx.query_tasks(tid_b).is_concurrent_with(tctx.query_tasks(tid_d))); } - template - void dispatch_get_access(test_utils::mock_buffer& mb, Handler& handler, access_mode mode, Functor rmfn) { + template + auto dispatch_get_access(Builder&& builder, test_utils::mock_buffer& mb, access_mode mode, Functor rmfn) { switch(mode) { - case access_mode::read: mb.template get_access(handler, rmfn); break; - case access_mode::write: mb.template get_access(handler, rmfn); break; - case access_mode::read_write: mb.template get_access(handler, rmfn); break; - case access_mode::discard_write: mb.template get_access(handler, rmfn); break; - case access_mode::discard_read_write: mb.template get_access(handler, rmfn); break; + case access_mode::read: return builder.read(mb, rmfn); break; + case access_mode::write: return builder.write(mb, rmfn); break; + case access_mode::read_write: return builder.read_write(mb, rmfn); break; + case access_mode::discard_write: return builder.discard_write(mb, rmfn); break; + case access_mode::discard_read_write: return builder.discard_read_write(mb, rmfn); break; default: utils::unreachable(); // LCOV_EXCL_LINE } } @@ -246,17 +176,18 @@ namespace detail { {access_mode::discard_read_write}, {access_mode::read_write}, {access_mode::discard_write, access_mode::read}}; for(const auto& mode_set : rw_mode_sets) { - auto tt = test_utils::task_test_context{}; - auto buf = tt.mbf.create_buffer(range<1>(128), true); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf = tctx.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - const auto tid_a = test_utils::add_compute_task(tt.tm, [&](handler& cgh) { - for(const auto& m : mode_set) { - dispatch_get_access(buf, cgh, m, fixed<1>{{0, 128}}); - } - }); - const auto tid_b = test_utils::add_compute_task( - tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<1>{{0, 128}}); }); - REQUIRE(test_utils::has_dependency(tt.tdag, tid_b, tid_a, dependency_kind::anti_dep)); + auto builder = tctx.device_compute(range<1>(ones)); + for(const auto& m : mode_set) { + builder = dispatch_get_access(std::move(builder), buf, m, all{}); + } + const auto tid_a = builder.submit(); + + const auto tid_b = tctx.device_compute(range<1>(ones)).discard_write(buf, all{}).submit(); + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_b))); + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_a, tid_b, dependency_kind::anti_dep)); } } @@ -269,59 +200,47 @@ namespace detail { CAPTURE(consumer_mode); CAPTURE(producer_mode); - auto tt = test_utils::task_test_context{}; - auto buf = tt.mbf.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - - const task_id tid_a = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { dispatch_get_access(buf, cgh, producer_mode, all()); }); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf = tctx.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - const task_id tid_b = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { dispatch_get_access(buf, cgh, consumer_mode, all()); }); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_a)); + const auto tid_a = dispatch_get_access(tctx.device_compute(range<1>(ones)), buf, producer_mode, all{}).submit(); + const auto tid_b = dispatch_get_access(tctx.device_compute(range<1>(ones)), buf, consumer_mode, all{}).submit(); + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_b))); - const task_id tid_c = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { dispatch_get_access(buf, cgh, producer_mode, all()); }); + const auto tid_c = dispatch_get_access(tctx.device_compute(range<1>(ones)), buf, producer_mode, all{}).submit(); const bool pure_consumer = consumer_mode == access_mode::read; const bool pure_producer = producer_mode == access_mode::discard_read_write || producer_mode == access_mode::discard_write; - CHECK( - test_utils::has_dependency(tt.tdag, tid_c, tid_b, pure_consumer || pure_producer ? dependency_kind::anti_dep : dependency_kind::true_dep)); + if(pure_consumer || pure_producer) { + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_b, tid_c, dependency_kind::anti_dep)); + } else { + CHECK(some_dependencies_match(tctx.get_task_recorder(), tid_b, tid_c, dependency_kind::true_dep)); + } } } } TEST_CASE("task_manager generates pseudo-dependencies for collective host tasks", "[task_manager][task-graph]") { - auto tt = test_utils::task_test_context{}; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); experimental::collective_group group; - auto tid_master = test_utils::add_host_task(tt.tm, on_master_node, [](handler&) {}); - auto tid_collective_implicit_1 = test_utils::add_host_task(tt.tm, experimental::collective, [](handler&) {}); - auto tid_collective_implicit_2 = test_utils::add_host_task(tt.tm, experimental::collective, [](handler&) {}); - auto tid_collective_explicit_1 = test_utils::add_host_task(tt.tm, experimental::collective(group), [](handler&) {}); - auto tid_collective_explicit_2 = test_utils::add_host_task(tt.tm, experimental::collective(group), [](handler&) {}); - - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_master, tid_collective_implicit_1)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_master, tid_collective_implicit_2)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_master, tid_collective_explicit_1)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_master, tid_collective_explicit_2)); - - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_1, tid_master)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_1, tid_collective_implicit_2)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_1, tid_collective_explicit_1)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_1, tid_collective_explicit_2)); - - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_2, tid_master)); - CHECK(test_utils::has_dependency(tt.tdag, tid_collective_implicit_2, tid_collective_implicit_1, dependency_kind::true_dep)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_2, tid_collective_explicit_1)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_implicit_2, tid_collective_explicit_2)); - - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_1, tid_master)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_1, tid_collective_implicit_1)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_1, tid_collective_implicit_2)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_1, tid_collective_explicit_2)); - - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_2, tid_master)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_2, tid_collective_implicit_1)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_collective_explicit_2, tid_collective_implicit_2)); - CHECK(test_utils::has_dependency(tt.tdag, tid_collective_explicit_2, tid_collective_explicit_1, dependency_kind::true_dep)); + const auto tid_master = tctx.master_node_host_task().name("master").submit(); + const auto tid_collective_implicit_1 = tctx.collective_host_task().name("collective implicit 1").submit(); + const auto tid_collective_implicit_2 = tctx.collective_host_task().name("collective implicit 2").submit(); + const auto tid_collective_explicit_1 = tctx.collective_host_task(group).name("collective explicit 1").submit(); + const auto tid_collective_explicit_2 = tctx.collective_host_task(group).name("collective explicit 2").submit(); + + CHECK(tctx.query_tasks(tid_master).is_concurrent_with(tctx.query_tasks(tid_collective_implicit_1))); + CHECK(tctx.query_tasks(tid_master).is_concurrent_with(tctx.query_tasks(tid_collective_implicit_2))); + CHECK(tctx.query_tasks(tid_master).is_concurrent_with(tctx.query_tasks(tid_collective_explicit_1))); + CHECK(tctx.query_tasks(tid_master).is_concurrent_with(tctx.query_tasks(tid_collective_explicit_2))); + + CHECK(tctx.query_tasks(tid_collective_implicit_1).successors().contains(tctx.query_tasks(tid_collective_implicit_2))); + CHECK(tctx.query_tasks(tid_collective_implicit_1).is_concurrent_with(tctx.query_tasks(tid_collective_explicit_1))); + CHECK(tctx.query_tasks(tid_collective_implicit_1).is_concurrent_with(tctx.query_tasks(tid_collective_explicit_2))); + + CHECK(tctx.query_tasks(tid_collective_implicit_2).is_concurrent_with(tctx.query_tasks(tid_collective_explicit_1))); + CHECK(tctx.query_tasks(tid_collective_implicit_2).is_concurrent_with(tctx.query_tasks(tid_collective_explicit_2))); + + CHECK(tctx.query_tasks(tid_collective_explicit_1).successors().contains(tctx.query_tasks(tid_collective_explicit_2))); } void check_path_length_and_front(const task_manager& tm, const task_graph& tdag, int path_length, const std::unordered_set& exec_front) { @@ -339,43 +258,39 @@ namespace detail { } TEST_CASE("task_manager keeps track of max pseudo critical path length and task front", "[task_manager][task-graph][task-front]") { - auto tt = test_utils::task_test_context{}; - auto buf_a = tt.mbf.create_buffer(range<1>(128)); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf_a = tctx.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - const auto tid_a = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); - check_path_length_and_front(tt.tm, tt.tdag, 1, {tid_a}); // 1: we always depend on the initial epoch task + const auto tid_a = tctx.master_node_host_task().discard_write(buf_a, all{}).submit(); + check_path_length_and_front(tctx.get_task_manager(), tctx.get_task_graph(), 1, {tid_a}); // 1: we always depend on the initial epoch task - const auto tid_b = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); - check_path_length_and_front(tt.tm, tt.tdag, 2, {tid_b}); + const auto tid_b = tctx.master_node_host_task().read_write(buf_a, all{}).submit(); + check_path_length_and_front(tctx.get_task_manager(), tctx.get_task_graph(), 2, {tid_b}); - const auto tid_c = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); - check_path_length_and_front(tt.tm, tt.tdag, 3, {tid_c}); + const auto tid_c = tctx.master_node_host_task().read(buf_a, all{}).submit(); + check_path_length_and_front(tctx.get_task_manager(), tctx.get_task_graph(), 3, {tid_c}); - const auto tid_d = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) {}); - check_path_length_and_front(tt.tm, tt.tdag, 3, {tid_c, tid_d}); + const auto tid_d = tctx.master_node_host_task().submit(); + check_path_length_and_front(tctx.get_task_manager(), tctx.get_task_graph(), 3, {tid_c, tid_d}); } TEST_CASE("task horizons are being generated with correct dependencies", "[task_manager][task-graph][task-horizon]") { - auto tt = test_utils::task_test_context{}; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); - tt.tm.set_horizon_step(2); - auto buf_a = tt.mbf.create_buffer(range<1>(128), true /* mark_as_host_initialized */); + tctx.set_horizon_step(2); + auto buf_a = tctx.create_buffer(range<1>(128), true /* mark_as_host_initialized */); - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); + tctx.master_node_host_task().discard_write(buf_a, all{}).submit(); - auto current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + auto current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); CHECK(current_horizon == nullptr); - const auto tid_c = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); + const auto tid_c = tctx.master_node_host_task().read(buf_a, all{}).submit(); - current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); REQUIRE(current_horizon != nullptr); CHECK(current_horizon->get_id() == tid_c + 1); - CHECK(test_utils::get_num_live_horizons(tt.tdag) == 1); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 1); auto horizon_dependencies = current_horizon->get_dependencies(); @@ -386,20 +301,19 @@ namespace detail { // current horizon is always part of the active task front expected_dependency_ids.insert(current_horizon->get_id()); - expected_dependency_ids.insert(test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) {})); - expected_dependency_ids.insert(test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) {})); - expected_dependency_ids.insert(test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) {})); - CHECK(test_utils::get_num_live_horizons(tt.tdag) == 1); - - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); - const auto tid_d = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({0, 128})); }); + expected_dependency_ids.insert(tctx.master_node_host_task().submit()); + expected_dependency_ids.insert(tctx.master_node_host_task().submit()); + expected_dependency_ids.insert(tctx.master_node_host_task().submit()); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 1); + + tctx.master_node_host_task().read_write(buf_a, all{}).submit(); + const auto tid_d = tctx.master_node_host_task().read_write(buf_a, all{}).submit(); expected_dependency_ids.insert(tid_d); - current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); REQUIRE(current_horizon != nullptr); CHECK(current_horizon->get_id() == tid_d + 1); - CHECK(test_utils::get_num_live_horizons(tt.tdag) == 2); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 2); horizon_dependencies = current_horizon->get_dependencies(); CHECK(std::distance(horizon_dependencies.begin(), horizon_dependencies.end()) == 5); @@ -412,34 +326,33 @@ namespace detail { } TEST_CASE("task horizons are being generated for the parallelism limit", "[task_manager][task-graph][task-horizon]") { - auto tt = test_utils::task_test_context{}; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); // we set a high step but low max parallelism to make sure that all horizons in this test are generated due to the parallelism limit, // regardless of what the defaults for these values are - tt.tm.set_horizon_step(256); + tctx.set_horizon_step(256); const auto max_para = 3; - tt.tm.set_horizon_max_parallelism(max_para); + tctx.get_task_manager().set_horizon_max_parallelism(max_para); const size_t buff_size = 128; const size_t num_tasks = 9; const size_t buff_elem_per_task = buff_size / num_tasks; - auto buf_a = tt.mbf.create_buffer(range<1>(buff_size), true /* mark_as_host_initialized */); + auto buf_a = tctx.create_buffer(range<1>(buff_size), true /* mark_as_host_initialized */); - auto current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + auto current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); CHECK(current_horizon == nullptr); for(size_t i = 0; i < num_tasks; ++i) { const auto offset = buff_elem_per_task * i; - test_utils::add_host_task( - tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({offset, buff_elem_per_task})); }); + tctx.master_node_host_task().read_write(buf_a, fixed<1>({offset, buff_elem_per_task})).submit(); } // divided by "max_para - 1" since there is also always the previous horizon in the set const auto expected_num_horizons = num_tasks / (max_para - 1); - CHECK(test_utils::get_num_live_horizons(tt.tdag) == expected_num_horizons); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == expected_num_horizons); // the most recent horizon should have 3 predecessors: 1 other horizon and 2 host tasks we generated - current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); REQUIRE(current_horizon != nullptr); CHECK(current_horizon->get_dependencies().size() == 3); } @@ -447,103 +360,94 @@ namespace detail { static inline region<3> make_region(size_t min, size_t max) { return box<3>({min, 0, 0}, {max, 1, 1}); } TEST_CASE("task horizons update previous writer data structure", "[task_manager][task-graph][task-horizon]") { - auto tt = test_utils::task_test_context{}; - - tt.tm.set_horizon_step(2); - auto buf_a = tt.mbf.create_buffer(range<1>(128)); - auto buf_b = tt.mbf.create_buffer(range<1>(128)); - - const task_id tid_1 = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - buf_a.get_access(cgh, fixed<1>({0, 64})); - buf_b.get_access(cgh, fixed<1>({0, 128})); - }); - const task_id tid_2 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({64, 64})); }); - [[maybe_unused]] const task_id tid_3 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({32, 64})); }); - const task_id tid_4 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({32, 64})); }); - - const auto horizon = task_manager_testspy::get_current_horizon(tt.tm); - CHECK(test_utils::get_num_live_horizons(tt.tdag) == 1); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + + tctx.set_horizon_step(2); + auto buf_a = tctx.create_buffer(range<1>(128)); + auto buf_b = tctx.create_buffer(range<1>(128)); + + const auto tid_1 = tctx.master_node_host_task().discard_write(buf_a, fixed<1>({0, 64})).discard_write(buf_b, fixed<1>({0, 128})).submit(); + + const auto tid_2 = tctx.master_node_host_task().discard_write(buf_a, fixed<1>({64, 64})).submit(); + tctx.master_node_host_task().read_write(buf_a, fixed<1>({32, 64})).submit(); + const auto tid_4 = tctx.master_node_host_task().read_write(buf_a, fixed<1>({32, 64})).submit(); + + const auto horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 1); CHECK(horizon != nullptr); - [[maybe_unused]] const task_id tid_6 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_b.get_access(cgh, fixed<1>({0, 128})); }); - [[maybe_unused]] const task_id tid_7 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_b.get_access(cgh, fixed<1>({0, 128})); }); + tctx.master_node_host_task().discard_write(buf_b, fixed<1>({0, 128})).submit(); + tctx.master_node_host_task().discard_write(buf_b, fixed<1>({0, 128})).submit(); { INFO("check that previous tasks are still last writers before the first horizon is applied"); - const auto& region_map_a = task_manager_testspy::get_last_writer(tt.tm, buf_a.get_id()); - CHECK(region_map_a.get_region_values(make_region(0, 32)).front().second == test_utils::get_task(tt.tdag, tid_1)); - CHECK(region_map_a.get_region_values(make_region(96, 128)).front().second == test_utils::get_task(tt.tdag, tid_2)); - CHECK(region_map_a.get_region_values(make_region(32, 96)).front().second == test_utils::get_task(tt.tdag, tid_4)); + const auto& region_map_a = task_manager_testspy::get_last_writer(tctx.get_task_manager(), buf_a.get_id()); + CHECK(region_map_a.get_region_values(make_region(0, 32)).front().second == test_utils::get_task(tctx.get_task_graph(), tid_1)); + CHECK(region_map_a.get_region_values(make_region(96, 128)).front().second == test_utils::get_task(tctx.get_task_graph(), tid_2)); + CHECK(region_map_a.get_region_values(make_region(32, 96)).front().second == test_utils::get_task(tctx.get_task_graph(), tid_4)); } - [[maybe_unused]] const task_id tid_8 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_b.get_access(cgh, fixed<1>({0, 128})); }); + const auto tid_8 = tctx.master_node_host_task().read_write(buf_b, fixed<1>({0, 128})).submit(); - CHECK(test_utils::get_num_live_horizons(tt.tdag) == 2); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 2); { INFO("check that only the previous horizon is the last writer of buff_a"); - const auto& region_map_a = task_manager_testspy::get_last_writer(tt.tm, buf_a.get_id()); + const auto& region_map_a = task_manager_testspy::get_last_writer(tctx.get_task_manager(), buf_a.get_id()); CHECK(region_map_a.get_region_values(make_region(0, 128)).front().second == horizon); } - const task_id tid_9 = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf_a.get_access(cgh, fixed<1>({64, 64})); }); + const auto tid_9 = tctx.master_node_host_task().read_write(buf_a, fixed<1>({64, 64})).submit(); { INFO("check that the previous horizon and task 11 are last writers of buff_a"); - const auto& region_map_a = task_manager_testspy::get_last_writer(tt.tm, buf_a.get_id()); + const auto& region_map_a = task_manager_testspy::get_last_writer(tctx.get_task_manager(), buf_a.get_id()); CHECK(region_map_a.get_region_values(make_region(0, 64)).front().second == horizon); - CHECK(region_map_a.get_region_values(make_region(64, 128)).front().second == test_utils::get_task(tt.tdag, tid_9)); + CHECK(region_map_a.get_region_values(make_region(64, 128)).front().second == test_utils::get_task(tctx.get_task_graph(), tid_9)); } } TEST_CASE("previous task horizon is used as last writer for host-initialized buffers", "[task_manager][task-graph][task-horizon]") { - auto tt = test_utils::task_test_context{}; - tt.tm.set_horizon_step(2); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + tctx.set_horizon_step(2); task_id initial_last_writer_id = -1; { - auto buf = tt.mbf.create_buffer(range<1>(1), true); - const auto tid = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); - const auto& deps = test_utils::get_task(tt.tdag, tid)->get_dependencies(); + auto buf = tctx.create_buffer(range<1>(1), true /* mark_as_host_initialized */); + const auto tid = tctx.master_node_host_task().read_write(buf, all{}).submit(); + const auto& deps = test_utils::get_task(tctx.get_task_graph(), tid)->get_dependencies(); CHECK(std::distance(deps.begin(), deps.end()) == 1); initial_last_writer_id = deps.begin()->node->get_id(); } - CHECK(test_utils::has_task(tt.tdag, initial_last_writer_id)); + CHECK(test_utils::has_task(tctx.get_task_graph(), initial_last_writer_id)); // Create a bunch of tasks to trigger horizon cleanup { - auto buf = tt.mbf.create_buffer(range<1>(1)); + auto buf = tctx.create_buffer(range<1>(1)); const task* last_executed_horizon = nullptr; // We need 7 tasks to generate a pseudo-critical path length of 6 (3x2 horizon step size), // and another one that triggers the actual deferred deletion. for(int i = 0; i < 8; ++i) { - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); - const auto current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + tctx.master_node_host_task().discard_write(buf, all{}).submit(); + const auto current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); if(last_executed_horizon != nullptr && current_horizon->get_id() > last_executed_horizon->get_id()) { - tt.tdag.erase_before_epoch(last_executed_horizon->get_id()); + tctx.get_task_graph().erase_before_epoch(last_executed_horizon->get_id()); } if(current_horizon != nullptr) { last_executed_horizon = current_horizon; } } } INFO("initial last writer with id " << initial_last_writer_id << " has been deleted"); - CHECK_FALSE(test_utils::has_task(tt.tdag, initial_last_writer_id)); + CHECK_FALSE(test_utils::has_task(tctx.get_task_graph(), initial_last_writer_id)); - auto buf = tt.mbf.create_buffer(range<1>(1), true); - const auto tid = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); - const auto& deps = test_utils::get_task(tt.tdag, tid)->get_dependencies(); + auto buf = tctx.create_buffer(range<1>(1), true); + const auto tid = tctx.master_node_host_task().read_write(buf, all{}).submit(); + const auto& deps = test_utils::get_task(tctx.get_task_graph(), tid)->get_dependencies(); CHECK(std::distance(deps.begin(), deps.end()) == 1); const auto* new_last_writer = deps.begin()->node; CHECK(new_last_writer->get_type() == task_type::horizon); - const auto current_horizon = task_manager_testspy::get_current_horizon(tt.tm); + const auto current_horizon = task_manager_testspy::get_current_horizon(tctx.get_task_manager()); REQUIRE(current_horizon); INFO("previous horizon is being used"); CHECK(new_last_writer->get_id() < current_horizon->get_id()); @@ -553,22 +457,21 @@ namespace detail { // Regression test: the order-dependencies between host tasks in the same collective group are built by tracking the last task in each collective group. // Once a horizon is inserted, new collective host tasks must order-depend on that horizon instead. - auto tt = test_utils::task_test_context{}; - tt.tm.set_horizon_step(2); - auto buf = tt.mbf.create_buffer(range<1>(1)); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + tctx.set_horizon_step(2); + auto buf = tctx.create_buffer(range<1>(1)); - [[maybe_unused]] const auto first_collective = test_utils::add_host_task(tt.tm, experimental::collective, [&](handler& cgh) {}); + tctx.collective_host_task().name("first_collective").submit(); // generate exactly two horizons for(int i = 0; i < 4; ++i) { - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); + tctx.master_node_host_task().discard_write(buf, all{}).submit(); } // This must depend on the first horizon, not first_collective - const auto second_collective = - test_utils::add_host_task(tt.tm, experimental::collective, [&](handler& cgh) { buf.get_access(cgh, all{}); }); + const auto second_collective = tctx.collective_host_task().name("second_collective").read(buf, all{}).submit(); - const auto second_collective_deps = test_utils::get_task(tt.tdag, second_collective)->get_dependencies(); + const auto second_collective_deps = test_utils::get_task(tctx.get_task_graph(), second_collective)->get_dependencies(); const auto master_node_dep = std::find_if(second_collective_deps.begin(), second_collective_deps.end(), [](const task::dependency d) { return d.node->get_type() == task_type::master_node; }); const auto horizon_dep = std::find_if(second_collective_deps.begin(), second_collective_deps.end(), // @@ -582,8 +485,8 @@ namespace detail { } TEST_CASE("buffer accesses with empty ranges do not generate data-flow dependencies", "[task_manager][task-graph]") { - auto tt = test_utils::task_test_context{}; - auto buf = tt.mbf.create_buffer(range<2>(32, 32), true /* mark_as_host_initialized */); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf = tctx.create_buffer(range<2>(32, 32), true /* mark_as_host_initialized */); const auto write_sr = GENERATE(values({subrange<2>{{16, 16}, {0, 0}}, subrange<2>{{16, 16}, {8, 8}}})); const auto read_sr = GENERATE(values({subrange<2>{{1, 1}, {0, 0}}, subrange<2>{{8, 8}, {16, 16}}})); @@ -593,12 +496,14 @@ namespace detail { CAPTURE(read_empty); CAPTURE(write_empty); - const auto write_tid = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<2>{write_sr}); }); - const auto read_tid = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<2>{read_sr}); }); + const auto write_tid = tctx.device_compute(range<2>(ones)).discard_write(buf, fixed<2>{write_sr}).submit(); + const auto read_tid = tctx.device_compute(range<2>(ones)).read(buf, fixed<2>{read_sr}).submit(); - CHECK(test_utils::has_any_dependency(tt.tdag, read_tid, write_tid) == (!write_empty && !read_empty)); + if(read_empty || write_empty) { + CHECK(tctx.query_tasks(write_tid).is_concurrent_with(tctx.query_tasks(read_tid))); + } else { + CHECK(tctx.query_tasks(write_tid).successors().contains(tctx.query_tasks(read_tid))); + } } TEST_CASE("side effects generate appropriate task-dependencies", "[task_manager][task-graph][side-effect]") { @@ -615,169 +520,141 @@ namespace detail { CAPTURE(order_a); CAPTURE(order_b); - auto tt = test_utils::task_test_context{}; - auto ho_common = tt.mhof.create_host_object(); // should generate dependencies - auto ho_a = tt.mhof.create_host_object(); // should NOT generate dependencies - auto ho_b = tt.mhof.create_host_object(); // -"- - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - ho_common.add_side_effect(cgh, order_a); - ho_a.add_side_effect(cgh, order_a); - }); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { - ho_common.add_side_effect(cgh, order_b); - ho_b.add_side_effect(cgh, order_b); - }); - - const auto deps_a = test_utils::get_task(tt.tdag, tid_a)->get_dependencies(); - REQUIRE(std::distance(deps_a.begin(), deps_a.end()) == 1); - CHECK(deps_a.front().node->get_id() == tt.initial_epoch_task); - - const auto deps_b = test_utils::get_task(tt.tdag, tid_b)->get_dependencies(); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto ho_common = tctx.create_host_object(); // should generate dependencies + auto ho_a = tctx.create_host_object(); // should NOT generate dependencies + auto ho_b = tctx.create_host_object(); // -"- + const auto tid_a = tctx.master_node_host_task().affect(ho_common, order_a).affect(ho_a, order_a).submit(); + const auto tid_b = tctx.master_node_host_task().affect(ho_common, order_b).affect(ho_b, order_b).submit(); + + CHECK(tctx.query_tasks(tid_a).predecessors().assert_count(1).contains(tctx.query_tasks(tctx.get_initial_epoch_task()))); + + const auto deps_b = test_utils::get_task(tctx.get_task_graph(), tid_b)->get_dependencies(); const auto expected_b = expected_dependencies.at({order_a, order_b}); CHECK(std::distance(deps_b.begin(), deps_b.end()) == expected_b.has_value()); if(expected_b) { - CHECK(deps_b.front().node == test_utils::get_task(tt.tdag, tid_a)); + CHECK(deps_b.front().node == test_utils::get_task(tctx.get_task_graph(), tid_a)); CHECK(deps_b.front().kind == *expected_b); } } TEST_CASE("side-effect dependencies are correctly subsumed by horizons", "[task_manager][task-graph][task-horizon]") { - auto tt = test_utils::task_test_context{}; - tt.tm.set_horizon_step(2); - auto ho = tt.mhof.create_host_object(); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + tctx.set_horizon_step(2); + auto ho = tctx.create_host_object(); - [[maybe_unused]] const auto first_task = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { ho.add_side_effect(cgh, experimental::side_effect_order::sequential); }); + tctx.master_node_host_task().name("first_task").affect(ho, experimental::side_effect_order::sequential).submit(); // generate exactly two horizons - auto buf = tt.mbf.create_buffer(range<1>(1)); + auto buf = tctx.create_buffer(range<1>(1)); for(int i = 0; i < 5; ++i) { - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); + tctx.master_node_host_task().discard_write(buf, all{}).submit(); } + const auto horizon_tid = task_manager_testspy::get_epoch_for_new_tasks(tctx.get_task_manager())->get_id(); // This must depend on the first horizon, not first_task - const auto second_task = - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { ho.add_side_effect(cgh, experimental::side_effect_order::sequential); }); - - const auto& second_deps = test_utils::get_task(tt.tdag, second_task)->get_dependencies(); - CHECK(std::distance(second_deps.begin(), second_deps.end()) == 1); - for(const auto& dep : second_deps) { - const auto type = dep.node->get_type(); - CHECK(type == task_type::horizon); - CHECK(dep.kind == dependency_kind::true_dep); - } + const auto second_task = tctx.master_node_host_task().name("second_task").affect(ho, experimental::side_effect_order::sequential).submit(); + CHECK(tctx.query_tasks(second_task).predecessors().assert_count(1).contains(tctx.query_tasks(horizon_tid))); } TEST_CASE("epochs create appropriate dependencies to predecessors and successors", "[task_manager][task-graph][epoch]") { - auto tt = test_utils::task_test_context{}; - - auto buf_a = tt.mbf.create_buffer(range<1>(1)); - const auto tid_a = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_a.get_access(cgh, all{}); }); - - auto buf_b = tt.mbf.create_buffer(range<1>(1)); - const auto tid_b = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_b.get_access(cgh, all{}); }); - - const auto tid_epoch = tt.tm.generate_epoch_task(epoch_action::none); - - const auto tid_c = test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_a.get_access(cgh, all{}); }); - const auto tid_d = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_b.get_access(cgh, all{}); }); - const auto tid_e = test_utils::add_compute_task(tt.tm, [&](handler& cgh) {}); - const auto tid_f = test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_b.get_access(cgh, all{}); }); - const auto tid_g = - test_utils::add_compute_task(tt.tm, [&](handler& cgh) { buf_b.get_access(cgh, all{}); }); - - CHECK(test_utils::has_dependency(tt.tdag, tid_epoch, tid_a)); - CHECK(test_utils::has_dependency(tt.tdag, tid_epoch, tid_b)); - CHECK(test_utils::has_dependency(tt.tdag, tid_c, tid_epoch)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_c, tid_a)); - CHECK(test_utils::has_dependency(tt.tdag, tid_d, tid_epoch)); // needs a true_dep on barrier since it only has anti_deps otherwise - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_d, tid_b)); - CHECK(test_utils::has_dependency(tt.tdag, tid_e, tid_epoch)); - CHECK(test_utils::has_dependency(tt.tdag, tid_f, tid_d)); - CHECK_FALSE(test_utils::has_any_dependency(tt.tdag, tid_f, tid_epoch)); - CHECK(test_utils::has_dependency(tt.tdag, tid_g, tid_f, dependency_kind::anti_dep)); - CHECK(test_utils::has_dependency(tt.tdag, tid_g, tid_epoch)); // needs a true_dep on barrier since it only has anti_deps otherwise + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + + auto buf_a = tctx.create_buffer(range<1>(1)); + const auto tid_a = tctx.device_compute(range<1>(ones)).name("a").discard_write(buf_a, all{}).submit(); + + auto buf_b = tctx.create_buffer(range<1>(1)); + const auto tid_b = tctx.device_compute(range<1>(ones)).name("b").discard_write(buf_b, all{}).submit(); + + const auto tid_epoch = tctx.epoch(epoch_action::none); + + const auto tid_c = tctx.device_compute(range<1>(ones)).name("c").read(buf_a, all{}).submit(); + const auto tid_d = tctx.device_compute(range<1>(ones)).name("d").discard_write(buf_b, all{}).submit(); + const auto tid_e = tctx.device_compute(range<1>(ones)).name("e").discard_write(buf_a, all{}).submit(); + const auto tid_f = tctx.device_compute(range<1>(ones)).name("f").read(buf_b, all{}).submit(); + const auto tid_g = tctx.device_compute(range<1>(ones)).name("g").discard_write(buf_b, all{}).submit(); + + CHECK(tctx.query_tasks(tid_a).successors().contains(tctx.query_tasks(tid_epoch))); + CHECK(tctx.query_tasks(tid_b).successors().contains(tctx.query_tasks(tid_epoch))); + CHECK(tctx.query_tasks(tid_epoch).successors().contains(tctx.query_tasks(tid_c))); + CHECK_FALSE(tctx.query_tasks(tid_c).predecessors().contains(tctx.query_tasks(tid_a))); + CHECK(tctx.query_tasks(tid_epoch).successors().contains(tctx.query_tasks(tid_d))); // needs a true_dep on barrier since it only has anti_deps otherwise + CHECK_FALSE(tctx.query_tasks(tid_d).predecessors().contains(tctx.query_tasks(tid_b))); + CHECK(tctx.query_tasks(tid_epoch).successors().contains(tctx.query_tasks(tid_e))); + CHECK(tctx.query_tasks(tid_f).predecessors().contains(tctx.query_tasks(tid_d))); + CHECK_FALSE(tctx.query_tasks(tid_f).predecessors().contains(tctx.query_tasks(tid_epoch))); + CHECK(tctx.query_tasks(tid_g).predecessors().contains(tctx.query_tasks(tid_f))); + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_f, tid_g, dependency_kind::anti_dep)); + CHECK( + tctx.query_tasks(tid_g).predecessors().contains(tctx.query_tasks(tid_epoch))); // needs a true_dep on barrier since it only has anti_deps otherwise } TEST_CASE("inserting epochs resets the need for horizons", "[task_manager][task-graph][task-horizon][epoch]") { - auto tt = test_utils::task_test_context{}; - tt.tm.set_horizon_step(2); - auto buf = tt.mbf.create_buffer(range<1>(1)); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + tctx.set_horizon_step(2); + auto buf = tctx.create_buffer(range<1>(1)); for(int i = 0; i < 3; ++i) { - test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); - tt.tm.generate_epoch_task(epoch_action::none); + tctx.master_node_host_task().discard_write(buf, all{}).submit(); + tctx.epoch(epoch_action::none); } - CHECK(test_utils::get_num_live_horizons(tt.tdag) == 0); + CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 0); } TEST_CASE("a sequence of epochs without intermediate tasks has defined behavior", "[task_manager][task-graph][epoch]") { - auto tt = test_utils::task_test_context{}; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); - auto tid_before = tt.initial_epoch_task; + auto tid_before = tctx.get_initial_epoch_task(); for(const auto action : {epoch_action::barrier, epoch_action::shutdown}) { - const auto tid = tt.tm.generate_epoch_task(action); + const auto tid = tctx.epoch(action); CAPTURE(tid_before, tid); - const auto deps = test_utils::get_task(tt.tdag, tid)->get_dependencies(); - CHECK(std::distance(deps.begin(), deps.end()) == 1); - for(const auto& d : deps) { - CHECK(d.kind == dependency_kind::true_dep); - CHECK(d.node->get_id() == tid_before); - } + CHECK(tctx.query_tasks(tid).predecessors().contains(tctx.query_tasks(tid_before))); tid_before = tid; } } TEST_CASE("fences introduce dependencies on host objects", "[task_manager][task-graph][fence]") { - auto tt = test_utils::task_test_context{}; - auto ho = tt.mhof.create_host_object(); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto ho = tctx.create_host_object(); - const auto tid_a = test_utils::add_host_task( - tt.tm, celerity::experimental::collective, [&](handler& cgh) { ho.add_side_effect(cgh, experimental::side_effect_order::sequential); }); - const auto tid_fence = test_utils::add_fence_task(tt.tm, ho); - const auto tid_b = test_utils::add_host_task( - tt.tm, celerity::experimental::collective, [&](handler& cgh) { ho.add_side_effect(cgh, experimental::side_effect_order::sequential); }); + const auto tid_a = tctx.collective_host_task().affect(ho, experimental::side_effect_order::sequential).submit(); + const auto tid_fence = tctx.fence(ho); + const auto tid_b = tctx.collective_host_task().affect(ho, experimental::side_effect_order::sequential).submit(); - CHECK(test_utils::has_dependency(tt.tdag, tid_fence, tid_a)); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_fence)); + CHECK(tctx.query_tasks(tid_fence).predecessors().contains(tctx.query_tasks(tid_a))); + CHECK(tctx.query_tasks(tid_b).predecessors().contains(tctx.query_tasks(tid_fence))); } TEST_CASE("fences introduce data dependencies", "[task_manager][task-graph][fence]") { - auto tt = test_utils::task_test_context{}; - auto buf = tt.mbf.create_buffer<1>({1}); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); + auto buf = tctx.create_buffer(range<1>(1)); - const auto tid_a = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); - const auto tid_fence = test_utils::add_fence_task(tt.tm, buf); - const auto tid_b = test_utils::add_host_task(tt.tm, on_master_node, [&](handler& cgh) { buf.get_access(cgh, all{}); }); + const auto tid_a = tctx.master_node_host_task().discard_write(buf, all{}).submit(); + const auto tid_fence = tctx.fence(buf); + const auto tid_b = tctx.master_node_host_task().discard_write(buf, all{}).submit(); - CHECK(test_utils::has_dependency(tt.tdag, tid_fence, tid_a)); - CHECK(test_utils::has_dependency(tt.tdag, tid_b, tid_fence, dependency_kind::anti_dep)); + CHECK(tctx.query_tasks(tid_fence).predecessors().contains(tctx.query_tasks(tid_a))); + CHECK(tctx.query_tasks(tid_b).predecessors().contains(tctx.query_tasks(tid_fence))); + CHECK(all_dependencies_match(tctx.get_task_recorder(), tid_fence, tid_b, dependency_kind::anti_dep)); } TEST_CASE("task_manager throws in tests if it detects an uninitialized read", "[task_manager]") { - test_utils::task_test_context tt; + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); SECTION("on a fully uninitialized buffer") { - auto buf = tt.mbf.create_buffer<1>({1}); + auto buf = tctx.create_buffer<1>({1}); - CHECK_THROWS_WITH((test_utils::add_compute_task( - tt.tm, [&](handler& cgh) { debug::set_task_name(cgh, "uninit_read"), buf.get_access(cgh, all{}); })), + CHECK_THROWS_WITH((tctx.device_compute(range<1>(ones)).name("uninit_read").read(buf, all{}).submit()), "Device kernel T1 \"uninit_read\" declares a reading access on uninitialized B0 {[0,0,0] - [1,1,1]}."); } SECTION("on a partially initialized buffer") { - auto buf = tt.mbf.create_buffer<2>({64, 64}); - test_utils::add_compute_task( - tt.tm, [&](handler& cgh) { buf.get_access(cgh, fixed<2>({{0, 0}, {32, 32}})); }); - - CHECK_THROWS_WITH((test_utils::add_compute_task( - tt.tm, [&](handler& cgh) { debug::set_task_name(cgh, "uninit_read"), buf.get_access(cgh, all{}); })), - "Device kernel T2 \"uninit_read\" declares a consuming access on uninitialized B0 {[0,32,0] - [32,64,1], [32,0,0] - [64,64,1]}. Make sure to " - "construct the accessor with no_init if this was unintentional."); + auto buf = tctx.create_buffer<2>({64, 64}); + tctx.device_compute(range<2>(ones)).discard_write(buf, fixed<2>({{0, 0}, {32, 32}})).submit(); + + CHECK_THROWS_WITH((tctx.device_compute(range<1>(ones)).name("uninit_read").read(buf, all{}).submit()), + "Device kernel T2 \"uninit_read\" declares a reading access on uninitialized B0 {[0,32,0] - [32,64,1], [32,0,0] - [64,64,1]}."); } } @@ -786,16 +663,14 @@ namespace detail { const auto action = GENERATE(values({epoch_action::none, epoch_action::barrier})); - task_graph tdag; - task_manager tm(1 /* num collective nodes */, tdag, nullptr /* recorder */, nullptr /* delegate */); - tm.generate_epoch_task(epoch_action::init); + test_utils::tdag_test_context tctx(1 /* num_collective_nodes */); for(int i = 0; i <= 25; ++i) { for(int j = 0; j < 5; ++j) { - tm.generate_command_group_task(invoke_command_group_function([](handler& cgh) { cgh.host_task(celerity::once, [] {}); })); + tctx.master_node_host_task().submit(); } - tm.generate_epoch_task(action); + tctx.epoch(action); } - tm.generate_epoch_task(epoch_action::shutdown); + tctx.epoch(epoch_action::shutdown); CHECK(test_utils::log_contains_exact(log_level::warn, "Your program appears to call queue::wait() excessively, which may lead to performance degradation. Consider using queue::fence() " diff --git a/test/test_utils.cc b/test/test_utils.cc index 9f89271f8..cc7245e88 100644 --- a/test/test_utils.cc +++ b/test/test_utils.cc @@ -341,10 +341,6 @@ std::string make_test_graph_title(const std::string& type, const size_t num_node return title; } -task_test_context::~task_test_context() { - if(g_print_graphs) { fmt::print("\n{}\n", detail::print_task_graph(trec, make_test_graph_title("Task Graph"))); } -} - } // namespace celerity::test_utils CATCH_REGISTER_LISTENER(celerity::test_utils_detail::global_setup_and_teardown); diff --git a/test/test_utils.h b/test/test_utils.h index cb024d249..be8c4bb07 100644 --- a/test/test_utils.h +++ b/test/test_utils.h @@ -150,13 +150,6 @@ namespace test_utils { return false; } - inline bool has_any_dependency(const detail::task_graph& tdag, detail::task_id dependent, detail::task_id dependency) { - for(auto dep : get_task(tdag, dependent)->get_dependencies()) { - if(dep.node->get_id() == dependency) return true; - } - return false; - } - class require_loop_assertion_registry { public: static require_loop_assertion_registry& get_instance() { @@ -263,7 +256,6 @@ namespace test_utils { detail::host_object_id get_id() const { return m_id; } private: - friend class mock_host_object_factory; friend class tdag_test_context; friend class cdag_test_context; friend class idag_test_context; @@ -306,81 +298,6 @@ namespace test_utils { detail::raw_allocation_id m_next_user_allocation_id = 1; }; - class mock_host_object_factory { - public: - explicit mock_host_object_factory() = default; - explicit mock_host_object_factory(detail::task_manager& tm) : m_task_mngr(&tm) {} - explicit mock_host_object_factory(detail::task_manager& tm, detail::scheduler& schdlr) : m_task_mngr(&tm), m_schdlr(&schdlr) {} - - mock_host_object create_host_object(bool owns_instance = true) { - const detail::host_object_id hoid = m_next_id++; - if(m_task_mngr != nullptr) { m_task_mngr->notify_host_object_created(hoid); } - if(m_schdlr != nullptr) { m_schdlr->notify_host_object_created(hoid, owns_instance); } - return mock_host_object(hoid); - } - - private: - detail::task_manager* m_task_mngr = nullptr; - detail::scheduler* m_schdlr = nullptr; - detail::host_object_id m_next_id = 0; - }; - - template - detail::task_id add_compute_task(detail::task_manager& tm, CGF cgf, range global_size = {1, 1}, id global_offset = {}) { - return tm.generate_command_group_task(detail::invoke_command_group_function([&, gs = global_size, go = global_offset](handler& cgh) { - cgf(cgh); - cgh.parallel_for(gs, go, [](id) {}); - })); - } - - template - detail::task_id add_nd_range_compute_task(detail::task_manager& tm, CGF cgf, celerity::nd_range execution_range = {{1, 1}, {1, 1}}) { - return tm.generate_command_group_task(detail::invoke_command_group_function([&, er = execution_range](handler& cgh) { - cgf(cgh); - cgh.parallel_for(er, [](nd_item) {}); - })); - } - - template - detail::task_id add_host_task(detail::task_manager& tm, Spec spec, CGF cgf) { - return tm.generate_command_group_task(detail::invoke_command_group_function([&](handler& cgh) { - cgf(cgh); - cgh.host_task(spec, [](auto...) {}); - })); - } - - inline detail::task_id add_fence_task(detail::task_manager& tm, mock_host_object ho) { - const detail::host_object_effect effect{ho.get_id(), experimental::side_effect_order::sequential}; - return tm.generate_fence_task(effect, nullptr); - } - - template - inline detail::task_id add_fence_task(detail::task_manager& tm, mock_buffer buf, subrange sr) { - detail::buffer_access access{buf.get_id(), access_mode::read, - std::make_unique>>(celerity::access::fixed(sr), buf.get_range())}; - return tm.generate_fence_task(std::move(access), nullptr); - } - - template - inline detail::task_id add_fence_task(detail::task_manager& tm, mock_buffer buf) { - return add_fence_task(tm, buf, {{}, buf.get_range()}); - } - - class mock_reduction_factory { - public: - detail::reduction_info create_reduction(const detail::buffer_id bid, const bool include_current_buffer_value) { - return detail::reduction_info{m_next_id++, bid, include_current_buffer_value}; - } - - private: - detail::reduction_id m_next_id = 1; - }; - - template - void add_reduction(handler& cgh, mock_reduction_factory& mrf, const mock_buffer& vars, bool include_current_buffer_value) { - detail::add_reduction(cgh, mrf.create_reduction(vars.get_id(), include_current_buffer_value)); - } - detail::system_info make_system_info(const size_t num_devices, const bool supports_d2d_copies); // This fixture (or a subclass) must be used by all tests that transitively use MPI. @@ -451,26 +368,6 @@ namespace test_utils { std::string make_test_graph_title(const std::string& type, size_t num_nodes, detail::node_id local_nid); std::string make_test_graph_title(const std::string& type, size_t num_nodes, detail::node_id local_nid, size_t num_devices_per_node); - // DEPRECATED: Use tdag_test_context in task_graph_test_utils.h instead - struct task_test_context { - detail::task_graph tdag; - detail::task_recorder trec; - detail::task_manager tm; - mock_buffer_factory mbf; - mock_host_object_factory mhof; - mock_reduction_factory mrf; - detail::task_id initial_epoch_task; - - explicit task_test_context(const detail::task_manager::policy_set& policy = {}) - : tm(1, tdag, &trec, nullptr /* delegate */, policy), mbf(tm), mhof(tm), initial_epoch_task(tm.generate_epoch_task(detail::epoch_action::init)) {} - - task_test_context(const task_test_context&) = delete; - task_test_context(task_test_context&&) = delete; - task_test_context& operator=(const task_test_context&) = delete; - task_test_context& operator=(task_test_context&&) = delete; - ~task_test_context(); - }; - // explicitly invoke a copy constructor without repeating the type template T copy(const T& v) { From 229761d8cc4e0f8b120d9dd6fceb0645f7b3a04d Mon Sep 17 00:00:00 2001 From: Gabriel Mitterrutzner Date: Thu, 5 Jun 2025 17:15:07 +0200 Subject: [PATCH 3/3] Address Clang-Tidy suggestions: misc-const-correctness, cppcoreguidelines-missing-std-forward and clang-analyzer-deadcode.DeadStores --- src/print_graph.cc | 2 +- test/task_graph_tests.cc | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/print_graph.cc b/src/print_graph.cc index f4bbdfec7..8e1572013 100644 --- a/src/print_graph.cc +++ b/src/print_graph.cc @@ -143,7 +143,7 @@ std::string print_task_graph(const task_recorder& recorder, const std::string& t CELERITY_DEBUG("print_task_graph, {} entries", recorder.get_graph_nodes().size()); for(const auto& tsk : recorder.get_graph_nodes()) { - const char* shape = tsk->type == task_type::epoch || tsk->type == task_type::horizon ? "ellipse" : "box style=rounded"; + const char* const shape = tsk->type == task_type::epoch || tsk->type == task_type::horizon ? "ellipse" : "box style=rounded"; fmt::format_to(std::back_inserter(dot), "{}[shape={} label=<{}>];", tsk->id, shape, get_task_label(*tsk)); } diff --git a/test/task_graph_tests.cc b/test/task_graph_tests.cc index 7cc58717e..4bccb0db4 100644 --- a/test/task_graph_tests.cc +++ b/test/task_graph_tests.cc @@ -161,11 +161,11 @@ namespace detail { template auto dispatch_get_access(Builder&& builder, test_utils::mock_buffer& mb, access_mode mode, Functor rmfn) { switch(mode) { - case access_mode::read: return builder.read(mb, rmfn); break; - case access_mode::write: return builder.write(mb, rmfn); break; - case access_mode::read_write: return builder.read_write(mb, rmfn); break; - case access_mode::discard_write: return builder.discard_write(mb, rmfn); break; - case access_mode::discard_read_write: return builder.discard_read_write(mb, rmfn); break; + case access_mode::read: return std::forward(builder.read(mb, rmfn)); break; + case access_mode::write: return std::forward(builder.write(mb, rmfn)); break; + case access_mode::read_write: return std::forward(builder.read_write(mb, rmfn)); break; + case access_mode::discard_write: return std::forward(builder.discard_write(mb, rmfn)); break; + case access_mode::discard_read_write: return std::forward(builder.discard_read_write(mb, rmfn)); break; default: utils::unreachable(); // LCOV_EXCL_LINE } } @@ -387,7 +387,7 @@ namespace detail { CHECK(region_map_a.get_region_values(make_region(32, 96)).front().second == test_utils::get_task(tctx.get_task_graph(), tid_4)); } - const auto tid_8 = tctx.master_node_host_task().read_write(buf_b, fixed<1>({0, 128})).submit(); + [[maybe_unused]] const auto tid_8 = tctx.master_node_host_task().read_write(buf_b, fixed<1>({0, 128})).submit(); CHECK(test_utils::get_num_live_horizons(tctx.get_task_graph()) == 2);