diff --git a/.bazelrc b/.bazelrc index 764036e..f3a7e62 100644 --- a/.bazelrc +++ b/.bazelrc @@ -5,7 +5,7 @@ common --enable_bzlmod build --output_filter='^//(core|pass|inou)' #build --output_filter="^//" #build --cxxopt="-std=c++17" --cxxopt="-fexceptions" --force_pic --build_tag_filters="-fixme" -build --cxxopt="-std=c++23" --force_pic --build_tag_filters="-fixme" +build --cxxopt="-std=c++20" --force_pic --build_tag_filters="-fixme" # test --cache_test_results=no # Use Clang @@ -55,7 +55,7 @@ build:asan --copt -fno-omit-frame-pointer build:asan --cxxopt -fsanitize=address build:asan --cxxopt -DADDRESS_SANITIZER #build:asan --cxxopt -O1 -build:asan --cxxopt -std=c++23 +build:asan --cxxopt -std=c++20 build:asan --cxxopt -g build:asan --cxxopt -fno-omit-frame-pointer build:asan --linkopt -fsanitize=address @@ -95,7 +95,7 @@ build:tsan --cxxopt -DTHREAD_SANITIZER build:tsan --cxxopt -DDYNAMIC_ANNOTATIONS_ENABLED=1 build:tsan --cxxopt -DDYNAMIC_ANNOTATIONS_EXTERNAL_IMPL=1 build:tsan --cxxopt -g -build:tsan --cxxopt -std=c++23 +build:tsan --cxxopt -std=c++20 # build:tsan --copt -O1 build:tsan --cxxopt -fno-omit-frame-pointer build:tsan --linkopt -fsanitize=thread diff --git a/hhds/BUILD.bazel b/hhds/BUILD.bazel index bf916ba..3b82534 100644 --- a/hhds/BUILD.bazel +++ b/hhds/BUILD.bazel @@ -73,6 +73,7 @@ cc_library( "graph.hpp", "graph_sizing.hpp", "hash_set3.hpp", + "tree.hpp", "unordered_dense.hpp", ], copts = COPTS + [ @@ -86,6 +87,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ "@emhash", + "@iassert", ], ) @@ -296,9 +298,8 @@ cc_test( #) cc_test( - name = "iterators", - srcs = ["tests/iterators.cpp"], - tags = ["manual"], # planned API, not yet implemented + name = "iterators_impl", + srcs = ["tests/iterators_impl.cpp"], deps = [ ":core", ":graph", @@ -308,8 +309,9 @@ cc_test( ) cc_test( - name = "iterators_impl", - srcs = ["tests/iterators_impl.cpp"], + name = "iterators", + srcs = ["tests/iterators.cpp"], + tags = ["manual"], # planned API, not yet implemented deps = [ ":core", ":graph", diff --git a/hhds/graph.cpp b/hhds/graph.cpp index 74fa8e6..2829f6e 100644 --- a/hhds/graph.cpp +++ b/hhds/graph.cpp @@ -2,10 +2,15 @@ #include +#include #include +#include #include +#include #include +#include "tree.hpp" + // TODO: // 8-Benchmark against boost library for some example similar to hardware // 9-Iterator single graph (fast, fwd, bwd) @@ -23,6 +28,115 @@ namespace hhds { +Node_hier::Node_hier(std::shared_ptr> hier_ref_value, int64_t hier_pos_value, Nid raw_nid_value) + : hier_ref(std::move(hier_ref_value)), hier_pos(hier_pos_value), raw_nid(raw_nid_value) {} + +auto Node_hier::get_root_gid() const noexcept -> Gid { + if (!hier_ref) { + return Gid_invalid; + } + return hier_ref->get_data(hier_ref->get_root()); +} + +auto Node_hier::get_current_gid() const noexcept -> Gid { + if (!hier_ref) { + return Gid_invalid; + } + return hier_ref->get_data(hier_pos); +} + +auto Pin_hier::get_root_gid() const noexcept -> Gid { + if (!hier_ref) { + return Gid_invalid; + } + return hier_ref->get_data(hier_ref->get_root()); +} + +auto Pin_hier::get_current_gid() const noexcept -> Gid { + if (!hier_ref) { + return Gid_invalid; + } + return hier_ref->get_data(hier_pos); +} + +auto to_class(const Node_hier& v) -> Node_class { return Node_class(nullptr, v.get_raw_nid() & ~static_cast(2)); } + +auto to_flat(const Node_hier& v) -> Node_flat { + return Node_flat(v.get_root_gid(), v.get_current_gid(), v.get_raw_nid() & ~static_cast(2)); +} + +auto to_class(const Node_flat& v) -> Node_class { return Node_class(nullptr, v.get_raw_nid() & ~static_cast(2)); } + +auto to_flat(const Node_class& v, Gid current_gid, Gid root_gid) -> Node_flat { + if (root_gid == Gid_invalid) { + root_gid = current_gid; + } + return Node_flat(root_gid, current_gid, v.get_raw_nid() & ~static_cast(2)); +} + +auto to_class(const Pin_hier& v) -> Pin_class { + return Pin_class(v.get_raw_nid() & ~static_cast(2), v.get_port_id(), v.get_pin_pid()); +} + +auto to_flat(const Pin_hier& v) -> Pin_flat { + return Pin_flat(v.get_root_gid(), v.get_current_gid(), v.get_raw_nid() & ~static_cast(2), v.get_port_id(), v.get_pin_pid()); +} + +auto to_class(const Pin_flat& v) -> Pin_class { + return Pin_class(v.get_raw_nid() & ~static_cast(2), v.get_port_id(), v.get_pin_pid()); +} + +auto to_flat(const Pin_class& v, Gid current_gid, Gid root_gid) -> Pin_flat { + if (root_gid == Gid_invalid) { + root_gid = current_gid; + } + return Pin_flat(root_gid, current_gid, v.get_raw_nid() & ~static_cast(2), v.get_port_id(), v.get_pin_pid()); +} + +auto to_flat(const Edge_class& e, Gid current_gid, Gid root_gid) -> Edge_flat { + if (root_gid == Gid_invalid) { + root_gid = current_gid; + } + Edge_flat out; + out.driver = to_flat(e.driver_pin, current_gid, root_gid); + out.sink = to_flat(e.sink_pin, current_gid, root_gid); + return out; +} + +auto to_flat(const Edge_hier& e) -> Edge_flat { + Edge_flat out; + out.driver = to_flat(e.driver); + out.sink = to_flat(e.sink); + return out; +} + +auto to_hier(const Edge_class& e, std::shared_ptr> hier_ref, int64_t hier_pos) -> Edge_hier { + Edge_hier out; + out.driver = Pin_hier(hier_ref, hier_pos, e.driver_pin.get_raw_nid(), e.driver_pin.get_port_id(), e.driver_pin.get_pin_pid()); + out.sink = Pin_hier(std::move(hier_ref), hier_pos, e.sink_pin.get_raw_nid(), e.sink_pin.get_port_id(), e.sink_pin.get_pin_pid()); + return out; +} + +auto to_class(const Edge_flat& e) -> Edge_class { + Edge_class out{}; + out.driver_pin = to_class(e.driver); + out.sink_pin = to_class(e.sink); + out.driver = Node_class(nullptr, out.driver_pin.get_raw_nid() | static_cast(2)); + out.sink = Node_class(nullptr, out.sink_pin.get_raw_nid() & ~static_cast(2)); + out.type = 2; // p -> p + return out; +} + +auto to_class(const Edge_hier& e) -> Edge_class { + Edge_class out{}; + out.driver_pin = to_class(e.driver); + out.sink_pin = to_class(e.sink); + out.driver = Node_class(nullptr, out.driver_pin.get_raw_nid() | static_cast(2)); + out.sink = Node_class(nullptr, out.sink_pin.get_raw_nid() & ~static_cast(2)); + out.type = 2; // p -> p + return out; +} + Pin::Pin() : master_nid(0), port_id(0), next_pin_id(0), ledge0(0), ledge1(0), use_overflow(0), sedges_{.set = nullptr} {} Pin::Pin(Nid mn, Port_id pid) @@ -151,17 +265,12 @@ auto Pin::delete_edge(Pid self_id, Vid other_id) -> bool { return pin->sedges_.set->erase(edge) != 0; } else { // delete from packed edges - constexpr int SHIFT = 14; - constexpr uint64_t SLOT_MASK = (1ULL << SHIFT) - 1; + constexpr int SHIFT = 14; + constexpr uint64_t SLOT = (1ULL << SHIFT) - 1; for (int i = 0; i < 4; ++i) { - // Extract the i-th packed slot from sedges_.sedges - uint64_t packed = (static_cast(pin->sedges_.sedges) >> (i * SHIFT)) & SLOT_MASK; - if (packed == 0) { - continue; - } - if ((packed >> 2) == (static_cast(other_id) >> 2)) { - // Clear the matching slot - pin->sedges_.sedges &= ~(static_cast(SLOT_MASK) << (i * SHIFT)); + uint64_t e = edge & (SLOT << (i * SHIFT)); + if (e >> 2 == (other_id >> 2)) { + pin->sedges_.sedges &= ~(static_cast(SLOT) << (i * SHIFT)); return true; } } @@ -598,7 +707,7 @@ void Graph::clear_graph() { node_table.emplace_back(2); // Output node node_table.emplace_back(3); // Constant (common value/issue to handle for toposort) - Each const value is a pin in node3 pin_table.emplace_back(0, 0); - invalidate_fast_class_cache(); + invalidate_traversal_caches(); } void Graph::bind_library(const GraphLibrary* owner, Gid self_gid) noexcept { @@ -606,7 +715,18 @@ void Graph::bind_library(const GraphLibrary* owner, Gid self_gid) noexcept { self_gid_ = self_gid; } -void Graph::invalidate_fast_class_cache() noexcept { fast_class_cache_valid_ = false; } +void Graph::invalidate_traversal_caches() noexcept { + fast_class_cache_valid_ = false; + fast_hier_cache_valid_ = false; + forward_class_cache_valid_ = false; + forward_flat_cache_valid_ = false; + forward_hier_cache_valid_ = false; + fast_hier_tree_cache_.reset(); + forward_hier_tree_cache_.reset(); + if (owner_lib_ != nullptr) { + owner_lib_->note_graph_mutation(); + } +} void Graph::rebuild_fast_class_cache() const { fast_class_cache_.clear(); @@ -629,6 +749,217 @@ void Graph::rebuild_fast_flat_cache() const { } } +void Graph::rebuild_fast_hier_cache() const { + fast_hier_cache_.clear(); + fast_hier_tree_cache_ = std::make_shared>(); + + Gid root_gid = self_gid_; + if (root_gid == Gid_invalid) { + root_gid = 0; + } + const Tree_pos root_pos = fast_hier_tree_cache_->add_root(root_gid); + + ankerl::unordered_dense::set active_graphs; + if (self_gid_ != Gid_invalid) { + active_graphs.insert(self_gid_); + } + fast_hier_impl(fast_hier_tree_cache_, root_pos, active_graphs, fast_hier_cache_); + + fast_hier_cache_epoch_ = owner_lib_ != nullptr ? owner_lib_->mutation_epoch() : 0; + fast_hier_cache_valid_ = true; +} + +void Graph::rebuild_forward_class_cache() const { + forward_class_cache_.clear(); + if (node_table.size() <= 1) { + forward_class_cache_valid_ = true; + return; + } + + const size_t node_count = node_table.size(); + std::vector> adjacency(node_count); + std::vector indegree(node_count, 0); + + auto add_dependency = [&](size_t driver_idx, Vid sink_vid) { + Nid sink_nid = 0; + if (sink_vid & static_cast(1)) { + const Pid sink_pid = (static_cast(sink_vid) & ~static_cast(2)) | static_cast(1); + sink_nid = ref_pin(sink_pid)->get_master_nid(); + } else { + sink_nid = static_cast(sink_vid); + } + + sink_nid &= ~static_cast(3); + const size_t sink_idx = static_cast(sink_nid >> 2); + if (sink_idx == 0 || sink_idx >= node_count) { + return; + } + + if (adjacency[driver_idx].insert(sink_idx).second) { + ++indegree[sink_idx]; + } + }; + + for (size_t driver_idx = 1; driver_idx < node_count; ++driver_idx) { + const Nid driver_nid = static_cast(driver_idx) << 2; + + auto node_edges = node_table[driver_idx].get_edges(driver_nid); + for (auto vid : node_edges) { + if (vid & static_cast(2)) { + continue; + } + add_dependency(driver_idx, vid); + } + + for (Pid pin_vid = node_table[driver_idx].get_next_pin_id(); pin_vid != 0;) { + const Pid canonical_pin = (pin_vid & ~static_cast(2)) | static_cast(1); + auto pin_edges = ref_pin(canonical_pin)->get_edges(canonical_pin); + for (auto vid : pin_edges) { + if (vid & static_cast(2)) { + continue; + } + add_dependency(driver_idx, vid); + } + pin_vid = ref_pin(canonical_pin)->get_next_pin_id(); + } + } + + std::priority_queue, std::greater> ready; + for (size_t idx = 1; idx < node_count; ++idx) { + if (indegree[idx] == 0) { + ready.push(idx); + } + } + + std::vector emitted(node_count, false); + forward_class_cache_.reserve(node_count - 1); + while (!ready.empty()) { + const size_t node_idx = ready.top(); + ready.pop(); + if (emitted[node_idx]) { + continue; + } + + emitted[node_idx] = true; + forward_class_cache_.emplace_back(const_cast(this), static_cast(node_idx) << 2); + + for (auto sink_idx : adjacency[node_idx]) { + if (indegree[sink_idx] == 0) { + continue; + } + --indegree[sink_idx]; + if (indegree[sink_idx] == 0) { + ready.push(sink_idx); + } + } + } + + // Preserve determinism under cycles by appending unresolved nodes by ID. + for (size_t idx = 1; idx < node_count; ++idx) { + if (!emitted[idx]) { + forward_class_cache_.emplace_back(const_cast(this), static_cast(idx) << 2); + } + } + + forward_class_cache_valid_ = true; +} + +void Graph::forward_flat_impl(Gid top_graph, ankerl::unordered_dense::set& active_graphs, std::vector& out) const { + for (const auto& node : forward_class()) { + const Nid node_nid = node.get_raw_nid(); + const auto& node_ref = node_table[static_cast(node_nid >> 2)]; + + if (node_ref.has_subnode() && owner_lib_ != nullptr) { + const Gid other_graph_id = node_ref.get_subnode(); + if (owner_lib_->has_graph(other_graph_id) && active_graphs.find(other_graph_id) == active_graphs.end()) { + active_graphs.insert(other_graph_id); + owner_lib_->get_graph(other_graph_id).forward_flat_impl(top_graph, active_graphs, out); + active_graphs.erase(other_graph_id); + continue; + } + } + + out.emplace_back(top_graph, self_gid_, node_nid); + } +} + +void Graph::fast_hier_impl(std::shared_ptr> hier_ref, int64_t hier_pos, ankerl::unordered_dense::set& active_graphs, + std::vector& out) const { + for (size_t i = 1; i < node_table.size(); ++i) { + const Nid node_id = static_cast(i) << 2; + const auto& node = node_table[i]; + + if (node.has_subnode() && owner_lib_ != nullptr) { + const Gid other_graph_id = node.get_subnode(); + if (owner_lib_->has_graph(other_graph_id) && active_graphs.find(other_graph_id) == active_graphs.end()) { + const Tree_pos child_hier_pos = hier_ref->add_child(hier_pos, other_graph_id); + active_graphs.insert(other_graph_id); + owner_lib_->get_graph(other_graph_id).fast_hier_impl(hier_ref, child_hier_pos, active_graphs, out); + active_graphs.erase(other_graph_id); + continue; + } + } + + out.emplace_back(hier_ref, hier_pos, node_id); + } +} + +void Graph::forward_hier_impl(std::shared_ptr> hier_ref, int64_t hier_pos, + ankerl::unordered_dense::set& active_graphs, std::vector& out) const { + for (const auto& node : forward_class()) { + const Nid node_nid = node.get_raw_nid(); + const auto& node_ref = node_table[static_cast(node_nid >> 2)]; + + if (node_ref.has_subnode() && owner_lib_ != nullptr) { + const Gid other_graph_id = node_ref.get_subnode(); + if (owner_lib_->has_graph(other_graph_id) && active_graphs.find(other_graph_id) == active_graphs.end()) { + const Tree_pos child_hier_pos = hier_ref->add_child(hier_pos, other_graph_id); + active_graphs.insert(other_graph_id); + owner_lib_->get_graph(other_graph_id).forward_hier_impl(hier_ref, child_hier_pos, active_graphs, out); + active_graphs.erase(other_graph_id); + continue; + } + } + + out.emplace_back(hier_ref, hier_pos, node_nid); + } +} + +void Graph::rebuild_forward_flat_cache() const { + forward_flat_cache_.clear(); + + const Gid top_graph = self_gid_; + ankerl::unordered_dense::set active_graphs; + if (self_gid_ != Gid_invalid) { + active_graphs.insert(self_gid_); + } + forward_flat_impl(top_graph, active_graphs, forward_flat_cache_); + + forward_flat_cache_epoch_ = owner_lib_ != nullptr ? owner_lib_->mutation_epoch() : 0; + forward_flat_cache_valid_ = true; +} + +void Graph::rebuild_forward_hier_cache() const { + forward_hier_cache_.clear(); + forward_hier_tree_cache_ = std::make_shared>(); + + Gid root_gid = self_gid_; + if (root_gid == Gid_invalid) { + root_gid = 0; + } + const Tree_pos root_pos = forward_hier_tree_cache_->add_root(root_gid); + + ankerl::unordered_dense::set active_graphs; + if (self_gid_ != Gid_invalid) { + active_graphs.insert(self_gid_); + } + + forward_hier_impl(forward_hier_tree_cache_, root_pos, active_graphs, forward_hier_cache_); + + forward_hier_cache_epoch_ = owner_lib_ != nullptr ? owner_lib_->mutation_epoch() : 0; + forward_hier_cache_valid_ = true; +} + auto Graph::fast_iter(bool hierarchy, Gid top_graph, uint32_t tree_node_num) const -> std::vector { if (top_graph == 0) { top_graph = self_gid_; @@ -677,24 +1008,58 @@ auto Graph::create_node() -> Node_class { Nid id = node_table.size(); assert(id); node_table.emplace_back(id); - invalidate_fast_class_cache(); + invalidate_traversal_caches(); Nid raw_nid = id << 2 | 0; return Node_class(this, raw_nid); } auto Graph::create_pin(Nid nid, Port_id pid) -> Pid { // nid is << 2 here but port_id is not << 2 id (here but pin id in actual) is also not << 2 + nid &= ~static_cast(2); // Pin ownership is by node identity, independent of edge role bit. Pid id = pin_table.size(); assert(id); pin_table.emplace_back(nid, pid); set_next_pin(nid, id); + invalidate_traversal_caches(); return id << 2 | 1; } -auto Node_class::create_pin(Port_id port_id_value) -> Pin_class { +auto Graph::make_pin_class(Pid pin_pid) const -> Pin_class { + const auto* pin = ref_pin(pin_pid); + return Pin_class(const_cast(this), pin->get_master_nid(), pin->get_port_id(), pin_pid); +} + +auto Pin_class::get_master_node() const -> Node_class { + if (graph != nullptr && pin_pid != 0) { + const auto* pin = graph->ref_pin(pin_pid); + return Node_class(graph, pin->get_master_nid()); + } + return Node_class(graph, raw_nid); +} + +auto Node_class::create_pin(Port_id port_id_value) const -> Pin_class { assert(graph != nullptr); const Pid pin_pid = graph->create_pin(raw_nid, port_id_value); - return Pin_class(raw_nid, port_id_value, pin_pid); + return Pin_class(graph, raw_nid, port_id_value, pin_pid); +} + +void Graph::set_subnode(Node_class node, Gid gid) { set_subnode(node.get_raw_nid(), gid); } + +void Graph::set_subnode(Nid nid, Gid gid) { + if (gid == Gid_invalid) { + return; + } + + if (owner_lib_ != nullptr) { + assert(owner_lib_->has_graph(gid)); + if (!owner_lib_->has_graph(gid)) { + return; + } + } + + nid &= ~static_cast(3); + ref_node(nid)->set_subnode(gid); + invalidate_traversal_caches(); } void Graph::add_edge(Vid driver_id, Vid sink_id) { @@ -702,15 +1067,28 @@ void Graph::add_edge(Vid driver_id, Vid sink_id) { sink_id = sink_id & ~2; add_edge_int(driver_id, sink_id); add_edge_int(sink_id, driver_id); + invalidate_traversal_caches(); } -void Graph::del_edge(Node_class node1, Node_class node2) { del_edge_int(node1.get_raw_nid(), node2.get_raw_nid()); } +void Graph::del_edge(Node_class node1, Node_class node2) { + del_edge_int(node1.get_raw_nid(), node2.get_raw_nid()); + invalidate_traversal_caches(); +} -void Graph::del_edge(Node_class node, Pin_class pin) { del_edge_int(node.get_raw_nid(), pin.get_pin_pid()); } +void Graph::del_edge(Node_class node, Pin_class pin) { + del_edge_int(node.get_raw_nid(), pin.get_pin_pid()); + invalidate_traversal_caches(); +} -void Graph::del_edge(Pin_class pin, Node_class node) { del_edge_int(pin.get_pin_pid(), node.get_raw_nid()); } +void Graph::del_edge(Pin_class pin, Node_class node) { + del_edge_int(pin.get_pin_pid(), node.get_raw_nid()); + invalidate_traversal_caches(); +} -void Graph::del_edge(Pin_class pin1, Pin_class pin2) { del_edge_int(pin1.get_pin_pid(), pin2.get_pin_pid()); } +void Graph::del_edge(Pin_class pin1, Pin_class pin2) { + del_edge_int(pin1.get_pin_pid(), pin2.get_pin_pid()); + invalidate_traversal_caches(); +} auto Graph::fast_class() const -> std::span { const size_t expected_size = node_table.size() > 0 ? node_table.size() - 1 : 0; @@ -720,11 +1098,43 @@ auto Graph::fast_class() const -> std::span { return std::span(fast_class_cache_.data(), fast_class_cache_.size()); } +auto Graph::forward_class() const -> std::span { + const size_t expected_size = node_table.size() > 0 ? node_table.size() - 1 : 0; + if (!forward_class_cache_valid_ || forward_class_cache_.size() != expected_size) { + rebuild_forward_class_cache(); + } + return std::span(forward_class_cache_.data(), forward_class_cache_.size()); +} + auto Graph::fast_flat() const -> std::span { rebuild_fast_flat_cache(); return std::span(fast_flat_cache_.data(), fast_flat_cache_.size()); } +auto Graph::fast_hier() const -> std::span { + const uint64_t expected_epoch = owner_lib_ != nullptr ? owner_lib_->mutation_epoch() : 0; + if (!fast_hier_cache_valid_ || (owner_lib_ != nullptr && fast_hier_cache_epoch_ != expected_epoch)) { + rebuild_fast_hier_cache(); + } + return std::span(fast_hier_cache_.data(), fast_hier_cache_.size()); +} + +auto Graph::forward_flat() const -> std::span { + const uint64_t expected_epoch = owner_lib_ != nullptr ? owner_lib_->mutation_epoch() : 0; + if (!forward_flat_cache_valid_ || (owner_lib_ != nullptr && forward_flat_cache_epoch_ != expected_epoch)) { + rebuild_forward_flat_cache(); + } + return std::span(forward_flat_cache_.data(), forward_flat_cache_.size()); +} + +auto Graph::forward_hier() const -> std::span { + const uint64_t expected_epoch = owner_lib_ != nullptr ? owner_lib_->mutation_epoch() : 0; + if (!forward_hier_cache_valid_ || (owner_lib_ != nullptr && forward_hier_cache_epoch_ != expected_epoch)) { + rebuild_forward_hier_cache(); + } + return std::span(forward_hier_cache_.data(), forward_hier_cache_.size()); +} + void Graph::del_edge_int(Vid driver_id, Vid sink_id) { if (driver_id & 1) { (void)ref_pin(driver_id)->delete_edge(driver_id, sink_id); @@ -741,9 +1151,10 @@ void Graph::del_edge_int(Vid driver_id, Vid sink_id) { auto Graph::out_edges(Node_class node) -> std::vector { std::vector out; - const Nid self_nid = node.get_raw_nid(); + const Nid self_nid = node.get_raw_nid() & ~static_cast(2); auto* self = ref_node(self_nid); auto edges = self->get_edges(self_nid); + const Node_class self_driver(this, self_nid | static_cast(2)); for (auto vid : edges) { if (vid & 2) { @@ -754,8 +1165,8 @@ auto Graph::out_edges(Node_class node) -> std::vector { Edge_class e{}; e.type = 3; // n -> p - e.driver = node; - e.sink_pin = Pin_class(self_nid, 0, sink_pid); // keep your existing ctor usage + e.driver = self_driver; + e.sink_pin = make_pin_class(sink_pid); // e.sink / e.driver_pin remain default out.push_back(e); continue; @@ -764,7 +1175,7 @@ auto Graph::out_edges(Node_class node) -> std::vector { Edge_class e{}; e.type = 1; // n -> n - e.driver = node; + e.driver = self_driver; e.sink = Node_class(this, sink_nid); // keep your existing ctor usage // e.driver_pin / e.sink_pin remain default out.push_back(e); @@ -775,9 +1186,10 @@ auto Graph::out_edges(Node_class node) -> std::vector { auto Graph::inp_edges(Node_class node) -> std::vector { std::vector out; - const Nid self_nid = node.get_raw_nid(); + const Nid self_nid = node.get_raw_nid() & ~static_cast(2); auto* self = ref_node(self_nid); auto edges = self->get_edges(self_nid); + const Node_class self_sink(this, self_nid & ~static_cast(2)); for (auto vid : edges) { if (!(vid & 2)) { @@ -788,8 +1200,8 @@ auto Graph::inp_edges(Node_class node) -> std::vector { Edge_class e{}; e.type = 4; // p -> n - e.driver_pin = Pin_class(self_nid, 0, driver_pid); - e.sink = node; + e.driver_pin = make_pin_class(driver_pid); + e.sink = self_sink; // e.driver / e.sink_pin remain default out.push_back(e); continue; @@ -799,7 +1211,7 @@ auto Graph::inp_edges(Node_class node) -> std::vector { Edge_class e{}; e.type = 1; // n -> n e.driver = Node_class(this, driver_nid); - e.sink = node; + e.sink = self_sink; // e.driver_pin / e.sink_pin remain default out.push_back(e); } @@ -807,8 +1219,129 @@ auto Graph::inp_edges(Node_class node) -> std::vector { return out; } +auto Graph::out_edges(Pin_class pin) -> std::vector { + std::vector out; + const Pid self_pid = pin.get_pin_pid(); + const Pid self_pid_lookup = (self_pid & ~static_cast(2)) | static_cast(1); + auto* self = ref_pin(self_pid_lookup); + auto edges = self->get_edges(self_pid_lookup); + const Pin_class self_driver_pin = make_pin_class(self_pid_lookup | static_cast(2)); + + for (auto vid : edges) { + if (vid & 2) { + continue; + } + if (vid & 1) { + const Pid sink_pid = static_cast(vid); + + Edge_class e{}; + e.type = 2; // p -> p + e.driver_pin = self_driver_pin; + e.sink_pin = make_pin_class(sink_pid); + out.push_back(e); + continue; + } + + const Nid sink_nid = static_cast(vid); + + Edge_class e{}; + e.type = 4; // p -> n + e.driver_pin = self_driver_pin; + e.sink = Node_class(this, sink_nid); + out.push_back(e); + } + + return out; +} + +auto Graph::inp_edges(Pin_class pin) -> std::vector { + std::vector out; + const Pid self_pid = pin.get_pin_pid(); + const Pid self_pid_sink = (self_pid & ~static_cast(2)) | static_cast(1); + auto* self = ref_pin(self_pid_sink); + auto edges = self->get_edges(self_pid_sink); + const Pin_class self_sink_pin = make_pin_class(self_pid_sink); + + for (auto vid : edges) { + if (!(vid & 2)) { + continue; + } + if (vid & 1) { + const Pid driver_pid = static_cast(vid); + + Edge_class e{}; + e.type = 2; // p -> p + e.driver_pin = make_pin_class(driver_pid); + e.sink_pin = self_sink_pin; + out.push_back(e); + continue; + } + + const Nid driver_nid = static_cast(vid); + + Edge_class e{}; + e.type = 3; // n -> p + e.driver = Node_class(this, driver_nid); + e.sink_pin = self_sink_pin; + out.push_back(e); + } + + return out; +} + +auto Graph::get_pins(Node_class node) -> std::vector { + std::vector out; + const Nid self_nid = node.get_raw_nid() & ~static_cast(2); + auto* self = ref_node(self_nid); + + Pid cur_pin = self->get_next_pin_id(); + while (cur_pin != 0) { + const Pid canonical_pin = (cur_pin & ~static_cast(2)) | static_cast(1); + out.push_back(make_pin_class(canonical_pin)); + cur_pin = ref_pin(canonical_pin)->get_next_pin_id(); + } + + return out; +} + +auto Graph::get_driver_pins(Node_class node) -> std::vector { + std::vector out; + for (const auto& pin : get_pins(node)) { + const Pid pid_lookup = (pin.get_pin_pid() & ~static_cast(2)) | static_cast(1); + auto* self = ref_pin(pid_lookup); + auto edges = self->get_edges(pid_lookup); + + for (auto vid : edges) { + // Driver pin: edge is outgoing (bit1=0) and target is a pin (bit0=1). + if (!(vid & static_cast(2)) && (vid & static_cast(1))) { + out.push_back(make_pin_class(pid_lookup)); + break; + } + } + } + return out; +} + +auto Graph::get_sink_pins(Node_class node) -> std::vector { + std::vector out; + for (const auto& pin : get_pins(node)) { + const Pid pid_lookup = (pin.get_pin_pid() & ~static_cast(2)) | static_cast(1); + auto* self = ref_pin(pid_lookup); + auto edges = self->get_edges(pid_lookup); + + for (auto vid : edges) { + // Sink pin: edge is incoming (bit1=1) and source is a pin (bit0=1). + if ((vid & static_cast(3)) == static_cast(3)) { + out.push_back(make_pin_class(pid_lookup)); + break; + } + } + } + return out; +} + void Graph::delete_node(Nid nid) { - invalidate_fast_class_cache(); + invalidate_traversal_caches(); auto* node = ref_node(nid); if (!node) { diff --git a/hhds/graph.hpp b/hhds/graph.hpp index 84d2066..0d1e077 100644 --- a/hhds/graph.hpp +++ b/hhds/graph.hpp @@ -153,21 +153,36 @@ class __attribute__((packed)) Node { static_assert(sizeof(Node) == 32, "Node size mismatch"); class Graph; +class Node_class; +template +class tree; class Pin_class { public: Pin_class() = default; + Pin_class(Graph* graph_value, Nid raw_nid_value, Port_id port_id_value, Pid pin_pid_value) + : graph(graph_value), raw_nid(raw_nid_value & ~static_cast(2)), port_id(port_id_value), pin_pid(pin_pid_value) {} Pin_class(Nid raw_nid_value, Port_id port_id_value, Pid pin_pid_value) - : raw_nid(raw_nid_value), port_id(port_id_value), pin_pid(pin_pid_value) {} + : raw_nid(raw_nid_value & ~static_cast(2)), port_id(port_id_value), pin_pid(pin_pid_value) {} - [[nodiscard]] Nid get_raw_nid() const noexcept { return raw_nid; } - [[nodiscard]] Pid get_pin_pid() const noexcept { return pin_pid; } - [[nodiscard]] Port_id get_port_id() const noexcept { return port_id; } + [[nodiscard]] Node_class get_master_node() const; + [[nodiscard]] Nid get_raw_nid() const noexcept { return raw_nid; } + [[nodiscard]] Pid get_pin_pid() const noexcept { return pin_pid; } + [[nodiscard]] Port_id get_port_id() const noexcept { return port_id; } // Interop with existing APIs that still accept raw Pid. [[nodiscard]] operator Pid() const noexcept { return pin_pid; } + [[nodiscard]] bool operator==(const Pin_class& other) const noexcept { return graph == other.graph && pin_pid == other.pin_pid; } + [[nodiscard]] bool operator!=(const Pin_class& other) const noexcept { return !(*this == other); } + + template + friend H AbslHashValue(H h, const Pin_class& pin) { + return H::combine(std::move(h), pin.graph, pin.pin_pid); + } + private: + Graph* graph = nullptr; Nid raw_nid = 0; Port_id port_id = 0; Pid pin_pid = 0; @@ -180,7 +195,7 @@ class Node_class { [[nodiscard]] Port_id get_port_id() const noexcept { return 0; } [[nodiscard]] Nid get_raw_nid() const noexcept { return raw_nid; } - [[nodiscard]] Pin_class create_pin(Port_id port_id); + [[nodiscard]] Pin_class create_pin(Port_id port_id) const; // Interop with existing APIs that still accept raw Nid. [[nodiscard]] operator Nid() const noexcept { return raw_nid; } @@ -225,6 +240,101 @@ class Node_flat { Nid raw_nid = 0; }; +class Pin_flat { +public: + Pin_flat() = default; + Pin_flat(Gid root_gid_value, Gid current_gid_value, Nid raw_nid_value, Port_id port_id_value, Pid pin_pid_value) + : root_gid(root_gid_value) + , current_gid(current_gid_value) + , raw_nid(raw_nid_value & ~static_cast(2)) + , port_id(port_id_value) + , pin_pid(pin_pid_value) {} + + [[nodiscard]] Gid get_root_gid() const noexcept { return root_gid; } + [[nodiscard]] Gid get_current_gid() const noexcept { return current_gid; } + [[nodiscard]] Nid get_raw_nid() const noexcept { return raw_nid; } + [[nodiscard]] Port_id get_port_id() const noexcept { return port_id; } + [[nodiscard]] Pid get_pin_pid() const noexcept { return pin_pid; } + + [[nodiscard]] bool operator==(const Pin_flat& other) const noexcept { + return root_gid == other.root_gid && current_gid == other.current_gid && pin_pid == other.pin_pid; + } + [[nodiscard]] bool operator!=(const Pin_flat& other) const noexcept { return !(*this == other); } + + template + friend H AbslHashValue(H h, const Pin_flat& pin) { + return H::combine(std::move(h), pin.root_gid, pin.current_gid, pin.pin_pid); + } + +private: + Gid root_gid = Gid_invalid; + Gid current_gid = Gid_invalid; + Nid raw_nid = 0; + Port_id port_id = 0; + Pid pin_pid = 0; +}; + +class Node_hier { +public: + Node_hier() = default; + Node_hier(std::shared_ptr> hier_ref_value, int64_t hier_pos_value, Nid raw_nid_value); + + [[nodiscard]] Gid get_root_gid() const noexcept; + [[nodiscard]] Gid get_current_gid() const noexcept; + [[nodiscard]] Port_id get_port_id() const noexcept { return 0; } + [[nodiscard]] Nid get_raw_nid() const noexcept { return raw_nid; } + + [[nodiscard]] bool operator==(const Node_hier& other) const noexcept { + return hier_ref.get() == other.hier_ref.get() && hier_pos == other.hier_pos && raw_nid == other.raw_nid; + } + [[nodiscard]] bool operator!=(const Node_hier& other) const noexcept { return !(*this == other); } + + template + friend H AbslHashValue(H h, const Node_hier& node) { + return H::combine(std::move(h), node.hier_ref.get(), node.hier_pos, node.raw_nid); + } + +private: + std::shared_ptr> hier_ref; + int64_t hier_pos = 0; + Nid raw_nid = 0; +}; + +class Pin_hier { +public: + Pin_hier() = default; + Pin_hier(std::shared_ptr> hier_ref_value, int64_t hier_pos_value, Nid raw_nid_value, Port_id port_id_value, + Pid pin_pid_value) + : hier_ref(std::move(hier_ref_value)) + , hier_pos(hier_pos_value) + , raw_nid(raw_nid_value & ~static_cast(2)) + , port_id(port_id_value) + , pin_pid(pin_pid_value) {} + + [[nodiscard]] Gid get_root_gid() const noexcept; + [[nodiscard]] Gid get_current_gid() const noexcept; + [[nodiscard]] Nid get_raw_nid() const noexcept { return raw_nid; } + [[nodiscard]] Port_id get_port_id() const noexcept { return port_id; } + [[nodiscard]] Pid get_pin_pid() const noexcept { return pin_pid; } + + [[nodiscard]] bool operator==(const Pin_hier& other) const noexcept { + return hier_ref.get() == other.hier_ref.get() && hier_pos == other.hier_pos && pin_pid == other.pin_pid; + } + [[nodiscard]] bool operator!=(const Pin_hier& other) const noexcept { return !(*this == other); } + + template + friend H AbslHashValue(H h, const Pin_hier& pin) { + return H::combine(std::move(h), pin.hier_ref.get(), pin.hier_pos, pin.pin_pid); + } + +private: + std::shared_ptr> hier_ref; + int64_t hier_pos = 0; + Nid raw_nid = 0; + Port_id port_id = 0; + Pid pin_pid = 0; +}; + class Edge_class { public: Node_class driver; @@ -240,6 +350,34 @@ class Edge_class { uint8_t type : 3; }; +class Edge_flat { +public: + Pin_flat driver; + Pin_flat sink; + + [[nodiscard]] bool operator==(const Edge_flat& other) const noexcept { return driver == other.driver && sink == other.sink; } + [[nodiscard]] bool operator!=(const Edge_flat& other) const noexcept { return !(*this == other); } + + template + friend H AbslHashValue(H h, const Edge_flat& edge) { + return H::combine(std::move(h), edge.driver, edge.sink); + } +}; + +class Edge_hier { +public: + Pin_hier driver; + Pin_hier sink; + + [[nodiscard]] bool operator==(const Edge_hier& other) const noexcept { return driver == other.driver && sink == other.sink; } + [[nodiscard]] bool operator!=(const Edge_hier& other) const noexcept { return !(*this == other); } + + template + friend H AbslHashValue(H h, const Edge_hier& edge) { + return H::combine(std::move(h), edge.driver, edge.sink); + } +}; + class GraphLibrary; class Graph { @@ -266,6 +404,8 @@ class Graph { void add_edge(Node_class driver_node, Pin_class sink_pin) { add_edge(driver_node.get_raw_nid(), sink_pin.get_pin_pid()); } void add_edge(Pin_class driver_pin, Node_class sink_node) { add_edge(driver_pin.get_pin_pid(), sink_node.get_raw_nid()); } void add_edge(Pin_class driver_pin, Pin_class sink_pin) { add_edge(driver_pin.get_pin_pid(), sink_pin.get_pin_pid()); } + void set_subnode(Node_class node, Gid gid); + void set_subnode(Nid nid, Gid gid); void add_edge(Pid driver_id, Pid sink_id); void del_edge(Node_class node1, Node_class node2); @@ -274,8 +414,17 @@ class Graph { void del_edge(Pin_class pin1, Pin_class pin2); [[nodiscard]] std::vector out_edges(Node_class node); [[nodiscard]] std::vector inp_edges(Node_class node); + [[nodiscard]] std::vector out_edges(Pin_class pin); + [[nodiscard]] std::vector inp_edges(Pin_class pin); + [[nodiscard]] std::vector get_pins(Node_class node); + [[nodiscard]] std::vector get_driver_pins(Node_class node); + [[nodiscard]] std::vector get_sink_pins(Node_class node); [[nodiscard]] auto fast_class() const -> std::span; + [[nodiscard]] auto forward_class() const -> std::span; [[nodiscard]] auto fast_flat() const -> std::span; + [[nodiscard]] auto forward_flat() const -> std::span; + [[nodiscard]] auto fast_hier() const -> std::span; + [[nodiscard]] auto forward_hier() const -> std::span; void delete_node(Nid nid); void display_graph() const; void display_next_pin_of_node() const; @@ -300,23 +449,46 @@ class Graph { [[nodiscard]] std::vector fast_iter(bool hierarchy, Gid top_graph = 0, uint32_t tree_node_num = 0) const; private: - void del_edge_int(Vid driver_id, Vid sink_id); - void add_edge_int(Pid self_id, Pid other_id); - void set_next_pin(Nid nid, Pid next_pin); - void bind_library(const GraphLibrary* owner, Gid self_gid) noexcept; - void invalidate_fast_class_cache() noexcept; - void rebuild_fast_class_cache() const; - void rebuild_fast_flat_cache() const; - void fast_iter_impl(bool hierarchy, Gid top_graph, uint32_t tree_node_num, uint32_t& next_tree_node_num, - ankerl::unordered_dense::set& active_graphs, std::vector& out) const; - - std::vector node_table; - std::vector pin_table; - mutable std::vector fast_class_cache_; - mutable std::vector fast_flat_cache_; - mutable bool fast_class_cache_valid_ = false; - const GraphLibrary* owner_lib_ = nullptr; - Gid self_gid_ = Gid_invalid; + void del_edge_int(Vid driver_id, Vid sink_id); + void add_edge_int(Pid self_id, Pid other_id); + void set_next_pin(Nid nid, Pid next_pin); + [[nodiscard]] Pin_class make_pin_class(Pid pin_pid) const; + void bind_library(const GraphLibrary* owner, Gid self_gid) noexcept; + void invalidate_traversal_caches() noexcept; + void rebuild_fast_class_cache() const; + void rebuild_fast_flat_cache() const; + void rebuild_fast_hier_cache() const; + void rebuild_forward_class_cache() const; + void rebuild_forward_flat_cache() const; + void rebuild_forward_hier_cache() const; + void fast_iter_impl(bool hierarchy, Gid top_graph, uint32_t tree_node_num, uint32_t& next_tree_node_num, + ankerl::unordered_dense::set& active_graphs, std::vector& out) const; + void fast_hier_impl(std::shared_ptr> hier_ref, int64_t hier_pos, ankerl::unordered_dense::set& active_graphs, + std::vector& out) const; + void forward_flat_impl(Gid top_graph, ankerl::unordered_dense::set& active_graphs, std::vector& out) const; + void forward_hier_impl(std::shared_ptr> hier_ref, int64_t hier_pos, ankerl::unordered_dense::set& active_graphs, + std::vector& out) const; + + std::vector node_table; + std::vector pin_table; + mutable std::vector fast_class_cache_; + mutable std::vector fast_flat_cache_; + mutable std::vector fast_hier_cache_; + mutable std::vector forward_class_cache_; + mutable std::vector forward_flat_cache_; + mutable std::vector forward_hier_cache_; + mutable std::shared_ptr> fast_hier_tree_cache_; + mutable std::shared_ptr> forward_hier_tree_cache_; + mutable bool fast_class_cache_valid_ = false; + mutable bool fast_hier_cache_valid_ = false; + mutable bool forward_class_cache_valid_ = false; + mutable bool forward_flat_cache_valid_ = false; + mutable bool forward_hier_cache_valid_ = false; + mutable uint64_t fast_hier_cache_epoch_ = 0; + mutable uint64_t forward_flat_cache_epoch_ = 0; + mutable uint64_t forward_hier_cache_epoch_ = 0; + const GraphLibrary* owner_lib_ = nullptr; + Gid self_gid_ = Gid_invalid; friend class GraphLibrary; }; @@ -336,6 +508,7 @@ class GraphLibrary { graphs_.push_back(std::make_unique()); graphs_.back()->bind_library(this, id); ++live_count_; + note_graph_mutation(); return id; } @@ -354,6 +527,8 @@ class GraphLibrary { return *graphs_[static_cast(id)]; } + [[nodiscard]] uint64_t mutation_epoch() const noexcept { return mutation_epoch_; } + // Tombstone-delete (IDs are not reused). void delete_graph(Gid id) noexcept { if (static_cast(id) >= graphs_.size()) { @@ -363,6 +538,7 @@ class GraphLibrary { if (slot) { slot.reset(); --live_count_; + note_graph_mutation(); } } @@ -373,9 +549,36 @@ class GraphLibrary { [[nodiscard]] Gid live_count() const noexcept { return live_count_; } private: + void note_graph_mutation() const noexcept { ++mutation_epoch_; } + std::vector> graphs_; // count of live graphs - Gid live_count_ = 0; + Gid live_count_ = 0; + mutable uint64_t mutation_epoch_ = 1; + + friend class Graph; }; +// Compact-tier conversions: information can be discarded (_hier/_flat -> _class), +// but hierarchy context cannot be reconstructed from _class/_flat alone. +[[nodiscard]] Node_class to_class(const Node_hier& v); +[[nodiscard]] Node_flat to_flat(const Node_hier& v); +[[nodiscard]] Node_class to_class(const Node_flat& v); +[[nodiscard]] Node_flat to_flat(const Node_class& v, Gid current_gid, Gid root_gid = Gid_invalid); +Node_hier to_hier(Node_class) = delete; +Node_hier to_hier(Node_flat) = delete; + +[[nodiscard]] Pin_class to_class(const Pin_hier& v); +[[nodiscard]] Pin_flat to_flat(const Pin_hier& v); +[[nodiscard]] Pin_class to_class(const Pin_flat& v); +[[nodiscard]] Pin_flat to_flat(const Pin_class& v, Gid current_gid, Gid root_gid = Gid_invalid); +Pin_hier to_hier(Pin_class) = delete; +Pin_hier to_hier(Pin_flat) = delete; + +[[nodiscard]] Edge_flat to_flat(const Edge_class& e, Gid current_gid, Gid root_gid = Gid_invalid); +[[nodiscard]] Edge_flat to_flat(const Edge_hier& e); +[[nodiscard]] Edge_hier to_hier(const Edge_class& e, std::shared_ptr> hier_ref, int64_t hier_pos); +[[nodiscard]] Edge_class to_class(const Edge_flat& e); +[[nodiscard]] Edge_class to_class(const Edge_hier& e); + } // namespace hhds diff --git a/hhds/tests/graph_test.cpp b/hhds/tests/graph_test.cpp index 5d630ba..378ab36 100644 --- a/hhds/tests/graph_test.cpp +++ b/hhds/tests/graph_test.cpp @@ -514,15 +514,15 @@ void test_fast_iter_hierarchy() { Graph& child = lib.get_graph(child_gid); Graph& leaf = lib.get_graph(leaf_gid); - (void)root.create_node(); // node 4 - const Nid root_sub = root.create_node(); // node 5 - (void)root.create_node(); // node 6 - root.ref_node(root_sub)->set_subnode(child_gid); // node 5 -> graph 2 subnode + (void)root.create_node(); // node 4 + const Nid root_sub = root.create_node(); // node 5 + (void)root.create_node(); // node 6 + root.set_subnode(root_sub, child_gid); // node 5 -> graph 2 subnode - (void)child.create_node(); // node 4 - const Nid child_sub = child.create_node(); // node 5 - (void)child.create_node(); // node 6 - child.ref_node(child_sub)->set_subnode(leaf_gid); // node 5 -> graph 3 subnode + (void)child.create_node(); // node 4 + const Nid child_sub = child.create_node(); // node 5 + (void)child.create_node(); // node 6 + child.set_subnode(child_sub, leaf_gid); // node 5 -> graph 3 subnode (void)leaf.create_node(); // node 4 @@ -560,17 +560,17 @@ void test_fast_iter_hierarchy_multiple_subnodes() { Graph& child = lib.get_graph(child_gid); Graph& leaf = lib.get_graph(leaf_gid); - (void)root.create_node(); // node 4 - const Nid root_sub = root.create_node(); // node 5 - (void)root.create_node(); // node 6 - const Nid root_sub2 = root.create_node(); // node 7 - root.ref_node(root_sub)->set_subnode(child_gid); // node 5 -> graph 2 subnode - root.ref_node(root_sub2)->set_subnode(child_gid); // node 7 -> graph 2 subnode - - (void)child.create_node(); // node 4 - const Nid child_sub = child.create_node(); // node 5 - (void)child.create_node(); // node 6 - child.ref_node(child_sub)->set_subnode(leaf_gid); // node 5 -> graph 3 subnode + (void)root.create_node(); // node 4 + const Nid root_sub = root.create_node(); // node 5 + (void)root.create_node(); // node 6 + const Nid root_sub2 = root.create_node(); // node 7 + root.set_subnode(root_sub, child_gid); // node 5 -> graph 2 subnode + root.set_subnode(root_sub2, child_gid); // node 7 -> graph 2 subnode + + (void)child.create_node(); // node 4 + const Nid child_sub = child.create_node(); // node 5 + (void)child.create_node(); // node 6 + child.set_subnode(child_sub, leaf_gid); // node 5 -> graph 3 subnode (void)leaf.create_node(); // node 4 diff --git a/hhds/tests/iterators.cpp b/hhds/tests/iterators.cpp index 30824c2..b290699 100644 --- a/hhds/tests/iterators.cpp +++ b/hhds/tests/iterators.cpp @@ -17,353 +17,557 @@ #include "hhds/graph.hpp" #include "hhds/tree.hpp" -// // --------------------------------------------------------------------------- -// // Section 8: Node pin iteration (api_todo.md #8) -// // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Section 1: Compact ID types (api_todo.md #1) +// --------------------------------------------------------------------------- -TEST(PinIteration, GetPins) { +TEST(CompactTypes, NodeClassFromGraph) { hhds::Graph g; - auto node = g.create_node(); - node.create_pin(1); - node.create_pin(2); - node.create_pin(3); - // pin 0 is not present yet: create_node() only creates the node entry. - int count = 0; - for (auto pin : g.get_pins(node)) { - (void)pin.get_port_id(); - count++; - } - EXPECT_EQ(count, 3); + // create_node returns Node_class directly (not raw Nid) + auto node = g.create_node(); + EXPECT_EQ(node.get_port_id(), 0); // Node_class encodes port-id 0 identity } -TEST(PinIteration, GetPinsIncludesPin0WhenMaterialized) { +TEST(CompactTypes, PinClassFromGraph) { hhds::Graph g; auto node = g.create_node(); - node.create_pin(0); // materialize pin 0 on this node - node.create_pin(1); - node.create_pin(2); - node.create_pin(3); + auto pin = node.create_pin(3); + + EXPECT_EQ(pin.get_raw_nid(), node.get_raw_nid()); // get_raw_nid() for internal cross-check only + EXPECT_EQ(pin.get_port_id(), 3); +} + +TEST(CompactTypes, NodeClassHashable) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + EXPECT_EQ(n1, n1); + EXPECT_NE(n1, n2); + + // Usable as absl::flat_hash_map key for external attribute tables + absl::flat_hash_map attrs; + attrs[n1] = 42; + EXPECT_EQ(attrs[n1], 42); +} + +// --------------------------------------------------------------------------- +// Section 2: Direction-aware edge iteration (api_todo.md #2) +// --------------------------------------------------------------------------- + +TEST(EdgeIteration, OutEdges) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto n3 = g.create_node(); + g.add_edge(n1, n2); + g.add_edge(n1, n3); + + // out_edges: edges where n1 is the driver int count = 0; - for (auto pin : g.get_pins(node)) { - (void)pin.get_port_id(); + for (auto edge : g.out_edges(n1)) { + EXPECT_EQ(edge.driver.get_port_id(), 0); count++; } - EXPECT_EQ(count, 4); + EXPECT_EQ(count, 2); } -// // --------------------------------------------------------------------------- -// // Section 9: Tree iterators return Tnode_class (api_todo.md #9) -// // --------------------------------------------------------------------------- - -TEST(TreeIterator, PreOrderYieldsTnodeClass) { - hhds::tree t; - auto root = t.add_root(1); // returns Tnode_class - auto c1 = t.add_child(root, 2); - t.add_child(root, 3); - t.add_child(c1, 4); +TEST(EdgeIteration, InpEdges) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto n3 = g.create_node(); + g.add_edge(n1, n3); + g.add_edge(n2, n3); - // pre_order yields Tnode_class + // inp_edges: edges where n3 is the sink int count = 0; - for (auto tnode : t.pre_order()) { - const auto& data = tnode.get_data(); // get_data() is read-only (const X&) - (void)data; + for (auto edge : g.inp_edges(n3)) { + EXPECT_EQ(edge.sink.get_port_id(), 0); count++; } - EXPECT_EQ(count, 4); + EXPECT_EQ(count, 2); } -TEST(TreeIterator, RefDataMutableAccess) { - hhds::tree t; - auto root = t.add_root(10); +// --------------------------------------------------------------------------- +// Section 3: del_edge (api_todo.md #3) +// --------------------------------------------------------------------------- + +TEST(DelEdge, BasicRemoval) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto p1 = n1.create_pin(0); + auto p2 = n2.create_pin(0); + g.add_edge(p1, p2); - // get_data() is const — read-only - const auto& ro = root.get_data(); - EXPECT_EQ(ro, 10); + g.del_edge(p1, p2); - // ref_data() gives a mutable unique_ptr handle - auto ptr = root.ref_data(); - *ptr = 99; - EXPECT_EQ(root.get_data(), 99); + int count = 0; + for (auto edge : g.out_edges(n1)) { + (void)edge; + count++; + } + EXPECT_EQ(count, 0); } -// // --------------------------------------------------------------------------- -// // Section 14: Hierarchy cursor — graphs (api_todo.md #14) -// // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Section 4: Lazy graph traversal (api_todo.md #4) +// --------------------------------------------------------------------------- -TEST(HierCursor, GraphBasicNavigation) { - hhds::GraphLibrary lib; +TEST(LazyTraversal, FastClassSingleGraph) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto n3 = g.create_node(); + g.add_edge(n1, n2); + g.add_edge(n2, n3); - // Create a 3-level hierarchy: top -> mid -> leaf - auto top = lib.create_graph(); // returns shared_ptr - auto mid = lib.create_graph(); // returns shared_ptr - auto leaf = lib.create_graph(); // returns shared_ptr - auto top_gid = top->get_gid(); - auto mid_gid = mid->get_gid(); - auto leaf_gid = leaf->get_gid(); - - // top has a node that instantiates mid - auto sub_mid = top->create_node(); - top->set_subnode(sub_mid, mid_gid); - - // mid has a node that instantiates leaf - auto sub_leaf = mid->create_node(); - mid->set_subnode(sub_leaf, leaf_gid); - - // Create cursor rooted at top - auto cursor = lib.create_cursor(top_gid); - EXPECT_TRUE(cursor.is_root()); - EXPECT_EQ(cursor.get_current_gid(), top_gid); - EXPECT_EQ(cursor.get_root_gid(), top_gid); - - // Descend into mid - EXPECT_TRUE(cursor.goto_first_child()); - EXPECT_EQ(cursor.get_current_gid(), mid_gid); - EXPECT_FALSE(cursor.is_root()); - EXPECT_EQ(cursor.depth(), 1); - - // Descend into leaf - EXPECT_TRUE(cursor.goto_first_child()); - EXPECT_EQ(cursor.get_current_gid(), leaf_gid); - EXPECT_TRUE(cursor.is_leaf()); - EXPECT_EQ(cursor.depth(), 2); - - // Cannot go further down - EXPECT_FALSE(cursor.goto_first_child()); - - // Go back up to mid - EXPECT_TRUE(cursor.goto_parent()); - EXPECT_EQ(cursor.get_current_gid(), mid_gid); - - // Go back up to top - EXPECT_TRUE(cursor.goto_parent()); - EXPECT_EQ(cursor.get_current_gid(), top_gid); - EXPECT_TRUE(cursor.is_root()); - - // Cannot go above root - EXPECT_FALSE(cursor.goto_parent()); + // fast_class: span over the graph's internal node storage (cheap, no allocation) + int count = 0; + for (auto node : g.fast_class()) { + (void)node.get_port_id(); + count++; + } + // 3 user nodes + built-in nodes (INPUT, OUTPUT, CONST) + EXPECT_GE(count, 3); } -TEST(HierCursor, GraphSiblingNavigation) { +TEST(LazyTraversal, FastFlatSingleGraph) { hhds::GraphLibrary lib; + const hhds::Gid gid = lib.create_graph(); + auto& g = lib.get_graph(gid); - auto top = lib.create_graph(); // returns shared_ptr - auto alu = lib.create_graph(); // returns shared_ptr - auto reg = lib.create_graph(); // returns shared_ptr - auto top_gid = top->get_gid(); - auto alu_gid = alu->get_gid(); - auto reg_gid = reg->get_gid(); - - // top has two sub-instances: ALU and RegFile - auto n_alu = top->create_node(); - top->set_subnode(n_alu, alu_gid); - - auto n_reg = top->create_node(); - top->set_subnode(n_reg, reg_gid); - - auto cursor = lib.create_cursor(top_gid); - EXPECT_TRUE(cursor.goto_first_child()); - - // Should be at first sub-instance - auto first = cursor.get_current_gid(); + (void)g.create_node(); + (void)g.create_node(); - // Move to sibling - EXPECT_TRUE(cursor.goto_next_sibling()); - auto second = cursor.get_current_gid(); - EXPECT_NE(first, second); + auto nodes = g.fast_flat(); - // No more siblings - EXPECT_FALSE(cursor.goto_next_sibling()); - - // Go back to previous sibling - EXPECT_TRUE(cursor.goto_prev_sibling()); - EXPECT_EQ(cursor.get_current_gid(), first); + // 3 built-ins + 2 created nodes + EXPECT_EQ(nodes.size(), 5); + for (const auto& node : nodes) { + EXPECT_EQ(node.get_root_gid(), gid); + EXPECT_EQ(node.get_current_gid(), gid); + EXPECT_EQ(node.get_port_id(), 0); + } } -TEST(HierCursor, GraphSharedModuleDisambiguation) { +TEST(LazyTraversal, FastFlatHierarchy) { hhds::GraphLibrary lib; + const hhds::Gid root_gid = lib.create_graph(); + const hhds::Gid child_gid = lib.create_graph(); + const hhds::Gid leaf_gid = lib.create_graph(); + + auto& root = lib.get_graph(root_gid); + auto& child = lib.get_graph(child_gid); + auto& leaf = lib.get_graph(leaf_gid); + + (void)root.create_node(); + auto root_sub = root.create_node(); + (void)root.create_node(); + root.set_subnode(root_sub, child_gid); + + (void)child.create_node(); + auto child_sub = child.create_node(); + (void)child.create_node(); + child.set_subnode(child_sub, leaf_gid); + + (void)leaf.create_node(); + + auto nodes = root.fast_flat(); + + EXPECT_EQ(nodes.size(), 14); + + int root_count = 0; + int child_count = 0; + int leaf_count = 0; + + for (const auto& node : nodes) { + EXPECT_EQ(node.get_root_gid(), root_gid); + if (node.get_current_gid() == root_gid) { + root_count++; + } else if (node.get_current_gid() == child_gid) { + child_count++; + } else if (node.get_current_gid() == leaf_gid) { + leaf_count++; + } else { + FAIL() << "Unexpected current_gid in fast_flat traversal"; + } + } - auto cpu_a = lib.create_graph(); // returns shared_ptr - auto cpu_b = lib.create_graph(); // returns shared_ptr - auto alu = lib.create_graph(); // returns shared_ptr - auto cpu_a_gid = cpu_a->get_gid(); - auto cpu_b_gid = cpu_b->get_gid(); - auto alu_gid = alu->get_gid(); - - // Both CPUs instantiate the same ALU - auto a_sub = cpu_a->create_node(); - cpu_a->set_subnode(a_sub, alu_gid); - - auto b_sub = cpu_b->create_node(); - cpu_b->set_subnode(b_sub, alu_gid); - - // Cursor rooted at CPU_A: going into ALU then up returns to CPU_A - auto cursor_a = lib.create_cursor(cpu_a_gid); - EXPECT_TRUE(cursor_a.goto_first_child()); - EXPECT_EQ(cursor_a.get_current_gid(), alu_gid); - EXPECT_TRUE(cursor_a.goto_parent()); - EXPECT_EQ(cursor_a.get_current_gid(), cpu_a_gid); - - // Cursor rooted at CPU_B: going into ALU then up returns to CPU_B - auto cursor_b = lib.create_cursor(cpu_b_gid); - EXPECT_TRUE(cursor_b.goto_first_child()); - EXPECT_EQ(cursor_b.get_current_gid(), alu_gid); - EXPECT_TRUE(cursor_b.goto_parent()); - EXPECT_EQ(cursor_b.get_current_gid(), cpu_b_gid); + EXPECT_EQ(root_count, 5); + EXPECT_EQ(child_count, 5); + EXPECT_EQ(leaf_count, 4); } -TEST(HierCursor, GraphIterateNodesAtLevel) { - hhds::GraphLibrary lib; - - auto top = lib.create_graph(); // returns shared_ptr - auto top_gid = top->get_gid(); +// --------------------------------------------------------------------------- +// Section 4 (cont.): add_edge(Node_class, Node_class) pin-0 shorthand +// --------------------------------------------------------------------------- - top->create_node(); - top->create_node(); - top->create_node(); +TEST(AddEdgeShorthand, NodeToNode) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); - auto cursor = lib.create_cursor(top_gid); + // add_edge with Node_class uses implicit pin 0 on both sides + g.add_edge(n1, n2); int count = 0; - for (auto node : cursor.each_node()) { - (void)node.get_current_gid(); // each_node yields Node_hier - (void)node.get_port_id(); + for (auto edge : g.out_edges(n1)) { + (void)edge; count++; } - // 3 user nodes + built-in nodes - EXPECT_GE(count, 3); + EXPECT_EQ(count, 1); } // // --------------------------------------------------------------------------- -// // Section 14: Hierarchy cursor — trees/forest (api_todo.md #14) +// // Section 8: Node pin iteration (api_todo.md #8) // // --------------------------------------------------------------------------- -TEST(ForestCursor, BasicNavigation) { - hhds::Forest forest; - - auto main_tree = forest.create_tree(); // returns shared_ptr> - auto sub_tree = forest.create_tree(); // returns shared_ptr> - auto leaf_tree = forest.create_tree(); // returns shared_ptr> - auto main_tid = main_tree->get_tid(); - auto sub_tid = sub_tree->get_tid(); - auto leaf_tid = leaf_tree->get_tid(); - - // main_tree root has a child that references sub_tree - auto root = main_tree->add_root(1); - auto child = main_tree->add_child(root, 2); - main_tree->add_subtree_ref(child, sub_tid); - - // sub_tree root has a child that references leaf_tree - auto sub_root = sub_tree->add_root(10); - auto sub_child = sub_tree->add_child(sub_root, 20); - sub_tree->add_subtree_ref(sub_child, leaf_tid); - - leaf_tree->add_root(100); - - auto cursor = forest.create_cursor(main_tid); - EXPECT_TRUE(cursor.is_root()); - EXPECT_EQ(cursor.get_current_tid(), main_tid); - - // Descend into sub_tree - EXPECT_TRUE(cursor.goto_first_child()); - EXPECT_EQ(cursor.get_current_tid(), sub_tid); - - // Descend into leaf_tree - EXPECT_TRUE(cursor.goto_first_child()); - EXPECT_EQ(cursor.get_current_tid(), leaf_tid); - EXPECT_TRUE(cursor.is_leaf()); - - // Go back up - EXPECT_TRUE(cursor.goto_parent()); - EXPECT_EQ(cursor.get_current_tid(), sub_tid); - - EXPECT_TRUE(cursor.goto_parent()); - EXPECT_EQ(cursor.get_current_tid(), main_tid); - EXPECT_TRUE(cursor.is_root()); - - EXPECT_FALSE(cursor.goto_parent()); -} +// TEST(PinIteration, GetPins) { +// hhds::Graph g; +// auto node = g.create_node(); +// node.create_pin(1); +// node.create_pin(2); +// node.create_pin(3); + +// // pin 0 is not present yet: create_node() only creates the node entry. +// int count = 0; +// for (auto pin : g.get_pins(node)) { +// (void)pin.get_port_id(); +// count++; +// } +// EXPECT_EQ(count, 3); +// } + +// TEST(PinIteration, GetPinsIncludesPin0WhenMaterialized) { +// hhds::Graph g; +// auto node = g.create_node(); +// node.create_pin(0); // materialize pin 0 on this node +// node.create_pin(1); +// node.create_pin(2); +// node.create_pin(3); + +// int count = 0; +// for (auto pin : g.get_pins(node)) { +// (void)pin.get_port_id(); +// count++; +// } +// EXPECT_EQ(count, 4); +// } // // --------------------------------------------------------------------------- -// // Section 14: get_callers (api_todo.md #14) +// // Section 9: Tree iterators return Tnode_class (api_todo.md #9) // // --------------------------------------------------------------------------- -TEST(GetCallers, GraphCallersTracking) { - hhds::GraphLibrary lib; - - auto cpu_a = lib.create_graph(); // returns shared_ptr - auto cpu_b = lib.create_graph(); // returns shared_ptr - auto alu = lib.create_graph(); // returns shared_ptr - auto alu_gid = alu->get_gid(); +// TEST(TreeIterator, PreOrderYieldsTnodeClass) { +// hhds::tree t; +// auto root = t.add_root(1); // returns Tnode_class +// auto c1 = t.add_child(root, 2); +// t.add_child(root, 3); +// t.add_child(c1, 4); + +// // pre_order yields Tnode_class +// int count = 0; +// for (auto tnode : t.pre_order()) { +// const auto& data = tnode.get_data(); // get_data() is read-only (const X&) +// (void)data; +// count++; +// } +// EXPECT_EQ(count, 4); +// } + +// TEST(TreeIterator, RefDataMutableAccess) { +// hhds::tree t; +// auto root = t.add_root(10); + +// // get_data() is const — read-only +// const auto& ro = root.get_data(); +// EXPECT_EQ(ro, 10); + +// // ref_data() gives a mutable unique_ptr handle +// auto ptr = root.ref_data(); +// *ptr = 99; +// EXPECT_EQ(root.get_data(), 99); +// } - auto a_sub = cpu_a->create_node(); - cpu_a->set_subnode(a_sub, alu_gid); +// // --------------------------------------------------------------------------- +// // Section 14: Hierarchy cursor — graphs (api_todo.md #14) +// // --------------------------------------------------------------------------- - auto b_sub = cpu_b->create_node(); - cpu_b->set_subnode(b_sub, alu_gid); +// TEST(HierCursor, GraphBasicNavigation) { +// hhds::GraphLibrary lib; + +// // Create a 3-level hierarchy: top -> mid -> leaf +// auto top = lib.create_graph(); // returns shared_ptr +// auto mid = lib.create_graph(); // returns shared_ptr +// auto leaf = lib.create_graph(); // returns shared_ptr +// auto top_gid = top->get_gid(); +// auto mid_gid = mid->get_gid(); +// auto leaf_gid = leaf->get_gid(); + +// // top has a node that instantiates mid +// auto sub_mid = top->create_node(); +// top->set_subnode(sub_mid, mid_gid); + +// // mid has a node that instantiates leaf +// auto sub_leaf = mid->create_node(); +// mid->set_subnode(sub_leaf, leaf_gid); + +// // Create cursor rooted at top +// auto cursor = lib.create_cursor(top_gid); +// EXPECT_TRUE(cursor.is_root()); +// EXPECT_EQ(cursor.get_current_gid(), top_gid); +// EXPECT_EQ(cursor.get_root_gid(), top_gid); + +// // Descend into mid +// EXPECT_TRUE(cursor.goto_first_child()); +// EXPECT_EQ(cursor.get_current_gid(), mid_gid); +// EXPECT_FALSE(cursor.is_root()); +// EXPECT_EQ(cursor.depth(), 1); + +// // Descend into leaf +// EXPECT_TRUE(cursor.goto_first_child()); +// EXPECT_EQ(cursor.get_current_gid(), leaf_gid); +// EXPECT_TRUE(cursor.is_leaf()); +// EXPECT_EQ(cursor.depth(), 2); + +// // Cannot go further down +// EXPECT_FALSE(cursor.goto_first_child()); + +// // Go back up to mid +// EXPECT_TRUE(cursor.goto_parent()); +// EXPECT_EQ(cursor.get_current_gid(), mid_gid); + +// // Go back up to top +// EXPECT_TRUE(cursor.goto_parent()); +// EXPECT_EQ(cursor.get_current_gid(), top_gid); +// EXPECT_TRUE(cursor.is_root()); + +// // Cannot go above root +// EXPECT_FALSE(cursor.goto_parent()); +// } + +// TEST(HierCursor, GraphSiblingNavigation) { +// hhds::GraphLibrary lib; + +// auto top = lib.create_graph(); // returns shared_ptr +// auto alu = lib.create_graph(); // returns shared_ptr +// auto reg = lib.create_graph(); // returns shared_ptr +// auto top_gid = top->get_gid(); +// auto alu_gid = alu->get_gid(); +// auto reg_gid = reg->get_gid(); + +// // top has two sub-instances: ALU and RegFile +// auto n_alu = top->create_node(); +// top->set_subnode(n_alu, alu_gid); + +// auto n_reg = top->create_node(); +// top->set_subnode(n_reg, reg_gid); + +// auto cursor = lib.create_cursor(top_gid); +// EXPECT_TRUE(cursor.goto_first_child()); + +// // Should be at first sub-instance +// auto first = cursor.get_current_gid(); + +// // Move to sibling +// EXPECT_TRUE(cursor.goto_next_sibling()); +// auto second = cursor.get_current_gid(); +// EXPECT_NE(first, second); + +// // No more siblings +// EXPECT_FALSE(cursor.goto_next_sibling()); + +// // Go back to previous sibling +// EXPECT_TRUE(cursor.goto_prev_sibling()); +// EXPECT_EQ(cursor.get_current_gid(), first); +// } + +// TEST(HierCursor, GraphSharedModuleDisambiguation) { +// hhds::GraphLibrary lib; + +// auto cpu_a = lib.create_graph(); // returns shared_ptr +// auto cpu_b = lib.create_graph(); // returns shared_ptr +// auto alu = lib.create_graph(); // returns shared_ptr +// auto cpu_a_gid = cpu_a->get_gid(); +// auto cpu_b_gid = cpu_b->get_gid(); +// auto alu_gid = alu->get_gid(); + +// // Both CPUs instantiate the same ALU +// auto a_sub = cpu_a->create_node(); +// cpu_a->set_subnode(a_sub, alu_gid); + +// auto b_sub = cpu_b->create_node(); +// cpu_b->set_subnode(b_sub, alu_gid); + +// // Cursor rooted at CPU_A: going into ALU then up returns to CPU_A +// auto cursor_a = lib.create_cursor(cpu_a_gid); +// EXPECT_TRUE(cursor_a.goto_first_child()); +// EXPECT_EQ(cursor_a.get_current_gid(), alu_gid); +// EXPECT_TRUE(cursor_a.goto_parent()); +// EXPECT_EQ(cursor_a.get_current_gid(), cpu_a_gid); + +// // Cursor rooted at CPU_B: going into ALU then up returns to CPU_B +// auto cursor_b = lib.create_cursor(cpu_b_gid); +// EXPECT_TRUE(cursor_b.goto_first_child()); +// EXPECT_EQ(cursor_b.get_current_gid(), alu_gid); +// EXPECT_TRUE(cursor_b.goto_parent()); +// EXPECT_EQ(cursor_b.get_current_gid(), cpu_b_gid); +// } + +// TEST(HierCursor, GraphIterateNodesAtLevel) { +// hhds::GraphLibrary lib; + +// auto top = lib.create_graph(); // returns shared_ptr +// auto top_gid = top->get_gid(); + +// top->create_node(); +// top->create_node(); +// top->create_node(); + +// auto cursor = lib.create_cursor(top_gid); + +// int count = 0; +// for (auto node : cursor.each_node()) { +// (void)node.get_current_gid(); // each_node yields Node_hier +// (void)node.get_port_id(); +// count++; +// } +// // 3 user nodes + built-in nodes +// EXPECT_GE(count, 3); +// } - // ALU should have 2 callers - int caller_count = 0; - for (auto& c : lib.get_callers(alu_gid)) { - (void)c.caller_gid; - (void)c.caller_node; - caller_count++; - } - EXPECT_EQ(caller_count, 2); -} +// // --------------------------------------------------------------------------- +// // Section 14: Hierarchy cursor — trees/forest (api_todo.md #14) +// // --------------------------------------------------------------------------- -TEST(GetCallers, ForestCallersTracking) { - hhds::Forest forest; +// TEST(ForestCursor, BasicNavigation) { +// hhds::Forest forest; - auto tree_a = forest.create_tree(); // returns shared_ptr> - auto tree_b = forest.create_tree(); // returns shared_ptr> - auto shared = forest.create_tree(); // returns shared_ptr> - auto shared_tid = shared->get_tid(); +// auto main_tree = forest.create_tree(); // returns shared_ptr> +// auto sub_tree = forest.create_tree(); // returns shared_ptr> +// auto leaf_tree = forest.create_tree(); // returns shared_ptr> +// auto main_tid = main_tree->get_tid(); +// auto sub_tid = sub_tree->get_tid(); +// auto leaf_tid = leaf_tree->get_tid(); - auto a_root = tree_a->add_root(1); - auto a_child = tree_a->add_child(a_root, 10); - tree_a->add_subtree_ref(a_child, shared_tid); +// // main_tree root has a child that references sub_tree +// auto root = main_tree->add_root(1); +// auto child = main_tree->add_child(root, 2); +// main_tree->add_subtree_ref(child, sub_tid); - auto b_root = tree_b->add_root(2); - auto b_child = tree_b->add_child(b_root, 20); - tree_b->add_subtree_ref(b_child, shared_tid); +// // sub_tree root has a child that references leaf_tree +// auto sub_root = sub_tree->add_root(10); +// auto sub_child = sub_tree->add_child(sub_root, 20); +// sub_tree->add_subtree_ref(sub_child, leaf_tid); - shared->add_root(100); +// leaf_tree->add_root(100); - // shared_tree should have 2 callers - int caller_count = 0; - for (auto& c : forest.get_callers(shared_tid)) { - (void)c.caller_tid; - (void)c.caller_tnode; - caller_count++; - } - EXPECT_EQ(caller_count, 2); -} +// auto cursor = forest.create_cursor(main_tid); +// EXPECT_TRUE(cursor.is_root()); +// EXPECT_EQ(cursor.get_current_tid(), main_tid); -TEST(GetCallers, CreateCursorFromCaller) { - hhds::GraphLibrary lib; +// // Descend into sub_tree +// EXPECT_TRUE(cursor.goto_first_child()); +// EXPECT_EQ(cursor.get_current_tid(), sub_tid); - auto cpu_a = lib.create_graph(); // returns shared_ptr - auto cpu_b = lib.create_graph(); // returns shared_ptr - auto alu = lib.create_graph(); // returns shared_ptr - auto alu_gid = alu->get_gid(); +// // Descend into leaf_tree +// EXPECT_TRUE(cursor.goto_first_child()); +// EXPECT_EQ(cursor.get_current_tid(), leaf_tid); +// EXPECT_TRUE(cursor.is_leaf()); - auto a_sub = cpu_a->create_node(); - cpu_a->set_subnode(a_sub, alu_gid); +// // Go back up +// EXPECT_TRUE(cursor.goto_parent()); +// EXPECT_EQ(cursor.get_current_tid(), sub_tid); - auto b_sub = cpu_b->create_node(); - cpu_b->set_subnode(b_sub, alu_gid); +// EXPECT_TRUE(cursor.goto_parent()); +// EXPECT_EQ(cursor.get_current_tid(), main_tid); +// EXPECT_TRUE(cursor.is_root()); - // Pick a caller and create a rooted cursor from it - auto callers_it = lib.get_callers(alu_gid).begin(); - auto cursor = lib.create_cursor(callers_it->caller_gid); +// EXPECT_FALSE(cursor.goto_parent()); +// } - // Descend into ALU from that specific parent - EXPECT_TRUE(cursor.goto_first_child()); - EXPECT_EQ(cursor.get_current_gid(), alu_gid); +// // --------------------------------------------------------------------------- +// // Section 14: get_callers (api_todo.md #14) +// // --------------------------------------------------------------------------- - // Going up returns to the caller we picked, not the other one - EXPECT_TRUE(cursor.goto_parent()); - EXPECT_EQ(cursor.get_current_gid(), callers_it->caller_gid); -} +// TEST(GetCallers, GraphCallersTracking) { +// hhds::GraphLibrary lib; + +// auto cpu_a = lib.create_graph(); // returns shared_ptr +// auto cpu_b = lib.create_graph(); // returns shared_ptr +// auto alu = lib.create_graph(); // returns shared_ptr +// auto alu_gid = alu->get_gid(); + +// auto a_sub = cpu_a->create_node(); +// cpu_a->set_subnode(a_sub, alu_gid); + +// auto b_sub = cpu_b->create_node(); +// cpu_b->set_subnode(b_sub, alu_gid); + +// // ALU should have 2 callers +// int caller_count = 0; +// for (auto& c : lib.get_callers(alu_gid)) { +// (void)c.caller_gid; +// (void)c.caller_node; +// caller_count++; +// } +// EXPECT_EQ(caller_count, 2); +// } + +// TEST(GetCallers, ForestCallersTracking) { +// hhds::Forest forest; + +// auto tree_a = forest.create_tree(); // returns shared_ptr> +// auto tree_b = forest.create_tree(); // returns shared_ptr> +// auto shared = forest.create_tree(); // returns shared_ptr> +// auto shared_tid = shared->get_tid(); + +// auto a_root = tree_a->add_root(1); +// auto a_child = tree_a->add_child(a_root, 10); +// tree_a->add_subtree_ref(a_child, shared_tid); + +// auto b_root = tree_b->add_root(2); +// auto b_child = tree_b->add_child(b_root, 20); +// tree_b->add_subtree_ref(b_child, shared_tid); + +// shared->add_root(100); + +// // shared_tree should have 2 callers +// int caller_count = 0; +// for (auto& c : forest.get_callers(shared_tid)) { +// (void)c.caller_tid; +// (void)c.caller_tnode; +// caller_count++; +// } +// EXPECT_EQ(caller_count, 2); +// } + +// TEST(GetCallers, CreateCursorFromCaller) { +// hhds::GraphLibrary lib; + +// auto cpu_a = lib.create_graph(); // returns shared_ptr +// auto cpu_b = lib.create_graph(); // returns shared_ptr +// auto alu = lib.create_graph(); // returns shared_ptr +// auto alu_gid = alu->get_gid(); + +// auto a_sub = cpu_a->create_node(); +// cpu_a->set_subnode(a_sub, alu_gid); + +// auto b_sub = cpu_b->create_node(); +// cpu_b->set_subnode(b_sub, alu_gid); + +// // Pick a caller and create a rooted cursor from it +// auto callers_it = lib.get_callers(alu_gid).begin(); +// auto cursor = lib.create_cursor(callers_it->caller_gid); + +// // Descend into ALU from that specific parent +// EXPECT_TRUE(cursor.goto_first_child()); +// EXPECT_EQ(cursor.get_current_gid(), alu_gid); + +// // Going up returns to the caller we picked, not the other one +// EXPECT_TRUE(cursor.goto_parent()); +// EXPECT_EQ(cursor.get_current_gid(), callers_it->caller_gid); +// } diff --git a/hhds/tests/iterators_impl.cpp b/hhds/tests/iterators_impl.cpp index 833e278..9d1820a 100644 --- a/hhds/tests/iterators_impl.cpp +++ b/hhds/tests/iterators_impl.cpp @@ -7,6 +7,20 @@ #include "absl/container/flat_hash_map.h" #include "hhds/graph.hpp" +#include "hhds/tree.hpp" + +namespace { + +bool contains_pin_pid(const std::vector& pins, hhds::Pid pid) { + for (const auto& pin : pins) { + if (pin.get_pin_pid() == pid) { + return true; + } + } + return false; +} + +} // namespace // --------------------------------------------------------------------------- // Section 1: Compact ID types (api_todo.md #1) @@ -43,6 +57,127 @@ TEST(CompactTypes, NodeClassHashable) { EXPECT_EQ(attrs[n1], 42); } +TEST(CompactTypes, NodeCompactConversions) { + hhds::GraphLibrary lib; + const hhds::Gid gid = lib.create_graph(); + auto& g = lib.get_graph(gid); + + auto node = g.create_node(); + + const auto flat_from_class = hhds::to_flat(node, gid); + EXPECT_EQ(flat_from_class.get_root_gid(), gid); + EXPECT_EQ(flat_from_class.get_current_gid(), gid); + EXPECT_EQ(flat_from_class.get_raw_nid(), node.get_raw_nid() & ~static_cast(2)); + + const auto class_from_flat = hhds::to_class(flat_from_class); + EXPECT_EQ(class_from_flat.get_raw_nid(), node.get_raw_nid() & ~static_cast(2)); + + bool found_in_hier = false; + for (const auto& hnode : g.fast_hier()) { + if ((hnode.get_raw_nid() & ~static_cast(2)) == (node.get_raw_nid() & ~static_cast(2))) { + found_in_hier = true; + const auto flat_from_hier = hhds::to_flat(hnode); + const auto class_from_hier = hhds::to_class(hnode); + EXPECT_EQ(flat_from_hier.get_root_gid(), gid); + EXPECT_EQ(flat_from_hier.get_current_gid(), gid); + EXPECT_EQ(flat_from_hier.get_raw_nid(), node.get_raw_nid() & ~static_cast(2)); + EXPECT_EQ(class_from_hier.get_raw_nid(), node.get_raw_nid() & ~static_cast(2)); + break; + } + } + EXPECT_TRUE(found_in_hier); +} + +TEST(CompactTypes, PinCompactConversions) { + hhds::GraphLibrary lib; + const hhds::Gid gid = lib.create_graph(); + auto& g = lib.get_graph(gid); + + auto node = g.create_node(); + auto pin = node.create_pin(3); + + const auto flat_from_class = hhds::to_flat(pin, gid); + EXPECT_EQ(flat_from_class.get_root_gid(), gid); + EXPECT_EQ(flat_from_class.get_current_gid(), gid); + EXPECT_EQ(flat_from_class.get_raw_nid(), node.get_raw_nid() & ~static_cast(2)); + EXPECT_EQ(flat_from_class.get_port_id(), 3); + EXPECT_EQ(flat_from_class.get_pin_pid(), pin.get_pin_pid()); + + const auto class_from_flat = hhds::to_class(flat_from_class); + EXPECT_EQ(class_from_flat.get_raw_nid(), node.get_raw_nid() & ~static_cast(2)); + EXPECT_EQ(class_from_flat.get_port_id(), 3); + EXPECT_EQ(class_from_flat.get_pin_pid(), pin.get_pin_pid()); + + absl::flat_hash_map attrs; + attrs[flat_from_class] = 7; + EXPECT_EQ(attrs[flat_from_class], 7); +} + +TEST(CompactTypes, EdgeFlatConversions) { + hhds::GraphLibrary lib; + const hhds::Gid gid = lib.create_graph(); + auto& g = lib.get_graph(gid); + + auto src = g.create_node(); + auto dst = g.create_node(); + auto sp = src.create_pin(1); + auto dp = dst.create_pin(2); + g.add_edge(sp, dp); + + auto out_edges = g.out_edges(sp); + ASSERT_EQ(out_edges.size(), 1U); + const auto& edge = out_edges[0]; + + const auto flat = hhds::to_flat(edge, gid); + EXPECT_EQ(flat.driver.get_root_gid(), gid); + EXPECT_EQ(flat.driver.get_current_gid(), gid); + EXPECT_EQ(flat.sink.get_root_gid(), gid); + EXPECT_EQ(flat.sink.get_current_gid(), gid); + EXPECT_EQ(flat.driver.get_pin_pid(), edge.driver_pin.get_pin_pid()); + EXPECT_EQ(flat.sink.get_pin_pid(), edge.sink_pin.get_pin_pid()); + + const auto edge_class = hhds::to_class(flat); + EXPECT_EQ(edge_class.driver_pin.get_pin_pid(), edge.driver_pin.get_pin_pid()); + EXPECT_EQ(edge_class.sink_pin.get_pin_pid(), edge.sink_pin.get_pin_pid()); + + absl::flat_hash_map attrs; + attrs[flat] = 11; + EXPECT_EQ(attrs[flat], 11); +} + +TEST(CompactTypes, EdgeHierConversions) { + hhds::GraphLibrary lib; + const hhds::Gid gid = lib.create_graph(); + auto& g = lib.get_graph(gid); + + auto src = g.create_node(); + auto dst = g.create_node(); + auto sp = src.create_pin(1); + auto dp = dst.create_pin(2); + g.add_edge(sp, dp); + + auto out_edges = g.out_edges(sp); + ASSERT_EQ(out_edges.size(), 1U); + const auto& edge = out_edges[0]; + + auto hier_ref = std::make_shared>(); + auto hier_pos = hier_ref->add_root(gid); + + const auto hier = hhds::to_hier(edge, hier_ref, hier_pos); + EXPECT_EQ(hier.driver.get_root_gid(), gid); + EXPECT_EQ(hier.driver.get_current_gid(), gid); + EXPECT_EQ(hier.sink.get_root_gid(), gid); + EXPECT_EQ(hier.sink.get_current_gid(), gid); + + const auto flat = hhds::to_flat(hier); + EXPECT_EQ(flat.driver.get_pin_pid(), edge.driver_pin.get_pin_pid()); + EXPECT_EQ(flat.sink.get_pin_pid(), edge.sink_pin.get_pin_pid()); + + const auto edge_class = hhds::to_class(hier); + EXPECT_EQ(edge_class.driver_pin.get_pin_pid(), edge.driver_pin.get_pin_pid()); + EXPECT_EQ(edge_class.sink_pin.get_pin_pid(), edge.sink_pin.get_pin_pid()); +} + // --------------------------------------------------------------------------- // Section 2: Direction-aware edge iteration (api_todo.md #2) // --------------------------------------------------------------------------- @@ -109,6 +244,70 @@ TEST(EdgeIteration, InpEdges) { EXPECT_EQ(count, 0); } +// --------------------------------------------------------------------------- +// Section 2 (cont.): Bit encoding contract +// --------------------------------------------------------------------------- + +TEST(BitEncoding, NodeToNode) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + g.add_edge(n1, n2); + + auto out = g.out_edges(n1); + ASSERT_EQ(out.size(), 1); + EXPECT_EQ((out[0].driver.get_raw_nid() & static_cast(1)), static_cast(0)); + EXPECT_EQ((out[0].driver.get_raw_nid() & static_cast(2)), static_cast(2)); + EXPECT_EQ((out[0].sink.get_raw_nid() & static_cast(1)), static_cast(0)); + EXPECT_EQ((out[0].sink.get_raw_nid() & static_cast(2)), static_cast(0)); +} + +TEST(BitEncoding, PinToPin) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto p1 = n1.create_pin(1); + auto p2 = n2.create_pin(2); + g.add_edge(p1, p2); + + auto out = g.out_edges(p1); + ASSERT_EQ(out.size(), 1); + EXPECT_EQ((out[0].driver_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((out[0].driver_pin.get_pin_pid() & static_cast(2)), static_cast(2)); + EXPECT_EQ((out[0].sink_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((out[0].sink_pin.get_pin_pid() & static_cast(2)), static_cast(0)); +} + +TEST(BitEncoding, NodeToPin) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto p2 = n2.create_pin(2); + g.add_edge(n1, p2); + + auto out = g.out_edges(n1); + ASSERT_EQ(out.size(), 1); + EXPECT_EQ((out[0].driver.get_raw_nid() & static_cast(1)), static_cast(0)); + EXPECT_EQ((out[0].driver.get_raw_nid() & static_cast(2)), static_cast(2)); + EXPECT_EQ((out[0].sink_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((out[0].sink_pin.get_pin_pid() & static_cast(2)), static_cast(0)); +} + +TEST(BitEncoding, PinToNode) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto p1 = n1.create_pin(1); + g.add_edge(p1, n2); + + auto out = g.out_edges(p1); + ASSERT_EQ(out.size(), 1); + EXPECT_EQ((out[0].driver_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((out[0].driver_pin.get_pin_pid() & static_cast(2)), static_cast(2)); + EXPECT_EQ((out[0].sink.get_raw_nid() & static_cast(1)), static_cast(0)); + EXPECT_EQ((out[0].sink.get_raw_nid() & static_cast(2)), static_cast(0)); +} + // --------------------------------------------------------------------------- // Section 3: del_edge (api_todo.md #3) // --------------------------------------------------------------------------- @@ -129,25 +328,29 @@ TEST(DelEdge, BasicRemoval) { EXPECT_EQ(count, 0); // node (pin0) has no edge count = 0; - // TODO: out_edge/inp_edges accepts node (pin0) and Pin_class/Pin_flat too for (auto edge : g.out_edges(p1)) { - EXPECT_EQ(edge.driver, p1); - EXPECT_EQ(edge.sink, p2); - - // TODO: get_master_node()-> Node_class - EXPECT_EQ(edge.driver.get_master_node(), n1); - EXPECT_EQ(edge.sink.get_master_node(), n2); + EXPECT_EQ(edge.driver_pin.get_pin_pid(), (p1.get_pin_pid() | static_cast(2))); + EXPECT_EQ(edge.sink_pin.get_pin_pid(), p2.get_pin_pid()); + EXPECT_EQ((edge.driver_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((edge.driver_pin.get_pin_pid() & static_cast(2)), static_cast(2)); + EXPECT_EQ((edge.sink_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((edge.sink_pin.get_pin_pid() & static_cast(2)), static_cast(0)); + EXPECT_EQ(edge.driver_pin.get_master_node().get_raw_nid(), n1.get_raw_nid()); + EXPECT_EQ(edge.sink_pin.get_master_node().get_raw_nid(), n2.get_raw_nid()); count++; } EXPECT_EQ(count, 1); + count = 0; for (auto edge : g.inp_edges(p2)) { - EXPECT_EQ(edge.driver, p1); - EXPECT_EQ(edge.sink, p2); - - // TODO: get_master_node()-> Node_class - EXPECT_EQ(edge.driver.get_master_node(), n1); - EXPECT_EQ(edge.sink.get_master_node(), n2); + EXPECT_EQ(edge.driver_pin.get_pin_pid(), (p1.get_pin_pid() | static_cast(2))); + EXPECT_EQ(edge.sink_pin.get_pin_pid(), p2.get_pin_pid()); + EXPECT_EQ((edge.driver_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((edge.driver_pin.get_pin_pid() & static_cast(2)), static_cast(2)); + EXPECT_EQ((edge.sink_pin.get_pin_pid() & static_cast(1)), static_cast(1)); + EXPECT_EQ((edge.sink_pin.get_pin_pid() & static_cast(2)), static_cast(0)); + EXPECT_EQ(edge.driver_pin.get_master_node().get_raw_nid(), n1.get_raw_nid()); + EXPECT_EQ(edge.sink_pin.get_master_node().get_raw_nid(), n2.get_raw_nid()); count++; } EXPECT_EQ(count, 1); @@ -209,6 +412,7 @@ TEST(DelEdge, BasicRemoval) { (void)edge; count++; } + EXPECT_EQ(count, 0); count = 0; for (auto edge : g.out_edges(n2)) { @@ -293,12 +497,12 @@ TEST(LazyTraversal, FastFlatHierarchy) { (void)root.create_node(); auto root_sub = root.create_node(); (void)root.create_node(); - root.ref_node(root_sub.get_raw_nid())->set_subnode(child_gid); + root.set_subnode(root_sub, child_gid); (void)child.create_node(); auto child_sub = child.create_node(); (void)child.create_node(); - child.ref_node(child_sub.get_raw_nid())->set_subnode(leaf_gid); + child.set_subnode(child_sub, leaf_gid); (void)leaf.create_node(); @@ -328,6 +532,217 @@ TEST(LazyTraversal, FastFlatHierarchy) { EXPECT_EQ(leaf_count, 4); } +TEST(LazyTraversal, ForwardClassTopologicalOrder) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + auto n3 = g.create_node(); + auto n4 = g.create_node(); + + auto n2p1 = n2.create_pin(1); + auto n4p1 = n4.create_pin(1); + + g.add_edge(n1, n2); + g.add_edge(n1, n3); + g.add_edge(n2p1, n4); + g.add_edge(n3, n4p1); + + auto order = g.forward_class(); + auto order2 = g.forward_class(); + EXPECT_EQ(order.data(), order2.data()); + + ASSERT_EQ(order.size(), 7); + absl::flat_hash_map pos; + for (size_t i = 0; i < order.size(); ++i) { + pos[order[i].get_raw_nid() & ~static_cast(3)] = i; + } + + const hhds::Nid n1_id = n1.get_raw_nid() & ~static_cast(3); + const hhds::Nid n2_id = n2.get_raw_nid() & ~static_cast(3); + const hhds::Nid n3_id = n3.get_raw_nid() & ~static_cast(3); + const hhds::Nid n4_id = n4.get_raw_nid() & ~static_cast(3); + + EXPECT_LT(pos[n1_id], pos[n2_id]); + EXPECT_LT(pos[n1_id], pos[n3_id]); + EXPECT_LT(pos[n2_id], pos[n4_id]); + EXPECT_LT(pos[n3_id], pos[n4_id]); + + const size_t before_size = order.size(); + (void)g.create_node(); + auto order3 = g.forward_class(); + EXPECT_EQ(order3.size(), before_size + 1); +} + +TEST(LazyTraversal, ForwardFlatHierarchyAndCacheInvalidation) { + hhds::GraphLibrary lib; + const hhds::Gid root_gid = lib.create_graph(); + const hhds::Gid child_gid = lib.create_graph(); + + auto& root = lib.get_graph(root_gid); + auto& child = lib.get_graph(child_gid); + + auto root_in = root.create_node(); + auto root_sub = root.create_node(); + auto root_out = root.create_node(); + root.add_edge(root_in, root_sub); + root.add_edge(root_sub, root_out); + + auto child_in = child.create_node(); + auto child_out = child.create_node(); + child.add_edge(child_in, child_out); + + root.set_subnode(root_sub, child_gid); + + auto flat1 = root.forward_flat(); + auto flat2 = root.forward_flat(); + EXPECT_EQ(flat1.data(), flat2.data()); + + size_t pos_root_in = flat1.size(); + size_t pos_root_out = flat1.size(); + size_t first_child = flat1.size(); + size_t last_child = 0; + + for (size_t i = 0; i < flat1.size(); ++i) { + const auto& n = flat1[i]; + if (n.get_current_gid() == root_gid && n.get_raw_nid() == root_in.get_raw_nid()) { + pos_root_in = i; + } + if (n.get_current_gid() == root_gid && n.get_raw_nid() == root_out.get_raw_nid()) { + pos_root_out = i; + } + if (n.get_current_gid() == child_gid) { + if (first_child == flat1.size()) { + first_child = i; + } + last_child = i; + } + } + + ASSERT_NE(first_child, flat1.size()); + ASSERT_LT(pos_root_in, first_child); + ASSERT_LT(last_child, pos_root_out); + + const size_t before_size = flat1.size(); + (void)child.create_node(); + auto flat3 = root.forward_flat(); + EXPECT_EQ(flat3.size(), before_size + 1); +} + +TEST(LazyTraversal, FastHierDistinguishesInstances) { + hhds::GraphLibrary lib; + const hhds::Gid root_gid = lib.create_graph(); + const hhds::Gid child_gid = lib.create_graph(); + + auto& root = lib.get_graph(root_gid); + auto& child = lib.get_graph(child_gid); + + (void)root.create_node(); + auto sub1 = root.create_node(); + auto sub2 = root.create_node(); + root.set_subnode(sub1, child_gid); + root.set_subnode(sub2, child_gid); + + (void)child.create_node(); + (void)child.create_node(); + + auto hier1 = root.fast_hier(); + auto hier2 = root.fast_hier(); + EXPECT_EQ(hier1.data(), hier2.data()); + + std::vector unique_child_nodes; + absl::flat_hash_map child_counts_by_raw_nid; + size_t root_count = 0; + for (const auto& node : hier1) { + EXPECT_EQ(node.get_root_gid(), root_gid); + if (node.get_current_gid() == root_gid) { + ++root_count; + continue; + } + if (node.get_current_gid() == child_gid) { + bool seen = false; + for (const auto& existing : unique_child_nodes) { + if (existing == node) { + seen = true; + break; + } + } + if (!seen) { + unique_child_nodes.push_back(node); + } + child_counts_by_raw_nid[node.get_raw_nid()]++; + continue; + } + FAIL() << "Unexpected graph ID in fast_hier traversal"; + } + + EXPECT_EQ(root_count, 4U); // built-ins + one regular node (subnodes are expanded) + EXPECT_EQ(unique_child_nodes.size(), 10U); // two child instances, each with 5 nodes + EXPECT_EQ(child_counts_by_raw_nid.size(), 5U); // child built-ins + 2 created nodes + for (const auto& [raw_nid, count] : child_counts_by_raw_nid) { + (void)raw_nid; + EXPECT_EQ(count, 2U); // each child-graph node appears in two hierarchy instances + } + + const size_t before_size = hier1.size(); + (void)child.create_node(); + auto hier3 = root.fast_hier(); + EXPECT_EQ(hier3.size(), before_size + 2U); // two child instances, each gets one new node +} + +TEST(LazyTraversal, ForwardHierOrderAndEpochInvalidation) { + hhds::GraphLibrary lib; + const hhds::Gid root_gid = lib.create_graph(); + const hhds::Gid child_gid = lib.create_graph(); + + auto& root = lib.get_graph(root_gid); + auto& child = lib.get_graph(child_gid); + + auto root_in = root.create_node(); + auto root_sub = root.create_node(); + auto root_out = root.create_node(); + root.add_edge(root_in, root_sub); + root.add_edge(root_sub, root_out); + root.set_subnode(root_sub, child_gid); + + auto child_in = child.create_node(); + auto child_out = child.create_node(); + child.add_edge(child_in, child_out); + + auto hier1 = root.forward_hier(); + auto hier2 = root.forward_hier(); + EXPECT_EQ(hier1.data(), hier2.data()); + + size_t pos_root_in = hier1.size(); + size_t pos_root_out = hier1.size(); + size_t first_child = hier1.size(); + size_t last_child = 0; + + for (size_t i = 0; i < hier1.size(); ++i) { + const auto& n = hier1[i]; + if (n.get_current_gid() == root_gid && n.get_raw_nid() == root_in.get_raw_nid()) { + pos_root_in = i; + } + if (n.get_current_gid() == root_gid && n.get_raw_nid() == root_out.get_raw_nid()) { + pos_root_out = i; + } + if (n.get_current_gid() == child_gid) { + if (first_child == hier1.size()) { + first_child = i; + } + last_child = i; + } + } + + ASSERT_NE(first_child, hier1.size()); + ASSERT_LT(pos_root_in, first_child); + ASSERT_LT(last_child, pos_root_out); + + const size_t before_size = hier1.size(); + (void)child.create_node(); + auto hier3 = root.forward_hier(); + EXPECT_EQ(hier3.size(), before_size + 1U); +} + // --------------------------------------------------------------------------- // Section 4 (cont.): add_edge(Node_class, Node_class) pin-0 shorthand // --------------------------------------------------------------------------- @@ -347,3 +762,70 @@ TEST(AddEdgeShorthand, NodeToNode) { } EXPECT_EQ(count, 1); } + +// --------------------------------------------------------------------------- +// Section 8: Node pin iteration (api_todo.md #8) +// --------------------------------------------------------------------------- + +TEST(PinIteration, GetPins) { + hhds::Graph g; + auto node = g.create_node(); + auto p1 = node.create_pin(1); + auto p2 = node.create_pin(2); + auto p3 = node.create_pin(3); + + auto pins = g.get_pins(node); + ASSERT_EQ(pins.size(), 3); + EXPECT_EQ(pins[0].get_pin_pid(), p1.get_pin_pid()); + EXPECT_EQ(pins[1].get_pin_pid(), p2.get_pin_pid()); + EXPECT_EQ(pins[2].get_pin_pid(), p3.get_pin_pid()); +} + +TEST(PinIteration, GetPinsIncludesPin0WhenMaterialized) { + hhds::Graph g; + auto node = g.create_node(); + auto p0 = node.create_pin(0); + auto p1 = node.create_pin(1); + auto p2 = node.create_pin(2); + auto p3 = node.create_pin(3); + + auto pins = g.get_pins(node); + ASSERT_EQ(pins.size(), 4); + EXPECT_EQ(pins[0].get_pin_pid(), p0.get_pin_pid()); + EXPECT_EQ(pins[1].get_pin_pid(), p1.get_pin_pid()); + EXPECT_EQ(pins[2].get_pin_pid(), p2.get_pin_pid()); + EXPECT_EQ(pins[3].get_pin_pid(), p3.get_pin_pid()); +} + +TEST(PinIteration, DriverAndSinkPins) { + hhds::Graph g; + auto n1 = g.create_node(); + auto n2 = g.create_node(); + + auto p0 = n1.create_pin(0); // driver-only + auto p1 = n1.create_pin(1); // sink-only + auto p2 = n1.create_pin(2); // both + + auto n2p0 = n2.create_pin(0); + auto n2p1 = n2.create_pin(1); + + g.add_edge(p0, n2p0); + g.add_edge(n2p1, p1); + g.add_edge(p2, n2p0); + g.add_edge(n2p1, p2); + + auto drivers = g.get_driver_pins(n1); + auto sinks = g.get_sink_pins(n1); + + ASSERT_EQ(drivers.size(), 2); + ASSERT_EQ(sinks.size(), 2); + + EXPECT_TRUE(contains_pin_pid(drivers, p0.get_pin_pid())); + EXPECT_FALSE(contains_pin_pid(sinks, p0.get_pin_pid())); + + EXPECT_FALSE(contains_pin_pid(drivers, p1.get_pin_pid())); + EXPECT_TRUE(contains_pin_pid(sinks, p1.get_pin_pid())); + + EXPECT_TRUE(contains_pin_pid(drivers, p2.get_pin_pid())); + EXPECT_TRUE(contains_pin_pid(sinks, p2.get_pin_pid())); +}