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

daisytuner / docc / 23978459061

04 Apr 2026 12:00PM UTC coverage: 64.742%. First build
23978459061

Pull #644

github

web-flow
Merge 6d8b19c63 into 811a5c32e
Pull Request #644: adapts new loop analysis api

39 of 44 new or added lines in 16 files covered. (88.64%)

29101 of 44949 relevant lines covered (64.74%)

532.86 hits per line

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

78.82
/sdfg/src/analysis/flop_analysis.cpp
1
#include "sdfg/analysis/flop_analysis.h"
2
#include <cassert>
3
#include <cstddef>
4
#include <string>
5
#include <unordered_map>
6
#include <unordered_set>
7
#include <vector>
8
#include "sdfg/analysis/analysis.h"
9
#include "sdfg/analysis/assumptions_analysis.h"
10
#include "sdfg/analysis/loop_analysis.h"
11
#include "sdfg/analysis/scope_analysis.h"
12
#include "sdfg/analysis/users.h"
13
#include "sdfg/data_flow/tasklet.h"
14
#include "sdfg/exceptions.h"
15
#include "sdfg/structured_control_flow/block.h"
16
#include "sdfg/structured_control_flow/control_flow_node.h"
17
#include "sdfg/structured_control_flow/if_else.h"
18
#include "sdfg/structured_control_flow/return.h"
19
#include "sdfg/structured_control_flow/sequence.h"
20
#include "sdfg/structured_control_flow/structured_loop.h"
21
#include "sdfg/structured_control_flow/while.h"
22
#include "sdfg/structured_sdfg.h"
23
#include "sdfg/symbolic/assumptions.h"
24
#include "sdfg/symbolic/polynomials.h"
25
#include "sdfg/symbolic/symbolic.h"
26
#include "symengine/functions.h"
27
#include "symengine/symengine_rcp.h"
28
#include "symengine/visitor.h"
29

30
namespace sdfg {
31
namespace analysis {
32

33
/// An expression is a parameter expression if all its symbols are parameters
34
bool FlopAnalysis::is_parameter_expression(const symbolic::SymbolSet& parameters, const symbolic::Expression& expr) {
318✔
35
    if (expr.is_null()) {
318✔
36
        return false;
×
37
    }
×
38
    for (auto& sym : symbolic::atoms(expr)) {
318✔
39
        if (!parameters.contains(sym)) {
166✔
40
            return false;
4✔
41
        }
4✔
42
    }
166✔
43
    return true;
314✔
44
}
318✔
45

46
symbolic::ExpressionSet FlopAnalysis::
47
    choose_bounds(const symbolic::SymbolSet& parameters, const symbolic::ExpressionSet& bounds) {
4✔
48
    symbolic::ExpressionSet result;
4✔
49
    for (auto& bound : bounds) {
4✔
50
        if (symbolic::eq(bound, SymEngine::NegInf) || symbolic::eq(bound, SymEngine::Inf)) {
4✔
51
            // Skip infinities
52
            continue;
×
53
        } else if (SymEngine::is_a<SymEngine::Integer>(*bound)) {
4✔
54
            // Collect integers
55
            result.insert(bound);
×
56
        } else if (!symbolic::has_dynamic_sizeof(bound) && this->is_parameter_expression(parameters, bound)) {
4✔
57
            // Collect parameter expressions if they do not contain dynamic_sizeof
58
            result.insert(bound);
4✔
59
        }
4✔
60
    }
4✔
61
    if (result.empty()) {
4✔
62
        // Fallback if no integers or parameter expressions were found
63
        return bounds;
×
64
    } else {
4✔
65
        return result;
4✔
66
    }
4✔
67
}
4✔
68

69
symbolic::Expression FlopAnalysis::replace_loop_indices(
70
    const symbolic::SymbolSet& parameters, const symbolic::Expression expr, symbolic::Assumptions& assumptions
71
) {
544✔
72
    symbolic::Expression result = expr;
544✔
73
    auto atoms = symbolic::atoms(result);
544✔
74
    for (auto sym : atoms) {
544✔
75
        if (!assumptions.contains(sym)) continue;
162✔
76
        symbolic::Assumption assumption = assumptions.at(sym);
162✔
77
        if (!assumption.constant() || assumption.map().is_null()) continue;
162✔
78
        symbolic::Expression ub, lb;
6✔
79
        if (assumption.tight_upper_bound().is_null()) {
6✔
80
            auto bounds = this->choose_bounds(parameters, assumption.upper_bounds());
×
81
            if (bounds.empty()) {
×
82
                ub = assumption.upper_bound();
×
83
            } else {
×
84
                ub = SymEngine::min(std::vector<symbolic::Expression>(bounds.begin(), bounds.end()));
×
85
            }
×
86
        } else {
6✔
87
            ub = assumption.tight_upper_bound();
6✔
88
        }
6✔
89
        if (assumption.tight_lower_bound().is_null()) {
6✔
90
            auto bounds = this->choose_bounds(parameters, assumption.lower_bounds());
×
91
            if (bounds.empty()) {
×
92
                lb = assumption.lower_bound();
×
93
            } else {
×
94
                lb = SymEngine::max(std::vector<symbolic::Expression>(bounds.begin(), bounds.end()));
×
95
            }
×
96
        } else {
6✔
97
            lb = assumption.tight_lower_bound();
6✔
98
        }
6✔
99
        result = symbolic::subs(result, sym, symbolic::div(symbolic::sub(ub, lb), symbolic::integer(2)));
6✔
100
        this->precise_ = false;
6✔
101
    }
6✔
102
    return result;
544✔
103
}
544✔
104

105
symbolic::SymbolSet FlopAnalysis::
106
    get_scope_parameters(const structured_control_flow::ControlFlowNode& scope, AnalysisManager& analysis_manager) {
228✔
107
    auto& users_analysis = analysis_manager.get<Users>();
228✔
108
    analysis::UsersView users_view(users_analysis, scope);
228✔
109

110
    std::unordered_set<std::string> all_uses, illegal_uses;
228✔
111
    for (auto* user : users_view.uses()) {
2,677✔
112
        if (!sdfg_.exists(user->container())) {
2,677✔
113
            continue;
×
114
        }
×
115

116
        Use not_allowed;
2,677✔
117
        switch (this->sdfg_.type(user->container()).type_id()) {
2,677✔
118
            case types::TypeID::Scalar:
2,137✔
119
                not_allowed = Use::WRITE;
2,137✔
120
                break;
2,137✔
121
            case types::TypeID::Pointer:
540✔
122
            case types::TypeID::Array:
540✔
123
                not_allowed = Use::MOVE;
540✔
124
                break;
540✔
125
            default:
×
126
                continue;
×
127
        }
2,677✔
128
        all_uses.insert(user->container());
2,677✔
129
        if (user->use() == not_allowed) {
2,677✔
130
            illegal_uses.insert(user->container());
528✔
131
        }
528✔
132
    }
2,677✔
133

134
    symbolic::SymbolSet parameters;
228✔
135
    for (auto& container : all_uses) {
973✔
136
        if (!illegal_uses.contains(container)) {
973✔
137
            parameters.insert(symbolic::symbol(container));
691✔
138
        }
691✔
139
    }
973✔
140

141
    return parameters;
228✔
142
}
228✔
143

144
symbolic::Expression FlopAnalysis::visit(structured_control_flow::ControlFlowNode& node, AnalysisManager& analysis_manager) {
176✔
145
    if (auto sequence = dynamic_cast<structured_control_flow::Sequence*>(&node)) {
176✔
146
        return this->visit_sequence(*sequence, analysis_manager);
2✔
147
    } else if (auto block = dynamic_cast<structured_control_flow::Block*>(&node)) {
174✔
148
        return this->visit_block(*block, analysis_manager);
72✔
149
    } else if (auto structured_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&node)) {
102✔
150
        return this->visit_structured_loop(*structured_loop, analysis_manager);
78✔
151
    } else if (auto if_else = dynamic_cast<structured_control_flow::IfElse*>(&node)) {
78✔
152
        return this->visit_if_else(*if_else, analysis_manager);
1✔
153
    } else if (auto while_loop = dynamic_cast<structured_control_flow::While*>(&node)) {
23✔
154
        return this->visit_while(*while_loop, analysis_manager);
22✔
155
    } else if (dynamic_cast<structured_control_flow::Return*>(&node) ||
22✔
156
               dynamic_cast<structured_control_flow::Break*>(&node) ||
1✔
157
               dynamic_cast<structured_control_flow::Continue*>(&node)) {
1✔
158
        this->flops_[&node] = symbolic::zero();
1✔
159
        return symbolic::zero();
1✔
160
    } else {
1✔
161
        this->flops_[&node] = SymEngine::null;
×
162
        this->precise_ = false;
×
163
        return SymEngine::null;
×
164
    }
×
165
}
176✔
166

167
symbolic::Expression FlopAnalysis::
168
    visit_sequence(structured_control_flow::Sequence& sequence, AnalysisManager& analysis_manager) {
147✔
169
    symbolic::Expression result = symbolic::zero();
147✔
170
    bool is_null = false;
147✔
171

172
    for (size_t i = 0; i < sequence.size(); i++) {
323✔
173
        symbolic::Expression child = this->visit(sequence.at(i).first, analysis_manager);
176✔
174
        if (child.is_null()) {
176✔
175
            is_null = true;
22✔
176
        }
22✔
177
        if (!is_null) {
176✔
178
            result = symbolic::add(result, child);
154✔
179
        }
154✔
180
    }
176✔
181

182
    if (is_null) {
147✔
183
        this->flops_[&sequence] = SymEngine::null;
22✔
184
        this->precise_ = false;
22✔
185
        return SymEngine::null;
22✔
186
    }
22✔
187
    this->flops_[&sequence] = result;
125✔
188
    return result;
125✔
189
}
147✔
190

191
symbolic::Expression FlopAnalysis::visit_block(structured_control_flow::Block& block, AnalysisManager& analysis_manager) {
72✔
192
    auto& dfg = block.dataflow();
72✔
193

194
    symbolic::Expression tasklets_result = symbolic::zero();
72✔
195
    for (auto tasklet : dfg.tasklets()) {
72✔
196
        if (tasklet->code() == data_flow::TaskletCode::fp_fma) {
60✔
197
            tasklets_result = symbolic::add(tasklets_result, symbolic::integer(2));
4✔
198
        } else if (data_flow::is_floating_point(tasklet->code())) {
56✔
199
            tasklets_result = symbolic::add(tasklets_result, symbolic::one());
6✔
200
        }
6✔
201
    }
60✔
202

203
    symbolic::Expression libnodes_result = symbolic::zero();
72✔
204
    for (auto libnode : dfg.library_nodes()) {
72✔
205
        symbolic::Expression tmp = libnode->flop();
11✔
206
        if (tmp.is_null()) {
11✔
207
            this->precise_ = false;
×
208
            return SymEngine::null;
×
209
        }
×
210
        libnodes_result = symbolic::add(libnodes_result, tmp);
11✔
211
    }
11✔
212

213
    // Determine scope parameters
214
    symbolic::SymbolSet parameters = this->get_scope_parameters(block, analysis_manager);
72✔
215

216
    // Filter the loop index variables in libnodes_result, and replace them by (upper_bound - lower_bound) / 2
217
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
72✔
218
    auto block_assumptions = assumptions_analysis.get(block);
72✔
219
    libnodes_result = this->replace_loop_indices(parameters, libnodes_result, block_assumptions);
72✔
220

221
    symbolic::Expression result = symbolic::add(tasklets_result, libnodes_result);
72✔
222
    this->flops_[&block] = result;
72✔
223
    return result;
72✔
224
}
72✔
225

226
symbolic::Expression FlopAnalysis::
227
    visit_structured_loop(structured_control_flow::StructuredLoop& loop, AnalysisManager& analysis_manager) {
78✔
228
    symbolic::Expression child = this->visit_sequence(loop.root(), analysis_manager);
78✔
229
    if (child.is_null()) {
78✔
230
        this->precise_ = false;
×
231
        return SymEngine::null;
×
232
    }
×
233

234
    auto& scope_analysis = analysis_manager.get<ScopeAnalysis>();
78✔
235
    structured_control_flow::ControlFlowNode* parent = scope_analysis.parent_scope(&loop);
78✔
236
    if (!parent) {
78✔
237
        throw InvalidSDFGException("FlopAnalysis: Could not find parent scope of structured loop");
×
238
    }
×
239
    this->flops_[&loop] = this->visit_structured_loop_with_scope(loop, analysis_manager, loop, child);
78✔
240
    return this->visit_structured_loop_with_scope(loop, analysis_manager, *parent, child);
78✔
241
}
78✔
242

243
symbolic::Expression FlopAnalysis::visit_structured_loop_with_scope(
244
    structured_control_flow::StructuredLoop& loop,
245
    AnalysisManager& analysis_manager,
246
    structured_control_flow::ControlFlowNode& scope,
247
    symbolic::Expression child_expr
248
) {
156✔
249
    // Determine scope parameters
250
    symbolic::SymbolSet parameters = this->get_scope_parameters(scope, analysis_manager);
156✔
251

252
    // Require existance of assumptions for the loop indvar
253
    auto indvar = loop.indvar();
156✔
254
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
156✔
255
    auto loop_assumptions = assumptions_analysis.get(loop.root());
156✔
256
    if (!loop_assumptions.contains(indvar)) {
156✔
257
        this->precise_ = false;
×
258
        return SymEngine::null;
×
259
    }
×
260
    bool done;
156✔
261

262
    // Determine initial value of loop
263
    symbolic::Expression init = SymEngine::null;
156✔
264
    done = false;
156✔
265
    if (!loop_assumptions[indvar].tight_lower_bound().is_null()) {
156✔
266
        init = this->replace_loop_indices(parameters, loop_assumptions[indvar].tight_lower_bound(), loop_assumptions);
156✔
267
        done = this->is_parameter_expression(parameters, init);
156✔
268
    }
156✔
269
    if (!done && !symbolic::eq(loop_assumptions[indvar].lower_bound(), SymEngine::NegInf)) {
156✔
270
        auto bounds = this->choose_bounds(parameters, loop_assumptions[indvar].lower_bounds());
2✔
271
        if (!bounds.empty()) {
2✔
272
            init = this->replace_loop_indices(
2✔
273
                parameters,
2✔
274
                SymEngine::max(std::vector<symbolic::Expression>(bounds.begin(), bounds.end())),
2✔
275
                loop_assumptions
2✔
276
            );
2✔
277
            this->precise_ = false;
2✔
278
            done = this->is_parameter_expression(parameters, init);
2✔
279
        }
2✔
280
    }
2✔
281
    if (!done) {
156✔
282
        init = this->replace_loop_indices(parameters, loop.init(), loop_assumptions);
2✔
283
        this->precise_ = false;
2✔
284
    }
2✔
285
    if (init.is_null()) {
156✔
286
        this->precise_ = false;
×
287
        return SymEngine::null;
×
288
    }
×
289

290
    // Determine bound of loop
291
    symbolic::Expression bound;
156✔
292
    done = false;
156✔
293
    if (!loop_assumptions[indvar].tight_upper_bound().is_null()) {
156✔
294
        bound = this->replace_loop_indices(parameters, loop_assumptions[indvar].tight_upper_bound(), loop_assumptions);
154✔
295
        done = this->is_parameter_expression(parameters, bound);
154✔
296
    }
154✔
297
    if (!done && !symbolic::eq(loop_assumptions[indvar].upper_bound(), SymEngine::Inf)) {
156✔
298
        auto bounds = this->choose_bounds(parameters, loop_assumptions[indvar].upper_bounds());
2✔
299
        if (!bounds.empty()) {
2✔
300
            bound = this->replace_loop_indices(
2✔
301
                parameters,
2✔
302
                SymEngine::min(std::vector<symbolic::Expression>(bounds.begin(), bounds.end())),
2✔
303
                loop_assumptions
2✔
304
            );
2✔
305
            this->precise_ = false;
2✔
306
            done = this->is_parameter_expression(parameters, bound);
2✔
307
        }
2✔
308
    }
2✔
309
    if (!done) {
156✔
NEW
310
        auto canonical_bound = loop.canonical_bound();
×
311
        if (!canonical_bound.is_null()) {
×
312
            bound =
×
313
                this->replace_loop_indices(parameters, symbolic::sub(canonical_bound, symbolic::one()), loop_assumptions);
×
314
            this->precise_ = false;
×
315
        }
×
316
    }
×
317
    if (bound.is_null()) {
156✔
318
        this->precise_ = false;
×
319
        return SymEngine::null;
×
320
    }
×
321

322
    // Determine stride of loop
323
    symbolic::SymbolVec symbols = {indvar};
156✔
324
    auto update_polynomial = symbolic::polynomial(loop.update(), symbols);
156✔
325
    if (update_polynomial.is_null()) {
156✔
326
        this->precise_ = false;
×
327
        return SymEngine::null;
×
328
    }
×
329
    auto update_coeffs = symbolic::affine_coefficients(update_polynomial, symbols);
156✔
330

331
    // For now, only allow polynomial of the form: 1 * indvar + n
332
    assert(update_coeffs.contains(indvar) && symbolic::eq(update_coeffs[indvar], symbolic::one()));
156✔
333
    symbolic::Expression stride =
156✔
334
        this->replace_loop_indices(parameters, update_coeffs[symbolic::symbol("__daisy_constant__")], loop_assumptions);
156✔
335

336
    return symbolic::mul(symbolic::div(symbolic::add(symbolic::sub(bound, init), symbolic::one()), stride), child_expr);
156✔
337
}
156✔
338

339
symbolic::Expression FlopAnalysis::
340
    visit_if_else(structured_control_flow::IfElse& if_else, AnalysisManager& analysis_manager) {
1✔
341
    if (if_else.size() == 0) {
1✔
342
        this->flops_[&if_else] = symbolic::zero();
×
343
        return symbolic::zero();
×
344
    }
×
345

346
    std::vector<symbolic::Expression> sub_flops;
1✔
347
    bool is_null = false;
1✔
348

349
    for (size_t i = 0; i < if_else.size(); i++) {
3✔
350
        symbolic::Expression child = this->visit_sequence(if_else.at(i).first, analysis_manager);
2✔
351
        if (child.is_null()) {
2✔
352
            is_null = true;
×
353
        }
×
354
        if (!is_null) {
2✔
355
            sub_flops.push_back(child);
2✔
356
        }
2✔
357
    }
2✔
358

359
    this->precise_ = false;
1✔
360
    if (is_null) {
1✔
361
        this->flops_[&if_else] = SymEngine::null;
×
362
        return SymEngine::null;
×
363
    }
×
364
    symbolic::Expression result = SymEngine::max(sub_flops);
1✔
365
    this->flops_[&if_else] = result;
1✔
366
    return result;
1✔
367
}
1✔
368

369
symbolic::Expression FlopAnalysis::visit_while(structured_control_flow::While& loop, AnalysisManager& analysis_manager) {
22✔
370
    this->visit_sequence(loop.root(), analysis_manager);
22✔
371
    this->flops_[&loop] = SymEngine::null;
22✔
372
    this->precise_ = false;
22✔
373
    // Return null because there is no good way to simply estimate the FLOPs of a while loop
374
    return SymEngine::null;
22✔
375
}
22✔
376

377
void FlopAnalysis::run(AnalysisManager& analysis_manager) {
43✔
378
    this->flops_.clear();
43✔
379
    this->precise_ = true;
43✔
380

381
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
43✔
382

383
    this->visit_sequence(this->sdfg_.root(), analysis_manager);
43✔
384
}
43✔
385

386
FlopAnalysis::FlopAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg) {}
43✔
387

388
bool FlopAnalysis::contains(const structured_control_flow::ControlFlowNode* node) {
31✔
389
    return this->flops_.contains(node);
31✔
390
}
31✔
391

392
symbolic::Expression FlopAnalysis::get(const structured_control_flow::ControlFlowNode* node) {
55✔
393
    auto it = this->flops_.find(node);
55✔
394
    if (it != this->flops_.end()) {
55✔
395
        return it->second;
55✔
396
    } else {
55✔
397
        return SymEngine::null;
×
398
    }
×
399
}
55✔
400

401
std::unordered_map<const structured_control_flow::ControlFlowNode*, symbolic::Expression> FlopAnalysis::get() {
×
402
    return this->flops_;
×
403
}
×
404

405
bool FlopAnalysis::precise() { return this->precise_; }
8✔
406

407
symbolic::Expression FlopAnalysis::get_if_valid_for_codegen(const symbolic::Expression flops) {
1✔
408
    if (!flops.is_null()) {
1✔
409
        for (auto& atom : SymEngine::atoms<SymEngine::Basic>(*flops)) { // TODO this excludes anything including
1✔
410
                                                                        // Infinity, as long as its not filtered out at
411
                                                                        // the beginning of flop analysis (untested,
412
                                                                        // whether parsing can handle incomplete traces
413
                                                                        // (we emit null, for such)
414
            if (SymEngine::is_a<SymEngine::Infty>(*atom)) {
1✔
415
                return SymEngine::null;
×
416
            }
×
417
        }
1✔
418
        if (!symbolic::has_dynamic_sizeof(flops)) {
1✔
419
            return flops;
1✔
420
        }
1✔
421
    }
1✔
422
    return SymEngine::null;
×
423
}
1✔
424

425
} // namespace analysis
426
} // 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