• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daisytuner / docc / 26556322966

27 May 2026 03:45PM UTC coverage: 60.869% (-0.02%) from 60.886%
26556322966

push

github

web-flow
Libnode ptr edges (#719)

Migrating SDFGs to treat pointers as inputs to libNodes / Calls as scalars.
A pointer will only appear in an output edge if its actually returned from the function (like malloc).

* Stdlib, Blas and Tensor Matmul nodes were migrated to this new format. Other, currently transitory Tensor Nodes are not yet migrated.
* DOCC version was bumped to incorporate previous docc-llvm versions (up to 0.4.0) that had been counted separately.
! Until all passes consider the use / leak of pointers as uncertainty / hiding potential writes, TensorNodes are declared as general side-effect.
* Lots of utility functions to centralize the creation (and edges) of various libNodes that needed to be changed.
* Fixed & unified docc paths across python and llvm front-ends.
* Skip BlockFusion test that fails to its libNodes currently having side effects
~ Prevent a crash in DotViz when using symbolic offsets into structs
* Removing old ConstProp pass, it is not safe for the new pointer representation and should not be all too critical

961 of 1749 new or added lines in 52 files covered. (54.95%)

87 existing lines in 28 files now uncovered.

35225 of 57870 relevant lines covered (60.87%)

11046.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.72
/opt/src/passes/offloading/code_motion/block_hoisting.cpp
1
#include "sdfg/passes/offloading/code_motion/block_hoisting.h"
2
#include <cstddef>
3
#include <string>
4
#include <unordered_map>
5
#include <vector>
6

7
#include "sdfg/analysis/analysis.h"
8
#include "sdfg/analysis/scope_analysis.h"
9
#include "sdfg/analysis/users.h"
10
#include "sdfg/builder/structured_sdfg_builder.h"
11
#include "sdfg/data_flow/access_node.h"
12
#include "sdfg/data_flow/data_flow_graph.h"
13
#include "sdfg/data_flow/library_node.h"
14
#include "sdfg/data_flow/library_nodes/stdlib/alloca.h"
15
#include "sdfg/data_flow/library_nodes/stdlib/memcpy.h"
16
#include "sdfg/data_flow/library_nodes/stdlib/memmove.h"
17
#include "sdfg/data_flow/library_nodes/stdlib/memset.h"
18
#include "sdfg/data_flow/memlet.h"
19
#include "sdfg/data_flow/tasklet.h"
20
#include "sdfg/element.h"
21
#include "sdfg/structured_control_flow/block.h"
22
#include "sdfg/structured_control_flow/control_flow_node.h"
23
#include "sdfg/structured_control_flow/for.h"
24
#include "sdfg/structured_control_flow/if_else.h"
25
#include "sdfg/structured_control_flow/map.h"
26
#include "sdfg/structured_control_flow/sequence.h"
27
#include "sdfg/structured_control_flow/structured_loop.h"
28
#include "sdfg/symbolic/symbolic.h"
29
#include "sdfg/targets/offloading/data_offloading_node.h"
30
#include "sdfg/visitor/structured_sdfg_visitor.h"
31

32
namespace sdfg {
33
namespace passes {
34

35
BlockHoisting::BlockHoisting(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager)
36
    : visitor::NonStoppingStructuredSDFGVisitor(builder, analysis_manager) {};
22✔
37

38
bool BlockHoisting::accept(structured_control_flow::Map& map_stmt) {
23✔
39
    auto& scope_analysis = analysis_manager_.get<analysis::ScopeAnalysis>();
23✔
40
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&map_stmt));
23✔
41

42
    bool applied = false;
23✔
43
    applied |= this->map_invariant_front(parent, map_stmt);
23✔
44
    applied |= this->map_invariant_back(parent, map_stmt);
23✔
45

46
    return applied;
23✔
47
}
23✔
48

49
bool BlockHoisting::accept(structured_control_flow::IfElse& if_else) {
18✔
50
    // Ignore incomplete branches for now
51
    if (if_else.size() == 0 || !if_else.is_complete()) {
18✔
52
        return false;
×
53
    }
×
54

55
    auto& scope_analysis = analysis_manager_.get<analysis::ScopeAnalysis>();
18✔
56
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&if_else));
18✔
57

58
    bool applied = false;
18✔
59
    applied |= this->if_else_invariant_front(parent, if_else);
18✔
60
    applied |= this->if_else_invariant_back(parent, if_else);
18✔
61

62
    return applied;
18✔
63
}
18✔
64

65
bool BlockHoisting::is_libnode_allowed(
66
    structured_control_flow::Sequence& body, data_flow::DataFlowGraph& dfg, data_flow::LibraryNode* libnode
67
) {
29✔
68
    if (dynamic_cast<stdlib::AllocaNode*>(libnode)) {
29✔
69
        return true;
3✔
70
    } else if (dynamic_cast<stdlib::MemcpyNode*>(libnode)) {
26✔
71
        return true;
6✔
72
    } else if (dynamic_cast<stdlib::MemmoveNode*>(libnode)) {
20✔
73
        return true;
6✔
74
    } else if (dynamic_cast<stdlib::MemsetNode*>(libnode)) {
14✔
75
        return true;
3✔
76
    } else if (dynamic_cast<offloading::DataOffloadingNode*>(libnode)) {
11✔
77
        return true;
11✔
78
    } else {
11✔
79
        return false;
×
80
    }
×
81
}
29✔
82

83
bool BlockHoisting::equal_libnodes(structured_control_flow::Block& block1, structured_control_flow::Block& block2) {
12✔
84
    if (equal_lib_blocks(block1, block2)) {
12✔
85
        return true;
6✔
86
    }
6✔
87

88
    auto* libnode1 = *block1.dataflow().library_nodes().begin();
6✔
89
    auto* libnode2 = *block2.dataflow().library_nodes().begin();
6✔
90
    if (auto* offloading_node1 = dynamic_cast<offloading::DataOffloadingNode*>(libnode1)) {
6✔
91
        if (auto* offloading_node2 = dynamic_cast<offloading::DataOffloadingNode*>(libnode2)) {
6✔
92
            return this->equal_offloading_nodes(block1, offloading_node1, block2, offloading_node2);
6✔
93
        }
6✔
94
    }
6✔
95
    return false;
×
96
}
6✔
97

98
bool BlockHoisting::equal_lib_blocks(structured_control_flow::Block& block1, structured_control_flow::Block& block2) {
12✔
99
    auto& dfg1 = block1.dataflow();
12✔
100
    auto& dfg2 = block2.dataflow();
12✔
101

102
    // Get library nodes
103
    auto* libnode1 = *dfg1.library_nodes().begin();
12✔
104
    auto* libnode2 = *dfg2.library_nodes().begin();
12✔
105

106
    // Collect in edges
107
    std::unordered_map<std::string, data_flow::Memlet*> iedges1, iedges2;
12✔
108
    for (auto& iedge : dfg1.in_edges(*libnode1)) {
17✔
109
        iedges1.insert({iedge.dst_conn(), &iedge});
17✔
110
    }
17✔
111
    for (auto& iedge : dfg2.in_edges(*libnode2)) {
17✔
112
        iedges2.insert({iedge.dst_conn(), &iedge});
17✔
113
    }
17✔
114

115
    // In edges must have the same type, subset, and container
116
    if (iedges1.size() != iedges2.size()) {
12✔
117
        return false;
×
118
    }
×
119
    for (auto [conn, iedge1] : iedges1) {
15✔
120
        if (!iedges2.contains(conn)) {
15✔
121
            return false;
×
122
        }
×
123
        auto* iedge2 = iedges2.at(conn);
15✔
124

125
        // Compare types
126
        if (iedge1->type() != iedge2->type()) {
15✔
127
            return false;
×
128
        }
×
129

130
        // Compare subsets
131
        if (iedge1->subset().size() != iedge2->subset().size()) {
15✔
132
            return false;
×
133
        }
×
134
        for (size_t i = 0; i < iedge1->subset().size(); i++) {
15✔
135
            if (!symbolic::eq(iedge1->subset().at(i), iedge2->subset().at(i))) {
×
136
                return false;
×
137
            }
×
138
        }
×
139

140
        // Compare containers
141
        auto& src1 = static_cast<data_flow::AccessNode&>(iedge1->src());
15✔
142
        auto& src2 = static_cast<data_flow::AccessNode&>(iedge2->src());
15✔
143
        if (!data_flow::AccessNode::identicalBackingData(src1, src2)) {
15✔
144
            return false;
2✔
145
        }
2✔
146
    }
15✔
147

148
    // Collect out edges
149
    std::unordered_map<std::string, data_flow::Memlet*> oedges1, oedges2;
10✔
150
    for (auto& oedge : dfg1.out_edges(*libnode1)) {
10✔
151
        oedges1.insert({oedge.src_conn(), &oedge});
5✔
152
    }
5✔
153
    for (auto& oedge : dfg2.out_edges(*libnode2)) {
10✔
154
        oedges2.insert({oedge.src_conn(), &oedge});
5✔
155
    }
5✔
156

157
    // Out edges must have the same type, subset, and container
158
    if (oedges1.size() != oedges2.size()) {
10✔
159
        return false;
×
160
    }
×
161
    for (auto [conn, oedge1] : oedges1) {
10✔
162
        if (!oedges2.contains(conn)) {
5✔
163
            return false;
×
164
        }
×
165
        auto& oedge2 = oedges2.at(conn);
5✔
166

167
        // Compare types
168
        if (oedge1->type() != oedge2->type()) {
5✔
169
            return false;
×
170
        }
×
171

172
        // Compare subsets
173
        if (oedge1->subset().size() != oedge2->subset().size()) {
5✔
174
            return false;
×
175
        }
×
176
        for (size_t i = 0; i < oedge1->subset().size(); i++) {
5✔
177
            if (!symbolic::eq(oedge1->subset().at(i), oedge2->subset().at(i))) {
×
178
                return false;
×
179
            }
×
180
        }
×
181

182
        // Compare containers
183
        auto& dst1 = static_cast<data_flow::AccessNode&>(oedge1->dst());
5✔
184
        auto& dst2 = static_cast<data_flow::AccessNode&>(oedge2->dst());
5✔
185
        if (!data_flow::AccessNode::identicalBackingData(dst1, dst2)) {
5✔
186
            return false;
4✔
187
        }
4✔
188
    }
5✔
189

190
    // Checks library node internals
191
    return this->equal_libnodes(libnode1, libnode2);
6✔
192
}
10✔
193

194
void BlockHoisting::if_else_extract_invariant_libnode_front(
195
    structured_control_flow::Sequence& parent, structured_control_flow::IfElse& if_else
196
) {
6✔
197
    auto& first_block = static_cast<structured_control_flow::Block&>(if_else.at(0).first.at(0).first);
6✔
198
    auto& first_dfg = first_block.dataflow();
6✔
199
    if (!first_dfg.library_nodes().empty()) {
6✔
200
        auto* first_libnode = *first_dfg.library_nodes().begin();
6✔
201
        if (auto* offloading_node = dynamic_cast<offloading::DataOffloadingNode*>(first_libnode)) {
6✔
202
            auto dev_in_idx = offloading_node->dev_ptr_input_idx();
2✔
203
            auto dev_out_idx = offloading_node->dev_ptr_output_idx();
2✔
204
            if (dev_in_idx >= 0) {
2✔
NEW
205
                auto* first_iedge =
×
NEW
206
                    first_dfg.in_edge_for_connector(*offloading_node, offloading_node->inputs().at(dev_in_idx));
×
NEW
207
                auto& first_src = static_cast<const data_flow::AccessNode&>(first_iedge->src());
×
UNCOV
208
                std::string first_device_container = first_src.data();
×
209

210
                for (size_t i = 1; i < if_else.size(); i++) {
×
211
                    auto& other_block = static_cast<structured_control_flow::Block&>(if_else.at(i).first.at(0).first);
×
212
                    auto& other_dfg = other_block.dataflow();
×
213
                    auto* other_offloading_node =
×
214
                        dynamic_cast<offloading::DataOffloadingNode*>(*other_dfg.library_nodes().begin());
×
NEW
215
                    auto* other_iedge =
×
NEW
216
                        other_dfg.in_edge_for_connector(*other_offloading_node, other_offloading_node->dev_in_conn());
×
NEW
217
                    auto& other_src = static_cast<const data_flow::AccessNode&>(other_iedge->src());
×
UNCOV
218
                    std::string other_device_container = other_src.data();
×
219

220
                    if_else.at(i)
×
221
                        .first
×
222
                        .replace(symbolic::symbol(other_device_container), symbolic::symbol(first_device_container));
×
223
                }
×
224
            } else if (dev_out_idx >= 0) {
2✔
225
                auto& first_oedge =
2✔
226
                    **first_dfg.out_edges_for_connector(*first_libnode, first_libnode->outputs().at(dev_out_idx))
2✔
227
                          .begin();
2✔
228
                auto& first_dst = static_cast<const data_flow::AccessNode&>(first_oedge.dst());
2✔
229
                std::string first_device_container = first_dst.data();
2✔
230

231
                for (size_t i = 1; i < if_else.size(); i++) {
6✔
232
                    auto& other_block = static_cast<structured_control_flow::Block&>(if_else.at(i).first.at(0).first);
4✔
233
                    auto& other_dfg = other_block.dataflow();
4✔
234
                    auto* other_libnode =
4✔
235
                        dynamic_cast<offloading::DataOffloadingNode*>(*other_dfg.library_nodes().begin());
4✔
236
                    auto& other_oedge =
4✔
237
                        **other_dfg.out_edges_for_connector(*other_libnode, other_libnode->dev_out_conn()).begin();
4✔
238
                    auto& other_dst = static_cast<const data_flow::AccessNode&>(other_oedge.dst());
4✔
239
                    std::string other_device_container = other_dst.data();
4✔
240

241
                    if_else.at(i)
4✔
242
                        .first
4✔
243
                        .replace(symbolic::symbol(other_device_container), symbolic::symbol(first_device_container));
4✔
244
                }
4✔
245
            }
2✔
246
        }
2✔
247
    }
6✔
248
    // This function is a wrapper that can be overridden in sub-classes
249
    this->if_else_extract_invariant_front(parent, if_else);
6✔
250
}
6✔
251

252
void BlockHoisting::if_else_extract_invariant_libnode_back(
253
    structured_control_flow::Sequence& parent, structured_control_flow::IfElse& if_else
254
) {
3✔
255
    size_t first_size = if_else.at(0).first.size();
3✔
256
    auto& first_block = static_cast<structured_control_flow::Block&>(if_else.at(0).first.at(first_size - 1).first);
3✔
257
    auto& first_dfg = first_block.dataflow();
3✔
258
    if (!first_dfg.library_nodes().empty()) {
3✔
259
        auto* first_libnode = *first_dfg.library_nodes().begin();
3✔
260
        if (auto* offloading_node = dynamic_cast<offloading::DataOffloadingNode*>(first_libnode)) {
3✔
261
            auto dev_in_idx = offloading_node->dev_ptr_input_idx();
1✔
262
            auto dev_out_idx = offloading_node->dev_ptr_output_idx();
1✔
263
            if (dev_in_idx >= 0) {
1✔
264
                auto* first_iedge =
1✔
265
                    &*first_dfg.in_edge_for_connector(*offloading_node, offloading_node->inputs().at(dev_in_idx));
1✔
266
                auto& first_src = static_cast<const data_flow::AccessNode&>(first_iedge->src());
1✔
267
                std::string first_device_container = first_src.data();
1✔
268

269
                for (size_t i = 1; i < if_else.size(); i++) {
3✔
270
                    size_t other_size = if_else.at(i).first.size();
2✔
271
                    auto& other_block =
2✔
272
                        static_cast<structured_control_flow::Block&>(if_else.at(i).first.at(other_size - 1).first);
2✔
273
                    auto& other_dfg = other_block.dataflow();
2✔
274
                    auto* other_offloading_node =
2✔
275
                        dynamic_cast<offloading::DataOffloadingNode*>(*other_dfg.library_nodes().begin());
2✔
276
                    auto* other_iedge =
2✔
277
                        other_dfg.in_edge_for_connector(*other_offloading_node, other_offloading_node->dev_in_conn());
2✔
278
                    auto& other_src = static_cast<const data_flow::AccessNode&>(other_iedge->src());
2✔
279
                    std::string other_device_container = other_src.data();
2✔
280

281
                    if_else.at(i)
2✔
282
                        .first
2✔
283
                        .replace(symbolic::symbol(other_device_container), symbolic::symbol(first_device_container));
2✔
284
                }
2✔
285
            } else if (dev_out_idx >= 0) {
1✔
NEW
286
                auto& first_oedge =
×
NEW
287
                    **first_dfg.out_edges_for_connector(*offloading_node, offloading_node->outputs().at(dev_out_idx))
×
NEW
288
                          .begin();
×
NEW
289
                auto& first_dst = static_cast<const data_flow::AccessNode&>(first_oedge.dst());
×
UNCOV
290
                std::string first_device_container = first_dst.data();
×
291

292
                for (size_t i = 1; i < if_else.size(); i++) {
×
293
                    size_t other_size = if_else.at(i).first.size();
×
294
                    auto& other_block =
×
295
                        static_cast<structured_control_flow::Block&>(if_else.at(i).first.at(other_size - 1).first);
×
296
                    auto& other_dfg = other_block.dataflow();
×
NEW
297
                    auto* other_libnode =
×
NEW
298
                        dynamic_cast<offloading::DataOffloadingNode*>(*other_dfg.library_nodes().begin());
×
NEW
299
                    auto& other_oedge =
×
NEW
300
                        **other_dfg.out_edges_for_connector(*other_libnode, other_libnode->dev_out_conn()).begin();
×
NEW
301
                    auto& other_dst = static_cast<const data_flow::AccessNode&>(other_oedge.dst());
×
UNCOV
302
                    std::string other_device_container = other_dst.data();
×
303

304
                    if_else.at(i)
×
305
                        .first
×
306
                        .replace(symbolic::symbol(other_device_container), symbolic::symbol(first_device_container));
×
307
                }
×
308
            }
×
309
        }
1✔
310
    }
3✔
311
    // This function is a wrapper that can be overridden in sub-classes
312
    this->if_else_extract_invariant_back(parent, if_else);
3✔
313
}
3✔
314

315
bool BlockHoisting::is_invariant_move(
316
    structured_control_flow::Sequence& body, data_flow::DataFlowGraph& dfg, bool no_loop_carried_dependencies
317
) {
41✔
318
    if (dfg.nodes().size() != 2) {
41✔
319
        return false;
30✔
320
    }
30✔
321
    if (dfg.edges().size() != 1) {
11✔
322
        return false;
×
323
    }
×
324
    auto& edge = *dfg.edges().begin();
11✔
325
    if (edge.type() != data_flow::MemletType::Dereference_Src) {
11✔
326
        return false;
8✔
327
    }
8✔
328

329
    if (no_loop_carried_dependencies) {
3✔
330
        return true;
3✔
331
    }
3✔
332
    auto& src = static_cast<data_flow::AccessNode&>(edge.src());
×
333

334
    auto& users_analysis = analysis_manager_.get<analysis::Users>();
×
335
    analysis::UsersView body_view(users_analysis, body);
×
336
    if (!body_view.writes(src.data()).empty() || !body_view.moves(src.data()).empty()) {
×
337
        return false;
×
338
    }
×
339

340
    return true;
×
341
}
×
342

343
bool BlockHoisting::is_invariant_view(
344
    structured_control_flow::Sequence& body,
345
    data_flow::DataFlowGraph& dfg,
346
    symbolic::Symbol indvar,
347
    bool no_loop_carried_dependencies
348
) {
41✔
349
    if (dfg.nodes().size() != 2) {
41✔
350
        return false;
30✔
351
    }
30✔
352
    if (dfg.edges().size() != 1) {
11✔
353
        return false;
×
354
    }
×
355
    auto& edge = *dfg.edges().begin();
11✔
356
    if (edge.type() != data_flow::MemletType::Reference) {
11✔
357
        return false;
4✔
358
    }
4✔
359

360
    if (!indvar.is_null()) {
7✔
361
        auto& subset = edge.subset();
1✔
362
        for (const auto& dim : subset) {
1✔
363
            if (symbolic::uses(dim, indvar)) {
1✔
364
                return false;
×
365
            }
×
366
        }
1✔
367
    }
1✔
368

369
    if (no_loop_carried_dependencies) {
7✔
370
        return true;
7✔
371
    }
7✔
372
    auto& src = static_cast<data_flow::AccessNode&>(edge.src());
×
373

374
    auto& users_analysis = analysis_manager_.get<analysis::Users>();
×
375
    analysis::UsersView body_view(users_analysis, body);
×
376
    if (!body_view.writes(src.data()).empty() || !body_view.moves(src.data()).empty()) {
×
377
        return false;
×
378
    }
×
379

380
    return true;
×
381
}
×
382

383
bool BlockHoisting::is_invariant_libnode(
384
    structured_control_flow::Sequence& body,
385
    data_flow::DataFlowGraph& dfg,
386
    symbolic::Symbol indvar,
387
    bool no_loop_carried_dependencies
388
) {
67✔
389
    if (dfg.library_nodes().size() != 1) {
67✔
390
        return false;
38✔
391
    }
38✔
392
    if (dfg.tasklets().size() != 0) {
29✔
393
        return false;
×
394
    }
×
395
    auto* libnode = *dfg.library_nodes().begin();
29✔
396
    if (!libnode) {
29✔
397
        return false;
×
398
    }
×
399
    if (dfg.data_nodes().size() != libnode->outputs().size() + libnode->inputs().size()) {
29✔
400
        return false;
×
401
    }
×
402

403
    if (!this->is_libnode_allowed(body, dfg, libnode)) {
29✔
404
        return false;
×
405
    }
×
406

407
    if (!indvar.is_null()) {
29✔
408
        if (libnode->symbols().contains(indvar)) {
8✔
409
            return false;
×
410
        }
×
411
        for (auto& oedge : dfg.out_edges(*libnode)) {
8✔
412
            for (const auto& dim : oedge.subset()) {
1✔
413
                if (symbolic::uses(dim, indvar)) {
×
414
                    return false;
×
415
                }
×
416
            }
×
417
        }
1✔
418
        for (auto& iedge : dfg.in_edges(*libnode)) {
13✔
419
            for (const auto& dim : iedge.subset()) {
13✔
420
                if (symbolic::uses(dim, indvar)) {
×
421
                    return false;
×
422
                }
×
423
            }
×
424
        }
13✔
425
    }
8✔
426

427
    if (no_loop_carried_dependencies) {
29✔
428
        return true;
29✔
429
    }
29✔
430

431
    auto& users_analysis = analysis_manager_.get<analysis::Users>();
×
432
    analysis::UsersView body_view(users_analysis, body);
×
433
    for (auto& iedge : dfg.in_edges(*libnode)) {
×
434
        auto& src = static_cast<data_flow::AccessNode&>(iedge.src());
×
435
        if (!body_view.writes(src.data()).empty() || !body_view.moves(src.data()).empty()) {
×
436
            return false;
×
437
        }
×
438
    }
×
439
    for (auto& oedge : dfg.out_edges(*libnode)) {
×
440
        auto& dst = static_cast<data_flow::AccessNode&>(oedge.dst());
×
441
        if (!body_view.writes(dst.data()).empty() || !body_view.moves(dst.data()).empty()) {
×
442
            return false;
×
443
        }
×
444
    }
×
445

446
    return true;
×
447
}
×
448

449
bool BlockHoisting::equal_moves(structured_control_flow::Block& block1, structured_control_flow::Block& block2) {
1✔
450
    // Edges must have same type and subset
451
    auto& edge1 = *block1.dataflow().edges().begin();
1✔
452
    auto& edge2 = *block2.dataflow().edges().begin();
1✔
453
    if (edge1.type() != edge2.type()) {
1✔
454
        return false;
×
455
    }
×
456
    if (edge1.subset().size() != edge2.subset().size()) {
1✔
457
        return false;
×
458
    }
×
459
    for (size_t i = 0; i < edge1.subset().size(); i++) {
2✔
460
        if (!symbolic::eq(edge1.subset().at(i), edge2.subset().at(i))) {
1✔
461
            return false;
×
462
        }
×
463
    }
1✔
464

465
    // Directions must be the same
466
    if (edge1.src_conn() != edge2.src_conn()) {
1✔
467
        return false;
×
468
    }
×
469
    if (edge1.dst_conn() != edge2.dst_conn()) {
1✔
470
        return false;
×
471
    }
×
472

473
    // src's must have the same containers
474
    auto& src1 = static_cast<data_flow::AccessNode&>(edge1.src());
1✔
475
    auto& src2 = static_cast<data_flow::AccessNode&>(edge2.src());
1✔
476
    if (!data_flow::AccessNode::identicalBackingData(src1, src2)) {
1✔
477
        return false;
×
478
    }
×
479

480
    // dst's must have the same containers
481
    auto& dst1 = static_cast<data_flow::AccessNode&>(edge1.dst());
1✔
482
    auto& dst2 = static_cast<data_flow::AccessNode&>(edge2.dst());
1✔
483
    if (!data_flow::AccessNode::identicalBackingData(dst1, dst2)) {
1✔
484
        return false;
×
485
    }
×
486

487
    return true;
1✔
488
}
1✔
489

490
bool BlockHoisting::equal_views(structured_control_flow::Block& block1, structured_control_flow::Block& block2) {
3✔
491
    // Edges must have same type and subset
492
    auto& edge1 = *block1.dataflow().edges().begin();
3✔
493
    auto& edge2 = *block2.dataflow().edges().begin();
3✔
494
    if (edge1.type() != edge2.type()) {
3✔
495
        return false;
×
496
    }
×
497
    if (edge1.subset().size() != edge2.subset().size()) {
3✔
498
        return false;
×
499
    }
×
500
    for (size_t i = 0; i < edge1.subset().size(); i++) {
6✔
501
        if (!symbolic::eq(edge1.subset().at(i), edge2.subset().at(i))) {
3✔
502
            return false;
×
503
        }
×
504
    }
3✔
505

506
    // src's must have the same containers
507
    auto& src1 = static_cast<data_flow::AccessNode&>(edge1.src());
3✔
508
    auto& src2 = static_cast<data_flow::AccessNode&>(edge2.src());
3✔
509
    if (!data_flow::AccessNode::identicalBackingData(src1, src2)) {
3✔
510
        return false;
×
511
    }
×
512

513
    // dst's must have the same containers
514
    auto& dst1 = static_cast<data_flow::AccessNode&>(edge1.dst());
3✔
515
    auto& dst2 = static_cast<data_flow::AccessNode&>(edge2.dst());
3✔
516
    if (!data_flow::AccessNode::identicalBackingData(dst1, dst2)) {
3✔
517
        return false;
×
518
    }
×
519

520
    return true;
3✔
521
}
3✔
522

523
bool BlockHoisting::equal_libnodes(data_flow::LibraryNode* libnode1, data_flow::LibraryNode* libnode2) {
6✔
524
    if (auto* alloca_node1 = dynamic_cast<stdlib::AllocaNode*>(libnode1)) {
6✔
525
        if (auto* alloca_node2 = dynamic_cast<stdlib::AllocaNode*>(libnode2)) {
1✔
526
            return symbolic::eq(alloca_node1->size(), alloca_node2->size());
1✔
527
        }
1✔
528
    }
1✔
529
    if (auto* memcpy_node1 = dynamic_cast<stdlib::MemcpyNode*>(libnode1)) {
5✔
530
        if (auto* memcpy_node2 = dynamic_cast<stdlib::MemcpyNode*>(libnode2)) {
2✔
531
            return symbolic::eq(memcpy_node1->count(), memcpy_node2->count());
2✔
532
        }
2✔
533
    }
2✔
534
    if (auto* memmove_node1 = dynamic_cast<stdlib::MemmoveNode*>(libnode1)) {
3✔
535
        if (auto* memmove_node2 = dynamic_cast<stdlib::MemmoveNode*>(libnode2)) {
2✔
536
            return symbolic::eq(memmove_node1->count(), memmove_node2->count());
2✔
537
        }
2✔
538
    }
2✔
539
    if (auto* memset_node1 = dynamic_cast<stdlib::MemsetNode*>(libnode1)) {
1✔
540
        if (auto* memset_node2 = dynamic_cast<stdlib::MemsetNode*>(libnode2)) {
1✔
541
            return symbolic::eq(memset_node1->value(), memset_node2->value()) &&
1✔
542
                   symbolic::eq(memset_node1->num(), memset_node2->num());
1✔
543
        }
1✔
544
    }
1✔
545

546
    return false;
×
547
}
1✔
548

549
bool BlockHoisting::map_invariant_front(structured_control_flow::Sequence& parent, structured_control_flow::Map& map_stmt) {
23✔
550
    // Extract first child
551
    auto& body = map_stmt.root();
23✔
552
    if (body.size() == 0) {
23✔
553
        return false;
×
554
    }
×
555
    auto first_child = body.at(0);
23✔
556
    if (!first_child.second.assignments().empty()) {
23✔
557
        return false;
×
558
    }
×
559
    auto& first_node = first_child.first;
23✔
560

561
    auto* block = dynamic_cast<structured_control_flow::Block*>(&first_node);
23✔
562
    if (!block) {
23✔
563
        return false;
×
564
    }
×
565
    if (this->map_invariant_move(parent, map_stmt, *block)) {
23✔
566
        return true;
1✔
567
    }
1✔
568
    if (this->map_invariant_view(parent, map_stmt, *block)) {
22✔
569
        return true;
1✔
570
    }
1✔
571
    if (this->map_invariant_libnode_front(parent, map_stmt, *block)) {
21✔
572
        return true;
5✔
573
    }
5✔
574

575
    return false;
16✔
576
}
21✔
577

578
bool BlockHoisting::map_invariant_back(structured_control_flow::Sequence& parent, structured_control_flow::Map& map_stmt) {
23✔
579
    // Extract last child
580
    auto& body = map_stmt.root();
23✔
581
    if (body.size() == 0) {
23✔
582
        return false;
×
583
    }
×
584
    auto last_child = body.at(body.size() - 1);
23✔
585
    if (!last_child.second.assignments().empty()) {
23✔
586
        return false;
×
587
    }
×
588
    auto& last_node = last_child.first;
23✔
589

590
    auto* block = dynamic_cast<structured_control_flow::Block*>(&last_node);
23✔
591
    if (!block) {
23✔
592
        return false;
×
593
    }
×
594
    return this->map_invariant_libnode_back(parent, map_stmt, *block);
23✔
595
}
23✔
596

597
bool BlockHoisting::map_invariant_move(
598
    structured_control_flow::Sequence& parent,
599
    structured_control_flow::Map& map_stmt,
600
    structured_control_flow::Block& block
601
) {
23✔
602
    auto& body = map_stmt.root();
23✔
603
    if (!this->is_invariant_move(body, block.dataflow())) {
23✔
604
        return false;
22✔
605
    }
22✔
606

607
    size_t map_index = parent.index(map_stmt);
1✔
608
    builder_.move_child(body, 0, parent, map_index);
1✔
609
    return true;
1✔
610
}
23✔
611

612
bool BlockHoisting::map_invariant_view(
613
    structured_control_flow::Sequence& parent,
614
    structured_control_flow::Map& map_stmt,
615
    structured_control_flow::Block& block
616
) {
22✔
617
    auto& body = map_stmt.root();
22✔
618
    if (!this->is_invariant_view(body, block.dataflow(), map_stmt.indvar())) {
22✔
619
        return false;
21✔
620
    }
21✔
621

622
    size_t map_index = parent.index(map_stmt);
1✔
623
    builder_.move_child(body, 0, parent, map_index);
1✔
624
    return true;
1✔
625
}
22✔
626

627
bool BlockHoisting::map_invariant_libnode_front(
628
    structured_control_flow::Sequence& parent,
629
    structured_control_flow::Map& map_stmt,
630
    structured_control_flow::Block& block
631
) {
21✔
632
    // For now, only allow libnode hoisting on sequential maps
633
    if (map_stmt.schedule_type().value() != ScheduleType_Sequential::value()) {
21✔
634
        return false;
9✔
635
    }
9✔
636

637
    auto& body = map_stmt.root();
12✔
638
    if (!this->is_invariant_libnode(body, block.dataflow(), map_stmt.indvar())) {
12✔
639
        return false;
7✔
640
    }
7✔
641

642
    size_t map_index = parent.index(map_stmt);
5✔
643
    builder_.move_child(body, 0, parent, map_index);
5✔
644
    return true;
5✔
645
}
12✔
646

647
bool BlockHoisting::map_invariant_libnode_back(
648
    structured_control_flow::Sequence& parent,
649
    structured_control_flow::Map& map_stmt,
650
    structured_control_flow::Block& block
651
) {
23✔
652
    // For now, only allow libnode hoisting on sequential maps
653
    if (map_stmt.schedule_type().value() != ScheduleType_Sequential::value()) {
23✔
654
        return false;
9✔
655
    }
9✔
656

657
    auto& body = map_stmt.root();
14✔
658
    if (!this->is_invariant_libnode(body, block.dataflow(), map_stmt.indvar())) {
14✔
659
        return false;
11✔
660
    }
11✔
661

662
    size_t map_index = parent.index(map_stmt);
3✔
663
    builder_.move_child(body, body.size() - 1, parent, map_index + 1);
3✔
664
    return true;
3✔
665
}
14✔
666

667
bool BlockHoisting::
668
    if_else_invariant_front(structured_control_flow::Sequence& parent, structured_control_flow::IfElse& if_else) {
18✔
669
    // Extract the first block of first case
670
    if (if_else.at(0).first.size() == 0) {
18✔
671
        return false;
×
672
    }
×
673
    auto first_child = if_else.at(0).first.at(0);
18✔
674
    if (!first_child.second.assignments().empty()) {
18✔
675
        return false;
×
676
    }
×
677
    auto* first_block = dynamic_cast<structured_control_flow::Block*>(&first_child.first);
18✔
678
    if (!first_block) {
18✔
679
        return false;
1✔
680
    }
1✔
681

682
    // Collect the first block of all other cases
683
    std::vector<structured_control_flow::Block*> other_blocks;
17✔
684
    for (size_t i = 1; i < if_else.size(); i++) {
36✔
685
        if (if_else.at(i).first.size() == 0) {
19✔
686
            return false;
×
687
        }
×
688
        auto other_child = if_else.at(i).first.at(0);
19✔
689
        if (!other_child.second.assignments().empty()) {
19✔
690
            return false;
×
691
        }
×
692
        auto* other_block = dynamic_cast<structured_control_flow::Block*>(&other_child.first);
19✔
693
        if (!other_block) {
19✔
694
            return false;
×
695
        }
×
696
        other_blocks.push_back(other_block);
19✔
697
    }
19✔
698

699
    // Check the first block of all cases for invariant move / view / libnode
700
    if (this->is_invariant_move(if_else.at(0).first, first_block->dataflow())) {
17✔
701
        for (size_t i = 0; i < other_blocks.size(); i++) {
2✔
702
            if (!this->is_invariant_move(if_else.at(i + 1).first, other_blocks[i]->dataflow())) {
1✔
703
                return false;
×
704
            }
×
705
            if (!this->equal_moves(*first_block, *other_blocks[i])) {
1✔
706
                return false;
×
707
            }
×
708
        }
1✔
709
        this->if_else_extract_invariant_front(parent, if_else);
1✔
710
        return true;
1✔
711
    } else if (this->is_invariant_view(if_else.at(0).first, first_block->dataflow())) {
16✔
712
        for (size_t i = 0; i < other_blocks.size(); i++) {
6✔
713
            if (!this->is_invariant_view(if_else.at(i + 1).first, other_blocks[i]->dataflow())) {
3✔
714
                return false;
×
715
            }
×
716
            if (!this->equal_views(*first_block, *other_blocks[i])) {
3✔
717
                return false;
×
718
            }
×
719
        }
3✔
720
        this->if_else_extract_invariant_front(parent, if_else);
3✔
721
        return true;
3✔
722
    } else if (this->is_invariant_libnode(if_else.at(0).first, first_block->dataflow())) {
13✔
723
        for (size_t i = 0; i < other_blocks.size(); i++) {
14✔
724
            if (!this->is_invariant_libnode(if_else.at(i + 1).first, other_blocks[i]->dataflow())) {
8✔
725
                return false;
×
726
            }
×
727
            if (!this->equal_libnodes(*first_block, *other_blocks[i])) {
8✔
728
                return false;
×
729
            }
×
730
        }
8✔
731
        this->if_else_extract_invariant_libnode_front(parent, if_else);
6✔
732
        return true;
6✔
733
    }
6✔
734

735
    return false;
7✔
736
}
17✔
737

738
bool BlockHoisting::
739
    if_else_invariant_back(structured_control_flow::Sequence& parent, structured_control_flow::IfElse& if_else) {
18✔
740
    // Extract the last block of first case
741
    if (if_else.at(0).first.size() == 0) {
18✔
742
        return false;
×
743
    }
×
744
    auto last_child = if_else.at(0).first.at(if_else.at(0).first.size() - 1);
18✔
745
    if (!last_child.second.assignments().empty()) {
18✔
746
        return false;
×
747
    }
×
748
    auto* last_block = dynamic_cast<structured_control_flow::Block*>(&last_child.first);
18✔
749
    if (!last_block) {
18✔
750
        return false;
2✔
751
    }
2✔
752

753
    // Collect the last block of all other cases
754
    std::vector<structured_control_flow::Block*> other_blocks;
16✔
755
    for (size_t i = 1; i < if_else.size(); i++) {
33✔
756
        if (if_else.at(i).first.size() == 0) {
17✔
757
            return false;
×
758
        }
×
759
        auto other_child = if_else.at(i).first.at(if_else.at(i).first.size() - 1);
17✔
760
        if (!other_child.second.assignments().empty()) {
17✔
761
            return false;
×
762
        }
×
763
        auto* other_block = dynamic_cast<structured_control_flow::Block*>(&other_child.first);
17✔
764
        if (!other_block) {
17✔
765
            return false;
×
766
        }
×
767
        other_blocks.push_back(other_block);
17✔
768
    }
17✔
769

770
    // Check the first block of all cases for invariant move / view / libnode
771
    if (this->is_invariant_libnode(if_else.at(0).first, last_block->dataflow())) {
16✔
772
        for (size_t i = 0; i < other_blocks.size(); i++) {
7✔
773
            if (!this->is_invariant_libnode(if_else.at(i + 1).first, other_blocks[i]->dataflow())) {
4✔
774
                return false;
×
775
            }
×
776
            if (!this->equal_libnodes(*last_block, *other_blocks[i])) {
4✔
777
                return false;
×
778
            }
×
779
        }
4✔
780
        this->if_else_extract_invariant_libnode_back(parent, if_else);
3✔
781
        return true;
3✔
782
    }
3✔
783

784
    return false;
13✔
785
}
16✔
786

787
void BlockHoisting::if_else_extract_invariant_front(
788
    structured_control_flow::Sequence& parent, structured_control_flow::IfElse& if_else
789
) {
10✔
790
    size_t if_else_index = parent.index(if_else);
10✔
791
    builder_.move_child(if_else.at(0).first, 0, parent, if_else_index);
10✔
792

793
    for (size_t i = 1; i < if_else.size(); i++) {
22✔
794
        builder_.remove_child(if_else.at(i).first, 0);
12✔
795
    }
12✔
796
}
10✔
797

798
void BlockHoisting::if_else_extract_invariant_back(
799
    structured_control_flow::Sequence& parent, structured_control_flow::IfElse& if_else
800
) {
3✔
801
    size_t if_else_index = parent.index(if_else);
3✔
802
    builder_.move_child(if_else.at(0).first, if_else.at(0).first.size() - 1, parent, if_else_index + 1);
3✔
803

804
    for (size_t i = 1; i < if_else.size(); i++) {
7✔
805
        builder_.remove_child(if_else.at(i).first, if_else.at(i).first.size() - 1);
4✔
806
    }
4✔
807
}
3✔
808

809
bool BlockHoisting::equal_offloading_nodes(
810
    structured_control_flow::Block& block1,
811
    offloading::DataOffloadingNode* offloading_node1,
812
    structured_control_flow::Block& block2,
813
    offloading::DataOffloadingNode* offloading_node2
814
) {
6✔
815
    if (!offloading_node1->equal_with(*offloading_node2)) {
6✔
816
        return false;
×
817
    }
×
818

819
    // Check in/out degree
820
    auto& dfg1 = block1.dataflow();
6✔
821
    auto& dfg2 = block2.dataflow();
6✔
822
    if (dfg1.in_degree(*offloading_node1) != dfg2.in_degree(*offloading_node2)) {
6✔
823
        return false;
×
824
    }
×
825
    if (dfg1.out_degree(*offloading_node1) != dfg2.out_degree(*offloading_node2)) {
6✔
826
        return false;
×
827
    }
×
828

829
    // In edges:
830
    for (int i = 0; i < offloading_node1->inputs().size(); i++) {
14✔
831
        auto* iedge1 = dfg1.in_edge_for_connector(*offloading_node1, offloading_node1->input(i));
8✔
832
        auto* iedge2 = dfg2.in_edge_for_connector(*offloading_node2, offloading_node2->input(i));
8✔
833
        if (!iedge1 || !iedge2) {
8✔
834
            return false;
×
835
        }
×
836

837
        // Compare types
838
        if (iedge1->type() != iedge2->type()) {
8✔
839
            return false;
×
840
        }
×
841

842
        // Compare subsets
843
        if (iedge1->subset().size() != iedge2->subset().size()) {
8✔
844
            return false;
×
845
        }
×
846
        for (size_t i = 0; i < iedge1->subset().size(); i++) {
8✔
847
            if (!symbolic::eq(iedge1->subset().at(i), iedge2->subset().at(i))) {
×
848
                return false;
×
849
            }
×
850
        }
×
851

852
        if (i == offloading_node1->host_ptr_input_idx()) { // only host side containers can be relied upon to match
8✔
853
            auto& src1 = static_cast<const data_flow::AccessNode&>(iedge1->src());
6✔
854
            auto& src2 = static_cast<const data_flow::AccessNode&>(iedge2->src());
6✔
855
            if (!data_flow::AccessNode::identicalBackingData(src1, src2)) {
6✔
856
                return false;
×
857
            }
×
858
        }
6✔
859
    }
8✔
860

861
    // Out edges:
862
    for (int i = 0; i < offloading_node1->outputs().size(); i++) {
10✔
863
        auto oedges1 = dfg1.out_edges_for_connector(*offloading_node1, offloading_node1->output(0));
4✔
864
        auto oedges2 = dfg2.out_edges_for_connector(*offloading_node2, offloading_node2->output(0));
4✔
865
        if (oedges1.size() != oedges2.size()) {
4✔
866
            return false;
×
867
        }
×
868
        for (int j = 0; j < oedges1.size(); j++) {
8✔
869
            auto oedge1 = oedges1.at(j);
4✔
870
            auto oedge2 = oedges2.at(j);
4✔
871

872
            if (oedge1 && oedge2) {
4✔
873
                // Compare types
874
                if (oedge1->type() != oedge2->type()) {
4✔
NEW
875
                    return false;
×
NEW
876
                }
×
877

878
                // Compare subsets
879
                if (oedge1->subset().size() != oedge2->subset().size()) {
4✔
NEW
880
                    return false;
×
NEW
881
                }
×
882
                for (size_t k = 0; k < oedge1->subset().size(); k++) {
4✔
NEW
883
                    if (!symbolic::eq(oedge1->subset().at(k), oedge2->subset().at(k))) {
×
NEW
884
                        return false;
×
NEW
885
                    }
×
NEW
886
                }
×
887

888
                // out edges can only be device side in new model, we only can rely on host containers being the same,
889
                // so do not check th
890
            } else if (!oedge1 ^ !oedge2) {
4✔
NEW
891
                return false;
×
NEW
892
            }
×
893
        }
4✔
894
    }
4✔
895

896
    return true;
6✔
897
}
6✔
898

899
} // namespace passes
900
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc