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

daisytuner / sdfglib / 15512041711

07 Jun 2025 09:59PM UTC coverage: 57.416% (+0.1%) from 57.315%
15512041711

push

github

web-flow
Merge pull request #44 from daisytuner/StructuredLoops

Add Structured Loops

51 of 102 new or added lines in 20 files covered. (50.0%)

10 existing lines in 8 files now uncovered.

7618 of 13268 relevant lines covered (57.42%)

116.01 hits per line

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

86.43
/src/analysis/data_parallelism_analysis.cpp
1
#include "sdfg/analysis/data_parallelism_analysis.h"
2

3
#include <regex>
4

5
#include "sdfg/analysis/assumptions_analysis.h"
6
#include "sdfg/analysis/users.h"
7
#include "sdfg/codegen/language_extensions/cpp_language_extension.h"
8
#include "sdfg/structured_control_flow/structured_loop.h"
9

10
namespace sdfg {
11
namespace analysis {
12

13
std::pair<data_flow::Subset, data_flow::Subset> DataParallelismAnalysis::substitution(
148✔
14
    const data_flow::Subset& subset1, const data_flow::Subset& subset2, const std::string& indvar,
15
    const std::unordered_set<std::string>& moving_symbols, symbolic::SymbolicMap& replacements,
16
    std::vector<std::string>& substitions) {
17
    data_flow::Subset subset1_new;
148✔
18
    for (auto& dim : subset1) {
340✔
19
        auto args = dim->get_args();
192✔
20
        auto new_dim = dim;
192✔
21
        for (auto& arg : args) {
333✔
22
            if (!SymEngine::is_a<SymEngine::Symbol>(*arg) &&
212✔
23
                !SymEngine::is_a<SymEngine::Integer>(*arg)) {
71✔
24
                bool is_moving = false;
69✔
25
                for (auto& atom : symbolic::atoms(arg)) {
176✔
26
                    auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
107✔
27
                    if (moving_symbols.find(sym->get_name()) != moving_symbols.end()) {
107✔
28
                        is_moving = true;
32✔
29
                        break;
32✔
30
                    }
31
                }
107✔
32
                if (!is_moving) {
69✔
33
                    if (replacements.find(arg) != replacements.end()) {
37✔
34
                        new_dim = symbolic::subs(new_dim, arg, replacements.at(arg));
×
35
                    } else {
×
36
                        auto repl = symbolic::symbol("c_" + std::to_string(replacements.size()));
37✔
37
                        substitions.push_back(repl->get_name());
37✔
38
                        replacements.insert({arg, repl});
37✔
39
                        new_dim = symbolic::subs(new_dim, arg, repl);
37✔
40
                    }
37✔
41
                }
37✔
42
            }
69✔
43
        }
44
        subset1_new.push_back(new_dim);
192✔
45
    }
192✔
46

47
    data_flow::Subset subset2_new;
148✔
48
    for (auto& dim : subset2) {
340✔
49
        auto args = dim->get_args();
192✔
50
        auto new_dim = dim;
192✔
51
        for (auto& arg : args) {
331✔
52
            if (!SymEngine::is_a<SymEngine::Symbol>(*arg) &&
208✔
53
                !SymEngine::is_a<SymEngine::Integer>(*arg)) {
69✔
54
                bool is_moving = false;
69✔
55
                for (auto& atom : symbolic::atoms(arg)) {
176✔
56
                    auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
107✔
57
                    if (moving_symbols.find(sym->get_name()) != moving_symbols.end()) {
107✔
58
                        is_moving = true;
32✔
59
                        break;
32✔
60
                    }
61
                }
107✔
62
                if (!is_moving) {
69✔
63
                    if (replacements.find(arg) != replacements.end()) {
37✔
64
                        new_dim = symbolic::subs(new_dim, arg, replacements.at(arg));
37✔
65
                    } else {
37✔
66
                        auto repl = symbolic::symbol("c_" + std::to_string(replacements.size()));
×
67
                        substitions.push_back(repl->get_name());
×
68
                        replacements.insert({arg, repl});
×
69
                        new_dim = symbolic::subs(new_dim, arg, repl);
×
70
                    }
×
71
                }
37✔
72
            }
69✔
73
        }
74
        subset2_new.push_back(new_dim);
192✔
75
    }
192✔
76

77
    return {subset1_new, subset2_new};
148✔
78
};
148✔
79

80
std::pair<data_flow::Subset, data_flow::Subset> DataParallelismAnalysis::delinearization(
150✔
81
    const data_flow::Subset& subset1, const data_flow::Subset& subset2,
82
    const std::unordered_set<std::string>& moving_symbols,
83
    const symbolic::Assumptions& assumptions) {
84
    // Attempt to prove:
85
    // dim = i + j * M
86
    // We can delinearize iff:
87
    // 1. M is a constant
88
    // 2. i and j are not symbols
89
    // 3. M >= ub(i)
90
    data_flow::Subset subset1_new;
150✔
91
    for (auto& dim : subset1) {
344✔
92
        if (!SymEngine::is_a<SymEngine::Add>(*dim)) {
194✔
93
            subset1_new.push_back(dim);
123✔
94
            continue;
123✔
95
        }
96
        auto add = SymEngine::rcp_static_cast<const SymEngine::Add>(dim);
71✔
97
        if (add->get_args().size() != 2) {
71✔
98
            subset1_new.push_back(dim);
3✔
99
            continue;
3✔
100
        }
101
        auto offset = add->get_args()[0];
68✔
102
        auto mult = add->get_args()[1];
68✔
103
        if (!SymEngine::is_a<SymEngine::Mul>(*mult)) {
68✔
104
            auto tmp = offset;
37✔
105
            offset = mult;
37✔
106
            mult = tmp;
37✔
107
        }
37✔
108
        if (!SymEngine::is_a<SymEngine::Mul>(*mult)) {
68✔
109
            subset1_new.push_back(dim);
36✔
110
            continue;
36✔
111
        }
112

113
        // Offset must be a symbol and moving
114
        if (!SymEngine::is_a<SymEngine::Symbol>(*offset)) {
32✔
115
            subset1_new.push_back(dim);
×
116
            continue;
×
117
        }
118
        auto off = SymEngine::rcp_static_cast<const SymEngine::Symbol>(offset);
32✔
119
        if (moving_symbols.find(off->get_name()) == moving_symbols.end()) {
32✔
120
            subset1_new.push_back(dim);
×
121
            continue;
×
122
        }
123

124
        // Multiplier must be two symbols and one moving
125
        auto mult_ = SymEngine::rcp_static_cast<const SymEngine::Mul>(mult);
32✔
126
        if (mult_->get_args().size() != 2) {
32✔
127
            subset1_new.push_back(dim);
×
128
            continue;
×
129
        }
130
        auto multiplier = mult_->get_args()[0];
32✔
131
        auto indvar_ = mult_->get_args()[1];
32✔
132
        if (!SymEngine::is_a<SymEngine::Symbol>(*multiplier) ||
64✔
133
            !SymEngine::is_a<SymEngine::Symbol>(*indvar_)) {
32✔
134
            subset1_new.push_back(dim);
×
135
            continue;
×
136
        }
137
        auto mul = SymEngine::rcp_static_cast<const SymEngine::Symbol>(multiplier);
32✔
138
        auto indvar = SymEngine::rcp_static_cast<const SymEngine::Symbol>(indvar_);
32✔
139
        if (moving_symbols.find(mul->get_name()) != moving_symbols.end()) {
32✔
140
            auto tmp = mul;
28✔
141
            mul = indvar;
28✔
142
            indvar = tmp;
28✔
143
        }
28✔
144
        if (moving_symbols.find(mul->get_name()) != moving_symbols.end() ||
64✔
145
            moving_symbols.find(indvar->get_name()) == moving_symbols.end()) {
32✔
146
            subset1_new.push_back(dim);
×
147
            continue;
×
148
        }
149

150
        bool is_nonnegative = false;
32✔
151
        symbolic::SymbolicSet lbs_off;
32✔
152
        symbolic::lower_bounds(off, assumptions, lbs_off);
32✔
153
        for (auto& lb : lbs_off) {
33✔
154
            if (SymEngine::is_a<SymEngine::Integer>(*lb)) {
32✔
155
                auto lb_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(lb);
32✔
156
                if (lb_int->as_int() >= 0) {
32✔
157
                    is_nonnegative = true;
31✔
158
                    break;
31✔
159
                }
160
            }
32✔
161
        }
162
        if (!is_nonnegative) {
32✔
163
            subset1_new.push_back(dim);
1✔
164
            continue;
1✔
165
        }
166

167
        bool success = false;
31✔
168
        symbolic::SymbolicSet ubs_off;
31✔
169
        symbolic::upper_bounds(off, assumptions, ubs_off);
31✔
170
        for (auto& ub_off : ubs_off) {
33✔
171
            if (symbolic::eq(mul, symbolic::add(ub_off, symbolic::one()))) {
32✔
172
                subset1_new.push_back(indvar);
30✔
173
                subset1_new.push_back(off);
30✔
174

175
                success = true;
30✔
176
                break;
30✔
177
            }
178
        }
179
        if (success) {
31✔
180
            continue;
30✔
181
        }
182
        subset1_new.push_back(dim);
1✔
183
    }
71✔
184

185
    data_flow::Subset subset2_new;
150✔
186
    for (auto& dim : subset2) {
344✔
187
        if (!SymEngine::is_a<SymEngine::Add>(*dim)) {
194✔
188
            subset2_new.push_back(dim);
124✔
189
            continue;
124✔
190
        }
191
        auto add = SymEngine::rcp_static_cast<const SymEngine::Add>(dim);
70✔
192
        if (add->get_args().size() != 2) {
70✔
193
            subset2_new.push_back(dim);
3✔
194
            continue;
3✔
195
        }
196
        auto offset = add->get_args()[0];
67✔
197
        auto mult = add->get_args()[1];
67✔
198
        if (!SymEngine::is_a<SymEngine::Mul>(*mult)) {
67✔
199
            auto tmp = offset;
36✔
200
            offset = mult;
36✔
201
            mult = tmp;
36✔
202
        }
36✔
203
        if (!SymEngine::is_a<SymEngine::Mul>(*mult)) {
67✔
204
            subset2_new.push_back(dim);
35✔
205
            continue;
35✔
206
        }
207

208
        // Offset must be a symbol and moving
209
        if (!SymEngine::is_a<SymEngine::Symbol>(*offset)) {
32✔
210
            subset2_new.push_back(dim);
×
211
            continue;
×
212
        }
213
        auto off = SymEngine::rcp_static_cast<const SymEngine::Symbol>(offset);
32✔
214
        if (moving_symbols.find(off->get_name()) == moving_symbols.end()) {
32✔
215
            subset2_new.push_back(dim);
×
216
            continue;
×
217
        }
218

219
        // Multiplier must be two symbols and one moving
220
        auto mult_ = SymEngine::rcp_static_cast<const SymEngine::Mul>(mult);
32✔
221
        if (mult_->get_args().size() != 2) {
32✔
222
            subset2_new.push_back(dim);
×
223
            continue;
×
224
        }
225
        auto multiplier = mult_->get_args()[0];
32✔
226
        auto indvar_ = mult_->get_args()[1];
32✔
227
        if (!SymEngine::is_a<SymEngine::Symbol>(*multiplier) ||
64✔
228
            !SymEngine::is_a<SymEngine::Symbol>(*indvar_)) {
32✔
229
            subset2_new.push_back(dim);
×
230
            continue;
×
231
        }
232
        auto mul = SymEngine::rcp_static_cast<const SymEngine::Symbol>(multiplier);
32✔
233
        auto indvar = SymEngine::rcp_static_cast<const SymEngine::Symbol>(indvar_);
32✔
234
        if (moving_symbols.find(mul->get_name()) != moving_symbols.end()) {
32✔
235
            auto tmp = mul;
28✔
236
            mul = indvar;
28✔
237
            indvar = tmp;
28✔
238
        }
28✔
239
        if (moving_symbols.find(mul->get_name()) != moving_symbols.end() ||
64✔
240
            moving_symbols.find(indvar->get_name()) == moving_symbols.end()) {
32✔
241
            subset2_new.push_back(dim);
×
242
            continue;
×
243
        }
244

245
        bool is_nonnegative = false;
32✔
246
        symbolic::SymbolicSet lbs_off;
32✔
247
        symbolic::lower_bounds(off, assumptions, lbs_off);
32✔
248
        for (auto& lb : lbs_off) {
33✔
249
            if (SymEngine::is_a<SymEngine::Integer>(*lb)) {
32✔
250
                auto lb_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(lb);
32✔
251
                if (lb_int->as_int() >= 0) {
32✔
252
                    is_nonnegative = true;
31✔
253
                    break;
31✔
254
                }
255
            }
32✔
256
        }
257
        if (!is_nonnegative) {
32✔
258
            subset2_new.push_back(dim);
1✔
259
            continue;
1✔
260
        }
261

262
        bool success = false;
31✔
263
        symbolic::SymbolicSet ubs_off;
31✔
264
        symbolic::upper_bounds(off, assumptions, ubs_off);
31✔
265
        for (auto& ub_off : ubs_off) {
33✔
266
            if (symbolic::eq(mul, symbolic::add(ub_off, symbolic::one()))) {
32✔
267
                subset2_new.push_back(indvar);
30✔
268
                subset2_new.push_back(off);
30✔
269

270
                success = true;
30✔
271
                break;
30✔
272
            }
273
        }
274
        if (success) {
31✔
275
            continue;
30✔
276
        }
277
        subset2_new.push_back(dim);
1✔
278
    }
70✔
279

280
    return {subset1_new, subset2_new};
150✔
281
};
150✔
282

283
bool DataParallelismAnalysis::disjoint(const data_flow::Subset& subset1,
147✔
284
                                       const data_flow::Subset& subset2, const std::string& indvar,
285
                                       const std::unordered_set<std::string>& moving_symbols,
286
                                       const symbolic::Assumptions& assumptions) {
287
    if (subset1.size() != subset2.size()) {
147✔
288
        return false;
×
289
    }
290

291
    codegen::CPPLanguageExtension language_extension;
147✔
292

293
    // Attempt to substitute complex constant expressions by parameters
294
    symbolic::SymbolicMap replacements;
147✔
295
    std::vector<std::string> substitions;
147✔
296
    auto [subset1_, subset2_] = DataParallelismAnalysis::substitution(
147✔
297
        subset1, subset2, indvar, moving_symbols, replacements, substitions);
147✔
298

299
    // Attempt to delinearize subsets
300
    auto [subset1_2, subset2_2] =
441✔
301
        DataParallelismAnalysis::delinearization(subset1_, subset2_, moving_symbols, assumptions);
147✔
302

303
    // Overapproximate multiplications with parameters
304
    data_flow::Subset subset1_new;
147✔
305
    for (auto& dim : subset1_2) {
367✔
306
        auto dim_ = dim;
220✔
307
        for (auto mul : symbolic::muls(dim)) {
223✔
308
            auto mul_ = SymEngine::rcp_static_cast<const SymEngine::Mul>(mul);
3✔
309
            auto arg1 = mul_->get_args()[0];
3✔
310
            if (SymEngine::is_a<SymEngine::Symbol>(*arg1) &&
3✔
311
                moving_symbols.find(
×
312
                    SymEngine::rcp_static_cast<const SymEngine::Symbol>(arg1)->get_name()) ==
×
313
                    moving_symbols.end()) {
×
314
                dim_ = symbolic::subs(dim_, mul_, symbolic::one());
×
315
            } else {
×
316
                auto arg2 = mul_->get_args()[1];
3✔
317
                if (SymEngine::is_a<SymEngine::Symbol>(*arg2) &&
6✔
318
                    moving_symbols.find(
6✔
319
                        SymEngine::rcp_static_cast<const SymEngine::Symbol>(arg2)->get_name()) ==
6✔
320
                        moving_symbols.end()) {
3✔
321
                    dim_ = symbolic::subs(dim_, mul_, symbolic::one());
×
322
                }
×
323
            }
3✔
324
        }
3✔
325
        subset1_new.push_back(dim_);
220✔
326
    }
220✔
327
    data_flow::Subset subset2_new;
147✔
328
    for (auto& dim : subset2_2) {
367✔
329
        auto dim_ = dim;
220✔
330
        for (auto mul : symbolic::muls(dim)) {
223✔
331
            auto mul_ = SymEngine::rcp_static_cast<const SymEngine::Mul>(mul);
3✔
332
            auto arg1 = mul_->get_args()[0];
3✔
333
            if (SymEngine::is_a<SymEngine::Symbol>(*arg1) &&
3✔
334
                moving_symbols.find(
×
335
                    SymEngine::rcp_static_cast<const SymEngine::Symbol>(arg1)->get_name()) ==
×
336
                    moving_symbols.end()) {
×
337
                dim_ = symbolic::subs(dim_, mul_, symbolic::one());
×
338
            } else {
×
339
                auto arg2 = mul_->get_args()[1];
3✔
340
                if (SymEngine::is_a<SymEngine::Symbol>(*arg2) &&
6✔
341
                    moving_symbols.find(
6✔
342
                        SymEngine::rcp_static_cast<const SymEngine::Symbol>(arg2)->get_name()) ==
6✔
343
                        moving_symbols.end()) {
3✔
344
                    dim_ = symbolic::subs(dim_, mul_, symbolic::one());
×
345
                }
×
346
            }
3✔
347
        }
3✔
348
        subset2_new.push_back(dim_);
220✔
349
    }
220✔
350

351
    // Collect parameters and dimensions
352
    std::unordered_set<std::string> dimensions_;
147✔
353
    std::unordered_set<std::string> parameters_;
147✔
354
    for (auto& dim : subset1_new) {
367✔
355
        for (auto& atom : symbolic::atoms(dim)) {
478✔
356
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
258✔
357
            if (sym->get_name() == indvar) {
258✔
358
                continue;
117✔
359
            }
360

361
            if (std::find(substitions.begin(), substitions.end(), sym->get_name()) !=
141✔
362
                substitions.end()) {
141✔
363
                continue;
36✔
364
            }
365
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
105✔
366
                parameters_.insert(sym->get_name());
36✔
367
            } else {
36✔
368
                dimensions_.insert(sym->get_name());
69✔
369
            }
370
        }
258✔
371
    }
372
    for (auto& dim : subset2_new) {
367✔
373
        for (auto& atom : symbolic::atoms(dim)) {
479✔
374
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
259✔
375
            if (sym->get_name() == indvar) {
259✔
376
                continue;
117✔
377
            }
378

379
            if (std::find(substitions.begin(), substitions.end(), sym->get_name()) !=
142✔
380
                substitions.end()) {
142✔
381
                continue;
36✔
382
            }
383
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
106✔
384
                parameters_.insert(sym->get_name());
37✔
385
            } else {
37✔
386
                dimensions_.insert(sym->get_name());
69✔
387
            }
388
        }
259✔
389
    }
390
    dimensions_.insert(indvar);
147✔
391
    std::vector<std::string> dimensions;
147✔
392
    for (auto& dim : dimensions_) {
363✔
393
        dimensions.push_back(dim);
216✔
394
    }
395
    for (auto mv : moving_symbols) {
760✔
396
        if (dimensions_.find(mv) == dimensions_.end()) {
613✔
397
            dimensions.push_back(mv);
397✔
398
            dimensions_.insert(mv);
397✔
399
        }
397✔
400
    }
613✔
401
    std::vector<std::string> parameters;
147✔
402
    for (auto& dim : parameters_) {
184✔
403
        parameters.push_back(dim);
37✔
404
    }
405

406
    // Double dimension space and constraints
407
    size_t k = 0;
147✔
408
    std::vector<std::string> doubled_dimensions;
147✔
409
    std::vector<std::string> constraints;
147✔
410
    for (auto& dim : dimensions) {
760✔
411
        doubled_dimensions.push_back(dim + "_1");
613✔
412
        doubled_dimensions.push_back(dim + "_2");
613✔
413

414
        // Proof: dim_1 != dim_2
415
        if (dim == indvar) {
613✔
416
            constraints.push_back(dim + "_1 != " + dim + "_2");
147✔
417
        }
147✔
418

419
        // Collect lb and ub
420
        auto lb1 = assumptions.at(symbolic::symbol(dim)).lower_bound();
613✔
421
        for (auto atom : symbolic::atoms(lb1)) {
692✔
422
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
79✔
423
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
79✔
424
                if (parameters_.find(sym->get_name()) == parameters_.end()) {
26✔
425
                    parameters_.insert(sym->get_name());
22✔
426
                    parameters.push_back(sym->get_name());
22✔
427
                }
22✔
428
            }
26✔
429
        }
79✔
430
        auto ub1 = assumptions.at(symbolic::symbol(dim)).upper_bound();
613✔
431
        for (auto atom : symbolic::atoms(ub1)) {
957✔
432
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
344✔
433
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
344✔
434
                if (parameters_.find(sym->get_name()) == parameters_.end()) {
295✔
435
                    parameters_.insert(sym->get_name());
209✔
436
                    parameters.push_back(sym->get_name());
209✔
437
                }
209✔
438
            }
295✔
439
        }
344✔
440

441
        // Add constraints
442
        auto lb2 = lb1;
613✔
443
        auto ub2 = ub1;
613✔
444
        for (auto& dim : dimensions) {
4,370✔
445
            lb1 = symbolic::subs(lb1, symbolic::symbol(dim), symbolic::symbol(dim + "_1"));
3,757✔
446
            ub1 = symbolic::subs(ub1, symbolic::symbol(dim), symbolic::symbol(dim + "_1"));
3,757✔
447
            lb2 = symbolic::subs(lb2, symbolic::symbol(dim), symbolic::symbol(dim + "_2"));
3,757✔
448
            ub2 = symbolic::subs(ub2, symbolic::symbol(dim), symbolic::symbol(dim + "_2"));
3,757✔
449
        }
450

451
        if (!SymEngine::eq(*lb1, *symbolic::infty(-1))) {
613✔
452
            auto mins = SymEngine::atoms<const SymEngine::Min>(*lb1);
613✔
453
            if (mins.size() > 0) {
613✔
454
                continue;
×
455
            }
456

457
            if (SymEngine::is_a<SymEngine::Max>(*lb1)) {
613✔
458
                auto max = SymEngine::rcp_static_cast<const SymEngine::Max>(lb1);
×
459
                auto args1 = max->get_args();
×
460
                constraints.push_back(language_extension.expression(args1[0]) + " <= " + dim +
×
461
                                      "_1");
462
                constraints.push_back(language_extension.expression(args1[1]) + " <= " + dim +
×
463
                                      "_1");
464

465
                auto max_ = SymEngine::rcp_static_cast<const SymEngine::Max>(lb2);
×
466
                auto args2 = max_->get_args();
×
467
                constraints.push_back(language_extension.expression(args2[0]) + " <= " + dim +
×
468
                                      "_2");
469
                constraints.push_back(language_extension.expression(args2[1]) + " <= " + dim +
×
470
                                      "_2");
471
            } else {
×
472
                constraints.push_back(language_extension.expression(lb1) + " <= " + dim + "_1");
613✔
473
                constraints.push_back(language_extension.expression(lb2) + " <= " + dim + "_2");
613✔
474
            }
475
        }
613✔
476
        if (!SymEngine::eq(*ub1, *symbolic::infty(1))) {
613✔
477
            auto maxs = SymEngine::atoms<const SymEngine::Max>(*ub1);
609✔
478
            if (maxs.size() > 0) {
609✔
479
                continue;
4✔
480
            }
481

482
            if (SymEngine::is_a<SymEngine::Min>(*ub1)) {
605✔
483
                auto min = SymEngine::rcp_static_cast<const SymEngine::Min>(ub1);
67✔
484
                auto args1 = min->get_args();
67✔
485
                constraints.push_back(dim + "_1 <= " + language_extension.expression(args1[0]));
67✔
486
                constraints.push_back(dim + "_1 <= " + language_extension.expression(args1[1]));
67✔
487

488
                auto min_ = SymEngine::rcp_static_cast<const SymEngine::Min>(ub2);
67✔
489
                auto args2 = min_->get_args();
67✔
490
                constraints.push_back(dim + "_2 <= " + language_extension.expression(args2[0]));
67✔
491
                constraints.push_back(dim + "_2 <= " + language_extension.expression(args2[1]));
67✔
492
            } else {
67✔
493
                constraints.push_back(dim + "_1 <= " + language_extension.expression(ub1));
538✔
494
                constraints.push_back(dim + "_2 <= " + language_extension.expression(ub2));
538✔
495
            }
496
        }
609✔
497

498
        // Add map constraints
499
        auto map = assumptions.at(symbolic::symbol(dim)).map();
609✔
500
        if (map == SymEngine::null) {
609✔
501
            continue;
340✔
502
        }
503
        if (!SymEngine::is_a<SymEngine::Add>(*map)) {
269✔
504
            continue;
×
505
        }
506
        auto args = SymEngine::rcp_static_cast<const SymEngine::Add>(map)->get_args();
269✔
507
        if (args.size() != 2) {
269✔
508
            continue;
×
509
        }
510
        auto arg0 = args[0];
269✔
511
        auto arg1 = args[1];
269✔
512
        if (!symbolic::eq(arg0, symbolic::symbol(dim))) {
269✔
513
            arg0 = args[1];
269✔
514
            arg1 = args[0];
269✔
515
        }
269✔
516
        if (!symbolic::eq(arg0, symbolic::symbol(dim))) {
269✔
517
            continue;
×
518
        }
519
        if (!SymEngine::is_a<SymEngine::Integer>(*arg1)) {
269✔
520
            continue;
×
521
        }
522
        if (!SymEngine::is_a<SymEngine::Integer>(*lb1)) {
269✔
523
            continue;
75✔
524
        }
525

526
        std::string new_k = "__daisy_iterator" + std::to_string(k++);
194✔
527

528
        std::string k_1 = new_k + "_1";
194✔
529
        constraints.push_back("exists " + k_1 + " : " + dim +
388✔
530
                              "_1 = " + language_extension.expression(lb1) + " + " + k_1 + " * " +
388✔
531
                              language_extension.expression(arg1));
194✔
532

533
        std::string k_2 = new_k + "_2";
194✔
534
        constraints.push_back("exists " + k_2 + " : " + dim +
388✔
535
                              "_2 = " + language_extension.expression(lb1) + " + " + k_2 + " * " +
388✔
536
                              language_extension.expression(arg1));
194✔
537
    }
613✔
538

539
    // Extend parameters by dependening parameters
540
    size_t num_params = parameters.size();
147✔
541
    size_t num_params_old = 0;
147✔
542
    do {
147✔
543
        for (size_t i = 0; i < num_params; i++) {
535✔
544
            auto param = parameters[i];
370✔
545
            auto lb = assumptions.at(symbolic::symbol(param)).lower_bound();
370✔
546
            for (auto& atom : symbolic::atoms(lb)) {
412✔
547
                auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
42✔
548
                if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
42✔
549
                    if (parameters_.find(sym->get_name()) == parameters_.end()) {
42✔
550
                        parameters_.insert(sym->get_name());
21✔
551
                        parameters.push_back(sym->get_name());
21✔
552
                    }
21✔
553
                }
42✔
554
            }
42✔
555
            auto ub = assumptions.at(symbolic::symbol(param)).upper_bound();
370✔
556
            for (auto& atom : symbolic::atoms(ub)) {
518✔
557
                auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
148✔
558
                if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
148✔
559
                    if (parameters_.find(sym->get_name()) == parameters_.end()) {
148✔
560
                        parameters_.insert(sym->get_name());
21✔
561
                        parameters.push_back(sym->get_name());
21✔
562
                    }
21✔
563
                }
148✔
564
            }
148✔
565
        }
370✔
566
        num_params_old = num_params;
165✔
567
        num_params = parameters.size();
165✔
568
    } while (num_params != num_params_old);
165✔
569

570
    // Collect constraints for parameters
571
    for (size_t i = 0; i < parameters.size(); i++) {
457✔
572
        auto lb = assumptions.at(symbolic::symbol(parameters[i])).lower_bound();
310✔
573
        auto ub = assumptions.at(symbolic::symbol(parameters[i])).upper_bound();
310✔
574

575
        std::string constraint = "";
310✔
576
        if (!SymEngine::eq(*lb, *symbolic::infty(-1))) {
310✔
577
            if (SymEngine::is_a<SymEngine::Max>(*lb)) {
310✔
578
                auto max = SymEngine::rcp_static_cast<const SymEngine::Max>(lb);
×
579
                auto args = max->get_args();
×
580
                constraints.push_back(language_extension.expression(args[0]) +
×
581
                                      " <= " + parameters[i]);
×
582
                constraints.push_back(language_extension.expression(args[1]) +
×
583
                                      " <= " + parameters[i]);
×
584
            } else if (SymEngine::atoms<const SymEngine::Min>(*lb).size() > 0) {
310✔
585
            } else {
×
586
                constraints.push_back(language_extension.expression(lb) + " <= " + parameters[i]);
310✔
587
            }
588
        }
310✔
589
        if (!SymEngine::eq(*ub, *symbolic::infty(1))) {
310✔
590
            if (SymEngine::is_a<SymEngine::Min>(*ub)) {
171✔
591
                auto min = SymEngine::rcp_static_cast<const SymEngine::Min>(ub);
21✔
592
                auto args = min->get_args();
21✔
593
                constraints.push_back(parameters[i] +
42✔
594
                                      " <= " + language_extension.expression(args[0]));
21✔
595
                constraints.push_back(parameters[i] +
42✔
596
                                      " <= " + language_extension.expression(args[1]));
21✔
597
            } else if (SymEngine::atoms<const SymEngine::Max>(*ub).size() > 0) {
171✔
598
            } else {
×
599
                constraints.push_back(parameters[i] + " <= " + language_extension.expression(ub));
150✔
600
            }
601
        }
171✔
602
    }
310✔
603

604
    // Allocate context
605
    isl_ctx* ctx = isl_ctx_alloc();
147✔
606

607
    // Define maps
608
    std::string map_1;
147✔
609
    if (!parameters.empty()) {
147✔
610
        map_1 += "[";
147✔
611
        map_1 += helpers::join(parameters, ", ");
147✔
612
    }
147✔
613
    if (!substitions.empty()) {
147✔
614
        if (!parameters.empty()) {
36✔
615
            map_1 += ", ";
36✔
616
        } else {
36✔
617
            map_1 += "[";
×
618
        }
619
        map_1 += helpers::join(substitions, ", ");
36✔
620
    }
36✔
621
    if (!map_1.empty()) {
147✔
622
        map_1 += "] -> ";
147✔
623
    }
147✔
624
    map_1 += "{ [" + helpers::join(doubled_dimensions, ", ") + "] -> [";
147✔
625
    for (size_t i = 0; i < subset1_new.size(); i++) {
367✔
626
        auto dim = subset1_new[i];
220✔
627
        for (auto& iter : dimensions) {
1,180✔
628
            dim = symbolic::subs(dim, symbolic::symbol(iter), symbolic::symbol(iter + "_1"));
960✔
629
        }
630
        map_1 += language_extension.expression(dim);
220✔
631
        if (i < subset1_new.size() - 1) {
220✔
632
            map_1 += ", ";
74✔
633
        }
74✔
634
    }
220✔
635
    map_1 += "] : " + helpers::join(constraints, " and ") + " }";
147✔
636

637
    std::string map_2;
147✔
638
    if (!parameters.empty()) {
147✔
639
        map_2 += "[";
147✔
640
        map_2 += helpers::join(parameters, ", ");
147✔
641
    }
147✔
642
    if (!substitions.empty()) {
147✔
643
        if (!parameters.empty()) {
36✔
644
            map_2 += ", ";
36✔
645
        } else {
36✔
646
            map_2 += "[";
×
647
        }
648
        map_2 += helpers::join(substitions, ", ");
36✔
649
    }
36✔
650
    if (!map_2.empty()) {
147✔
651
        map_2 += "] -> ";
147✔
652
    }
147✔
653
    map_2 += "{ [" + helpers::join(doubled_dimensions, ", ") + "] -> [";
147✔
654
    for (size_t i = 0; i < subset2_new.size(); i++) {
367✔
655
        auto dim = subset2_new[i];
220✔
656
        for (auto& iter : dimensions) {
1,180✔
657
            dim = symbolic::subs(dim, symbolic::symbol(iter), symbolic::symbol(iter + "_2"));
960✔
658
        }
659
        map_2 += language_extension.expression(dim);
220✔
660
        if (i < subset2_new.size() - 1) {
220✔
661
            map_2 += ", ";
74✔
662
        }
74✔
663
    }
220✔
664
    map_2 += "] : " + helpers::join(constraints, " and ") + " }";
147✔
665

666
    // Replace NV symbols with names without .
667
    map_1 = std::regex_replace(map_1, std::regex("\\."), "_");
147✔
668
    map_2 = std::regex_replace(map_2, std::regex("\\."), "_");
147✔
669

670
    isl_map* index_map_1 = isl_map_read_from_str(ctx, map_1.c_str());
147✔
671
    if (!index_map_1) {
147✔
672
        isl_ctx_free(ctx);
×
673
        return false;
×
674
    }
675

676
    isl_map* index_map_2 = isl_map_read_from_str(ctx, map_2.c_str());
147✔
677
    if (!index_map_2) {
147✔
678
        isl_map_free(index_map_1);
×
679
        isl_ctx_free(ctx);
×
680
        return false;
×
681
    }
682

683
    isl_map* intersection = isl_map_intersect(index_map_1, index_map_2);
147✔
684
    if (!intersection) {
147✔
685
        isl_map_free(index_map_1);
×
686
        isl_map_free(index_map_2);
×
687
        isl_ctx_free(ctx);
×
688
        return false;
×
689
    }
690

691
    bool disjoint = isl_map_is_empty(intersection);
147✔
692

693
    isl_map_free(intersection);
147✔
694
    isl_ctx_free(ctx);
147✔
695

696
    return disjoint;
147✔
697
};
147✔
698

699
void DataParallelismAnalysis::classify(analysis::AnalysisManager& analysis_manager,
70✔
700
                                       const structured_control_flow::StructuredLoop* loop) {
701
    // Strictly monotonic update
702
    auto& indvar = loop->indvar();
70✔
703
    auto& update = loop->update();
70✔
704
    if (symbolic::strict_monotonicity(update, indvar) != symbolic::Sign::POSITIVE) {
70✔
705
        this->results_.insert({loop, DataParallelismAnalysisResult()});
×
706
        return;
×
707
    }
708

709
    // Users analysis
710
    auto& body = loop->root();
70✔
711
    auto& users = analysis_manager.get<analysis::Users>();
70✔
712
    analysis::UsersView body_users(users, body);
70✔
713
    if (!body_users.views().empty() || !body_users.moves().empty()) {
70✔
714
        this->results_.insert({loop, DataParallelismAnalysisResult()});
×
715
        return;
×
716
    }
717

718
    this->results_.insert({loop, DataParallelismAnalysisResult()});
70✔
719
    auto& result = this->results_.at(loop);
70✔
720

721
    // Assumptions analysis
722
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
70✔
723
    auto assumptions = assumptions_analysis.get(body);
70✔
724

725
    // For each container, we now classify the access pattern
726

727
    // 1. Identify private containers
728
    auto locals = users.locals(sdfg_, body);
70✔
729
    for (auto& local : locals) {
171✔
730
        result.insert({local, Parallelism::PRIVATE});
101✔
731
    }
732

733
    // 2. Filter our read-only containers
734
    std::unordered_set<std::string> writeset;
70✔
735
    std::unordered_set<std::string> moving_symbols = {indvar->get_name()};
70✔
736
    for (auto& entry : body_users.writes()) {
300✔
737
        writeset.insert(entry->container());
230✔
738

739
        auto& type = sdfg_.type(entry->container());
230✔
740
        if (!dynamic_cast<const types::Scalar*>(&type)) {
230✔
741
            continue;
83✔
742
        }
743
        if (!types::is_integer(type.primitive_type())) {
147✔
744
            continue;
21✔
745
        }
746
        moving_symbols.insert(entry->container());
126✔
747
    }
748
    for (auto& entry : body_users.reads()) {
842✔
749
        if (writeset.find(entry->container()) != writeset.end()) {
772✔
750
            // Loop-carried dependencies in tasklets's conditions
751
            if (dynamic_cast<data_flow::Tasklet*>(entry->element())) {
326✔
752
                // Locals cannot be loop-carried
753
                if (locals.find(entry->container()) != locals.end()) {
6✔
754
                    continue;
5✔
755
                } else {
756
                    result.clear();
1✔
757
                    return;
1✔
758
                }
759
            }
760

761
            continue;
320✔
762
        }
763
        if (entry->container() == indvar->get_name()) {
446✔
764
            continue;
170✔
765
        }
766
        result.insert({entry->container(), Parallelism::READONLY});
276✔
767
    }
768

769
    // 3. Prove parallelism
770
    /** For each container of the writeset, we check the following properties:
771
     *  1. For all i, writes are disjoint (false -> empty parallelism)
772
     *  2. For all i, reads and writes are disjoint (false -> empty parallelism)
773
     */
774
    for (auto& container : writeset) {
224✔
775
        // Skip if already classified
776
        if (result.find(container) != result.end()) {
155✔
777
            continue;
83✔
778
        }
779

780
        // For each i1, i2, subset(i1) and subset(i2) are disjoint
781
        bool ww_conflict = false;
72✔
782
        auto writes = body_users.writes(container);
72✔
783
        for (auto& write : writes) {
132✔
784
            for (auto& write_ : writes) {
148✔
785
                if (write == write_ && writes.size() > 1) {
88✔
786
                    continue;
6✔
787
                }
788

789
                // Determine moving symbols locally
790
                std::unordered_set<std::string> moving_symbols_local = moving_symbols;
82✔
791

792
                auto subsets = write->subsets();
82✔
793
                auto subsets_ = write_->subsets();
82✔
794
                for (auto& subset : subsets) {
148✔
795
                    for (auto& subset_ : subsets_) {
148✔
796
                        if (!this->disjoint(subset, subset_, indvar->get_name(),
82✔
797
                                            moving_symbols_local, assumptions)) {
798
                            ww_conflict = true;
16✔
799
                            break;
16✔
800
                        }
801
                    }
802
                    if (ww_conflict) {
82✔
803
                        break;
16✔
804
                    }
805
                }
806
                if (ww_conflict) {
82✔
807
                    break;
16✔
808
                }
809
            }
82✔
810
            if (ww_conflict) {
76✔
811
                break;
16✔
812
            }
813
        }
814
        if (ww_conflict) {
72✔
815
            result.insert({container, Parallelism::DEPENDENT});
16✔
816
            continue;
16✔
817
        }
818

819
        bool rw_conflict = false;
56✔
820
        auto reads = body_users.reads(container);
56✔
821
        for (auto& read : reads) {
102✔
822
            for (auto& write_ : writes) {
103✔
823
                // Determine moving symbols locally
824
                std::unordered_set<std::string> moving_symbols_local = moving_symbols;
57✔
825

826
                auto subsets = read->subsets();
57✔
827
                auto subsets_ = write_->subsets();
57✔
828
                for (auto& subset : subsets) {
119✔
829
                    for (auto& subset_ : subsets_) {
127✔
830
                        if (!this->disjoint(subset, subset_, indvar->get_name(),
65✔
831
                                            moving_symbols_local, assumptions)) {
832
                            rw_conflict = true;
3✔
833
                            break;
3✔
834
                        }
835
                    }
836
                    if (rw_conflict) {
65✔
837
                        break;
3✔
838
                    }
839
                }
840
                if (rw_conflict) {
57✔
841
                    break;
3✔
842
                }
843
            }
57✔
844
            if (rw_conflict) {
49✔
845
                break;
3✔
846
            }
847
        }
848
        if (rw_conflict) {
56✔
849
            result.insert({container, Parallelism::DEPENDENT});
3✔
850
            continue;
3✔
851
        }
852

853
        result.insert({container, Parallelism::PARALLEL});
53✔
854
    }
72✔
855

856
    // 4. Reductions
857
    for (auto& entry : result) {
356✔
858
        auto& container = entry.first;
287✔
859
        auto& dep_type = entry.second;
287✔
860
        if (dep_type != Parallelism::DEPENDENT) {
287✔
861
            continue;
268✔
862
        }
863

864
        // Check if it is a reduction
865
        auto reads = body_users.reads(container);
19✔
866
        auto writes = body_users.writes(container);
19✔
867

868
        // Criterion: Must write to constant location
869
        bool is_reduction = true;
19✔
870
        auto first_write = writes.at(0);
19✔
871
        auto first_subset = first_write->subsets().at(0);
19✔
872
        for (auto& dim : first_subset) {
32✔
873
            for (auto& sym : moving_symbols) {
42✔
874
                if (symbolic::uses(dim, sym)) {
29✔
875
                    is_reduction = false;
9✔
876
                    break;
9✔
877
                }
878
            }
879
            if (!is_reduction) {
22✔
880
                break;
9✔
881
            }
882
        }
883
        if (!is_reduction) {
19✔
884
            continue;
9✔
885
        }
886

887
        // Criterion: All writes must have the same subset
888
        for (auto& write : writes) {
20✔
889
            for (auto& subset : write->subsets()) {
20✔
890
                if (subset.size() != first_subset.size()) {
10✔
891
                    is_reduction = false;
×
892
                    break;
×
893
                }
894

895
                for (size_t i = 0; i < subset.size(); i++) {
22✔
896
                    if (!symbolic::eq(subset[i], first_subset[i])) {
12✔
897
                        is_reduction = false;
×
898
                        break;
×
899
                    }
900
                }
12✔
901
            }
902
        }
903
        if (!is_reduction) {
10✔
904
            continue;
×
905
        }
906

907
        // Criterion: All reads must have the same subset
908
        for (auto& read : reads) {
18✔
909
            for (auto& subset : read->subsets()) {
16✔
910
                if (subset.size() != first_subset.size()) {
8✔
911
                    is_reduction = false;
×
912
                    break;
×
913
                }
914

915
                for (size_t i = 0; i < subset.size(); i++) {
19✔
916
                    if (!symbolic::eq(subset[i], first_subset[i])) {
11✔
917
                        is_reduction = false;
×
918
                        break;
×
919
                    }
920
                }
11✔
921
            }
922
        }
923

924
        if (is_reduction) {
10✔
925
            result[container] = Parallelism::REDUCTION;
10✔
926
        }
10✔
927
    }
19✔
928
};
70✔
929

930
void DataParallelismAnalysis::run(analysis::AnalysisManager& analysis_manager) {
41✔
931
    this->loops_.clear();
41✔
932
    this->results_.clear();
41✔
933

934
    // Collect all for loops
935
    std::list<const structured_control_flow::ControlFlowNode*> queue = {&sdfg_.root()};
41✔
936
    while (!queue.empty()) {
291✔
937
        auto current = queue.front();
250✔
938
        queue.pop_front();
250✔
939

940
        if (auto sequence_stmt = dynamic_cast<const structured_control_flow::Sequence*>(current)) {
250✔
941
            for (size_t i = 0; i < sequence_stmt->size(); i++) {
250✔
942
                queue.push_back(&sequence_stmt->at(i).first);
139✔
943
            }
139✔
944
        } else if (auto if_else_stmt =
250✔
945
                       dynamic_cast<const structured_control_flow::IfElse*>(current)) {
139✔
946
            for (size_t i = 0; i < if_else_stmt->size(); i++) {
×
947
                queue.push_back(&if_else_stmt->at(i).first);
×
948
            }
×
949
        } else if (auto while_stmt = dynamic_cast<const structured_control_flow::While*>(current)) {
139✔
950
            queue.push_back(&while_stmt->root());
×
951
        } else if (auto sloop_stmt =
139✔
952
                       dynamic_cast<const structured_control_flow::StructuredLoop*>(current)) {
139✔
953
            this->loops_.insert(sloop_stmt);
70✔
954
            queue.push_back(&sloop_stmt->root());
70✔
955
        }
70✔
956
    }
957

958
    // Classify each loop
959
    for (auto& entry : this->loops_) {
111✔
960
        this->classify(analysis_manager, entry);
70✔
961
    }
962
};
41✔
963

964
DataParallelismAnalysis::DataParallelismAnalysis(StructuredSDFG& sdfg)
82✔
965
    : Analysis(sdfg) {
41✔
966

967
      };
41✔
968

969
const DataParallelismAnalysisResult& DataParallelismAnalysis::get(
53✔
970
    const structured_control_flow::StructuredLoop& loop) const {
971
    return this->results_.at(&loop);
53✔
972
};
973

974
bool DataParallelismAnalysis::is_contiguous(const structured_control_flow::StructuredLoop& loop) {
3✔
975
    return symbolic::contiguity(loop.update(), loop.indvar());
3✔
976
};
977

NEW
978
bool DataParallelismAnalysis::is_strictly_monotonic(
×
979
    const structured_control_flow::StructuredLoop& loop) {
UNCOV
980
    return symbolic::strict_monotonicity(loop.update(), loop.indvar()) == symbolic::Sign::POSITIVE;
×
981
};
982

983
symbolic::Expression DataParallelismAnalysis::bound(
2✔
984
    const structured_control_flow::StructuredLoop& loop) {
985
    auto& indvar = loop.indvar();
2✔
986
    auto& condition = loop.condition();
2✔
987
    auto args = condition->get_args();
2✔
988
    if (args.size() != 2) {
2✔
989
        return SymEngine::null;
×
990
    }
991
    auto& arg0 = args[0];
2✔
992
    auto& arg1 = args[1];
2✔
993
    if (SymEngine::eq(*arg0, *indvar)) {
2✔
994
        return arg1;
2✔
995
    } else if (SymEngine::eq(*arg1, *indvar)) {
×
996
        return arg0;
×
997
    }
998
    return SymEngine::null;
×
999
};
2✔
1000

1001
}  // namespace analysis
1002
}  // 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