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

daisytuner / docc / 28228448983

26 Jun 2026 09:06AM UTC coverage: 61.824% (+0.2%) from 61.644%
28228448983

Pull #806

github

web-flow
Merge 14f513ee8 into a0c38ffa1
Pull Request #806: Map Collapse for Multiple targets in a neste sequence

165 of 185 new or added lines in 2 files covered. (89.19%)

844 existing lines in 25 files now uncovered.

39002 of 63086 relevant lines covered (61.82%)

977.72 hits per line

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

82.66
/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/reduce.h"
8
#include "sdfg/structured_control_flow/structured_loop.h"
9
#include "sdfg/structured_control_flow/while.h"
10
#include "sdfg/symbolic/conjunctive_normal_form.h"
11

12
namespace sdfg {
13
namespace analysis {
14

15
LoopAnalysis::LoopAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg), loops_(), loop_tree_() {}
379✔
16

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

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

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

53
    while (!queue.empty()) {
6,039✔
54
        auto current = queue.front();
4,263✔
55
        queue.pop_front();
4,263✔
56

57
        structured_control_flow::While* new_while = nullptr;
4,263✔
58
        structured_control_flow::Map* new_map = nullptr;
4,263✔
59
        structured_control_flow::For* new_for = nullptr;
4,263✔
60
        structured_control_flow::ControlFlowNode* new_loop = nullptr;
4,263✔
61
        // Loop detected
62
        if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
4,263✔
63
            new_loop = while_stmt;
12✔
64
            new_while = while_stmt;
12✔
65
        } else if (auto map_stmt = dynamic_cast<structured_control_flow::Map*>(current)) {
4,251✔
66
            new_map = map_stmt;
785✔
67
            new_loop = map_stmt;
785✔
68
        } else if (auto for_stmt = dynamic_cast<structured_control_flow::For*>(current)) {
3,466✔
69
            new_for = for_stmt;
534✔
70
            new_loop = for_stmt;
534✔
71
        } else if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
2,932✔
72
            // Generic structured loop (e.g. Reduce) that is neither a Map nor a For.
73
            new_loop = loop_stmt;
62✔
74
        }
62✔
75

76
        if (new_loop != nullptr) {
4,263✔
77
            auto id = this->loops_.size();
1,393✔
78
            this->loops_.push_back(new_loop);
1,393✔
79
            this->loop_tree_[new_loop] = parent_loop;
1,393✔
80
            this->loop_children_[parent_loop].push_back(new_loop);
1,393✔
81
            this->loop_children_[new_loop]; // ensure it gets created
1,393✔
82
            auto& loop_info = loop_infos_[new_loop];
1,393✔
83
            init_new_loop_info(loop_info, id, loop_level, new_loop, new_map, new_while);
1,393✔
84
        }
1,393✔
85

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

129
    if (parent_loop != nullptr) {
1,776✔
130
        auto& state = loop_infos_.at(parent_loop);
1,396✔
131
        state.local.contains_side_effects = side_effects;
1,396✔
132
        state.local.contains_non_perfectly_nested = non_perfectly_nested;
1,396✔
133
    }
1,396✔
134
}
1,776✔
135

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

148
LoopAnalysis::LoopState& LoopAnalysis::compute_loop_infos(structured_control_flow::ControlFlowNode* loop) {
1,393✔
149
    // Recursion
150
    auto& loop_children = this->loop_children_.at(loop);
1,393✔
151
    for (auto& child_loop : loop_children) {
1,393✔
152
        this->compute_loop_infos(child_loop);
797✔
153
    }
797✔
154

155
    auto& loop_state = this->loop_infos_.at(loop);
1,393✔
156
    auto new_state = this->aggregate_loop_info(loop);
1,393✔
157
    loop_state.nest = std::move(new_state.nest);
1,393✔
158
    loop_state.local.last_child_id = new_state.last_child_id;
1,393✔
159
    return loop_state;
1,393✔
160
}
1,393✔
161

162
LoopAnalysis::AggregatedResult LoopAnalysis::aggregate_loop_info(structured_control_flow::ControlFlowNode* loop) const {
1,413✔
163
    auto& loop_state = this->loop_infos_.at(loop);
1,413✔
164
    const LocalLoopInfo& local = loop_state.local;
1,413✔
165
    auto& loop_children = this->loop_children_.at(loop);
1,413✔
166

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

180
    bool is_perfectly_nested = !local.contains_non_perfectly_nested;
1,413✔
181
    bool is_perfectly_parallel = local.type == LocalLoopInfo::LoopType::Map;
1,413✔
182
    bool is_elementwise = local.is_elementwise;
1,413✔
183
    bool map_stack_member = local.type == LocalLoopInfo::LoopType::Map;
1,413✔
184
    bool has_side_effects = local.contains_side_effects;
1,413✔
185
    bool map_stack_children = map_stack_member && loop_children.size() <= 1;
1,413✔
186
    uint32_t map_stack_depth = 0;
1,413✔
187

188
    for (auto& child_loop : loop_children) {
1,413✔
189
        auto& sub_state = this->loop_infos_.at(child_loop);
818✔
190
        auto& sub_info = sub_state.nest;
818✔
191
        info.num_loops += sub_info.num_loops;
818✔
192
        info.num_maps += sub_info.num_maps;
818✔
193
        info.num_fors += sub_info.num_fors;
818✔
194
        info.num_whiles += sub_info.num_whiles;
818✔
195
        info.max_depth = std::max(info.max_depth, 1 + sub_info.max_depth);
818✔
196
        result.last_child_id = std::max(result.last_child_id, sub_state.local.last_child_id);
818✔
197

198
        has_side_effects |= sub_info.has_side_effects;
818✔
199
        is_perfectly_nested &= sub_info.is_perfectly_nested;
818✔
200
        is_perfectly_parallel &= sub_info.is_perfectly_parallel;
818✔
201
        is_elementwise &= sub_info.is_elementwise;
818✔
202
        if (map_stack_children) {
818✔
203
            map_stack_depth = sub_info.map_stack_depth; // only allowed if there is just a single, direct child
364✔
204
        }
364✔
205
    }
818✔
206

207
    info.is_perfectly_parallel = is_perfectly_parallel;
1,413✔
208
    auto child_count = loop_children.size();
1,413✔
209
    if (child_count > 1) {
1,413✔
210
        is_perfectly_nested = false;
98✔
211
    } else if (child_count < 1) {
1,315✔
212
        is_perfectly_nested = true;
718✔
213
    }
718✔
214
    info.is_perfectly_nested = is_perfectly_nested;
1,413✔
215
    info.is_elementwise = is_elementwise && is_perfectly_nested & is_perfectly_parallel;
1,413✔
216
    info.has_side_effects = has_side_effects;
1,413✔
217
    if (map_stack_member) {
1,413✔
218
        if (local.contains_non_perfectly_nested) { // needs to be perfectly nested to form a larger stack
803✔
219
            map_stack_depth = 0;
414✔
220
        }
414✔
221
        info.map_stack_depth = map_stack_depth + 1;
803✔
222
    }
803✔
223

224
    return result;
1,413✔
225
}
1,413✔
226

227
void LoopAnalysis::reindex_loop_nest_idx() {
11✔
228
    auto& root_loops = this->loop_children_.at(nullptr);
11✔
229

230
    for (size_t i = 0; i < root_loops.size(); ++i) {
43✔
231
        this->loop_infos_[root_loops.at(i)].nest.loopnest_index = i;
32✔
232
    }
32✔
233
}
11✔
234

235
void LoopAnalysis::run(AnalysisManager& analysis_manager) {
379✔
236
    this->loops_.clear();
379✔
237
    this->loop_tree_.clear();
379✔
238
    this->loop_infos_.clear();
379✔
239
    this->loop_children_.clear();
379✔
240
    this->loop_children_[nullptr]; // ensure it exists
379✔
241
    this->run(this->sdfg_.root(), nullptr, 0);
379✔
242

243
    // Set loopnest indices for outermost loops
244
    int loopnest_index = 0;
379✔
245
    auto& root_loops = this->loop_children_.at(nullptr);
379✔
246

247
    for (auto* root_loop : root_loops) {
592✔
248
        this->compute_loop_infos(root_loop);
592✔
249
        this->loop_infos_[root_loop].nest.loopnest_index = loopnest_index++;
592✔
250
    }
592✔
251
}
379✔
252

253
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::loops() const {
194✔
254
    return this->loops_;
194✔
255
} // copies by default...
194✔
256
const std::vector<structured_control_flow::ControlFlowNode*>& LoopAnalysis::loops_in_pre_order() const {
117✔
257
    return this->loops_;
117✔
258
}
117✔
259

260
LoopInfo LoopAnalysis::loop_info(structured_control_flow::ControlFlowNode* loop) const {
383✔
261
    return this->loop_infos_.at(loop).nest;
383✔
262
}
383✔
263

264
const LocalLoopInfo& LoopAnalysis::loop_info_local(structured_control_flow::ControlFlowNode* loop) const {
113✔
265
    return this->loop_infos_.at(loop).local;
113✔
266
}
113✔
267

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

279
const std::unordered_map<structured_control_flow::ControlFlowNode*, structured_control_flow::ControlFlowNode*>&
280
LoopAnalysis::loop_tree() const {
16✔
281
    return this->loop_tree_;
16✔
282
}
16✔
283

284
structured_control_flow::ControlFlowNode* LoopAnalysis::parent_loop(structured_control_flow::ControlFlowNode* loop
285
) const {
54✔
286
    return this->loop_tree_.at(loop);
54✔
287
}
54✔
288

289
const std::vector<structured_control_flow::ControlFlowNode*>& LoopAnalysis::outermost_loops() const {
152✔
290
    return loop_children_.at(nullptr);
152✔
291
}
152✔
292

293
bool LoopAnalysis::is_outermost_loop(structured_control_flow::ControlFlowNode* loop) const {
1✔
294
    if (this->loop_tree_.find(loop) == this->loop_tree_.end()) {
1✔
UNCOV
295
        return false;
×
296
    }
×
297
    return this->loop_tree_.at(loop) == nullptr;
1✔
298
}
1✔
299

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

320
const std::vector<sdfg::structured_control_flow::ControlFlowNode*>& LoopAnalysis::
321
    children(sdfg::structured_control_flow::ControlFlowNode* node) const {
8,341✔
322
    // Find unique child
323
    return loop_children_.at(node);
8,341✔
324
}
8,341✔
325

326
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::
327
    loop_tree_paths(sdfg::structured_control_flow::ControlFlowNode* loop) const {
330✔
328
    return this->loop_tree_paths(loop, this->loop_tree_);
330✔
329
};
330✔
330

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

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

353
    return paths;
331✔
354
};
707✔
355

356
std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::
357
    descendants(sdfg::structured_control_flow::ControlFlowNode* loop) const {
2,284✔
358
    std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> desc;
2,284✔
359
    std::list<sdfg::structured_control_flow::ControlFlowNode*> queue = {loop};
2,284✔
360
    while (!queue.empty()) { // TODO use ordered list directly
9,883✔
361
        auto current = queue.front();
7,599✔
362
        queue.pop_front();
7,599✔
363
        auto& children = this->children(current);
7,599✔
364
        for (auto& child : children) {
7,599✔
365
            if (desc.find(child) == desc.end()) {
5,315✔
366
                desc.insert(child);
5,315✔
367
                queue.push_back(child);
5,315✔
368
            }
5,315✔
369
        }
5,315✔
370
    }
7,599✔
371
    return desc;
2,284✔
372
}
2,284✔
373

UNCOV
374
void LoopAnalysis::dump_to_file(std::filesystem::path file) const {
×
375
    nlohmann::json arr;
×
376
    for (auto& loop : this->loops_) {
×
377
        auto& info = loop_infos_.at(loop);
×
378

379
        nlohmann::json entry;
×
UNCOV
380
        loop_info_local_to_json(entry["info"], info.local);
×
UNCOV
381
        entry["nest"] = loop_info_to_json(info.nest);
×
382
        arr.push_back(entry);
×
383
    }
×
384

385

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

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

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

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

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

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

428
    if (had_parent) {
4✔
429
        auto& pl = loop_infos_.at(new_parent_loop).local;
3✔
430
        pl.contains_side_effects = saved_side_effects;
3✔
431
        pl.contains_non_perfectly_nested = saved_non_perfect;
3✔
432
    }
3✔
433

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

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

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

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

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

460
    reindex_loop_nest_idx();
4✔
461
}
4✔
462

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

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

487
    // The moved subtree occupies the contiguous pre-order range [first_moved_idx, next_after_moved_idx).
488
    auto first_moved_idx = moved_state.local.loop_id;
2✔
489
    auto next_after_moved_idx = moved_state.local.last_child_id + 1;
2✔
490
    auto moved_span = next_after_moved_idx - first_moved_idx;
2✔
491

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

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

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

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

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

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

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

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

560
    reindex_loop_nest_idx();
2✔
561
}
2✔
562

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

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

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

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

609
    for (auto* elem : std::ranges::subrange(first_removed, next_not_removed) | std::views::reverse) {
12✔
610
        loop_infos_.erase(elem);
12✔
611
        loop_tree_.erase(elem);
12✔
612
        loop_children_.erase(elem);
12✔
613
    }
12✔
614

615
    auto& parent_children = loop_children_.at(parent_of_removed);
5✔
616
    parent_children
5✔
617
        .erase(std::remove(parent_children.begin(), parent_children.end(), existing_loop), parent_children.end());
5✔
618

619
    loops_.erase(first_removed, next_not_removed);
5✔
620

621
    reindex(first_removed, loops_.end());
5✔
622

623
    if (parent_of_removed != nullptr) {
5✔
624
        propagate_changed_nest_info(loops_.begin() + loop_infos_.at(parent_of_removed).local.loop_id);
2✔
625
    }
2✔
626

627
    reindex_loop_nest_idx();
5✔
628
}
5✔
629

630
void LoopAnalysis::
631
    added_local_contents(structured_control_flow::ControlFlowNode* loop, bool side_effects, bool non_perfectly_nested) {
3✔
632
    auto& state = loop_infos_.at(loop);
3✔
633
    state.local.contains_side_effects = side_effects;
3✔
634
    state.local.contains_non_perfectly_nested = non_perfectly_nested;
3✔
635

636
    propagate_changed_nest_info(loops_.begin() + state.local.loop_id);
3✔
637
}
3✔
638

639
void loop_info_local_to_json(nlohmann::json& j, const LocalLoopInfo& info) {
×
640
    j["loop_id"] = info.loop_id;
×
641
    j["last_child_id"] = info.last_child_id;
×
642
    j["loop_level"] = info.loop_level;
×
643
    j["type"] = info.type;
×
UNCOV
644
    j["is_elementwise"] = info.is_elementwise;
×
UNCOV
645
    j["contains_non_perfectly_nested"] = info.contains_non_perfectly_nested;
×
UNCOV
646
    j["contains_side_effects"] = info.contains_side_effects;
×
UNCOV
647
}
×
648

649
} // namespace analysis
650
} // 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