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

daisytuner / docc / 30614148740

31 Jul 2026 07:49AM UTC coverage: 64.691% (+0.2%) from 64.531%
30614148740

push

github

web-flow
Adding ProducerIntoConsumer MapFusion into the new, recursive MapFusionPass (#873)

Refactored the code of MapFusion Transform into MapFusionByAccessWorker.

The old transform still exists for its use as standalone transformation for now and also to run the old pass to cover everything not yet implemented in the new ByAccessWorker.

Refactored MapFusionByDomain into LoopFusionPass, the sequential & recursive loop-fusion logic into a base that handles the existing by-domain fusion and the by-access fusion, separately disablable.

For this, the existing per-loop caches of LoopFusionPass have been extended to contain the data needed by by-access fusion (assumptions, non-map loops and more details about subsets and locations of accesses).

LoopFusionPass still maintains these caches as fusions happen, so it never needs to clear any analysis.

For now, the MapFusionByAccessWorker has not been verified and skips any ConsumerIntoProducer fusions, as that is a greater change to how the cached data needs to be updated and its also a smaller portion of compile-time.

The MapFusionByAccessWorker was refactored to use the FusionLoopCandidate structure to access any cached data. It can be used as a basis for a new Transformation, that simply creates these per-loop structures on the fly, so that Transform and efficient pass can share code again.
The ByAccessWorker was also optimized to support per "fusion container" selection of replacing memory accesses with local transients and to consider the domain-equality checks already done by the NewMapFusionPass.

If the domains match, we can leave in the indirect writes in, which makes the fused code is a valid substitute for the original loop, avoiding the need for any loop-duplication.

Elliding same-subset indirect write-read patterns in the same block or successive blocks is a far easier problem to solve, then leaving the original loop lying around and hoping that it can be proven completely dead and removed by later ... (continued)

1388 of 1894 new or added lines in 13 files covered. (73.28%)

45088 of 69697 relevant lines covered (64.69%)

725.27 hits per line

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

79.59
/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
NEW
26
) {
×
NEW
27
    if (replacements) {
×
NEW
28
        return symbolic::vectors_of_expressions_match(a, b, *replacements);
×
NEW
29
    } else {
×
NEW
30
        return symbolic::vectors_of_expressions_match(a, b);
×
NEW
31
    }
×
NEW
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() {
268✔
49
        if (loop_stack_.empty()) {
268✔
50
            return nullptr;
7✔
51
        }
7✔
52
        return &loop_stack_.back();
261✔
53
    }
268✔
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
NEW
61
    ) {
×
NEW
62
        bool updated = false;
×
NEW
63
        auto& target_access = local_access ? into.local_access : into.nested_access;
×
NEW
64

×
NEW
65
        if (target_access.common_subset.has_value() && subset.has_value() &&
×
NEW
66
            !vectors_of_expressions_match(target_access.common_subset.value(), subset.value(), lower_indvars)) {
×
NEW
67
            if (!target_access.subsets_conflict) {
×
NEW
68
                target_access.subsets_conflict = true;
×
NEW
69
                updated = true;
×
NEW
70
            }
×
NEW
71
        } else if (!target_access.common_subset.has_value() && subset.has_value() && !target_access.subsets_conflict) {
×
NEW
72
            target_access.common_subset = subset.value();
×
NEW
73
            updated = true;
×
NEW
74
        }
×
NEW
75
        if (not_understood && !target_access.subsets_conflict) {
×
NEW
76
            target_access.subsets_conflict = true;
×
NEW
77
            updated = true;
×
NEW
78
        }
×
NEW
79
        return updated;
×
NEW
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) {}
54✔
89

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

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

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

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

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

190
        parent.fusion_candidate.nested_incompatible |= current.fusion_candidate.incompatible |
60✔
191
                                                       current.fusion_candidate.nested_incompatible;
60✔
192
    }
60✔
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 {
129✔
201
        auto current = get_current_loop();
129✔
202
        if (current && edge.is_dst_pointed_to_write()) {
129✔
203
            found_indirect_arg_access(container, edge, block, current, true);
121✔
204
        }
121✔
205
    }
129✔
NEW
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 {
139✔
217
        auto current = get_current_loop();
139✔
218
        if (current && (edge.is_src_address_leak() || edge.is_src_pointed_to_address_leak(sdfg_.type(container)))) {
139✔
NEW
219
            current->fusion_candidate.aliasing_encountered();
×
220
        } else if (current && edge.is_src_pointed_to_read()) {
139✔
221
            found_indirect_arg_access(container, edge, block, current, false);
136✔
222
        }
136✔
223
    }
139✔
224
    void use_as_symbol_write(
225
        const symbolic::Symbol& container, const ControlFlowNode* node, const Element* user, SymbolWriteLocation loc
226
    ) override {}
172✔
227
};
228

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

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

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

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

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

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

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

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

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

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

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

298
    State state(builder, analysis_manager, std::move(loop_ana));
54✔
299

300
    auto& assumption_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
54✔
301
    auto& arguments_analysis = analysis_manager.get<analysis::ArgumentsAnalysis>();
54✔
302

303
    for (auto* control_flow_node : state.loop_analysis->loops()) {
172✔
304
        if (auto* loop = dyn_cast<StructuredLoop*>(control_flow_node)) {
172✔
305
            auto& indvar = loop->indvar();
172✔
306
            auto& assumpts = assumption_analysis.get(loop->root(), true);
172✔
307
            auto* indvar_boundaries = find_indvar_boundaries(indvar, assumpts);
172✔
308

309
            std::unique_ptr<FusionLoopCandidate> cand;
172✔
310

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

323
    LoopIndirectAccessFinder indirect_access_finder(builder.subject(), *state.loop_analysis, state.fuse_candidates);
54✔
324
    indirect_access_finder.dispatch(builder.subject().root());
54✔
325

326
    const std::string* dir = nullptr;
54✔
327
    if (DUMP_LOOP_INFOS) {
54✔
NEW
328
        dir = builder.subject().metadata_if_exists("output_dir");
×
NEW
329
        if (dir) {
×
NEW
330
            state.loop_analysis->dump_to_file(std::filesystem::path(*dir) / "loop_infos.pre-fusion.json");
×
NEW
331
        }
×
NEW
332
    }
×
333

334
    LoopFusionHandler handler(config_, state);
54✔
335

336
    NeighboringPatternVisitor v(handler);
54✔
337
    v.dispatch(builder.subject().root());
54✔
338

339
    if (dir) {
54✔
NEW
340
        state.loop_analysis->dump_to_file(std::filesystem::path(*dir) / "loop_infos.post-fusion.json");
×
NEW
341
    }
×
342

343
    return state.total_fused_count();
54✔
344
}
54✔
345

346
const symbolic::Assumption* LoopFusionPass::
347
    find_indvar_boundaries(const symbolic::Symbol& indvar, const symbolic::Assumptions& assumptions) {
172✔
348
    auto it = assumptions.find(indvar);
172✔
349
    if (it != assumptions.end()) {
172✔
350
        return &it->second;
172✔
351
    }
172✔
352

NEW
353
    return nullptr;
×
354
}
172✔
355

356
LoopFusionHandler::LoopFusionHandler(const LoopFusionConfig& config, LoopFusionPass::State& state)
357
    : config_(config), state_(state), LoopFusionByAccessWorker(config.allow_init_hoist) {}
54✔
358

359
PatternHandler::MatchResult LoopFusionHandler::fuse_contents(
360
    ControlFlowNode* first_top,
361
    FusionLoopCandidate* first_current,
362
    FusionLoopCandidate* second_innermost,
363
    const symbolic::ExpressionMapping& indvar_mapping,
364
    Sequence& target_root,
365
    bool can_remove_original
366
) {
19✔
367
    auto first_elem_id = first_current->loop->element_id();
19✔
368

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

379
    std::optional<std::unordered_map<const ControlFlowNode*, const ControlFlowNode*>> copy_mapping;
19✔
380
    if (can_remove_original) {
19✔
381
        state_.builder.move_children(first_current->loop->root(), *append_root);
19✔
382
    } else {
19✔
NEW
383
        deepcopy::StructuredSDFGDeepCopy copier(state_.builder, *append_root, first_current->loop->root());
×
NEW
384
        copy_mapping = copier.insert();
×
NEW
385
    }
×
386

387
    update_fused_seq(*append_root, indvar_mapping);
19✔
388

389
    if (append_root != &target_root) { // need to fixup / flatten the copied sequence into the target sequence
19✔
390
        state_.builder.move_children(*append_root, target_root, 0);
19✔
391
        state_.builder.remove_from_parent(*append_root);
19✔
392
        append_root = nullptr;
19✔
393
    }
19✔
394

395
    update_candidate_state(first_top, first_current, second_innermost, indvar_mapping);
19✔
396

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

422
    bool removed_first = false;
19✔
423
    if (can_remove_original) {
19✔
424
        state_.loop_analysis->removed_loop(first_top);
19✔
425
        state_.builder.remove_from_parent(*first_top);
19✔
426
        removed_first = true;
19✔
427
    }
19✔
428

429
    if constexpr (DUMP_GRAPHS) {
430
        auto dir = state_.builder.subject().metadata_if_exists("output_dir");
431
        if (dir) {
432
            std::filesystem::path pdir = *dir;
433
            visualizer::DotVisualizer::writeToFile(
434
                state_.builder.subject(),
435
                pdir / ("map_fusion_by_domain_pass_dump_" + std::to_string(state_.fused_by_domain_count) + "_" +
436
                        std::to_string(second_innermost->loop->element_id()) + ".dot")
437
            );
438
        }
439
    }
440

441
    state_.fused_by_domain_count++;
19✔
442

443
    // if there are further loops inside the now fused body, visit those as well
444
    return {.removed_first = removed_first, .visit_second_body = keep_visiting_second};
19✔
445
}
19✔
446

447
analysis::LoopAnalysis& LoopFusionHandler::get_loop_analysis() { return *state_.loop_analysis; }
125✔
448

449
FusionLoopCandidate* LoopFusionHandler::get_fuse_candidate(StructuredLoop& loop) {
102✔
450
    return state_.fuse_candidates.at(loop.element_id()).get();
102✔
451
}
102✔
452

453
builder::StructuredSDFGBuilder& LoopFusionHandler::builder() { return state_.builder; }
22✔
454

455
void LoopFusionHandler::update_copied_leaf_contents_from_first_to_second(
456
    const Plan& plan, FusionLoopCandidate* first_current, FusionLoopCandidate* second_current
457
) {
22✔
458
    auto first_top = &plan.first;
22✔
459

460

461
    auto& fusion_regs = plan.fusion_candidates_;
22✔
462

463
    std::unordered_map<std::string, const loop_fusion::FusionRegCandidate*> cand_map;
22✔
464
    for (const auto& cand : fusion_regs) {
26✔
465
        cand_map[cand.container] = &cand;
26✔
466
    }
26✔
467

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

486
PatternHandler::MatchResult LoopFusionHandler::match(StructuredLoop& first, StructuredLoop& second, bool no_uses_between) {
60✔
487
    auto first_it = state_.fuse_candidates.find(first.element_id());
60✔
488
    if (first_it == state_.fuse_candidates.end()) {
60✔
NEW
489
        return {};
×
NEW
490
    }
×
491
    FusionLoopCandidate* first_current = nullptr;
60✔
492
    FusionLoopCandidate* first_top = first_it->second.get();
60✔
493
    FusionLoopCandidate* first_next = first_top;
60✔
494

495
    auto second_it = state_.fuse_candidates.find(second.element_id());
60✔
496
    if (second_it == state_.fuse_candidates.end()) {
60✔
NEW
497
        return {};
×
NEW
498
    }
×
499
    FusionLoopCandidate* second_current = nullptr;
60✔
500
    FusionLoopCandidate* second_top = second_it->second.get();
60✔
501
    FusionLoopCandidate* second_next = second_top;
60✔
502

503
    SymEngine::map_basic_basic indvar_mapping;
60✔
504
    int current_level = -1;
60✔
505
    int last_matched_level = -1;
60✔
506
    auto first_info = state_.loop_analysis->loop_info(&first);
60✔
507
    auto second_info = state_.loop_analysis->loop_info(&second);
60✔
508

509
    // Skip if both have side effects
510
    if (first_info.has_side_effects && second_info.has_side_effects) {
60✔
NEW
511
        return {};
×
NEW
512
    }
×
513

514
    int32_t first_max_stack_depth = first_info.map_stack_depth - 1;
60✔
515
    int32_t second_max_stack_depth = second_info.map_stack_depth - 1;
60✔
516
    bool more_first = true;
60✔
517
    bool more_second = true;
60✔
518
    bool fusing_option = first_next->is_by_domain_candidate && second_next->is_by_domain_candidate;
60✔
519
    bool domains_match = true;
60✔
520
    bool both_map = first_next->is_map && second_next->is_map;
60✔
521

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

526
    // descend evenly through candidates for fusion by domain.
527
    do {
85✔
528
        ++current_level;
85✔
529

530
        if (fusing_option) {
85✔
531
            auto insertion = indvar_mapping.insert({first_next->loop->indvar(), second_next->loop->indvar()});
80✔
532
            assert(insertion.second);
80✔
533
            fusing_option = this->loop_match(*first_next, *second_next, indvar_mapping);
80✔
534
            if (!fusing_option) {
80✔
535
                domains_match = false;
27✔
536
                indvar_mapping.erase(insertion.first);
27✔
537
            }
27✔
538
        } else {
80✔
539
            domains_match = false;
5✔
540
        }
5✔
541
        auto res = this->check_ins_outs(*first_next, *second_next, indvar_mapping, true, !both_map);
85✔
542
        if (!res.no_conflicts) {
85✔
543
            // will occur on data-dependencies (from consumer to producer) or on subset mismatches
544
            fusing_option = false;
9✔
545
        }
9✔
546
        if (first_max_stack_depth != second_max_stack_depth && !res.overlap) { // heuristic: do not fuse if there is no
85✔
547
                                                                               // memory shared between uneven
548
                                                                               // candidates
NEW
549
            return {};
×
NEW
550
        }
×
551
        if (res.subset_mismatch) { // If subsets mismatch on any level, we cannot guarantee correctness without much
85✔
552
                                   // more checks, so fusion-by-domain is out
553
            break;
22✔
554
        }
22✔
555

556
        if (fusing_option) {
63✔
557
            last_matched_level = current_level;
43✔
558
            first_current = first_next;
43✔
559
            second_current = second_next;
43✔
560
        }
43✔
561
        more_first = current_level < first_max_stack_depth;
63✔
562
        more_second = current_level < second_max_stack_depth;
63✔
563
        if (more_first) {
63✔
564
            first_next = state_.get_next_level_map_stack(*first_next);
29✔
565
        }
29✔
566
        if (more_second) {
63✔
567
            second_next = state_.get_next_level_map_stack(*second_next);
29✔
568
        }
29✔
569
    } while (more_first && more_second);
63✔
570

571
    if (last_matched_level >= 0) {
60✔
572
        // we found a match for fusion-by-domain. In case there are nested loops we still need to verify they don't
573
        // conflict as well
574
        auto nested_check = this->check_ins_outs(*first_current, *second_current, indvar_mapping, false, !both_map);
30✔
575

576
        if (!nested_check.no_conflicts) {
30✔
NEW
577
            DEBUG_PRINTLN(
×
NEW
578
                "Should not have discovered fusion conflicts this late:"
×
NEW
579
                << last_matched_level + 1 << " lvls): #" << first.element_id() << " | #"
×
NEW
580
                << first_current->loop->element_id() << ", #" << second.element_id() << " | #"
×
NEW
581
                << second_current->loop->element_id()
×
NEW
582
            );
×
NEW
583
            return {};
×
NEW
584
        }
×
585
        if (!nested_check.subset_mismatch && config_.map_fusion_by_domain) {
30✔
586
            DEBUG_PRINTLN(
19✔
587
                "Fusing loop stack by-domain (" << last_matched_level + 1 << " lvls): #" << first.element_id() << " | #"
19✔
588
                                                << first_current->loop->element_id() << " -> #" << second.element_id()
19✔
589
                                                << " | #" << second_current->loop->element_id()
19✔
590
            );
19✔
591

592
            auto& target_root = second_current->loop->root();
19✔
593
            return fuse_contents(&first, first_current, second_current, indvar_mapping, target_root, no_uses_between);
19✔
594
        }
19✔
595
    }
30✔
596

597
    if (config_.map_fusion_by_access) {
41✔
598
        // we did not find an absolute blocker for fusing, but simple fusion by domain also did not work out, so try the
599
        // fusion-by-access
600
        StructuredLoop *first_loop, *second_loop;
37✔
601
        if (last_matched_level >= 0) {
37✔
602
            first_loop = first_current->loop;
7✔
603
            second_loop = second_current->loop;
7✔
604
        } else {
30✔
605
            first_loop = &first;
30✔
606
            second_loop = &second;
30✔
607
        }
30✔
608
        bool leaf_loops = state_.loop_analysis->children(first_loop).empty() &&
37✔
609
                          state_.loop_analysis->children(second_loop).empty();
37✔
610

611
        return try_complex_fuse_producer_into_consumer(
37✔
612
            *first_top, *second_top, no_uses_between, domains_match && leaf_loops
37✔
613
        );
37✔
614
    } else {
37✔
615
        return {};
4✔
616
    }
4✔
617
}
41✔
618

619
PatternHandler::MatchResult LoopFusionHandler::try_complex_fuse_producer_into_consumer(
620
    FusionLoopCandidate& first, FusionLoopCandidate& second, bool no_uses_between, bool domains_match
621
) {
37✔
622
    auto outcome = try_fuse_by_access(first, second, domains_match);
37✔
623

624
    if (outcome.fused) {
37✔
625
        state_.fused_by_access_count++;
22✔
626
    }
22✔
627

628
    return outcome.pattern_result;
37✔
629
}
37✔
630

631
bool LoopFusionHandler::check_no_overlap(
632
    const StructuredLoop& map, const StructuredLoop& second, const std::unordered_set<std::string>& skipped_containers
633
) {
1✔
634
    auto& first_cand = *state_.fuse_candidates.at(map.element_id());
1✔
635
    auto& second_cand = *state_.fuse_candidates.at(second.element_id());
1✔
636
    for (auto& arg : first_cand.args) {
3✔
637
        if (skipped_containers.contains(arg.first)) {
3✔
NEW
638
            return false;
×
NEW
639
        }
×
640
    }
3✔
641
    return true;
1✔
642
}
1✔
643

644
bool LoopFusionHandler::
645
    loop_match(FusionLoopCandidate& first, FusionLoopCandidate& second, SymEngine::map_basic_basic& canonical_indvars) {
80✔
646
    if (first.incompatible || second.incompatible) {
80✔
NEW
647
        return false;
×
NEW
648
    }
×
649

650
    bool lower_match =
80✔
651
        symbolic::eq(first.indvar_boundaries->tight_lower_bound(), second.indvar_boundaries->tight_lower_bound());
80✔
652
    if (!lower_match) {
80✔
653
        return false;
13✔
654
    }
13✔
655
    bool upper_match =
67✔
656
        symbolic::eq(first.indvar_boundaries->tight_upper_bound(), second.indvar_boundaries->tight_upper_bound());
67✔
657
    if (!upper_match) {
67✔
658
        return false;
14✔
659
    }
14✔
660
    auto first_canonicalized_map = SymEngine::subs(first.indvar_boundaries->map(), canonical_indvars);
53✔
661
    bool map_match = symbolic::eq(first_canonicalized_map, second.indvar_boundaries->map());
53✔
662
    if (!map_match) {
53✔
NEW
663
        return false;
×
NEW
664
    }
×
665

666
    return true;
53✔
667
}
53✔
668

669
void LoopFusionHandler::update_moved_candidate_states(FusionLoopCandidate* top, const symbolic::ExpressionMapping& replace) {
19✔
670
    auto& info = state_.loop_analysis->loop_info_local(top->loop);
19✔
671
    auto& candidates = state_.fuse_candidates;
19✔
672

673
    auto& by_id = state_.loop_analysis->loops_in_pre_order();
19✔
674
    for (auto i = info.loop_id; i <= info.last_child_id; ++i) {
42✔
675
        auto* child_loop = by_id.at(i);
23✔
676
        auto cand_it = candidates.find(child_loop->element_id());
23✔
677
        if (cand_it != candidates.end()) {
23✔
678
            auto& child_cand = *cand_it->second;
23✔
679
            child_cand.replace(replace);
23✔
680
        }
23✔
681
    }
23✔
682
}
19✔
683

684
data_flow::Subset updated_subset(const data_flow::Subset& subset, const symbolic::ExpressionMapping& canonical_indvars) {
90✔
685
    std::vector<symbolic::Expression> updated_subset(subset.size());
90✔
686
    for (auto i = 0; i < subset.size(); i++) {
198✔
687
        updated_subset[i] = symbolic::subs(subset[i], canonical_indvars);
108✔
688
    }
108✔
689
    return std::move(updated_subset);
90✔
690
}
90✔
691

692
void LoopFusionHandler::update_candidate_state(
693
    ControlFlowNode* first_top,
694
    FusionLoopCandidate* first_current,
695
    FusionLoopCandidate* second_current,
696
    const symbolic::ExpressionMapping& canonical_indvars
697
) {
19✔
698
    // merge metadata from first -> second
699
    update_moved_candidate_states(first_current, canonical_indvars);
19✔
700

701
    update_candidate_args_up(
19✔
702
        first_top,
19✔
703
        first_current,
19✔
704
        second_current,
19✔
705
        [&](const std::string& name, FusionArg& source_arg, std::unordered_map<std::string, FusionArg>& target_args) {
116✔
706
            // skip the induction variable, we already know those match and they would not be useful to track for
707
            // the next levels up
708
            if (first_current->loop->indvar()->get_name() != name) {
116✔
709
                auto it = target_args.find(name);
113✔
710
                if (it != target_args.end()) {
113✔
711
                    auto& second_arg = it->second;
59✔
712
                    second_arg.local_access.merge_into(source_arg.local_access);
59✔
713
                    second_arg.nested_access.merge_into(source_arg.nested_access);
59✔
714
                    second_arg.arg.merge(source_arg.arg);
59✔
715
                } else {
59✔
716
                    auto [it, fresh] = target_args.emplace(name, source_arg); // copy over
54✔
717
                }
54✔
718
            }
113✔
719
        }
116✔
720
    );
19✔
721
}
19✔
722

723
void LoopFusionHandler::update_candidate_args_up(
724
    ControlFlowNode* first_top,
725
    FusionLoopCandidate* first_current,
726
    FusionLoopCandidate* second_current,
727
    const std::function<
728
        void(const std::string& name, FusionArg& source_arg, std::unordered_map<std::string, FusionArg>& target_args)>&
729
        action
730
) {
41✔
731
    auto terminate_at = state_.loop_analysis->parent_loop(first_top);
41✔
732
    do {
58✔
733
        auto& second_args = second_current->args;
58✔
734
        for (auto& [name, arg] : first_current->args) {
228✔
735
            action(name, arg, second_args);
228✔
736
        }
228✔
737
        // assumptions depend on loop and outer scopes, so they remain identical for the loop that is used as basis
738
        // (that the other gets inlined into) for now we always inline into 2nd.
739

740
        first_current = state_.get_parent(*first_current);
58✔
741
        second_current = state_.get_parent(*second_current);
58✔
742
    } while (first_current && first_current->loop != terminate_at);
58✔
743
}
41✔
744

745
LoopFusionHandler::InOutCheckResult LoopFusionHandler::check_ins_outs(
746
    const FusionLoopCandidate& first_candidate,
747
    const FusionLoopCandidate& second_candidate,
748
    symbolic::ExpressionMapping& canonical_indvars,
749
    bool local_not_nested,
750
    bool only_no_overlap
751
) {
115✔
752
    auto& first_args = first_candidate.args;
115✔
753
    auto& second_args = second_candidate.args;
115✔
754

755
    return check_ins_outs(first_args, second_args, canonical_indvars, local_not_nested, only_no_overlap);
115✔
756
}
115✔
757

758
LoopFusionHandler::InOutCheckResult LoopFusionHandler::check_ins_outs(
759
    const std::unordered_map<std::string, FusionArg>& first_args,
760
    const std::unordered_map<std::string, FusionArg>& second_args,
761
    symbolic::ExpressionMapping& canonical_indvars,
762
    bool local_not_nested,
763
    bool only_no_overlap
764
) {
115✔
765
    bool overlap = false;
115✔
766
    bool no_conflicts = true;
115✔
767
    bool subset_mismatch = false;
115✔
768

769
    for (auto& [name, prod_meta] : first_args) {
485✔
770
        auto cons_it = second_args.find(name);
485✔
771
        if (cons_it != second_args.end()) {
485✔
772
            auto& cons_meta = cons_it->second;
277✔
773
            if (prod_meta.arg.is_input && cons_meta.arg.is_output) {
277✔
774
                // there could be conflicts here. So for now, abort.
775
                // Future Work: if both were to strictly match indvars (or never match other iterations),
776
                // it would never be a conflict
777
                overlap = true;
7✔
778
                no_conflicts = false;
7✔
779
                continue;
7✔
780
            } else if (prod_meta.arg.is_output && cons_meta.arg.is_input) {
270✔
781
                overlap = true;
112✔
782
                auto& prod_collected_accesses = local_not_nested ? prod_meta.local_access : prod_meta.nested_access;
112✔
783
                auto& cons_collected_accesses = local_not_nested ? cons_meta.local_access : cons_meta.nested_access;
112✔
784
                if (prod_collected_accesses.subset_conflicts_with(cons_collected_accesses, canonical_indvars)) {
112✔
785
                    subset_mismatch = true;
34✔
786
                    // conflict (between vars unproved, fuse-by-access might find a solution with conflicting subsets)
787
                    continue;
34✔
788
                }
34✔
789
            } else if (prod_meta.arg.is_ptr && cons_meta.arg.is_ptr && prod_meta.arg.is_explicit_input &&
158✔
790
                       cons_meta.arg.is_explicit_input) {
158✔
791
                overlap = true;
4✔
792
                continue;
4✔
793
            }
4✔
794
        }
277✔
795
    }
485✔
796

797
    if (only_no_overlap && overlap) {
115✔
798
        no_conflicts = false;
2✔
799
    }
2✔
800

801
    return {no_conflicts, overlap, subset_mismatch};
115✔
802
}
115✔
803

804
void LoopFusionHandler::update_fused_seq(Sequence& sequence, const symbolic::ExpressionMapping& replacements) {
19✔
805
    sequence.replace(replacements);
19✔
806
}
19✔
807

808
/**
809
 * Merge a newly observed access (its `subset` and `not_understood` flag) into the FusionArg `into`.
810
 * If `into` already tracks a different subset, we can no longer describe the access with a single
811
 * subset and mark it not_understood. Returns true if `into` was modified.
812
 */
813
bool FusionArgCommonAccesses::merge_into(
814
    Block* block,
815
    const std::optional<data_flow::Subset>& subset,
816
    bool not_understood,
817
    bool write_not_read,
818
    const symbolic::ExpressionMapping* lower_indvars
819
) {
498✔
820
    bool updated = merge_subset(subset, not_understood, lower_indvars);
498✔
821

822
    if (write_not_read) {
498✔
823
        updated |= wr_block.merge_into(block, false);
230✔
824
    } else {
268✔
825
        updated |= rd_block.merge_into(block, false);
268✔
826
    }
268✔
827

828
    return updated;
498✔
829
}
498✔
830

831
bool FusionArgCommonBlock::merge_into(Block* other_block, bool other_conflict) {
1,870✔
832
    bool updated = false;
1,870✔
833

834
    if (other_conflict && !this->block_conflict) {
1,870✔
NEW
835
        this->block_conflict = true;
×
NEW
836
        updated = true;
×
837
    } else if (!this->block_conflict && this->common_block && other_block) {
1,870✔
838
        if (this->common_block != other_block) {
107✔
839
            this->block_conflict = true;
9✔
840
            this->common_block = nullptr;
9✔
841
            updated = true;
9✔
842
        }
9✔
843
    } else if (!this->block_conflict && !this->common_block && other_block) {
1,763✔
844
        this->common_block = other_block;
650✔
845
        updated = true;
650✔
846
    }
650✔
847

848
    return updated;
1,870✔
849
}
1,870✔
850

851
bool FusionArgCommonBlock::merge_into(const FusionArgCommonBlock& other) {
1,372✔
852
    return merge_into(other.common_block, other.block_conflict);
1,372✔
853
}
1,372✔
854

855
bool FusionArgCommonAccesses::
856
    merge_into(const FusionArgCommonAccesses& other, const symbolic::ExpressionMapping* lower_indvars) {
686✔
857
    bool update = merge_subset(other.common_subset, other.subsets_conflict, lower_indvars);
686✔
858
    update |= wr_block.merge_into(other.wr_block);
686✔
859
    update |= rd_block.merge_into(other.rd_block);
686✔
860
    return update;
686✔
861
}
686✔
862

863
bool FusionArgCommonAccesses::subset_conflicts_with(
864
    const FusionArgCommonAccesses& other_common_acceses, symbolic::ExpressionMapping& canonical_indvars
865
) const {
112✔
866
    if (subsets_conflict || other_common_acceses.subsets_conflict) {
112✔
867
        return true;
5✔
868
    }
5✔
869

870
    if (common_subset.has_value() && other_common_acceses.common_subset.has_value()) {
107✔
871
        if (!symbolic::vectors_of_expressions_match(
64✔
872
                common_subset.value(), other_common_acceses.common_subset.value(), canonical_indvars
64✔
873
            )) {
64✔
874
            return true;
29✔
875
        }
29✔
876
    }
64✔
877

878
    return false;
78✔
879
}
107✔
880

881
bool FusionArgCommonAccesses::merge_subset(
882
    const std::optional<data_flow::Subset>& subset,
883
    bool not_understood,
884
    const symbolic::ExpressionMapping* lower_indvars
885
) {
1,184✔
886
    bool updated = false;
1,184✔
887

888
    std::optional<data_flow::Subset> mapped_subset_holder;
1,184✔
889
    const data_flow::Subset* mapped_subset = nullptr;
1,184✔
890
    if (subset.has_value()) {
1,184✔
891
        if (lower_indvars) {
749✔
892
            mapped_subset_holder = symbolic::substitute(subset.value(), *lower_indvars);
206✔
893
            mapped_subset = &mapped_subset_holder.value();
206✔
894
        } else {
543✔
895
            mapped_subset = &subset.value();
543✔
896
        }
543✔
897
    }
749✔
898

899
    if (common_subset.has_value() && mapped_subset &&
1,184✔
900
        !symbolic::vectors_of_expressions_match(common_subset.value(), *mapped_subset)) {
1,184✔
901
        if (!subsets_conflict) {
26✔
902
            subsets_conflict = true;
22✔
903
            updated = true;
22✔
904
        }
22✔
905
    } else if (!common_subset.has_value() && mapped_subset && !subsets_conflict) {
1,158✔
906
        common_subset = *mapped_subset;
586✔
907
        updated = true;
586✔
908
    }
586✔
909
    if (not_understood && !subsets_conflict) {
1,184✔
910
        subsets_conflict = true;
3✔
911
        updated = true;
3✔
912
    }
3✔
913
    return updated;
1,184✔
914
}
1,184✔
915

916
bool FusionArg::saw_access_locally() const {
27✔
917
    return local_access.subsets_conflict || local_access.common_subset.has_value();
27✔
918
}
27✔
919

NEW
920
void FusionLoopCandidate::non_indvar_writes() { this->incompatible = true; }
×
921

NEW
922
void FusionLoopCandidate::aliasing_encountered() { this->incompatible = true; }
×
923

924
void FusionLoopCandidate::replace(const symbolic::ExpressionMapping& mapping) {
23✔
925
    for (auto& [name, arg] : args) {
96✔
926
        if (arg.local_access.common_subset.has_value()) {
96✔
927
            arg.local_access.common_subset = updated_subset(arg.local_access.common_subset.value(), mapping);
41✔
928
        }
41✔
929
        if (arg.nested_access.common_subset.has_value()) {
96✔
930
            arg.nested_access.common_subset = updated_subset(arg.nested_access.common_subset.value(), mapping);
49✔
931
        }
49✔
932
    }
96✔
933
    symbolic::substitute(assumptions, mapping);
23✔
934

935
    if constexpr (DUMP_ASSUMPTIONS) {
936
        std::cout << "Updated #" << this->loop->element_id() << " to:" << std::endl;
937
        std::cout << this->assumptions << std::endl;
938
    }
939
}
23✔
940

941
NeighboringPatternVisitor::NeighboringPatternVisitor(PatternHandler& handler) : handler_(handler) {}
54✔
942

943
bool NeighboringPatternVisitor::visit(sdfg::structured_control_flow::Sequence& node) {
203✔
944
    if (node.size() < 2) { // impossible to find a match, just descend into it
203✔
945
        return ActualStructuredSDFGVisitor::visit(node);
102✔
946
    }
102✔
947

948
    // Iterate over sequence looking for consecutive (StructuredLoop, StructuredLoop) pairs
949
    size_t i = 0;
101✔
950
    structured_control_flow::ControlFlowNode* override_last = nullptr;
101✔
951
    while (i < node.size()) {
324✔
952
        auto& child_node = node.at(i);
223✔
953
        auto* first = dyn_cast<structured_control_flow::StructuredLoop*>(&child_node);
223✔
954
        if (!first) {
223✔
955
            i++;
95✔
956
            dispatch(child_node);
95✔
957
            continue;
95✔
958
        }
95✔
959
        if (first->root().size() == 0) {
128✔
NEW
960
            i++;
×
NEW
961
            continue;
×
NEW
962
        }
×
963

964
        StructuredLoop* second = nullptr;
128✔
965

966
        if (i + 1 < node.size()) {
128✔
967
            second = dyn_cast<structured_control_flow::StructuredLoop*>(&node.at(i + 1));
63✔
968
            if (second) {
63✔
969
                if (second->root().size() == 0) {
59✔
NEW
970
                    i++;
×
NEW
971
                    continue;
×
NEW
972
                }
×
973

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

976
                if (!result.removed_first) {
59✔
977
                    dispatch(child_node);
38✔
978
                }
38✔
979
                if (result.visit_second_body) {
59✔
980
                    auto* second_updated_child = result.second_root_replacement ? result.second_root_replacement
3✔
981
                                                                                : second;
3✔
982
                    dispatch(*second_updated_child);
3✔
983
                }
3✔
984
                if (result.removed_first) {
59✔
985
                    // do not increment i, we can use at as next firs
986
                    continue;
21✔
987
                }
21✔
988
            } else if (i + 2 < node.size()) {
59✔
989
                auto* mid_block = dyn_cast<structured_control_flow::Block*>(&node.at(i + 1));
4✔
990
                bool skippable = false;
4✔
991
                std::unordered_set<std::string> skipped_containers;
4✔
992
                if (mid_block) {
4✔
993
                    if (mid_block->dataflow().nodes().empty()) {
4✔
994
                        skippable = true;
1✔
995
                    } else if (mid_block->is_a_library_node<stdlib::MallocNode>()) {
3✔
NEW
996
                        for (auto& data_flow_node : mid_block->dataflow().nodes()) {
×
NEW
997
                            if (auto* container = dynamic_cast<data_flow::AccessNode*>(&data_flow_node)) {
×
NEW
998
                                skipped_containers.emplace(container->data());
×
NEW
999
                            }
×
NEW
1000
                        }
×
NEW
1001
                        skippable = true;
×
NEW
1002
                    }
×
1003
                }
4✔
1004
                if (skippable) {
4✔
1005
                    second = dyn_cast<structured_control_flow::StructuredLoop*>(&node.at(i + 2));
1✔
1006
                    if (second) {
1✔
1007
                        if (second->root().size() == 0) {
1✔
NEW
1008
                            i += 2;
×
NEW
1009
                            continue;
×
NEW
1010
                        }
×
1011

1012
                        if (!handler_.check_no_overlap(*first, *second, skipped_containers)) {
1✔
NEW
1013
                            i += 2;
×
NEW
1014
                            continue;
×
NEW
1015
                        }
×
1016

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

1019
                        if (!result.removed_first) {
1✔
NEW
1020
                            dispatch(child_node);
×
NEW
1021
                        }
×
1022
                        if (result.visit_second_body) {
1✔
NEW
1023
                            auto* second_updated_child = result.second_root_replacement ? result.second_root_replacement
×
NEW
1024
                                                                                        : second;
×
NEW
1025
                            dispatch(*second_updated_child);
×
NEW
1026
                        }
×
1027
                        if (result.removed_first) {
1✔
1028
                            i += 1; // skip the block, retry with second as next first
1✔
1029
                            continue;
1✔
1030
                        }
1✔
1031
                        // we visited [first, skipped, second] successfully, without shifting indices, move to second as
1032
                        // new first
NEW
1033
                        i += 2;
×
NEW
1034
                        continue;
×
1035
                    } else {
1✔
1036
                        // we know i+1 is worthless, so skip it
NEW
1037
                        i += 2;
×
NEW
1038
                        continue;
×
NEW
1039
                    }
×
1040
                }
1✔
1041
            }
4✔
1042
        } else {
65✔
1043
            dispatch(child_node);
65✔
1044
        }
65✔
1045
        i++;
106✔
1046
    }
106✔
1047

1048
    return true;
101✔
1049
}
203✔
1050

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