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

daisytuner / docc / 30647115023

31 Jul 2026 04:26PM UTC coverage: 64.824% (+0.02%) from 64.806%
30647115023

Pull #918

github

web-flow
Merge b2331620d into 60edac6b5
Pull Request #918: Loop fusion migration in normalize

46 of 49 new or added lines in 2 files covered. (93.88%)

45628 of 70387 relevant lines covered (64.82%)

724.48 hits per line

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

80.13
/opt/src/passes/loop_fusion/loop_fusion_pass.cpp
1
#include "sdfg/passes/loop_fusion/loop_fusion_pass.h"
2

3
#include "../../../../sdfg/include/sdfg/symbolic/assumptions.h"
4
#include "sdfg/analysis/assumptions_analysis.h"
5
#include "sdfg/analysis/base_user_visitor.h"
6
#include "sdfg/data_flow/library_nodes/stdlib/malloc.h"
7
#include "sdfg/deepcopy/structured_sdfg_deep_copy.h"
8
#include "sdfg/structured_sdfg.h"
9
#include "sdfg/symbolic/utils.h"
10
#include "sdfg/visitor/structured_sdfg_visitor.h"
11
#include "sdfg/visualizer/dot_visualizer.h"
12
#include "symengine/subs.h"
13

14
namespace sdfg::passes::loop_fusion {
15

16
static const symbolic::Symbol lower_indvar_placeholder = symbolic::symbol("__lower_it");
17

18
static inline constexpr bool DUMP_ASSUMPTIONS = false;
19
static inline constexpr bool DUMP_LOOP_INFOS = false;
20
static inline constexpr bool DUMP_GRAPHS = false;
21

22
static bool vectors_of_expressions_match(
23
    const std::vector<symbolic::Expression>& a,
24
    const std::vector<symbolic::Expression>& b,
25
    const symbolic::ExpressionMapping* replacements
26
) {
×
27
    if (replacements) {
×
28
        return symbolic::vectors_of_expressions_match(a, b, *replacements);
×
29
    } else {
×
30
        return symbolic::vectors_of_expressions_match(a, b);
×
31
    }
×
32
}
×
33

34
class LoopIndirectAccessFinder : public analysis::BaseUserVisitor {
35
    const StructuredSDFG& sdfg_;
36
    analysis::LoopAnalysis& loop_analysis_;
37
    std::unordered_map<analysis::ElementId, std::unique_ptr<FusionLoopCandidate>>& fuse_candidates_;
38
    struct LoopEntry {
39
        ControlFlowNode* loop;
40
        analysis::LocalLoopInfo::LoopType type;
41
        FusionLoopCandidate& fusion_candidate;
42
        symbolic::Expression indvar_placeholder; // SymEngine::Function(tight_lower_bound, tight_upper_bound, step,
43
                                                 // loop-level)
44
        std::unordered_set<std::string> indvars;
45
    };
46
    std::deque<LoopEntry> loop_stack_;
47

48
    LoopEntry* get_current_loop() {
276✔
49
        if (loop_stack_.empty()) {
276✔
50
            return nullptr;
7✔
51
        }
7✔
52
        return &loop_stack_.back();
269✔
53
    }
276✔
54

55
    static bool merge_fusion_arg_props_into(
56
        FusionArg& into,
57
        const std::optional<data_flow::Subset>& subset,
58
        bool not_understood,
59
        bool local_access,
60
        const symbolic::ExpressionMapping* lower_indvars = nullptr
61
    ) {
×
62
        bool updated = false;
×
63
        auto& target_access = local_access ? into.local_access : into.nested_access;
×
64

×
65
        if (target_access.common_subset.has_value() && subset.has_value() &&
×
66
            !vectors_of_expressions_match(target_access.common_subset.value(), subset.value(), lower_indvars)) {
×
67
            if (!target_access.subsets_conflict) {
×
68
                target_access.subsets_conflict = true;
×
69
                updated = true;
×
70
            }
×
71
        } else if (!target_access.common_subset.has_value() && subset.has_value() && !target_access.subsets_conflict) {
×
72
            target_access.common_subset = subset.value();
×
73
            updated = true;
×
74
        }
×
75
        if (not_understood && !target_access.subsets_conflict) {
×
76
            target_access.subsets_conflict = true;
×
77
            updated = true;
×
78
        }
×
79
        return updated;
×
80
    }
×
81

82
public:
83
    LoopIndirectAccessFinder(
84
        const StructuredSDFG& sdfg,
85
        analysis::LoopAnalysis& loops,
86
        std::unordered_map<analysis::ElementId, std::unique_ptr<FusionLoopCandidate>>& fuse_candidates
87
    )
88
        : sdfg_(sdfg), loop_analysis_(loops), fuse_candidates_(fuse_candidates) {}
56✔
89

90
    bool visit(sdfg::structured_control_flow::While& node) override {
×
91
        // far from being supported as fuse candidates, so do the normal stuff
92
        auto res = ActualStructuredSDFGVisitor::visit(node);
×
93
        return res;
×
94
    }
×
95

96
    static symbolic::Expression get_indvar_placeholder(FusionLoopCandidate& candidate, size_t level) {
184✔
97
        auto* indvar_bounds = candidate.indvar_boundaries;
184✔
98
        symbolic::Expression stride = symbolic::integer(1);
184✔
99
        if (symbolic::null_safe_eq(symbolic::sub(indvar_bounds->map(), indvar_bounds->symbol()), stride)) {
184✔
100
            return SymEngine::function_symbol(
184✔
101
                "indvar",
184✔
102
                {indvar_bounds->tight_lower_bound(), indvar_bounds->tight_upper_bound(), stride, symbolic::integer(level)
184✔
103
                }
184✔
104
            );
184✔
105
        } else {
184✔
106
            return {};
×
107
        }
×
108
    }
184✔
109

110
    bool handleStructuredLoop(sdfg::structured_control_flow::StructuredLoop& node) override {
184✔
111
        auto cand_it = fuse_candidates_.find(node.element_id());
184✔
112
        bool is_relevant_loop = cand_it != fuse_candidates_.end();
184✔
113
        if (is_relevant_loop) {
184✔
114
            auto type = is_a(node.type_id(), ElementType::Map) ? analysis::LocalLoopInfo::LoopType::Map
184✔
115
                                                               : analysis::LocalLoopInfo::LoopType::For;
184✔
116
            auto& candidate = *cand_it->second.get();
184✔
117
            loop_stack_.emplace_back(&node, type, candidate, get_indvar_placeholder(candidate, loop_stack_.size() - 1));
184✔
118
            loop_stack_.back().indvars.emplace(node.indvar()->get_name());
184✔
119
        }
184✔
120
        auto res = BaseUserVisitor::handleStructuredLoop(node);
184✔
121
        if (is_relevant_loop) {
184✔
122
            auto size = loop_stack_.size();
184✔
123
            if (size > 1) {
184✔
124
                auto& parent = loop_stack_.at(size - 2);
68✔
125
                propagate_indirect_accesses_up(loop_stack_.back(), parent);
68✔
126
            }
68✔
127
            loop_stack_.pop_back();
184✔
128
        }
184✔
129
        return res;
184✔
130
    }
184✔
131

132
    void use_as_symbol_read(
133
        const std::string& container,
134
        const ControlFlowNode* node,
135
        const Element* user,
136
        SymbolReadLocation loc,
137
        int loc_index,
138
        symbolic::Expression expr
139
    ) override {}
1,006✔
140

141
    static void found_indirect_arg_access(
142
        const std::string& container,
143
        const data_flow::Memlet& edge,
144
        const Block& block,
145
        LoopEntry* current,
146
        bool is_write
147
    ) {
265✔
148
        auto& cand = current->fusion_candidate;
265✔
149
        auto arg_it = cand.args.find(container);
265✔
150
        if (arg_it != cand.args.end()) {
265✔
151
            auto& fusion_arg = arg_it->second;
257✔
152
            fusion_arg.local_access.merge_into(const_cast<Block*>(&block), edge.subset(), false, is_write);
257✔
153
            std::optional<data_flow::Subset> generalized_subset_holder;
257✔
154
            const data_flow::Subset* generalized_subset = &edge.subset();
257✔
155
            if (!current->indvar_placeholder.is_null()) {
257✔
156
                generalized_subset_holder = symbolic::
257✔
157
                    substitute(*generalized_subset, {{cand.indvar_boundaries->symbol(), current->indvar_placeholder}});
257✔
158
                generalized_subset = &generalized_subset_holder.value();
257✔
159
            }
257✔
160

161
            fusion_arg.nested_access.merge_into(const_cast<Block*>(&block), *generalized_subset, false, is_write);
257✔
162
        }
257✔
163
    }
265✔
164

165
    static void propagate_indirect_accesses_up(LoopEntry& current, LoopEntry& parent) {
68✔
166
        auto& parent_cand = parent.fusion_candidate;
68✔
167
        std::optional<symbolic::ExpressionMapping> indvar_mapping;
68✔
168
        if (current.fusion_candidate.is_by_domain_candidate) {
68✔
169
            auto& indvar_bounds = current.fusion_candidate.indvar_boundaries;
68✔
170
            symbolic::Expression stride = symbolic::integer(1);
68✔
171
            if (symbolic::null_safe_eq(symbolic::sub(indvar_bounds->map(), indvar_bounds->symbol()), stride)) {
68✔
172
                indvar_mapping = symbolic::ExpressionMapping();
68✔
173
                indvar_mapping->emplace(
68✔
174
                    SymEngine::rcp_static_cast<const SymEngine::Basic>(indvar_bounds->symbol()),
68✔
175
                    current.indvar_placeholder
68✔
176
                );
68✔
177
            }
68✔
178
        }
68✔
179
        for (auto& [container, meta] : current.fusion_candidate.args) {
322✔
180
            auto arg_it = parent_cand.args.find(container);
322✔
181
            if (arg_it != parent_cand.args.end()) {
322✔
182
                auto& parent_arg = arg_it->second;
258✔
183
                parent_arg.nested_access
258✔
184
                    .merge_into(meta.nested_access, indvar_mapping.has_value() ? &*indvar_mapping : nullptr);
258✔
185
                parent_arg.nested_access
258✔
186
                    .merge_into(meta.local_access, indvar_mapping.has_value() ? &*indvar_mapping : nullptr);
258✔
187
            }
258✔
188
        }
322✔
189

190
        parent.fusion_candidate.nested_incompatible |= current.fusion_candidate.incompatible |
68✔
191
                                                       current.fusion_candidate.nested_incompatible;
68✔
192
    }
68✔
193

194

195
    void use_as_dst_node(
196
        const std::string& container,
197
        const data_flow::AccessNode& node,
198
        const data_flow::Memlet& edge,
199
        const Block& block
200
    ) override {
133✔
201
        auto current = get_current_loop();
133✔
202
        if (current && edge.is_dst_pointed_to_write()) {
133✔
203
            found_indirect_arg_access(container, edge, block, current, true);
125✔
204
        }
125✔
205
    }
133✔
206
    void use_as_return_src(const std::string& container, const Return& ret) override {}
×
207
    /**
208
     * Dangerous, if somebody builds a value derived from indvar and then uses that for addressing we would not notice.
209
     * But normally those should be folded into the accesses
210
     */
211
    void use_as_src_node(
212
        const std::string& container,
213
        const data_flow::AccessNode& node,
214
        const data_flow::Memlet& edge,
215
        const Block& block
216
    ) override {
143✔
217
        auto current = get_current_loop();
143✔
218
        if (current && (edge.is_src_address_leak() || edge.is_src_pointed_to_address_leak(sdfg_.type(container)))) {
143✔
219
            current->fusion_candidate.aliasing_encountered();
×
220
        } else if (current && edge.is_src_pointed_to_read()) {
143✔
221
            found_indirect_arg_access(container, edge, block, current, false);
140✔
222
        }
140✔
223
    }
143✔
224
    void use_as_symbol_write(
225
        const symbolic::Symbol& container, const ControlFlowNode* node, const Element* user, SymbolWriteLocation loc
226
    ) override {}
184✔
227
};
228

229
FusionLoopCandidate* LoopFusionPass::State::get_next_level_map_stack(FusionLoopCandidate& current) {
62✔
230
    auto& children = loop_analysis->children(current.loop);
62✔
231
    if (children.empty()) {
62✔
232
        return nullptr;
×
233
    }
×
234

235
    auto* next = children.at(0);
62✔
236
    return fuse_candidates.at(next->element_id()).get();
62✔
237
}
62✔
238

239
FusionLoopCandidate* LoopFusionPass::State::get_parent(FusionLoopCandidate& current) {
120✔
240
    auto* parent = loop_analysis->parent_loop(current.loop);
120✔
241
    if (!parent) {
120✔
242
        return nullptr;
79✔
243
    }
79✔
244
    auto it = fuse_candidates.find(parent->element_id());
41✔
245
    if (it != fuse_candidates.end()) {
41✔
246
        return it->second.get();
41✔
247
    } else {
41✔
248
        return nullptr;
×
249
    }
×
250
}
41✔
251

252
uint32_t LoopFusionPass::State::total_fused_count() const { return fused_by_domain_count + fused_by_access_count; }
56✔
253

254
std::ostream& operator<<(std::ostream& os, const symbolic::Expression& expr) {
×
255
    if (!expr.is_null()) {
×
256
        os << expr->__str__();
×
257
    } else {
×
258
        os << "null";
×
259
    }
×
260
    return os;
×
261
}
×
262

263
std::ostream& operator<<(std::ostream& os, const symbolic::Symbol& sym) {
×
264
    if (sym.is_null()) {
×
265
        os << "null";
×
266
    } else {
×
267
        os << sym->get_name();
×
268
    }
×
269
    return os;
×
270
}
×
271

272
std::ostream& operator<<(std::ostream& os, const symbolic::Assumption& assump) {
×
273
    os << "\t" << "const: " << (assump.constant() ? "true" : "false") << std::endl;
×
274
    os << "\t" << "map: " << assump.map() << std::endl;
×
275
    os << "\t" << "lower_bounds: " << assump.lower_bounds() << std::endl;
×
276
    os << "\t" << "upper_bounds: " << assump.upper_bounds() << std::endl;
×
277
    os << "\ttight_lower: " << assump.tight_lower_bound() << std::endl;
×
278
    os << "\ttight_upper: " << assump.tight_upper_bound() << std::endl;
×
279
    os << "\t" << "constraints: " << assump.constraints() << std::endl;
×
280
    return os;
×
281
}
×
282

283
std::ostream& operator<<(std::ostream& os, const symbolic::Assumptions& ass) {
×
284
    for (auto& [sym, as] : ass) {
×
285
        os << "\t" << sym << ":" << std::endl << as << std::endl;
×
286
    }
×
287
    return os;
×
288
}
×
289

290
LoopFusionPass::LoopFusionPass(const LoopFusionConfig& config) : config_(config) {}
4✔
291

292
LoopFusionPass::LoopFusionPass() = default;
52✔
293

294
bool LoopFusionPass::run_pass(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
56✔
295
    auto loop_ana = std::make_unique<analysis::LoopAnalysis>(builder.subject());
56✔
296
    loop_ana->run(analysis_manager);
56✔
297

298
    static uint32_t run = 0;
56✔
299
    DEBUG_PRINTLN("LoopFusion pass #" << run);
56✔
300

301
    State state(builder, analysis_manager, std::move(loop_ana));
56✔
302
    state.run = run;
56✔
303
    run++;
56✔
304

305
    auto& assumption_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
56✔
306
    auto& arguments_analysis = analysis_manager.get<analysis::ArgumentsAnalysis>();
56✔
307

308
    for (auto* control_flow_node : state.loop_analysis->loops()) {
184✔
309
        if (auto* loop = dyn_cast<StructuredLoop*>(control_flow_node)) {
184✔
310
            auto& indvar = loop->indvar();
184✔
311
            auto& assumpts = assumption_analysis.get(loop->root(), true);
184✔
312
            auto* indvar_boundaries = find_indvar_boundaries(indvar, assumpts);
184✔
313

314
            std::unique_ptr<FusionLoopCandidate> cand;
184✔
315

316
            bool tight = indvar_boundaries && !indvar_boundaries->tight_lower_bound().is_null() &&
184✔
317
                         !indvar_boundaries->tight_upper_bound().is_null() && !indvar_boundaries->map().is_null();
184✔
318
            bool is_map = is_a(loop->type_id(), ElementType::Map);
184✔
319
            cand = std::make_unique<FusionLoopCandidate>(loop, indvar_boundaries, assumpts, is_map, tight);
184✔
320
            auto& args = arguments_analysis.arguments(analysis_manager, *loop);
184✔
321
            for (auto [name, arg] : args) {
729✔
322
                cand->args.emplace(name, arg);
729✔
323
            }
729✔
324
            state.fuse_candidates[control_flow_node->element_id()] = std::move(cand);
184✔
325
        }
184✔
326
    }
184✔
327

328
    LoopIndirectAccessFinder indirect_access_finder(builder.subject(), *state.loop_analysis, state.fuse_candidates);
56✔
329
    indirect_access_finder.dispatch(builder.subject().root());
56✔
330

331
    const std::string* dir = nullptr;
56✔
332
    if (DUMP_LOOP_INFOS) {
56✔
333
        dir = builder.subject().metadata_if_exists("output_dir");
×
334
        if (dir) {
×
335
            state.loop_analysis->dump_to_file(std::filesystem::path(*dir) / "loop_infos.pre-fusion.json");
×
336
        }
×
337
    }
×
338

339
    LoopFusionHandler handler(config_, state);
56✔
340

341
    NeighboringPatternVisitor v(handler);
56✔
342
    v.dispatch(builder.subject().root());
56✔
343

344
    if (dir) {
56✔
345
        state.loop_analysis->dump_to_file(std::filesystem::path(*dir) / "loop_infos.post-fusion.json");
×
346
    }
×
347

348
    return state.total_fused_count();
56✔
349
}
56✔
350

351
const symbolic::Assumption* LoopFusionPass::
352
    find_indvar_boundaries(const symbolic::Symbol& indvar, const symbolic::Assumptions& assumptions) {
184✔
353
    auto it = assumptions.find(indvar);
184✔
354
    if (it != assumptions.end()) {
184✔
355
        return &it->second;
184✔
356
    }
184✔
357

358
    return nullptr;
×
359
}
184✔
360

361
LoopFusionHandler::LoopFusionHandler(const LoopFusionConfig& config, LoopFusionPass::State& state)
362
    : config_(config), state_(state), LoopFusionByAccessWorker(config.allow_init_hoist) {}
56✔
363

364
PatternHandler::MatchResult LoopFusionHandler::fuse_contents(
365
    ControlFlowNode* first_top,
366
    FusionLoopCandidate* first_current,
367
    FusionLoopCandidate* second_innermost,
368
    const symbolic::ExpressionMapping& indvar_mapping,
369
    Sequence& target_root,
370
    bool can_remove_original
371
) {
19✔
372
    auto first_elem_id = first_current->loop->element_id();
19✔
373

374
    Sequence* append_root = nullptr;
19✔
375
    if (target_root.size() == 0) {
19✔
376
        // target seq is empty, so we can just append to it
377
        append_root = &target_root;
×
378
    } else {
19✔
379
        // there currently is no way to prepend-copy with replace, so add to new sequence,
380
        // replace on it, then flatten it into the existing
381
        append_root = &state_.builder.add_sequence_before(target_root, target_root.at(0), {});
19✔
382
    }
19✔
383

384
    std::optional<std::unordered_map<const ControlFlowNode*, const ControlFlowNode*>> copy_mapping;
19✔
385
    if (can_remove_original) {
19✔
386
        state_.builder.move_children(first_current->loop->root(), *append_root);
19✔
387
    } else {
19✔
388
        deepcopy::StructuredSDFGDeepCopy copier(state_.builder, *append_root, first_current->loop->root());
×
389
        copy_mapping = copier.insert();
×
390
    }
×
391

392
    update_fused_seq(*append_root, indvar_mapping);
19✔
393

394
    if (append_root != &target_root) { // need to fixup / flatten the copied sequence into the target sequence
19✔
395
        state_.builder.move_children(*append_root, target_root, 0);
19✔
396
        state_.builder.remove_from_parent(*append_root);
19✔
397
        append_root = nullptr;
19✔
398
    }
19✔
399

400
    update_candidate_state(first_top, first_current, second_innermost, indvar_mapping);
19✔
401

402
    auto first_children = state_.loop_analysis->children(first_current->loop);
19✔
403
    bool keep_visiting_second = !state_.loop_analysis->children(second_innermost->loop).empty() ||
19✔
404
                                !first_children.empty();
19✔
405
    auto& prev_local_info = state_.loop_analysis->loop_info_local(first_current->loop);
19✔
406
    if (can_remove_original) {
19✔
407
        for (auto& child : first_children) {
19✔
408
            state_.loop_analysis->moved_loop(child, second_innermost->loop, true);
2✔
409
        }
2✔
410
        state_.loop_analysis->added_local_contents(
19✔
411
            second_innermost->loop, prev_local_info.contains_side_effects, prev_local_info.contains_non_perfectly_nested
19✔
412
        );
19✔
413
    } else {
19✔
414
        for (auto& child : first_children) {
×
415
            state_.loop_analysis->copied_loop(
×
416
                child,
×
417
                second_innermost->loop,
×
418
                const_cast<structured_control_flow::ControlFlowNode*>(copy_mapping->at(child)),
×
419
                true
×
420
            );
×
421
        }
×
422
        state_.loop_analysis->added_local_contents(
×
423
            second_innermost->loop, prev_local_info.contains_side_effects, prev_local_info.contains_non_perfectly_nested
×
424
        );
×
425
    }
×
426

427
    bool removed_first = false;
19✔
428
    if (can_remove_original) {
19✔
429
        state_.loop_analysis->removed_loop(first_top);
19✔
430
        state_.builder.remove_from_parent(*first_top);
19✔
431
        removed_first = true;
19✔
432
    }
19✔
433

434
    if constexpr (DUMP_GRAPHS) {
435
        auto dir = state_.builder.subject().metadata_if_exists("output_dir");
436
        if (dir) {
437
            std::filesystem::path pdir = *dir;
438
            visualizer::DotVisualizer::writeToFile(
439
                state_.builder.subject(),
440
                pdir / ("map_fusion_by_domain_pass_" + std::to_string(state_.run) + "_dump_" +
441
                        std::to_string(state_.fused_by_domain_count) + "_" +
442
                        std::to_string(second_innermost->loop->element_id()) + ".dot")
443
            );
444
        }
445
    }
446

447
    state_.fused_by_domain_count++;
19✔
448

449
    // if there are further loops inside the now fused body, visit those as well
450
    return {.removed_first = removed_first, .visit_second_body = keep_visiting_second};
19✔
451
}
19✔
452

453
analysis::LoopAnalysis& LoopFusionHandler::get_loop_analysis() { return *state_.loop_analysis; }
119✔
454

455
FusionLoopCandidate* LoopFusionHandler::get_fuse_candidate(StructuredLoop& loop) {
102✔
456
    return state_.fuse_candidates.at(loop.element_id()).get();
102✔
457
}
102✔
458

459
builder::StructuredSDFGBuilder& LoopFusionHandler::builder() { return state_.builder; }
22✔
460

461
void LoopFusionHandler::update_copied_leaf_contents_from_first_to_second(
462
    const Plan& plan, FusionLoopCandidate* first_current, FusionLoopCandidate* second_current
463
) {
22✔
464
    auto first_top = &plan.first;
22✔
465

466

467
    auto& fusion_regs = plan.fusion_candidates_;
22✔
468

469
    std::unordered_map<std::string, const loop_fusion::FusionRegCandidate*> cand_map;
22✔
470
    for (const auto& cand : fusion_regs) {
26✔
471
        cand_map[cand.container] = &cand;
26✔
472
    }
26✔
473

474
    update_candidate_args_up(first_top, first_current, second_current, [&](auto& name, auto& source_arg, auto& target_args) {
112✔
475
        auto cand_it = cand_map.find(name);
112✔
476
        if (cand_it != cand_map.end() && cand_it->second->integrated_rle) {
112✔
477
            // was RLEd, no longer exists
478
        } else {
85✔
479
            auto it = target_args.find(name);
85✔
480
            if (it != target_args.end()) {
85✔
481
                auto& second_arg = it->second;
46✔
482
                second_arg.local_access.merge_into(source_arg.local_access);
46✔
483
                second_arg.nested_access.merge_into(source_arg.nested_access);
46✔
484
                second_arg.arg.merge(source_arg.arg);
46✔
485
            } else {
46✔
486
                auto [it, fresh] = target_args.emplace(name, source_arg); // copy over
39✔
487
            }
39✔
488
        }
85✔
489
    });
112✔
490
}
22✔
491

492
PatternHandler::MatchResult LoopFusionHandler::match(StructuredLoop& first, StructuredLoop& second, bool no_uses_between) {
60✔
493
    auto first_it = state_.fuse_candidates.find(first.element_id());
60✔
494
    if (first_it == state_.fuse_candidates.end()) {
60✔
495
        return {};
×
496
    }
×
497
    FusionLoopCandidate* first_current = nullptr;
60✔
498
    FusionLoopCandidate* first_top = first_it->second.get();
60✔
499
    FusionLoopCandidate* first_next = first_top;
60✔
500

501
    auto second_it = state_.fuse_candidates.find(second.element_id());
60✔
502
    if (second_it == state_.fuse_candidates.end()) {
60✔
503
        return {};
×
504
    }
×
505
    FusionLoopCandidate* second_current = nullptr;
60✔
506
    FusionLoopCandidate* second_top = second_it->second.get();
60✔
507
    FusionLoopCandidate* second_next = second_top;
60✔
508

509
    SymEngine::map_basic_basic indvar_mapping;
60✔
510
    int current_level = -1;
60✔
511
    int last_matched_level = -1;
60✔
512
    auto first_info = state_.loop_analysis->loop_info(&first);
60✔
513
    auto second_info = state_.loop_analysis->loop_info(&second);
60✔
514

515
    // Skip if both have side effects
516
    if (first_info.has_side_effects && second_info.has_side_effects) {
60✔
517
        return {};
×
518
    }
×
519

520
    int32_t first_max_stack_depth = first_info.map_stack_depth - 1;
60✔
521
    int32_t second_max_stack_depth = second_info.map_stack_depth - 1;
60✔
522
    bool more_first = true;
60✔
523
    bool more_second = true;
60✔
524
    bool fusing_option = first_next->is_by_domain_candidate && second_next->is_by_domain_candidate;
60✔
525
    bool domains_match = true;
60✔
526
    bool both_map = first_next->is_map && second_next->is_map;
60✔
527
    bool no_overlap_candidate = false;
60✔
528

529
    // descend the map stacks down. Last level on which everything matches is the one we can fuse.
530
    // In case there are any further maps nested inside either one of the candidates, we then need to run verification
531
    // that there are no subset conflicts in those nested loops that prevent us from fusing the parents
532

533
    // descend evenly through candidates for fusion by domain.
534
    do {
87✔
535
        ++current_level;
87✔
536

537
        if (fusing_option) {
87✔
538
            auto insertion = indvar_mapping.insert({first_next->loop->indvar(), second_next->loop->indvar()});
84✔
539
            assert(insertion.second);
84✔
540
            fusing_option = this->loop_match(*first_next, *second_next, indvar_mapping);
84✔
541
            if (!fusing_option) {
84✔
542
                domains_match = false;
26✔
543
                indvar_mapping.erase(insertion.first);
26✔
544
            }
26✔
545
        } else {
84✔
546
            domains_match = false;
3✔
547
        }
3✔
548
        auto res = this->check_ins_outs(*first_next, *second_next, indvar_mapping, true, !both_map);
87✔
549
        if (!res.no_conflicts) {
87✔
550
            // will occur on data-dependencies (from consumer to producer) or on subset mismatches
551
            fusing_option = false;
9✔
552
        }
9✔
553
        if (!res.overlap) {
87✔
554
            // No shared memory between the 2 loops. This only makes sense if the iteration domain matches perfectly
555
            if (first_max_stack_depth != second_max_stack_depth) {
12✔
556
                // loop stacks are uneven
NEW
557
                return {};
×
558
            } else {
12✔
559
                no_overlap_candidate = true;
12✔
560
            }
12✔
561
        }
12✔
562
        if (res.subset_mismatch) { // If subsets mismatch on any level, we cannot guarantee correctness without much
87✔
563
                                   // more checks, so fusion-by-domain is out
564
            break;
22✔
565
        }
22✔
566

567
        if (fusing_option) {
65✔
568
            last_matched_level = current_level;
48✔
569
            first_current = first_next;
48✔
570
            second_current = second_next;
48✔
571
        }
48✔
572
        more_first = current_level < first_max_stack_depth;
65✔
573
        more_second = current_level < second_max_stack_depth;
65✔
574
        if (more_first) {
65✔
575
            first_next = state_.get_next_level_map_stack(*first_next);
31✔
576
        }
31✔
577
        if (more_second) {
65✔
578
            second_next = state_.get_next_level_map_stack(*second_next);
31✔
579
        }
31✔
580
    } while (more_first && more_second);
65✔
581

582
    if (last_matched_level >= 0) {
60✔
583
        if (no_overlap_candidate) {
32✔
584
            if (!state_.loop_analysis->children(first_current->loop).empty() ||
5✔
585
                !state_.loop_analysis->children(second_current->loop).empty()) {
5✔
586
                // we only would want to fuse no-overlap cases, if ALL dimensions match.
587
                // this means there can be no loops nested inside the level we are fusing
588
                return {};
2✔
589
            }
2✔
590
        }
5✔
591
        // we found a match for fusion-by-domain. In case there are nested loops we still need to verify they don't
592
        // conflict as well
593
        auto nested_check = this->check_ins_outs(*first_current, *second_current, indvar_mapping, false, !both_map);
30✔
594

595
        if (!nested_check.no_conflicts) {
30✔
596
            DEBUG_PRINTLN(
×
597
                "Should not have discovered fusion conflicts this late:"
×
598
                << last_matched_level + 1 << " lvls): #" << first.element_id() << " | #"
×
599
                << first_current->loop->element_id() << ", #" << second.element_id() << " | #"
×
600
                << second_current->loop->element_id()
×
601
            );
×
602
            return {};
×
603
        }
×
604
        if (!nested_check.subset_mismatch && config_.map_fusion_by_domain) {
30✔
605
            DEBUG_PRINTLN(
19✔
606
                "Fusing loop stack by-domain (" << last_matched_level + 1 << " lvls): #" << first.element_id() << " | #"
19✔
607
                                                << first_current->loop->element_id() << " -> #" << second.element_id()
19✔
608
                                                << " | #" << second_current->loop->element_id()
19✔
609
            );
19✔
610

611
            auto& target_root = second_current->loop->root();
19✔
612
            return fuse_contents(&first, first_current, second_current, indvar_mapping, target_root, no_uses_between);
19✔
613
        }
19✔
614
    }
30✔
615

616
    if (config_.map_fusion_by_access) {
39✔
617
        // we did not find an absolute blocker for fusing, but simple fusion by domain also did not work out, so try the
618
        // fusion-by-access
619
        StructuredLoop *first_loop, *second_loop;
35✔
620
        if (last_matched_level >= 0) {
35✔
621
            first_loop = first_current->loop;
7✔
622
            second_loop = second_current->loop;
7✔
623
        } else {
28✔
624
            first_loop = &first;
28✔
625
            second_loop = &second;
28✔
626
        }
28✔
627
        bool leaf_loops = state_.loop_analysis->children(first_loop).empty() &&
35✔
628
                          state_.loop_analysis->children(second_loop).empty();
35✔
629

630
        return try_complex_fuse_producer_into_consumer(
35✔
631
            *first_top, *second_top, no_uses_between, domains_match && leaf_loops
35✔
632
        );
35✔
633
    } else {
35✔
634
        return {};
4✔
635
    }
4✔
636
}
39✔
637

638
PatternHandler::MatchResult LoopFusionHandler::try_complex_fuse_producer_into_consumer(
639
    FusionLoopCandidate& first, FusionLoopCandidate& second, bool no_uses_between, bool domains_match
640
) {
35✔
641
    auto outcome = try_fuse_by_access(first, second, domains_match);
35✔
642

643
    if (outcome.fused) {
35✔
644
        if constexpr (DUMP_GRAPHS) {
645
            auto dir = state_.builder.subject().metadata_if_exists("output_dir");
646
            if (dir) {
647
                std::filesystem::path pdir = *dir;
648
                visualizer::DotVisualizer::writeToFile(
649
                    state_.builder.subject(),
650
                    pdir / ("map_fusion_by_domain_pass_" + std::to_string(state_.run) + "_dump_" +
651
                            std::to_string(state_.fused_by_domain_count) + "_" +
652
                            std::to_string(second.loop->element_id()) + ".dot")
653
                );
654
            }
655
        }
656

657
        state_.fused_by_access_count++;
22✔
658
    }
22✔
659

660
    return outcome.pattern_result;
35✔
661
}
35✔
662

663
bool LoopFusionHandler::check_no_overlap(
664
    const StructuredLoop& map, const StructuredLoop& second, const std::unordered_set<std::string>& skipped_containers
665
) {
1✔
666
    auto& first_cand = *state_.fuse_candidates.at(map.element_id());
1✔
667
    auto& second_cand = *state_.fuse_candidates.at(second.element_id());
1✔
668
    for (auto& arg : first_cand.args) {
3✔
669
        if (skipped_containers.contains(arg.first)) {
3✔
670
            return false;
×
671
        }
×
672
    }
3✔
673
    return true;
1✔
674
}
1✔
675

676
bool LoopFusionHandler::
677
    loop_match(FusionLoopCandidate& first, FusionLoopCandidate& second, SymEngine::map_basic_basic& canonical_indvars) {
84✔
678
    if (first.incompatible || second.incompatible) {
84✔
679
        return false;
×
680
    }
×
681

682
    bool lower_match =
84✔
683
        symbolic::eq(first.indvar_boundaries->tight_lower_bound(), second.indvar_boundaries->tight_lower_bound());
84✔
684
    if (!lower_match) {
84✔
685
        return false;
13✔
686
    }
13✔
687
    bool upper_match =
71✔
688
        symbolic::eq(first.indvar_boundaries->tight_upper_bound(), second.indvar_boundaries->tight_upper_bound());
71✔
689
    if (!upper_match) {
71✔
690
        return false;
13✔
691
    }
13✔
692
    auto first_canonicalized_map = SymEngine::subs(first.indvar_boundaries->map(), canonical_indvars);
58✔
693
    bool map_match = symbolic::eq(first_canonicalized_map, second.indvar_boundaries->map());
58✔
694
    if (!map_match) {
58✔
695
        return false;
×
696
    }
×
697

698
    return true;
58✔
699
}
58✔
700

701
void LoopFusionHandler::update_moved_candidate_states(FusionLoopCandidate* top, const symbolic::ExpressionMapping& replace) {
19✔
702
    auto& info = state_.loop_analysis->loop_info_local(top->loop);
19✔
703
    auto& candidates = state_.fuse_candidates;
19✔
704

705
    auto& by_id = state_.loop_analysis->loops_in_pre_order();
19✔
706
    for (auto i = info.loop_id; i <= info.last_child_id; ++i) {
40✔
707
        auto* child_loop = by_id.at(i);
21✔
708
        auto cand_it = candidates.find(child_loop->element_id());
21✔
709
        if (cand_it != candidates.end()) {
21✔
710
            auto& child_cand = *cand_it->second;
21✔
711
            child_cand.replace(replace);
21✔
712
        }
21✔
713
    }
21✔
714
}
19✔
715

716
data_flow::Subset updated_subset(const data_flow::Subset& subset, const symbolic::ExpressionMapping& canonical_indvars) {
86✔
717
    std::vector<symbolic::Expression> updated_subset(subset.size());
86✔
718
    for (auto i = 0; i < subset.size(); i++) {
190✔
719
        updated_subset[i] = symbolic::subs(subset[i], canonical_indvars);
104✔
720
    }
104✔
721
    return std::move(updated_subset);
86✔
722
}
86✔
723

724
void LoopFusionHandler::update_candidate_state(
725
    ControlFlowNode* first_top,
726
    FusionLoopCandidate* first_current,
727
    FusionLoopCandidate* second_current,
728
    const symbolic::ExpressionMapping& canonical_indvars
729
) {
19✔
730
    // merge metadata from first -> second
731
    update_moved_candidate_states(first_current, canonical_indvars);
19✔
732

733
    update_candidate_args_up(
19✔
734
        first_top,
19✔
735
        first_current,
19✔
736
        second_current,
19✔
737
        [&](const std::string& name, FusionArg& source_arg, std::unordered_map<std::string, FusionArg>& target_args) {
120✔
738
            // skip the induction variable, we already know those match and they would not be useful to track for
739
            // the next levels up
740
            if (first_current->loop->indvar()->get_name() != name) {
120✔
741
                auto it = target_args.find(name);
117✔
742
                if (it != target_args.end()) {
117✔
743
                    auto& second_arg = it->second;
57✔
744
                    second_arg.local_access.merge_into(source_arg.local_access);
57✔
745
                    second_arg.nested_access.merge_into(source_arg.nested_access);
57✔
746
                    second_arg.arg.merge(source_arg.arg);
57✔
747
                } else {
60✔
748
                    auto [it, fresh] = target_args.emplace(name, source_arg); // copy over
60✔
749
                }
60✔
750
            }
117✔
751
        }
120✔
752
    );
19✔
753
}
19✔
754

755
void LoopFusionHandler::update_candidate_args_up(
756
    ControlFlowNode* first_top,
757
    FusionLoopCandidate* first_current,
758
    FusionLoopCandidate* second_current,
759
    const std::function<
760
        void(const std::string& name, FusionArg& source_arg, std::unordered_map<std::string, FusionArg>& target_args)>&
761
        action
762
) {
41✔
763
    auto terminate_at = state_.loop_analysis->parent_loop(first_top);
41✔
764
    do {
60✔
765
        auto& second_args = second_current->args;
60✔
766
        for (auto& [name, arg] : first_current->args) {
232✔
767
            action(name, arg, second_args);
232✔
768
        }
232✔
769
        // assumptions depend on loop and outer scopes, so they remain identical for the loop that is used as basis
770
        // (that the other gets inlined into) for now we always inline into 2nd.
771

772
        first_current = state_.get_parent(*first_current);
60✔
773
        second_current = state_.get_parent(*second_current);
60✔
774
    } while (first_current && first_current->loop != terminate_at);
60✔
775
}
41✔
776

777
LoopFusionHandler::InOutCheckResult LoopFusionHandler::check_ins_outs(
778
    const FusionLoopCandidate& first_candidate,
779
    const FusionLoopCandidate& second_candidate,
780
    symbolic::ExpressionMapping& canonical_indvars,
781
    bool local_not_nested,
782
    bool only_no_overlap
783
) {
117✔
784
    auto& first_args = first_candidate.args;
117✔
785
    auto& second_args = second_candidate.args;
117✔
786

787
    return check_ins_outs(first_args, second_args, canonical_indvars, local_not_nested, only_no_overlap);
117✔
788
}
117✔
789

790
LoopFusionHandler::InOutCheckResult LoopFusionHandler::check_ins_outs(
791
    const std::unordered_map<std::string, FusionArg>& first_args,
792
    const std::unordered_map<std::string, FusionArg>& second_args,
793
    symbolic::ExpressionMapping& canonical_indvars,
794
    bool local_not_nested,
795
    bool only_no_overlap
796
) {
117✔
797
    bool overlap = false;
117✔
798
    bool no_conflicts = true;
117✔
799
    bool subset_mismatch = false;
117✔
800

801
    for (auto& [name, prod_meta] : first_args) {
480✔
802
        auto cons_it = second_args.find(name);
480✔
803
        if (cons_it != second_args.end()) {
480✔
804
            auto& cons_meta = cons_it->second;
271✔
805
            if (prod_meta.arg.is_input && cons_meta.arg.is_output) {
271✔
806
                // there could be conflicts here. So for now, abort.
807
                // Future Work: if both were to strictly match indvars (or never match other iterations),
808
                // it would never be a conflict
809
                overlap = true;
7✔
810
                no_conflicts = false;
7✔
811
                continue;
7✔
812
            } else if (prod_meta.arg.is_output && cons_meta.arg.is_input) {
264✔
813
                overlap = true;
112✔
814
                auto& prod_collected_accesses = local_not_nested ? prod_meta.local_access : prod_meta.nested_access;
112✔
815
                auto& cons_collected_accesses = local_not_nested ? cons_meta.local_access : cons_meta.nested_access;
112✔
816
                if (prod_collected_accesses.subset_conflicts_with(cons_collected_accesses, canonical_indvars)) {
112✔
817
                    subset_mismatch = true;
34✔
818
                    // conflict (between vars unproved, fuse-by-access might find a solution with conflicting subsets)
819
                    continue;
34✔
820
                }
34✔
821
            } else if (prod_meta.arg.is_ptr && cons_meta.arg.is_ptr && prod_meta.arg.is_explicit_input &&
152✔
822
                       cons_meta.arg.is_explicit_input) {
152✔
823
                overlap = true;
4✔
824
                continue;
4✔
825
            }
4✔
826
        }
271✔
827
    }
480✔
828

829
    if (only_no_overlap && overlap) {
117✔
830
        no_conflicts = false;
2✔
831
    }
2✔
832

833
    return {no_conflicts, overlap, subset_mismatch};
117✔
834
}
117✔
835

836
void LoopFusionHandler::update_fused_seq(Sequence& sequence, const symbolic::ExpressionMapping& replacements) {
19✔
837
    sequence.replace(replacements);
19✔
838
}
19✔
839

840
/**
841
 * Merge a newly observed access (its `subset` and `not_understood` flag) into the FusionArg `into`.
842
 * If `into` already tracks a different subset, we can no longer describe the access with a single
843
 * subset and mark it not_understood. Returns true if `into` was modified.
844
 */
845
bool FusionArgCommonAccesses::merge_into(
846
    Block* block,
847
    const std::optional<data_flow::Subset>& subset,
848
    bool not_understood,
849
    bool write_not_read,
850
    const symbolic::ExpressionMapping* lower_indvars
851
) {
514✔
852
    bool updated = merge_subset(subset, not_understood, lower_indvars);
514✔
853

854
    if (write_not_read) {
514✔
855
        updated |= wr_block.merge_into(block, false);
238✔
856
    } else {
276✔
857
        updated |= rd_block.merge_into(block, false);
276✔
858
    }
276✔
859

860
    return updated;
514✔
861
}
514✔
862

863
bool FusionArgCommonBlock::merge_into(Block* other_block, bool other_conflict) {
1,958✔
864
    bool updated = false;
1,958✔
865

866
    if (other_conflict && !this->block_conflict) {
1,958✔
867
        this->block_conflict = true;
×
868
        updated = true;
×
869
    } else if (!this->block_conflict && this->common_block && other_block) {
1,958✔
870
        if (this->common_block != other_block) {
115✔
871
            this->block_conflict = true;
9✔
872
            this->common_block = nullptr;
9✔
873
            updated = true;
9✔
874
        }
9✔
875
    } else if (!this->block_conflict && !this->common_block && other_block) {
1,843✔
876
        this->common_block = other_block;
682✔
877
        updated = true;
682✔
878
    }
682✔
879

880
    return updated;
1,958✔
881
}
1,958✔
882

883
bool FusionArgCommonBlock::merge_into(const FusionArgCommonBlock& other) {
1,444✔
884
    return merge_into(other.common_block, other.block_conflict);
1,444✔
885
}
1,444✔
886

887
bool FusionArgCommonAccesses::
888
    merge_into(const FusionArgCommonAccesses& other, const symbolic::ExpressionMapping* lower_indvars) {
722✔
889
    bool update = merge_subset(other.common_subset, other.subsets_conflict, lower_indvars);
722✔
890
    update |= wr_block.merge_into(other.wr_block);
722✔
891
    update |= rd_block.merge_into(other.rd_block);
722✔
892
    return update;
722✔
893
}
722✔
894

895
bool FusionArgCommonAccesses::subset_conflicts_with(
896
    const FusionArgCommonAccesses& other_common_acceses, symbolic::ExpressionMapping& canonical_indvars
897
) const {
112✔
898
    if (subsets_conflict || other_common_acceses.subsets_conflict) {
112✔
899
        return true;
5✔
900
    }
5✔
901

902
    if (common_subset.has_value() && other_common_acceses.common_subset.has_value()) {
107✔
903
        if (!symbolic::vectors_of_expressions_match(
64✔
904
                common_subset.value(), other_common_acceses.common_subset.value(), canonical_indvars
64✔
905
            )) {
64✔
906
            return true;
29✔
907
        }
29✔
908
    }
64✔
909

910
    return false;
78✔
911
}
107✔
912

913
bool FusionArgCommonAccesses::merge_subset(
914
    const std::optional<data_flow::Subset>& subset,
915
    bool not_understood,
916
    const symbolic::ExpressionMapping* lower_indvars
917
) {
1,236✔
918
    bool updated = false;
1,236✔
919

920
    std::optional<data_flow::Subset> mapped_subset_holder;
1,236✔
921
    const data_flow::Subset* mapped_subset = nullptr;
1,236✔
922
    if (subset.has_value()) {
1,236✔
923
        if (lower_indvars) {
789✔
924
            mapped_subset_holder = symbolic::substitute(subset.value(), *lower_indvars);
230✔
925
            mapped_subset = &mapped_subset_holder.value();
230✔
926
        } else {
559✔
927
            mapped_subset = &subset.value();
559✔
928
        }
559✔
929
    }
789✔
930

931
    if (common_subset.has_value() && mapped_subset &&
1,236✔
932
        !symbolic::vectors_of_expressions_match(common_subset.value(), *mapped_subset)) {
1,236✔
933
        if (!subsets_conflict) {
26✔
934
            subsets_conflict = true;
22✔
935
            updated = true;
22✔
936
        }
22✔
937
    } else if (!common_subset.has_value() && mapped_subset && !subsets_conflict) {
1,210✔
938
        common_subset = *mapped_subset;
618✔
939
        updated = true;
618✔
940
    }
618✔
941
    if (not_understood && !subsets_conflict) {
1,236✔
942
        subsets_conflict = true;
3✔
943
        updated = true;
3✔
944
    }
3✔
945
    return updated;
1,236✔
946
}
1,236✔
947

948
bool FusionArg::saw_access_locally() const {
27✔
949
    return local_access.subsets_conflict || local_access.common_subset.has_value();
27✔
950
}
27✔
951

952
void FusionLoopCandidate::non_indvar_writes() { this->incompatible = true; }
×
953

954
void FusionLoopCandidate::aliasing_encountered() { this->incompatible = true; }
×
955

956
void FusionLoopCandidate::replace(const symbolic::ExpressionMapping& mapping) {
21✔
957
    for (auto& [name, arg] : args) {
84✔
958
        if (arg.local_access.common_subset.has_value()) {
84✔
959
            arg.local_access.common_subset = updated_subset(arg.local_access.common_subset.value(), mapping);
41✔
960
        }
41✔
961
        if (arg.nested_access.common_subset.has_value()) {
84✔
962
            arg.nested_access.common_subset = updated_subset(arg.nested_access.common_subset.value(), mapping);
45✔
963
        }
45✔
964
    }
84✔
965
    symbolic::substitute(assumptions, mapping);
21✔
966

967
    if constexpr (DUMP_ASSUMPTIONS) {
968
        std::cout << "Updated #" << this->loop->element_id() << " to:" << std::endl;
969
        std::cout << this->assumptions << std::endl;
970
    }
971
}
21✔
972

973
NeighboringPatternVisitor::NeighboringPatternVisitor(PatternHandler& handler) : handler_(handler) {}
56✔
974

975
bool NeighboringPatternVisitor::visit(sdfg::structured_control_flow::Sequence& node) {
210✔
976
    if (node.size() < 2) { // impossible to find a match, just descend into it
210✔
977
        return ActualStructuredSDFGVisitor::visit(node);
108✔
978
    }
108✔
979

980
    // Iterate over sequence looking for consecutive (StructuredLoop, StructuredLoop) pairs
981
    size_t i = 0;
102✔
982
    structured_control_flow::ControlFlowNode* override_last = nullptr;
102✔
983
    while (i < node.size()) {
327✔
984
        auto& child_node = node.at(i);
225✔
985
        auto* first = dyn_cast<structured_control_flow::StructuredLoop*>(&child_node);
225✔
986
        if (!first) {
225✔
987
            i++;
97✔
988
            dispatch(child_node);
97✔
989
            continue;
97✔
990
        }
97✔
991
        if (first->root().size() == 0) {
128✔
992
            i++;
×
993
            continue;
×
994
        }
×
995

996
        StructuredLoop* second = nullptr;
128✔
997

998
        if (i + 1 < node.size()) {
128✔
999
            second = dyn_cast<structured_control_flow::StructuredLoop*>(&node.at(i + 1));
63✔
1000
            if (second) {
63✔
1001
                if (second->root().size() == 0) {
59✔
1002
                    i++;
×
1003
                    continue;
×
1004
                }
×
1005

1006
                auto result = handler_.match(*first, *second, true);
59✔
1007

1008
                if (!result.removed_first) {
59✔
1009
                    dispatch(child_node);
38✔
1010
                }
38✔
1011
                if (result.visit_second_body) {
59✔
1012
                    auto* second_updated_child = result.second_root_replacement ? result.second_root_replacement
2✔
1013
                                                                                : second;
2✔
1014
                    dispatch(*second_updated_child);
2✔
1015
                }
2✔
1016
                if (result.removed_first) {
59✔
1017
                    // do not increment i, we can use at as next firs
1018
                    continue;
21✔
1019
                }
21✔
1020
            } else if (i + 2 < node.size()) {
59✔
1021
                auto* mid_block = dyn_cast<structured_control_flow::Block*>(&node.at(i + 1));
4✔
1022
                bool skippable = false;
4✔
1023
                std::unordered_set<std::string> skipped_containers;
4✔
1024
                if (mid_block) {
4✔
1025
                    if (mid_block->dataflow().nodes().empty()) {
4✔
1026
                        skippable = true;
1✔
1027
                    } else if (mid_block->is_a_library_node<stdlib::MallocNode>()) {
3✔
1028
                        for (auto& data_flow_node : mid_block->dataflow().nodes()) {
×
1029
                            if (auto* container = dynamic_cast<data_flow::AccessNode*>(&data_flow_node)) {
×
1030
                                skipped_containers.emplace(container->data());
×
1031
                            }
×
1032
                        }
×
1033
                        skippable = true;
×
1034
                    }
×
1035
                }
4✔
1036
                if (skippable) {
4✔
1037
                    second = dyn_cast<structured_control_flow::StructuredLoop*>(&node.at(i + 2));
1✔
1038
                    if (second) {
1✔
1039
                        if (second->root().size() == 0) {
1✔
1040
                            i += 2;
×
1041
                            continue;
×
1042
                        }
×
1043

1044
                        if (!handler_.check_no_overlap(*first, *second, skipped_containers)) {
1✔
1045
                            i += 2;
×
1046
                            continue;
×
1047
                        }
×
1048

1049
                        auto result = handler_.match(*first, *second, true);
1✔
1050

1051
                        if (!result.removed_first) {
1✔
1052
                            dispatch(child_node);
×
1053
                        }
×
1054
                        if (result.visit_second_body) {
1✔
1055
                            auto* second_updated_child = result.second_root_replacement ? result.second_root_replacement
×
1056
                                                                                        : second;
×
1057
                            dispatch(*second_updated_child);
×
1058
                        }
×
1059
                        if (result.removed_first) {
1✔
1060
                            i += 1; // skip the block, retry with second as next first
1✔
1061
                            continue;
1✔
1062
                        }
1✔
1063
                        // we visited [first, skipped, second] successfully, without shifting indices, move to second as
1064
                        // new first
1065
                        i += 2;
×
1066
                        continue;
×
1067
                    } else {
1✔
1068
                        // we know i+1 is worthless, so skip it
1069
                        i += 2;
×
1070
                        continue;
×
1071
                    }
×
1072
                }
1✔
1073
            }
4✔
1074
        } else {
65✔
1075
            dispatch(child_node);
65✔
1076
        }
65✔
1077
        i++;
106✔
1078
    }
106✔
1079

1080
    return true;
102✔
1081
}
210✔
1082

1083
} // namespace sdfg::passes::loop_fusion
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