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

daisytuner / sdfglib / 15494080064

06 Jun 2025 03:26PM UTC coverage: 57.304% (-0.4%) from 57.704%
15494080064

Pull #60

github

web-flow
Merge d1409e43e into 1878f29f7
Pull Request #60: removes kernel node in favor of function types

78 of 99 new or added lines in 11 files covered. (78.79%)

91 existing lines in 14 files now uncovered.

7583 of 13233 relevant lines covered (57.3%)

116.04 hits per line

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

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

3
#include <regex>
4

5
#include "sdfg/codegen/language_extensions/cpp_language_extension.h"
6

7
namespace sdfg {
8
namespace analysis {
9

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

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

74
    return {subset1_new, subset2_new};
148✔
75
};
148✔
76

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

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

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

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

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

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

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

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

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

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

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

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

277
    return {subset1_new, subset2_new};
150✔
278
};
150✔
279

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

288
    codegen::CPPLanguageExtension language_extension;
147✔
289

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

523
        std::string new_k = "__daisy_iterator" + std::to_string(k++);
194✔
524

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

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

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

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

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

601
    // Allocate context
602
    isl_ctx* ctx = isl_ctx_alloc();
147✔
603

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

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

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

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

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

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

688
    bool disjoint = isl_map_is_empty(intersection);
147✔
689

690
    isl_map_free(intersection);
147✔
691
    isl_ctx_free(ctx);
147✔
692

693
    return disjoint;
147✔
694
};
147✔
695

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

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

715
    this->results_.insert({loop, DataParallelismAnalysisResult()});
70✔
716
    auto& result = this->results_.at(loop);
70✔
717

718
    // Assumptions analysis
719
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
70✔
720
    auto assumptions = assumptions_analysis.get(body);
70✔
721

722
    // For each container, we now classify the access pattern
723

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

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

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

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

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

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

786
                // Determine moving symbols locally
787
                std::unordered_set<std::string> moving_symbols_local = moving_symbols;
82✔
788

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

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

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

850
        result.insert({container, Parallelism::PARALLEL});
53✔
851
    }
72✔
852

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

861
        // Check if it is a reduction
862
        auto reads = body_users.reads(container);
19✔
863
        auto writes = body_users.writes(container);
19✔
864

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

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

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

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

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

921
        if (is_reduction) {
10✔
922
            result[container] = Parallelism::REDUCTION;
10✔
923
        }
10✔
924
    }
19✔
925
};
70✔
926

927
void DataParallelismAnalysis::run(analysis::AnalysisManager& analysis_manager) {
41✔
928
    this->loops_.clear();
41✔
929
    this->results_.clear();
41✔
930

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

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

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

962
DataParallelismAnalysis::DataParallelismAnalysis(StructuredSDFG& sdfg)
82✔
963
    : Analysis(sdfg) {
41✔
964

965
      };
41✔
966

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

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

976
bool DataParallelismAnalysis::is_strictly_monotonic(const structured_control_flow::For& loop) {
×
977
    return symbolic::strict_monotonicity(loop.update(), loop.indvar()) == symbolic::Sign::POSITIVE;
×
978
};
979

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

997
}  // namespace analysis
998
}  // 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