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

daisytuner / sdfglib / 15342647640

30 May 2025 08:25AM UTC coverage: 58.573% (+0.02%) from 58.553%
15342647640

Pull #44

github

web-flow
Merge 003852900 into ad9a83c5a
Pull Request #44: Add Structured Loops

52 of 103 new or added lines in 20 files covered. (50.49%)

2 existing lines in 2 files now uncovered.

8212 of 14020 relevant lines covered (58.57%)

109.72 hits per line

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

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

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

8
namespace sdfg {
9
namespace analysis {
10

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

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

75
    return {subset1_new, subset2_new};
151✔
76
};
151✔
77

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

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

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

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

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

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

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

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

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

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

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

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

278
    return {subset1_new, subset2_new};
153✔
279
};
153✔
280

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

289
    codegen::CPPLanguageExtension language_extension;
150✔
290

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

524
        std::string new_k = "__daisy_iterator" + std::to_string(k++);
195✔
525

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

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

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

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

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

602
    // Allocate context
603
    isl_ctx* ctx = isl_ctx_alloc();
150✔
604

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

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

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

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

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

685
    bool disjoint = isl_map_is_empty(intersection);
150✔
686

687
    isl_map_free(intersection);
150✔
688
    isl_ctx_free(ctx);
150✔
689

690
    return disjoint;
150✔
691
};
150✔
692

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

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

712
    this->results_.insert({loop, DataParallelismAnalysisResult()});
73✔
713
    auto& result = this->results_.at(loop);
73✔
714

715
    // Assumptions analysis
716
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
73✔
717
    auto assumptions = assumptions_analysis.get(body);
73✔
718

719
    // For each container, we now classify the access pattern
720

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

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

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

755
            continue;
329✔
756
        }
757
        if (entry->container() == indvar->get_name()) {
478✔
758
            continue;
179✔
759
        }
760
        result.insert({entry->container(), Parallelism::READONLY});
299✔
761
    }
762

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

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

783
                // Determine moving symbols locally
784
                std::unordered_set<std::string> moving_symbols_local = moving_symbols;
85✔
785

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

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

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

847
        result.insert({container, Parallelism::PARALLEL});
54✔
848
    }
75✔
849

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

858
        // Check if it is a reduction
859
        auto reads = body_users.reads(container);
21✔
860
        auto writes = body_users.writes(container);
21✔
861

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

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

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

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

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

918
        if (is_reduction) {
11✔
919
            result[container] = Parallelism::REDUCTION;
11✔
920
        }
11✔
921
    }
21✔
922
};
73✔
923

924
void DataParallelismAnalysis::run(analysis::AnalysisManager& analysis_manager) {
42✔
925
    this->loops_.clear();
42✔
926
    this->results_.clear();
42✔
927

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

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

957
    // Classify each loop
958
    for (auto& entry : this->loops_) {
115✔
959
        this->classify(analysis_manager, entry);
73✔
960
    }
961
};
42✔
962

963
DataParallelismAnalysis::DataParallelismAnalysis(StructuredSDFG& sdfg)
84✔
964
    : Analysis(sdfg) {
42✔
965

966
      };
42✔
967

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

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

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

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

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