docc 0.8.3 (0d44999b)
C++ API Reference
Loading...
Searching...
No Matches
local_buffer_reuse.h
Go to the documentation of this file.
1#pragma once
2
3#include <string>
4#include <tuple>
5#include <typeinfo>
6#include <vector>
7
16#include "sdfg/passes/pass.h"
19
20namespace sdfg {
21namespace passes {
22
23// Forward declarations for variadic template helpers
24namespace detail {
25
26// Helper to check if a block contains a specific library node type
27template<typename LibNode>
29 return blk->dataflow().template is_a_library_node<LibNode>() != nullptr;
30}
31
32// Helper to get library node from block
33template<typename LibNode>
35 return blk->dataflow().template is_a_library_node<LibNode>();
36}
37
38// Check if lib node has ref_container in its outputs
39template<typename LibNode>
40bool lib_node_writes_to_ref(structured_control_flow::Block* blk, const std::string& ref_container) {
41 auto* lib_node = get_lib_node<LibNode>(blk);
42 if (lib_node == nullptr) {
43 return false;
44 }
45
46 auto& dataflow = blk->dataflow();
47 if (dataflow.out_degree(*lib_node) > 0) {
48 for (auto& edge : dataflow.out_edges(*lib_node)) {
49 auto* access = dynamic_cast<const data_flow::AccessNode*>(&edge.dst());
50 if (access != nullptr && access->data() == ref_container) {
51 return true;
52 }
53 }
54 return false;
55 }
56 return true; // No output edges, assume ok for intermediate nodes
57}
58
59// Check if lib node has ref_container in its inputs
60template<typename LibNode>
61bool lib_node_reads_from_ref(structured_control_flow::Block* blk, const std::string& ref_container) {
62 auto* lib_node = get_lib_node<LibNode>(blk);
63 if (lib_node == nullptr) {
64 return false;
65 }
66
67 auto& dataflow = blk->dataflow();
68 for (auto& edge : dataflow.in_edges(*lib_node)) {
69 auto* access = dynamic_cast<const data_flow::AccessNode*>(&edge.src());
70 if (access != nullptr && access->data() == ref_container) {
71 return true;
72 }
73 }
74 return false;
75}
76
77// Check if lib node has ref_container in its outputs or inputs (for last node)
78template<typename LibNode>
79bool lib_node_uses_ref(structured_control_flow::Block* blk, const std::string& ref_container) {
80 auto* lib_node = get_lib_node<LibNode>(blk);
81 if (lib_node == nullptr) {
82 return false;
83 }
84
85 auto& dataflow = blk->dataflow();
86 if (dataflow.out_degree(*lib_node) > 0) {
87 for (auto& edge : dataflow.out_edges(*lib_node)) {
88 auto* access = dynamic_cast<const data_flow::AccessNode*>(&edge.dst());
89 if (access != nullptr && access->data() == ref_container) {
90 return true;
91 }
92 }
93 return false;
94 } else {
95 for (auto& edge : dataflow.in_edges(*lib_node)) {
96 auto* access = dynamic_cast<const data_flow::AccessNode*>(&edge.src());
97 if (access != nullptr && access->data() == ref_container) {
98 return true;
99 }
100 }
101 return false;
102 }
103}
104
105} // namespace detail
106
107// Variadic template version supporting N library node types
108template<class... LibNodes>
110private:
111 static constexpr size_t N = sizeof...(LibNodes);
112
113 std::string references(const structured_control_flow::Block& block, const std::string& container) {
114 auto& dataflow = block.dataflow();
115 if (dataflow.nodes().size() != 2) {
116 return "";
117 }
118 if (dataflow.edges().size() != 1) {
119 return "";
120 }
121 auto& edge = *dataflow.edges().begin();
122 auto* access_src = dynamic_cast<const data_flow::AccessNode*>(&edge.src());
123 if (access_src == nullptr || access_src->data() != container) {
124 return "";
125 }
126 auto* access_dst = dynamic_cast<const data_flow::AccessNode*>(&edge.dst());
127 if (access_dst == nullptr) {
128 return "";
129 }
130 return access_dst->data();
131 }
132
133 // Get block at index, return nullptr if not valid
135 if (idx >= static_cast<int>(seq.size())) {
136 return nullptr;
137 }
138 auto* blk = dyn_cast<structured_control_flow::Block*>(&seq.at(idx));
139 if (blk == nullptr) {
140 return nullptr;
141 }
142 return blk;
143 }
144
145 // Helper to check lib node type at compile-time index
146 template<size_t I>
147 using LibNodeAt = typename std::tuple_element<I, std::tuple<LibNodes...>>::type;
148
149 // Verify a single malloc-ref-lib triple at position k (0-indexed)
150 // Returns: {malloc_container, ref_container} or {"", ""} on failure
151 template<size_t K>
152 std::pair<std::string, std::string>
153 verify_triple(structured_control_flow::Sequence& seq, int base_idx, const std::string& prev_ref_container) {
154 int malloc_idx = base_idx + K * 3;
155 int ref_idx = malloc_idx + 1;
156 int lib_idx = malloc_idx + 2;
157
158 auto* malloc_blk = get_block(seq, malloc_idx);
159 auto* ref_blk = get_block(seq, ref_idx);
160 auto* lib_blk = get_block(seq, lib_idx);
161
162 if (!malloc_blk || !ref_blk || !lib_blk) {
163 return {"", ""};
164 }
165
166 auto* malloc_node = malloc_blk->template is_a_library_node<stdlib::MallocNode>();
167 if (malloc_node == nullptr) {
168 return {"", ""};
169 }
170
171 // Get malloc container
172 auto& dst = (*malloc_blk->dataflow().out_edges(*malloc_node).begin()).dst();
173 std::string malloc_container = dynamic_cast<const data_flow::AccessNode&>(dst).data();
174
175 // Get ref container
176 std::string ref_container = references(*ref_blk, malloc_container);
177 if (ref_container.empty()) {
178 return {"", ""};
179 }
180
181 // Check lib node type
182 if (!detail::block_has_lib_node<LibNodeAt<K>>(lib_blk)) {
183 return {"", ""};
184 }
185
186 // First lib node: just check it writes to its ref_container
187 if constexpr (K == 0) {
188 if (!detail::lib_node_writes_to_ref<LibNodeAt<K>>(lib_blk, ref_container)) {
189 return {"", ""};
190 }
191 }
192 // Last lib node: check it reads from prev ref and writes to its ref
193 else if constexpr (K == N - 1) {
194 if (!detail::lib_node_reads_from_ref<LibNodeAt<K>>(lib_blk, prev_ref_container)) {
195 return {"", ""};
196 }
197 if (!detail::lib_node_uses_ref<LibNodeAt<K>>(lib_blk, ref_container)) {
198 return {"", ""};
199 }
200 }
201 // Middle lib nodes: check reads from prev, writes to current
202 else {
203 if (!detail::lib_node_reads_from_ref<LibNodeAt<K>>(lib_blk, prev_ref_container)) {
204 return {"", ""};
205 }
206 if (!detail::lib_node_writes_to_ref<LibNodeAt<K>>(lib_blk, ref_container)) {
207 return {"", ""};
208 }
209 }
210
211 return {malloc_container, ref_container};
212 }
213
214 // Recursive verification of all triples
215 template<size_t K>
216 bool verify_all_triples(
218 int base_idx,
219 const symbolic::Expression& first_malloc_size,
220 const std::string& prev_ref_container,
221 std::vector<std::string>& malloc_containers,
222 std::vector<std::string>& ref_containers
223 ) {
224 auto [malloc_container, ref_container] = verify_triple<K>(seq, base_idx, prev_ref_container);
225 if (malloc_container.empty()) {
226 return false;
227 }
228
229 // For K > 0, verify malloc sizes match
230 if constexpr (K > 0) {
231 int malloc_idx = base_idx + K * 3;
232 auto* malloc_blk = get_block(seq, malloc_idx);
233 auto* malloc_node = malloc_blk->template is_a_library_node<stdlib::MallocNode>();
234 if (!symbolic::eq(first_malloc_size, malloc_node->size())) {
235 return false;
236 }
237 }
238
239 malloc_containers.push_back(malloc_container);
240 ref_containers.push_back(ref_container);
241
242 if constexpr (K + 1 < N) {
243 return verify_all_triples<
244 K + 1>(seq, base_idx, first_malloc_size, ref_container, malloc_containers, ref_containers);
245 }
246 return true;
247 }
248
249 // Find free block for a malloc container (returns nullptr if not found or constraints not met)
250 structured_control_flow::Block* find_free_block(const std::string& malloc_container, analysis::Users& users_analysis) {
251 auto users = users_analysis.uses(malloc_container);
252 if (users.size() != 3) { // malloc (2 accesses) + ref (2 accesses) - but uses counts access nodes
253 return nullptr;
254 }
255
256 for (auto& user : users) {
257 if (auto* access_node = dynamic_cast<data_flow::AccessNode*>(user->element())) {
258 auto& parent_graph = access_node->get_parent();
259 auto parent_blk = dyn_cast<structured_control_flow::Block*>(parent_graph.get_parent());
260 if (parent_blk != nullptr && parent_blk->dataflow().is_a_library_node<stdlib::FreeNode>() != nullptr) {
261 return parent_blk;
262 }
263 }
264 }
265 return nullptr;
266 }
267
268 // Remove free block for a malloc container
269 bool remove_free_block(structured_control_flow::Block* free_blk) {
270 if (free_blk == nullptr) {
271 return false;
272 }
273
274 auto parent_scope = dyn_cast<structured_control_flow::Sequence*>(free_blk->get_parent());
275 if (parent_scope == nullptr) {
276 return false;
277 }
278
279 int free_blk_index = parent_scope->index(*free_blk);
280 builder_.remove_child(*parent_scope, free_blk_index);
281 return true;
282 }
283
284public:
288
289 static std::string name() {
290 return "LocalBufferReuseN";
291 }
292
293 virtual bool accept(structured_control_flow::Sequence& node) override {
294 bool applied = false;
295
296 auto& users_analysis = this->analysis_manager_.template get<analysis::Users>();
297
298 // Each pattern needs N * 3 blocks: (malloc, ref, lib) for each of N lib nodes
299 constexpr int pattern_size = N * 3;
300
301 int i = 0;
302 while (i + pattern_size <= static_cast<int>(node.size())) {
303 std::vector<std::string> malloc_containers;
304 std::vector<std::string> ref_containers;
305
306 // Get first malloc size for comparison
307 auto* first_malloc_blk = get_block(node, i);
308 if (!first_malloc_blk) {
309 i++;
310 continue;
311 }
312 auto* first_malloc = first_malloc_blk->template is_a_library_node<stdlib::MallocNode>();
313 if (!first_malloc) {
314 i++;
315 continue;
316 }
317
318 // Verify all triples
319 if (!verify_all_triples<0>(node, i, first_malloc->size(), "", malloc_containers, ref_containers)) {
320 i++;
321 continue;
322 }
323
324 // Pattern matched! Now verify all free block constraints before applying changes.
325 // We keep the first malloc/ref and reuse it for subsequent ones.
326
327 // Phase 1: Verify all constraints are met (find all free blocks)
328 std::vector<structured_control_flow::Block*> free_blocks;
329 bool all_constraints_met = true;
330 for (int k = 1; k < static_cast<int>(N); k++) {
331 auto* free_blk = find_free_block(malloc_containers[k], users_analysis);
332 if (free_blk == nullptr) {
333 all_constraints_met = false;
334 break;
335 }
336 auto parent_scope = dyn_cast<structured_control_flow::Sequence*>(free_blk->get_parent());
337 if (parent_scope == nullptr) {
338 all_constraints_met = false;
339 break;
340 }
341 free_blocks.push_back(free_blk);
342 }
343
344 if (!all_constraints_met) {
345 i++;
346 continue;
347 }
348
349 // Phase 2: Apply all changes (process from last to first to maintain correct indices)
350 for (int k = N - 1; k >= 1; k--) {
351 // Update reference block to use previous ref_container instead of this malloc_container
352 int ref_idx = i + k * 3 + 1;
353 auto* ref_blk = get_block(node, ref_idx);
354 if (ref_blk) {
355 for (auto& n : ref_blk->dataflow().nodes()) {
356 auto* access = dynamic_cast<data_flow::AccessNode*>(&n);
357 if (access != nullptr && access->data() == malloc_containers[k]) {
358 access->data(ref_containers[k - 1]);
359 }
360 }
361 }
362
363 // Remove the malloc block (at index i + k * 3)
364 int malloc_idx = i + k * 3;
365 builder_.remove_child(node, malloc_idx);
366
367 // Remove the free block for this malloc container
368 remove_free_block(free_blocks[k - 1]);
369
371 "Eliminated tensor with containers " << malloc_containers[0] << " and " << malloc_containers[k]
372 );
373 }
374
375 applied = true;
376 i++;
377 }
378
379 return applied;
380 }
381};
382
383// Keep the original 2-lib-node version for backward compatibility
384template<class T, class S>
386
387// Type aliases for common patterns with 2 lib nodes
390
391// Type aliases for 3 lib nodes
394
395// Type aliases for 4 lib nodes
396typedef VisitorPass<
399
400// Type aliases for 5 lib nodes
408
410 Pipeline pipeline("LocalBufferReusePipeline");
411
412 // Register passes from longest to shortest to maximize elimination
413 // 5 lib nodes first
414 // pipeline.register_pass<ConvBatchNormReLUConvBatchNormEliminationPass>();
415
416 // 4 lib nodes
417 // pipeline.register_pass<ConvBatchNormReLUConvEliminationPass>();
418
419 // The 5 and 4 lib node passes are not safe in generla - only with im2col/im2row conv expansion.
420
421 // 3 lib nodes
423
424 // 2 lib nodes (original patterns)
427
428 return pipeline;
429}
430
431} // namespace passes
432} // namespace sdfg
Definition analysis.h:53
Note: Even though the class references unique_ptr, it will never delete an SDFG it has a reference to...
Definition structured_sdfg_builder.h:44
Node representing access to a data container in the dataflow graph.
Definition access_node.h:84
const std::string & data() const
Get the name of the data container.
auto edges() const
Definition data_flow_graph.h:76
In N, C, D0...Dn tensor, applies normalization to per channel in C across all the inner dimensions.
Definition batchnorm_node.h:14
Convolution operation following ONNX Conv operator specification.
Definition conv_node.h:72
Definition relu_node.h:14
Definition local_buffer_reuse.h:109
LocalBufferReuseN(builder::StructuredSDFGBuilder &builder, analysis::AnalysisManager &analysis_manager)
Definition local_buffer_reuse.h:285
static std::string name()
Definition local_buffer_reuse.h:289
virtual bool accept(structured_control_flow::Sequence &node) override
Definition local_buffer_reuse.h:293
Definition pipeline.h:23
void register_pass(Args &&... args)
Definition pipeline.h:43
Definition pass.h:55
Definition free.h:14
A control flow node representing a basic block with dataflow computations.
Definition block.h:42
const data_flow::DataFlowGraph & dataflow() const
Access the dataflow graph (const version).
A sequential container of control flow nodes.
Definition sequence.h:52
const ControlFlowNode & at(size_t i) const
Access a child by index (const version).
size_t size() const
Get the number of children in this sequence.
Definition users.h:19
std::vector< User * > uses() const
Definition structured_sdfg_visitor.h:53
NonStoppingStructuredSDFGVisitor(builder::StructuredSDFGBuilder &builder, analysis::AnalysisManager &analysis_manager)
analysis::AnalysisManager & analysis_manager_
Definition structured_sdfg_visitor.h:19
builder::StructuredSDFGBuilder & builder_
Definition structured_sdfg_visitor.h:18
bool eq(const Expression lhs, const Expression rhs)
Tests structural equality of two expressions.
SymEngine::RCP< const SymEngine::Basic > Expression
Reference-counted pointer to a symbolic expression (SymEngine Basic).
Definition symbolic.h:83
#define DEBUG_PRINTLN(msg)
Definition helpers.h:12
Definition function_builder.h:10
Definition local_buffer_reuse.h:24
bool lib_node_uses_ref(structured_control_flow::Block *blk, const std::string &ref_container)
Definition local_buffer_reuse.h:79
bool block_has_lib_node(structured_control_flow::Block *blk)
Definition local_buffer_reuse.h:28
LibNode * get_lib_node(structured_control_flow::Block *blk)
Definition local_buffer_reuse.h:34
bool lib_node_reads_from_ref(structured_control_flow::Block *blk, const std::string &ref_container)
Definition local_buffer_reuse.h:61
bool lib_node_writes_to_ref(structured_control_flow::Block *blk, const std::string &ref_container)
Definition local_buffer_reuse.h:40
Definition approximate_math.h:12
VisitorPass< LocalBufferReuseN< math::tensor::ConvNode, math::tensor::BatchNormNode, math::tensor::ReLUNode > > ConvBatchNormReLUEliminationPass
Definition local_buffer_reuse.h:393
LocalBufferReuseN< T, S > LocalBufferReuse
Definition local_buffer_reuse.h:385
VisitorPass< LocalBufferReuseN< math::tensor::ConvNode, math::tensor::BatchNormNode, math::tensor::ReLUNode, math::tensor::ConvNode > > ConvBatchNormReLUConvEliminationPass
Definition local_buffer_reuse.h:398
VisitorPass< LocalBufferReuseN< math::tensor::ConvNode, math::tensor::BatchNormNode > > ConvBatchNormEliminationPass
Definition local_buffer_reuse.h:388
VisitorPass< LocalBufferReuseN< math::tensor::ConvNode, math::tensor::BatchNormNode, math::tensor::ReLUNode, math::tensor::ConvNode, math::tensor::BatchNormNode > > ConvBatchNormReLUConvBatchNormEliminationPass
Definition local_buffer_reuse.h:407
Pipeline local_buffer_reuse_pipeline()
Definition local_buffer_reuse.h:409
VisitorPass< LocalBufferReuseN< math::tensor::BatchNormNode, math::tensor::ReLUNode > > BatchNormReLUEliminationPass
Definition local_buffer_reuse.h:389
Definition base_user_visitor.h:5
Definition control_flow_node.h:16
Definition analysis.h:7
element_cast_target_t< T > * dyn_cast(Element *element)
Efficient down-cast to T, or nullptr if element is not a T.
Definition element.h:246