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

daisytuner / sdfglib / 15340968114

30 May 2025 06:47AM UTC coverage: 58.553% (+0.2%) from 58.324%
15340968114

push

github

web-flow
Add parallel Map node

* Introduce Map data structure

* Prepare infrastructure

* implement analysis support

* Add basic infrastructure

* visualizer/serializer

* include fix

* update from main

* remove default

* happens before test + fixes

* builder test

* dispatcher test

* visitor, copy and serializer tests

* for2map structures

* for2map conversion draft

* add tests

* Bug fixes

* small updates from feedback

* Visitor style pass implementation

* cleanup

* fixes linting errors

---------

Co-authored-by: Lukas Truemper <lukas.truemper@outlook.de>

258 of 381 new or added lines in 26 files covered. (67.72%)

17 existing lines in 14 files now uncovered.

8184 of 13977 relevant lines covered (58.55%)

109.83 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 "sdfg/codegen/language_extensions/cpp_language_extension.h"
4

5
namespace sdfg {
6
namespace analysis {
7

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

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

72
    return {subset1_new, subset2_new};
151✔
73
};
151✔
74

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

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

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

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

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

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

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

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

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

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

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

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

275
    return {subset1_new, subset2_new};
153✔
276
};
153✔
277

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

286
    codegen::CPPLanguageExtension language_extension;
150✔
287

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

294
    // Attempt to delinearize subsets
295
    auto [subset1_2, subset2_2] =
450✔
296
        DataParallelismAnalysis::delinearization(subset1_, subset2_, moving_symbols, assumptions);
150✔
297

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

346
    // Collect parameters and dimensions
347
    std::unordered_set<std::string> dimensions_;
150✔
348
    std::unordered_set<std::string> parameters_;
150✔
349
    for (auto& dim : subset1_new) {
374✔
350
        for (auto& atom : symbolic::atoms(dim)) {
490✔
351
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
266✔
352
            if (sym->get_name() == indvar) {
266✔
353
                continue;
118✔
354
            }
355

356
            if (std::find(substitions.begin(), substitions.end(), sym->get_name()) !=
148✔
357
                substitions.end()) {
148✔
358
                continue;
38✔
359
            }
360
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
110✔
361
                parameters_.insert(sym->get_name());
40✔
362
            } else {
40✔
363
                dimensions_.insert(sym->get_name());
70✔
364
            }
365
        }
266✔
366
    }
367
    for (auto& dim : subset2_new) {
374✔
368
        for (auto& atom : symbolic::atoms(dim)) {
491✔
369
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
267✔
370
            if (sym->get_name() == indvar) {
267✔
371
                continue;
118✔
372
            }
373

374
            if (std::find(substitions.begin(), substitions.end(), sym->get_name()) !=
149✔
375
                substitions.end()) {
149✔
376
                continue;
38✔
377
            }
378
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
111✔
379
                parameters_.insert(sym->get_name());
41✔
380
            } else {
41✔
381
                dimensions_.insert(sym->get_name());
70✔
382
            }
383
        }
267✔
384
    }
385
    dimensions_.insert(indvar);
150✔
386
    std::vector<std::string> dimensions;
150✔
387
    for (auto& dim : dimensions_) {
370✔
388
        dimensions.push_back(dim);
220✔
389
    }
390
    for (auto mv : moving_symbols) {
768✔
391
        if (dimensions_.find(mv) == dimensions_.end()) {
618✔
392
            dimensions.push_back(mv);
398✔
393
            dimensions_.insert(mv);
398✔
394
        }
398✔
395
    }
618✔
396
    std::vector<std::string> parameters;
150✔
397
    for (auto& dim : parameters_) {
191✔
398
        parameters.push_back(dim);
41✔
399
    }
400

401
    // Double dimension space and constraints
402
    size_t k = 0;
150✔
403
    std::vector<std::string> doubled_dimensions;
150✔
404
    std::vector<std::string> constraints;
150✔
405
    for (auto& dim : dimensions) {
768✔
406
        doubled_dimensions.push_back(dim + "_1");
618✔
407
        doubled_dimensions.push_back(dim + "_2");
618✔
408

409
        // Proof: dim_1 != dim_2
410
        if (dim == indvar) {
618✔
411
            constraints.push_back(dim + "_1 != " + dim + "_2");
150✔
412
        }
150✔
413

414
        // Collect lb and ub
415
        auto lb1 = assumptions.at(symbolic::symbol(dim)).lower_bound();
618✔
416
        for (auto atom : symbolic::atoms(lb1)) {
701✔
417
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
83✔
418
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
83✔
419
                if (parameters_.find(sym->get_name()) == parameters_.end()) {
28✔
420
                    parameters_.insert(sym->get_name());
23✔
421
                    parameters.push_back(sym->get_name());
23✔
422
                }
23✔
423
            }
28✔
424
        }
83✔
425
        auto ub1 = assumptions.at(symbolic::symbol(dim)).upper_bound();
618✔
426
        for (auto atom : symbolic::atoms(ub1)) {
971✔
427
            auto sym = SymEngine::rcp_static_cast<const SymEngine::Symbol>(atom);
353✔
428
            if (moving_symbols.find(sym->get_name()) == moving_symbols.end()) {
353✔
429
                if (parameters_.find(sym->get_name()) == parameters_.end()) {
302✔
430
                    parameters_.insert(sym->get_name());
212✔
431
                    parameters.push_back(sym->get_name());
212✔
432
                }
212✔
433
            }
302✔
434
        }
353✔
435

436
        // Add constraints
437
        auto lb2 = lb1;
618✔
438
        auto ub2 = ub1;
618✔
439
        for (auto& dim : dimensions) {
4,386✔
440
            lb1 = symbolic::subs(lb1, symbolic::symbol(dim), symbolic::symbol(dim + "_1"));
3,768✔
441
            ub1 = symbolic::subs(ub1, symbolic::symbol(dim), symbolic::symbol(dim + "_1"));
3,768✔
442
            lb2 = symbolic::subs(lb2, symbolic::symbol(dim), symbolic::symbol(dim + "_2"));
3,768✔
443
            ub2 = symbolic::subs(ub2, symbolic::symbol(dim), symbolic::symbol(dim + "_2"));
3,768✔
444
        }
445

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

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

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

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

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

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

521
        std::string new_k = "__daisy_iterator" + std::to_string(k++);
195✔
522

523
        std::string k_1 = new_k + "_1";
195✔
524
        constraints.push_back("exists " + k_1 + " : " + dim +
390✔
525
                              "_1 = " + language_extension.expression(lb1) + " + " + k_1 + " * " +
390✔
526
                              language_extension.expression(arg1));
195✔
527

528
        std::string k_2 = new_k + "_2";
195✔
529
        constraints.push_back("exists " + k_2 + " : " + dim +
390✔
530
                              "_2 = " + language_extension.expression(lb1) + " + " + k_2 + " * " +
390✔
531
                              language_extension.expression(arg1));
195✔
532
    }
618✔
533

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

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

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

599
    // Allocate context
600
    isl_ctx* ctx = isl_ctx_alloc();
150✔
601

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

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

661
    isl_map* index_map_1 = isl_map_read_from_str(ctx, map_1.c_str());
150✔
662
    if (!index_map_1) {
150✔
663
        isl_ctx_free(ctx);
×
664
        return false;
×
665
    }
666

667
    isl_map* index_map_2 = isl_map_read_from_str(ctx, map_2.c_str());
150✔
668
    if (!index_map_2) {
150✔
669
        isl_map_free(index_map_1);
×
670
        isl_ctx_free(ctx);
×
671
        return false;
×
672
    }
673

674
    isl_map* intersection = isl_map_intersect(index_map_1, index_map_2);
150✔
675
    if (!intersection) {
150✔
676
        isl_map_free(index_map_1);
×
677
        isl_map_free(index_map_2);
×
678
        isl_ctx_free(ctx);
×
679
        return false;
×
680
    }
681

682
    bool disjoint = isl_map_is_empty(intersection);
150✔
683

684
    isl_map_free(intersection);
150✔
685
    isl_ctx_free(ctx);
150✔
686

687
    return disjoint;
150✔
688
};
150✔
689

690
void DataParallelismAnalysis::classify(analysis::AnalysisManager& analysis_manager,
73✔
691
                                       const structured_control_flow::For* loop) {
692
    // Strictly monotonic update
693
    auto& indvar = loop->indvar();
73✔
694
    auto& update = loop->update();
73✔
695
    if (symbolic::strict_monotonicity(update, indvar) != symbolic::Sign::POSITIVE) {
73✔
696
        this->results_.insert({loop, DataParallelismAnalysisResult()});
×
697
        return;
×
698
    }
699

700
    // Users analysis
701
    auto& body = loop->root();
73✔
702
    auto& users = analysis_manager.get<analysis::Users>();
73✔
703
    analysis::UsersView body_users(users, body);
73✔
704
    if (!body_users.views().empty() || !body_users.moves().empty()) {
73✔
705
        this->results_.insert({loop, DataParallelismAnalysisResult()});
×
706
        return;
×
707
    }
708

709
    this->results_.insert({loop, DataParallelismAnalysisResult()});
73✔
710
    auto& result = this->results_.at(loop);
73✔
711

712
    // Assumptions analysis
713
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
73✔
714
    auto assumptions = assumptions_analysis.get(body);
73✔
715

716
    // For each container, we now classify the access pattern
717

718
    // 1. Identify private containers
719
    auto locals = users.locals(sdfg_, body);
73✔
720
    for (auto& local : locals) {
180✔
721
        result.insert({local, Parallelism::PRIVATE});
107✔
722
    }
723

724
    // 2. Filter our read-only containers
725
    std::unordered_set<std::string> writeset;
73✔
726
    std::unordered_set<std::string> moving_symbols = {indvar->get_name()};
73✔
727
    for (auto& entry : body_users.writes()) {
311✔
728
        writeset.insert(entry->container());
238✔
729

730
        auto& type = sdfg_.type(entry->container());
238✔
731
        if (!dynamic_cast<const types::Scalar*>(&type)) {
238✔
732
            continue;
87✔
733
        }
734
        if (!types::is_integer(type.primitive_type())) {
151✔
735
            continue;
21✔
736
        }
737
        moving_symbols.insert(entry->container());
130✔
738
    }
739
    for (auto& entry : body_users.reads()) {
885✔
740
        if (writeset.find(entry->container()) != writeset.end()) {
812✔
741
            // Loop-carried dependencies in tasklets's conditions
742
            if (dynamic_cast<data_flow::Tasklet*>(entry->element())) {
335✔
743
                // Locals cannot be loop-carried
744
                if (locals.find(entry->container()) != locals.end()) {
6✔
745
                    continue;
5✔
746
                } else {
747
                    result.clear();
1✔
748
                    return;
1✔
749
                }
750
            }
751

752
            continue;
329✔
753
        }
754
        if (entry->container() == indvar->get_name()) {
477✔
755
            continue;
178✔
756
        }
757
        result.insert({entry->container(), Parallelism::READONLY});
299✔
758
    }
759

760
    // 3. Prove parallelism
761
    /** For each container of the writeset, we check the following properties:
762
     *  1. For all i, writes are disjoint (false -> empty parallelism)
763
     *  2. For all i, reads and writes are disjoint (false -> empty parallelism)
764
     */
765
    for (auto& container : writeset) {
233✔
766
        // Skip if already classified
767
        if (result.find(container) != result.end()) {
161✔
768
            continue;
86✔
769
        }
770

771
        // For each i1, i2, subset(i1) and subset(i2) are disjoint
772
        bool ww_conflict = false;
75✔
773
        auto writes = body_users.writes(container);
75✔
774
        for (auto& write : writes) {
136✔
775
            for (auto& write_ : writes) {
152✔
776
                if (write == write_ && writes.size() > 1) {
91✔
777
                    continue;
6✔
778
                }
779

780
                // Determine moving symbols locally
781
                std::unordered_set<std::string> moving_symbols_local = moving_symbols;
85✔
782

783
                auto subsets = write->subsets();
85✔
784
                auto subsets_ = write_->subsets();
85✔
785
                for (auto& subset : subsets) {
152✔
786
                    for (auto& subset_ : subsets_) {
152✔
787
                        if (!this->disjoint(subset, subset_, indvar->get_name(),
85✔
788
                                            moving_symbols_local, assumptions)) {
789
                            ww_conflict = true;
18✔
790
                            break;
18✔
791
                        }
792
                    }
793
                    if (ww_conflict) {
85✔
794
                        break;
18✔
795
                    }
796
                }
797
                if (ww_conflict) {
85✔
798
                    break;
18✔
799
                }
800
            }
85✔
801
            if (ww_conflict) {
79✔
802
                break;
18✔
803
            }
804
        }
805
        if (ww_conflict) {
75✔
806
            result.insert({container, Parallelism::DEPENDENT});
18✔
807
            continue;
18✔
808
        }
809

810
        bool rw_conflict = false;
57✔
811
        auto reads = body_users.reads(container);
57✔
812
        for (auto& read : reads) {
103✔
813
            for (auto& write_ : writes) {
103✔
814
                // Determine moving symbols locally
815
                std::unordered_set<std::string> moving_symbols_local = moving_symbols;
57✔
816

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

844
        result.insert({container, Parallelism::PARALLEL});
54✔
845
    }
75✔
846

847
    // 4. Reductions
848
    for (auto& entry : result) {
380✔
849
        auto& container = entry.first;
308✔
850
        auto& dep_type = entry.second;
308✔
851
        if (dep_type != Parallelism::DEPENDENT) {
308✔
852
            continue;
287✔
853
        }
854

855
        // Check if it is a reduction
856
        auto reads = body_users.reads(container);
21✔
857
        auto writes = body_users.writes(container);
21✔
858

859
        // Criterion: Must write to constant location
860
        bool is_reduction = true;
21✔
861
        auto first_write = writes.at(0);
21✔
862
        auto first_subset = first_write->subsets().at(0);
21✔
863
        for (auto& dim : first_subset) {
36✔
864
            for (auto& sym : moving_symbols) {
48✔
865
                if (symbolic::uses(dim, sym)) {
33✔
866
                    is_reduction = false;
10✔
867
                    break;
10✔
868
                }
869
            }
870
            if (!is_reduction) {
25✔
871
                break;
10✔
872
            }
873
        }
874
        if (!is_reduction) {
21✔
875
            continue;
10✔
876
        }
877

878
        // Criterion: All writes must have the same subset
879
        for (auto& write : writes) {
22✔
880
            for (auto& subset : write->subsets()) {
22✔
881
                if (subset.size() != first_subset.size()) {
11✔
882
                    is_reduction = false;
×
883
                    break;
×
884
                }
885

886
                for (size_t i = 0; i < subset.size(); i++) {
25✔
887
                    if (!symbolic::eq(subset[i], first_subset[i])) {
14✔
888
                        is_reduction = false;
×
889
                        break;
×
890
                    }
891
                }
14✔
892
            }
893
        }
894
        if (!is_reduction) {
11✔
895
            continue;
×
896
        }
897

898
        // Criterion: All reads must have the same subset
899
        for (auto& read : reads) {
19✔
900
            for (auto& subset : read->subsets()) {
16✔
901
                if (subset.size() != first_subset.size()) {
8✔
902
                    is_reduction = false;
×
903
                    break;
×
904
                }
905

906
                for (size_t i = 0; i < subset.size(); i++) {
19✔
907
                    if (!symbolic::eq(subset[i], first_subset[i])) {
11✔
908
                        is_reduction = false;
×
909
                        break;
×
910
                    }
911
                }
11✔
912
            }
913
        }
914

915
        if (is_reduction) {
11✔
916
            result[container] = Parallelism::REDUCTION;
11✔
917
        }
11✔
918
    }
21✔
919
};
73✔
920

921
void DataParallelismAnalysis::run(analysis::AnalysisManager& analysis_manager) {
42✔
922
    this->loops_.clear();
42✔
923
    this->results_.clear();
42✔
924

925
    // Collect all for loops
926
    std::list<const structured_control_flow::ControlFlowNode*> queue = {&sdfg_.root()};
42✔
927
    while (!queue.empty()) {
312✔
928
        auto current = queue.front();
270✔
929
        queue.pop_front();
270✔
930

931
        if (auto sequence_stmt = dynamic_cast<const structured_control_flow::Sequence*>(current)) {
270✔
932
            for (size_t i = 0; i < sequence_stmt->size(); i++) {
270✔
933
                queue.push_back(&sequence_stmt->at(i).first);
150✔
934
            }
150✔
935
        } else if (auto if_else_stmt =
270✔
936
                       dynamic_cast<const structured_control_flow::IfElse*>(current)) {
150✔
937
            for (size_t i = 0; i < if_else_stmt->size(); i++) {
×
938
                queue.push_back(&if_else_stmt->at(i).first);
×
939
            }
×
940
        } else if (auto while_stmt = dynamic_cast<const structured_control_flow::While*>(current)) {
150✔
941
            queue.push_back(&while_stmt->root());
×
942
        } else if (auto for_stmt = dynamic_cast<const structured_control_flow::For*>(current)) {
150✔
943
            this->loops_.insert(for_stmt);
73✔
944
            queue.push_back(&for_stmt->root());
73✔
945
        } else if (auto kern_stmt = dynamic_cast<const structured_control_flow::Kernel*>(current)) {
150✔
946
            // this->loops_.insert(kern_stmt);
947
            queue.push_back(&kern_stmt->root());
5✔
948
        } else if (auto map_stmt = dynamic_cast<const structured_control_flow::Map*>(current)) {
77✔
NEW
949
            queue.push_back(&map_stmt->root());
×
UNCOV
950
        }
×
951
    }
952

953
    // Classify each loop
954
    for (auto& entry : this->loops_) {
115✔
955
        this->classify(analysis_manager, entry);
73✔
956
    }
957
};
42✔
958

959
DataParallelismAnalysis::DataParallelismAnalysis(StructuredSDFG& sdfg)
84✔
960
    : Analysis(sdfg) {
42✔
961

962
      };
42✔
963

964
const DataParallelismAnalysisResult& DataParallelismAnalysis::get(
54✔
965
    const structured_control_flow::For& loop) const {
966
    return this->results_.at(&loop);
54✔
967
};
968

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

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

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

994
}  // namespace analysis
995
}  // 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

© 2025 Coveralls, Inc