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

daisytuner / docc / 28809522800

06 Jul 2026 05:12PM UTC coverage: 62.882% (-0.08%) from 62.96%
28809522800

Pull #843

github

web-flow
Merge beb5b23d7 into 95ea95f1f
Pull Request #843: adds type_ids for Element classes and a human-readable element_type

413 of 587 new or added lines in 121 files covered. (70.36%)

74 existing lines in 6 files now uncovered.

40564 of 64508 relevant lines covered (62.88%)

961.48 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/structured_control_flow/map.h"
6
#include "sdfg/structured_control_flow/reduce.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_() {}
472✔
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,759✔
24
    auto is_elementwise = (map != nullptr && map->is_contiguous());
1,759✔
25
    LocalLoopInfo::LoopType type;
1,759✔
26
    if (while_loop != nullptr) {
1,759✔
27
        type = LocalLoopInfo::LoopType::While;
12✔
28
    } else if (map != nullptr) {
1,747✔
29
        type = LocalLoopInfo::LoopType::Map;
928✔
30
    } else {
928✔
31
        type = LocalLoopInfo::LoopType::For;
819✔
32
    }
819✔
33

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

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

52
    while (!queue.empty()) {
7,576✔
53
        auto current = queue.front();
5,341✔
54
        queue.pop_front();
5,341✔
55

56
        structured_control_flow::While* new_while = nullptr;
5,341✔
57
        structured_control_flow::Map* new_map = nullptr;
5,341✔
58
        structured_control_flow::For* new_for = nullptr;
5,341✔
59
        structured_control_flow::ControlFlowNode* new_loop = nullptr;
5,341✔
60
        // Loop detected
61
        if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
5,341✔
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)) {
5,329✔
65
            new_map = map_stmt;
928✔
66
            new_loop = map_stmt;
928✔
67
        } else if (auto for_stmt = dynamic_cast<structured_control_flow::For*>(current)) {
4,401✔
68
            new_for = for_stmt;
757✔
69
            new_loop = for_stmt;
757✔
70
        } else if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
3,644✔
71
            // Generic structured loop (e.g. Reduce) that is neither a Map nor a For.
72
            new_loop = loop_stmt;
62✔
73
        }
62✔
74

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

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

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

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

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

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

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

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

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

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

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

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

223
    return result;
1,779✔
224
}
1,779✔
225

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

384

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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