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

daisytuner / sdfglib / 20478008532

23 Dec 2025 01:16PM UTC coverage: 39.061% (+0.03%) from 39.033%
20478008532

push

github

web-flow
Merge pull request #405 from daisytuner/extend-loop-info

extends loop info with has_side_effects flag

13536 of 44970 branches covered (30.1%)

Branch coverage included in aggregate %.

31 of 46 new or added lines in 2 files covered. (67.39%)

3 existing lines in 2 files now uncovered.

11653 of 19517 relevant lines covered (59.71%)

83.94 hits per line

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

54.66
/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
#include "sdfg/symbolic/series.h"
11

12
namespace sdfg {
13
namespace analysis {
14

15
LoopAnalysis::LoopAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg), loops_(), loop_tree_(DFSLoopComparator(&loops_)) {}
65!
16

17
void LoopAnalysis::
18
    run(structured_control_flow::ControlFlowNode& scope, structured_control_flow::ControlFlowNode* parent_loop) {
187✔
19
    std::list<structured_control_flow::ControlFlowNode*> queue = {&scope};
187!
20
    while (!queue.empty()) {
584✔
21
        auto current = queue.front();
397✔
22
        queue.pop_front();
397✔
23

24
        // Loop detected
25
        if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
397!
26
            this->loops_.push_back(while_stmt);
2!
27
            this->loop_tree_[while_stmt] = parent_loop;
2!
28
        } else if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
397!
29
            this->loops_.push_back(loop_stmt);
120!
30
            this->loop_tree_[loop_stmt] = parent_loop;
120!
31
        }
120✔
32

33
        if (dynamic_cast<structured_control_flow::Block*>(current)) {
397!
34
            continue;
81✔
35
        } else if (auto sequence_stmt = dynamic_cast<structured_control_flow::Sequence*>(current)) {
316!
36
            for (size_t i = 0; i < sequence_stmt->size(); i++) {
397!
37
                queue.push_back(&sequence_stmt->at(i).first);
206!
38
            }
206✔
39
        } else if (auto if_else_stmt = dynamic_cast<structured_control_flow::IfElse*>(current)) {
316!
40
            for (size_t i = 0; i < if_else_stmt->size(); i++) {
7!
41
                queue.push_back(&if_else_stmt->at(i).first);
4!
42
            }
4✔
43
        } else if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
125!
44
            this->run(while_stmt->root(), while_stmt);
2!
45
        } else if (auto for_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
122!
46
            this->run(for_stmt->root(), for_stmt);
120!
47
        } else if (dynamic_cast<structured_control_flow::Break*>(current)) {
120!
48
            continue;
×
49
        } else if (dynamic_cast<structured_control_flow::Continue*>(current)) {
×
50
            continue;
×
51
        } else if (dynamic_cast<structured_control_flow::Return*>(current)) {
×
52
            continue;
×
53
        } else {
54
            throw std::runtime_error("Unsupported control flow node type");
×
55
        }
56
    }
57
}
187✔
58

59
void LoopAnalysis::run(AnalysisManager& analysis_manager) {
65✔
60
    this->loops_.clear();
65✔
61
    this->loop_tree_.clear();
65✔
62
    this->loop_infos_.clear();
65✔
63
    this->run(this->sdfg_.root(), nullptr);
65✔
64

65
    // Loop info for outermost loops
66
    for (const auto& [loop, parent] : this->loop_tree_) {
411✔
67
        if (parent != nullptr) {
122✔
68
            continue;
47✔
69
        }
70

71
        LoopInfo info;
72
        auto descendants = this->descendants(loop);
75✔
73
        descendants.insert(loop);
75!
74

75
        // Structure of loop nest
76
        info.num_loops = descendants.size();
75✔
77
        info.max_depth = 0;
75✔
78
        for (const auto& path : this->loop_tree_paths(loop)) {
153!
79
            info.max_depth = std::max(info.max_depth, path.size());
78!
80
        }
81

82
        info.is_perfectly_nested = true;
75✔
83
        auto current = loop;
75✔
84
        while (true) {
112✔
85
            auto children = this->children(current);
112!
86
            if (children.empty()) {
112✔
87
                break;
68✔
88
            }
89

90
            if (children.size() > 1) {
44✔
91
                info.is_perfectly_nested = false;
3✔
92
                break;
3✔
93
            }
94

95
            auto child = children[0];
41✔
96
            structured_control_flow::Sequence* root = nullptr;
41✔
97

98
            if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(current)) {
41!
99
                root = &while_stmt->root();
×
100
            } else if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
41!
101
                root = &loop_stmt->root();
41!
102
            }
41✔
103

104
            if (root == nullptr || root->size() != 1 || &root->at(0).first != child) {
41!
105
                info.is_perfectly_nested = false;
4✔
106
                break;
4✔
107
            }
108

109
            current = child;
37✔
110
        }
112✔
111

112
        // Count types of loops
113
        info.num_maps = 0;
75✔
114
        info.num_fors = 0;
75✔
115
        info.num_whiles = 0;
75✔
116
        for (auto node : descendants) {
197✔
117
            if (dynamic_cast<structured_control_flow::Map*>(node)) {
122!
118
                info.num_maps++;
20✔
119
            } else if (dynamic_cast<structured_control_flow::StructuredLoop*>(node)) {
122!
120
                info.num_fors++;
100✔
121
            } else if (dynamic_cast<structured_control_flow::While*>(node)) {
102!
122
                info.num_whiles++;
2✔
123
            }
2✔
124
        }
125
        info.is_perfectly_parallel = (info.num_loops == info.num_maps);
75✔
126

127
        // Classifiy loop nest
128
        info.is_elementwise = false;
75✔
129
        if (info.is_perfectly_nested && info.is_perfectly_parallel) {
75✔
130
            bool all_contiguous = true;
8✔
131
            for (auto node : descendants) {
16✔
132
                if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(node)) {
11!
133
                    bool is_contiguous =
11✔
134
                        symbolic::series::is_contiguous(loop_stmt->update(), loop_stmt->indvar(), symbolic::Assumptions());
11!
135
                    if (!is_contiguous) {
11✔
136
                        all_contiguous = false;
3✔
137
                        break;
3✔
138
                    }
139
                } else {
8✔
140
                    all_contiguous = false;
×
141
                    break;
×
142
                }
143
            }
144
            info.is_elementwise = all_contiguous;
8✔
145
        }
8✔
146

147
        // Criterion: Loop must not have side-effecting body
148
        structured_control_flow::Sequence* root = nullptr;
75✔
149
        if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(loop)) {
75!
150
            root = &while_stmt->root();
1!
151
        } else if (auto loop_stmt = dynamic_cast<structured_control_flow::For*>(loop)) {
75!
152
            root = &loop_stmt->root();
64!
153
        }
64✔
154
        // Maps cannot have side effects by definition
155

156
        info.has_side_effects = false;
75✔
157
        if (root != nullptr) {
75✔
158
            std::list<const structured_control_flow::ControlFlowNode*> queue = {root};
65!
159
            while (!queue.empty()) {
283✔
160
                auto current = queue.front();
218✔
161
                queue.pop_front();
218✔
162

163
                if (auto block = dynamic_cast<const structured_control_flow::Block*>(current)) {
218!
164
                    for (auto& node : block->dataflow().nodes()) {
329!
165
                        if (auto library_node = dynamic_cast<const data_flow::LibraryNode*>(&node)) {
254!
NEW
166
                            if (library_node->side_effect()) {
×
NEW
167
                                info.has_side_effects = true;
×
NEW
168
                                break;
×
169
                            }
NEW
170
                        }
×
171
                    }
172
                } else if (auto seq = dynamic_cast<const structured_control_flow::Sequence*>(current)) {
218!
173
                    for (size_t i = 0; i < seq->size(); i++) {
218!
174
                        auto& child = seq->at(i).first;
114!
175
                        queue.push_back(&child);
114!
176
                    }
114✔
177
                } else if (auto ifelse = dynamic_cast<const structured_control_flow::IfElse*>(current)) {
143!
178
                    for (size_t i = 0; i < ifelse->size(); i++) {
7!
179
                        auto& branch = ifelse->at(i).first;
4!
180
                        queue.push_back(&branch);
4!
181
                    }
4✔
182
                } else if (auto loop = dynamic_cast<const structured_control_flow::For*>(current)) {
39!
183
                    queue.push_back(&loop->root());
35!
184
                } else if (auto while_stmt = dynamic_cast<const structured_control_flow::While*>(current)) {
36!
NEW
185
                    queue.push_back(&while_stmt->root());
×
186
                } else if (auto loop = dynamic_cast<const structured_control_flow::Map*>(current)) {
1!
187
                    continue;
1✔
NEW
188
                } else if (auto for_stmt = dynamic_cast<const structured_control_flow::Break*>(current)) {
×
NEW
189
                    continue;
×
NEW
190
                } else if (auto for_stmt = dynamic_cast<const structured_control_flow::Continue*>(current)) {
×
NEW
191
                    continue;
×
NEW
192
                } else if (auto for_stmt = dynamic_cast<const structured_control_flow::Return*>(current)) {
×
NEW
193
                    info.has_side_effects = true;
×
NEW
194
                    break;
×
195
                } else {
NEW
196
                    throw InvalidSDFGException("Unknown control flow node type in Loop Analysis.");
×
197
                }
198
            }
199
        }
65✔
200

201
        this->loop_infos_[loop] = info;
150!
202
    }
75✔
203
}
65✔
204

205
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::loops() const { return this->loops_; }
4✔
206

207
const LoopInfo& LoopAnalysis::loop_info(structured_control_flow::ControlFlowNode* loop) const {
11✔
208
    return this->loop_infos_.at(loop);
11✔
209
}
210

211
structured_control_flow::ControlFlowNode* LoopAnalysis::find_loop_by_indvar(const std::string& indvar) {
×
212
    for (auto& loop : this->loops_) {
×
213
        if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(loop)) {
×
214
            if (loop_stmt->indvar()->get_name() == indvar) {
×
215
                return loop;
×
216
            }
217
        }
×
218
    }
219
    return nullptr;
×
220
}
×
221

222
bool LoopAnalysis::is_monotonic(structured_control_flow::StructuredLoop* loop, AssumptionsAnalysis& assumptions_analysis) {
75✔
223
    auto assums = assumptions_analysis.get(*loop, true);
75✔
224

225
    return symbolic::series::is_monotonic(loop->update(), loop->indvar(), assums);
75!
226
}
75✔
227

228
bool LoopAnalysis::is_contiguous(structured_control_flow::StructuredLoop* loop, AssumptionsAnalysis& assumptions_analysis) {
12✔
229
    auto assums = assumptions_analysis.get(*loop, true);
12✔
230

231
    return symbolic::series::is_contiguous(loop->update(), loop->indvar(), assums);
12!
232
}
12✔
233

234
symbolic::Expression LoopAnalysis::
235
    canonical_bound(structured_control_flow::StructuredLoop* loop, AssumptionsAnalysis& assumptions_analysis) {
3✔
236
    if (!LoopAnalysis::is_monotonic(loop, assumptions_analysis)) {
3!
237
        return SymEngine::null;
×
238
    }
239

240
    symbolic::CNF cnf;
3✔
241
    try {
242
        cnf = symbolic::conjunctive_normal_form(loop->condition());
3!
243
    } catch (const std::runtime_error& e) {
3!
244
        return SymEngine::null;
×
245
    }
×
246

247
    bool has_complex_clauses = false;
3✔
248
    for (auto& clause : cnf) {
8✔
249
        if (clause.size() > 1) {
5!
250
            has_complex_clauses = true;
×
251
            break;
×
252
        }
253
    }
254
    if (has_complex_clauses) {
3!
255
        return SymEngine::null;
×
256
    }
257

258
    auto indvar = loop->indvar();
3!
259
    symbolic::Expression bound = SymEngine::null;
3!
260
    for (auto& clause : cnf) {
8✔
261
        for (auto& literal : clause) {
10✔
262
            if (SymEngine::is_a<SymEngine::StrictLessThan>(*literal)) {
5!
263
                auto lt = SymEngine::rcp_dynamic_cast<const SymEngine::StrictLessThan>(literal);
3!
264
                auto lhs = lt->get_args()[0];
3!
265
                auto rhs = lt->get_args()[1];
3!
266
                if (SymEngine::eq(*lhs, *indvar)) {
3!
267
                    if (bound == SymEngine::null) {
3!
268
                        bound = rhs;
2!
269
                    } else {
2✔
270
                        bound = symbolic::min(bound, rhs);
1!
271
                    }
272
                } else {
3✔
273
                    return SymEngine::null;
×
274
                }
275
            } else if (SymEngine::is_a<SymEngine::LessThan>(*literal)) {
5!
276
                auto le = SymEngine::rcp_dynamic_cast<const SymEngine::LessThan>(literal);
2!
277
                auto lhs = le->get_args()[0];
2!
278
                auto rhs = le->get_args()[1];
2!
279
                if (SymEngine::eq(*lhs, *indvar)) {
2!
280
                    if (bound == SymEngine::null) {
2!
281
                        bound = symbolic::add(rhs, symbolic::one());
1!
282
                    } else {
1✔
283
                        bound = symbolic::min(bound, symbolic::add(rhs, symbolic::one()));
1!
284
                    }
285
                } else {
2✔
286
                    return SymEngine::null;
×
287
                }
288
            } else {
2!
289
                return SymEngine::null;
×
290
            }
291
        }
292
    }
293

294
    return bound;
3✔
295
}
3✔
296

297
symbolic::Integer LoopAnalysis::stride(structured_control_flow::StructuredLoop* loop) {
×
298
    auto expr = loop->update();
×
299
    auto indvar = loop->indvar();
×
300

301
    if (SymEngine::is_a<SymEngine::Add>(*expr)) {
×
302
        auto add_expr = SymEngine::rcp_static_cast<const SymEngine::Add>(expr);
×
303
        if (add_expr->get_args().size() != 2) {
×
304
            return SymEngine::null;
×
305
        }
306
        auto arg1 = add_expr->get_args()[0];
×
307
        auto arg2 = add_expr->get_args()[1];
×
308
        if (symbolic::eq(arg1, indvar)) {
×
309
            if (SymEngine::is_a<SymEngine::Integer>(*arg2)) {
×
310
                return SymEngine::rcp_static_cast<const SymEngine::Integer>(arg2);
×
311
            }
312
        }
×
313
        if (symbolic::eq(arg2, indvar)) {
×
314
            if (SymEngine::is_a<SymEngine::Integer>(*arg1)) {
×
315
                return SymEngine::rcp_static_cast<const SymEngine::Integer>(arg1);
×
316
            }
317
        }
×
318
    }
×
319
    return SymEngine::null;
×
320
}
×
321

322
const std::map<structured_control_flow::ControlFlowNode*, structured_control_flow::ControlFlowNode*, DFSLoopComparator>&
323
LoopAnalysis::loop_tree() const {
8✔
324
    return this->loop_tree_;
8✔
325
}
326

327
structured_control_flow::ControlFlowNode* LoopAnalysis::parent_loop(structured_control_flow::ControlFlowNode* loop
×
328
) const {
329
    return this->loop_tree_.at(loop);
×
330
}
331

332
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::outermost_loops() const {
6✔
333
    std::vector<structured_control_flow::ControlFlowNode*> outermost_loops_;
6✔
334
    for (const auto& [loop, parent] : this->loop_tree_) {
29✔
335
        if (parent == nullptr) {
23✔
336
            outermost_loops_.push_back(loop);
13!
337
        }
13✔
338
    }
339
    return outermost_loops_;
6✔
340
}
6!
341

342
bool LoopAnalysis::is_outermost_loop(structured_control_flow::ControlFlowNode* loop) const {
×
343
    if (this->loop_tree_.find(loop) == this->loop_tree_.end()) {
×
344
        return false;
×
345
    }
346
    return this->loop_tree_.at(loop) == nullptr;
×
347
}
×
348

349
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::outermost_maps() const {
×
350
    std::vector<structured_control_flow::ControlFlowNode*> outermost_maps_;
×
351
    for (const auto& [loop, parent] : this->loop_tree_) {
×
352
        if (dynamic_cast<structured_control_flow::Map*>(loop)) {
×
353
            auto ancestor = parent;
×
354
            while (true) {
×
355
                if (ancestor == nullptr) {
×
356
                    outermost_maps_.push_back(loop);
×
357
                    break;
×
358
                }
359
                if (dynamic_cast<structured_control_flow::Map*>(ancestor)) {
×
360
                    break;
×
361
                }
362
                ancestor = this->loop_tree_.at(ancestor);
×
363
            }
364
        }
×
365
    }
366
    return outermost_maps_;
×
367
}
×
368

369
std::vector<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::
370
    children(sdfg::structured_control_flow::ControlFlowNode* node) const {
115✔
371
    // Find unique child
372
    return this->children(node, this->loop_tree_);
115✔
373
};
374

375
std::vector<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::children(
468✔
376
    sdfg::structured_control_flow::ControlFlowNode* node,
377
    const std::map<
378
        sdfg::structured_control_flow::ControlFlowNode*,
379
        sdfg::structured_control_flow::ControlFlowNode*,
380
        DFSLoopComparator>& tree
381
) const {
382
    // Find unique child
383
    std::vector<sdfg::structured_control_flow::ControlFlowNode*> c;
468✔
384
    for (auto& entry : tree) {
1,707✔
385
        if (entry.second == node) {
1,239✔
386
            c.push_back(entry.first);
173!
387
        }
173✔
388
    }
389
    return c;
468✔
390
};
468!
391

392
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::
393
    loop_tree_paths(sdfg::structured_control_flow::ControlFlowNode* loop) const {
77✔
394
    return this->loop_tree_paths(loop, this->loop_tree_);
77✔
395
};
396

397
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::loop_tree_paths(
128✔
398
    sdfg::structured_control_flow::ControlFlowNode* loop,
399
    const std::map<
400
        sdfg::structured_control_flow::ControlFlowNode*,
401
        sdfg::structured_control_flow::ControlFlowNode*,
402
        DFSLoopComparator>& tree
403
) const {
404
    // Collect all paths in tree starting from loop recursively (DFS)
405
    std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> paths;
128✔
406
    auto children = this->children(loop, tree);
128!
407
    if (children.empty()) {
128✔
408
        paths.push_back({loop});
81!
409
        return paths;
81✔
410
    }
411

412
    for (auto& child : children) {
98✔
413
        auto p = this->loop_tree_paths(child, tree);
51!
414
        for (auto& path : p) {
102✔
415
            path.insert(path.begin(), loop);
51!
416
            paths.push_back(path);
51!
417
        }
418
    }
51✔
419

420
    return paths;
47✔
421
};
128!
422

423
std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::
424
    descendants(sdfg::structured_control_flow::ControlFlowNode* loop) const {
154✔
425
    std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> desc;
154✔
426
    std::list<sdfg::structured_control_flow::ControlFlowNode*> queue = {loop};
154!
427
    while (!queue.empty()) {
379✔
428
        auto current = queue.front();
225✔
429
        queue.pop_front();
225✔
430
        auto children = this->children(current, this->loop_tree_);
225!
431
        for (auto& child : children) {
296✔
432
            if (desc.find(child) == desc.end()) {
71!
433
                desc.insert(child);
71!
434
                queue.push_back(child);
71!
435
            }
71✔
436
        }
437
    }
225✔
438
    return desc;
154✔
439
}
154!
440

441
} // namespace analysis
442
} // 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