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

daisytuner / sdfglib / 20440855126

22 Dec 2025 06:40PM UTC coverage: 39.033% (-0.001%) from 39.034%
20440855126

push

github

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

Adds loop info to analysis and instrumentation

13484 of 44849 branches covered (30.07%)

Branch coverage included in aggregate %.

59 of 110 new or added lines in 7 files covered. (53.64%)

11 existing lines in 6 files now uncovered.

11622 of 19471 relevant lines covered (59.69%)

83.87 hits per line

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

54.6
/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_) {
262✔
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!
NEW
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✔
NEW
140
                    all_contiguous = false;
×
NEW
141
                    break;
×
142
                }
143
            }
144
            info.is_elementwise = all_contiguous;
8✔
145
        }
8✔
146

147
        this->loop_infos_[loop] = info;
150!
148
    }
75✔
149
}
65✔
150

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

153
const LoopInfo& LoopAnalysis::loop_info(structured_control_flow::ControlFlowNode* loop) const {
11✔
154
    return this->loop_infos_.at(loop);
11✔
155
}
156

157
structured_control_flow::ControlFlowNode* LoopAnalysis::find_loop_by_indvar(const std::string& indvar) {
×
158
    for (auto& loop : this->loops_) {
×
159
        if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(loop)) {
×
160
            if (loop_stmt->indvar()->get_name() == indvar) {
×
161
                return loop;
×
162
            }
163
        }
×
164
    }
165
    return nullptr;
×
166
}
×
167

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

171
    return symbolic::series::is_monotonic(loop->update(), loop->indvar(), assums);
75!
172
}
75✔
173

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

177
    return symbolic::series::is_contiguous(loop->update(), loop->indvar(), assums);
12!
178
}
12✔
179

180
symbolic::Expression LoopAnalysis::
181
    canonical_bound(structured_control_flow::StructuredLoop* loop, AssumptionsAnalysis& assumptions_analysis) {
3✔
182
    if (!LoopAnalysis::is_monotonic(loop, assumptions_analysis)) {
3!
183
        return SymEngine::null;
×
184
    }
185

186
    symbolic::CNF cnf;
3✔
187
    try {
188
        cnf = symbolic::conjunctive_normal_form(loop->condition());
3!
189
    } catch (const std::runtime_error& e) {
3!
190
        return SymEngine::null;
×
191
    }
×
192

193
    bool has_complex_clauses = false;
3✔
194
    for (auto& clause : cnf) {
8✔
195
        if (clause.size() > 1) {
5!
196
            has_complex_clauses = true;
×
197
            break;
×
198
        }
199
    }
200
    if (has_complex_clauses) {
3!
201
        return SymEngine::null;
×
202
    }
203

204
    auto indvar = loop->indvar();
3!
205
    symbolic::Expression bound = SymEngine::null;
3!
206
    for (auto& clause : cnf) {
8✔
207
        for (auto& literal : clause) {
10✔
208
            if (SymEngine::is_a<SymEngine::StrictLessThan>(*literal)) {
5!
209
                auto lt = SymEngine::rcp_dynamic_cast<const SymEngine::StrictLessThan>(literal);
3!
210
                auto lhs = lt->get_args()[0];
3!
211
                auto rhs = lt->get_args()[1];
3!
212
                if (SymEngine::eq(*lhs, *indvar)) {
3!
213
                    if (bound == SymEngine::null) {
3!
214
                        bound = rhs;
2!
215
                    } else {
2✔
216
                        bound = symbolic::min(bound, rhs);
1!
217
                    }
218
                } else {
3✔
219
                    return SymEngine::null;
×
220
                }
221
            } else if (SymEngine::is_a<SymEngine::LessThan>(*literal)) {
5!
222
                auto le = SymEngine::rcp_dynamic_cast<const SymEngine::LessThan>(literal);
2!
223
                auto lhs = le->get_args()[0];
2!
224
                auto rhs = le->get_args()[1];
2!
225
                if (SymEngine::eq(*lhs, *indvar)) {
2!
226
                    if (bound == SymEngine::null) {
2!
227
                        bound = symbolic::add(rhs, symbolic::one());
1!
228
                    } else {
1✔
229
                        bound = symbolic::min(bound, symbolic::add(rhs, symbolic::one()));
1!
230
                    }
231
                } else {
2✔
232
                    return SymEngine::null;
×
233
                }
234
            } else {
2!
235
                return SymEngine::null;
×
236
            }
237
        }
238
    }
239

240
    return bound;
3✔
241
}
3✔
242

243
symbolic::Integer LoopAnalysis::stride(structured_control_flow::StructuredLoop* loop) {
×
244
    auto expr = loop->update();
×
245
    auto indvar = loop->indvar();
×
246

247
    if (SymEngine::is_a<SymEngine::Add>(*expr)) {
×
248
        auto add_expr = SymEngine::rcp_static_cast<const SymEngine::Add>(expr);
×
249
        if (add_expr->get_args().size() != 2) {
×
250
            return SymEngine::null;
×
251
        }
252
        auto arg1 = add_expr->get_args()[0];
×
253
        auto arg2 = add_expr->get_args()[1];
×
254
        if (symbolic::eq(arg1, indvar)) {
×
255
            if (SymEngine::is_a<SymEngine::Integer>(*arg2)) {
×
256
                return SymEngine::rcp_static_cast<const SymEngine::Integer>(arg2);
×
257
            }
258
        }
×
259
        if (symbolic::eq(arg2, indvar)) {
×
260
            if (SymEngine::is_a<SymEngine::Integer>(*arg1)) {
×
261
                return SymEngine::rcp_static_cast<const SymEngine::Integer>(arg1);
×
262
            }
263
        }
×
264
    }
×
265
    return SymEngine::null;
×
266
}
×
267

268
const std::map<structured_control_flow::ControlFlowNode*, structured_control_flow::ControlFlowNode*, DFSLoopComparator>&
269
LoopAnalysis::loop_tree() const {
8✔
270
    return this->loop_tree_;
8✔
271
}
272

273
structured_control_flow::ControlFlowNode* LoopAnalysis::parent_loop(structured_control_flow::ControlFlowNode* loop
×
274
) const {
275
    return this->loop_tree_.at(loop);
×
276
}
277

278
const std::vector<structured_control_flow::ControlFlowNode*> LoopAnalysis::outermost_loops() const {
6✔
279
    std::vector<structured_control_flow::ControlFlowNode*> outermost_loops_;
6✔
280
    for (const auto& [loop, parent] : this->loop_tree_) {
29✔
281
        if (parent == nullptr) {
23✔
282
            outermost_loops_.push_back(loop);
13!
283
        }
13✔
284
    }
285
    return outermost_loops_;
6✔
286
}
6!
287

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

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

315
std::vector<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::
316
    children(sdfg::structured_control_flow::ControlFlowNode* node) const {
115✔
317
    // Find unique child
318
    return this->children(node, this->loop_tree_);
115✔
319
};
320

321
std::vector<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::children(
468✔
322
    sdfg::structured_control_flow::ControlFlowNode* node,
323
    const std::map<
324
        sdfg::structured_control_flow::ControlFlowNode*,
325
        sdfg::structured_control_flow::ControlFlowNode*,
326
        DFSLoopComparator>& tree
327
) const {
328
    // Find unique child
329
    std::vector<sdfg::structured_control_flow::ControlFlowNode*> c;
468✔
330
    for (auto& entry : tree) {
1,707✔
331
        if (entry.second == node) {
1,239✔
332
            c.push_back(entry.first);
173!
333
        }
173✔
334
    }
335
    return c;
468✔
336
};
468!
337

338
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::
339
    loop_tree_paths(sdfg::structured_control_flow::ControlFlowNode* loop) const {
77✔
340
    return this->loop_tree_paths(loop, this->loop_tree_);
77✔
341
};
342

343
std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> LoopAnalysis::loop_tree_paths(
128✔
344
    sdfg::structured_control_flow::ControlFlowNode* loop,
345
    const std::map<
346
        sdfg::structured_control_flow::ControlFlowNode*,
347
        sdfg::structured_control_flow::ControlFlowNode*,
348
        DFSLoopComparator>& tree
349
) const {
350
    // Collect all paths in tree starting from loop recursively (DFS)
351
    std::list<std::vector<sdfg::structured_control_flow::ControlFlowNode*>> paths;
128✔
352
    auto children = this->children(loop, tree);
128!
353
    if (children.empty()) {
128✔
354
        paths.push_back({loop});
81!
355
        return paths;
81✔
356
    }
357

358
    for (auto& child : children) {
98✔
359
        auto p = this->loop_tree_paths(child, tree);
51!
360
        for (auto& path : p) {
102✔
361
            path.insert(path.begin(), loop);
51!
362
            paths.push_back(path);
51!
363
        }
364
    }
51✔
365

366
    return paths;
47✔
367
};
128!
368

369
std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> LoopAnalysis::
370
    descendants(sdfg::structured_control_flow::ControlFlowNode* loop) const {
154✔
371
    std::unordered_set<sdfg::structured_control_flow::ControlFlowNode*> desc;
154✔
372
    std::list<sdfg::structured_control_flow::ControlFlowNode*> queue = {loop};
154!
373
    while (!queue.empty()) {
379✔
374
        auto current = queue.front();
225✔
375
        queue.pop_front();
225✔
376
        auto children = this->children(current, this->loop_tree_);
225!
377
        for (auto& child : children) {
296✔
378
            if (desc.find(child) == desc.end()) {
71!
379
                desc.insert(child);
71!
380
                queue.push_back(child);
71!
381
            }
71✔
382
        }
383
    }
225✔
384
    return desc;
154✔
385
}
154!
386

387
} // namespace analysis
388
} // 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