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

daisytuner / docc / 26520678771

27 May 2026 03:22PM UTC coverage: 60.864% (-0.02%) from 60.886%
26520678771

Pull #719

github

web-flow
Merge 99c5e4f9d into 707dadcf8
Pull Request #719: Libnode ptr edges

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

90 existing lines in 29 files now uncovered.

35222 of 57870 relevant lines covered (60.86%)

11043.61 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 · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc