YOrch 1.0.0
Loading...
Searching...
No Matches
serial_dfs_explicit_heap_stack.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cstddef>
5#include <utility>
6#include <vector>
7
8#include "../../result.hpp"
9#include "node_entry.hpp"
10
11namespace yorch::detail {
12
22 std::size_t node_index = 0;
23 std::size_t next_child_ordinal = 0;
24 bool entered = false;
25 bool payload_live = false;
27 bool fanout_prepared = false;
28};
29
38template <std::size_t I, typename Plan, typename Slots>
40 Plan& plan,
41 Slots& slots,
43 return enter_node<I>(plan, slots, fanout);
44}
45
49template <std::size_t I, typename Plan, typename Slots, typename Ctx>
51 Plan& plan,
52 Slots& slots,
54 Ctx& ctx) {
55 return enter_node<I>(plan, slots, fanout, ctx);
56}
57
65template <std::size_t I, typename Slots>
66constexpr void destroy_node_payload_runtime(Slots& slots) noexcept {
67 slots.template destroy<I>();
68}
69
70template <std::size_t I, typename Plan, typename Slots>
71constexpr void prepare_fanout_runtime(Slots& slots, plan_fanout_state<Plan>& fanout) {
72 fanout.template prepare_fanout_staging<I>(slots);
73}
74
75template <std::size_t I, typename Plan>
76constexpr void destroy_fanout_runtime(plan_fanout_state<Plan>& fanout) noexcept {
77 fanout.template destroy_fanout_staging<I>();
78}
79
96template <typename Plan, typename Slots, std::size_t... I>
97[[nodiscard]] consteval auto make_enter_node_dispatch_table(std::index_sequence<I...>) {
99 return std::array<fn_t, sizeof...(I)> {
101 };
102}
103
108template <typename Plan, typename Slots, typename Ctx, std::size_t... I>
109[[nodiscard]] consteval auto make_enter_node_dispatch_table(std::index_sequence<I...>) {
111 return std::array<fn_t, sizeof...(I)> {
113 };
114}
115
124template <typename Plan, typename Slots, std::size_t... I>
125[[nodiscard]] consteval auto make_destroy_node_dispatch_table(std::index_sequence<I...>) {
126 using fn_t = void (*)(Slots&) noexcept;
127 return std::array<fn_t, sizeof...(I)> {
129 };
130}
131
132template <typename Plan, typename Slots, std::size_t... I>
133[[nodiscard]] consteval auto make_prepare_fanout_dispatch_table(std::index_sequence<I...>) {
134 using fn_t = void (*)(Slots&, plan_fanout_state<Plan>&);
135 return std::array<fn_t, sizeof...(I)> {
137 };
138}
139
140template <typename Plan, typename Slots, std::size_t... I>
141[[nodiscard]] consteval auto make_destroy_fanout_dispatch_table(std::index_sequence<I...>) {
142 using fn_t = void (*)(plan_fanout_state<Plan>&) noexcept;
143 return std::array<fn_t, sizeof...(I)> {
145 };
146}
147
148template <typename Plan, std::size_t... I>
149[[nodiscard]] consteval auto make_requires_fanout_staging_table(std::index_sequence<I...>) {
150 return std::array<bool, sizeof...(I)> {
152 };
153}
154
165template <typename Plan, typename EnterInvoker, typename DestroyInvoker, typename PrepareFanoutInvoker, typename DestroyFanoutInvoker>
172 std::vector<dfs_stack_frame> frames;
173 frames.reserve(Plan::max_level + 1);
174 frames.push_back(dfs_stack_frame {
175 .node_index = 0,
176 .requires_fanout_staging = requires_fanout_staging_by_index[0]});
177
178 // `current_step` always represents the result of the subtree that most
179 // recently finished. `subtree_complete == false` means we are still
180 // descending into the current top frame. `subtree_complete == true` means
181 // the top subtree has ended and its result now needs to be consumed by the
182 // parent frame.
184 bool subtree_complete = false;
185
186 while (!frames.empty()) {
187 auto& frame = frames.back();
188
189 if (!subtree_complete) {
190 // First time we see this frame: execute the node itself, materialize
191 // its payload if needed, and record whether that payload must stay
192 // alive until the subtree later unwinds.
193 if (!frame.entered) {
194 const auto entered = enter_node_by_index(frame.node_index);
195 frame.entered = true;
196 frame.payload_live = entered.payload_live;
197
198 // Non-success from the node body ends this subtree immediately.
199 // Child traversal is skipped and the result is handed to the
200 // parent during the unwind phase below.
201 if (!entered.step.ok()) {
202 current_step = entered.step;
203 subtree_complete = true;
204 continue;
205 }
206 }
207
208 if (frame.requires_fanout_staging && !frame.fanout_prepared) {
209 prepare_fanout_by_index(frame.node_index);
210 frame.fanout_prepared = true;
211 }
212
213 // The node body already succeeded and there are no more children to
214 // visit, so this whole subtree completes with `success`.
215 if (frame.next_child_ordinal == Plan::child_counts[frame.node_index]) {
217 subtree_complete = true;
218 continue;
219 }
220
221 // Descend into the next direct child of the current node. Children
222 // are stored in one flattened adjacency array, so this uses the
223 // parent's child-list start offset plus the next child ordinal to
224 // recover the concrete child node index.
225 const auto child_index = Plan::child_indices[
226 Plan::child_offsets[frame.node_index] + frame.next_child_ordinal];
227 frames.push_back(dfs_stack_frame {
228 .node_index = child_index,
229 .requires_fanout_staging = requires_fanout_staging_by_index[child_index]});
230 continue;
231 }
232
233 // We are leaving the current subtree. If the node produced a live
234 // payload, destroy it now because the node is about to leave the active
235 // DFS path.
236 // Take a by-value snapshot before `pop_back()` so later stack mutation
237 // cannot leave us with a dangling reference to the finished frame.
238 const auto finished = frames.back();
239 if (finished.requires_fanout_staging && finished.fanout_prepared) {
241 }
242 if (finished.payload_live) {
244 }
245
246 frames.pop_back();
247 if (frames.empty()) {
248 // Popping the last frame means the root subtree just finished, so
249 // `current_step` is the final plan result.
250 return current_step;
251 }
252
253 auto& parent = frames.back();
255 // From the parent's perspective both `success` and `abort_branch`
256 // mean "the current child subtree has ended, move to the next
257 // sibling if there is one".
258 ++parent.next_child_ordinal;
259
260 if (parent.next_child_ordinal == Plan::child_counts[parent.node_index]) {
261 // This parent has now exhausted all direct children, so its own
262 // subtree finishes successfully and the unwind continues upward.
264 subtree_complete = true;
265 } else {
266 // There is another sibling to execute, so switch back to the
267 // descend phase and keep the parent frame active.
268 subtree_complete = false;
269 }
270
271 continue;
272 }
273
274 // `failure`, `retry`, and `abort_execution` are non-local results. They
275 // stop sibling traversal at every level, so we keep unwinding with the
276 // same `current_step` until the stack becomes empty.
277 subtree_complete = true;
278 }
279
280 return current_step;
281}
282
283template <typename Plan, typename Slots>
285 static constexpr auto enter =
287 std::make_index_sequence<Plan::node_count> {});
288 static constexpr auto destroy =
290 std::make_index_sequence<Plan::node_count> {});
291 static constexpr auto prepare_fanout =
293 std::make_index_sequence<Plan::node_count> {});
294 static constexpr auto destroy_fanout =
296 std::make_index_sequence<Plan::node_count> {});
297 static constexpr auto requires_fanout_staging =
299 std::make_index_sequence<Plan::node_count> {});
300};
301
302template <typename Plan, typename Slots, typename Ctx>
304 static constexpr auto enter =
306 std::make_index_sequence<Plan::node_count> {});
307 static constexpr auto destroy =
309 std::make_index_sequence<Plan::node_count> {});
310 static constexpr auto prepare_fanout =
312 std::make_index_sequence<Plan::node_count> {});
313 static constexpr auto destroy_fanout =
315 std::make_index_sequence<Plan::node_count> {});
316 static constexpr auto requires_fanout_staging =
318 std::make_index_sequence<Plan::node_count> {});
319};
320
321template <typename Plan, typename Slots>
323 Plan& plan,
324 Slots& slots,
325 plan_fanout_state<Plan>& fanout) {
328 [&](std::size_t node_index) constexpr -> node_enter_result {
329 return explicit_heap_stack_dispatch<Plan, Slots>::enter[node_index](plan, slots, fanout);
330 },
331 [&](std::size_t node_index) constexpr {
333 },
334 [&](std::size_t node_index) constexpr {
336 },
337 [&](std::size_t node_index) constexpr {
339 });
340}
341
342template <typename Plan, typename Slots, typename Ctx>
344 Plan& plan,
345 Slots& slots,
347 Ctx& ctx) {
350 [&](std::size_t node_index) constexpr -> node_enter_result {
352 plan,
353 slots,
354 fanout,
355 ctx);
356 },
357 [&](std::size_t node_index) constexpr {
359 },
360 [&](std::size_t node_index) constexpr {
362 slots,
363 fanout);
364 },
365 [&](std::size_t node_index) constexpr {
367 fanout);
368 });
369}
370
371} // namespace yorch::detail
consteval auto make_destroy_fanout_dispatch_table(std::index_sequence< I... >)
constexpr void destroy_node_payload_runtime(Slots &slots) noexcept
Per-node runtime thunk for destroying node I's payload slot.
constexpr step_result run_explicit_heap_stack(Plan &plan, Slots &slots, plan_fanout_state< Plan > &fanout)
constexpr node_enter_result enter_node_runtime(Plan &plan, Slots &slots, plan_fanout_state< Plan > &fanout)
Per-node runtime thunk for entering node I.
consteval auto make_requires_fanout_staging_table(std::index_sequence< I... >)
consteval auto make_prepare_fanout_dispatch_table(std::index_sequence< I... >)
constexpr void prepare_fanout_runtime(Slots &slots, plan_fanout_state< Plan > &fanout)
constexpr step_result run_explicit_heap_stack_loop(const auto &requires_fanout_staging_by_index, EnterInvoker enter_node_by_index, DestroyInvoker destroy_node_payload_by_index, PrepareFanoutInvoker prepare_fanout_by_index, DestroyFanoutInvoker destroy_fanout_by_index)
Executes serial DFS using a heap-backed explicit stack.
constexpr void destroy_fanout_runtime(plan_fanout_state< Plan > &fanout) noexcept
consteval auto make_enter_node_dispatch_table(std::index_sequence< I... >)
Builds the runtime dispatch table for node entry without external context.
consteval auto make_destroy_node_dispatch_table(std::index_sequence< I... >)
Builds the runtime dispatch table for payload destruction.
constexpr bool is_adapter_descriptor_v
Definition adapters.hpp:63
Runtime frame used by the explicit-heap-stack DFS executor.
Represents the basic outcome of a task step.
Definition result.hpp:42
static constexpr step_result success() noexcept
Creates a successful result.
Definition result.hpp:46