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

daisytuner / docc / 28158800507

25 Jun 2026 08:57AM UTC coverage: 61.644% (+0.06%) from 61.582%
28158800507

push

github

web-flow
MapFusionByDomain (#771)

 + New Map fusion caches data about iteration domain and map candidates
 + only matches up iteration domain exactly, per loop level.
 + Can support fusing non-leaf stacks of loops (stack ends where the shallower stack stops being perfectly nested & parallel)
 + new Element::replace for bulk replacements
 + New PatternMatcher visitor supports descending into replaced or modified nodes to allow for single-pass nested loop fusings
 + LoopAnalysis can now be kept up-to-date with changes done by Map-fusion
 + unit tests for the updating of LoopAnalysis
 * updated LoopAnalysis to be easier to keep up-to-date with changes. LoopTree is no longer ordered, if you want to iterate in pre-order, use the specific method for that
 + convenience StructuredSDFGBuilder.remove_from_parent()
 + RedundantLoadElim pass to skip reading from memory locations that have just been written (same block). Fusing no longer needs to do this
     RedundantLoadElimination does a simple check for other writes to the same structure. Can skip writes if redundant or not modify, if their are writes to different indices
* Updated verifiers to match new fusion
~ moved verifier checks behind correctness checks in npbench harness. Its more critical if we do not even get the expected results
* Added MapFusionByDomain also to loop-norm stage (currently inactive, causes more kernels that currently cannot be safely offloaded to CUDA.
---------

Co-authored-by: Lukas Truemper <lukas.truemper@outlook.de>

771 of 1186 new or added lines in 55 files covered. (65.01%)

6 existing lines in 6 files now uncovered.

38302 of 62134 relevant lines covered (61.64%)

987.24 hits per line

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

82.59
/sdfg/src/analysis/loop_analysis.cpp
1
#include "sdfg/analysis/loop_analysis.h"
2
#include <unordered_set>
3
#include <vector>
4

5
#include "sdfg/analysis/assumptions_analysis.h"
6
#include "sdfg/structured_control_flow/map.h"
7
#include "sdfg/structured_control_flow/structured_loop.h"
8
#include "sdfg/structured_control_flow/while.h"
9
#include "sdfg/symbolic/conjunctive_normal_form.h"
10

11
namespace sdfg {
12
namespace analysis {
13

14
LoopAnalysis::LoopAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg), loops_(), loop_tree_() {}
360✔
15

16
void LoopAnalysis::init_new_loop_info(
17
    LoopState& info,
18
    uint32_t id,
19
    uint32_t loop_level,
20
    structured_control_flow::ControlFlowNode* loop,
21
    structured_control_flow::Map* map,
22
    structured_control_flow::ControlFlowNode* while_loop
23
) {
1,369✔
24
    auto is_elementwise = (map != nullptr && map->is_contiguous());
1,369✔
25
    LocalLoopInfo::LoopType type;
1,369✔
26
    if (while_loop != nullptr) {
1,369✔
27
        type = LocalLoopInfo::LoopType::While;
12✔
28
    } else if (map != nullptr) {
1,357✔
29
        type = LocalLoopInfo::LoopType::Map;
784✔
30
    } else {
784✔
31
        type = LocalLoopInfo::LoopType::For;
573✔
32
    }
573✔
33

34
    info.local = {
1,369✔
35
        .loop_id = id,
1,369✔
36
        .loop_level = loop_level,
1,369✔
37
        .type = type,
1,369✔
38
        .is_elementwise = is_elementwise,
1,369✔
39
        .contains_non_perfectly_nested = false,
1,369✔
40
        .contains_side_effects = false
1,369✔
41
    };
1,369✔
42
}
1,369✔
43

44
void LoopAnalysis::
45
    run(structured_control_flow::ControlFlowNode& scope,
46
        structured_control_flow::ControlFlowNode* parent_loop,
47
        uint32_t loop_level) {
1,733✔
48
    std::list<structured_control_flow::ControlFlowNode*> queue = {&scope};
1,733✔
49
    bool non_perfectly_nested = false;
1,733✔
50
    bool side_effects = false;
1,733✔
51

52
    while (!queue.empty()) {
5,908✔
53
        auto current = queue.front();
4,175✔
54
        queue.pop_front();
4,175✔
55

56
        structured_control_flow::While* new_while = nullptr;
4,175✔
57
        structured_control_flow::Map* new_map = nullptr;
4,175✔
58
        structured_control_flow::For* new_for = nullptr;
4,175✔
59
        structured_control_flow::ControlFlowNode* new_loop = nullptr;
4,175✔
60
        // Loop detected
61
        if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
4,175✔
62
            new_loop = while_stmt;
12✔
63
            new_while = while_stmt;
12✔
64
        } else if (auto map_stmt = dynamic_cast<structured_control_flow::Map*>(current)) {
4,163✔
65
            new_map = map_stmt;
784✔
66
            new_loop = map_stmt;
784✔
67
        } else if (auto for_stmt = dynamic_cast<structured_control_flow::For*>(current)) {
3,379✔
68
            new_for = for_stmt;
573✔
69
            new_loop = for_stmt;
573✔
70
        }
573✔
71

72
        if (new_loop != nullptr) {
4,175✔
73
            auto id = this->loops_.size();
1,369✔
74
            this->loops_.push_back(new_loop);
1,369✔
75
            this->loop_tree_[new_loop] = parent_loop;
1,369✔
76
            this->loop_children_[parent_loop].push_back(new_loop);
1,369✔
77
            this->loop_children_[new_loop]; // ensure it gets created
1,369✔
78
            auto& loop_info = loop_infos_[new_loop];
1,369✔
79
            init_new_loop_info(loop_info, id, loop_level, new_loop, new_map, new_while);
1,369✔
80
        }
1,369✔
81

82
        if (auto block = dynamic_cast<structured_control_flow::Block*>(current)) {
4,175✔
83
            non_perfectly_nested = true;
1,060✔
84
            for (auto& node : block->dataflow().nodes()) {
3,595✔
85
                if (auto library_node = dynamic_cast<data_flow::LibraryNode*>(&node)) {
3,595✔
86
                    if (library_node->side_effect()) { // also look at pointer metadata (no capture to not infer
122✔
87
                                                       // side-effects)
88
                        side_effects = true;
120✔
89
                        break;
120✔
90
                    }
120✔
91
                }
122✔
92
            }
3,595✔
93
        } else if (auto sequence_stmt = dynamic_cast<structured_control_flow::Sequence*>(current)) {
3,115✔
94
            auto seq_entries = sequence_stmt->size();
1,739✔
95
            if (current != &scope) { // the body-root of each loop is expected to be a sequence
1,739✔
96
                non_perfectly_nested = true;
10✔
97
            }
10✔
98
            for (size_t i = 0; i < seq_entries; i++) {
4,171✔
99
                auto entry = sequence_stmt->at(i);
2,432✔
100
                queue.push_back(&entry.first);
2,432✔
101
            }
2,432✔
102
        } else if (auto if_else_stmt = dynamic_cast<structured_control_flow::IfElse*>(current)) {
1,739✔
103
            non_perfectly_nested = true;
7✔
104
            for (size_t i = 0; i < if_else_stmt->size(); i++) {
17✔
105
                queue.push_back(&if_else_stmt->at(i).first);
10✔
106
            }
10✔
107
        } else if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
1,369✔
108
            this->run(while_stmt->root(), while_stmt, loop_level + 1);
12✔
109
        } else if (auto for_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
1,357✔
110
            this->run(for_stmt->root(), for_stmt, loop_level + 1);
1,357✔
111
        } else if (dynamic_cast<structured_control_flow::Break*>(current)) {
1,357✔
112
            non_perfectly_nested = true;
×
113
            continue;
×
114
        } else if (dynamic_cast<structured_control_flow::Continue*>(current)) {
×
115
            non_perfectly_nested = true;
×
116
            continue;
×
117
        } else if (dynamic_cast<structured_control_flow::Return*>(current)) {
×
118
            non_perfectly_nested = true;
×
119
            continue;
×
120
        } else {
×
121
            throw std::runtime_error("Unsupported control flow node type");
×
122
        }
×
123
    }
4,175✔
124

125
    if (parent_loop != nullptr) {
1,733✔
126
        auto& state = loop_infos_.at(parent_loop);
1,372✔
127
        state.local.contains_side_effects = side_effects;
1,372✔
128
        state.local.contains_non_perfectly_nested = non_perfectly_nested;
1,372✔
129
    }
1,372✔
130
}
1,733✔
131

132
structured_control_flow::Sequence* LoopAnalysis::get_loop_content_root(structured_control_flow::ControlFlowNode* loop) {
×
133
    structured_control_flow::Sequence* root = nullptr;
×
134
    if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(loop)) {
×
135
        root = &while_stmt->root();
×
136
    } else if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(loop)) {
×
137
        root = &loop_stmt->root();
×
138
    } else {
×
139
        throw std::runtime_error("Node is not a loop");
×
140
    }
×
141
    return root;
×
142
}
×
143

144
LoopAnalysis::LoopState& LoopAnalysis::compute_loop_infos(structured_control_flow::ControlFlowNode* loop) {
1,369✔
145
    // Recursion
146
    auto& loop_children = this->loop_children_.at(loop);
1,369✔
147
    for (auto& child_loop : loop_children) {
1,369✔
148
        this->compute_loop_infos(child_loop);
792✔
149
    }
792✔
150

151
    auto& loop_state = this->loop_infos_.at(loop);
1,369✔
152
    auto new_state = this->aggregate_loop_info(loop);
1,369✔
153
    loop_state.nest = std::move(new_state.nest);
1,369✔
154
    loop_state.local.last_child_id = new_state.last_child_id;
1,369✔
155
    return loop_state;
1,369✔
156
}
1,369✔
157

158
LoopAnalysis::AggregatedResult LoopAnalysis::aggregate_loop_info(structured_control_flow::ControlFlowNode* loop) const {
1,389✔
159
    auto& loop_state = this->loop_infos_.at(loop);
1,389✔
160
    const LocalLoopInfo& local = loop_state.local;
1,389✔
161
    auto& loop_children = this->loop_children_.at(loop);
1,389✔
162

163
    // Start from the existing infos to preserve fields that do not depend on the subtree
164
    // (element_id, loop_level, loopnest_index) and reset the aggregated, subtree-derived fields.
165
    AggregatedResult result;
1,389✔
166
    auto& info = result.nest;
1,389✔
167
    info.element_id = loop->element_id();
1,389✔
168
    info.loop_level = local.loop_level;
1,389✔
169
    info.num_loops = 1;
1,389✔
170
    info.num_maps = local.type == LocalLoopInfo::LoopType::Map ? 1 : 0;
1,389✔
171
    info.num_whiles = local.type == LocalLoopInfo::LoopType::While ? 1 : 0;
1,389✔
172
    info.num_fors = local.type == LocalLoopInfo::LoopType::For ? 1 : 0;
1,389✔
173
    info.max_depth = 1;
1,389✔
174
    result.last_child_id = local.loop_id;
1,389✔
175

176
    bool is_perfectly_nested = !local.contains_non_perfectly_nested;
1,389✔
177
    bool is_perfectly_parallel = local.type == LocalLoopInfo::LoopType::Map;
1,389✔
178
    bool is_elementwise = local.is_elementwise;
1,389✔
179
    bool map_stack_member = local.type == LocalLoopInfo::LoopType::Map;
1,389✔
180
    bool has_side_effects = local.contains_side_effects;
1,389✔
181
    bool map_stack_children = map_stack_member && loop_children.size() <= 1;
1,389✔
182
    uint32_t map_stack_depth = 0;
1,389✔
183

184
    for (auto& child_loop : loop_children) {
1,389✔
185
        auto& sub_state = this->loop_infos_.at(child_loop);
813✔
186
        auto& sub_info = sub_state.nest;
813✔
187
        info.num_loops += sub_info.num_loops;
813✔
188
        info.num_maps += sub_info.num_maps;
813✔
189
        info.num_fors += sub_info.num_fors;
813✔
190
        info.num_whiles += sub_info.num_whiles;
813✔
191
        info.max_depth = std::max(info.max_depth, 1 + sub_info.max_depth);
813✔
192
        result.last_child_id = std::max(result.last_child_id, sub_state.local.last_child_id);
813✔
193

194
        has_side_effects |= sub_info.has_side_effects;
813✔
195
        is_perfectly_nested &= sub_info.is_perfectly_nested;
813✔
196
        is_perfectly_parallel &= sub_info.is_perfectly_parallel;
813✔
197
        is_elementwise &= sub_info.is_elementwise;
813✔
198
        if (map_stack_children) {
813✔
199
            map_stack_depth = sub_info.map_stack_depth; // only allowed if there is just a single, direct child
363✔
200
        }
363✔
201
    }
813✔
202

203
    info.is_perfectly_parallel = is_perfectly_parallel;
1,389✔
204
    auto child_count = loop_children.size();
1,389✔
205
    if (child_count > 1) {
1,389✔
206
        is_perfectly_nested = false;
98✔
207
    } else if (child_count < 1) {
1,291✔
208
        is_perfectly_nested = true;
699✔
209
    }
699✔
210
    info.is_perfectly_nested = is_perfectly_nested;
1,389✔
211
    info.is_elementwise = is_elementwise && is_perfectly_nested & is_perfectly_parallel;
1,389✔
212
    info.has_side_effects = has_side_effects;
1,389✔
213
    if (map_stack_member) {
1,389✔
214
        if (local.contains_non_perfectly_nested) { // needs to be perfectly nested to form a larger stack
802✔
215
            map_stack_depth = 0;
414✔
216
        }
414✔
217
        info.map_stack_depth = map_stack_depth + 1;
802✔
218
    }
802✔
219

220
    return result;
1,389✔
221
}
1,389✔
222

223
void LoopAnalysis::reindex_loop_nest_idx() {
11✔
224
    auto& root_loops = this->loop_children_.at(nullptr);
11✔
225

226
    for (size_t i = 0; i < root_loops.size(); ++i) {
43✔
227
        this->loop_infos_[root_loops.at(i)].nest.loopnest_index = i;
32✔
228
    }
32✔
229
}
11✔
230

231
void LoopAnalysis::run(AnalysisManager& analysis_manager) {
360✔
232
    this->loops_.clear();
360✔
233
    this->loop_tree_.clear();
360✔
234
    this->loop_infos_.clear();
360✔
235
    this->loop_children_.clear();
360✔
236
    this->loop_children_[nullptr]; // ensure it exists
360✔
237
    this->run(this->sdfg_.root(), nullptr, 0);
360✔
238

239
    // Set loopnest indices for outermost loops
240
    int loopnest_index = 0;
360✔
241
    auto& root_loops = this->loop_children_.at(nullptr);
360✔
242

243
    for (auto* root_loop : root_loops) {
573✔
244
        this->compute_loop_infos(root_loop);
573✔
245
        this->loop_infos_[root_loop].nest.loopnest_index = loopnest_index++;
573✔
246
    }
573✔
247
}
360✔
248

249
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::loops() const {
182✔
250
    return this->loops_;
182✔
251
} // copies by default...
182✔
252
const std::vector<structured_control_flow::ControlFlowNode*>& LoopAnalysis::loops_in_pre_order() const {
112✔
253
    return this->loops_;
112✔
254
}
112✔
255

256
LoopInfo LoopAnalysis::loop_info(structured_control_flow::ControlFlowNode* loop) const {
420✔
257
    return this->loop_infos_.at(loop).nest;
420✔
258
}
420✔
259

260
const LocalLoopInfo& LoopAnalysis::loop_info_local(structured_control_flow::ControlFlowNode* loop) const {
113✔
261
    return this->loop_infos_.at(loop).local;
113✔
262
}
113✔
263

264
structured_control_flow::ControlFlowNode* LoopAnalysis::find_loop_by_indvar(const std::string& indvar) {
×
265
    for (auto& loop : this->loops_) {
×
266
        if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(loop)) {
×
267
            if (loop_stmt->indvar()->get_name() == indvar) {
×
268
                return loop;
×
269
            }
×
270
        }
×
271
    }
×
272
    return nullptr;
×
273
}
×
274

275
const std::unordered_map<structured_control_flow::ControlFlowNode*, structured_control_flow::ControlFlowNode*>&
276
LoopAnalysis::loop_tree() const {
16✔
277
    return this->loop_tree_;
16✔
278
}
16✔
279

280
structured_control_flow::ControlFlowNode* LoopAnalysis::parent_loop(structured_control_flow::ControlFlowNode* loop
281
) const {
54✔
282
    return this->loop_tree_.at(loop);
54✔
283
}
54✔
284

285
const std::vector<structured_control_flow::ControlFlowNode*>& LoopAnalysis::outermost_loops() const {
152✔
286
    return loop_children_.at(nullptr);
152✔
287
}
152✔
288

289
bool LoopAnalysis::is_outermost_loop(structured_control_flow::ControlFlowNode* loop) const {
1✔
290
    if (this->loop_tree_.find(loop) == this->loop_tree_.end()) {
1✔
291
        return false;
×
292
    }
×
293
    return this->loop_tree_.at(loop) == nullptr;
1✔
294
}
1✔
295

296
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::outermost_maps() const {
×
297
    std::vector<structured_control_flow::ControlFlowNode*> outermost_maps_;
×
298
    for (const auto& [loop, parent] : this->loop_tree_) {
×
299
        if (dynamic_cast<structured_control_flow::Map*>(loop)) {
×
300
            auto ancestor = parent;
×
301
            while (true) {
×
302
                if (ancestor == nullptr) {
×
303
                    outermost_maps_.push_back(loop);
×
304
                    break;
×
305
                }
×
306
                if (dynamic_cast<structured_control_flow::Map*>(ancestor)) {
×
307
                    break;
×
308
                }
×
309
                ancestor = this->loop_tree_.at(ancestor);
×
310
            }
×
311
        }
×
312
    }
×
313
    return outermost_maps_;
×
314
}
×
315

316
const std::vector<sdfg::structured_control_flow::ControlFlowNode*>& LoopAnalysis::
317
    children(sdfg::structured_control_flow::ControlFlowNode* node) const {
8,427✔
318
    // Find unique child
319
    return loop_children_.at(node);
8,427✔
320
}
8,427✔
321

322
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::
323
    loop_tree_paths(sdfg::structured_control_flow::ControlFlowNode* loop) const {
330✔
324
    return this->loop_tree_paths(loop, this->loop_tree_);
330✔
325
};
330✔
326

327
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::loop_tree_paths(
328
    sdfg::structured_control_flow::ControlFlowNode* loop,
329
    const std::unordered_map<
330
        sdfg::structured_control_flow::ControlFlowNode*,
331
        sdfg::structured_control_flow::ControlFlowNode*>& tree
332
) const {
707✔
333
    // Collect all paths in tree starting from loop recursively (DFS)
334
    std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> paths;
707✔
335
    auto& children = this->children(loop);
707✔
336
    if (children.empty()) {
707✔
337
        paths.push_back({loop});
376✔
338
        return paths;
376✔
339
    }
376✔
340

341
    for (auto& child : children) {
377✔
342
        auto p = this->loop_tree_paths(child, tree);
377✔
343
        for (auto& path : p) {
393✔
344
            path.insert(path.begin(), loop);
393✔
345
            paths.push_back(path);
393✔
346
        }
393✔
347
    }
377✔
348

349
    return paths;
331✔
350
};
707✔
351

352
std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::
353
    descendants(sdfg::structured_control_flow::ControlFlowNode* loop) const {
2,344✔
354
    std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> desc;
2,344✔
355
    std::list<sdfg::structured_control_flow::ControlFlowNode*> queue = {loop};
2,344✔
356
    while (!queue.empty()) { // TODO use ordered list directly
10,029✔
357
        auto current = queue.front();
7,685✔
358
        queue.pop_front();
7,685✔
359
        auto& children = this->children(current);
7,685✔
360
        for (auto& child : children) {
7,685✔
361
            if (desc.find(child) == desc.end()) {
5,341✔
362
                desc.insert(child);
5,341✔
363
                queue.push_back(child);
5,341✔
364
            }
5,341✔
365
        }
5,341✔
366
    }
7,685✔
367
    return desc;
2,344✔
368
}
2,344✔
369

370
void LoopAnalysis::dump_to_file(std::filesystem::path file) const {
×
371
    nlohmann::json arr;
×
372
    for (auto& loop : this->loops_) {
×
373
        auto& info = loop_infos_.at(loop);
×
374

NEW
375
        nlohmann::json entry;
×
NEW
376
        loop_info_local_to_json(entry["info"], info.local);
×
NEW
377
        entry["nest"] = loop_info_to_json(info.nest);
×
NEW
378
        arr.push_back(entry);
×
UNCOV
379
    }
×
380

381

382
    std::filesystem::create_directories(file.parent_path());
×
383
    std::ofstream out(file, std::ofstream::out);
×
384
    if (!out.is_open()) {
×
385
        std::cerr << "Could not open file " << file << " for writing JSON output." << std::endl;
×
386
    }
×
387
    out << arr << std::endl;
×
388
    out.close();
×
389
}
×
390

391
void LoopAnalysis::copied_loop(
392
    structured_control_flow::ControlFlowNode* existing_loop,
393
    structured_control_flow::ControlFlowNode* new_parent_loop,
394
    structured_control_flow::ControlFlowNode* new_loop,
395
    bool start_not_end
396
) {
4✔
397
    // `existing_loop` is only the conceptual source of the copy. The caller has already materialized
398
    // `new_loop` (with whatever subtree it has) inside `new_parent_loop` in the SDFG, so we simply
399
    // (re)derive everything for the new subtree directly from the SDFG instead of cloning infos.
400
    (void) existing_loop;
4✔
401

402
    // Where the root of the new subtree should live in the pre-order `loops_` vector.
403
    auto insert_idx = child_insertion_index(new_parent_loop, start_not_end);
4✔
404

405
    uint32_t new_loop_level = new_parent_loop == nullptr ? 0 : loop_infos_.at(new_parent_loop).local.loop_level + 1;
4✔
406

407
    // run() rescans the scope and overwrites the parent's local side-effect / non-perfect-nesting
408
    // flags based on that scan. Adding a loop child does not legitimately change either flag (they
409
    // describe non-loop body content), so snapshot and restore them around the call.
410
    bool had_parent = new_parent_loop != nullptr;
4✔
411
    bool saved_side_effects = false;
4✔
412
    bool saved_non_perfect = false;
4✔
413
    if (had_parent) {
4✔
414
        auto& pl = loop_infos_.at(new_parent_loop).local;
3✔
415
        saved_side_effects = pl.contains_side_effects;
3✔
416
        saved_non_perfect = pl.contains_non_perfectly_nested;
3✔
417
    }
3✔
418

419
    // Analyze the new subtree. run() appends it (in pre-order) to the end of `loops_`, registers it
420
    // in loop_tree_/loop_children_ (as the last child of new_parent_loop) and fills in local infos.
421
    auto block_start = static_cast<uint32_t>(loops_.size());
4✔
422
    this->run(*new_loop, new_parent_loop, new_loop_level);
4✔
423

424
    if (had_parent) {
4✔
425
        auto& pl = loop_infos_.at(new_parent_loop).local;
3✔
426
        pl.contains_side_effects = saved_side_effects;
3✔
427
        pl.contains_non_perfectly_nested = saved_non_perfect;
3✔
428
    }
3✔
429

430
    // Bottom-up fill the nest infos (and last_child_id spans) of the freshly added subtree. At this
431
    // point the subtree still sits at the tail of `loops_` with contiguous, correct relative ids.
432
    this->compute_loop_infos(new_loop);
4✔
433

434
    // run() always appends the new root as the last child; move it to the front if requested.
435
    if (start_not_end) {
4✔
436
        auto& np_children = loop_children_.at(new_parent_loop);
1✔
437
        np_children.pop_back();
1✔
438
        np_children.insert(np_children.begin(), new_loop);
1✔
439
    }
1✔
440

441
    // Splice the new block out of the tail of `loops_` and into its pre-order position.
442
    std::vector<structured_control_flow::ControlFlowNode*> new_block(loops_.begin() + block_start, loops_.end());
4✔
443
    loops_.erase(loops_.begin() + block_start, loops_.end());
4✔
444
    loops_.insert(loops_.begin() + insert_idx, new_block.begin(), new_block.end());
4✔
445

446
    // Everything from the insertion point onward now has a stale loop_id (the block moved, the rest
447
    // shifted right). reindex preserves each node's subtree span, which is correct for all of them.
448
    reindex(loops_.begin() + insert_idx, loops_.end());
4✔
449

450
    // Propagate the new subtree's contribution (grown last_child_id, changed nest aggregates) up the
451
    // parent chain. The new subtree itself already holds up-to-date infos from compute_loop_infos.
452
    if (new_parent_loop != nullptr) {
4✔
453
        propagate_changed_nest_info(loops_.begin() + loop_infos_.at(new_parent_loop).local.loop_id);
3✔
454
    }
3✔
455

456
    reindex_loop_nest_idx();
4✔
457
}
4✔
458

459
uint32_t LoopAnalysis::child_insertion_index(structured_control_flow::ControlFlowNode* new_parent, bool start_not_end)
460
    const {
6✔
461
    const auto& children = loop_children_.at(new_parent);
6✔
462
    auto it = start_not_end ? children.begin() : children.end();
6✔
463
    if (it != children.end()) {
6✔
464
        // Insert right where the chosen child's subtree starts.
465
        return loop_infos_.at(*it).local.loop_id;
1✔
466
    }
1✔
467
    if (new_parent == nullptr) {
5✔
468
        // Appending after all root-level loops.
469
        return static_cast<uint32_t>(loops_.size());
1✔
470
    }
1✔
471
    // Insert right after the new parent's current subtree.
472
    return loop_infos_.at(new_parent).local.last_child_id + 1;
4✔
473
}
5✔
474

475
void LoopAnalysis::moved_loop(
476
    structured_control_flow::ControlFlowNode* existing_loop,
477
    structured_control_flow::StructuredLoop* new_parent,
478
    bool start_not_end
479
) {
2✔
480
    auto& moved_state = loop_infos_.at(existing_loop);
2✔
481
    auto old_parent = loop_tree_.at(existing_loop);
2✔
482

483
    // The moved subtree occupies the contiguous pre-order range [first_moved_idx, next_after_moved_idx).
484
    auto first_moved_idx = moved_state.local.loop_id;
2✔
485
    auto next_after_moved_idx = moved_state.local.last_child_id + 1;
2✔
486
    auto moved_span = next_after_moved_idx - first_moved_idx;
2✔
487

488
    // Translate the requested child position (front/back) into an index in the pre-order `loops_`.
489
    auto insert_idx = child_insertion_index(new_parent, start_not_end);
2✔
490
    auto& new_parent_children = loop_children_.at(new_parent);
2✔
491

492
    // Snapshot the moved block of nodes before we mutate `loops_`.
493
    std::vector<structured_control_flow::ControlFlowNode*>
2✔
494
        moved_block(loops_.begin() + first_moved_idx, loops_.begin() + next_after_moved_idx);
2✔
495

496
    // Update the level of every node in the moved subtree (its root's level becomes new_parent.level + 1).
497
    auto level_delta = static_cast<int64_t>(loop_infos_.at(new_parent).local.loop_level) + 1 -
2✔
498
                       static_cast<int64_t>(moved_state.local.loop_level);
2✔
499
    if (level_delta != 0) {
2✔
500
        for (auto* node : moved_block) {
3✔
501
            auto& local = loop_infos_.at(node).local;
3✔
502
            local.loop_level = static_cast<uint32_t>(static_cast<int64_t>(local.loop_level) + level_delta);
3✔
503
        }
3✔
504
    }
2✔
505

506
    // Detach from the old parent's child list and reparent the moved root.
507
    auto& old_parent_children = loop_children_.at(old_parent);
2✔
508
    old_parent_children.erase(
2✔
509
        std::remove(old_parent_children.begin(), old_parent_children.end(), existing_loop), old_parent_children.end()
2✔
510
    );
2✔
511
    loop_tree_[existing_loop] = new_parent;
2✔
512

513
    // Splice the moved block out of `loops_` and back in at the insertion point.
514
    // Erasing first shifts everything after the block left by `moved_span`; account for that when the
515
    // insertion point lies past the removed block.
516
    loops_.erase(loops_.begin() + first_moved_idx, loops_.begin() + next_after_moved_idx);
2✔
517
    if (insert_idx > next_after_moved_idx) {
2✔
NEW
518
        insert_idx -= moved_span;
×
519
    } else if (insert_idx > first_moved_idx) {
2✔
520
        // Insertion point was inside the moved block (only possible if new_parent is within the moved
521
        // subtree, which is not a valid move) -> clamp to the block's start.
522
        insert_idx = first_moved_idx;
1✔
523
    }
1✔
524
    loops_.insert(loops_.begin() + insert_idx, moved_block.begin(), moved_block.end());
2✔
525

526
    // Insert into the new parent's child list. Recompute the iterator since `start_not_end` may not be the
527
    // only supported mode in the future; for begin()/end() this is stable across the splice above.
528
    auto new_insert_it = start_not_end ? new_parent_children.begin() : new_parent_children.end();
2✔
529
    new_parent_children.insert(new_insert_it, existing_loop);
2✔
530

531
    // All loop_ids from the lower of the two touched regions onward are now stale: reindex the whole span.
532
    auto reindex_from = std::min(first_moved_idx, insert_idx);
2✔
533
    reindex(loops_.begin() + reindex_from, loops_.end());
2✔
534

535
    // Propagate nest-info changes up both affected parent chains. The deeper of the two parents must be
536
    // processed first so the shallower one aggregates already-updated children.
537
    structured_control_flow::ControlFlowNode* lo = old_parent;
2✔
538
    structured_control_flow::ControlFlowNode* hi = new_parent;
2✔
539
    if (lo != hi) {
2✔
540
        auto level_of = [&](structured_control_flow::ControlFlowNode* n) {
4✔
541
            return n == nullptr ? -1 : static_cast<int64_t>(loop_infos_.at(n).local.loop_level);
4✔
542
        };
4✔
543
        if (level_of(lo) < level_of(hi)) {
2✔
544
            std::swap(lo, hi);
1✔
545
        }
1✔
546
        if (lo != nullptr) {
2✔
547
            propagate_changed_nest_info(loops_.begin() + loop_infos_.at(lo).local.loop_id);
2✔
548
        }
2✔
549
        if (hi != nullptr) {
2✔
550
            propagate_changed_nest_info(loops_.begin() + loop_infos_.at(hi).local.loop_id);
2✔
551
        }
2✔
552
    } else if (new_parent != nullptr) {
2✔
NEW
553
        propagate_changed_nest_info(loops_.begin() + loop_infos_.at(new_parent).local.loop_id);
×
NEW
554
    }
×
555

556
    reindex_loop_nest_idx();
2✔
557
}
2✔
558

559
void LoopAnalysis::reindex(
560
    std::vector<structured_control_flow::ControlFlowNode*>::iterator start,
561
    std::vector<structured_control_flow::ControlFlowNode*>::iterator end
562
) {
11✔
563
    for (auto it = start; it != end; ++it) {
69✔
564
        auto& local_info = loop_infos_.at(*it).local;
58✔
565
        auto new_loop_id = static_cast<uint32_t>(std::distance(loops_.begin(), it));
58✔
566
        // The number of (transitive) descendants is unchanged; only the base index shifts.
567
        auto span = local_info.last_child_id - local_info.loop_id;
58✔
568
        local_info.loop_id = new_loop_id;
58✔
569
        local_info.last_child_id = new_loop_id + span;
58✔
570
    }
58✔
571
}
11✔
572

573
void LoopAnalysis::propagate_changed_nest_info(std::vector<structured_control_flow::ControlFlowNode*>::iterator top) {
12✔
574
    // Starting at `top`, recompute each loop's nest info from its (already up-to-date) children and local state.
575
    // Walk up the parent chain, stopping as soon as a loop's info is unchanged or the root (nullptr) is reached.
576
    auto loop_info_equal = [](const LoopInfo& a, const LoopInfo& b) {
12✔
577
        bool equal = true;
6✔
578
#define X(type, name, val) equal = equal && (a.name == b.name);
78✔
579
        LOOP_INFO_PROPERTIES
6✔
580
#undef X
6✔
581
        return equal;
6✔
582
    };
6✔
583

584
    structured_control_flow::ControlFlowNode* current = *top;
12✔
585
    while (current != nullptr) {
29✔
586
        auto new_info = this->aggregate_loop_info(current);
20✔
587
        auto& state = this->loop_infos_.at(current);
20✔
588
        if (state.local.last_child_id == new_info.last_child_id && loop_info_equal(state.nest, new_info.nest)) {
20✔
589
            break; // no change here means nothing changes further up
3✔
590
        }
3✔
591
        state.local.last_child_id = new_info.last_child_id;
17✔
592
        state.nest = std::move(new_info.nest);
17✔
593
        current = this->loop_tree_.at(current);
17✔
594
    }
17✔
595
}
12✔
596

597
void LoopAnalysis::removed_loop(structured_control_flow::ControlFlowNode* existing_loop) {
5✔
598
    auto& state = loop_infos_.at(existing_loop);
5✔
599
    auto removed_loop_idx = state.local.loop_id;
5✔
600
    auto next_not_removed_idx = state.local.last_child_id + 1;
5✔
601
    auto parent_of_removed = loop_tree_.at(existing_loop);
5✔
602
    auto first_removed = loops_.begin() + removed_loop_idx;
5✔
603
    auto next_not_removed = loops_.begin() + next_not_removed_idx;
5✔
604

605
    for (auto* elem : std::ranges::subrange(first_removed, next_not_removed) | std::views::reverse) {
12✔
606
        loop_infos_.erase(elem);
12✔
607
        loop_tree_.erase(elem);
12✔
608
        loop_children_.erase(elem);
12✔
609
    }
12✔
610

611
    auto& parent_children = loop_children_.at(parent_of_removed);
5✔
612
    parent_children
5✔
613
        .erase(std::remove(parent_children.begin(), parent_children.end(), existing_loop), parent_children.end());
5✔
614

615
    loops_.erase(first_removed, next_not_removed);
5✔
616

617
    reindex(first_removed, loops_.end());
5✔
618

619
    if (parent_of_removed != nullptr) {
5✔
620
        propagate_changed_nest_info(loops_.begin() + loop_infos_.at(parent_of_removed).local.loop_id);
2✔
621
    }
2✔
622

623
    reindex_loop_nest_idx();
5✔
624
}
5✔
625

626
void LoopAnalysis::
627
    added_local_contents(structured_control_flow::ControlFlowNode* loop, bool side_effects, bool non_perfectly_nested) {
3✔
628
    auto& state = loop_infos_.at(loop);
3✔
629
    state.local.contains_side_effects = side_effects;
3✔
630
    state.local.contains_non_perfectly_nested = non_perfectly_nested;
3✔
631

632
    propagate_changed_nest_info(loops_.begin() + state.local.loop_id);
3✔
633
}
3✔
634

NEW
635
void loop_info_local_to_json(nlohmann::json& j, const LocalLoopInfo& info) {
×
NEW
636
    j["loop_id"] = info.loop_id;
×
NEW
637
    j["last_child_id"] = info.last_child_id;
×
NEW
638
    j["loop_level"] = info.loop_level;
×
NEW
639
    j["type"] = info.type;
×
NEW
640
    j["is_elementwise"] = info.is_elementwise;
×
NEW
641
    j["contains_non_perfectly_nested"] = info.contains_non_perfectly_nested;
×
NEW
642
    j["contains_side_effects"] = info.contains_side_effects;
×
NEW
643
}
×
644

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

© 2026 Coveralls, Inc