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

daisytuner / docc / 30964722872

05 Aug 2026 12:53AM UTC coverage: 64.899% (+0.001%) from 64.898%
30964722872

push

github

web-flow
Merge pull request #934 from daisytuner/softmax-fusion

extends loop fusion to handle softmax

12 of 16 new or added lines in 1 file covered. (75.0%)

45945 of 70795 relevant lines covered (64.9%)

721.04 hits per line

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

80.08
/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() {
283✔
49
        if (loop_stack_.empty()) {
283✔
50
            return nullptr;
7✔
51
        }
7✔
52
        return &loop_stack_.back();
276✔
53
    }
283✔
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) {}
57✔
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) {
188✔
97
        auto* indvar_bounds = candidate.indvar_boundaries;
188✔
98
        symbolic::Expression stride = symbolic::integer(1);
188✔
99
        if (symbolic::null_safe_eq(symbolic::sub(indvar_bounds->map(), indvar_bounds->symbol()), stride)) {
188✔
100
            return SymEngine::function_symbol(
188✔
101
                "indvar",
188✔
102
                {indvar_bounds->tight_lower_bound(), indvar_bounds->tight_upper_bound(), stride, symbolic::integer(level)
188✔
103
                }
188✔
104
            );
188✔
105
        } else {
188✔
106
            return {};
×
107
        }
×
108
    }
188✔
109

110
    bool handleStructuredLoop(sdfg::structured_control_flow::StructuredLoop& node) override {
188✔
111
        auto cand_it = fuse_candidates_.find(node.element_id());
188✔
112
        bool is_relevant_loop = cand_it != fuse_candidates_.end();
188✔
113
        if (is_relevant_loop) {
188✔
114
            auto type = is_a(node.type_id(), ElementType::Map) ? analysis::LocalLoopInfo::LoopType::Map
188✔
115
                                                               : analysis::LocalLoopInfo::LoopType::For;
188✔
116
            auto& candidate = *cand_it->second.get();
188✔
117
            loop_stack_.emplace_back(&node, type, candidate, get_indvar_placeholder(candidate, loop_stack_.size() - 1));
188✔
118
            loop_stack_.back().indvars.emplace(node.indvar()->get_name());
188✔
119
        }
188✔
120
        auto res = BaseUserVisitor::handleStructuredLoop(node);
188✔
121
        if (is_relevant_loop) {
188✔
122
            auto size = loop_stack_.size();
188✔
123
            if (size > 1) {
188✔
124
                auto& parent = loop_stack_.at(size - 2);
70✔
125
                propagate_indirect_accesses_up(loop_stack_.back(), parent);
70✔
126
            }
70✔
127
            loop_stack_.pop_back();
188✔
128
        }
188✔
129
        return res;
188✔
130
    }
188✔
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,031✔
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
    ) {
272✔
148
        auto& cand = current->fusion_candidate;
272✔
149
        auto arg_it = cand.args.find(container);
272✔
150
        if (arg_it != cand.args.end()) {
272✔
151
            auto& fusion_arg = arg_it->second;
264✔
152
            fusion_arg.local_access.merge_into(const_cast<Block*>(&block), edge.subset(), false, is_write);
264✔
153
            std::optional<data_flow::Subset> generalized_subset_holder;
264✔
154
            const data_flow::Subset* generalized_subset = &edge.subset();
264✔
155
            if (!current->indvar_placeholder.is_null()) {
264✔
156
                generalized_subset_holder = symbolic::
264✔
157
                    substitute(*generalized_subset, {{cand.indvar_boundaries->symbol(), current->indvar_placeholder}});
264✔
158
                generalized_subset = &generalized_subset_holder.value();
264✔
159
            }
264✔
160

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

165
    static void propagate_indirect_accesses_up(LoopEntry& current, LoopEntry& parent) {
70✔
166
        auto& parent_cand = parent.fusion_candidate;
70✔
167
        std::optional<symbolic::ExpressionMapping> indvar_mapping;
70✔
168
        auto add_placeholder_mapping = [&](LoopEntry& entry) {
140✔
169
            if (!entry.fusion_candidate.is_by_domain_candidate) {
140✔
NEW
170
                return;
×
NEW
171
            }
×
172
            auto& indvar_bounds = entry.fusion_candidate.indvar_boundaries;
140✔
173
            symbolic::Expression stride = symbolic::integer(1);
140✔
174
            if (entry.indvar_placeholder.is_null() ||
140✔
175
                !symbolic::null_safe_eq(symbolic::sub(indvar_bounds->map(), indvar_bounds->symbol()), stride)) {
140✔
NEW
176
                return;
×
NEW
177
            }
×
178
            if (!indvar_mapping.has_value()) {
140✔
179
                indvar_mapping = symbolic::ExpressionMapping();
70✔
180
            }
70✔
181
            indvar_mapping->emplace(
140✔
182
                SymEngine::rcp_static_cast<const SymEngine::Basic>(indvar_bounds->symbol()), entry.indvar_placeholder
140✔
183
            );
140✔
184
        };
140✔
185
        // Generalize the child's indvar (as before) AND the parent's own indvar. Child accesses that
186
        // reference the parent's index (e.g. a reduction writing acc[parent_i] inside an inner loop)
187
        // must use the same placeholder as the parent's direct accesses, otherwise a single container
188
        // ends up with both a placeholder and a raw-symbol subset and is falsely flagged as conflicting.
189
        add_placeholder_mapping(current);
70✔
190
        add_placeholder_mapping(parent);
70✔
191
        for (auto& [container, meta] : current.fusion_candidate.args) {
331✔
192
            auto arg_it = parent_cand.args.find(container);
331✔
193
            if (arg_it != parent_cand.args.end()) {
331✔
194
                auto& parent_arg = arg_it->second;
265✔
195
                parent_arg.nested_access
265✔
196
                    .merge_into(meta.nested_access, indvar_mapping.has_value() ? &*indvar_mapping : nullptr);
265✔
197
                parent_arg.nested_access
265✔
198
                    .merge_into(meta.local_access, indvar_mapping.has_value() ? &*indvar_mapping : nullptr);
265✔
199
            }
265✔
200
        }
331✔
201

202
        parent.fusion_candidate.nested_incompatible |= current.fusion_candidate.incompatible |
70✔
203
                                                       current.fusion_candidate.nested_incompatible;
70✔
204
    }
70✔
205

206

207
    void use_as_dst_node(
208
        const std::string& container,
209
        const data_flow::AccessNode& node,
210
        const data_flow::Memlet& edge,
211
        const Block& block
212
    ) override {
136✔
213
        auto current = get_current_loop();
136✔
214
        if (current && edge.is_dst_pointed_to_write()) {
136✔
215
            found_indirect_arg_access(container, edge, block, current, true);
128✔
216
        }
128✔
217
    }
136✔
218
    void use_as_return_src(const std::string& container, const Return& ret) override {}
×
219
    /**
220
     * Dangerous, if somebody builds a value derived from indvar and then uses that for addressing we would not notice.
221
     * But normally those should be folded into the accesses
222
     */
223
    void use_as_src_node(
224
        const std::string& container,
225
        const data_flow::AccessNode& node,
226
        const data_flow::Memlet& edge,
227
        const Block& block
228
    ) override {
147✔
229
        auto current = get_current_loop();
147✔
230
        if (current && (edge.is_src_address_leak() || edge.is_src_pointed_to_address_leak(sdfg_.type(container)))) {
147✔
231
            current->fusion_candidate.aliasing_encountered();
×
232
        } else if (current && edge.is_src_pointed_to_read()) {
147✔
233
            found_indirect_arg_access(container, edge, block, current, false);
144✔
234
        }
144✔
235
    }
147✔
236
    void use_as_symbol_write(
237
        const symbolic::Symbol& container, const ControlFlowNode* node, const Element* user, SymbolWriteLocation loc
238
    ) override {}
188✔
239
};
240

241
FusionLoopCandidate* LoopFusionPass::State::get_next_level_map_stack(FusionLoopCandidate& current) {
63✔
242
    auto& children = loop_analysis->children(current.loop);
63✔
243
    if (children.empty()) {
63✔
244
        return nullptr;
×
245
    }
×
246

247
    auto* next = children.at(0);
63✔
248
    return fuse_candidates.at(next->element_id()).get();
63✔
249
}
63✔
250

251
FusionLoopCandidate* LoopFusionPass::State::get_parent(FusionLoopCandidate& current) {
122✔
252
    auto* parent = loop_analysis->parent_loop(current.loop);
122✔
253
    if (!parent) {
122✔
254
        return nullptr;
82✔
255
    }
82✔
256
    auto it = fuse_candidates.find(parent->element_id());
40✔
257
    if (it != fuse_candidates.end()) {
40✔
258
        return it->second.get();
40✔
259
    } else {
40✔
260
        return nullptr;
×
261
    }
×
262
}
40✔
263

264
uint32_t LoopFusionPass::State::total_fused_count() const { return fused_by_domain_count + fused_by_access_count; }
57✔
265

266
std::ostream& operator<<(std::ostream& os, const symbolic::Expression& expr) {
×
267
    if (!expr.is_null()) {
×
268
        os << expr->__str__();
×
269
    } else {
×
270
        os << "null";
×
271
    }
×
272
    return os;
×
273
}
×
274

275
std::ostream& operator<<(std::ostream& os, const symbolic::Symbol& sym) {
×
276
    if (sym.is_null()) {
×
277
        os << "null";
×
278
    } else {
×
279
        os << sym->get_name();
×
280
    }
×
281
    return os;
×
282
}
×
283

284
std::ostream& operator<<(std::ostream& os, const symbolic::Assumption& assump) {
×
285
    os << "\t" << "const: " << (assump.constant() ? "true" : "false") << std::endl;
×
286
    os << "\t" << "map: " << assump.map() << std::endl;
×
287
    os << "\t" << "lower_bounds: " << assump.lower_bounds() << std::endl;
×
288
    os << "\t" << "upper_bounds: " << assump.upper_bounds() << std::endl;
×
289
    os << "\ttight_lower: " << assump.tight_lower_bound() << std::endl;
×
290
    os << "\ttight_upper: " << assump.tight_upper_bound() << std::endl;
×
291
    os << "\t" << "constraints: " << assump.constraints() << std::endl;
×
292
    return os;
×
293
}
×
294

295
std::ostream& operator<<(std::ostream& os, const symbolic::Assumptions& ass) {
×
296
    for (auto& [sym, as] : ass) {
×
297
        os << "\t" << sym << ":" << std::endl << as << std::endl;
×
298
    }
×
299
    return os;
×
300
}
×
301

302
LoopFusionPass::LoopFusionPass(const LoopFusionConfig& config) : config_(config) {}
5✔
303

304
LoopFusionPass::LoopFusionPass() = default;
52✔
305

306
bool LoopFusionPass::run_pass(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
57✔
307
    auto loop_ana = std::make_unique<analysis::LoopAnalysis>(builder.subject());
57✔
308
    loop_ana->run(analysis_manager);
57✔
309

310
    static uint32_t run = 0;
57✔
311
    DEBUG_PRINTLN("LoopFusion pass #" << run);
57✔
312

313
    State state(builder, analysis_manager, std::move(loop_ana));
57✔
314
    state.run = run;
57✔
315
    run++;
57✔
316

317
    auto& assumption_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
57✔
318
    auto& arguments_analysis = analysis_manager.get<analysis::ArgumentsAnalysis>();
57✔
319

320
    for (auto* control_flow_node : state.loop_analysis->loops()) {
188✔
321
        if (auto* loop = dyn_cast<StructuredLoop*>(control_flow_node)) {
188✔
322
            auto& indvar = loop->indvar();
188✔
323
            auto& assumpts = assumption_analysis.get(loop->root(), true);
188✔
324
            auto* indvar_boundaries = find_indvar_boundaries(indvar, assumpts);
188✔
325

326
            std::unique_ptr<FusionLoopCandidate> cand;
188✔
327

328
            bool tight = indvar_boundaries && !indvar_boundaries->tight_lower_bound().is_null() &&
188✔
329
                         !indvar_boundaries->tight_upper_bound().is_null() && !indvar_boundaries->map().is_null();
188✔
330
            bool is_map = is_a(loop->type_id(), ElementType::Map);
188✔
331
            cand = std::make_unique<FusionLoopCandidate>(loop, indvar_boundaries, assumpts, is_map, tight);
188✔
332
            auto& args = arguments_analysis.arguments(analysis_manager, *loop);
188✔
333
            for (auto [name, arg] : args) {
747✔
334
                cand->args.emplace(name, arg);
747✔
335
            }
747✔
336
            state.fuse_candidates[control_flow_node->element_id()] = std::move(cand);
188✔
337
        }
188✔
338
    }
188✔
339

340
    LoopIndirectAccessFinder indirect_access_finder(builder.subject(), *state.loop_analysis, state.fuse_candidates);
57✔
341
    indirect_access_finder.dispatch(builder.subject().root());
57✔
342

343
    const std::string* dir = nullptr;
57✔
344
    if (DUMP_LOOP_INFOS) {
57✔
345
        dir = builder.subject().metadata_if_exists("output_dir");
×
346
        if (dir) {
×
347
            state.loop_analysis->dump_to_file(std::filesystem::path(*dir) / "loop_infos.pre-fusion.json");
×
348
        }
×
349
    }
×
350

351
    LoopFusionHandler handler(config_, state);
57✔
352

353
    NeighboringPatternVisitor v(handler);
57✔
354
    v.dispatch(builder.subject().root());
57✔
355

356
    if (dir) {
57✔
357
        state.loop_analysis->dump_to_file(std::filesystem::path(*dir) / "loop_infos.post-fusion.json");
×
358
    }
×
359

360
    return state.total_fused_count();
57✔
361
}
57✔
362

363
const symbolic::Assumption* LoopFusionPass::
364
    find_indvar_boundaries(const symbolic::Symbol& indvar, const symbolic::Assumptions& assumptions) {
188✔
365
    auto it = assumptions.find(indvar);
188✔
366
    if (it != assumptions.end()) {
188✔
367
        return &it->second;
188✔
368
    }
188✔
369

370
    return nullptr;
×
371
}
188✔
372

373
LoopFusionHandler::LoopFusionHandler(const LoopFusionConfig& config, LoopFusionPass::State& state)
374
    : config_(config), state_(state), LoopFusionByAccessWorker(config.allow_init_hoist) {}
57✔
375

376
PatternHandler::MatchResult LoopFusionHandler::fuse_contents(
377
    ControlFlowNode* first_top,
378
    FusionLoopCandidate* first_current,
379
    FusionLoopCandidate* second_innermost,
380
    const symbolic::ExpressionMapping& indvar_mapping,
381
    Sequence& target_root,
382
    bool can_remove_original
383
) {
21✔
384
    auto first_elem_id = first_current->loop->element_id();
21✔
385

386
    Sequence* append_root = nullptr;
21✔
387
    if (target_root.size() == 0) {
21✔
388
        // target seq is empty, so we can just append to it
389
        append_root = &target_root;
×
390
    } else {
21✔
391
        // there currently is no way to prepend-copy with replace, so add to new sequence,
392
        // replace on it, then flatten it into the existing
393
        append_root = &state_.builder.add_sequence_before(target_root, target_root.at(0), {});
21✔
394
    }
21✔
395

396
    std::optional<std::unordered_map<const ControlFlowNode*, const ControlFlowNode*>> copy_mapping;
21✔
397
    if (can_remove_original) {
21✔
398
        state_.builder.move_children(first_current->loop->root(), *append_root);
21✔
399
    } else {
21✔
400
        deepcopy::StructuredSDFGDeepCopy copier(state_.builder, *append_root, first_current->loop->root());
×
401
        copy_mapping = copier.insert();
×
402
    }
×
403

404
    update_fused_seq(*append_root, indvar_mapping);
21✔
405

406
    if (append_root != &target_root) { // need to fixup / flatten the copied sequence into the target sequence
21✔
407
        state_.builder.move_children(*append_root, target_root, 0);
21✔
408
        state_.builder.remove_from_parent(*append_root);
21✔
409
        append_root = nullptr;
21✔
410
    }
21✔
411

412
    update_candidate_state(first_top, first_current, second_innermost, indvar_mapping);
21✔
413

414
    auto first_children = state_.loop_analysis->children(first_current->loop);
21✔
415
    bool keep_visiting_second = !state_.loop_analysis->children(second_innermost->loop).empty() ||
21✔
416
                                !first_children.empty();
21✔
417
    auto& prev_local_info = state_.loop_analysis->loop_info_local(first_current->loop);
21✔
418
    if (can_remove_original) {
21✔
419
        for (auto& child : first_children) {
21✔
420
            state_.loop_analysis->moved_loop(child, second_innermost->loop, true);
3✔
421
        }
3✔
422
        state_.loop_analysis->added_local_contents(
21✔
423
            second_innermost->loop, prev_local_info.contains_side_effects, prev_local_info.contains_non_perfectly_nested
21✔
424
        );
21✔
425
    } else {
21✔
426
        for (auto& child : first_children) {
×
427
            state_.loop_analysis->copied_loop(
×
428
                child,
×
429
                second_innermost->loop,
×
430
                const_cast<structured_control_flow::ControlFlowNode*>(copy_mapping->at(child)),
×
431
                true
×
432
            );
×
433
        }
×
434
        state_.loop_analysis->added_local_contents(
×
435
            second_innermost->loop, prev_local_info.contains_side_effects, prev_local_info.contains_non_perfectly_nested
×
436
        );
×
437
    }
×
438

439
    bool removed_first = false;
21✔
440
    if (can_remove_original) {
21✔
441
        state_.loop_analysis->removed_loop(first_top);
21✔
442
        state_.builder.remove_from_parent(*first_top);
21✔
443
        removed_first = true;
21✔
444
    }
21✔
445

446
    if constexpr (DUMP_GRAPHS) {
447
        auto dir = state_.builder.subject().metadata_if_exists("output_dir");
448
        if (dir) {
449
            std::filesystem::path pdir = *dir;
450
            visualizer::DotVisualizer::writeToFile(
451
                state_.builder.subject(),
452
                pdir / ("map_fusion_by_domain_pass_" + std::to_string(state_.run) + "_dump_" +
453
                        std::to_string(state_.fused_by_domain_count) + "_" +
454
                        std::to_string(second_innermost->loop->element_id()) + ".dot")
455
            );
456
        }
457
    }
458

459
    state_.fused_by_domain_count++;
21✔
460

461
    // if there are further loops inside the now fused body, visit those as well
462
    return {.removed_first = removed_first, .visit_second_body = keep_visiting_second};
21✔
463
}
21✔
464

465
analysis::LoopAnalysis& LoopFusionHandler::get_loop_analysis() { return *state_.loop_analysis; }
115✔
466

467
FusionLoopCandidate* LoopFusionHandler::get_fuse_candidate(StructuredLoop& loop) {
98✔
468
    return state_.fuse_candidates.at(loop.element_id()).get();
98✔
469
}
98✔
470

471
builder::StructuredSDFGBuilder& LoopFusionHandler::builder() { return state_.builder; }
21✔
472

473
void LoopFusionHandler::update_copied_leaf_contents_from_first_to_second(
474
    const Plan& plan, FusionLoopCandidate* first_current, FusionLoopCandidate* second_current
475
) {
21✔
476
    auto first_top = &plan.first;
21✔
477

478

479
    auto& fusion_regs = plan.fusion_candidates_;
21✔
480

481
    std::unordered_map<std::string, const loop_fusion::FusionRegCandidate*> cand_map;
21✔
482
    for (const auto& cand : fusion_regs) {
25✔
483
        cand_map[cand.container] = &cand;
25✔
484
    }
25✔
485

486
    update_candidate_args_up(first_top, first_current, second_current, [&](auto& name, auto& source_arg, auto& target_args) {
106✔
487
        auto cand_it = cand_map.find(name);
106✔
488
        if (cand_it != cand_map.end() && cand_it->second->integrated_rle) {
106✔
489
            // was RLEd, no longer exists
490
        } else {
81✔
491
            auto it = target_args.find(name);
81✔
492
            if (it != target_args.end()) {
81✔
493
                auto& second_arg = it->second;
44✔
494
                second_arg.local_access.merge_into(source_arg.local_access);
44✔
495
                second_arg.nested_access.merge_into(source_arg.nested_access);
44✔
496
                second_arg.arg.merge(source_arg.arg);
44✔
497
            } else {
44✔
498
                auto [it, fresh] = target_args.emplace(name, source_arg); // copy over
37✔
499
            }
37✔
500
        }
81✔
501
    });
106✔
502
}
21✔
503

504
PatternHandler::MatchResult LoopFusionHandler::match(StructuredLoop& first, StructuredLoop& second, bool no_uses_between) {
63✔
505
    auto first_it = state_.fuse_candidates.find(first.element_id());
63✔
506
    if (first_it == state_.fuse_candidates.end()) {
63✔
507
        return {};
×
508
    }
×
509
    FusionLoopCandidate* first_current = nullptr;
63✔
510
    FusionLoopCandidate* first_top = first_it->second.get();
63✔
511
    FusionLoopCandidate* first_next = first_top;
63✔
512

513
    auto second_it = state_.fuse_candidates.find(second.element_id());
63✔
514
    if (second_it == state_.fuse_candidates.end()) {
63✔
515
        return {};
×
516
    }
×
517
    FusionLoopCandidate* second_current = nullptr;
63✔
518
    FusionLoopCandidate* second_top = second_it->second.get();
63✔
519
    FusionLoopCandidate* second_next = second_top;
63✔
520

521
    SymEngine::map_basic_basic indvar_mapping;
63✔
522
    int current_level = -1;
63✔
523
    int last_matched_level = -1;
63✔
524
    auto first_info = state_.loop_analysis->loop_info(&first);
63✔
525
    auto second_info = state_.loop_analysis->loop_info(&second);
63✔
526

527
    // Skip if both have side effects
528
    if (first_info.has_side_effects && second_info.has_side_effects) {
63✔
529
        return {};
×
530
    }
×
531

532
    int32_t first_max_stack_depth = first_info.map_stack_depth - 1;
63✔
533
    int32_t second_max_stack_depth = second_info.map_stack_depth - 1;
63✔
534
    bool more_first = true;
63✔
535
    bool more_second = true;
63✔
536
    bool fusing_option = first_next->is_by_domain_candidate && second_next->is_by_domain_candidate;
63✔
537
    bool domains_match = true;
63✔
538
    bool both_map = first_next->is_map && second_next->is_map;
63✔
539
    bool no_overlap_candidate = false;
63✔
540

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

545
    // descend evenly through candidates for fusion by domain.
546
    do {
90✔
547
        ++current_level;
90✔
548

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

579
        if (fusing_option) {
68✔
580
            last_matched_level = current_level;
49✔
581
            first_current = first_next;
49✔
582
            second_current = second_next;
49✔
583
        }
49✔
584
        more_first = current_level < first_max_stack_depth;
68✔
585
        more_second = current_level < second_max_stack_depth;
68✔
586
        if (more_first) {
68✔
587
            first_next = state_.get_next_level_map_stack(*first_next);
31✔
588
        }
31✔
589
        if (more_second) {
68✔
590
            second_next = state_.get_next_level_map_stack(*second_next);
32✔
591
        }
32✔
592
    } while (more_first && more_second);
68✔
593

594
    if (last_matched_level >= 0) {
63✔
595
        if (no_overlap_candidate) {
33✔
596
            if (!state_.loop_analysis->children(first_current->loop).empty() ||
5✔
597
                !state_.loop_analysis->children(second_current->loop).empty()) {
5✔
598
                // we only would want to fuse no-overlap cases, if ALL dimensions match.
599
                // this means there can be no loops nested inside the level we are fusing
600
                return {};
2✔
601
            }
2✔
602
        }
5✔
603
        // we found a match for fusion-by-domain. In case there are nested loops we still need to verify they don't
604
        // conflict as well
605
        auto nested_check = this->check_ins_outs(*first_current, *second_current, indvar_mapping, false, !both_map);
31✔
606

607
        if (!nested_check.no_conflicts) {
31✔
608
            DEBUG_PRINTLN(
×
609
                "Should not have discovered fusion conflicts this late:"
×
610
                << last_matched_level + 1 << " lvls): #" << first.element_id() << " | #"
×
611
                << first_current->loop->element_id() << ", #" << second.element_id() << " | #"
×
612
                << second_current->loop->element_id()
×
613
            );
×
614
            return {};
×
615
        }
×
616
        if (!nested_check.subset_mismatch && config_.map_fusion_by_domain) {
31✔
617
            DEBUG_PRINTLN(
21✔
618
                "Fusing loop stack by-domain (" << last_matched_level + 1 << " lvls): #" << first.element_id() << " | #"
21✔
619
                                                << first_current->loop->element_id() << " -> #" << second.element_id()
21✔
620
                                                << " | #" << second_current->loop->element_id()
21✔
621
            );
21✔
622

623
            auto& target_root = second_current->loop->root();
21✔
624
            return fuse_contents(&first, first_current, second_current, indvar_mapping, target_root, no_uses_between);
21✔
625
        }
21✔
626
    }
31✔
627

628
    if (config_.map_fusion_by_access) {
40✔
629
        // we did not find an absolute blocker for fusing, but simple fusion by domain also did not work out, so try the
630
        // fusion-by-access
631
        StructuredLoop *first_loop, *second_loop;
34✔
632
        if (last_matched_level >= 0) {
34✔
633
            first_loop = first_current->loop;
6✔
634
            second_loop = second_current->loop;
6✔
635
        } else {
28✔
636
            first_loop = &first;
28✔
637
            second_loop = &second;
28✔
638
        }
28✔
639
        bool leaf_loops = state_.loop_analysis->children(first_loop).empty() &&
34✔
640
                          state_.loop_analysis->children(second_loop).empty();
34✔
641

642
        return try_complex_fuse_producer_into_consumer(
34✔
643
            *first_top, *second_top, no_uses_between, domains_match && leaf_loops
34✔
644
        );
34✔
645
    } else {
34✔
646
        return {};
6✔
647
    }
6✔
648
}
40✔
649

650
PatternHandler::MatchResult LoopFusionHandler::try_complex_fuse_producer_into_consumer(
651
    FusionLoopCandidate& first, FusionLoopCandidate& second, bool no_uses_between, bool domains_match
652
) {
34✔
653
    auto outcome = try_fuse_by_access(first, second, domains_match);
34✔
654

655
    if (outcome.fused) {
34✔
656
        if constexpr (DUMP_GRAPHS) {
657
            auto dir = state_.builder.subject().metadata_if_exists("output_dir");
658
            if (dir) {
659
                std::filesystem::path pdir = *dir;
660
                visualizer::DotVisualizer::writeToFile(
661
                    state_.builder.subject(),
662
                    pdir / ("map_fusion_by_domain_pass_" + std::to_string(state_.run) + "_dump_" +
663
                            std::to_string(state_.fused_by_domain_count) + "_" +
664
                            std::to_string(second.loop->element_id()) + ".dot")
665
                );
666
            }
667
        }
668

669
        state_.fused_by_access_count++;
21✔
670
    }
21✔
671

672
    return outcome.pattern_result;
34✔
673
}
34✔
674

675
bool LoopFusionHandler::check_no_overlap(
676
    const StructuredLoop& map, const StructuredLoop& second, const std::unordered_set<std::string>& skipped_containers
677
) {
1✔
678
    auto& first_cand = *state_.fuse_candidates.at(map.element_id());
1✔
679
    auto& second_cand = *state_.fuse_candidates.at(second.element_id());
1✔
680
    for (auto& arg : first_cand.args) {
3✔
681
        if (skipped_containers.contains(arg.first)) {
3✔
682
            return false;
×
683
        }
×
684
    }
3✔
685
    return true;
1✔
686
}
1✔
687

688
bool LoopFusionHandler::
689
    loop_match(FusionLoopCandidate& first, FusionLoopCandidate& second, SymEngine::map_basic_basic& canonical_indvars) {
87✔
690
    if (first.incompatible || second.incompatible) {
87✔
691
        return false;
×
692
    }
×
693

694
    bool lower_match =
87✔
695
        symbolic::eq(first.indvar_boundaries->tight_lower_bound(), second.indvar_boundaries->tight_lower_bound());
87✔
696
    if (!lower_match) {
87✔
697
        return false;
13✔
698
    }
13✔
699
    bool upper_match =
74✔
700
        symbolic::eq(first.indvar_boundaries->tight_upper_bound(), second.indvar_boundaries->tight_upper_bound());
74✔
701
    if (!upper_match) {
74✔
702
        return false;
13✔
703
    }
13✔
704
    auto first_canonicalized_map = SymEngine::subs(first.indvar_boundaries->map(), canonical_indvars);
61✔
705
    bool map_match = symbolic::eq(first_canonicalized_map, second.indvar_boundaries->map());
61✔
706
    if (!map_match) {
61✔
707
        return false;
×
708
    }
×
709

710
    return true;
61✔
711
}
61✔
712

713
void LoopFusionHandler::update_moved_candidate_states(FusionLoopCandidate* top, const symbolic::ExpressionMapping& replace) {
21✔
714
    auto& info = state_.loop_analysis->loop_info_local(top->loop);
21✔
715
    auto& candidates = state_.fuse_candidates;
21✔
716

717
    auto& by_id = state_.loop_analysis->loops_in_pre_order();
21✔
718
    for (auto i = info.loop_id; i <= info.last_child_id; ++i) {
45✔
719
        auto* child_loop = by_id.at(i);
24✔
720
        auto cand_it = candidates.find(child_loop->element_id());
24✔
721
        if (cand_it != candidates.end()) {
24✔
722
            auto& child_cand = *cand_it->second;
24✔
723
            child_cand.replace(replace);
24✔
724
        }
24✔
725
    }
24✔
726
}
21✔
727

728
data_flow::Subset updated_subset(const data_flow::Subset& subset, const symbolic::ExpressionMapping& canonical_indvars) {
95✔
729
    std::vector<symbolic::Expression> updated_subset(subset.size());
95✔
730
    for (auto i = 0; i < subset.size(); i++) {
208✔
731
        updated_subset[i] = symbolic::subs(subset[i], canonical_indvars);
113✔
732
    }
113✔
733
    return std::move(updated_subset);
95✔
734
}
95✔
735

736
void LoopFusionHandler::update_candidate_state(
737
    ControlFlowNode* first_top,
738
    FusionLoopCandidate* first_current,
739
    FusionLoopCandidate* second_current,
740
    const symbolic::ExpressionMapping& canonical_indvars
741
) {
21✔
742
    // merge metadata from first -> second
743
    update_moved_candidate_states(first_current, canonical_indvars);
21✔
744

745
    update_candidate_args_up(
21✔
746
        first_top,
21✔
747
        first_current,
21✔
748
        second_current,
21✔
749
        [&](const std::string& name, FusionArg& source_arg, std::unordered_map<std::string, FusionArg>& target_args) {
130✔
750
            // skip the induction variable, we already know those match and they would not be useful to track for
751
            // the next levels up
752
            if (first_current->loop->indvar()->get_name() != name) {
130✔
753
                auto it = target_args.find(name);
127✔
754
                if (it != target_args.end()) {
127✔
755
                    auto& second_arg = it->second;
66✔
756
                    second_arg.local_access.merge_into(source_arg.local_access);
66✔
757
                    second_arg.nested_access.merge_into(source_arg.nested_access);
66✔
758
                    second_arg.arg.merge(source_arg.arg);
66✔
759
                } else {
66✔
760
                    auto [it, fresh] = target_args.emplace(name, source_arg); // copy over
61✔
761
                }
61✔
762
            }
127✔
763
        }
130✔
764
    );
21✔
765
}
21✔
766

767
void LoopFusionHandler::update_candidate_args_up(
768
    ControlFlowNode* first_top,
769
    FusionLoopCandidate* first_current,
770
    FusionLoopCandidate* second_current,
771
    const std::function<
772
        void(const std::string& name, FusionArg& source_arg, std::unordered_map<std::string, FusionArg>& target_args)>&
773
        action
774
) {
42✔
775
    auto terminate_at = state_.loop_analysis->parent_loop(first_top);
42✔
776
    do {
61✔
777
        auto& second_args = second_current->args;
61✔
778
        for (auto& [name, arg] : first_current->args) {
236✔
779
            action(name, arg, second_args);
236✔
780
        }
236✔
781
        // assumptions depend on loop and outer scopes, so they remain identical for the loop that is used as basis
782
        // (that the other gets inlined into) for now we always inline into 2nd.
783

784
        first_current = state_.get_parent(*first_current);
61✔
785
        second_current = state_.get_parent(*second_current);
61✔
786
    } while (first_current && first_current->loop != terminate_at);
61✔
787
}
42✔
788

789
LoopFusionHandler::InOutCheckResult LoopFusionHandler::check_ins_outs(
790
    const FusionLoopCandidate& first_candidate,
791
    const FusionLoopCandidate& second_candidate,
792
    symbolic::ExpressionMapping& canonical_indvars,
793
    bool local_not_nested,
794
    bool only_no_overlap
795
) {
121✔
796
    auto& first_args = first_candidate.args;
121✔
797
    auto& second_args = second_candidate.args;
121✔
798

799
    return check_ins_outs(first_args, second_args, canonical_indvars, local_not_nested, only_no_overlap);
121✔
800
}
121✔
801

802
LoopFusionHandler::InOutCheckResult LoopFusionHandler::check_ins_outs(
803
    const std::unordered_map<std::string, FusionArg>& first_args,
804
    const std::unordered_map<std::string, FusionArg>& second_args,
805
    symbolic::ExpressionMapping& canonical_indvars,
806
    bool local_not_nested,
807
    bool only_no_overlap
808
) {
121✔
809
    bool overlap = false;
121✔
810
    bool no_conflicts = true;
121✔
811
    bool subset_mismatch = false;
121✔
812

813
    for (auto& [name, prod_meta] : first_args) {
496✔
814
        auto cons_it = second_args.find(name);
496✔
815
        if (cons_it != second_args.end()) {
496✔
816
            auto& cons_meta = cons_it->second;
285✔
817
            if (prod_meta.arg.is_input && cons_meta.arg.is_output) {
285✔
818
                // there could be conflicts here. So for now, abort.
819
                // Future Work: if both were to strictly match indvars (or never match other iterations),
820
                // it would never be a conflict
821
                overlap = true;
7✔
822
                no_conflicts = false;
7✔
823
                continue;
7✔
824
            } else if (prod_meta.arg.is_output && cons_meta.arg.is_input) {
278✔
825
                overlap = true;
116✔
826
                auto& prod_collected_accesses = local_not_nested ? prod_meta.local_access : prod_meta.nested_access;
116✔
827
                auto& cons_collected_accesses = local_not_nested ? cons_meta.local_access : cons_meta.nested_access;
116✔
828
                if (prod_collected_accesses.subset_conflicts_with(cons_collected_accesses, canonical_indvars)) {
116✔
829
                    subset_mismatch = true;
33✔
830
                    // conflict (between vars unproved, fuse-by-access might find a solution with conflicting subsets)
831
                    continue;
33✔
832
                }
33✔
833
            } else if (prod_meta.arg.is_ptr && cons_meta.arg.is_ptr && prod_meta.arg.is_explicit_input &&
162✔
834
                       cons_meta.arg.is_explicit_input) {
162✔
835
                overlap = true;
8✔
836
                continue;
8✔
837
            }
8✔
838
        }
285✔
839
    }
496✔
840

841
    if (only_no_overlap && overlap) {
121✔
842
        no_conflicts = false;
4✔
843
    }
4✔
844

845
    return {no_conflicts, overlap, subset_mismatch};
121✔
846
}
121✔
847

848
void LoopFusionHandler::update_fused_seq(Sequence& sequence, const symbolic::ExpressionMapping& replacements) {
21✔
849
    sequence.replace(replacements);
21✔
850
}
21✔
851

852
/**
853
 * Merge a newly observed access (its `subset` and `not_understood` flag) into the FusionArg `into`.
854
 * If `into` already tracks a different subset, we can no longer describe the access with a single
855
 * subset and mark it not_understood. Returns true if `into` was modified.
856
 */
857
bool FusionArgCommonAccesses::merge_into(
858
    Block* block,
859
    const std::optional<data_flow::Subset>& subset,
860
    bool not_understood,
861
    bool write_not_read,
862
    const symbolic::ExpressionMapping* lower_indvars
863
) {
528✔
864
    bool updated = merge_subset(subset, not_understood, lower_indvars);
528✔
865

866
    if (write_not_read) {
528✔
867
        updated |= wr_block.merge_into(block, false);
244✔
868
    } else {
284✔
869
        updated |= rd_block.merge_into(block, false);
284✔
870
    }
284✔
871

872
    return updated;
528✔
873
}
528✔
874

875
bool FusionArgCommonBlock::merge_into(Block* other_block, bool other_conflict) {
2,028✔
876
    bool updated = false;
2,028✔
877

878
    if (other_conflict && !this->block_conflict) {
2,028✔
879
        this->block_conflict = true;
1✔
880
        updated = true;
1✔
881
    } else if (!this->block_conflict && this->common_block && other_block) {
2,027✔
882
        if (this->common_block != other_block) {
123✔
883
            this->block_conflict = true;
12✔
884
            this->common_block = nullptr;
12✔
885
            updated = true;
12✔
886
        }
12✔
887
    } else if (!this->block_conflict && !this->common_block && other_block) {
1,904✔
888
        this->common_block = other_block;
705✔
889
        updated = true;
705✔
890
    }
705✔
891

892
    return updated;
2,028✔
893
}
2,028✔
894

895
bool FusionArgCommonBlock::merge_into(const FusionArgCommonBlock& other) {
1,500✔
896
    return merge_into(other.common_block, other.block_conflict);
1,500✔
897
}
1,500✔
898

899
bool FusionArgCommonAccesses::
900
    merge_into(const FusionArgCommonAccesses& other, const symbolic::ExpressionMapping* lower_indvars) {
750✔
901
    bool update = merge_subset(other.common_subset, other.subsets_conflict, lower_indvars);
750✔
902
    update |= wr_block.merge_into(other.wr_block);
750✔
903
    update |= rd_block.merge_into(other.rd_block);
750✔
904
    return update;
750✔
905
}
750✔
906

907
bool FusionArgCommonAccesses::subset_conflicts_with(
908
    const FusionArgCommonAccesses& other_common_acceses, symbolic::ExpressionMapping& canonical_indvars
909
) const {
116✔
910
    if (subsets_conflict || other_common_acceses.subsets_conflict) {
116✔
911
        return true;
5✔
912
    }
5✔
913

914
    if (common_subset.has_value() && other_common_acceses.common_subset.has_value()) {
111✔
915
        if (!symbolic::vectors_of_expressions_match(
67✔
916
                common_subset.value(), other_common_acceses.common_subset.value(), canonical_indvars
67✔
917
            )) {
67✔
918
            return true;
28✔
919
        }
28✔
920
    }
67✔
921

922
    return false;
83✔
923
}
111✔
924

925
bool FusionArgCommonAccesses::merge_subset(
926
    const std::optional<data_flow::Subset>& subset,
927
    bool not_understood,
928
    const symbolic::ExpressionMapping* lower_indvars
929
) {
1,278✔
930
    bool updated = false;
1,278✔
931

932
    std::optional<data_flow::Subset> mapped_subset_holder;
1,278✔
933
    const data_flow::Subset* mapped_subset = nullptr;
1,278✔
934
    if (subset.has_value()) {
1,278✔
935
        if (lower_indvars) {
819✔
936
            mapped_subset_holder = symbolic::substitute(subset.value(), *lower_indvars);
240✔
937
            mapped_subset = &mapped_subset_holder.value();
240✔
938
        } else {
579✔
939
            mapped_subset = &subset.value();
579✔
940
        }
579✔
941
    }
819✔
942

943
    if (common_subset.has_value() && mapped_subset &&
1,278✔
944
        !symbolic::vectors_of_expressions_match(common_subset.value(), *mapped_subset)) {
1,278✔
945
        if (!subsets_conflict) {
17✔
946
            subsets_conflict = true;
14✔
947
            updated = true;
14✔
948
        }
14✔
949
    } else if (!common_subset.has_value() && mapped_subset && !subsets_conflict) {
1,261✔
950
        common_subset = *mapped_subset;
636✔
951
        updated = true;
636✔
952
    }
636✔
953
    if (not_understood && !subsets_conflict) {
1,278✔
954
        subsets_conflict = true;
3✔
955
        updated = true;
3✔
956
    }
3✔
957
    return updated;
1,278✔
958
}
1,278✔
959

960
bool FusionArg::saw_access_locally() const {
26✔
961
    return local_access.subsets_conflict || local_access.common_subset.has_value();
26✔
962
}
26✔
963

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

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

968
void FusionLoopCandidate::replace(const symbolic::ExpressionMapping& mapping) {
24✔
969
    for (auto& [name, arg] : args) {
95✔
970
        if (arg.local_access.common_subset.has_value()) {
95✔
971
            arg.local_access.common_subset = updated_subset(arg.local_access.common_subset.value(), mapping);
45✔
972
        }
45✔
973
        if (arg.nested_access.common_subset.has_value()) {
95✔
974
            arg.nested_access.common_subset = updated_subset(arg.nested_access.common_subset.value(), mapping);
50✔
975
        }
50✔
976
    }
95✔
977
    symbolic::substitute(assumptions, mapping);
24✔
978

979
    if constexpr (DUMP_ASSUMPTIONS) {
980
        std::cout << "Updated #" << this->loop->element_id() << " to:" << std::endl;
981
        std::cout << this->assumptions << std::endl;
982
    }
983
}
24✔
984

985
NeighboringPatternVisitor::NeighboringPatternVisitor(PatternHandler& handler) : handler_(handler) {}
57✔
986

987
bool NeighboringPatternVisitor::visit(sdfg::structured_control_flow::Sequence& node) {
218✔
988
    if (node.size() < 2) { // impossible to find a match, just descend into it
218✔
989
        return ActualStructuredSDFGVisitor::visit(node);
112✔
990
    }
112✔
991

992
    // Iterate over sequence looking for consecutive (StructuredLoop, StructuredLoop) pairs
993
    size_t i = 0;
106✔
994
    structured_control_flow::ControlFlowNode* override_last = nullptr;
106✔
995
    while (i < node.size()) {
341✔
996
        auto& child_node = node.at(i);
235✔
997
        auto* first = dyn_cast<structured_control_flow::StructuredLoop*>(&child_node);
235✔
998
        if (!first) {
235✔
999
            i++;
99✔
1000
            dispatch(child_node);
99✔
1001
            continue;
99✔
1002
        }
99✔
1003
        if (first->root().size() == 0) {
136✔
1004
            i++;
×
1005
            continue;
×
1006
        }
×
1007

1008
        StructuredLoop* second = nullptr;
136✔
1009

1010
        if (i + 1 < node.size()) {
136✔
1011
            second = dyn_cast<structured_control_flow::StructuredLoop*>(&node.at(i + 1));
66✔
1012
            if (second) {
66✔
1013
                if (second->root().size() == 0) {
62✔
1014
                    i++;
×
1015
                    continue;
×
1016
                }
×
1017

1018
                auto result = handler_.match(*first, *second, true);
62✔
1019

1020
                if (!result.removed_first) {
62✔
1021
                    dispatch(child_node);
39✔
1022
                }
39✔
1023
                if (result.visit_second_body) {
62✔
1024
                    auto* second_updated_child = result.second_root_replacement ? result.second_root_replacement
4✔
1025
                                                                                : second;
4✔
1026
                    dispatch(*second_updated_child);
4✔
1027
                }
4✔
1028
                if (result.removed_first) {
62✔
1029
                    // do not increment i, we can use at as next firs
1030
                    continue;
23✔
1031
                }
23✔
1032
            } else if (i + 2 < node.size()) {
62✔
1033
                auto* mid_block = dyn_cast<structured_control_flow::Block*>(&node.at(i + 1));
4✔
1034
                bool skippable = false;
4✔
1035
                std::unordered_set<std::string> skipped_containers;
4✔
1036
                if (mid_block) {
4✔
1037
                    if (mid_block->dataflow().nodes().empty()) {
4✔
1038
                        skippable = true;
1✔
1039
                    } else if (mid_block->is_a_library_node<stdlib::MallocNode>()) {
3✔
1040
                        for (auto& data_flow_node : mid_block->dataflow().nodes()) {
×
1041
                            if (auto* container = dynamic_cast<data_flow::AccessNode*>(&data_flow_node)) {
×
1042
                                skipped_containers.emplace(container->data());
×
1043
                            }
×
1044
                        }
×
1045
                        skippable = true;
×
1046
                    }
×
1047
                }
4✔
1048
                if (skippable) {
4✔
1049
                    second = dyn_cast<structured_control_flow::StructuredLoop*>(&node.at(i + 2));
1✔
1050
                    if (second) {
1✔
1051
                        if (second->root().size() == 0) {
1✔
1052
                            i += 2;
×
1053
                            continue;
×
1054
                        }
×
1055

1056
                        if (!handler_.check_no_overlap(*first, *second, skipped_containers)) {
1✔
1057
                            i += 2;
×
1058
                            continue;
×
1059
                        }
×
1060

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

1063
                        if (!result.removed_first) {
1✔
1064
                            dispatch(child_node);
×
1065
                        }
×
1066
                        if (result.visit_second_body) {
1✔
1067
                            auto* second_updated_child = result.second_root_replacement ? result.second_root_replacement
×
1068
                                                                                        : second;
×
1069
                            dispatch(*second_updated_child);
×
1070
                        }
×
1071
                        if (result.removed_first) {
1✔
1072
                            i += 1; // skip the block, retry with second as next first
1✔
1073
                            continue;
1✔
1074
                        }
1✔
1075
                        // we visited [first, skipped, second] successfully, without shifting indices, move to second as
1076
                        // new first
1077
                        i += 2;
×
1078
                        continue;
×
1079
                    } else {
1✔
1080
                        // we know i+1 is worthless, so skip it
1081
                        i += 2;
×
1082
                        continue;
×
1083
                    }
×
1084
                }
1✔
1085
            }
4✔
1086
        } else {
70✔
1087
            dispatch(child_node);
70✔
1088
        }
70✔
1089
        i++;
112✔
1090
    }
112✔
1091

1092
    return true;
106✔
1093
}
218✔
1094

1095
} // 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